tongshangming 3 роки тому
батько
коміт
43ba5b4f78

+ 2 - 2
src/assets/base.css

@@ -52,8 +52,8 @@
 
 :root {
   --el-color-primary: #3454cd;
-  --el-aside-width: 200px;
-  --el-main-padding: 16px;
+  --menu-width: 200px;
+  --main-padding: 16px;
 }
 
 *,

+ 8 - 0
src/components.d.ts

@@ -11,8 +11,10 @@ declare module '@vue/runtime-core' {
     ElAvatar: typeof import('element-plus/es')['ElAvatar']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCard: typeof import('element-plus/es')['ElCard']
+    ElCol: typeof import('element-plus/es')['ElCol']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
     ElContainer: typeof import('element-plus/es')['ElContainer']
+    ElDialog: typeof import('element-plus/es')['ElDialog']
     ElDropdown: typeof import('element-plus/es')['ElDropdown']
     ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
     ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
@@ -25,6 +27,8 @@ declare module '@vue/runtime-core' {
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElPagination: typeof import('element-plus/es')['ElPagination']
+    ElRadio: typeof import('element-plus/es')['ElRadio']
+    ElRow: typeof import('element-plus/es')['ElRow']
     ElSpace: typeof import('element-plus/es')['ElSpace']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
@@ -33,6 +37,10 @@ declare module '@vue/runtime-core' {
     GlobalHeader: typeof import('./components/GlobalHeader.vue')['default']
     GlobalMenu: typeof import('./components/GlobalMenu.vue')['default']
     IEpArrowDown: typeof import('~icons/ep/arrow-down')['default']
+    IEpLock: typeof import('~icons/ep/lock')['default']
+    IEpRefresh: typeof import('~icons/ep/refresh')['default']
+    IEpSearch: typeof import('~icons/ep/search')['default']
+    IEpUser: typeof import('~icons/ep/user')['default']
     ProTable: typeof import('./components/ProTable.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']

+ 15 - 8
src/components/GlobalHeader.vue

@@ -1,19 +1,22 @@
 <script setup>
 import { useMenuStore } from '@/stores/menu'
+import { useUserStore } from '@/stores/user'
 import logo from '@/assets/logo.png'
 
 const menuStore = useMenuStore()
 const [value, toggle] = useToggle()
-
 const handleToggle = () => {
   toggle()
   menuStore.setCollapse(value.value)
 }
+
+const userStore = useUserStore()
+const user = userStore.user
 </script>
 
 <template>
   <header class="layout-header">
-    <div class="logo flex items-center justify-center pl-2" :class="{ collapse: value }">
+    <div class="logo flex items-center justify-center" :class="{ collapse: value }">
       <img :src="logo" />
       <div class="ml-2" :class="{ hidden: value }">方是科技管理系统</div>
     </div>
@@ -23,22 +26,26 @@ const handleToggle = () => {
           <icon-menu-unfold-one size="22" :strokeWidth="2" v-if="!value" />
           <icon-menu-fold-one size="22" :strokeWidth="2" v-else />
         </el-button>
-        <el-button link @click=""><icon-refresh size="22" :strokeWidth="2" /></el-button>
       </el-space>
       <div class="flex items-center">
         <el-avatar class="mr-10px"></el-avatar>
         <el-dropdown>
           <span class="el-dropdown-link">
-            Dropdown List
+            {{ user.username }}
             <el-icon class="el-icon--right">
               <i-ep-arrow-down />
             </el-icon>
           </span>
           <template #dropdown>
             <el-dropdown-menu>
-              <el-dropdown-item>Action 1</el-dropdown-item>
-              <el-dropdown-item>Action 2</el-dropdown-item>
-              <el-dropdown-item>Action 3</el-dropdown-item>
+              <el-dropdown-item>
+                <el-icon><icon-setting-two></icon-setting-two></el-icon>
+                <div>个人设置</div>
+              </el-dropdown-item>
+              <el-dropdown-item @click="userStore.logout">
+                <el-icon><icon-logout></icon-logout></el-icon>
+                <div>退出登录</div>
+              </el-dropdown-item>
             </el-dropdown-menu>
           </template>
         </el-dropdown>
@@ -56,7 +63,7 @@ const handleToggle = () => {
   align-items: center;
 
   .logo {
-    width: var(--el-aside-width);
+    width: var(--menu-width);
 
     img {
       height: 36px;

+ 11 - 1
src/components/GlobalMenu.vue

@@ -24,7 +24,14 @@ const route = useRoute()
 </script>
 
 <template>
-  <el-menu :default-active="route.path" :collapse="menuStore.collapse" router class="layout-menu">
+  <el-menu
+    :default-active="route.path"
+    :collapse="menuStore.collapse"
+    :collapse-transition="false"
+    router
+    class="layout-menu"
+    :class="{ 'menu-normal': !menuStore.collapse }"
+  >
     <component :is="item.menuName" :index="item.path" v-for="item in menuRouter">
       <el-icon v-if="item.menuName === 'el-menu-item'"><component :is="item.meta?.icon"></component></el-icon>
       <template #title>
@@ -42,6 +49,9 @@ const route = useRoute()
 </template>
 
 <style scoped>
+.menu-normal {
+  width: var(--menu-width);
+}
 .layout-menu {
   background-color: #fff;
   height: 100%;

+ 118 - 21
src/components/ProTable.vue

@@ -1,19 +1,38 @@
 <script setup lang="ts">
-import { ElMessageBox } from 'element-plus'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import type { PropType } from 'vue'
 
+interface column {
+  label?: string
+  value: string
+  prop: string
+  formType: string
+  options: Array<any>
+  rules: any
+  search: boolean
+}
+interface CRUD {
+  create: Function
+  update: Function
+  delete: Function
+  getList: Function
+  getRecord: Function
+}
 const props = defineProps({
-  request: {
-    type: Function,
-    default: () => {}
-  },
+  crud: Object as PropType<CRUD>,
   pageSize: {
     type: Number,
     default: 10
+  },
+  columns: {
+    type: Array as PropType<column[]>,
+    default: () => []
   }
 })
 
 // ============== 查询部分开始 ===============
-const query = ref({})
+const query = ref<any>({})
+const searchList = computed(() => props.columns.filter(item => item.search))
 const handleQuery = () => {
   curPage.value = 1
   getTableData()
@@ -29,8 +48,8 @@ const total = ref(0)
 const curPage = ref(1)
 
 const getTableData = () => {
-  props
-    .request({
+  props.crud
+    ?.getList({
       ...query.value,
       pageSize: props.pageSize,
       page: curPage.value
@@ -49,29 +68,84 @@ const pageChange = () => {
 
 // ============== crud部分开始 ===============
 const handleCreate = () => {
-  curPage.value = 1
-  getTableData()
+  dialogTitle.value = '新建'
+  dialogVisible.value = true
 }
-const handleUpdate = () => {
-  query.value = {}
+const handleUpdate = (row: any) => {
+  dialogTitle.value = '编辑'
+  dialogVisible.value = true
+  if (props.crud?.getRecord) {
+    props.crud.getRecord({ id: row.id }).then((res: any) => {
+      formData.value = res.data
+    })
+  } else {
+    formData.value = row
+  }
 }
-const handleDelete = () => {
+const handleDelete = (id: string | number) => {
   ElMessageBox.confirm('您确定要删除该项吗', '提示', {
     type: 'warning'
-  }).then(() => {})
+  }).then(async () => {
+    const res = await props.crud?.delete(id)
+    if (res.code === 0) {
+      ElMessage({
+        type: 'success',
+        message: '删除成功'
+      })
+    }
+  })
 }
 const handlePatchDelete = () => {}
 // ============== crud部分结束 ===============
+
+const dialogTitle = ref('新建')
+const dialogVisible = ref(false)
+const beforeClose = () => {
+  console.log('object')
+  formData.value = {}
+}
+
+const formData = ref<any>({})
+const formRef = ref()
+
+const submit = () => {
+  let res, message
+  formRef.value.validate(async (valid: boolean) => {
+    if (valid) {
+      if (formData.id) {
+        res = props.crud?.update(formData.value)
+        message = '新建成功'
+      } else {
+        res = props.crud?.create(formData.value)
+        message = '修改成功'
+      }
+
+      if (res.code === 0) {
+        ElMessage({
+          type: 'success',
+          message
+        })
+        dialogVisible.value = false
+      }
+    }
+  })
+}
 </script>
 
 <template>
-  <div class="flex flex-col" style="height: calc(100vh - 100px)">
+  <div class="flex flex-col" style="height: calc(100vh - 60px - var(--main-padding) * 2)">
     <el-card class="mb-4" shadow="never">
       <el-form :inline="true">
-        <slot name="queryBar" :query="query"></slot>
+        <el-form-item :label="item.label" v-for="item in searchList">
+          <component :is="'el-' + item.formType" v-model="query[item.prop]">
+            <template v-if="item.formType === 'radio-group'">
+              <el-radio :label="option.label" v-for="option in item.options">{{ option.value }}</el-radio>
+            </template>
+          </component>
+        </el-form-item>
         <el-form-item>
-          <el-button type="primary" icon="search" @click="handleQuery">查询</el-button>
-          <el-button icon="i-ep-refresh" @click="handleReset">重置</el-button>
+          <el-button type="primary" @click="handleQuery">查询</el-button>
+          <el-button @click="handleReset">重置</el-button>
         </el-form-item>
       </el-form>
     </el-card>
@@ -87,9 +161,9 @@ const handlePatchDelete = () => {}
         <el-table class="flex-grow-1 h-full" :data="tableData">
           <slot name="table"></slot>
           <el-table-column fixed="right" label="操作" width="120">
-            <template #default>
-              <el-button link type="primary" size="small" @click="handleUpdate">编辑</el-button>
-              <el-button link type="danger" size="small" @click="handleDelete">删除</el-button>
+            <template #default="{ row }">
+              <el-button link type="primary" size="small" @click="handleUpdate(row)">编辑</el-button>
+              <el-button link type="danger" size="small" @click="handleDelete(row.id)">删除</el-button>
             </template>
           </el-table-column>
         </el-table>
@@ -105,6 +179,29 @@ const handlePatchDelete = () => {}
         </div>
       </div>
     </el-card>
+
+    <el-dialog draggable :title="dialogTitle" v-model="dialogVisible" width="1000px" :before-close="beforeClose">
+      <el-form :model="formData" ref="formRef" label-width="100px">
+        <el-row :gutter="20">
+          <el-col :span="12" v-for="item in columns">
+            <el-form-item :label="item.label" :rules="item.rules" :prop="item.prop">
+              <component :is="'el-' + item.formType" v-model="formData[item.prop]">
+                <template v-if="item.formType === 'radio-group'">
+                  <el-radio :label="option.label" v-for="option in item.options">{{ option.value }}</el-radio>
+                </template>
+              </component>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+
+      <template #footer>
+        <span>
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="submit">保存</el-button>
+        </span>
+      </template>
+    </el-dialog>
   </div>
 </template>
 

+ 3 - 0
src/layouts/BasicLayout.vue

@@ -25,4 +25,7 @@
 .el-footer {
   padding: 0;
 }
+.el-main {
+  padding: var(--main-padding);
+}
 </style>

+ 4 - 6
src/router/guard.ts

@@ -8,8 +8,7 @@ import 'nprogress/nprogress.css'
 const whiteList = ['/login']
 const title = useTitle()
 
-
-const createRouterGuard = (router:Router) => {
+const createRouterGuard = (router: Router) => {
   router.beforeEach(async (to, from) => {
     const userStore = useUserStore()
 
@@ -21,16 +20,15 @@ const createRouterGuard = (router:Router) => {
 
     if (token) {
       if (to.path === '/login') {
-        return '/'
+        return '/home'
       } else {
-        
         const routerStore = useRouterStore()
         if (!userStore.flag) {
           userStore.getUserInfo()
-          
+
           const accessedRouters = await routerStore.generatorRouter('')
           accessedRouters.forEach(route => {
-            router.addRoute('layout',route)
+            router.addRoute('layout', route)
           })
 
           return { ...to, replace: true }

+ 13 - 2
src/stores/user.ts

@@ -1,14 +1,25 @@
+import router from '@/router'
 
 export const useUserStore = defineStore({
   id: 'user',
   state: () => ({
     user: {},
     flag: false,
-    token: 'test'
+    token: 't'
   }),
   actions: {
     getUserInfo() {
       this.flag = true
+    },
+    login() {
+      this.token = 'test'
+      useStorage('token', 'test')
+      console.log(useStorage('token', 'test'))
+      router.replace({ path: '/home' })
+    },
+    logout() {
+      this.$reset()
+      router.push({ path: '/login' })
     }
   }
-})
+})

+ 34 - 0
src/utils/color.ts

@@ -0,0 +1,34 @@
+export default {
+  // hex颜色转rgb颜色
+  hexToRgb(str: string) {
+    str = str.replace('#', '')
+    const hxs = str.match(/../g) || []
+    const res = []
+
+    for (var i = 0; i < 3; i++) {
+      res[i] = parseInt(hxs[i], 16)
+    }
+
+    return res
+  },
+  // rgb颜色转hex颜色
+  rgbToHex(rgb: Array<number>) {
+    const hexs = rgb.map(item => item.toString(16))
+    for (var i = 0; i < 3; i++) {
+      if (hexs[i].length == 1) hexs[i] = '0' + hexs[i]
+    }
+    return '#' + hexs.join('')
+  },
+  // 加深
+  darken(color: string, level: number) {
+    var rgbc = this.hexToRgb(color)
+    for (var i = 0; i < 3; i++) rgbc[i] = Math.floor(rgbc[i] * (1 - level))
+    return this.rgbToHex(rgbc)
+  },
+  // 变淡
+  lighten(color: string, level: number) {
+    var rgbc = this.hexToRgb(color)
+    for (var i = 0; i < 3; i++) rgbc[i] = Math.floor((255 - rgbc[i]) * level + rgbc[i])
+    return this.rgbToHex(rgbc)
+  }
+}

+ 35 - 6
src/views/system/user.vue

@@ -13,15 +13,44 @@ const testRequest = () => {
     })
   })
 }
+
+const columns = [
+  {
+    label: '用户名',
+    value: '',
+    prop: 'name',
+    formType: 'input',
+    rules: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
+    search: true
+  },
+  {
+    label: '手机号',
+    value: '',
+    prop: 'phone',
+    formType: 'input',
+    search: true
+  },
+  {
+    label: '性别',
+    value: '',
+    prop: 'gender',
+    formType: 'radio-group',
+    options: [
+      {
+        label: '1',
+        value: '男'
+      },
+      {
+        label: '2',
+        value: '女'
+      }
+    ]
+  }
+]
 </script>
 
 <template>
-  <pro-table :request="testRequest">
-    <template #queryBar="{ query }">
-      <el-form-item label="所属栏目">
-        <el-input v-model="query.column"></el-input>
-      </el-form-item>
-    </template>
+  <pro-table :crud="{ getList: testRequest }" :columns="columns">
     <template #table>
       <el-table-column prop="date" label="Date" />
       <el-table-column prop="name" label="Name" />

+ 6 - 1
src/views/user/Login.vue

@@ -1,7 +1,12 @@
 <script setup lang="ts">
+import { useUserStore } from '@/stores/user'
+
+const userStore = useUserStore()
 const username = ref('')
 const password = ref('')
-const handleSubmit = () => {}
+const handleSubmit = () => {
+  userStore.login()
+}
 </script>
 
 <template>