From a35f4b4d0c555493cc464bfd36d037230547f1aa Mon Sep 17 00:00:00 2001
From: tj <1378534974@qq.com>
Date: 星期四, 20 三月 2025 09:44:44 +0800
Subject: [PATCH] 1.高级安全防护

---
 src/views/system/modules/SysDictItemModal.vue  |  267 +++++++++
 src/views/system/modules/SecurityModal.vue     |  173 ++++++
 package.json                                   |    8 
 src/views/system/SecurityList.vue              |  219 +++++--
 src/views/system/modules/CloudContentModal.vue |  154 +++++
 src/views/system/modules/SysDictModal.vue      |  146 +++++
 src/components/Editor.vue                      |  119 ++++
 src/views/system/SysDictList.vue               |  227 ++++++++
 src/views/system/CloudContent.vue              |  234 ++++++++
 README.md                                      |   80 ++
 10 files changed, 1,564 insertions(+), 63 deletions(-)

diff --git a/README.md b/README.md
index 5c5dadb..08acd7c 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,80 @@
-## jspERP-web
-node -v v16.20.2
+jshERP-web Vue
+
+#### 前端技术
+
+- 基础框架:[ant-design-vue](https://github.com/vueComponent/ant-design-vue) - Ant Design Of Vue 实现
+- JavaScript框架:Vue
+- Jeecg-boot 的前段UI框架
+- Webpack
+- node
+- yarn
+- eslint
+- @vue/cli 3.2.1
+- [vue-cropper](https://github.com/xyxiao001/vue-cropper) - 头像裁剪组件
+- [@antv/g2](https://antv.alipay.com/zh-cn/index.html) - Alipay AntV 数据可视化图表
+- [Viser-vue](https://viserjs.github.io/docs.html#/viser/guide/installation)  - antv/g2 封装实现
+
+
+
+项目运行
+----
+
+- 安装nodeJS
+```
+建议安装node-v20.17.0-x64版本 教程参考 https://blog.csdn.net/Coin_Collecter/article/details/136484312
+```
+
+- 安装yarn
+```
+npm install -g yarn
+```
+
+- 配镜像源(速度快)
+```
+yarn config set registry https://registry.npmmirror.com
+```
+
+- 安装依赖
+```
+yarn install
+```
+
+- 开发模式运行
+```
+yarn serve
+```
+
+- 编译发布项目
+```
+yarn build
+```
+
+
+其他说明
+----
+
+- 项目使用的 [vue-cli3](https://cli.vuejs.org/guide/), 请更新您的 cli
+
+- 关闭 Eslint (不推荐) 移除 `package.json` 中 `eslintConfig` 整个节点代码
+
+- 修改 Ant Design 配色,在文件 `vue.config.js` 中,其他 less 变量覆盖参考 [ant design](https://ant.design/docs/react/customize-theme-cn) 官方说明
+```ecmascript 6
+  css: {
+    loaderOptions: {
+      less: {
+        modifyVars: {
+          /* less 变量覆盖,用于自定义 ant design 主题 */
+
+          'primary-color': '#F5222D',
+          'link-color': '#F5222D',
+          'border-radius-base': '4px',
+        },
+        javascriptEnabled: true,
+      }
+    }
+  }
+```
+
+
 
 
diff --git a/package.json b/package.json
index c8d1e65..01a13a4 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,9 @@
     "lodash.pick": "^4.4.0",
     "md5": "^2.2.1",
     "nprogress": "^0.2.0",
+    "quill": "^2.0.3",
+    "quill-image-drop-module": "^1.0.3",
+    "quill-image-resize-module": "^3.0.0",
     "viser-vue": "^2.4.4",
     "vue": "^2.6.10",
     "vue-area-linkage": "^5.1.0",
@@ -33,12 +36,15 @@
     "vue-ls": "^3.2.0",
     "vue-photo-preview": "^1.1.3",
     "vue-print-nb-jeecg": "^1.0.9",
+    "vue-quill-editor": "^3.0.6",
     "vue-router": "^3.0.1",
     "vue-splitpane": "^1.0.4",
     "vuedraggable": "^2.20.0",
-    "vuex": "^3.1.0"
+    "vuex": "^3.1.0",
+    "wangeditor": "^4.7.15"
   },
   "devDependencies": {
+    "@babel/plugin-proposal-class-properties": "^7.18.6",
     "@babel/polyfill": "^7.2.5",
     "@vue/cli-plugin-babel": "^3.3.0",
     "@vue/cli-plugin-eslint": "^3.3.0",
diff --git a/src/components/Editor.vue b/src/components/Editor.vue
new file mode 100644
index 0000000..1bf2afb
--- /dev/null
+++ b/src/components/Editor.vue
@@ -0,0 +1,119 @@
+<template>
+  <div class="editor-container" :style="{ width: width }">
+    <div ref="editor"></div>
+  </div>
+</template>
+
+<script>
+import E from 'wangeditor'
+
+export default {
+  name: 'Editor',
+  props: {
+    value: {
+      type: String,
+      default: ''
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: Number,
+      default: 300
+    }
+  },
+  data() {
+    return {
+      editor: null,
+      content: ''
+    }
+  },
+  watch: {
+    value: {
+      handler(val) {
+        if (val !== this.content && this.editor) {
+          this.content = val || ''
+          this.editor.txt.html(this.content)
+        }
+      },
+      immediate: true
+    }
+  },
+  mounted() {
+    this.initEditor()
+  },
+  beforeDestroy() {
+    if (this.editor) {
+      this.editor.destroy()
+      this.editor = null
+    }
+  },
+  methods: {
+    initEditor() {
+      this.editor = new E(this.$refs.editor)
+
+      // 配置菜单栏
+      this.editor.config.menus = [
+        'head',
+        'bold',
+        'fontSize',
+        'fontName',
+        'italic',
+        'underline',
+        'strikeThrough',
+        'foreColor',
+        'backColor',
+        'link',
+        'list',
+        'justify',
+        'quote',
+        'emoticon',
+        'image',
+        'table',
+        'code',
+        'undo',
+        'redo'
+      ]
+
+      // 配置编辑器高度
+      this.editor.config.height = this.height
+
+      // 配置图片上传
+      this.editor.config.uploadImgServer = '/jshERP-boot/common/upload'
+      this.editor.config.uploadFileName = 'file'
+      this.editor.config.uploadImgHooks = {
+        customInsert: (insertImgFn, result) => {
+          if (result.code === 200) {
+            insertImgFn(result.data.url)
+          } else {
+            this.$message.error('图片上传失败')
+          }
+        }
+      }
+
+      // 配置 onchange 回调函数
+      this.editor.config.onchange = (html) => {
+        this.content = html
+        this.$emit('input', html)
+        this.$emit('change', html)
+      }
+
+      // 创建编辑器
+      this.editor.create()
+
+      // 设置初始内容
+      if (this.value) {
+        this.editor.setHtml(this.value)
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.editor-container {
+  border: 1px solid #ccc;
+  z-index: 100;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/system/CloudContent.vue b/src/views/system/CloudContent.vue
new file mode 100644
index 0000000..6c6622e
--- /dev/null
+++ b/src/views/system/CloudContent.vue
@@ -0,0 +1,234 @@
+<template>
+  <a-row :gutter="24">
+    <a-col :md="24">
+      <a-card :style="cardStyle" :bordered="false">
+        <!-- 查询区域 -->
+        <div class="table-page-search-wrapper">
+          <a-form layout="inline" @keyup.enter.native="searchQuery">
+            <a-row :gutter="24">
+              <a-col :md="6" :sm="24">
+                <a-form-item label="标题" :labelCol="labelCol" :wrapperCol="wrapperCol">
+                  <a-input placeholder="请输入标题查询" v-model="queryParam.title"></a-input>
+                </a-form-item>
+              </a-col>
+              <a-col :md="6" :sm="24">
+                <a-form-item label="内容类型" :labelCol="labelCol" :wrapperCol="wrapperCol">
+                  <a-select placeholder="请选择内容类型" v-model="queryParam.type" allowClear>
+                    <a-select-option value="privacy_policy">隐私政策</a-select-option>
+                    <a-select-option value="user_guide">用户指南</a-select-option>
+                  </a-select>
+                </a-form-item>
+              </a-col>
+              <a-col :md="6" :sm="24">
+                <a-form-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol">
+                  <a-select placeholder="请选择状态" v-model="queryParam.status" allowClear>
+                    <a-select-option :value="0">草稿</a-select-option>
+                    <a-select-option :value="1">已发布</a-select-option>
+                  </a-select>
+                </a-form-item>
+              </a-col>
+              <span style="float: left;overflow: hidden;" class="table-page-search-submitButtons">
+                <a-col :md="6" :sm="24">
+                  <a-button type="primary" @click="searchQuery">查询</a-button>
+                  <a-button style="margin-left: 8px" @click="searchReset">重置</a-button>
+                </a-col>
+              </span>
+            </a-row>
+          </a-form>
+        </div>
+        <!-- 操作按钮区域 -->
+        <div class="table-operator" style="margin-top: 5px">
+          <a-button  type="primary" icon="plus" @click="handleAdd">新增</a-button>
+          <a-button  type="primary" icon="delete" @click="batchDel">删除</a-button>
+        </div>
+        <!-- table区域-begin -->
+        <div>
+          <a-table
+            ref="table"
+            size="middle"
+            bordered
+            rowKey="id"
+            :columns="columns"
+            :dataSource="dataSource"
+            :pagination="ipagination"
+            :loading="loading"
+            :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
+            @change="handleTableChange">
+            <span slot="action" slot-scope="text, record">
+              <a @click="handleEdit(record)">编辑</a>
+              <a-divider type="vertical" />
+              <a-popconfirm title="确定删除吗?" @confirm="() => handleDelete(record.id)">
+                <a>删除</a>
+              </a-popconfirm>
+            </span>
+            <template slot="statusSlot" slot-scope="text">
+              <a-tag :color="text === 1 ? 'green' : 'orange'">
+                {{ text === 1 ? '已发布' : '草稿' }}
+              </a-tag>
+            </template>
+            <template slot="typeSlot" slot-scope="text">
+              {{ text === 'privacy_policy' ? '隐私政策' : '用户指南' }}
+            </template>
+          </a-table>
+        </div>
+        <!-- table区域-end -->
+
+        <!-- 表单区域 -->
+        <cloud-content-modal ref="modalForm" @ok="modalFormOk"></cloud-content-modal>
+      </a-card>
+    </a-col>
+  </a-row>
+</template>
+
+<script>
+import { JeecgListMixin } from '@/mixins/JeecgListMixin'
+import CloudContentModal from './modules/CloudContentModal'
+import { deleteAction, getAction } from '@/api/manage'
+
+export default {
+  name: "CloudContentList",
+  mixins:[JeecgListMixin],
+  components: {
+    CloudContentModal
+  },
+  data () {
+    return {
+      description: '云内容管理页面',
+      labelCol: {
+        xs: { span: 24 },
+        sm: { span: 5 },
+      },
+      wrapperCol: {
+        xs: { span: 24 },
+        sm: { span: 16 },
+      },
+      // 查询条件
+      queryParam: {
+        title: '',
+        type: '',
+        status: undefined
+      },
+      // 表头
+      columns: [
+        {
+          title: '#',
+          dataIndex: '',
+          key:'rowIndex',
+          width:60,
+          align:"center",
+          customRender:function (t,r,index) {
+            return parseInt(index)+1;
+          }
+        },
+        {
+          title: '操作',
+          dataIndex: 'action',
+          width:120,
+          align:"center",
+          scopedSlots: { customRender: 'action' }
+        },
+        {
+          title: '标题',
+          align:"left",
+          dataIndex: 'title'
+        },
+        {
+          title: '内容类型',
+          align:"center",
+          dataIndex: 'type',
+          scopedSlots: { customRender: 'typeSlot' }
+        },
+        {
+          title: '版本号',
+          align:"center",
+          dataIndex: 'version'
+        },
+        {
+          title: '状态',
+          align:"center",
+          dataIndex: 'status',
+          scopedSlots: { customRender: 'statusSlot' }
+        },
+        {
+          title: '创建时间',
+          align:"center",
+          dataIndex: 'createTime',
+          width: 180
+        }
+      ],
+      url: {
+        list: "/cloudContent/list",
+        delete: "/cloudContent/delete",
+        deleteBatch: "/cloudContent/deleteBatch",
+        exportXlsUrl: "/cloudContent/exportXls",
+        importExcelUrl: "/cloudContent/importExcel",
+      },
+      btnEnableList: [],
+      cardStyle: {
+        height: '100%'
+      }
+    }
+  },
+  computed: {
+    importExcelUrl: function(){
+      return `${window._CONFIG['domianURL']}/${this.url.importExcelUrl}`;
+    }
+  },
+  methods: {
+    searchReset() {
+      this.queryParam = {
+        title: '',
+        type: '',
+        status: undefined
+      }
+      this.loadData(1);
+    },
+    handleEdit: function (record) {
+      this.$refs.modalForm.edit(record);
+      this.$refs.modalForm.title = "编辑";
+      this.$refs.modalForm.disableSubmit = false;
+      if(this.btnEnableList.indexOf(1)===-1) {
+        this.$refs.modalForm.isReadOnly = true
+      }
+    },
+    handleAdd: function () {
+      this.$refs.modalForm.add();
+      this.$refs.modalForm.title = "新增";
+      this.$refs.modalForm.disableSubmit = false;
+    },
+    handleDelete(id) {
+      deleteAction(this.url.delete, {id: id}).then((res) => {
+        if (res.success) {
+          this.$message.success(res.message);
+          this.loadData();
+        } else {
+          this.$message.warning(res.message);
+        }
+      });
+    },
+    batchDel() {
+      if (this.selectedRowKeys.length <= 0) {
+        this.$message.warning('请选择一条记录!');
+        return;
+      }
+      let ids = "";
+      this.selectedRowKeys.forEach(function(val) {
+        ids+=val+",";
+      });
+      ids = ids.substring(0,ids.length-1);
+      deleteAction(this.url.deleteBatch, {ids: ids}).then((res) => {
+        if (res.success) {
+          this.$message.success(res.message);
+          this.loadData();
+          this.selectedRowKeys = [];
+        } else {
+          this.$message.warning(res.message);
+        }
+      });
+    }
+  }
+}
+</script>
+<style scoped>
+@import '~@assets/less/common.less'
+</style>
\ No newline at end of file
diff --git a/src/views/system/SecurityList.vue b/src/views/system/SecurityList.vue
index 24a044c..912acb2 100644
--- a/src/views/system/SecurityList.vue
+++ b/src/views/system/SecurityList.vue
@@ -9,13 +9,22 @@
             <a-row :gutter="24">
               <a-col :md="6" :sm="24">
                 <a-form-item label="关键词" :labelCol="labelCol" :wrapperCol="wrapperCol">
-                  <a-input placeholder="请输入关键词查询" v-model="queryParam.name"></a-input>
+                  <a-input placeholder="请输入关键词查询" v-model="queryParam.keyword"></a-input>
                 </a-form-item>
               </a-col>
               <a-col :md="6" :sm="24">
-                <a-form-item label="备注" :labelCol="labelCol" :wrapperCol="wrapperCol">
-                  <a-input placeholder="请输入备注查询" v-model="queryParam.description"></a-input>
-                </a-form-item>
+                <a-form-item label="防护类型" :labelCol="labelCol" :wrapperCol="wrapperCol">
+                    <a-select
+                      optionFilterProp="children"
+                      :dropdownMatchSelectWidth="false"
+                      showSearch allow-clear style="width: 100%"
+                      placeholder="请选择防护类型"
+                      v-model="queryParam.type">
+                      <a-select-option v-for="(type,index) in typeList" :value="type.itemValue" :key="index">
+                        {{ type.itemText }}
+                      </a-select-option>
+                    </a-select>
+                  </a-form-item>
               </a-col>
               <span style="float: left;overflow: hidden;" class="table-page-search-submitButtons">
                 <a-col :md="6" :sm="24">
@@ -28,10 +37,10 @@
         </div>
         <!-- 操作按钮区域 -->
         <div class="table-operator"  style="margin-top: 5px">
-          <a-button v-if="btnEnableList.indexOf(1)>-1" @click="handleAdd" type="primary" icon="plus">新增</a-button>
-          <a-button v-if="btnEnableList.indexOf(1)>-1" @click="batchDel" icon="delete">删除</a-button>
-          <a-button v-if="btnEnableList.indexOf(1)>-1" @click="batchSetStatus(true)" icon="check-square">启用</a-button>
-          <a-button v-if="btnEnableList.indexOf(1)>-1" @click="batchSetStatus(false)" icon="close-square">禁用</a-button>
+          <a-button  @click="handleAdd" type="primary" icon="plus">新增</a-button>
+          <a-button  @click="batchDel" icon="delete">删除</a-button>
+          <a-button  @click="batchSetStatus(true)" icon="check-square">启用</a-button>
+          <a-button  @click="batchSetStatus(false)" icon="close-square">禁用</a-button>
         </div>
         <!-- table区域-begin -->
         <div>
@@ -48,27 +57,26 @@
             :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
             @change="handleTableChange">
             <span slot="action" slot-scope="text, record">
-              <a @click="handleSetFunction(record)">分配功能</a>
-              <a-divider type="vertical" />
-              <a @click="handleSetPushBtn(record.id, record.name)">分配按钮</a>
-              <a-divider type="vertical" />
               <a @click="handleEdit(record)">编辑</a>
-              <a-divider v-if="btnEnableList.indexOf(1)>-1" type="vertical" />
-              <a-popconfirm v-if="btnEnableList.indexOf(1)>-1" title="确定删除吗?" @confirm="() => handleDelete(record.id)">
+              <a-divider  type="vertical" />
+              <a-popconfirm  title="确定删除吗?" @confirm="() => handleDelete(record.id)">
                 <a>删除</a>
               </a-popconfirm>
-              <a-modal v-model="roleModalVisible" title="操作提示" @ok="handleRoleTip(record)">
-                <p>保存角色已经操作成功!现在继续<b>分配功能</b>吗?</p>
-              </a-modal>
+          
             </span>
-            <span slot="typeTitle">
+            <!-- <span slot="typeTitle">
               防护类型
-              <a-tooltip title="1、全部数据-该角色对应的用户可以看到全部单据;2、本机构数据-该角色对应的用户可以看到自己所在机构的全部单据;
-                3、个人数据-该角色对应的用户只可以看到自己的单据。单据是指采购入库、销售出库等">
+              <a-tooltip title="">
                 <a-icon type="question-circle" />
               </a-tooltip>
-            </span>
+            </span> -->
             <!-- 状态渲染模板 -->
+
+            
+            <template slot="type" slot-scope="type">
+              <a-tag color="green">{{ getDictItemLabel(typeList,type) }}</a-tag>
+            </template>
+
             <template slot="customRenderFlag" slot-scope="enabled">
               <a-tag v-if="enabled" color="green">启用</a-tag>
               <a-tag v-if="!enabled" color="orange">禁用</a-tag>
@@ -77,36 +85,34 @@
         </div>
         <!-- table区域-end -->
         <!-- 表单区域 -->
-        <role-modal ref="modalForm" @ok="roleModalFormOk"></role-modal>
+        <security-modal ref="modalForm" @ok="roleModalFormOk"></security-modal>
         <role-function-modal ref="roleFunctionModal" @ok="roleFunctionModalFormOk"></role-function-modal>
         <role-push-btn-modal ref="rolePushBtnModal" @ok="modalFormOk"></role-push-btn-modal>
-        <a-modal v-model="roleFunctionModalVisible" title="操作提示" @ok="handleRoleFunctionTip">
-          <p>分配功能已经操作成功!现在继续<b>分配按钮</b>吗?</p>
-        </a-modal>
+        
       </a-card>
     </a-col>
   </a-row>
 </template>
 <!-- f r o m 7 5  2 7 1  8 9 2 0 -->
 <script>
-  import RoleModal from './modules/RoleModal'
+  import SecurityModal from './modules/SecurityModal'
   import RoleFunctionModal from './modules/RoleFunctionModal'
   import RolePushBtnModal from './modules/RolePushBtnModal'
   import { JeecgListMixin } from '@/mixins/JeecgListMixin'
   import JDate from '@/components/jeecg/JDate'
+  import {getAction,deleteAction,postAction } from '@/api/manage'
   export default {
-    name: "RoleList",
+    name: "SecurityList",
     mixins:[JeecgListMixin],
     components: {
-      RoleModal,
+      SecurityModal,
       RoleFunctionModal,
       RolePushBtnModal,
       JDate
     },
     data () {
       return {
-        description: '角色管理页面',
-        roleModalVisible: false,
+        description: '高级安全防护管理页面',
         roleFunctionModalVisible: false,
         currentRoleId: '',
         labelCol: {
@@ -118,8 +124,8 @@
         },
         // 查询条件
         queryParam: {
-          name: '',
-          description: '',
+          keyword: '',
+          type: '',
         },
         // 表头
         columns: [
@@ -144,8 +150,9 @@
             title: '关键词', align:"left", dataIndex: 'keyword', width: 120
           },
           {
-            align:"left", dataIndex: 'type', width: 100,
-            slots: { title: 'typeTitle' }
+            title:'	防护类型',align:"left", dataIndex: 'type', width: 100,
+            slots: { title: 'typeTitle' },
+            scopedSlots: { customRender: 'type' }
           },
           { title: '状态',dataIndex: 'status',width:60,align:"center",
             scopedSlots: { customRender: 'customRenderFlag' }
@@ -157,6 +164,7 @@
           deleteBatch: "/config-security/deleteBatch",
           batchSetStatusUrl: "/config-security/batchSetStatus"
         },
+        typeList:[],
       }
     },
     computed: {
@@ -164,11 +172,56 @@
         return `${window._CONFIG['domianURL']}/${this.url.importExcelUrl}`;
       }
     },
+    
+    mounted(){
+      this.loadDictData();
+    },
     methods: {
-      handleSetFunction(record) {
-        this.$refs.roleFunctionModal.edit(record);
-        this.$refs.roleFunctionModal.title = "分配功能给:" + record.name + "【分配之后请继续分配按钮】"
-        this.$refs.roleFunctionModal.disableSubmit = false;
+
+      searchQuery() {
+        this.ipagination.current = 1;
+        this.loadData();
+      },
+
+      loadData(arg) {
+        //加载数据 若传入参数1则加载第一页的内容
+        // this.ipagination.current = 1;
+
+        let params = this.queryParam
+        params.currentPage = this.ipagination.current;
+        params.pageSize = this.ipagination.pageSize;
+       
+        this.loading = true;
+        getAction(this.url.list, params).then((res) => {
+          if (res.code===200) {
+            this.dataSource = res.data.rows;
+            this.ipagination.total = res.data.total;
+            this.tableAddTotalRow(this.columns, this.dataSource)
+            this.realityPriceTotal = res.data.realityPriceTotal
+          } else if(res.code===510){
+            this.$message.warning(res.data)
+          } else {
+            this.$message.warning(res.data.message)
+          }
+          this.loading = false;
+        })
+      },
+
+      loadDictData() {
+        // 安全防护类型
+        var dictCode='securityType'
+        getAction(`/sysDict/items/dict-code/${dictCode}`).then((res)=>{
+          if(res.code === 200){
+            this.typeList = res.data;
+          }else{
+            this.$message.info(res.data);
+          }
+        })
+      },
+
+      getDictItemLabel(list, value) {
+        const item = list.find(item => item.itemValue === value);
+        return item ? item.itemText : null;
       },
       handleSetPushBtn(roleId, roleName) {
         this.$refs.rolePushBtnModal.edit(roleId);
@@ -178,7 +231,6 @@
       roleModalFormOk() {
         //重载列表
         this.loadData()
-        this.roleModalVisible = true
       },
       roleFunctionModalFormOk(id) {
         //重载列表
@@ -186,37 +238,84 @@
         this.roleFunctionModalVisible = true
         this.currentRoleId = id
       },
-      handleRoleTip(record) {
-        if(record) {
-          this.roleModalVisible = false
-          this.handleSetFunction(record)
-        }
-      },
-      handleRoleFunctionTip() {
-        if(this.currentRoleId) {
-          this.roleFunctionModalVisible = false
-          let roleName = ''
-          for (let i = 0; i < this.dataSource.length; i++) {
-            if(this.dataSource[i].id == this.currentRoleId) {
-              roleName = this.dataSource[i].name
-            }
-          }
-          this.handleSetPushBtn(this.currentRoleId, roleName)
-        }
-      },
+  
       handleAdd: function () {
         this.$refs.modalForm.add();
-        this.$refs.modalForm.title = "新增【保存之后请继续分配功能】";
+        this.$refs.modalForm.title = "新增";
         this.$refs.modalForm.disableSubmit = false;
       },
       handleEdit: function (record) {
+        console.log("edit", record);
         this.$refs.modalForm.edit(record);
-        this.$refs.modalForm.title = "编辑【保存之后请继续分配功能】";
+        this.$refs.modalForm.title = "编辑";
         this.$refs.modalForm.disableSubmit = false;
         if(this.btnEnableList.indexOf(1)===-1) {
           this.$refs.modalForm.isReadOnly = true
         }
-      }
+      },
+      handleDelete(id) {
+        deleteAction(this.url.delete+`/${id}`, {id: id}).then((res) => {
+          if (res.code === 200) {
+            this.loadData();
+            this.$message.success(res.message);
+          } else {
+            this.$message.warning(res.message);
+          }
+        });
+      },
+      batchDel() {
+        if (this.selectedRowKeys.length <= 0) {
+          this.$message.warning('请选择一条记录!');
+          return;
+        }
+        let ids = "";
+        this.selectedRowKeys.forEach(function(val) {
+          ids+=val+",";
+        });
+        ids = ids.substring(0,ids.length-1);
+        deleteAction(this.url.deleteBatch, {ids: ids}).then((res) => {
+          if(res.code === 200){
+            this.$message.success(res.msg);
+            this.loadData();
+            this.selectedRowKeys = [];
+          } else {
+            this.$message.warning(res.msg);
+          }
+        });
+      },
+       batchSetStatus: function (status) {
+            if(!this.url.batchSetStatusUrl){
+              this.$message.error("请设置url.batchSetStatusUrl属性!")
+              return
+            }
+            if (this.selectedRowKeys.length <= 0) {
+              this.$message.warning('请选择一条记录!');
+              return;
+            } else {
+              var ids = "";
+              for (var a = 0; a < this.selectedRowKeys.length; a++) {
+                ids += this.selectedRowKeys[a] + ",";
+              }
+              var that = this;
+              this.$confirm({
+                title: "确认操作",
+                content: "是否操作选中数据?",
+                onOk: function () {
+                  that.loading = true;
+                  postAction(that.url.batchSetStatusUrl, {status: status, ids: ids}).then((res) => {
+                    if(res.code === 200){
+                      that.loadData()
+                    } else {
+                      that.$message.warning(res.data.message);
+                    }
+                  }).finally(() => {
+                    that.loading = false;
+                  });
+                }
+              });
+            }
+          },
+
     }
   }
 </script>
diff --git a/src/views/system/SysDictList.vue b/src/views/system/SysDictList.vue
new file mode 100644
index 0000000..8c2518e
--- /dev/null
+++ b/src/views/system/SysDictList.vue
@@ -0,0 +1,227 @@
+<template>
+  <a-row :gutter="24">
+    <a-col :md="24">
+      <a-card :style="cardStyle" :bordered="false">
+        <!-- 查询区域 -->
+        <div class="table-page-search-wrapper">
+          <a-form layout="inline" @keyup.enter.native="searchQuery">
+            <a-row :gutter="24">
+              <a-col :md="6" :sm="24">
+                <a-form-item label="字典编码" :labelCol="labelCol" :wrapperCol="wrapperCol">
+                  <a-input placeholder="请输入字典编码" v-model="queryParam.dictCode"></a-input>
+                </a-form-item>
+              </a-col>
+              <a-col :md="6" :sm="24">
+                <a-form-item label="字典名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
+                  <a-input placeholder="请输入字典名称" v-model="queryParam.dictName"></a-input>
+                </a-form-item>
+              </a-col>
+              <a-col :md="6" :sm="24">
+                <a-form-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol">
+                  <a-select placeholder="请选择状态" v-model="queryParam.status" allowClear>
+                    <a-select-option :value="0">停用</a-select-option>
+                    <a-select-option :value="1">启用</a-select-option>
+                  </a-select>
+                </a-form-item>
+              </a-col>
+              <span style="float: left;overflow: hidden;" class="table-page-search-submitButtons">
+                <a-col :md="6" :sm="24">
+                  <a-button type="primary" @click="searchQuery">查询</a-button>
+                  <a-button style="margin-left: 8px" @click="searchReset">重置</a-button>
+                </a-col>
+              </span>
+            </a-row>
+          </a-form>
+        </div>
+        <!-- 操作按钮区域 -->
+        <div class="table-operator" style="margin-top: 5px">
+          <a-button type="primary" icon="plus" @click="handleAdd">新增</a-button>
+          <a-button type="primary" icon="delete" @click="batchDel">删除</a-button>
+        </div>
+        <!-- table区域-begin -->
+        <div>
+          <a-table
+            ref="table"
+            size="middle"
+            bordered
+            rowKey="id"
+            :columns="columns"
+            :dataSource="dataSource"
+            :pagination="ipagination"
+            :loading="loading"
+            :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
+            @change="handleTableChange">
+            <span slot="action" slot-scope="text, record">
+              <a @click="handleEdit(record)">编辑</a>
+              <a-divider type="vertical" />
+              <a @click="handleItems(record)">字典项</a>
+              <a-divider type="vertical" />
+              <a-popconfirm title="确定删除吗?" @confirm="() => handleDelete(record.id)">
+                <a>删除</a>
+              </a-popconfirm>
+            </span>
+            <template slot="statusSlot" slot-scope="text">
+              <a-tag :color="text === 1 ? 'green' : 'orange'">
+                {{ text === 1 ? '启用' : '停用' }}
+              </a-tag>
+            </template>
+          </a-table>
+        </div>
+        <!-- table区域-end -->
+        
+        <!-- 表单区域 -->
+        <sys-dict-modal ref="modalForm" @ok="modalFormOk"></sys-dict-modal>
+        <sys-dict-item-modal ref="itemModalForm" @ok="modalFormOk"></sys-dict-item-modal>
+      </a-card>
+    </a-col>
+  </a-row>
+</template>
+
+<script>
+  import { JeecgListMixin } from '@/mixins/JeecgListMixin'
+  import SysDictModal from './modules/SysDictModal'
+  import SysDictItemModal from './modules/SysDictItemModal'
+  import { deleteAction } from '@/api/manage'
+
+  export default {
+    name: "SysDictList",
+    mixins:[JeecgListMixin],
+    components: {
+      SysDictModal,
+      SysDictItemModal
+    },
+    data () {
+      return {
+        description: '字典管理页面',
+        labelCol: {
+          xs: { span: 24 },
+          sm: { span: 5 },
+        },
+        wrapperCol: {
+          xs: { span: 24 },
+          sm: { span: 16 },
+        },
+        // 查询条件
+        queryParam: {
+          dictCode: '',
+          dictName: '',
+          status: undefined
+        },
+        // 表头
+        columns: [
+          {
+            title: '#',
+            dataIndex: '',
+            key:'rowIndex',
+            width:60,
+            align:"center",
+            customRender:function (t,r,index) {
+              return parseInt(index)+1;
+            }
+          },
+          {
+            title: '操作',
+            dataIndex: 'action',
+            width:150,
+            align:"center",
+            scopedSlots: { customRender: 'action' }
+          },
+          {
+            title: '字典编码',
+            align:"left",
+            dataIndex: 'dictCode'
+          },
+          {
+            title: '字典名称',
+            align:"left",
+            dataIndex: 'dictName'
+          },
+          {
+            title: '描述',
+            align:"left",
+            dataIndex: 'description'
+          },
+          {
+            title: '状态',
+            align:"center",
+            dataIndex: 'status',
+            scopedSlots: { customRender: 'statusSlot' }
+          },
+          {
+            title: '创建时间',
+            align:"center",
+            dataIndex: 'createTime',
+            width: 180
+          }
+        ],
+        url: {
+          list: "/sysDict/list",
+          delete: "/sysDict/delete",
+          deleteBatch: "/sysDict/deleteBatch"
+        },
+        btnEnableList: [],
+        cardStyle: {
+          height: '100%'
+        }
+      }
+    },
+    methods: {
+      searchReset() {
+        this.queryParam = {
+          dictCode: '',
+          dictName: '',
+          status: undefined
+        }
+        this.loadData(1);
+      },
+      handleEdit: function (record) {
+        this.$refs.modalForm.edit(record);
+        this.$refs.modalForm.title = "编辑";
+        this.$refs.modalForm.disableSubmit = false;
+      },
+      handleAdd: function () {
+        this.$refs.modalForm.add();
+        this.$refs.modalForm.title = "新增";
+        this.$refs.modalForm.disableSubmit = false;
+      },
+      handleItems: function (record) {
+        this.$refs.itemModalForm.show(record);
+        this.$refs.itemModalForm.title = "字典项管理";
+        this.$refs.itemModalForm.disableSubmit = false;
+      },
+      handleDelete(id) {
+        deleteAction(this.url.delete, {id: id}).then((res) => {
+          if (res.success) {
+            this.$message.success(res.message);
+            this.loadData();
+          } else {
+            this.$message.warning(res.message);
+          }
+        });
+      },
+      batchDel() {
+        if (this.selectedRowKeys.length <= 0) {
+          this.$message.warning('请选择一条记录!');
+          return;
+        }
+        let ids = "";
+        this.selectedRowKeys.forEach(function(val) {
+          ids+=val+",";
+        });
+        ids = ids.substring(0,ids.length-1);
+        deleteAction(this.url.deleteBatch, {ids: ids}).then((res) => {
+          if (res.success) {
+            this.$message.success(res.message);
+            this.loadData();
+            this.selectedRowKeys = [];
+          } else {
+            this.$message.warning(res.message);
+          }
+        });
+      }
+    }
+  }
+</script>
+<style scoped>
+  @import '~@assets/less/common.less'
+</style> 
\ No newline at end of file
diff --git a/src/views/system/modules/CloudContentModal.vue b/src/views/system/modules/CloudContentModal.vue
new file mode 100644
index 0000000..4d8f42b
--- /dev/null
+++ b/src/views/system/modules/CloudContentModal.vue
@@ -0,0 +1,154 @@
+<template>
+  <div ref="container">
+    <a-modal
+      :title="title"
+      :width="1200"
+      :visible="visible"
+      :confirmLoading="confirmLoading"
+      :getContainer="() => $refs.container"
+      :maskStyle="{'top':'93px','left':'154px'}"
+      :wrapClassName="wrapClassNameInfo()"
+      :mask="isDesktop()"
+      :maskClosable="false"
+      @ok="handleOk"
+      @cancel="handleCancel"
+      cancelText="取消"
+      okText="保存"
+      style="top:10%;height: 80%;">
+      <a-spin :spinning="confirmLoading">
+        <a-form :form="form" id="cloudContentModal">
+          <a-row class="form-row" :gutter="24">
+            <a-col :span="12">
+              <a-form-item label="标题" :labelCol="labelCol" :wrapperCol="wrapperCol">
+                <a-input placeholder="请输入标题" v-decorator="['title', validatorRules.title]" />
+              </a-form-item>
+            </a-col>
+            <a-col :span="12">
+              <a-form-item label="内容类型" :labelCol="labelCol" :wrapperCol="wrapperCol">
+                <a-select placeholder="请选择内容类型" v-decorator="['type', validatorRules.type]">
+                  <a-select-option value="privacy_policy">隐私政策</a-select-option>
+                  <a-select-option value="user_guide">用户指南</a-select-option>
+                </a-select>
+              </a-form-item>
+            </a-col>
+            <a-col :span="12">
+              <a-form-item label="版本号" :labelCol="labelCol" :wrapperCol="wrapperCol">
+                <a-input placeholder="请输入版本号" v-decorator="['version', validatorRules.version]" />
+              </a-form-item>
+            </a-col>
+            <a-col :span="12">
+              <a-form-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol">
+                <a-radio-group v-decorator="['status', {initialValue: 0}]">
+                  <a-radio :value="0">草稿</a-radio>
+                  <a-radio :value="1">发布</a-radio>
+                </a-radio-group>
+              </a-form-item>
+            </a-col>
+            <a-col :span="24">
+              <a-form-item label="内容" :labelCol="{span: 2}" :wrapperCol="{span: 22}">
+                <editor v-decorator="['content', validatorRules.content]" :height="400" />
+              </a-form-item>
+            </a-col>
+          </a-row>
+        </a-form>
+      </a-spin>
+    </a-modal>
+  </div>
+</template>
+
+<script>
+import { mixinDevice } from '@/utils/mixin'
+import Editor from '@/components/Editor'
+import pick from 'lodash.pick'
+
+export default {
+  name: "CloudContentModal",
+  mixins: [mixinDevice],
+  components: {
+    Editor
+  },
+  data () {
+    return {
+      title: "操作",
+      visible: false,
+      model: {},
+      labelCol: {
+        xs: { span: 24 },
+        sm: { span: 5 },
+      },
+      wrapperCol: {
+        xs: { span: 24 },
+        sm: { span: 16 },
+      },
+      confirmLoading: false,
+      form: this.$form.createForm(this),
+      validatorRules: {
+        title: {
+          rules: [{ required: true, message: '请输入标题!' }]
+        },
+        type: {
+          rules: [{ required: true, message: '请选择内容类型!' }]
+        },
+        version: {
+          rules: [{ required: true, message: '请输入版本号!' }]
+        },
+        content: {
+          rules: [{ required: true, message: '请输入内容!' }]
+        }
+      }
+    }
+  },
+  methods: {
+    add () {
+      this.edit({})
+    },
+    edit (record) {
+      this.form.resetFields()
+      this.model = Object.assign({}, record)
+      this.visible = true
+      this.$nextTick(() => {
+        this.form.setFieldsValue(pick(this.model,'title', 'type', 'version', 'content', 'status'))
+      })
+    },
+    close () {
+      this.$emit('close')
+      this.visible = false
+    },
+    handleOk () {
+      const that = this
+      this.form.validateFields((err, values) => {
+        if (!err) {
+          that.confirmLoading = true
+          let formData = Object.assign(this.model, values)
+          let obj
+          if(!this.model.id){
+            obj = this.$http.post('/cloudContent', formData)
+          }else{
+            obj = this.$http.put('/cloudContent', formData)
+          }
+          obj.then((res)=>{
+            if(res.code === 200){
+              that.$message.success(res.msg)
+              that.$emit('ok')
+            }else{
+              that.$message.warning(res.msg)
+            }
+          }).finally(() => {
+            that.confirmLoading = false
+            that.close()
+          })
+        }
+      })
+    },
+    handleCancel () {
+      this.close()
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.form-row {
+  padding: 0 24px;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/system/modules/SecurityModal.vue b/src/views/system/modules/SecurityModal.vue
new file mode 100644
index 0000000..e49166b
--- /dev/null
+++ b/src/views/system/modules/SecurityModal.vue
@@ -0,0 +1,173 @@
+<template>
+  <div ref="container">
+    <a-modal
+      :title="title"
+      :width="800"
+      :visible="visible"
+      :confirmLoading="confirmLoading"
+      :getContainer="() => $refs.container"
+      :maskStyle="{'top':'93px','left':'154px'}"
+      :wrapClassName="wrapClassNameInfo()"
+      :mask="isDesktop()"
+      :maskClosable="false"
+      @ok="handleOk"
+      @cancel="handleCancel"
+      cancelText="取消"
+      okText="保存">
+      <a-spin :spinning="confirmLoading">
+        <a-form :form="form">
+          <a-row class="form-row" :gutter="24">
+            <a-col :span="24">
+              <a-form-item label="关键词" :labelCol="labelCol" :wrapperCol="wrapperCol">
+                <a-input placeholder="请输入关键词" v-decorator="['keyword', validatorRules.keyword]" />
+              </a-form-item>
+            </a-col>
+            <a-col :span="24">
+              <a-form-item label="防护类型" :labelCol="labelCol" :wrapperCol="wrapperCol">
+                <!-- <a-input placeholder="请输入防护类型" v-decorator="['type', validatorRules.type]" /> -->
+                <a-select
+                      optionFilterProp="children"
+                      :dropdownMatchSelectWidth="false"
+                      showSearch allow-clear style="width: 100%"
+                      placeholder="请选择防护类型"
+                      v-decorator="['type', validatorRules.type]"
+                      >
+                      <a-select-option v-for="(type,index) in typeList" :value="type.itemValue" :key="index">
+                        {{ type.itemText }}
+                      </a-select-option>
+                    </a-select>
+              </a-form-item>
+            </a-col>
+            <a-col :span="24">
+              <a-form-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol">
+                <a-radio-group v-decorator="['status', {initialValue: 1}]">
+                  <a-radio :value="1">启用</a-radio>
+                  <a-radio :value="0">禁用</a-radio>
+                </a-radio-group>
+              </a-form-item>
+            </a-col>
+          </a-row>
+        </a-form>
+      </a-spin>
+    </a-modal>
+  </div>
+</template>
+
+<script>
+  import { mixinDevice } from '@/utils/mixin'
+  import { postAction, putAction,getAction } from '@/api/manage'
+  import pick from 'lodash.pick'
+
+  export default {
+    name: "SysDictModal",
+    mixins: [mixinDevice],
+    data () {
+      return {
+        title:"操作",
+        visible: false,
+        model: {},
+        labelCol: {
+          xs: { span: 24 },
+          sm: { span: 5 },
+        },
+        wrapperCol: {
+          xs: { span: 24 },
+          sm: { span: 16 },
+        },
+        confirmLoading: false,
+        form: this.$form.createForm(this),
+        validatorRules:{
+          keyword: {
+            rules: [{
+              required: true,
+              message: '请输入关键词!'
+            },
+            {
+              max: 50,
+              message: '关键词长度不能超过50个字符!'
+            }
+          ]
+          },
+          type: {
+            rules: [{
+              required: true,
+              message: '请输入防护类型!'
+            }]
+          }
+        },
+        url: {
+          add: "/config-security",
+          edit: "/config-security"
+        },
+        typeList:[],
+      }
+    },
+    mounted(){
+      this.loadDictData();
+    },
+    methods: {
+      loadDictData() {
+        // 安全防护类型
+        var dictCode='securityType'
+        getAction(`/sysDict/items/dict-code/${dictCode}`).then((res)=>{
+          if(res.code === 200){
+            this.typeList = res.data;
+          }else{
+            this.$message.info(res.data);
+          }
+        })
+      },
+      add () {
+        this.edit({})
+      },
+      edit (record) {
+        debugger;
+        this.form.resetFields()
+        this.model = Object.assign({}, record)
+        this.visible = true
+        this.$nextTick(() => {
+          this.form.setFieldsValue(pick(this.model,'keyword','type','status'))
+        })
+      },
+      close () {
+        this.$emit('close')
+        this.visible = false
+      },
+      handleOk () {
+        const that = this
+        this.form.validateFields((err, values) => {
+          if (!err) {
+            that.confirmLoading = true
+            let formData = Object.assign(this.model, values)
+            let obj
+            if(!this.model.id){
+              obj = postAction(this.url.add, formData)
+            }else{
+              obj = putAction(this.url.edit, formData)
+            }
+            obj.then((res)=>{
+              if(res.code === 200){
+                that.$message.success(res.msg)
+                that.$emit('ok')
+              }else{
+                that.$message.warning(res.msg)
+              }
+            }).finally(() => {
+              that.confirmLoading = false
+              that.close()
+            })
+          }
+        })
+      },
+      handleCancel () {
+        this.close()
+      }
+    }
+  }
+</script>
+
+<style lang="less" scoped>
+  .form-row {
+    padding: 0 24px;
+  }
+</style> 
\ No newline at end of file
diff --git a/src/views/system/modules/SysDictItemModal.vue b/src/views/system/modules/SysDictItemModal.vue
new file mode 100644
index 0000000..8fa48f2
--- /dev/null
+++ b/src/views/system/modules/SysDictItemModal.vue
@@ -0,0 +1,267 @@
+<template>
+  <div ref="container">
+    <a-modal
+      :title="title"
+      :width="1000"
+      :visible="visible"
+      :confirmLoading="confirmLoading"
+      :getContainer="() => $refs.container"
+      :maskStyle="{'top':'93px','left':'154px'}"
+      :wrapClassName="wrapClassNameInfo()"
+      :mask="isDesktop()"
+      :maskClosable="false"
+      @ok="handleOk"
+      @cancel="handleCancel"
+      cancelText="关闭">
+      <template slot="footer">
+        <a-button key="back" @click="handleCancel">关闭</a-button>
+      </template>
+      <a-card :bordered="false">
+        <!-- 操作按钮区域 -->
+        <div class="table-operator">
+          <a-button type="primary" icon="plus" @click="handleAdd">新增</a-button>
+        </div>
+        <!-- table区域-begin -->
+        <div>
+          <a-table
+            ref="table"
+            size="middle"
+            bordered
+            rowKey="id"
+            :columns="columns"
+            :dataSource="dataSource"
+            :pagination="false"
+            :loading="loading">
+            <span slot="action" slot-scope="text, record">
+              <a @click="handleEdit(record)">编辑</a>
+              <a-divider type="vertical" />
+              <a-popconfirm title="确定删除吗?" @confirm="() => handleDelete(record.id)">
+                <a>删除</a>
+              </a-popconfirm>
+            </span>
+            <template slot="statusSlot" slot-scope="text">
+              <a-tag :color="text === 1 ? 'green' : 'orange'">
+                {{ text === 1 ? '启用' : '停用' }}
+              </a-tag>
+            </template>
+          </a-table>
+        </div>
+        <!-- table区域-end -->
+      </a-card>
+      
+      <a-modal
+        :title="itemTitle"
+        :width="600"
+        :visible="itemVisible"
+        :confirmLoading="itemConfirmLoading"
+        @ok="handleItemOk"
+        @cancel="handleItemCancel"
+        cancelText="取消"
+        okText="保存">
+        <a-form :form="itemForm">
+          <a-form-item label="字典项文本" :labelCol="labelCol" :wrapperCol="wrapperCol">
+            <a-input placeholder="请输入字典项文本" v-decorator="['itemText', validatorRules.itemText]" />
+          </a-form-item>
+          <a-form-item label="字典项值" :labelCol="labelCol" :wrapperCol="wrapperCol">
+            <a-input placeholder="请输入字典项值" v-decorator="['itemValue', validatorRules.itemValue]" />
+          </a-form-item>
+          <a-form-item label="描述" :labelCol="labelCol" :wrapperCol="wrapperCol">
+            <a-textarea placeholder="请输入描述" v-decorator="['description']" :rows="2" />
+          </a-form-item>
+          <a-form-item label="排序" :labelCol="labelCol" :wrapperCol="wrapperCol">
+            <a-input-number placeholder="请输入排序" v-decorator="['sortOrder', {initialValue: 0}]" :min="0" style="width: 100%" />
+          </a-form-item>
+          <a-form-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol">
+            <a-radio-group v-decorator="['status', {initialValue: 1}]">
+              <a-radio :value="1">启用</a-radio>
+              <a-radio :value="0">停用</a-radio>
+            </a-radio-group>
+          </a-form-item>
+        </a-form>
+      </a-modal>
+    </a-modal>
+  </div>
+</template>
+
+<script>
+  import { mixinDevice } from '@/utils/mixin'
+  import { getAction, postAction, putAction, deleteAction } from '@/api/manage'
+  import pick from 'lodash.pick'
+
+  export default {
+    name: "SysDictItemModal",
+    mixins: [mixinDevice],
+    data () {
+      return {
+        title:"字典项管理",
+        visible: false,
+        model: {},
+        labelCol: {
+          xs: { span: 24 },
+          sm: { span: 5 },
+        },
+        wrapperCol: {
+          xs: { span: 24 },
+          sm: { span: 16 },
+        },
+        confirmLoading: false,
+        loading: false,
+        dataSource: [],
+        
+        // 字典项表单相关
+        itemTitle: "",
+        itemVisible: false,
+        itemConfirmLoading: false,
+        itemModel: {},
+        itemForm: this.$form.createForm(this),
+        validatorRules: {
+          itemText: {
+            rules: [{
+              required: true,
+              message: '请输入字典项文本!'
+            }]
+          },
+          itemValue: {
+            rules: [{
+              required: true,
+              message: '请输入字典项值!'
+            }]
+          }
+        },
+        
+        // 表头
+        columns: [
+          {
+            title: '#',
+            dataIndex: '',
+            key:'rowIndex',
+            width:60,
+            align:"center",
+            customRender:function (t,r,index) {
+              return parseInt(index)+1;
+            }
+          },
+          {
+            title: '操作',
+            dataIndex: 'action',
+            width:120,
+            align:"center",
+            scopedSlots: { customRender: 'action' }
+          },
+          {
+            title: '文本',
+            align:"left",
+            dataIndex: 'itemText'
+          },
+          {
+            title: '值',
+            align:"left",
+            dataIndex: 'itemValue'
+          },
+          {
+            title: '描述',
+            align:"left",
+            dataIndex: 'description'
+          },
+          {
+            title: '排序',
+            align:"center",
+            dataIndex: 'sortOrder',
+            width: 80
+          },
+          {
+            title: '状态',
+            align:"center",
+            dataIndex: 'status',
+            width: 80,
+            scopedSlots: { customRender: 'statusSlot' }
+          }
+        ]
+      }
+    },
+    methods: {
+      show (record) {
+        this.model = Object.assign({}, record)
+        this.visible = true
+        this.loadData()
+      },
+      loadData () {
+        this.loading = true
+        getAction(`/sysDict/items/${this.model.id}`).then((res) => {
+          if (res.code === 200) {
+            this.dataSource = res.data
+          }
+          this.loading = false
+        })
+      },
+      handleAdd () {
+        this.itemTitle = "新增字典项"
+        this.itemForm.resetFields()
+        this.itemModel = {}
+        this.itemVisible = true
+      },
+      handleEdit (record) {
+        this.itemTitle = "编辑字典项"
+        this.itemForm.resetFields()
+        this.itemModel = Object.assign({}, record)
+        this.itemVisible = true
+        this.$nextTick(() => {
+          this.itemForm.setFieldsValue(pick(this.itemModel,'itemText','itemValue','description','sortOrder','status'))
+        })
+      },
+      handleDelete (id) {
+        deleteAction(`/sysDict/item/${id}`).then((res) => {
+          if (res.code === 200) {
+            this.$message.success(res.msg)
+            this.loadData()
+          } else {
+            this.$message.warning(res.msg)
+          }
+        })
+      },
+      handleItemOk () {
+        const that = this
+        this.itemForm.validateFields((err, values) => {
+          if (!err) {
+            that.itemConfirmLoading = true
+            let formData = Object.assign(this.itemModel, values)
+            formData.dictId = this.model.id
+            let obj
+            if(!this.itemModel.id){
+              obj = postAction('/sysDict/item', formData)
+            }else{
+              obj = putAction('/sysDict/item', formData)
+            }
+            obj.then((res)=>{
+              if(res.code === 200){
+                that.$message.success(res.msg)
+                that.loadData()
+                that.itemVisible = false
+              }else{
+                that.$message.warning(res.msg)
+              }
+            }).finally(() => {
+              that.itemConfirmLoading = false
+            })
+          }
+        })
+      },
+      handleItemCancel () {
+        this.itemVisible = false
+      },
+      handleCancel () {
+        this.close()
+      },
+      close () {
+        this.$emit('close')
+        this.visible = false
+      }
+    }
+  }
+</script>
+
+<style lang="less" scoped>
+  .table-operator {
+    margin-bottom: 18px;
+  }
+</style> 
\ No newline at end of file
diff --git a/src/views/system/modules/SysDictModal.vue b/src/views/system/modules/SysDictModal.vue
new file mode 100644
index 0000000..944e3ed
--- /dev/null
+++ b/src/views/system/modules/SysDictModal.vue
@@ -0,0 +1,146 @@
+<template>
+  <div ref="container">
+    <a-modal
+      :title="title"
+      :width="800"
+      :visible="visible"
+      :confirmLoading="confirmLoading"
+      :getContainer="() => $refs.container"
+      :maskStyle="{'top':'93px','left':'154px'}"
+      :wrapClassName="wrapClassNameInfo()"
+      :mask="isDesktop()"
+      :maskClosable="false"
+      @ok="handleOk"
+      @cancel="handleCancel"
+      cancelText="取消"
+      okText="保存">
+      <a-spin :spinning="confirmLoading">
+        <a-form :form="form">
+          <a-row class="form-row" :gutter="24">
+            <a-col :span="12">
+              <a-form-item label="字典编码" :labelCol="labelCol" :wrapperCol="wrapperCol">
+                <a-input placeholder="请输入字典编码" v-decorator="['dictCode', validatorRules.dictCode]" />
+              </a-form-item>
+            </a-col>
+            <a-col :span="12">
+              <a-form-item label="字典名称" :labelCol="labelCol" :wrapperCol="wrapperCol">
+                <a-input placeholder="请输入字典名称" v-decorator="['dictName', validatorRules.dictName]" />
+              </a-form-item>
+            </a-col>
+            <a-col :span="24">
+              <a-form-item label="描述" :labelCol="labelCol" :wrapperCol="wrapperCol">
+                <a-textarea placeholder="请输入描述" v-decorator="['description']" :rows="2" />
+              </a-form-item>
+            </a-col>
+            <a-col :span="12">
+              <a-form-item label="状态" :labelCol="labelCol" :wrapperCol="wrapperCol">
+                <a-radio-group v-decorator="['status', {initialValue: 1}]">
+                  <a-radio :value="1">启用</a-radio>
+                  <a-radio :value="0">停用</a-radio>
+                </a-radio-group>
+              </a-form-item>
+            </a-col>
+          </a-row>
+        </a-form>
+      </a-spin>
+    </a-modal>
+  </div>
+</template>
+
+<script>
+  import { mixinDevice } from '@/utils/mixin'
+  import { postAction, putAction } from '@/api/manage'
+  import pick from 'lodash.pick'
+
+  export default {
+    name: "SysDictModal",
+    mixins: [mixinDevice],
+    data () {
+      return {
+        title:"操作",
+        visible: false,
+        model: {},
+        labelCol: {
+          xs: { span: 24 },
+          sm: { span: 5 },
+        },
+        wrapperCol: {
+          xs: { span: 24 },
+          sm: { span: 16 },
+        },
+        confirmLoading: false,
+        form: this.$form.createForm(this),
+        validatorRules:{
+          dictCode: {
+            rules: [{
+              required: true,
+              message: '请输入字典编码!'
+            }]
+          },
+          dictName: {
+            rules: [{
+              required: true,
+              message: '请输入字典名称!'
+            }]
+          }
+        },
+        url: {
+          add: "/sysDict",
+          edit: "/sysDict"
+        }
+      }
+    },
+    methods: {
+      add () {
+        this.edit({})
+      },
+      edit (record) {
+        this.form.resetFields()
+        this.model = Object.assign({}, record)
+        this.visible = true
+        this.$nextTick(() => {
+          this.form.setFieldsValue(pick(this.model,'dictCode','dictName','description','status'))
+        })
+      },
+      close () {
+        this.$emit('close')
+        this.visible = false
+      },
+      handleOk () {
+        const that = this
+        this.form.validateFields((err, values) => {
+          if (!err) {
+            that.confirmLoading = true
+            let formData = Object.assign(this.model, values)
+            let obj
+            if(!this.model.id){
+              obj = postAction(this.url.add, formData)
+            }else{
+              obj = putAction(this.url.edit, formData)
+            }
+            obj.then((res)=>{
+              if(res.code === 200){
+                that.$message.success(res.msg)
+                that.$emit('ok')
+              }else{
+                that.$message.warning(res.msg)
+              }
+            }).finally(() => {
+              that.confirmLoading = false
+              that.close()
+            })
+          }
+        })
+      },
+      handleCancel () {
+        this.close()
+      }
+    }
+  }
+</script>
+
+<style lang="less" scoped>
+  .form-row {
+    padding: 0 24px;
+  }
+</style> 
\ No newline at end of file

--
Gitblit v1.9.3