zhengnaiwen_citu пре 7 месеци
родитељ
комит
4fc8b63ca0

+ 26 - 0
src/components/MButton/index.vue

@@ -0,0 +1,26 @@
+<template>
+  <el-button v-bind="$attrs" :type="type" v-on="$listeners">
+    <template v-if="text">
+      <el-link :underline="false" :type="type" text>
+        <slot></slot>
+      </el-link>
+    </template>
+    <template v-else>
+      <slot></slot>
+    </template>
+  </el-button>
+</template>
+
+<script>
+export default {
+  name: 'MButton',
+  props: {
+    text: Boolean,
+    type: String
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 44 - 0
src/components/MDialog/index.vue

@@ -0,0 +1,44 @@
+<template>
+  <el-dialog
+    :visible.sync="show"
+    width="800px"
+    lock-scroll
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    destroy-on-close
+    v-bind="$attrs"
+    v-on="$listeners"
+  >
+    <slot></slot>
+    <span slot="footer">
+      <el-button @click="show = false">取 消</el-button>
+      <el-button type="primary" @click="sure">确 定</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+export default {
+  name: 'm-dialog',
+  data () {
+    return {
+      show: false
+    }
+  },
+  methods: {
+    open () {
+      this.show = true
+    },
+    close () {
+      this.show = false
+    },
+    sure () {
+      this.$emit('sure')
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 9 - 16
src/components/MForm/index.vue

@@ -58,10 +58,6 @@
       </template>
     </el-form-item>
     <slot></slot>
-    <el-form-item>
-      <el-button type="primary" :loading="loading" @click="submitForm('formRef')">提交</el-button>
-      <el-button @click="resetForm('formRef')">重置</el-button>
-    </el-form-item>
   </el-form>
 </template>
 
@@ -85,8 +81,7 @@ export default {
       query: {
         ...this.value
       },
-      rules: {},
-      loading: false
+      rules: {}
     }
   },
   watch: {
@@ -104,18 +99,16 @@ export default {
     }, {})
   },
   methods: {
-    submitForm (formName) {
-      this.$refs[formName].validate((valid) => {
-        if (valid) {
-          this.$emit('submit', this.query, this.unloading)
-        }
-      })
+    setValue () {
+      this.query = { ...this.value }
     },
-    unloading () {
-      this.loading = false
+    resetFields () {
+      this.$refs.formRef.resetFields()
     },
-    resetForm (formName) {
-      this.$refs[formName].resetFields()
+    validate (callback) {
+      this.$refs.formRef.validate((valid) => {
+        callback(valid)
+      })
     }
   }
 }

+ 93 - 0
src/components/MSearch/index.vue

@@ -0,0 +1,93 @@
+<template>
+  <el-card  shadow="never">
+    <el-form :inline="true" :model="form" ref="form" label-width="80px" size="small" :show-message="false" inline-message>
+      <el-form-item
+        v-for="(item, index) in items"
+        :key="index"
+        v-bind="item"
+      >
+        <!-- 输入框 -->
+        <el-input
+          v-if="item.type === 'input'"
+          v-model="form[item.prop]"
+          v-bind="item.option"
+        ></el-input>
+
+        <!-- 下拉框 -->
+        <el-select
+          v-if="item.type === 'select'"
+          v-model="form[item.prop]"
+          v-bind="item.option"
+        >
+          <el-option
+            v-for="option in item.options"
+            :key="option.value"
+            :label="option.label"
+            :value="option.value"
+          ></el-option>
+        </el-select>
+
+        <!-- 时间选择器 -->
+        <el-date-picker
+          v-if="item.type === 'date'"
+          v-model="form[item.prop]"
+          v-bind="item.option"
+        ></el-date-picker>
+
+        <!-- 其他类型可以根据需要扩展 -->
+      </el-form-item>
+      <el-form-item>
+        <el-button class="ml-3" icon="el-icon-search" @click="onSubmit">查询</el-button>
+        <el-button class="ml-3" icon="el-icon-refresh" @click="onReset">重置</el-button>
+        <slot name="button"></slot>
+      </el-form-item>
+    </el-form>
+  </el-card>
+</template>
+
+<script>
+export default {
+  name: 'm-search',
+  props: {
+    items: {
+      type: Array,
+      default: () => []
+    },
+    value: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  data () {
+    return {
+      form: { ...this.value }
+    }
+  },
+  watch: {
+    form: {
+      handler (newVal) {
+        this.$emit('input', newVal)
+      },
+      deep: true
+    }
+  },
+  methods: {
+    setValue () {
+      this.form = { ...this.value }
+    },
+    onSubmit () {
+      this.$emit('search', this.form)
+    },
+    onReset () {
+      this.$refs.form.resetFields()
+      this.$emit('reset', this.form)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+::v-deep .el-form-item {
+  margin: 0 !important;
+}
+</style>

+ 42 - 0
src/components/MTable/MTableColumn.vue

@@ -0,0 +1,42 @@
+<template>
+  <el-table-column v-bind="item">
+    <!-- 默认插槽 -->
+    <template slot-scope="scope">
+      <!-- 如果有对应 prop 的插槽,则渲染插槽内容 -->
+      <template v-if="$scopedSlots[item.prop]">
+        <slot :name="item.prop" v-bind="scope"></slot>
+      </template>
+      <!-- 否则渲染默认内容 -->
+      <template v-else>
+        {{ scope.row[item.prop] }}
+      </template>
+    </template>
+    <!-- 递归处理嵌套列 -->
+    <template v-if="item.children">
+      <m-table-column
+        v-for="(_item, i) in item.children"
+        :key="i"
+        :item="_item"
+      >
+        <!-- 将插槽逐层传递 -->
+        <template #default="scope">
+          <slot :name="item.prop" v-bind="scope"></slot>
+        </template>
+      </m-table-column>
+    </template>
+  </el-table-column>
+</template>
+
+<script>
+export default {
+  name: 'm-table-column',
+  props: {
+    item: {
+      type: Object,
+      default: () => ({})
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 73 - 0
src/components/MTable/index.vue

@@ -0,0 +1,73 @@
+<template>
+  <el-card  shadow="never">
+    <el-table
+      ref="table"
+      :data="items"
+      v-bind="$attrs"
+      v-on="$listeners"
+    >
+      <template #append>
+        <slot name="table-append"></slot>
+      </template>
+      <m-table-column
+        v-for="header in headers"
+        :key="header.prop ?? header.label"
+        :item="header"
+      >
+        <template v-if="$scopedSlots[header.prop]" #[header.prop]="scope">
+          <slot :name="header.prop" v-bind="scope"></slot>
+        </template>
+      </m-table-column>
+    </el-table>
+    <div class="pt-3">
+      <el-pagination
+        @current-change="handleCurrentChange"
+        :current-page="pageCurrent"
+        :page-size="pageSize"
+        layout="total, prev, pager, next, jumper"
+        :total="total"
+        :hide-on-single-page="true"
+      >
+      </el-pagination>
+    </div>
+  </el-card>
+</template>
+
+<script>
+import MTableColumn from './MTableColumn.vue'
+export default {
+  name: 'm-table',
+  components: { MTableColumn },
+  props: {
+    items: {
+      type: Array,
+      default: () => []
+    },
+    headers: {
+      type: Array,
+      default: () => []
+    },
+    pageSize: {
+      type: Number,
+      default: 10
+    },
+    pageCurrent: {
+      type: Number,
+      default: 1
+    },
+    total: {
+      type: Number,
+      default: 0
+    }
+  },
+  methods: {
+    handleCurrentChange (val) {
+      this.$emit('pageChange', val)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 1 - 1
src/styles/index.scss

@@ -25,7 +25,7 @@ $max-classes: 10;
   .pb-#{$i} {  
     padding-bottom: $base-unit * $i;  
   }  
-  .pb-#{$i} {  
+  .mb-#{$i} {  
     margin-bottom: $base-unit * $i;  
   }  
   .pa-#{$i} {  

+ 40 - 0
src/utils/base64ToFile.js

@@ -0,0 +1,40 @@
+
+const util = {
+  // 创建一个a标签,并做下载点击事件
+  downloadFile: function (blob, fileName) {
+    if (window.navigator.msSaveOrOpenBlob) {
+      // 兼容ie11
+      try {
+        window.navigator.msSaveOrOpenBlob(blob, fileName)
+      } catch (e) {
+        console.log(e)
+      }
+      return
+    }
+    const link = document.createElement('a')
+    link.href = window.URL.createObjectURL(blob)
+    link.download = fileName
+    // 此写法兼容可火狐浏览器
+    document.body.appendChild(link)
+    const evt = document.createEvent('MouseEvents')
+    evt.initEvent('click', false, false)
+    link.dispatchEvent(evt)
+    document.body.removeChild(link)
+  },
+  // 将Base64文件转为 Blob
+  buildBlobByByte: function (data) {
+    const raw = window.atob(data)
+    const rawLength = raw.length
+    const uInt8Array = new Uint8Array(rawLength)
+    for (let i = 0; i < rawLength; ++i) {
+      uInt8Array[i] = raw.charCodeAt(i)
+    }
+    return new Blob([uInt8Array])
+  },
+  // 二进制数组 生成文件
+  downloadFileByByte: function (data, fileName) {
+    const blob = this.buildBlobByByte(data)
+    this.downloadFile(blob, fileName)
+  }
+}
+export default util

+ 202 - 0
src/views/systemManage/roleManage/index.vue

@@ -0,0 +1,202 @@
+<template>
+  <div>
+    <m-search :items="searchItems" v-model="searchValues" class="mb-3" @search="search" @reset="search">
+      <template #button>
+        <el-button type="primary" icon="el-icon-plus" @click="handleAdd">
+          新增
+        </el-button>
+      </template>
+    </m-search>
+    <MTable
+      :items="items"
+      :headers="headers"
+      :page-size="pageInfo.size"
+      :page-current="pageInfo.current"
+      :total="pageInfo.total"
+      @page-change="handlePageChange"
+    >
+      <template #createTime="scope">
+        {{ dateFormat(scope.row.createTime) }}
+      </template>
+      <template #actions="scope">
+        <el-button type="text">
+          <el-link :underline="false" type="primary" text @click="handleClick(scope.row)">数据权限</el-link>
+        </el-button>
+        <el-button type="text">
+          <el-link :underline="false" type="primary" text @click="handleClick(scope.row)">编辑</el-link>
+        </el-button>
+        <el-button type="text">
+          <el-link :underline="false" type="danger" text @click="handleClick(scope.row)">删除</el-link>
+        </el-button>
+      </template>
+    </MTable>
+    <MDialog ref="dialog" :title="isEdit ? '编辑角色' : '新增角色'" @sure="handleSave">
+      <MForm></MForm>
+    </MDialog>
+  </div>
+</template>
+
+<script>
+import MForm from '@/components/MForm'
+import MTable from '@/components/MTable'
+import MDialog from '@/components/MDialog'
+import MSearch from '@/components/MSearch'
+import {
+  getRoleList,
+  deleteRole,
+  getRoleDetails,
+  saveRole,
+  getMenuTree,
+  saveDataPermission
+} from '@/api/menu'
+import { dateFormat } from '@/utils/date'
+// import UserEdit from './components/userEdit'
+// import DataPermission from './components/DataPermission'
+export default {
+  name: 'role-manage-role',
+  components: {
+    MForm,
+    MTable,
+    MDialog,
+    MSearch
+  },
+  data () {
+    return {
+      searchValues: {
+        roleName: null
+      },
+      searchItems: [
+        {
+          label: '角色名称',
+          prop: 'roleName',
+          type: 'input'
+        }
+      ],
+      roles: [],
+      isEdit: false,
+      itemData: {},
+      itemDataPermission: {},
+      show: false,
+      showMenu: false,
+      headers: [
+        { label: '角色', align: 'start', prop: 'roleName' },
+        { label: '创建时间', align: 'start', prop: 'createTime' },
+        { label: '操作', align: 'start', prop: 'actions', fixed: 'right' }
+      ],
+      items: [],
+      total: 0,
+      pageInfo: {
+        size: 10,
+        current: 1
+      },
+      orders: [],
+      loading: true
+    }
+  },
+  created () {
+    this.getMenu()
+  },
+  methods: {
+    async getMenu () {
+      try {
+        const { data } = await getMenuTree({})
+        this.roles = data.routes
+        this.initPage()
+      } catch (error) {
+        this.$message.error(error)
+      }
+    },
+    async initPage () {
+      this.loading = false
+      try {
+        const { data } = await getRoleList({ ...this.pageInfo, orders: this.orders, entity: this.searchValues })
+        this.items = data.records
+        this.total = data.total
+      } catch (error) {
+        this.$message.error(error)
+      }
+    },
+    async handleEdit (item) {
+      try {
+        const { data } = await getRoleDetails({ roleId: item.id })
+        this.itemData = data
+        this.isEdit = true
+        this.show = true
+      } catch (error) {
+        this.$message.error(error)
+      }
+    },
+    async handleAdd () {
+      this.isEdit = false
+      this.$refs.dialog.open()
+    },
+    handleData (item) {
+      this.itemDataPermission = item
+      this.showMenu = true
+    },
+    handleDelete (ids) {
+      // console.log(ids)
+      // if (Array.isArray(ids)) return
+      if (Array.isArray(ids) && ids.length === 0) return this.$message.warning('请选择删除的选项')
+      // console.log(ids)
+      // if (ids) return
+      this.$confirm('提示', '是否确定删除该选项').then(async () => {
+        try {
+          await deleteRole({ id: ids })
+          this.$message.success('删除成功')
+          this.initPage()
+        } catch (error) {
+          this.$message.error(error)
+        }
+      })
+    },
+    async handleSave () {
+      const obj = this.$refs.edit.getValue()
+      if (this.isEdit) obj.id = this.itemData.id
+      try {
+        await saveRole(obj)
+        this.$message.success(this.isEdit ? '编辑成功' : '添加成功')
+        this.$refs.dialog.closest()
+        this.initPage()
+      } catch (error) {
+        this.$message.error(error)
+      }
+    },
+    async handleSaveMenu () {
+      const obj = this.$refs.dataPermission.getValue()
+      if (!obj) {
+        return
+      }
+      try {
+        await saveDataPermission(obj)
+        this.$message.success('保存成功')
+        this.showMenu = false
+        this.initPage()
+      } catch (error) {
+        this.$message.error(error)
+      }
+    },
+    handlePageChange (page) {
+      this.pageInfo.current = page
+      this.initPage()
+    },
+    dateFormat (str) {
+      const date = new Date(+str * 1000)
+      return dateFormat('YYYY-mm-dd HH:MM:SS', date)
+    },
+    handleSort (val) {
+      this.orders = val
+      this.initPage()
+    },
+    search (val) {
+      Object.assign(this.searchValues, val)
+      this.pageInfo.current = 1
+      this.initPage()
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 34 - 14
src/views/systemManage/safety/index.vue

@@ -3,7 +3,12 @@
     <el-tabs v-model="activeName">
       <el-tab-pane label="安全中心" name="first">
         <div class="box">
-          <m-form :items="formItems" v-model="formValue" @submit="handleSubmit"></m-form>
+          <m-form ref="form" :items="formItems" v-model="formValue">
+            <el-form-item>
+              <m-button type="primary" @click="handleSubmit">保存</m-button>
+              <m-button @click="handleReset">重置</m-button>
+            </el-form-item>
+          </m-form>
         </div>
       </el-tab-pane>
     </el-tabs>
@@ -12,11 +17,13 @@
 
 <script>
 import MForm from '@/components/MForm'
+import MButton from '@/components/MButton'
 import { resetPassword } from '@/api/user'
 export default {
   name: 'system-manage-safety',
   components: {
-    MForm
+    MForm,
+    MButton
   },
   data () {
     const valid = (rule, value, callback) => {
@@ -82,18 +89,31 @@ export default {
     }
   },
   methods: {
-    async handleSubmit (val, unloading) {
-      const { renewPwd, ...obj } = this.formValue
-      try {
-        await resetPassword({ ...obj, type: 2 })
-        this.$message.success('重置密码成功')
-        unloading()
-        setTimeout(() => {
-          this.$store.dispatch('user/userLogout')
-        }, 1000)
-      } catch (error) {
-        this.$message.error(error)
-      }
+    handleReset () {
+      this.$refs.form.resetFields()
+    },
+    async handleSubmit () {
+      this.$refs.form.validate(valid => {
+        if (!valid) {
+          return
+        }
+        this.$confirm('确定修改密码吗?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }).then(async () => {
+          const { renewPwd, ...obj } = this.formValue
+          try {
+            await resetPassword({ ...obj, type: 2 })
+            this.$message.success('重置密码成功')
+            setTimeout(() => {
+              this.$store.dispatch('user/userLogout')
+            }, 1000)
+          } catch (error) {
+            this.$message.error(error)
+          }
+        }).catch(_ => {})
+      })
     }
   }
 }

+ 196 - 0
src/views/systemManage/user/index.vue

@@ -0,0 +1,196 @@
+<template>
+  <div class="pa-3 white">
+  </div>
+</template>
+
+<script>
+// import MFilter from '@/components/Filter'
+// import TableList from '@/components/List/table.vue'
+// import EditDialog from '@/components/Dialog'
+// import EditUser from './components/editUser.vue'
+// import Upload from '@/components/UploadBtn'
+// import ResetPassword from './components/resetPassword.vue'
+import util from '@/utils/base64ToFile'
+import { getUserList, saveUser, deleteUser, resetPassword, downloadUserTemplate, userExcelExport } from '@/api/user'
+import { getRoleList } from '@/api/menu'
+import { currentTime } from '@/utils/date'
+export default {
+  name: 'user-list',
+  components: { },
+  data () {
+    return {
+      filter: {
+        list: [
+          {
+            type: 'textField',
+            key: 'searchKey',
+            value: null,
+            label: '用户名'
+          }
+        ]
+      },
+      query: {
+        name: null,
+        username: null
+      },
+      showReset: false,
+      headers: [
+        { text: '用户昵称', align: 'start', value: 'name' },
+        { text: '账号', align: 'start', value: 'username' },
+        { text: '人员编码', value: 'employeeCode' },
+        { text: '电话', align: 'start', value: 'phone' },
+        { text: '角色', align: 'start', value: 'role' },
+        { text: '状态', align: 'start', value: 'state' },
+        { text: '登陆时间', align: 'start', value: 'loginTime' },
+        { text: '上次登录时间', align: 'start', value: 'lastLoginTime' },
+        { text: '创建时间', align: 'start', value: 'createdTime' },
+        { text: '操作', align: 'start', value: 'actions' }
+      ],
+      items: [],
+      total: 0,
+      pageInfo: {
+        current: 1,
+        size: 10
+      },
+      loading: false,
+      isEdit: false,
+      show: false,
+      title: '新增',
+      roleList: [],
+      itemData: {},
+      uploadLoading: false
+    }
+  },
+  async created () {
+    await this.initDice()
+    await this.init()
+  },
+  methods: {
+    formatToTime (str) {
+      return currentTime(str)
+    },
+    async init () {
+      try {
+        this.loading = true
+        const { data } = await getUserList({ ...this.pageInfo, ...this.query })
+        this.items = data.records.map(e => {
+          // e.roleName = e.role.map(_m => _m.roleName).toString()
+          return e
+        })
+        this.total = data.total
+      } catch (error) {
+        this.$snackbar.error(error)
+      } finally {
+        this.loading = false
+      }
+    },
+    handleSearch (val) {
+      // console.log(val)
+      this.pageInfo.current = 1
+      Object.assign(this.query, val)
+      this.init()
+    },
+    async initDice () {
+      try {
+        const { data } = await getRoleList({ size: 999 })
+        this.roleList = data.records
+      } catch (error) {
+        this.$snackbar.error(error)
+      }
+    },
+    handleEdit (item) {
+      this.title = '编辑用户'
+      this.itemData = item
+      this.isEdit = true
+      this.show = true
+    },
+    handleAdd () {
+      this.title = '新增用户'
+      this.isEdit = false
+      this.show = true
+    },
+    async handleSave () {
+      if (!this.$refs.user.validate()) return
+      const param = this.$refs.user.getValue()
+      if (this.isEdit) param.id = this.itemData.id
+      param.roleId = param.roleId.toString()
+      try {
+        await saveUser(param)
+        this.$snackbar.success('保存成功')
+        this.show = false
+        this.init()
+      } catch (error) {
+        this.$snackbar.error(error)
+      }
+    },
+    handleDelete (userId) {
+      if (Array.isArray(userId)) return
+      this.$confirm('提示', '是否确定删除该选项')
+        .then(async () => {
+          try {
+            await deleteUser({ userId })
+            this.$snackbar.success('删除成功')
+            this.init()
+          } catch (error) {
+            this.$snackbar.error(error)
+          }
+        })
+    },
+    handleReset (item) {
+      this.itemData = item
+      this.showReset = true
+    },
+    async handleSaveReset () {
+      if (!this.$refs.password.validate()) return
+      const obj = this.$refs.password.getValue()
+      const companyCode = this.itemData.companyInfo.companyCode
+      const userId = this.itemData.id
+      const param = {
+        type: 1,
+        userId,
+        companyCode
+      }
+      Object.assign(param, obj)
+      try {
+        await resetPassword(param)
+        this.showReset = false
+        this.$snackbar.success('修改成功')
+      } catch (error) {
+        this.$snackbar.error(error)
+      }
+    },
+    pageHandleChange (page) {
+      this.pageInfo.current = page
+      this.init()
+    },
+    // 下载批量上传文件模板
+    async downloadTemplate () {
+      try {
+        const { data } = await downloadUserTemplate()
+        util.downloadFileByByte(data, '批量上传文件模板.xls')
+      } catch (error) {
+        this.$snackbar.error(error)
+      }
+    },
+    // 批量上传用户
+    async batchImportUsers (file) {
+      const formData = new FormData()
+      formData.append('file', file)
+      this.uploadLoading = true
+      try {
+        await userExcelExport(formData)
+        this.$snackbar.success('上传成功')
+        this.init()
+      } catch (error) {
+        this.$snackbar.error(error)
+      } finally {
+        this.uploadLoading = false
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>