SSCMS项目学习

451 阅读1分钟

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="栏目之间用换行分割,下级栏目在栏目前添加“-”字符,索引可以放到括号中,如:&#10;栏目一(栏目索引)&#10;-下级栏目(下级索引)&#10;--下下级栏目"></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对应一个文件 image.png 删除的代码

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的实现.