Ver código fonte

【模版目录】更新管理端源码

吴昊天 3 anos atrás
pai
commit
6f9dc32e8a

+ 25 - 8
template/admin/package-lock.json

@@ -4992,8 +4992,7 @@
     "big.js": {
       "version": "5.2.2",
       "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
-      "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
-      "dev": true
+      "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="
     },
     "binary-extensions": {
       "version": "2.0.0",
@@ -8004,8 +8003,7 @@
     "emojis-list": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
-      "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
-      "dev": true
+      "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q=="
     },
     "encodeurl": {
       "version": "1.0.2",
@@ -12822,7 +12820,6 @@
       "version": "2.1.3",
       "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
       "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
-      "dev": true,
       "requires": {
         "minimist": "^1.2.5"
       }
@@ -14446,9 +14443,29 @@
       "integrity": "sha1-sr52n6MZQL6e7qZGnAdeNQBvo9M="
     },
     "monaco-editor": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.21.3.tgz",
-      "integrity": "sha512-9N7wATLpi+googstvtm6IKg97vPQ77FDYEpkow5tLriM/VJ0DaTRyUP4UVzcoH7KlPDX+e/rE7/imcOUeGkT6g=="
+      "version": "0.28.1",
+      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.28.1.tgz",
+      "integrity": "sha512-P1vPqxB4B1ZFzTeR1ScggSp9/5NoQrLCq88fnlNUsuRAP1usEBN4TIpI2lw0AYIZNVIanHk0qwjze2uJwGOHUw=="
+    },
+    "monaco-editor-webpack-plugin": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-4.2.0.tgz",
+      "integrity": "sha512-/P3sFiEgBl+Y50he4mbknMhbLJVop5gBUZiPS86SuHUDOOnQiQ5rL1jU5lwt1XKAwMEkhwZbUwqaHxTPkb1Utw==",
+      "requires": {
+        "loader-utils": "^2.0.0"
+      },
+      "dependencies": {
+        "loader-utils": {
+          "version": "2.0.2",
+          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
+          "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^2.1.2"
+          }
+        }
+      }
     },
     "move-concurrently": {
       "version": "1.0.1",

+ 1 - 1
template/admin/package.json

@@ -52,7 +52,7 @@
     "uglifyjs-webpack-plugin": "^2.2.0",
     "v-org-tree": "^1.0.6",
     "v-viewer": "^1.5.1",
-    "view-design": "^4.4.0",
+    "view-design": "^4.7.0",
     "vue": "^2.5.10",
     "vue-awesome-swiper": "^4.1.1",
     "vue-codemirror": "^4.0.6",

+ 13 - 0
template/admin/src/api/product.js

@@ -388,3 +388,16 @@ export function importCard(data) {
     params: data,
   });
 }
+
+/**
+ * @description 商品批量设置
+ * @param {Number} param id {Number} 属性id
+ * @param {Object} param data {Object} 传值参数
+ */
+ export function batchSetting(data) {
+  return request({
+    url: `product/batch/setting`,
+    method: 'POST',
+    data,
+  });
+}

+ 11 - 0
template/admin/src/api/setting.js

@@ -1077,4 +1077,15 @@ export function saveType(type) {
     method: 'get',
     params: data,
   });
+}
+/**
+ * 添加语言地区表单
+ * @param {*} id 
+ * @returns 
+ */
+ export function langCountryForm(id) {
+  return request({
+    url: `setting/lang_country/form/${id}`,
+    method: 'get',
+  });
 }

+ 11 - 0
template/admin/src/api/user.js

@@ -310,6 +310,17 @@ export function updtaeAdmin(data) {
     data,
   });
 }
+/**
+ * @description 文件管理 --- 设置密码
+ * data 请求参数
+ */
+export function setFilePassword(data) {
+  return request({
+    url: `setting/set_file_password`,
+    method: 'PUT',
+    data,
+  });
+}
 
 /**
  * @description 个人中心 --- 设置会员等级

+ 7 - 0
template/admin/src/components/main/components/user/user.vue

@@ -11,6 +11,7 @@
         <!--消息中心<Badge style="margin-left: 10px" :count="messageUnreadCount"></Badge>-->
         <!--</DropdownItem>-->
         <DropdownItem name="userCenter"><Icon type="ios-contact-outline" class="iconImg" />个人中心</DropdownItem>
+		    <DropdownItem v-show = "info.level === 0" name="fileEdit"><Icon type="ios-document-outline" class="iconImg" />文件管理</DropdownItem>
         <DropdownItem name="logout"><Icon type="ios-log-out" class="iconImg" />退出登录</DropdownItem>
       </DropdownMenu>
     </Dropdown>
@@ -75,6 +76,9 @@ export default {
     userCenter() {
       this.$router.push('/admin/system/user');
     },
+    fileEdit(){
+      this.$router.push('/admin/system/files');
+    },
     message() {
       this.$router.push({
         name: 'message_page',
@@ -91,6 +95,9 @@ export default {
         case 'message':
           this.message();
           break;
+        case 'fileEdit':
+          this.fileEdit();
+        break;
       }
     },
   },

+ 274 - 2
template/admin/src/pages/product/productList/index.vue

@@ -45,6 +45,20 @@
           ><Button type="primary" class="bnt mr15" icon="md-add">添加商品</Button></router-link
         >
         <Button v-auth="['product-crawl-save']" type="success" class="bnt mr15" @click="onCopy">商品采集</Button>
+        <Dropdown class="bnt mr15" @on-click="batchSelect">
+          <Button type="info">
+            批量操作
+            <Icon type="ios-arrow-down"></Icon>
+          </Button>
+          <DropdownMenu slot="list">
+            <DropdownItem :name="1">分类设置</DropdownItem>
+            <DropdownItem :name="2">物流设置</DropdownItem>
+            <DropdownItem :name="3">赠送积分</DropdownItem>
+            <DropdownItem :name="4">赠送优惠券</DropdownItem>
+            <DropdownItem :name="5">用户标签</DropdownItem>
+            <DropdownItem :name="6">商品推荐</DropdownItem>
+          </DropdownMenu>
+        </Dropdown>
         <Button
           v-auth="['product-product-product_show']"
           class="bnt mr15"
@@ -133,11 +147,125 @@
     >
       <tao-bao ref="taobaos" v-if="modals" @on-close="onClose"></tao-bao>
     </Modal>
+    <Modal
+      v-model="batchModal"
+      class="batch-box"
+      scrollable
+      closable
+      title="批量设置"
+      :mask-closable="false"
+      width="1000"
+      @on-ok="batchSub"
+      @on-visible-change="clearBatchData"
+    >
+      <Form
+        class="batchFormData"
+        ref="batchFormData"
+        :rules="ruleBatch"
+        :model="batchFormData"
+        :label-width="120"
+        label-position="right"
+        @submit.native.prevent
+      >
+        <Row :gutter="24" type="flex">
+          <Col span="24" v-if="batchType == 1">
+            <Divider orientation="left">基础设置</Divider>
+            <FormItem label="商品分类:" prop="cate_id">
+              <Select v-model="batchFormData.cate_id" placeholder="请选择商品分类" multiple class="perW20">
+                <Option v-for="item in treeSelect" :disabled="item.pid === 0" :value="item.id" :key="item.id">{{
+                  item.html + item.cate_name
+                }}</Option>
+              </Select>
+            </FormItem>
+          </Col>
+          <Col span="24" v-if="batchType == 2">
+            <Divider orientation="left">物流设置</Divider>
+            <FormItem label="物流方式:" prop="logistics">
+              <CheckboxGroup v-model="batchFormData.logistics" @on-change="logisticsBtn">
+                <Checkbox label="1">快递</Checkbox>
+
+                <Checkbox label="2">到店核销</Checkbox>
+              </CheckboxGroup>
+            </FormItem>
+            <FormItem label="运费设置:">
+              <RadioGroup v-model="batchFormData.freight">
+                <!-- <Radio :label="1">包邮</Radio> -->
+                <Radio :label="2">固定邮费</Radio>
+                <Radio :label="3">运费模板</Radio>
+              </RadioGroup>
+            </FormItem>
+            <FormItem label="" v-if="batchFormData.freight == 2">
+              <div class="acea-row">
+                <InputNumber :min="0" v-model="batchFormData.postage" placeholder="请输入金额" class="perW20 maxW" />
+              </div>
+            </FormItem>
+            <FormItem label="" v-if="batchFormData.freight == 3" prop="temp_id">
+              <div class="acea-row">
+                <Select v-model="batchFormData.temp_id" clearable placeholder="请选择运费模板" class="perW20 maxW">
+                  <Option v-for="(item, index) in templateList" :value="item.id" :key="index">{{ item.name }}</Option>
+                </Select>
+              </div>
+            </FormItem>
+          </Col>
+          <Col span="24" v-if="[3, 4, 5, 6].includes(batchType)">
+            <Divider orientation="left" v-if="[3, 4, 5, 6].includes(batchType)">营销设置</Divider>
+            <FormItem label="赠送积分:" prop="give_integral" v-if="batchType == 3">
+              <InputNumber v-model="batchFormData.give_integral" :min="0" :max="999999" placeholder="请输入积分" />
+            </FormItem>
+            <FormItem label="赠送优惠券:" v-if="batchType == 4">
+              <div v-if="couponName.length" class="mb20">
+                <Tag closable v-for="(item, index) in couponName" :key="index" @on-close="handleClose(item)">{{
+                  item.title
+                }}</Tag>
+              </div>
+              <Button type="primary" @click="addCoupon">添加优惠券</Button>
+            </FormItem>
+            <FormItem label="关联用户标签:" prop="label_id" v-if="batchType == 5">
+              <div style="display: flex">
+                <div class="labelInput acea-row row-between-wrapper" @click="openLabel">
+                  <div style="width: 90%">
+                    <div v-if="dataLabel.length">
+                      <Tag closable v-for="(item, index) in dataLabel" @on-close="closeLabel(item)" :key="index">{{
+                        item.label_name
+                      }}</Tag>
+                    </div>
+                    <span class="span" v-else>选择用户关联标签</span>
+                  </div>
+                  <div class="iconfont iconxiayi"></div>
+                </div>
+              </div>
+            </FormItem>
+            <FormItem label="商品推荐:" v-if="batchType == 6">
+              <CheckboxGroup v-model="batchFormData.recommend">
+                <Checkbox label="is_hot">热卖单品</Checkbox>
+                <Checkbox label="is_benefit">促销单品</Checkbox>
+                <Checkbox label="is_best">精品推荐</Checkbox>
+                <Checkbox label="is_new">首发新品</Checkbox>
+                <Checkbox label="is_good">优品推荐</Checkbox>
+              </CheckboxGroup>
+            </FormItem>
+          </Col>
+        </Row>
+      </Form>
+    </Modal>
+    <!-- 用户标签 -->
+    <Modal
+      v-model="labelShow"
+      scrollable
+      title="请选择用户标签"
+      :closable="false"
+      width="500"
+      :footer-hide="true"
+      :mask-closable="false"
+    >
+      <userLabel ref="userLabel" @activeData="activeData" @close="labelClose"></userLabel>
+    </Modal>
     <!-- 商品弹窗 -->
     <div v-if="isProductBox">
       <div class="bg" @click="isProductBox = false"></div>
       <goodsDetail :goodsId="goodsId"></goodsDetail>
     </div>
+    <coupon-list ref="couponTemplates" @nameId="nameId" :couponids="batchFormData.coupon_ids"></coupon-list>
   </div>
 </template>
 
@@ -148,19 +276,23 @@ import toExcel from '../../../utils/Excel.js';
 import { mapState } from 'vuex';
 import taoBao from './taoBao';
 import goodsDetail from './components/goodsDetail.vue';
+import couponList from '@/components/couponList';
 
 import {
   getGoodHeade,
   getGoods,
   PostgoodsIsShow,
-  treeListApi,
+  treeListApi, // 分类列表
   productShowApi,
   productUnshowApi,
   storeProductApi,
+  batchSetting,
 } from '@/api/product';
+import userLabel from '@/components/labelList';
+
 export default {
   name: 'product_productList',
-  components: { expandRow, attribute, taoBao, goodsDetail },
+  components: { expandRow, attribute, taoBao, goodsDetail, userLabel, couponList },
   computed: {
     ...mapState('userLevel', ['categoryId']),
   },
@@ -168,6 +300,24 @@ export default {
     return {
       template: false,
       modals: false,
+      batchModal: false,
+      labelShow: false,
+      batchType: 1, // 批量设置类型
+      batchFormData: {
+        cate_id: [],
+        logistics: [],
+        freight: 2,
+        postage: 0,
+        temp_id: null,
+        give_integral: 0,
+        label_id: [],
+        coupon_ids: [],
+        recommend: [],
+      },
+      ruleBatch: {},
+      couponName: [], // 优惠券
+      dataLabel: [], // 标签
+      templateList: [], // 运费模版
       grid: {
         xl: 6,
         lg: 8,
@@ -278,6 +428,7 @@ export default {
       ids: [],
       goodsId: '',
       isProductBox: false,
+      treeSelect: [],
     };
   },
   watch: {
@@ -298,10 +449,104 @@ export default {
     }
   },
   methods: {
+    batchSub() {
+      console.log(this.selectedIds, this.ids);
+      let data = this.batchFormData;
+      data.ids = this.ids;
+      data.type = this.batchType;
+      let activeIds = [];
+      this.dataLabel.forEach((item) => {
+        activeIds.push(item.id);
+      });
+      data.label_id = activeIds;
+      batchSetting(data)
+        .then((res) => {
+          this.$Message.success(res.msg);
+          this.getDataList();
+          this.clearBatchData();
+        })
+        .catch((err) => {
+          this.$Message.error(err.msg);
+        });
+    },
+    clearBatchData(status) {
+      if (!status) {
+        this.batchFormData = {
+          cate_id: [],
+          logistics: [],
+          freight: 0,
+          postage: null,
+          temp_id: null,
+          give_integral: null,
+          label_id: [],
+          coupon_ids: [],
+          recommend: [],
+        };
+        this.ids = [];
+        this.dataLabel = [];
+        this.clearAll(false);
+      }
+    },
+    // 批量设置商品
+    batchSelect(type) {
+      if (this.ids.length === 0) {
+        this.$Message.warning('请选择要设置的商品');
+      } else {
+        this.batchType = type;
+        this.batchModal = true;
+        this.productGetTemplate();
+      }
+    },
+    activeData(dataLabel) {
+      this.labelShow = false;
+      this.dataLabel = dataLabel;
+    },
+    nameId(id, names) {
+      this.batchFormData.coupon_ids = id;
+      this.couponName = this.unique(names);
+    },
+    handleClose(name) {
+      let index = this.couponName.indexOf(name);
+      this.couponName.splice(index, 1);
+      this.formValidate.coupon_ids.splice(index, 1);
+    },
+    //对象数组去重;
+    unique(arr) {
+      const res = new Map();
+      return arr.filter((arr) => !res.has(arr.id) && res.set(arr.id, 1));
+    },
+    // 获取运费模板;
+    productGetTemplate() {
+      productGetTemplateApi().then((res) => {
+        this.templateList = res.data;
+      });
+    },
+    // 标签弹窗关闭
+    labelClose() {
+      this.labelShow = false;
+    },
     look(row) {
       this.goodsId = row.id;
       this.isProductBox = true;
     },
+    // 物流方式
+    logisticsBtn(e) {
+      this.batchFormData.logistics = e;
+    },
+    // 关联用户标签
+    openLabel(row) {
+      this.labelShow = true;
+      this.$refs.userLabel.userLabel(JSON.parse(JSON.stringify(this.dataLabel)));
+    },
+    closeLabel(label) {
+      let index = this.dataLabel.indexOf(this.dataLabel.filter((d) => d.id == label.id)[0]);
+      this.dataLabel.splice(index, 1);
+    },
+    // 添加优惠券
+    addCoupon() {
+      this.$refs.couponTemplates.isTemplate = true;
+      this.$refs.couponTemplates.tableList();
+    },
     getPath() {
       this.columns2 = [...this.columns];
       if (name !== '1' && name !== '2') {
@@ -376,6 +621,7 @@ export default {
           });
       }
     },
+
     // 全选
     // onSelectTab (selection) {
     //     let data = []
@@ -412,6 +658,9 @@ export default {
         this.setChecked();
       });
     },
+    clearAll(status) {
+      this.$refs.table.selectAll(status);
+    },
     //  取消某一行
     handleCancelRow(selection, row) {
       this.selectedIds.delete(row.id);
@@ -605,7 +854,13 @@ export default {
     overflow: auto;
   }
 }
+.batch-box{
+   >>> .ivu-modal-body {
+    overflow: auto;
+    min-height: 350px;
 
+  }
+}
 .tabBox_img {
   width: 36px;
   height: 36px;
@@ -655,4 +910,21 @@ export default {
     border: 1px solid #eee;
   }
 }
+
+.labelInput {
+  border: 1px solid #dcdee2;
+  width: 20%;
+  padding: 0 5px;
+  border-radius: 5px;
+  min-height: 30px;
+  cursor: pointer;
+
+  .span {
+    color: #c5c8ce;
+  }
+
+  .iconxiayi {
+    font-size: 12px;
+  }
+}
 </style>

+ 40 - 2
template/admin/src/pages/setting/multiLanguage/country.vue

@@ -34,6 +34,11 @@
       </Form>
     </Card>
     <Card :bordered="false" dis-hover>
+      <Row type="flex">
+        <Col v-bind="grid">
+          <Button type="primary" icon="md-add" @click="add">添加语言地区</Button>
+        </Col>
+      </Row>
       <Table
         ref="table"
         :columns="columns"
@@ -46,7 +51,7 @@
         <template slot-scope="{ row, index }" slot="action">
           <a @click="edit(row)">编辑</a>
           <Divider type="vertical" />
-          <a @click="del(row, '删除语言', index)">删除</a>
+          <a @click="del(row, '删除地区语言', index)">删除</a>
         </template>
       </Table>
       <div class="acea-row row-right page">
@@ -64,10 +69,17 @@
 </template>
 <script>
 import { mapState } from 'vuex';
-import { langCountryList } from '@/api/setting';
+import { langCountryList, langCountryForm } from '@/api/setting';
 export default {
   data() {
     return {
+      grid: {
+        xl: 7,
+        lg: 7,
+        md: 12,
+        sm: 24,
+        xs: 24,
+      },
       formValidate: {
         keyword: '',
         page: 1,
@@ -115,6 +127,32 @@ export default {
     this.getList();
   },
   methods: {
+    // 添加
+    add() {
+      this.$modalForm(langCountryForm(0)).then(() => this.getList());
+    },
+    edit(row) {
+      this.$modalForm(langCountryForm(row.id)).then(() => this.getList());
+    },
+    // 删除
+    del(row, tit, num) {
+      let delfromData = {
+        title: tit,
+        num: num,
+        url: `setting/lang_country/del/${row.id}`,
+        method: 'DELETE',
+        ids: '',
+      };
+      this.$modalSure(delfromData)
+        .then((res) => {
+          this.$Message.success(res.msg);
+          this.tabList.splice(num, 1);
+          // this.getList();
+        })
+        .catch((res) => {
+          this.$Message.error(res.msg);
+        });
+    },
     selChange() {
       this.formValidate.page = 1;
       this.getList();

+ 1 - 1
template/admin/src/pages/setting/multiLanguage/langList.vue

@@ -102,7 +102,7 @@
             <Radio :label="item.value" v-for="(item, index) in langType.isAdmin" :key="index">{{ item.title }}</Radio>
           </RadioGroup>
         </FormItem>
-        <Input v-model="langFormData.edit" type="hidden"></Input>
+        <Input v-model="langFormData.edit" v-show="false"></Input>
         <FormItem label="语言说明:" prop="remarks" class="mb20">
           <Input v-model="langFormData.remarks" placeholder="请输入语言说明" style="width: 330px"></Input>
         </FormItem>

+ 0 - 10
template/admin/src/pages/setting/user/index.vue

@@ -22,12 +22,6 @@
         <FormItem label="确认新密码" prop="conf_pwd">
           <Input type="password" v-model="formValidate.conf_pwd" class="input"></Input>
         </FormItem>
-		<FormItem label="文件管理新密码" prop="file_pwd">
-		  <Input type="password" v-model="formValidate.file_pwd" class="input"></Input>
-		</FormItem>
-		<FormItem label="文件管理确认新密码" prop="conf_file_pwd">
-		  <Input type="password" v-model="formValidate.conf_file_pwd" class="input"></Input>
-		</FormItem>
         <FormItem>
           <Button type="primary" @click="handleSubmit('formValidate')">提交</Button>
         </FormItem>
@@ -59,16 +53,12 @@ export default {
         pwd: '',
         new_pwd: '',
         conf_pwd: '',
-		file_pwd: '',
-		conf_file_pwd: '',
       },
       ruleValidate: {
         real_name: [{ required: true, message: '您的姓名不能为空', trigger: 'blur' }],
         pwd: [{ required: true, message: '请输入您的原始密码', trigger: 'blur' }],
         new_pwd: [{ required: true, message: '请输入您的新密码', trigger: 'blur' }],
         conf_pwd: [{ required: true, message: '请确认您的新密码', trigger: 'blur' }],
-		file_pwd: [{ required: true, message: '请输入您的文件管理新密码', trigger: 'blur' }],
-		conf_file_pwd: [{ required: true, message: '请确认您的文件管理新密码', trigger: 'blur' }],
       },
     };
   },

+ 80 - 0
template/admin/src/pages/setting/userFile/index.vue

@@ -0,0 +1,80 @@
+<template>
+  <div>
+    <div class="i-layout-page-header">
+      <div class="i-layout-page-header">
+        <span class="ivu-page-header-title">{{ $route.meta.title }}</span>
+      </div>
+    </div>
+    <Card :bordered="false" dis-hover class="ivu-mt">
+      <Form ref="formValidate" :model="formValidate" :rules="ruleValidate" :label-width="160" label-position="right">
+        <FormItem label="账号" prop="">
+          <Input type="text" v-model="account" :disabled="true" class="input"></Input>
+        </FormItem>
+		<FormItem label="文件管理新密码" prop="file_pwd">
+		  <Input type="password" v-model="formValidate.file_pwd" class="input"></Input>
+		</FormItem>
+		<FormItem label="文件管理确认新密码" prop="conf_file_pwd">
+		  <Input type="password" v-model="formValidate.conf_file_pwd" class="input"></Input>
+		</FormItem>
+        <FormItem>
+          <Button type="primary" @click="handleSubmit('formValidate')">提交</Button>
+        </FormItem>
+      </Form>
+    </Card>
+  </div>
+</template>
+
+<script>
+import { setFilePassword } from '@/api/user';
+import { mapState } from 'vuex';
+export default {
+  name: 'setting_files',
+  computed: {
+    ...mapState('media', ['isMobile']),
+    ...mapState('userLevel', ['categoryId']),
+    labelWidth() {
+      return this.isMobile ? undefined : 75;
+    },
+    labelPosition() {
+      return this.isMobile ? 'top' : 'left';
+    },
+  },
+  data() {
+    return {
+      account: '',
+      formValidate: {
+		file_pwd: '',
+		conf_file_pwd: '',
+      },
+      ruleValidate: {
+		file_pwd: [{ required: true, message: '请输入您的文件管理新密码', trigger: 'blur' }],
+		conf_file_pwd: [{ required: true, message: '请确认您的文件管理新密码', trigger: 'blur' }],
+      },
+    };
+  },
+  mounted() {
+    this.account = this.$store.state.userInfo.userInfo.account;
+  },
+  methods: {
+    handleSubmit(name) {
+      this.$refs[name].validate((valid) => {
+        if (valid) {
+          setFilePassword(this.formValidate)
+            .then((res) => {
+              this.$Message.success(res.msg);
+            })
+            .catch((res) => {
+              this.$Message.error(res.msg);
+            });
+        }
+      });
+    },
+  },
+};
+</script>
+
+<style scoped>
+.input {
+  width: 400px;
+}
+</style>

+ 233 - 70
template/admin/src/pages/system/maintain/systemFile/opendir.vue

@@ -27,13 +27,13 @@
 				</template>
 			</Table>
 		</Card>
-		<Modal :class-name="className" v-model="modals" scrollable footer-hide closable :mask-closable="false" width="80%">
+		<Modal :class-name="className" v-model="modals" scrollable footer-hide closable :mask-closable="false" width="80%" :before-close = 'editModalChange'>
 			<p slot="header" class="diy-header" ref="diyHeader">
 				<span>{{title}}</span>
 				<Icon @click="winChanges" class="diy-header-icon" :type="className ? 'ios-contract' : 'ios-qr-scanner'"  size = "20"/>
 			</p>
 			<div style="height: 100%;">
-				<Button type="primary" id="savefile" class="diy-button" @click="savefile">保存</Button>
+				<Button type="primary" id="savefile" class="diy-button" @click="savefile(indexEditor)">保存</Button>
 				<Button id="refresh" class="diy-button" @click="refreshfile">刷新</Button>
 				
 				<div class="file-box">
@@ -48,7 +48,7 @@
 					</div>
 					<div class="file-left">
 						<Tree class="diy-tree-render" :data="navList" :render="renderContent" :load-data="loadData" @on-contextmenu="handleContextMenu" expand-node>
-							<template class="diy-menu" slot="contextMenu">
+							<template transfer slot="contextMenu">
 								<DropdownItem v-if="contextData && contextData.isDir" @click.native="handleContextCreateFolder()">新建文件夹</DropdownItem>
 								<DropdownItem v-if="contextData && contextData.isDir" @click.native="handleContextCreateFile()">新建文件</DropdownItem>
 								<DropdownItem @click.native="handleContextRename()">重命名</DropdownItem>
@@ -58,7 +58,11 @@
 					</div>
 					<div class="file-fix"></div>
 					<div class="file-content">
-						<div id="container" style="height:100%;min-height: 600px;"></div>
+						<Tabs type="card" v-model="indexEditor" style="height: 100%;" @on-click="toggleEditor" :animated="false" closable @on-tab-remove="handleTabRemove">
+							<TabPane v-for="(value) in editorIndex" :key="value.index" :name="value.index.toString()" :label="value.title" :icon="value.icon" v-if="value.tab">
+								<div ref="container" :id="'container_'+value.index" style="height:100%;min-height: 560px;"></div>
+							</TabPane>
+						</Tabs>
 					</div>
 					<Spin size="large" fix v-if="spinShow"></Spin>
 				</div>
@@ -105,17 +109,38 @@
 		getCookies,
 		removeCookies
 	} from '@/libs/util';
-	import Fullscreen from '@/components/main/components/fullscreen';
+	// import Fullscreen from '@/components/main/components/fullscreen';
 	import * as monaco from 'monaco-editor'
 	export default {
 		name: 'opendir',
 		data() {
 			return {
-				editor: '',
+				modals: false,  //编辑器开关
+				editor: '',   //当前编辑器对象
+				editorIndex:[  //选项卡数组
+					{
+						tab:true,
+						index:'0',
+						title:'',
+						icon:'',
+					}
+				],
+				editorList:[],  //编辑器数组
+				indexEditor:0,  //当前编辑器索引
+				code: '',       //当前文件打开时的内容
+				navList: [],   //左侧导航数据
+				navItem:{},   //左侧导航点击是选中的数据
+				contextData:null,   //左侧导航右键点击是产生的数据对象
+				
+				
+				fileType:'',    // 文件操作类型 createFolder|创建文件夹 createFile|创建文件 delFolder|删除文件夹或者文件
+				className:"",  //全屏 class名
+				// fullscreen:false,  // 是否全屏
+				isSave:true,   //当前文件是否保存
+				
 				isShowLogn: false, // 登录
 				isShowList: false, // 登录之后列表
-				code: '',
-				modals: false,
+				
 				spinShow: false,
 				loading: false,
 				tabList: [],
@@ -151,17 +176,17 @@
 						minWidth: 150,
 					},
 				],
-				formItem: {
+				formItem: {    //记录当前路径信息,获取文件列表时使用
 					dir: '',
 					superior: 0,
 					filedir: '',
 				},
-				rows: {},
-				pathname: '',
-				title: '',
-				navList: [],   //左侧导航数据
-				navItem:{},   //左侧导航点击是选中的数据
-				contextData:null,   //左侧导航右键点击是产生的数据对象
+				dir:'',   //当前完整文件路径
+				// rows: {},  // 
+				pathname: '',   // 当前文件路径
+				title: '',   //当前文件标题
+				
+				
 				formFile: {      //重命名表单
 					filename: '',
 				},
@@ -170,17 +195,13 @@
 						{ required: true, message: '请输入文件或文件夹的名字', trigger: 'blur' }
 					]
 				},
-				formShow:false,
-				// 文件操作类型 createFolder|创建文件夹 createFile|创建文件 delFolder|删除文件夹或者文件
-				fileType:'',
-				formTitle:'测试',
-				className:"",
-				fullscreen:false,  // 是否全屏
+				formShow:false,  //表单开关
+				formTitle:'',	//表单标题
+				
 			};
 		},
 		components: {
 			loginFrom,
-			Fullscreen
 		},
 		mounted() {
 			this.initEditor();
@@ -261,7 +282,7 @@
 			},
 			// 打开
 			open(row) {
-				this.rows = row;
+				// this.rows = row;
 				this.formItem = {
 					dir: row.path,
 					superior: 0,
@@ -275,28 +296,48 @@
 				this.spinShow = true;
 				this.pathname = row.pathname;
 				this.title = row.filename;
+				this.editorIndex[0].title = row.filename;
 				this.navList = this.navListForTab;
-				this.openfile(row.pathname,true);
+				this.dir = row.path;
+				// 创建代码容器
+				if(this.editorList.length <= 0)
+				{
+					this.initEditor();
+				}
+				this.openfile(row.pathname,false);
 			},
-			// 保存
-			savefile() {
+			/**
+			 * 保存
+			 * @param {Object} index   // 当前索引
+			 * @param {Object} type    // true 不更新当前本地数据,false或者为空 更新当前数据
+			 */
+			savefile(index,type) {
+				let code = this.editorList[index].editor.getValue();
 				let data = {
-					comment: this.editor.getValue(),
-					filepath: this.pathname,
+					comment: code,
+					filepath: this.editorList[index].path,
 				};
+				let that = this;
 				savefileApi(data)
 					.then(async (res) => {
-						this.$Message.success(res.msg);
-						// this.modals = false;
+						if(!type)
+						{
+							that.code = code;
+							that.isSave = true;
+							that.editorIndex[index].icon = '';
+							that.editorList[index].isSave = true;
+						}
+						that.$Message.success(res.msg);
+						that.$Modal.remove();
 					})
 					.catch((res) => {
-						this.catchFun(res);
+						that.catchFun(res);
 					});
 			},
 			// 刷新
 			refreshfile() {
 				// 刷新编辑器
-				if(!this.navItem.isDir) this.openfile(this.navItem.pathname,false);
+				if(this.editorList[this.indexEditor]) this.openfile(this.editorList[this.indexEditor].path,true);
 			},
 			// 登录跳转
 			onLogin(data) {
@@ -381,11 +422,20 @@
 			 * @param {Object} data
 			 */
 			clickDir(data,root,node){
-				this.navItem = data;
-				this.pathname = data.pathname;
+				let that = this;
+				that.navItem = data;
+				that.pathname = data.pathname;
 				if(!data.isDir)
 				{
-					this.openfile(data.pathname,false);
+					
+					let index = that.editorIndex.length
+					// 创建tabs
+					that.editorIndex.push({tab:true,index:index.toString(),title:data.title,icon:''});
+					that.indexEditor = index.toString();
+					// 创建代码容器
+					that.initEditor();
+					that.openfile(data.pathname,true);
+					
 				}
 			},
 			//侧边栏右键点击事件
@@ -394,7 +444,7 @@
 				this.contextData = data;
 				
 			},
-			// 文件操作类型 createFolder|创建文件夹 createFile|创建文件 delFolder|删除文件夹或者文件
+			// 文件操作类型 createFolder|创建文件夹 createFile|创建文件 delFolder|删除文件夹或者文件 renameFile|文件重命名
 			//创建文件夹
 			handleContextCreateFolder () {
 				this.formFile.filename = '';
@@ -443,21 +493,24 @@
 			},
 			//打开文件
 			openfile(path,is_edit){
+				let that = this;
 				openfileApi(path)
 					.then(async (res) => {
 						let data = res.data;
-						this.code = data.content;
-						// this.editor.setValue(this.code);
+						that.code = data.content;
+						// 保存相对信息
+						that.editorList[that.indexEditor].path = path;
+						that.editorList[that.indexEditor].oldCode = that.code;
 						//改变属性
-						this.changeModel(data.mode,this.code);
-						if(is_edit)
+						that.changeModel(data.mode,that.code);
+						if(!is_edit)
 						{
-							this.modals = true;
-							this.spinShow = false;
+							that.modals = true;
+							that.spinShow = false;
 						}
 					})
 					.catch((res) => {
-						this.catchFun(res);
+						that.catchFun(res);
 					});
 			},
 			/**
@@ -465,38 +518,45 @@
 			 */
 			initEditor(){
 				let that = this;
-				// 初始化编辑器,确保dom已经渲染
-				that.editor = monaco.editor.create(document.getElementById('container'), {
-					value:that.code,//编辑器初始显示文字
-					language:'sql',//语言支持自行查阅demo
-					automaticLayout: true,//自动布局
-					theme:'vs-dark' ,//官方自带三种主题vs, hc-black, or vs-dark
-					foldingStrategy: 'indentation', // 代码可分小段折叠
-					overviewRulerBorder: false, // 不要滚动条的边框
-					scrollbar: { // 滚动条设置
-						verticalScrollbarSize: 4, // 竖滚动条
-						horizontalScrollbarSize: 10, // 横滚动条
-					},
-					autoIndent: true, // 自动布局
-					tabSize: 4, // tab缩进长度
-					autoClosingOvertype:'always',
-				});
-
-				//添加按键监听
-				that.editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S, function () {
-				  that.savefile();
+				
+				that.$nextTick(() => {
+				  // 初始化编辑器,确保dom已经渲染
+				  that.editor = monaco.editor.create(document.getElementById('container_'+that.indexEditor), {
+				  	value:that.code,//编辑器初始显示文字
+				  	language:'sql',//语言支持自行查阅demo
+				  	automaticLayout: true,//自动布局
+				  	theme:'vs-dark' ,//官方自带三种主题vs, hc-black, or vs-dark
+				  	foldingStrategy: 'indentation', // 代码可分小段折叠
+				  	overviewRulerBorder: false, // 不要滚动条的边框
+				  	scrollbar: { // 滚动条设置
+				  		verticalScrollbarSize: 4, // 竖滚动条
+				  		horizontalScrollbarSize: 10, // 横滚动条
+				  	},
+				  	autoIndent: true, // 自动布局
+				  	tabSize: 4, // tab缩进长度
+				  	autoClosingOvertype:'always',
+				  });
+				  //添加按键监听
+				  that.editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S, function () {
+				    that.savefile(that.indexEditor);
+				  });
+				  that.editor.onKeyUp(() => {
+				  	// 当键盘按下,判断当前编辑器文本与已保存的编辑器文本是否一致
+				  	if(that.editor.getValue() != that.code){
+				  		that.isSave = false;
+				  		that.editorIndex[that.indexEditor].icon = 'md-warning';
+						that.editorList[that.indexEditor].isSave = false;
+				  	}
+				  });
+				  that.editorList.push({'editor':that.editor,'oldCode':that.code,'path':'','isSave':true,'index':that.indexEditor});
 				});
-
-			},
-			getValue(){
-				this.editor.getValue(); //获取编辑器中的文本
 			},
 			/**
 			 * 切换语言
 			 * @param {Object} mode
 			 */
 			changeModel(mode,value){
-				var oldModel = this.editor.getModel();//获取旧模型
+				var oldModel = this.editorList[this.indexEditor].editor.getModel();//获取旧模型
 				// var value = this.editor.getValue();//获取旧的文本
 				//创建新模型,value为旧文本,id为modeId,即语言(language.id)
 				//modesIds即为支持语言
@@ -510,7 +570,7 @@
 					oldModel.dispose();
 				}
 				//设置新模型
-				this.editor.setModel(newModel);
+				this.editorList[this.indexEditor].editor.setModel(newModel);
 			},
 			// 文件操作类型 createFolder|创建文件夹 createFile|创建文件 delFolder|删除文件夹或者文件
 			handleSubmit(name) {
@@ -616,6 +676,9 @@
 					}
 				})
 			},
+			/**
+			 * 窗口最大化
+			 */
 			winChanges(){
 				if(this.className)
 				{
@@ -623,6 +686,76 @@
 				}else{
 					this.className = "diy-fullscreen";
 				}
+			},
+			/**
+			 * 切换选项卡
+			 * @param {Object} index
+			 */
+			toggleEditor(index){
+				index = Number(index);
+				this.code = this.editorList[index].oldCode; //设置文件打开时的代码
+				this.editor = this.editorList[index].editor;  //设置编辑器实例
+			},
+			handleTabRemove (index) {
+				let that = this;
+				
+				// 关闭选项卡
+				that.editorIndex[index].tab = false;  // 关闭选项卡
+				// 判断当前文件有没有保存
+				if(!that.editorList[index].isSave)
+				{
+					that.$Modal.confirm({
+					  title: '文件未保存',
+					  content: '您是否需要保存当前文件',
+					  loading: true,
+					  onOk: () => {
+						// 保存文件
+						that.savefile(index);
+					  },
+					  onCancel: () => {
+					    that.$Message.info('取消保存');
+					  },
+					});
+				}
+				
+				
+			},
+			//编辑器状态变化
+			editModalChange()
+			{
+				let that = this;
+				that.editorList.forEach(function(value,index){
+					if(value.isSave === false)
+					{
+						if(confirm(`${that.editorIndex[index].title}文件未保存,是否要保存该文件`))
+						{
+							// 保存当前文件
+							that.savefile(index,true);   
+						}else{
+							that.$Message.info(`已取消${that.editorIndex[index].title}文件保存`);
+						}
+					}
+					// 销毁当前编辑器
+					 that.editorList[index].editor.dispose();
+					 that.editorList[index].editor = null;
+				});
+				// 初始话数据
+				that.modals = false;  //编辑器开关
+				that.editor = '';   //当前编辑器对象
+				that.editorIndex = [  //选项卡数组
+					{
+						tab:true,
+						index:'0',
+						title:'',
+						icon:'',
+					}
+				];
+				that.editorList = [];  //编辑器数组
+				that.indexEditor = '0';  //当前编辑器索引
+				that.code = '';       //当前文件打开时的内容
+				that.navList = [];   //左侧导航数据
+				that.navItem = {};   //左侧导航点击是选中的数据
+				that.contextData = null;   //左侧导航右键点击是产生的数据对象
 			}
 		},
 	};
@@ -764,7 +897,7 @@
 		white-space:nowrap;/* 不换行 */
 		overflow:hidden;
 		text-overflow:ellipsis;
-		padding: 5px 5px;
+		padding: 7px 5px;
 body >>>.ivu-select-dropdown{
 	background: #fff;
 }
@@ -815,9 +948,39 @@ body >>>.ivu-select-dropdown{
 				height: 100%;
 				.ivu-modal-body
 					height: 100%;
+			.ivu-tabs 
+				.ivu-tabs-content-animated
+					height: 92%;
+					background-color:#2f2f2f !important;
+			.ivu-tabs-content
+				height: 100%
+			.ivu-tabs
+				.ivu-tabs-tabpane
+					height: 92%
 >>>.ivu-modal
 	.ivu-modal-content
 		.ivu-modal-body
 			height: 632px;
 			overflow: hidden;
+	.ivu-tabs 
+		.ivu-tabs-content-animated
+			height: 560px;
+			margin-top: -1px;
+		.ivu-tabs-tabpane
+			height: 560px;
+			margin-top: -1px;
+	.ivu-tabs-nav .ivu-tabs-tab .ivu-icon
+		color: #f00;
+>>>body .ivu-select-dropdown .ivu-dropdown-transfer
+		background:red !important;
+// 导航栏右键样式 无效
+.ivu-select-dropdown.ivu-dropdown-transfer{
+  background:#fff !important;
+}
+.ivu-select-dropdown.ivu-dropdown-transfer .ivu-dropdown-menu .ivu-dropdown-item:hover{
+	background-color: #e5e5e5 !important;
+}
+// 选项卡头部
+>>>.ivu-tabs.ivu-tabs-card > .ivu-tabs-bar .ivu-tabs-nav-container
+	background-color: #333;
 </style>

+ 9 - 0
template/admin/src/router/routers.js

@@ -57,6 +57,15 @@ const frameIn = [
         },
         component: () => import('@/pages/setting/user/index'),
       },
+	  {
+	    path: '/admin/system/files',
+	    name: `systemFiles`,
+	    meta: {
+	      auth: ['admin-setting-files'],
+	      title: '文件管理',
+	    },
+	    component: () => import('@/pages/setting/userFile/index'),
+	  },
       // 刷新页面 必须保留
       {
         path: 'refresh',

+ 1 - 0
template/admin/src/styles/style.css

@@ -677,3 +677,4 @@ body {
 .paddingBox {
   padding: 0 10px 10px;
 }
+