SSCMS是开源的net项目,可以自由建站。按照官方原文说法:SSCMS 是一款跨平台、可分布式部署的产品,产品能够运行在各类操作系统之上,同时支持 MySql、SqlServer、PostgreSql以及本地SQLite数据库。
以前了解过,并用它搭建了一个网站,但是没有仔细去看它的代码。今天花了一天时间看了一下。把自己看明白的记录一下。
前端还是用web页面,但是内容是vue。而且做了分离,就是html一个文件,操作方法逻辑一个对应js 文件。
然后是控制器,这个使用了部分类,分成了两个文件,与其他项目一个文件相比,它这个确实增加了复杂度。
我列举一下文件。那频道channels 这个来说吧。 view 还是 channels.cshtml
代码
@page
@{ Layout = "_Layout"; }
@section Styles{
<link rel="stylesheet" href="/sitefiles/assets/lib/ueditor/third-party/custom/custom.css">
<style>
.commands {
text-align: right;
padding-right: 10px;
}
</style>
}
<el-row>
<el-col :span="20">
<el-form v-on:submit.native.prevent size="mini" :inline="true">
<el-form-item label="关键字">
<el-input v-model="filterText" placeholder="请输入栏目名称/栏目Id"></el-input>
</el-form-item>
<el-form-item label="栏目索引" v-if="indexNames && indexNames.length > 0">
<el-select v-model="filterIndexName" placeholder="选择栏目索引进行过滤">
<el-option label="<无筛选>" value=""></el-option>
<el-option v-for="indexName in indexNames" :key="indexName" :label="indexName" :value="indexName"></el-option>
</el-select>
</el-form-item>
<el-form-item label="栏目组" v-if="groupNames && groupNames.length > 0">
<el-select v-model="filterGroupName" placeholder="选择栏目组进行过滤">
<el-option label="<无筛选>" value=""></el-option>
<el-option v-for="groupName in groupNames" :key="groupName" :label="groupName" :value="groupName"></el-option>
</el-select>
</el-form-item>
</el-form>
</el-col>
<el-col :span="4" align="right">
<el-popover
id="sortColumns"
placement="top"
width="360"
trigger="click">
<div>
设置显示列
</div>
<el-table
:data="columns"
size="mini"
style="width: 100%; overflow: auto; max-height: 500px !important">
<el-table-column prop="displayName"></el-table-column>
<el-table-column prop="attributeName"></el-table-column>
<el-table-column align="right" width="80">
<template slot-scope="scope">
<el-switch
v-model="scope.row.isList"
:disabled="scope.row.attributeName === 'ChannelName'"
v-on:change="handleColumnsChange"
size="mini">
</el-switch>
</template>
</el-table-column>
</el-table>
<div slot="reference" class="el-dropdown">
<span style="cursor: pointer;">
显示列<i class="el-icon-arrow-down el-icon--right"></i>
</span>
</div>
</el-popover>
</el-col>
</el-row>
<div class="el-table el-table--fit el-table--enable-row-hover el-table--enable-row-transition" style="width: 100%;">
<div class="hidden-columns">
<div></div>
<div
v-for="column in columns"
v-if="column.isList && column.attributeName !== 'ChannelName'"
:key="column.attributeName">
</div>
<div></div>
</div>
<div class="el-table__header-wrapper">
<table cellspacing="0" cellpadding="0" border="0" class="el-table__header" style="width: 100%;">
<colgroup>
<col>
<col
v-for="column in columns"
v-if="column.isList && column.attributeName !== 'ChannelName'"
:key="column.attributeName"
:width="getColumnWidth(column.attributeName)">
<col :width="commandsWidth">
</colgroup>
<thead class="has-gutter">
<tr class="">
<th colspan="1" rowspan="1" class="is-leaf">
<div class="cell">栏目名称(提示:可以对栏目进行拖拽操作)</div>
</th>
<th
v-for="column in columns"
v-if="column.isList && column.attributeName !== 'ChannelName'"
:key="column.attributeName"
colspan="1"
rowspan="1"
class="is-leaf">
<div class="cell">{{ column.displayName }}</div>
</th>
<th colspan="1" rowspan="1" class="is-leaf">
<div class="cell"></div>
</th>
</tr>
</thead>
</table>
</div>
<div class="el-table__column-resize-proxy" style="display: none;"></div>
</div>
<el-tree
ref="tree"
class="tree"
node-key="value"
draggable
show-checkbox
check-strictly
highlight-current
:data="root"
:default-expanded-keys="expandedChannelIds"
:filter-node-method="filterNode"
:allow-drop="allowDrop"
:allow-drag="allowDrag"
v-on:node-drop="handleDrop"
v-on:check-change="handleCheckChange"
>
<div class="el-table__body-wrapper is-scrolling-none" slot-scope="{ node, data }">
<table v-on:dblclick="btnEditClick(data)" v-on:click.ctrl.stop="btnCheckClick(data)" cellspacing="0" cellpadding="0" border="0" class="el-table__body" style="width: 100%;">
<colgroup>
<col>
<col
v-for="column in columns"
v-if="column.isList && column.attributeName !== 'ChannelName'"
:key="column.attributeName"
:width="getColumnWidth(column.attributeName)">
<col :width="commandsWidth">
</colgroup>
<tbody>
<tr class="el-table__row">
<td rowspan="1" colspan="1">
<div class="cell">
<el-link :underline="false" :href="getChannelUrl(data)" v-on:click.stop.native target="_blank">
<i class="el-icon-link"></i>
</el-link>
{{ data.label }} ({{ data.count }})
<!-- imageUrl -->
<el-popover
v-if="data.imageUrl"
width="400"
trigger="hover">
<el-image :src="data.imageUrl"></el-image>
<el-link slot="reference" :underline="false">
<i class="el-icon-picture-outline"></i>
</el-link>
</el-popover>
<!-- linkType && linkUrl -->
<el-tooltip v-if="data.channel.linkType != 'None' || data.channel.linkUrl" effect="light" content="指定链接" placement="top">
<i class="fa fa-external-link"></i>
</el-tooltip>
</div>
</td>
<td
v-for="column in columns"
v-if="column.isList && column.attributeName !== 'ChannelName'"
:key="column.attributeName"
rowspan="1"
colspan="1">
<div class="cell">
<template v-if="column.attributeName === 'IndexName'">
<el-tag size="mini" v-if="data.channel.indexName">
{{ data.channel.indexName }}
</el-tag>
</template>
<template v-else-if="column.attributeName === 'GroupNames'">
<template v-if="data.groupNames && groupNames" v-for="groupName in data.groupNames">
<el-tag v-if="groupNames.indexOf(groupName) !== -1" :key="groupName" size="mini" type="info">
{{ groupName }}
</el-tag>
</template>
</template>
<template v-else-if="column.attributeName === 'ChannelTemplateId'">
<el-link
v-if="isTemplateEditable && data.channel.channelTemplateId > 0"
:underline="false"
:href="getTemplateEditorUrl(true, data.channel.channelTemplateId)"
target="_blank"
type="primary">
{{ getTemplate(true, data.channel.channelTemplateId).templateName }}
</el-link>
</template>
<template v-else-if="column.attributeName === 'ContentTemplateId'">
<el-link
v-if="isTemplateEditable && data.channel.contentTemplateId > 0"
:underline="false"
:href="getTemplateEditorUrl(false, data.channel.contentTemplateId)"
target="_blank"
type="primary">
{{ getTemplate(false, data.channel.contentTemplateId).templateName }}
</el-link>
</template>
<template v-else>
<span v-html="data.channel.getEntityValue(column.attributeName)"></span>
</template>
</div>
</td>
<td rowspan="1" colspan="1" class="commands">
<div class="cell">
<el-link v-for="menu in data.menus" :underline="false" type="primary" size="small" style="margin-left: 5px" v-on:click.stop.native="btnMenuClick(menu, data)">
{{ menu.text }}
</el-link>
<el-link :underline="false" type="primary" size="small" style="margin-left: 5px" v-on:click.stop.native="btnEditClick(data)">
编辑
</el-link>
<el-link :underline="false" type="primary" size="small" style="margin-left: 5px" v-if="siteId !== data.value" v-on:click.stop.native="btnDeleteClick(data)">
删除
</el-link>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</el-tree>
<el-row>
<el-divider></el-divider>
<div style="height: 10px"></div>
<el-button size="small" type="primary" plain style="margin-left: 0px;" icon="el-icon-plus" v-on:click="btnAppendClick">
添 加
</el-button>
<el-button size="small" type="primary" plain style="margin-left: 0px;" icon="el-icon-upload" v-on:click="btnImportClick">
导 入
</el-button>
<el-button size="small" type="primary" plain style="margin-left: 0px;" :disabled="channelIds.length === 0" icon="el-icon-download" v-on:click="btnExportClick">
导 出
</el-button>
<el-button size="small" type="primary" plain style="margin-left: 0px;" icon="el-icon-files" :disabled="channelIds.length === 0" v-on:click="btnSetGroupClick">
分 组
</el-button>
<el-button size="small" type="primary" plain style="margin-left: 0px;" :disabled="channelIds.length === 0" icon="el-icon-sort" v-on:click="btnSetTaxisClick">
排 序
</el-button>
<el-button size="small" type="primary" plain style="margin-left: 0px;" :disabled="channelIds.length === 0" icon="el-icon-right" v-on:click="btnTranslateClick">
复 制
</el-button>
<el-button size="small" type="primary" plain style="margin-left: 0px;" :disabled="channelIds.length === 0" icon="el-icon-magic-stick" v-on:click="btnCreateClick">
生 成
</el-button>
<div style="height: 5px"></div>
</el-row>
<el-drawer
id="appendForm"
v-if="appendForm"
title="添加栏目"
ref="appendPanel"
:visible.sync="appendPanel"
destroy-on-close
:wrapperClosable="false"
direction="rtl"
size="90%">
<div class="drawer__content">
<el-form v-on:submit.native.prevent ref="appendForm" :model="appendForm" size="small" label-width="120px">
<el-form-item label="父栏目" prop="parentIds" :rules="{ required: true, message: '请选择父栏目' }">
<el-cascader
v-model="appendForm.parentIds"
:options="root"
:props="{ checkStrictly: true }"
style="width: 100%;"
placeholder="请选择父栏目">
</el-cascader>
</el-form-item>
<el-form-item label="栏目" prop="channels" :rules="{ required: true, message: '请输入栏目名称' }">
<el-input v-model="appendForm.channels" type="textarea" rows="10" :autosize="{ minRows: 10, maxRows: 20}" placeholder="栏目之间用换行分割,下级栏目在栏目前添加“-”字符,索引可以放到括号中,如: 栏目一(栏目索引) -下级栏目(下级索引) --下下级栏目"></el-input>
</el-form-item>
<el-form-item>
<el-checkbox label="采用父栏目模板" v-model="appendForm.isParentTemplates"></el-checkbox>
<el-checkbox label="将栏目名称作为栏目索引" v-model="appendForm.isIndexName"></el-checkbox>
</el-form-item>
<el-form-item v-if="!appendForm.isParentTemplates" label="栏目模板" prop="channels" :rules="{ required: true, message: '请选择栏目模板' }">
<el-select v-model="appendForm.channelTemplateId" size="mini" placeholder="请选择栏目模板">
<el-option label="<默认>" :value="0"></el-option>
<el-option v-for="channelTemplate in channelTemplates" :key="channelTemplate.id" :label="channelTemplate.templateName" :value="channelTemplate.id"></el-option>
</el-select>
</el-form-item>
<el-form-item v-if="!appendForm.isParentTemplates" label="内容模板" prop="channels" :rules="{ required: true, message: '请选择内容模板' }">
<el-select v-model="appendForm.contentTemplateId" size="mini" placeholder="请选择内容模板">
<el-option label="<默认>" :value="0"></el-option>
<el-option v-for="contentTemplate in contentTemplates" :key="contentTemplate.id" :label="contentTemplate.templateName" :value="contentTemplate.id"></el-option>
</el-select>
</el-form-item>
<el-divider></el-divider>
<div class="drawer__footer">
<el-button type="primary" v-on:click="btnAppendSubmitClick" size="small">确 定</el-button>
<el-button size="small" v-on:click="btnCancelClick">取 消</el-button>
</div>
</el-form>
</div>
</el-drawer>
<el-drawer
id="deleteForm"
v-if="deleteForm"
title="删除栏目"
ref="deletePanel"
:visible.sync="deletePanel"
destroy-on-close
:wrapperClosable="false"
direction="rtl"
size="80%">
<div class="drawer__content">
<el-alert type="warning">
- 此操作 <strong>不可以</strong> 被回滚。<br>
- 此操作将永久删除该栏目,包括所有下级栏目。
<br>
- 删除完成后栏目包含的所有内容将进入回收站。
</el-alert>
<br />
<el-form v-on:submit.native.prevent size="mini" ref="deleteForm" :model="deleteForm" label-width="160px">
<el-form-item>
<el-radio v-model="deleteForm.deleteFiles" :label="false">保留生成的文件</el-radio>
<el-radio v-model="deleteForm.deleteFiles" :label="true">删除生成的文件</el-radio>
</el-form-item>
<el-form-item>
请输入以下信息以确认您的操作: <span style="color:#F56C6C">{{ deleteForm.label }}</span>
</el-form-item>
<el-form-item label="需要删除的栏目名称" prop="channelName" :rules="{ required: true, message: '请输入栏目名称' }">
<el-input v-model="deleteForm.channelName" placeholder="请输入需要删除的栏目名称"></el-input>
</el-form-item>
<el-divider></el-divider>
<div class="drawer__footer">
<el-button type="danger" size="small" v-on:click="btnDeleteSubmitClick">删 除</el-button>
<el-button size="small" v-on:click="btnCancelClick">取 消</el-button>
</div>
</el-form>
</div>
</el-drawer>
<el-drawer
id="importForm"
v-if="importForm"
title="导入栏目"
ref="importPanel"
:visible.sync="importPanel"
destroy-on-close
:wrapperClosable="false"
direction="rtl"
size="80%">
<div class="drawer__content">
<el-form v-on:submit.native.prevent ref="importForm" size="small" :model="importForm" label-width="160px">
<el-form-item label="栏目压缩包" prop="fileName" :rules="{ required: true, message: '请上传栏目压缩包' }">
<el-upload
:drag="true"
:limit="1"
:action="uploadUrl"
:auto-upload="true"
:headers="{Authorization: 'Bearer ' + $token}"
:file-list="importUploadList"
:before-upload="uploadBefore"
:on-progress="uploadProgress"
:on-success="uploadSuccess"
:on-error="uploadError"
:multiple="false">
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</el-upload>
</el-form-item>
<el-form-item label="父栏目" prop="channelIds" :rules="{ required: true, message: '请选择父栏目' }">
<el-cascader
v-model="importForm.channelIds"
:options="root"
:props="{ checkStrictly: true }"
style="width: 100%;"
placeholder="请选择父栏目">
</el-cascader>
</el-form-item>
<el-form-item>
<el-checkbox label="覆盖同名栏目" v-model="importForm.isOverride"></el-checkbox>
</el-form-item>
<el-divider></el-divider>
<div class="drawer__footer">
<el-button type="primary" size="small" v-on:click="btnImportSubmitClick">确 定</el-button>
<el-button size="small" v-on:click="btnCancelClick">取 消</el-button>
</div>
</el-form>
</div>
</el-drawer>
<el-drawer
id="editForm"
v-if="editPanel && form"
title="编辑栏目"
:visible.sync="editPanel"
destroy-on-close
:wrapperClosable="false"
direction="rtl"
size="90%">
<div class="drawer__content">
<el-form v-on:submit.native.prevent size="mini" ref="editForm" :model="form" label-width="200px">
<el-form-item label="栏目名称" prop="channelName" :rules="{ required: true, message: '请输入栏目名称' }">
<el-input v-model="form.channelName" placeholder="请输入栏目名称"></el-input>
</el-form-item>
<el-form-item label="栏目索引">
<el-input v-model="form.indexName" placeholder="请输入栏目索引"></el-input>
</el-form-item>
<el-form-item label="栏目组" v-if="groupNames && groupNames.length > 0">
<el-checkbox-group v-model="form.groupNames">
<el-checkbox v-for="groupName in groupNames" :key="groupName" :label="groupName" :value="groupName" name="groupNames"></el-checkbox>
</el-checkbox-group>
<el-button size="mini" icon="el-icon-circle-plus-outline" v-on:click="btnEditAddGroupClick">
新增栏目组
</el-button>
</el-form-item>
<el-form-item label="栏目模板">
<el-select v-model="form.channelTemplateId" placeholder="请选择栏目模板">
<el-option label="<默认>" :value="0"></el-option>
<el-option v-for="channelTemplate in channelTemplates" :key="channelTemplate.id" :label="channelTemplate.templateName" :value="channelTemplate.id"></el-option>
</el-select>
<el-link
v-if="isTemplateEditable && form.channelTemplateId > 0"
:underline="false"
:href="getTemplateEditorUrl(true, form.channelTemplateId)"
target="_blank"
type="primary">
编辑
</el-link>
</el-form-item>
<el-form-item label="内容模板">
<el-select v-model="form.contentTemplateId" placeholder="请选择内容模板">
<el-option label="<默认>" :value="0"></el-option>
<el-option v-for="contentTemplate in contentTemplates" :key="contentTemplate.id" :label="contentTemplate.templateName" :value="contentTemplate.id"></el-option>
</el-select>
<el-link
v-if="isTemplateEditable && form.contentTemplateId > 0"
:underline="false"
:href="getTemplateEditorUrl(false, form.contentTemplateId)"
target="_blank"
type="primary">
编辑
</el-link>
</el-form-item>
<el-form-item label="链接类型">
<el-tooltip effect="light" content="设置此栏目的链接与子栏目及内容的关系" placement="right">
<el-select v-model="form.linkType" placeholder="请选择链接类型" v-on:change="form.linkUrl = form.linkToChannel = '';">
<el-option v-for="linkType in editLinkTypes" :key="linkType.value" :label="linkType.label" :value="linkType.value"></el-option>
</el-select>
</el-tooltip>
</el-form-item>
<el-form-item v-if="form.linkType == 'None'" label="外部链接">
<el-input v-model="form.linkUrl" placeholder="设置后栏目链接将转向此地址"></el-input>
</el-form-item>
<el-form-item v-if="form.linkType == 'LinkToChannel'" label="指定栏目" prop="linkUrl" :rules="{ required: true, message: '请选择链接到指定栏目' }">
<el-tag
v-if="form.linkToChannel"
closable
size="small"
v-on:close="form.linkToChannel = ''"
type="primary">
{{ form.linkToChannel }}
</el-tag>
<el-cascader
v-else
v-model="form.linkUrl"
filterable
:options="root"
:props="{ checkStrictly: true }"
style="width: 100%;"
placeholder="请选择栏目">
</el-cascader>
</el-form-item>
<el-form-item label="新增内容排序规则">
<el-tooltip effect="light" content="新增或导入的内容将按照此排序规则进行排序" placement="right">
<el-select v-model="form.defaultTaxisType" placeholder="请选择新增内容排序规则">
<el-option v-for="taxisType in editTaxisTypes" :key="taxisType.value" :label="taxisType.label" :value="taxisType.value"></el-option>
</el-select>
</el-tooltip>
</el-form-item>
<el-form-item label="生成页面路径">
<el-input v-model="form.filePath" placeholder="在此设置栏目的生成页面地址"></el-input>
</el-form-item>
<el-form-item label="下级栏目页面命名规则">
<el-input style="width: 70%" v-model="form.channelFilePathRule" placeholder="在此设置下级栏目页面命名规则"></el-input>
<el-button v-on:click.prevent="btnSetClick(form.id, true, form.channelFilePathRule)">构造</el-button>
</el-form-item>
<el-form-item label="内容页面命名规则">
<el-input style="width: 70%" v-model="form.contentFilePathRule" placeholder="在此设置栏目页面命名规则"></el-input>
<el-button v-on:click.prevent="btnSetClick(form.id, false, form.contentFilePathRule)">构造</el-button>
</el-form-item>
<el-form-item label="栏目关键字">
<el-input v-model="form.keywords"></el-input>
</el-form-item>
<el-form-item label="栏目描述">
<el-input type="textarea" v-model="form.description"></el-input>
</el-form-item>
@await Html.PartialAsync("_PartialForm")
<el-divider></el-divider>
<div class="drawer__footer">
<el-button type="primary" v-on:click="btnSaveClick" size="small">确 定</el-button>
<el-button size="small" v-on:click="btnCancelClick">取 消</el-button>
</div>
</el-form>
</div>
</el-drawer>
@section Scripts{
<script src="/sitefiles/assets/lib/ueditor/editor_config.js" type="text/javascript" ></script>
<script src="/sitefiles/assets/lib/ueditor/ueditor.all.js" type="text/javascript"></script>
<script src="/sitefiles/assets/lib/ueditor/third-party/custom/custom.js" type="text/javascript" ></script>
<script src="/sitefiles/assets/js/admin/cms/channels.js" type="text/javascript"></script> }
仔细看就vue组件框架 elementui
操作逻辑在这哥文件channels.js 它的代码
var $url = '/cms/channels/channels';
var $urlUpdate = $url + '/actions/update';
var $urlDelete = $url + '/actions/delete';
var data = utils.init({
siteId: utils.getQueryInt("siteId"),
root: null,
expandedChannelIds: [],
indexNames: [],
groupNames: [],
channelTemplates: [],
contentTemplates: [],
defaultChannelTemplate: null,
defaultContentTemplate: null,
columns: null,
commandsWidth: 160,
channelIds: [],
filterText: '',
filterIndexName: '',
filterGroupName: '',
appendPanel: false,
appendForm: null,
editPanel: false,
form: null,
editLinkTypes: [],
editTaxisTypes: [],
styles: [],
relatedFields: null,
siteUrl: null,
isTemplateEditable: false,
deletePanel: false,
deleteForm: null,
importPanel: false,
importForm: null,
importUploadList: []
});
var methods = {
apiList: function(expandedChannelIds) {
var $this = this;
utils.loading(this, true);
$api.get($url, {
params: {
siteId: this.siteId
}
}).then(function (response) {
var res = response.data;
$this.root = [res.channel];
$this.indexNames = res.indexNames;
$this.groupNames = res.groupNames;
$this.channelTemplates = res.channelTemplates;
$this.contentTemplates = res.contentTemplates;
$this.defaultChannelTemplate = $this.channelTemplates.find(function(x) {
return x.defaultTemplate;
});
$this.defaultContentTemplate = $this.contentTemplates.find(function(x) {
return x.defaultTemplate;
});
$this.columns = res.columns;
$this.commandsWidth = res.commandsWidth;
$this.isTemplateEditable = res.isTemplateEditable;
$this.expandedChannelIds = expandedChannelIds ? expandedChannelIds : [$this.siteId];
$this.editLinkTypes = res.linkTypes;
$this.editTaxisTypes = res.taxisTypes;
$this.siteUrl = res.siteUrl;
}).catch(function (error) {
utils.error(error);
}).then(function () {
utils.loading($this, false);
});
},
apiGet: function(channelId) {
var $this = this;
utils.loading(this, true);
$api.get($url + '/' + this.siteId + '/' + channelId).then(function (response) {
var res = response.data;
$this.form = _.assign({}, res.entity);
if (!$this.form.groupNames) {
$this.form.groupNames = [];
}
$this.styles = res.styles;
$this.relatedFields = res.relatedFields;
$this.form.filePath = res.filePath;
$this.form.channelFilePathRule = res.channelFilePathRule;
$this.form.contentFilePathRule = res.contentFilePathRule;
$this.editPanel = true;
utils.loadEditors($this.styles, $this.form);
}).catch(function (error) {
utils.error(error);
}).then(function () {
utils.loading($this, false);
});
},
apiAppend: function () {
var $this = this;
utils.loading(this, true);
$api.post($url + '/actions/append', {
siteId: this.siteId,
parentId: this.appendForm.parentIds[this.appendForm.parentIds.length - 1],
channelTemplateId: this.appendForm.channelTemplateId,
contentTemplateId: this.appendForm.contentTemplateId,
isParentTemplates: this.appendForm.isParentTemplates,
isIndexName: this.appendForm.isIndexName,
channels: this.appendForm.channels,
}).then(function (response) {
var res = response.data;
$this.appendPanel = false;
$this.apiList(res);
utils.success('栏目添加成功!');
}).catch(function (error) {
utils.loading($this, false);
utils.error(error);
});
},
apiUpdate: function () {
var $this = this;
utils.loading(this, true);
$api.post($urlUpdate, this.form).then(function (response) {
var res = response.data;
$this.editPanel = false;
$this.apiList(res);
utils.success('栏目编辑成功!');
}).catch(function (error) {
utils.loading($this, false);
utils.error(error);
});
},
apiDelete: function () {
var $this = this;
utils.loading(this, true);
$api.post($urlDelete, this.deleteForm).then(function (response) {
var res = response.data;
$this.deletePanel = false;
$this.apiList(res);
utils.success('栏目删除成功!');
}).catch(function (error) {
utils.loading($this, false);
utils.error(error);
});
},
apiImport: function () {
var $this = this;
utils.loading(this, true);
$api.post($url + '/actions/import', {
siteId: this.importForm.siteId,
channelId: this.importForm.channelIds[this.importForm.channelIds.length - 1],
fileName: this.importForm.fileName,
isOverride: this.importForm.isOverride,
}).then(function (response) {
var res = response.data;
$this.importPanel = false;
$this.apiList(res);
utils.success('栏目导入成功!');
}).catch(function (error) {
utils.loading($this, false);
utils.error(error);
})
},
apiDrop: function (sourceId, targetId, dropType) {
var $this = this;
utils.loading(this, true);
$api.post($url + '/actions/drop', {
siteId: this.siteId,
sourceId: sourceId,
targetId: targetId,
dropType: dropType
}).then(function (response) {
var res = response.data;
utils.success('栏目排序成功!');
}).catch(function (error) {
utils.error(error);
}).then(function () {
utils.loading($this, false);
});
},
apiColumns: function(attributeNames) {
var $this = this;
$api.post($url + '/actions/columns', {
siteId: this.siteId,
attributeNames: attributeNames
}).then(function(response) {
var res = response.data;
}).catch(function(error) {
utils.error(error);
});
},
runFormLayerImageUploadText: function(attributeName, no, text) {
this.insertText(attributeName, no, text);
},
runFormLayerImageUploadEditor: function(attributeName, html) {
this.insertEditor(attributeName, html);
},
runMaterialLayerImageSelect: function(attributeName, no, text) {
this.insertText(attributeName, no, text);
},
runFormLayerFileUpload: function(attributeName, no, text) {
this.insertText(attributeName, no, text);
},
runMaterialLayerFileSelect: function(attributeName, no, text) {
this.insertText(attributeName, no, text);
},
runFormLayerVideoUpload: function(attributeName, no, text) {
this.insertText(attributeName, no, text);
},
runMaterialLayerVideoSelect: function(attributeName, no, text) {
this.insertText(attributeName, no, text);
},
runEditorLayerImage: function(attributeName, html) {
this.insertEditor(attributeName, html);
},
insertText: function(attributeName, no, text) {
var count = this.form[utils.getCountName(attributeName)] || 0;
if (count <= no) {
this.form[utils.getCountName(attributeName)] = no;
}
this.form[utils.getExtendName(attributeName, no)] = text;
this.form = _.assign({}, this.form);
},
insertEditor: function(attributeName, html) {
if (!attributeName) attributeName = 'Body';
if (!html) return;
utils.getEditor(attributeName).execCommand('insertHTML', html);
},
setRuleText: function(rule, isChannel) {
if (isChannel) {
this.form.channelFilePathRule = rule;
} else {
this.form.contentFilePathRule = rule;
}
},
updateGroups: function(res, message) {
this.groupNames = res.groupNames;
utils.success(message);
},
handleColumnsChange: function() {
var listColumns = _.filter(this.columns, function(o) { return o.isList; });
var attributeNames = _.map(listColumns, function(column) {
return column.attributeName;
});
this.apiColumns(attributeNames);
},
btnSetClick: function(channelId, isChannel, rule) {
var url = utils.getCmsUrl('settingsCreateRuleLayerSet', {
siteId: this.siteId,
isChannel: isChannel,
channelId: channelId,
rule: rule || ''
});
utils.openLayer({
title: '构造',
url: url,
width: 800,
height: 500
});
},
getColumnWidth: function(attributeName) {
if (attributeName === 'Id') return 80;
if (attributeName === 'ChannelTemplateId' || attributeName === 'ContentTemplateId') return 120;
if (attributeName === 'IndexName') return 120;
return 180;
},
getTemplate: function(isChannel, templateId) {
var template = null;
if (isChannel) {
template = this.channelTemplates.find(function(x) {
return x.id === templateId;
});
} else {
template = this.contentTemplates.find(function(x) {
return x.id === templateId;
});
}
if (!template) {
template = isChannel ? this.defaultChannelTemplate : this.defaultContentTemplate;
}
return template;
},
getTemplateEditorUrl: function(isChannel, templateId) {
return utils.getCmsUrl('templatesEditor', {
siteId: this.siteId,
templateId: templateId,
templateType: isChannel ? 'ChannelTemplate' : 'ContentTemplate',
accessToken: $token
});
},
btnEditAddGroupClick: function() {
utils.openLayer({
title: '新增栏目组',
url: utils.getCommonUrl('groupChannelLayerAdd', {siteId: this.siteId}),
width: 500,
height: 300
});
},
handleDrop: function(draggingNode, dropNode, dropType, ev) {
this.apiDrop(draggingNode.data.value, dropNode.data.value, dropType);
},
allowDrop: function(draggingNode, dropNode, type) {
if (dropNode.data.value === this.siteId) {
return false;
} else {
return true;
}
},
allowDrag: function(draggingNode) {
return draggingNode.data.value !== this.siteId;
},
getChannelUrl: function(data) {
return utils.getRootUrl('redirect', {siteId: this.siteId, channelId: data.value});
},
handleCheckChange() {
this.channelIds = this.$refs.tree.getCheckedKeys();
},
btnCheckClick: function(row) {
if (this.channelIds.indexOf(row.value) !== -1) {
this.channelIds.splice(this.channelIds.indexOf(row.value), 1);
} else {
this.channelIds.push(row.value);
}
this.$refs.tree.setCheckedKeys(this.channelIds);
},
filterNode: function(value, data) {
if (!value) return true;
if (value.channelName && value.indexName && value.groupName) {
return (data.label.indexOf(value.channelName) !== -1 || data.value + '' === value.channelName) && data.channel.indexName === value.indexName && data.groupNames.indexOf(value.groupName) !== -1;
} else if (value.channelName && value.indexName) {
return (data.label.indexOf(value.channelName) !== -1 || data.value + '' === value.channelName) && data.channel.indexName === value.indexName;
} else if (value.channelName && value.groupName) {
return (data.label.indexOf(value.channelName) !== -1 || data.value + '' === value.channelName) && data.groupNames.indexOf(value.groupName) !== -1;
} else if (value.indexName && value.groupName) {
return data.channel.indexName === value.indexName && data.groupNames.indexOf(value.groupName) !== -1;
} else if (value.channelName) {
return (data.label.indexOf(value.channelName) !== -1 || data.value + '' === value.channelName);
} else if (value.groupName) {
return data.groupNames.indexOf(value.groupName) !== -1;
} else if (value.indexName) {
return data.channel.indexName === value.indexName;
}
return true;
},
btnCancelClick: function() {
this.appendPanel = false;
this.deletePanel = false;
this.importPanel = false;
this.editPanel = false;
},
btnAppendClick: function() {
this.appendForm = {
parentIds: [this.siteId],
channelTemplateId: 0,
contentTemplateId: 0,
isParentTemplates: true,
isIndexName: false,
channels: ''
};
this.appendPanel = true;
},
btnAppendSubmitClick: function() {
var $this = this;
this.$refs.appendForm.validate(function(valid) {
if (valid) {
$this.apiAppend();
}
});
},
btnEditClick: function(row) {
this.apiGet(row.value);
},
btnSaveClick: function() {
var $this = this;
this.$refs.editForm.validate(function(valid) {
if (valid) {
$this.apiUpdate();
}
});
},
btnDeleteClick: function(data) {
this.deleteForm = {
siteId: this.siteId,
channelId: data.value,
label: data.label,
channelName: null,
deleteFiles: false
};
this.deletePanel = true;
},
btnDeleteSubmitClick: function() {
var $this = this;
this.$refs.deleteForm.validate(function(valid) {
if (valid) {
if ($this.deleteForm.channelName == $this.deleteForm.label) {
$this.apiDelete();
} else {
utils.error('请检查您输入的栏目名称是否正确');
}
}
});
},
btnImportClick: function() {
this.importForm = {
siteId: this.siteId,
channelIds: [this.siteId],
fileName: null,
isOverride: true
};
this.importPanel = true;
},
btnTranslateClick: function() {
location.href = utils.getCmsUrl('channelsTranslate', {
siteId: this.siteId,
channelIds: this.channelIds.join(','),
returnUrl: location.href
});
},
btnImportSubmitClick: function() {
var $this = this;
this.$refs.importForm.validate(function(valid) {
if (valid) {
$this.apiImport();
}
});
},
btnExportClick: function() {
var $this = this;
utils.loading(this, true);
$api.post($url + '/actions/export', {
siteId: this.siteId,
channelIds: this.channelIds
}).then(function (response) {
var res = response.data;
window.open(res.value);
}).catch(function (error) {
utils.error(error);
}).then(function () {
utils.loading($this, false);
});
},
btnSetGroupClick: function() {
utils.openLayer({
title: '设置栏目组',
url: utils.getCmsUrl('channelsLayerGroup', {
siteId: this.siteId,
channelIds: this.channelIds.join(',')
}),
width: 700,
height: 400
});
},
btnSetTaxisClick: function() {
utils.openLayer({
title: '栏目排序',
url: utils.getCmsUrl('channelsLayerTaxis', {
siteId: this.siteId,
channelIds: this.channelIds.join(',')
}),
width: 500,
height: 260
});
},
btnCreateClick: function() {
utils.openLayer({
title: '生成页面',
url: utils.getCmsUrl('channelsLayerCreate', {
siteId: this.siteId,
channelIds: this.channelIds.join(',')
}),
width: 500,
height: 260
});
},
btnMenuClick: function(menu, channel) {
var url = utils.addQuery(menu.link, {
siteId: this.siteId,
channelId: channel.value
});
if (menu.target == '_layer') {
utils.openLayer({
title: menu.text,
url: url,
full: true
});
} else if (menu.target == '_self') {
location.href = url;
} else if (menu.target == '_parent') {
parent.location.href = url;
} else if (menu.target == '_top') {
top.location.href = url;
} else if (menu.target == '_blank') {
window.open(url);
} else {
utils.addTab(menu.text, url);
}
},
uploadBefore(file) {
var isZip = file.name.indexOf('.zip', file.name.length - '.zip'.length) !== -1;
if (!isZip) {
utils.error('导入文件只能是 Zip 格式!');
}
return isZip;
},
uploadProgress: function() {
utils.loading(this, true);
},
uploadSuccess: function(res, file) {
this.loading && this.loading.close();
this.importForm.fileName = res.value;
},
uploadError: function(err) {
this.loading && this.loading.close();
var error = JSON.parse(err.message);
utils.error(error.message);
},
btnLayerClick: function(options) {
var query = {
siteId: this.siteId
};
if (options.attributeName) {
query.attributeName = options.attributeName;
}
if (options.no) {
query.no = options.no;
}
if (options.contentId) {
query.contentId = options.contentId;
}
var args = {
title: options.title,
url: utils.getCommonUrl(options.name, query)
};
if (!options.full) {
args.width = options.width ? options.width : 700;
args.height = options.height ? options.height : 500;
}
utils.openLayer(args);
},
btnExtendAddClick: function(style) {
var no = this.form[utils.getCountName(style.attributeName)] + 1;
this.form[utils.getCountName(style.attributeName)] = no;
this.form[utils.getExtendName(style.attributeName, no)] = '';
this.form = _.assign({}, this.form);
},
btnExtendRemoveClick: function(style) {
var no = this.form[utils.getCountName(style.attributeName)];
this.form[utils.getCountName(style.attributeName)] = no - 1;
this.form[utils.getExtendName(style.attributeName, no)] = '';
this.form = _.assign({}, this.form);
},
btnExtendPreviewClick: function(attributeName, no) {
var count = this.form[utils.getCountName(attributeName)];
var data = [];
for (var i = 0; i <= count; i++) {
var imageUrl = this.form[utils.getExtendName(attributeName, i)];
imageUrl = utils.getUrl(this.siteUrl, imageUrl);
data.push({
"src": imageUrl
});
}
layer.photos({
photos: {
"start": no,
"data": data
}
,anim: 5
});
},
btnCloseClick: function() {
utils.removeTab();
},
};
var $vue = new Vue({
el: "#main",
data: data,
methods: methods,
watch: {
filterText: function(val) {
this.$refs.tree.filter({
channelName: val,
indexName: this.filterIndexName,
groupName: this.filterGroupName
});
},
filterIndexName: function(val) {
this.$refs.tree.filter({
channelName: this.filterText,
indexName: val,
groupName: this.filterGroupName
});
},
filterGroupName: function(val) {
this.$refs.tree.filter({
channelName: this.filterText,
indexName: this.filterIndexName,
groupName: val
});
}
},
created: function () {
var $this = this;
utils.keyPress(function () {
if ($this.editPanel) {
$this.btnSaveClick();
} else if ($this.appendPanel) {
$this.btnAppendSubmitClick();
} else if ($this.deletePanel) {
$this.btnDeleteSubmitClick();
}
}, function () {
if ($this.editPanel || $this.appendPanel || $this.deletePanel || $this.importPanel) {
$this.btnCancelClick();
} else {
$this.btnCloseClick();
}
});
this.uploadUrl = $apiUrl + $url + '/actions/upload?siteId=' + this.siteId;
this.apiList();
}
});
对应的操作api控制器 ChannelsController,分类很多类 第一个的代码
namespace SSCMS.Web.Controllers.Admin.Cms.Channels
{
[OpenApiIgnore]
[Authorize(Roles = Types.Roles.Administrator)]
[Route(Constants.ApiAdminPrefix)]
public partial class ChannelsController : ControllerBase
{
private const string Route = "cms/channels/channels";
private const string RouteGet = "cms/channels/channels/{siteId:int}/{channelId:int}";
private const string RouteUpdate = "cms/channels/channels/actions/update";
private const string RouteDelete = "cms/channels/channels/actions/delete";
private const string RouteAppend = "cms/channels/channels/actions/append";
private const string RouteUpload = "cms/channels/channels/actions/upload";
private const string RouteImport = "cms/channels/channels/actions/import";
private const string RouteExport = "cms/channels/channels/actions/export";
private const string RouteDrop = "cms/channels/channels/actions/drop";
private const string RouteColumns = "cms/channels/channels/actions/columns";
private readonly ICacheManager _cacheManager;
private readonly IAuthManager _authManager;
private readonly IPathManager _pathManager;
private readonly ICreateManager _createManager;
private readonly IDatabaseManager _databaseManager;
private readonly IPluginManager _pluginManager;
private readonly ISiteRepository _siteRepository;
private readonly IChannelRepository _channelRepository;
private readonly IContentRepository _contentRepository;
private readonly IChannelGroupRepository _channelGroupRepository;
private readonly ITemplateRepository _templateRepository;
private readonly ITableStyleRepository _tableStyleRepository;
private readonly IRelatedFieldItemRepository _relatedFieldItemRepository;
public ChannelsController(ICacheManager cacheManager, IAuthManager authManager, IPathManager pathManager, ICreateManager createManager, IDatabaseManager databaseManager, IPluginManager pluginManager, ISiteRepository siteRepository, IChannelRepository channelRepository, IContentRepository contentRepository, IChannelGroupRepository channelGroupRepository, ITemplateRepository templateRepository, ITableStyleRepository tableStyleRepository, IRelatedFieldItemRepository relatedFieldItemRepository)
{
_cacheManager = cacheManager;
_authManager = authManager;
_pathManager = pathManager;
_createManager = createManager;
_databaseManager = databaseManager;
_pluginManager = pluginManager;
_siteRepository = siteRepository;
_channelRepository = channelRepository;
_contentRepository = contentRepository;
_channelGroupRepository = channelGroupRepository;
_templateRepository = templateRepository;
_tableStyleRepository = tableStyleRepository;
_relatedFieldItemRepository = relatedFieldItemRepository;
}
public class ChannelColumn
{
public string AttributeName { get; set; }
public string DisplayName { get; set; }
public InputType InputType { get; set; }
public bool IsList { get; set; }
}
public class ColumnsRequest : ChannelRequest
{
public List<string> AttributeNames { get; set; }
}
public class ChannelsResult
{
public Cascade<int> Channel { get; set; }
public IEnumerable<string> IndexNames { get; set; }
public IEnumerable<string> GroupNames { get; set; }
public IEnumerable<Template> ChannelTemplates { get; set; }
public IEnumerable<Template> ContentTemplates { get; set; }
public List<ContentColumn> Columns { get; set; }
public int CommandsWidth { get; set; }
public bool IsTemplateEditable { get; set; }
public IEnumerable<Select<string>> LinkTypes { get; set; }
public IEnumerable<Select<string>> TaxisTypes { get; set; }
public string SiteUrl { get; set; }
}
public class ChannelResult
{
public Entity Entity { get; set; }
public IEnumerable<TableStyle> Styles { get; set; }
public Dictionary<int, List<Dto.Cascade<int>>> RelatedFields { get; set; }
public string FilePath { get; set; }
public string ChannelFilePathRule { get; set; }
public string ContentFilePathRule { get; set; }
}
public class ImportRequest : ChannelRequest
{
public string FileName { get; set; }
public bool IsOverride { get; set; }
}
public class DropRequest : SiteRequest
{
public int SourceId { get; set; }
public int TargetId { get; set; }
public string DropType { get; set; }
}
public class ChannelIdsRequest : SiteRequest
{
public List<int> ChannelIds { get; set; }
}
public class DeleteRequest : SiteRequest
{
public int ChannelId { get; set; }
public string ChannelName { get; set; }
public bool DeleteFiles { get; set; }
}
public class AppendRequest : SiteRequest
{
public int ParentId { get; set; }
public int ChannelTemplateId { get; set; }
public int ContentTemplateId { get; set; }
public bool IsParentTemplates { get; set; }
public bool IsIndexName { get; set; }
public string Channels { get; set; }
}
private async Task<List<TableStyle>> GetStylesAsync(Channel channel)
{
var styles = new List<TableStyle>
{
new TableStyle()
{
AttributeName = nameof(Channel.ImageUrl),
DisplayName = "栏目图片",
InputType = InputType.Image
},
new TableStyle()
{
AttributeName = nameof(Channel.Content),
DisplayName = "栏目正文",
InputType = InputType.TextEditor
}
};
var tableStyles = await _tableStyleRepository.GetChannelStylesAsync(channel);
styles.AddRange(tableStyles);
return styles;
}
}
}
这个文件只有action 文件名称,没有具体实现的逻辑。
其他文件才有。差不多一个action对应一个文件
删除的代码
namespace SSCMS.Web.Controllers.Admin.Cms.Channels
{
public partial class ChannelsController
{
[HttpPost, Route(RouteDelete)]
public async Task<ActionResult<List<int>>> Delete([FromBody] DeleteRequest request)
{
//这里是权限检查
if (!await _authManager.HasSitePermissionsAsync(request.SiteId,
MenuUtils.SitePermissions.Channels))
{
return Unauthorized();
}
var site = await _siteRepository.GetAsync(request.SiteId);
if (site == null) return this.Error(Constants.ErrorNotFound);
var channel = await _channelRepository.GetAsync(request.ChannelId);
if (channel == null) return this.Error("无法确定父栏目");
var channelIdList = await _channelRepository.GetChannelIdsAsync(request.SiteId, request.ChannelId, ScopeType.All);
if (request.DeleteFiles)
{
await _createManager.DeleteChannelsAsync(site, channelIdList);
}
var adminId = _authManager.AdminId;
foreach (var channelId in channelIdList)
{
await _contentRepository.TrashContentsAsync(site, channelId, adminId);
}
foreach (var channelId in channelIdList)
{
await _channelRepository.DeleteAsync(site, channelId, adminId);
}
await _authManager.AddSiteLogAsync(request.SiteId, "删除栏目", $"栏目:{channel.ChannelName}");
return new List<int>
{
request.SiteId,
channel.ParentId
};
}
}
}
这个业务流程逻辑理解 ui前端-->控制器-->仓储层 IChannelRepository-->基类Repository-->Dapper SSCMS.Web-->SSCMS.Core-->SSCMS-->Datory
SSCMS.Core项目是对接口SSCMS的实现.