Browse Source

路由+课程分类页面

刘冰洁 1 year ago
parent
commit
fc42a70565
30 changed files with 2268 additions and 101 deletions
  1. 2 0
      package.json
  2. 35 0
      pnpm-lock.yaml
  3. 2 1
      src/locales/lang/zh-cn.ts
  4. 11 0
      src/router/modules/management.ts
  5. 0 0
      src/service/api/login.ts
  6. 61 5
      src/service/api/user.ts
  7. 1 1
      src/service/request/instance.ts
  8. 26 26
      src/typings/api.copy.ts
  9. 2 0
      src/typings/page-route.d.ts
  10. 24 0
      src/typings/sort.ts
  11. 2 0
      src/typings/system.d.ts
  12. 356 0
      src/views/_builtin/login/components/pwd-login/components/verifition/Verify.vue
  13. 260 0
      src/views/_builtin/login/components/pwd-login/components/verifition/Verify/VerifyPoints.vue
  14. 366 0
      src/views/_builtin/login/components/pwd-login/components/verifition/Verify/VerifySlide.vue
  15. 27 0
      src/views/_builtin/login/components/pwd-login/components/verifition/api/index.js
  16. 11 0
      src/views/_builtin/login/components/pwd-login/components/verifition/utils/ase.js
  17. 30 0
      src/views/_builtin/login/components/pwd-login/components/verifition/utils/axios.js
  18. 35 0
      src/views/_builtin/login/components/pwd-login/components/verifition/utils/util.js
  19. 5 0
      src/views/_builtin/login/components/pwd-login/index.vue
  20. 69 0
      src/views/_builtin/login/components/pwd-login/indexCp.vue
  21. 1 0
      src/views/index.ts
  22. 87 4
      src/views/management/auth/index.vue
  23. 49 0
      src/views/management/sort/api.ts
  24. 153 0
      src/views/management/sort/components/table-action-add.vue
  25. 181 0
      src/views/management/sort/crud.tsx
  26. 180 0
      src/views/management/sort/index.vue
  27. 126 0
      src/views/management/sort/index.vuebak
  28. 9 0
      src/views/management/usdt/index.vue
  29. 1 1
      src/views/management/user/components/table-action-modal.vue
  30. 156 63
      src/views/management/user/index.vue

+ 2 - 0
package.json

@@ -82,7 +82,9 @@
     "vditor": "^3.9.2",
     "vue": "3.3.4",
     "vue-i18n": "^9.2.2",
+    "vue-monoplasty-slide-verify": "^1.3.1",
     "vue-router": "^4.2.1",
+    "vue-slider-vertify": "^0.0.1",
     "vuedraggable": "^4.1.0",
     "wangeditor": "^4.7.15",
     "xgplayer": "^3.0.2"

+ 35 - 0
pnpm-lock.yaml

@@ -91,9 +91,15 @@ dependencies:
   vue-i18n:
     specifier: ^9.2.2
     version: 9.2.2(vue@3.3.4)
+  vue-monoplasty-slide-verify:
+    specifier: ^1.3.1
+    version: 1.3.1
   vue-router:
     specifier: ^4.2.1
     version: 4.2.1(vue@3.3.4)
+  vue-slider-vertify:
+    specifier: ^0.0.1
+    version: 0.0.1
   vuedraggable:
     specifier: ^4.1.0
     version: 4.1.0(vue@3.3.4)
@@ -4281,6 +4287,14 @@ packages:
       '@vue/compiler-core': 3.3.4
       '@vue/shared': 3.3.4
 
+  /@vue/compiler-sfc@2.7.14:
+    resolution: {integrity: sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==}
+    dependencies:
+      '@babel/parser': 7.21.8
+      postcss: 8.4.23
+      source-map: 0.6.1
+    dev: false
+
   /@vue/compiler-sfc@3.3.4:
     resolution: {integrity: sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==}
     dependencies:
@@ -12821,6 +12835,13 @@ packages:
       vue: 3.3.4
     dev: false
 
+  /vue-monoplasty-slide-verify@1.3.1:
+    resolution: {integrity: sha512-oMP9RdBo/2M2D8CcEE1IJCXKWOGPUyFNKFgMwj8+BMEA5Je4wF3jUbCnQe/hNNmV1cUBdeTNp0w/TdlP1A96SQ==}
+    engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
+    dependencies:
+      vue: 2.7.14
+    dev: false
+
   /vue-router@4.2.1(vue@3.3.4):
     resolution: {integrity: sha512-nW28EeifEp8Abc5AfmAShy5ZKGsGzjcnZ3L1yc2DYUo+MqbBClrRP9yda3dIekM4I50/KnEwo1wkBLf7kHH5Cw==}
     peerDependencies:
@@ -12830,6 +12851,13 @@ packages:
       vue: 3.3.4
     dev: false
 
+  /vue-slider-vertify@0.0.1:
+    resolution: {integrity: sha512-0nWYalhUKOX/FERDO/UIMHvJLnn+qffnecyrB0/NblCzc+AvECW1MA78kOvggx2AIOsqGGqTTlx/Nwwm86DAoA==}
+    dependencies:
+      prismjs: 1.29.0
+      vue: 3.3.4
+    dev: false
+
   /vue-template-compiler@2.7.14:
     resolution: {integrity: sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==}
     dependencies:
@@ -12849,6 +12877,13 @@ packages:
       typescript: 5.0.4
     dev: true
 
+  /vue@2.7.14:
+    resolution: {integrity: sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==}
+    dependencies:
+      '@vue/compiler-sfc': 2.7.14
+      csstype: 3.1.2
+    dev: false
+
   /vue@3.3.4:
     resolution: {integrity: sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==}
     dependencies:

+ 2 - 1
src/locales/lang/zh-cn.ts

@@ -75,7 +75,8 @@ const locale: LocaleMessages<I18nType.Schema> = {
         auth: '权限管理',
         role: '角色管理',
         route: '路由管理',
-        user: '用户管理'
+        user: '用户管理',
+        sort: '课程分类'
       },
       about: '关于'
     }

+ 11 - 0
src/router/modules/management.ts

@@ -36,6 +36,17 @@ const management: AuthRoute.Route = {
         icon: 'ic:round-manage-accounts'
       }
     },
+    {
+      name: 'management_sort',
+      path: '/management/sort',
+      component: 'self',
+      meta: {
+        title: '课程分类',
+        i18nTitle: 'message.routes.management.sort',
+        requiresAuth: true,
+        icon: 'material-symbols:sort'
+      }
+    },
     {
       name: 'management_route',
       path: '/management/route',

+ 0 - 0
src/service/api/login.ts


+ 61 - 5
src/service/api/user.ts

@@ -41,7 +41,7 @@ export interface QueryParams {
   description?: string;
   isActive?: string;
   createTime?: string;
-  modifyTime?:string;
+  modifyTime?: string;
   createUid?: number;
   disabled?: string;
 }
@@ -67,7 +67,7 @@ export interface QueryRes {
  * @param {string} params.description 权限描述
  * @returns
  */
-export function query(pageNum: number, pageSize: number, params: QueryParams){
+export function query(pageNum: number, pageSize: number, params: QueryParams) {
   return request.post(`/permission/query?pageNum=${pageNum}&pageSize=${pageSize}`, params);
 }
 // 参数接口
@@ -95,8 +95,8 @@ export interface Query_1Res {
 /**
  * 查询部门
  * @param {string} pageNum
-  * @param {string} pageSize
-  * @param {object} params EasSysDepartment
+ * @param {string} pageSize
+ * @param {object} params EasSysDepartment
  * @param {number} params.id
  * @param {string} params.depname 部门名称
  * @param {string} params.address 部门地址
@@ -109,6 +109,62 @@ export interface Query_1Res {
  * @param {string} params.disabled 状态
  * @returns
  */
-export function query_1(pageNum: number, pageSize: number, params: Query_1Params){
+export function query_1(pageNum: number, pageSize: number, params: Query_1Params) {
   return request.post(`/department/query?pageNum=${pageNum}&pageSize=${pageSize}`, params);
 }
+
+
+
+// 查询
+
+// 响应接口
+export interface SelectAll_1Res {
+  status: boolean;
+  msg: string;
+  data: Record<string, unknown>;
+}
+
+/**
+ * 查询全部课程类别
+ * @returns
+ */
+export function selectAll_1() {
+  return request.get(`/selectAll`);
+}
+
+
+// 添加
+
+// 参数接口
+export interface AddEasEduCategoryParams {
+  id?: number;
+  name?: string;
+  description?: string;
+  createTime?: Record<string, unknown>;
+  modifyTime?: Record<string, unknown>;
+  createUid?: number;
+  disabled?: string;
+}
+
+// 响应接口
+export interface AddEasEduCategoryRes {
+  status: boolean;
+  msg: string;
+  data: Record<string, unknown>;
+}
+
+/**
+ * 添加课程类别
+ * @param {object} params EasEduCategory
+ * @param {number} params.id
+ * @param {string} params.name 学科名称
+ * @param {string} params.description 学科描述
+ * @param {object} params.createTime 创建时间
+ * @param {object} params.modifyTime 修改时间
+ * @param {number} params.createUid 创建用户ID
+ * @param {string} params.disabled 状态
+ * @returns
+ */
+export function addEasEduCategory(params: AddEasEduCategoryParams){
+  return request.post(`/addEasEduCategory`, params);
+}

+ 1 - 1
src/service/request/instance.ts

@@ -66,7 +66,7 @@ export default class CustomAxiosInstance {
           const { codeKey, dataKey, successCode } = this.backendConfig;
           // 请求成功
           if (backend[codeKey] === successCode || response.status === 200) {
-            console.log('fanhui');
+            // console.log('fanhui');
             return handleServiceResult(null, backend[dataKey]);
           }
 

+ 26 - 26
src/typings/api.copy.ts

@@ -1,29 +1,29 @@
-
 declare namespace ApiUserMa {
-  interface User {
-    /** 用户id */
+	interface User {
+		/** 用户id */
 		id?: number;
-    /** 用户名 */
-    userName: string | null;
-    /** 用户年龄 */
-    age: number | null;
-    /**
-     * 用户性别
-     * - 0: 女
-     * - 1: 男
-     */
-    gender: '0' | '1' | null;
-    /** 用户手机号码 */
-    phone: string;
-    /** 用户邮箱 */
-    email: string | null;
-    /**
-     * 用户状态
-     * - 1: 启用
-     * - 2: 禁用
-     * - 3: 冻结
-     * - 4: 软删除
-     */
-    userStatus: '1' | '2' | '3' | '4' | null;
-  }
+		name: number;
+		/** 用户名 */
+		description: string | null;
+		/** 用户年龄 */
+		createTime: number | null;
+		modifyTime: string | null;
+		createUid: number;
+		// /**
+		//  * 用户性别
+		//  * - 0: 女
+		//  * - 1: 男
+		//  */
+		// gender: '0' | '1' | null;
+		// /** 用户手机号码 */
+		// phone: string;
+		// /** 用户邮箱 */
+		// email: string | null;
+		/**
+		 * 用户状态
+		 * - N: 启用
+		 * - Y: 禁用
+		 */
+		disabled: "N" | "Y" | null;
+	}
 }

+ 2 - 0
src/typings/page-route.d.ts

@@ -57,6 +57,7 @@ declare namespace PageRoute {
     | 'management_auth'
     | 'management_role'
     | 'management_route'
+    | 'management_sort'
     | 'management_user'
     | 'multi-menu'
     | 'multi-menu_first'
@@ -115,6 +116,7 @@ declare namespace PageRoute {
     | 'management_auth'
     | 'management_role'
     | 'management_route'
+    | 'management_sort'
     | 'management_user'
     | 'multi-menu_first_second-new_third'
     | 'multi-menu_first_second'

+ 24 - 0
src/typings/sort.ts

@@ -0,0 +1,24 @@
+declare namespace CourseSort {
+  interface User extends ApiUserMa.User {
+    /** 序号 */
+    index: number;
+    /** 表格的key(id) */
+    key: string;
+  }
+
+  /**
+   * 用户性别
+   * - 0: 女
+   * - 1: 男
+   */
+  // type GenderKey = NonNullable<User['gender']>;
+
+  /**
+   * 用户状态
+   * - 1: 启用
+   * - 2: 禁用
+   * - 3: 冻结
+   * - 4: 软删除
+   */
+  type UserStatusKey = NonNullable<User['disabled']>;
+}

+ 2 - 0
src/typings/system.d.ts

@@ -379,6 +379,8 @@ declare namespace I18nType {
         role: string;
         route: string;
         user: string;
+			  sort: string;
+				usdt:string;
       };
       about: string;
     };

File diff suppressed because it is too large
+ 356 - 0
src/views/_builtin/login/components/pwd-login/components/verifition/Verify.vue


+ 260 - 0
src/views/_builtin/login/components/pwd-login/components/verifition/Verify/VerifyPoints.vue

@@ -0,0 +1,260 @@
+<template>
+    <div style="position: relative"
+        >
+        <div class="verify-img-out">
+            <div class="verify-img-panel" :style="{'width': setSize.imgWidth,
+                                                   'height': setSize.imgHeight,
+                                                   'background-size' : setSize.imgWidth + ' '+ setSize.imgHeight,
+                                                   'margin-bottom': vSpace + 'px'}"
+                                                    >
+                <div class="verify-refresh" style="z-index:3" @click="refresh" v-show="showRefresh">
+                    <i class="iconfont icon-refresh"></i>
+                </div>
+                <img :src="'data:image/png;base64,'+pointBackImgBase" 
+                ref="canvas"
+                alt=""  style="width:100%;height:100%;display:block"
+                @click="bindingClick?canvasClick($event):undefined">
+
+                <div v-for="(tempPoint, index) in tempPoints" :key="index" class="point-area"
+                     :style="{
+                        'background-color':'#1abd6c',
+                        color:'#fff',
+                        'z-index':9999,
+                        width:'20px',
+                        height:'20px',
+                        'text-align':'center',
+                        'line-height':'20px',
+                        'border-radius': '50%',
+                        position:'absolute',
+                        top:parseInt(tempPoint.y-10) + 'px',
+                        left:parseInt(tempPoint.x-10) + 'px'
+                     }">
+                    {{index + 1}}
+                </div>
+            </div>
+        </div>
+        <!-- 'height': this.barSize.height, -->
+        <div class="verify-bar-area"
+             :style="{'width': setSize.imgWidth,
+                      'color': this.barAreaColor,
+                      'border-color': this.barAreaBorderColor,
+                      'line-height':this.barSize.height}">
+            <span class="verify-msg">{{text}}</span>
+        </div>
+    </div>
+</template>
+<script type="text/babel">
+    /**
+     * VerifyPoints
+     * @description 点选
+     * */
+    import {resetSize, _code_chars, _code_color1, _code_color2} from './../utils/util'
+    import {aesEncrypt} from "./../utils/ase"
+    import {reqGet,reqCheck}  from "./../api/index"
+    import { computed, onMounted, reactive, ref,watch,nextTick,toRefs, watchEffect,getCurrentInstance} from 'vue';
+    export default {
+        name: 'VerifyPoints',
+        props: {
+            //弹出式pop,固定fixed
+            mode: {
+                type: String,
+                default: 'fixed'
+            },
+            captchaType:{
+                type:String,
+            },
+            //间隔
+            vSpace: {
+                type: Number,
+                default: 5
+            },
+            imgSize: {
+                type: Object,
+                default() {
+                    return {
+                        width: '310px',
+                        height: '155px'
+                    }
+                }
+            },
+            barSize: {
+                type: Object,
+                default() {
+                    return {
+                        width: '310px',
+                        height: '40px'
+                    }
+                }
+            }
+        },
+        setup(props,context){
+            const {mode,captchaType,vSpace,imgSize,barSize} = toRefs(props)
+            const { proxy } = getCurrentInstance();
+            let secretKey = ref(''),           //后端返回的ase加密秘钥
+                checkNum = ref(3),             //默认需要点击的字数
+                fontPos = reactive([]),            //选中的坐标信息
+                checkPosArr = reactive([]),        //用户点击的坐标
+                num = ref(1),                 //点击的记数
+                pointBackImgBase = ref(''),    //后端获取到的背景图片
+                poinTextList = reactive([]),        //后端返回的点击字体顺序
+                backToken = ref(''),           //后端返回的token值
+                setSize = reactive({
+                    imgHeight: 0,
+                    imgWidth: 0,
+                    barHeight: 0,
+                    barWidth: 0
+                }),
+                tempPoints = reactive([]),
+                text = ref(''),
+                barAreaColor = ref(undefined),
+                barAreaBorderColor = ref(undefined),
+                showRefresh = ref(true),
+                bindingClick = ref(true)
+
+                
+               
+
+                const init = ()=>{
+                    //加载页面
+                    fontPos.splice(0, fontPos.length)
+                    checkPosArr.splice(0, checkPosArr.length)
+                    num.value = 1
+                    getPictrue();
+                    nextTick(() => {
+                        let {imgHeight,imgWidth,barHeight,barWidth} = resetSize(proxy)
+                        setSize.imgHeight = imgHeight
+                        setSize.imgWidth = imgWidth
+                        setSize.barHeight = barHeight
+                        setSize.barWidth = barWidth
+                        proxy.$parent.$emit('ready', proxy)
+                    })
+                }
+                 onMounted(()=>{
+                    // 禁止拖拽
+                    init()
+                    proxy.$el.onselectstart = function () {
+                        return false
+                    }
+                })
+                const canvas = ref(null)
+                const canvasClick = (e)=>{
+                    checkPosArr.push(getMousePos(canvas, e));
+                    if (num.value == checkNum.value) {
+                        num.value = createPoint(getMousePos(canvas, e));
+                        //按比例转换坐标值
+                        let arr = pointTransfrom(checkPosArr,setSize)
+                        checkPosArr.length = 0
+                        checkPosArr.push(...arr);
+                        //等创建坐标执行完
+                        setTimeout(() => {
+                            // var flag = this.comparePos(this.fontPos, this.checkPosArr);
+                            //发送后端请求
+                            var captchaVerification = secretKey.value? aesEncrypt(backToken.value+'---'+JSON.stringify(checkPosArr),secretKey.value):backToken.value+'---'+JSON.stringify(checkPosArr)
+                            let data = {
+                                captchaType:captchaType.value,
+                                "pointJson":secretKey.value? aesEncrypt(JSON.stringify(checkPosArr),secretKey.value):JSON.stringify(checkPosArr),
+                                "token":backToken.value
+                            }
+                            reqCheck(data).then(res=>{
+                                if (res.repCode == "0000") {
+                                    barAreaColor.value = '#4cae4c'
+                                    barAreaBorderColor.value = '#5cb85c'
+                                    text.value = '验证成功'
+                                    bindingClick.value = false
+                                    if (mode.value=='pop') {
+                                        setTimeout(()=>{
+                                            proxy.$parent.clickShow = false;
+                                            refresh();
+                                        },1500)
+                                    }
+                                    proxy.$parent.$emit('success', {captchaVerification})
+                                }else{
+                                    proxy.$parent.$emit('error', proxy)
+                                    barAreaColor.value = '#d9534f'
+                                    barAreaBorderColor.value = '#d9534f'
+                                    text.value = '验证失败'
+                                    setTimeout(() => {
+                                        refresh();
+                                    }, 700);
+                                }
+                            })
+                        }, 400);
+                    }
+                    if (num.value < checkNum.value) {
+                        num.value = createPoint(getMousePos(canvas, e));
+                    }
+                }
+                 //获取坐标
+                const getMousePos = function (obj, e) {
+                    var x = e.offsetX 
+                    var y = e.offsetY 
+                    return {x, y}
+                }
+                //创建坐标点
+                const createPoint = function (pos) {
+                    tempPoints.push(Object.assign({}, pos))
+                    return num.value+1;
+                }
+                const refresh = function () {
+                    tempPoints.splice(0, tempPoints.length)
+                    barAreaColor.value = '#000'
+                    barAreaBorderColor.value = '#ddd'
+                    bindingClick.value = true
+                    fontPos.splice(0, fontPos.length)
+                    checkPosArr.splice(0, checkPosArr.length)
+                    num.value = 1
+                    getPictrue();
+                    text.value = '验证失败'
+                    showRefresh.value = true
+                }
+
+                // 请求背景图片和验证图片
+                function getPictrue() {
+                    let data = {
+                        captchaType:captchaType.value
+                    }
+                    reqGet(data).then(res=>{
+                        if (res.repCode == "0000") {
+                            pointBackImgBase.value = res.repData.originalImageBase64
+                            backToken.value = res.repData.token
+                            secretKey.value = res.repData.secretKey
+                            poinTextList.value = res.repData.wordList
+                            text.value = '请依次点击【' + poinTextList.value.join(",") + '】'
+                        }else{
+                            text.value = res.repMsg;
+                        }
+                    })
+                }
+                //坐标转换函数
+                const pointTransfrom = function(pointArr,imgSize){
+                    var newPointArr = pointArr.map(p=>{
+                        let x = Math.round(310 * p.x/parseInt(imgSize.imgWidth)) 
+                        let y =Math.round(155 * p.y/parseInt(imgSize.imgHeight)) 
+                        return {x,y}
+                    })
+                    return newPointArr
+                }
+                return {
+                    secretKey,
+                    checkNum,
+                    fontPos,
+                    checkPosArr,
+                    num,
+                    pointBackImgBase,
+                    poinTextList,
+                    backToken,
+                    setSize,
+                    tempPoints,
+                    text,
+                    barAreaColor,
+                    barAreaBorderColor,
+                    showRefresh,
+                    bindingClick,
+                    init,
+                    canvas,
+                    canvasClick,
+                    getMousePos,createPoint,refresh,getPictrue,pointTransfrom
+                }
+        },
+    }
+</script>

+ 366 - 0
src/views/_builtin/login/components/pwd-login/components/verifition/Verify/VerifySlide.vue

@@ -0,0 +1,366 @@
+<template>
+    <div style="position: relative;">
+        <div v-if="type === '2'" class="verify-img-out"
+             :style="{height: (parseInt(setSize.imgHeight) + vSpace) + 'px'}"
+            >
+            <div class="verify-img-panel" :style="{width: setSize.imgWidth,
+                                                   height: setSize.imgHeight,}">
+                <img :src="'data:image/png;base64,'+backImgBase" alt="" style="width:100%;height:100%;display:block">
+                <div class="verify-refresh" @click="refresh" v-show="showRefresh"><i class="iconfont icon-refresh"></i>
+                </div>
+                <transition name="tips">
+                    <span class="verify-tips" v-if="tipWords" :class="passFlag ?'suc-bg':'err-bg'">{{tipWords}}</span>
+                </transition>
+            </div>
+        </div>
+        <!-- 公共部分 -->
+        <div class="verify-bar-area" :style="{width: setSize.imgWidth,
+                                              height: barSize.height,
+                                              'line-height':barSize.height}">
+            <span class="verify-msg" v-text="text"></span>
+            <div class="verify-left-bar"
+                 :style="{width: (leftBarWidth!==undefined)?leftBarWidth: barSize.height, height: barSize.height, 'border-color': leftBarBorderColor, transaction: transitionWidth}">
+                <span class="verify-msg" v-text="finishText"></span>
+                <div class="verify-move-block"
+                     @touchstart="start"
+                     @mousedown="start"
+                     :style="{width: barSize.height, height: barSize.height, 'background-color': moveBlockBackgroundColor, left: moveBlockLeft, transition: transitionLeft}">
+                    <i :class="['verify-icon iconfont', iconClass]"
+                       :style="{color: iconColor}"></i>
+                    <div v-if="type === '2'" class="verify-sub-block"
+                        :style="{'width':Math.floor(parseInt(setSize.imgWidth)*47/310)+ 'px',
+                                  'height': setSize.imgHeight,
+                                  'top':'-' + (parseInt(setSize.imgHeight) + vSpace) + 'px',
+                                  'background-size': setSize.imgWidth + ' ' + setSize.imgHeight,
+                                  }">
+                        <img :src="'data:image/png;base64,'+blockBackImgBase" alt=""  style="width:100%;height:100%;display:block;-webkit-user-drag:none;">
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+<script type="text/babel">
+    /**
+     * VerifySlide
+     * @description 滑块
+     * */
+    import {aesEncrypt} from "./../utils/ase"
+    import {resetSize} from './../utils/util'
+    import {reqGet,reqCheck}  from "./../api/index"
+ import { computed, onMounted, reactive, ref,watch,nextTick,toRefs, watchEffect,getCurrentInstance} from 'vue';
+    //  "captchaType":"blockPuzzle",
+    export default {
+        name: 'VerifySlide',
+        props: {
+            captchaType:{
+                type:String,
+            },
+            type: {
+                type: String,
+                default: '1'
+            },
+            //弹出式pop,固定fixed
+            mode: {
+                type: String,
+                default: 'fixed'
+            },
+            vSpace: {
+                type: Number,
+                default: 5
+            },
+            explain: {
+                type: String,
+                default: '向右滑动完成验证'
+            },
+            imgSize: {
+                type: Object,
+                default() {
+                    return {
+                        width: '310px',
+                        height: '155px'
+                    }
+                }
+            },
+            blockSize: {
+                type: Object,
+                default() {
+                    return {
+                        width: '50px',
+                        height: '50px'
+                    }
+                }
+            },
+            barSize: {
+                type: Object,
+                default() {
+                    return {
+                        width: '310px',
+                        height: '40px'
+                    }
+                }
+            }
+        },
+        setup(props,context){
+            const {mode,captchaType,vSpace,imgSize,barSize,type,blockSize,explain} = toRefs(props)
+            const { proxy } = getCurrentInstance();
+            let secretKey = ref(''),           //后端返回的ase加密秘钥
+                passFlag = ref(''),         //是否通过的标识
+                backImgBase = ref(''),      //验证码背景图片
+                blockBackImgBase = ref(''), //验证滑块的背景图片
+                backToken = ref(''),        //后端返回的唯一token值
+                startMoveTime = ref(''),    //移动开始的时间
+                endMovetime = ref(''),      //移动结束的时间
+                tipsBackColor = ref(''),    //提示词的背景颜色
+                tipWords = ref(''),
+                text = ref(''),
+                finishText = ref(''),
+                setSize = reactive({
+                    imgHeight: 0,
+                    imgWidth: 0,
+                    barHeight: 0,
+                    barWidth: 0
+                }),
+                top = ref(0),
+                left = ref(0),
+                moveBlockLeft = ref(undefined),
+                leftBarWidth = ref(undefined),
+                // 移动中样式
+                moveBlockBackgroundColor = ref(undefined),
+                leftBarBorderColor = ref('#ddd'),
+                iconColor = ref(undefined),
+                iconClass = ref('icon-right'),
+                status = ref(false),	    //鼠标状态
+                isEnd = ref(false) ,		//是够验证完成
+                showRefresh = ref(true),
+                transitionLeft = ref(''),
+                transitionWidth = ref(''),
+                startLeft = ref(0) 
+
+                const barArea = computed(()=>{
+                    return proxy.$el.querySelector('.verify-bar-area')
+                })
+                function init() {
+                    text.value = explain.value
+                    getPictrue();
+                    nextTick(() => {
+                        let {imgHeight,imgWidth,barHeight,barWidth} = resetSize(proxy)
+                        setSize.imgHeight = imgHeight
+                        setSize.imgWidth = imgWidth
+                        setSize.barHeight = barHeight
+                        setSize.barWidth = barWidth
+                        proxy.$parent.$emit('ready', proxy)
+                    })
+
+                    window.removeEventListener("touchmove", function (e) {
+                        move(e);
+                    });
+                    window.removeEventListener("mousemove", function (e) {
+                        move(e);
+                    });
+
+                    //鼠标松开
+                    window.removeEventListener("touchend", function () {
+                        end();
+                    });
+                    window.removeEventListener("mouseup", function () {
+                        end();
+                    });
+
+                    window.addEventListener("touchmove", function (e) {
+                        move(e);
+                    });
+                    window.addEventListener("mousemove", function (e) {
+                        move(e);
+                    });
+
+                    //鼠标松开
+                    window.addEventListener("touchend", function () {
+                        end();
+                    });
+                    window.addEventListener("mouseup", function () {
+                        end();
+                    });
+                }
+                watch(type,()=>{
+                    init()
+                })
+                onMounted(()=>{
+                    // 禁止拖拽
+                    init()
+                    proxy.$el.onselectstart = function () {
+                        return false
+                    }
+                })
+                //鼠标按下
+                function start(e) {
+                    e = e || window.event
+                    if (!e.touches) {  //兼容PC端 
+                        var x = e.clientX;
+                    } else {           //兼容移动端
+                        var x = e.touches[0].pageX;
+                    }
+                    console.log(barArea);
+                    startLeft.value =Math.floor(x - barArea.value.getBoundingClientRect().left);
+                    startMoveTime.value = +new Date();    //开始滑动的时间
+                    if (isEnd.value == false) {
+                        text.value = ''
+                        moveBlockBackgroundColor.value = '#337ab7'
+                        leftBarBorderColor.value = '#337AB7'
+                        iconColor.value = '#fff'
+                        e.stopPropagation();
+                        status.value = true;
+                    }
+                }
+                //鼠标移动
+                function move(e) {
+                    e = e || window.event
+                    if (status.value && isEnd.value == false) {
+                        if (!e.touches) {  //兼容PC端 
+                            var x = e.clientX;
+                        } else {           //兼容移动端
+                            var x = e.touches[0].pageX;
+                        }
+                        var bar_area_left = barArea.value.getBoundingClientRect().left;
+                        var move_block_left = x - bar_area_left //小方块相对于父元素的left值
+                        if (move_block_left >= barArea.value.offsetWidth - parseInt(parseInt(blockSize.value.width) / 2) - 2) {
+                            move_block_left = barArea.value.offsetWidth - parseInt(parseInt(blockSize.value.width) / 2) - 2;
+                        }
+                        if (move_block_left <= 0) {
+                            move_block_left = parseInt(parseInt(blockSize.value.width) / 2);
+                        }
+                        //拖动后小方块的left值
+                        moveBlockLeft.value = (move_block_left - startLeft.value) + "px"
+                        leftBarWidth.value = (move_block_left - startLeft.value) + "px"
+                    }
+                }
+
+                //鼠标松开
+                function end() {
+                    endMovetime.value = +new Date(); 
+                    //判断是否重合
+                    if (status.value && isEnd.value == false) {
+                        var moveLeftDistance = parseInt((moveBlockLeft.value || '').replace('px', ''));
+                        moveLeftDistance = moveLeftDistance * 310/ parseInt(setSize.imgWidth)
+                        let data = {
+                            captchaType:captchaType.value,
+                            "pointJson":secretKey.value ? aesEncrypt(JSON.stringify({x:moveLeftDistance,y:5.0}),secretKey.value):JSON.stringify({x:moveLeftDistance,y:5.0}),
+                            "token":backToken.value
+                        }
+                        reqCheck(data).then(res=>{
+                            if (res.repCode == "0000") {
+                                moveBlockBackgroundColor.value = '#5cb85c'
+                                leftBarBorderColor.value = '#5cb85c'
+                                iconColor.value = '#fff'
+                                iconClass.value = 'icon-check'
+                                showRefresh.value = false
+                                isEnd.value = true;   
+                                if (mode.value=='pop') {
+                                    setTimeout(()=>{
+                                        proxy.$parent.clickShow = false;
+                                        refresh();
+                                    },1500)
+                                }
+                                passFlag.value = true
+                                tipWords.value = `${((endMovetime.value-startMoveTime.value)/1000).toFixed(2)}s验证成功`
+                                var captchaVerification = secretKey.value ? aesEncrypt(backToken.value+'---'+JSON.stringify({x:moveLeftDistance,y:5.0}),secretKey.value):backToken.value+'---'+JSON.stringify({x:moveLeftDistance,y:5.0})
+                                setTimeout(()=>{
+                                    tipWords.value = ""
+                                    proxy.$parent.closeBox();
+                                    proxy.$parent.$emit('success', {captchaVerification})
+                                },1000)
+                            }else{
+                                moveBlockBackgroundColor.value = '#d9534f'
+                                leftBarBorderColor.value = '#d9534f'
+                                iconColor.value = '#fff'
+                                iconClass.value = 'icon-close'
+                                passFlag.value = false
+                                setTimeout(function () {
+                                    refresh();
+                                }, 1000);
+                                proxy.$parent.$emit('error',proxy)
+                                tipWords.value = "验证失败"
+                                setTimeout(()=>{
+                                        tipWords.value = ""
+                                },1000)
+                            }
+                        })
+                        status.value = false;
+                    }
+                }
+
+                const refresh = ()=>{
+                    showRefresh.value = true
+                    finishText.value = ''
+
+                    transitionLeft.value = 'left .3s'
+                    moveBlockLeft.value = 0
+
+                    leftBarWidth.value = undefined
+                    transitionWidth.value = 'width .3s'
+
+                    leftBarBorderColor.value = '#ddd'
+                    moveBlockBackgroundColor.value = '#fff'
+                    iconColor.value = '#000'
+                    iconClass.value = 'icon-right'
+                    isEnd.value = false
+
+                    getPictrue()
+                    setTimeout(() => {
+                        transitionWidth.value = ''
+                        transitionLeft.value = ''
+                        text.value = explain.value
+                    }, 300)
+                }
+
+                // 请求背景图片和验证图片
+                function getPictrue(){
+                    let data = {
+                        captchaType:captchaType.value
+                    }
+                    reqGet(data).then(res=>{
+                        if (res.repCode == "0000") {
+                            backImgBase.value = res.repData.originalImageBase64
+                            blockBackImgBase.value = res.repData.jigsawImageBase64
+                            backToken.value = res.repData.token
+                            secretKey.value = res.repData.secretKey
+                        }else{
+                            tipWords.value = res.repMsg;
+                        }
+                    })
+                }
+                return {
+                    secretKey,           //后端返回的ase加密秘钥
+                    passFlag,         //是否通过的标识
+                    backImgBase,      //验证码背景图片
+                    blockBackImgBase, //验证滑块的背景图片
+                    backToken,        //后端返回的唯一token值
+                    startMoveTime,    //移动开始的时间
+                    endMovetime,      //移动结束的时间
+                    tipsBackColor,    //提示词的背景颜色
+                    tipWords,
+                    text,
+                    finishText,
+                    setSize,
+                    top,
+                    left,
+                    moveBlockLeft,
+                    leftBarWidth,
+                    // 移动中样式
+                    moveBlockBackgroundColor,
+                    leftBarBorderColor,
+                    iconColor,
+                    iconClass,
+                    status,	    //鼠标状态
+                    isEnd,		//是够验证完成
+                    showRefresh,
+                    transitionLeft,
+                    transitionWidth,
+                    barArea,
+                    refresh,
+                    start
+                }
+        },
+    }
+</script>
+

+ 27 - 0
src/views/_builtin/login/components/pwd-login/components/verifition/api/index.js

@@ -0,0 +1,27 @@
+/**
+ * 此处可直接引用自己项目封装好的 axios 配合后端联调
+ */
+
+
+import request from "./../utils/axios"  //组件内部封装的axios
+// import request from "@/api/axios.js"       //调用项目封装的axios
+
+//获取验证图片  以及token
+export function reqGet(data) {
+	return  request({
+        url: '/captcha/get',
+        method: 'post',
+        data
+    })
+}
+
+//滑动或者点选验证
+export function reqCheck(data) {
+	return  request({
+        url: '/captcha/check',
+        method: 'post',
+        data
+    })
+}
+
+

+ 11 - 0
src/views/_builtin/login/components/pwd-login/components/verifition/utils/ase.js

@@ -0,0 +1,11 @@
+import CryptoJS from 'crypto-js'
+/**
+ * @word 要加密的内容
+ * @keyWord String  服务器随机返回的关键字
+ *  */
+export function aesEncrypt(word,keyWord="XwKsGlMcdPMEhR1B"){
+  var key = CryptoJS.enc.Utf8.parse(keyWord);
+  var srcs = CryptoJS.enc.Utf8.parse(word);
+  var encrypted = CryptoJS.AES.encrypt(srcs, key, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
+  return encrypted.toString();
+}

+ 30 - 0
src/views/_builtin/login/components/pwd-login/components/verifition/utils/axios.js

@@ -0,0 +1,30 @@
+import axios from 'axios';
+
+axios.defaults.baseURL = 'https://captcha.anji-plus.com/captcha-api';
+
+const service = axios.create({
+  timeout: 40000,
+  headers: {
+    'X-Requested-With': 'XMLHttpRequest',
+    'Content-Type': 'application/json; charset=UTF-8'
+  },
+})
+service.interceptors.request.use(
+  config => {
+    return config
+  },
+  error => {
+    Promise.reject(error)
+  }
+)
+
+// response interceptor
+service.interceptors.response.use(
+  response => {
+    const res = response.data;
+    return res
+  },
+  error => {
+  }
+)
+export default service

+ 35 - 0
src/views/_builtin/login/components/pwd-login/components/verifition/utils/util.js

@@ -0,0 +1,35 @@
+export function resetSize(vm) {
+    var img_width, img_height, bar_width, bar_height;	//图片的宽度、高度,移动条的宽度、高度
+
+    var parentWidth = vm.$el.parentNode.offsetWidth || window.offsetWidth
+    var parentHeight = vm.$el.parentNode.offsetHeight || window.offsetHeight
+    if (vm.imgSize.width.indexOf('%') != -1) {
+        img_width = parseInt(vm.imgSize.width) / 100 * parentWidth + 'px'
+    } else {
+        img_width = vm.imgSize.width;
+    }
+
+    if (vm.imgSize.height.indexOf('%') != -1) {
+        img_height = parseInt(vm.imgSize.height) / 100 * parentHeight + 'px'
+    } else {
+        img_height = vm.imgSize.height
+    }
+
+    if (vm.barSize.width.indexOf('%') != -1) {
+        bar_width = parseInt(vm.barSize.width) / 100 * parentWidth + 'px'
+    } else {
+        bar_width = vm.barSize.width
+    }
+
+    if (vm.barSize.height.indexOf('%') != -1) {
+        bar_height = parseInt(vm.barSize.height) / 100 * parentHeight + 'px'
+    } else {
+        bar_height = vm.barSize.height
+    }
+
+    return {imgWidth: img_width, imgHeight: img_height, barWidth: bar_width, barHeight: bar_height}
+}
+
+export const _code_chars = [1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
+export const _code_color1 = ['#fffff0', '#f0ffff', '#f0fff0', '#fff0f0']
+export const _code_color2 = ['#FF0033', '#006699', '#993366', '#FF9900', '#66CC66', '#FF33CC']

+ 5 - 0
src/views/_builtin/login/components/pwd-login/index.vue

@@ -35,7 +35,9 @@
   </n-form>
 </template>
 
+
 <script setup lang="ts">
+
 import { reactive, ref } from 'vue';
 import type { FormInst, FormRules } from 'naive-ui';
 import { loginModuleLabels } from '@/constants';
@@ -73,6 +75,9 @@ function handleLoginOtherAccount(param: { userName: string; password: string })
   const { userName, password } = param;
   login(userName, password);
 }
+
+
+
 </script>
 
 <style scoped></style>

+ 69 - 0
src/views/_builtin/login/components/pwd-login/indexCp.vue

@@ -0,0 +1,69 @@
+<template>
+  <div class="captcha-container">
+    <div class="captcha-wrapper" ref="wrapperRef">
+      <div class="slider" ref="sliderRef" :style="{ left: sliderLeft + 'px' }" @mousedown="startDrag"></div>
+      <div class="background">请拖动滑块完成验证</div>
+    </div>
+    <button class="verify-btn" @click="verify">验证</button>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted, onUnmounted } from 'vue';
+
+const sliderLeft = ref(0);
+const isDragging = ref(false);
+let dragStartX = 0;
+const wrapperRef = ref<HTMLDivElement | null>(null); // 创建一个引用
+const sliderRef = ref<HTMLDivElement | null>(null); // 创建另一个引用
+
+const startDrag = (event: MouseEvent) => {
+  isDragging.value = true;
+  dragStartX = event.clientX;
+};
+
+const handleDrag = (event: MouseEvent) => {
+  if (!isDragging.value) return;
+
+  const sliderWidth = sliderRef.value?.offsetWidth ?? 0; // 使用引用获取宽度
+  const wrapperWidth = wrapperRef.value?.clientWidth ?? 0;
+  const maxSliderLeft = wrapperWidth - sliderWidth;
+
+  let newSliderLeft = event.clientX - dragStartX;
+  if (newSliderLeft < 0) newSliderLeft = 0;
+  if (newSliderLeft > maxSliderLeft) newSliderLeft = maxSliderLeft;
+
+  sliderLeft.value = newSliderLeft;
+};
+
+const endDrag = () => {
+  isDragging.value = false;
+};
+
+const verify = () => {
+  if (!wrapperRef.value || !sliderRef.value) return; // 检查引用是否存在
+
+  const maxSliderLeft = wrapperRef.value.clientWidth - sliderRef.value.clientWidth; // 计算最大滑块左侧位置
+  if (sliderLeft.value === maxSliderLeft) {
+    // 验证成功
+    console.log('验证成功');
+  } else {
+    // 验证失败
+    console.log('验证失败');
+  }
+};
+
+onMounted(() => {
+  window.addEventListener('mousemove', handleDrag);
+  window.addEventListener('mouseup', endDrag);
+});
+
+onUnmounted(() => {
+  window.removeEventListener('mousemove', handleDrag);
+  window.removeEventListener('mouseup', endDrag);
+});
+</script>
+
+<style scoped>
+/* 样式内容 */
+</style>

+ 1 - 0
src/views/index.ts

@@ -36,6 +36,7 @@ export const views: Record<
   management_auth: () => import('./management/auth/index.vue'),
   management_role: () => import('./management/role/index.vue'),
   management_route: () => import('./management/route/index.vue'),
+  management_sort: () => import('./management/sort/index.vue'),
   management_user: () => import('./management/user/index.vue'),
   'multi-menu_first_second-new_third': () => import('./multi-menu/first/second-new/third/index.vue'),
   'multi-menu_first_second': () => import('./multi-menu/first/second/index.vue'),

+ 87 - 4
src/views/management/auth/index.vue

@@ -1,9 +1,92 @@
 <template>
-  <div>权限管理</div>
+  <div class="h-full overflow-hidden">
+    <n-card title="权限管理" :bordered="false" class="rounded-16px shadow-sm">
+      <n-data-table :columns="columns" :data="tableData" :pagination="pagination" />
+    </n-card>
+  </div>
 </template>
 
-<script setup lang="tsx">
+<script setup lang="ts">
+import { ref } from 'vue';
+import type { Ref } from 'vue';
+import type { DataTableColumns, PaginationProps } from 'naive-ui';
+import { query } from '~/src/service/api/user';
+import type { QueryParams } from '~/src/service/api/user';
 
-</script>
+const tableData = ref<QueryParams[]>([]);
+const pagination: PaginationProps = ref({
+  page: 1,
+  pageSize: 10,
+  showSizePicker: true,
+  pageSizes: [10, 20, 50]
+  // onChange: (page: number) => {
+  //   // 处理页码变化
+  // },
+  // onUpdatePageSize: (pageSize: number) => {
+  //   // 处理每页显示数量变化
+  // }
+}).value;
+
+async function getTableData() {
+  const pageNum = pagination.page as number;
+  const pageSize = pagination.pageSize as number;
+  const params: QueryParams = {};
+
+  query(pageNum, pageSize, params).then(res => {
+    // console.log(res);
+    tableData.value = res.data as [];
+  });
+}
 
-<style scoped></style>
+const columns: Ref<DataTableColumns<QueryParams>> = ref([
+  {
+    type: 'selection',
+    align: 'center'
+  },
+  {
+    key: 'name',
+    title: '部门名称',
+    align: 'center'
+  },
+  {
+    key: 'description',
+    title: '部门地址',
+    align: 'center'
+  },
+  {
+    key: 'isActive',
+    title: '是否激活',
+    align: 'center',
+    render: (row: QueryParams) => {
+      return row.isActive ? '是' : '否';
+    }
+  },
+  {
+    key: 'createTime',
+    title: '创建时间',
+    align: 'center'
+  },
+  {
+    key: 'modifyTime',
+    title: '修改时间',
+    align: 'center'
+  },
+  {
+    key: 'createUid',
+    title: '创建用户ID',
+    align: 'center'
+  },
+  {
+    key: 'disabled',
+    title: '状态',
+    align: 'center'
+  }
+]) as Ref<DataTableColumns<QueryParams>>;
+
+function init() {
+  getTableData();
+}
+
+// 初始化
+init();
+</script>

+ 49 - 0
src/views/management/sort/api.ts

@@ -0,0 +1,49 @@
+import type { UserPageQuery } from '@fast-crud/fast-crud';
+import { request } from '@/service/request';
+
+// 响应接口
+export interface SelectAll_1Res {
+  status: boolean;
+  msg: string;
+  data: Record<string, unknown>;
+}
+
+const apiPrefix = ``;
+
+export type HeaderGroupRecord = {
+  id: number;
+  [key: string]: any;
+};
+
+function resHandle(res: any) {
+  return res.data;
+}
+export async function selectAll_1(query: UserPageQuery) {
+  const res = await request.get(`/selectAll`, query);
+  return resHandle(res);
+}
+
+export async function AddObj(obj: HeaderGroupRecord) {
+  const res = await request.post(`${apiPrefix}/add`, obj);
+  return resHandle(res);
+}
+
+export async function UpdateObj(obj: HeaderGroupRecord) {
+  const res = await request.post(`${apiPrefix}/update`, obj);
+  return resHandle(res);
+}
+
+export async function DelObj(id: number) {
+  const res = await request.post(`${apiPrefix}/delete`, { id });
+  return resHandle(res);
+}
+
+export async function GetObj(id: number) {
+  const res = await request.get(`${apiPrefix}/info`, { params: { id } });
+  return resHandle(res);
+}
+
+export async function BatchDelete(ids: number[]) {
+  const res = await request.post(`${apiPrefix}/batchDelete`, { ids });
+  return resHandle(res);
+}

+ 153 - 0
src/views/management/sort/components/table-action-add.vue

@@ -0,0 +1,153 @@
+<template>
+  <n-modal v-model:show="modalVisible" preset="card" :title="title" class="w-700px">
+    <n-form ref="formRef" label-placement="left" :label-width="80" :model="formModel" :rules="rules">
+      <n-grid :cols="24" :x-gap="18">
+        <n-form-item-grid-item :span="12" label="ID" path="userName">
+          <n-input v-model:value="formModel.userName" />
+        </n-form-item-grid-item>
+        <n-form-item-grid-item :span="12" label="学科名称" path="age">
+          <n-input-number v-model:value="formModel.age" clearable />
+        </n-form-item-grid-item>
+        <n-form-item-grid-item :span="12" label="学科描述" path="gender">
+          <n-radio-group v-model:value="formModel.gender">
+            <n-radio v-for="item in genderOptions" :key="item.value" :value="item.value">{{ item.label }}</n-radio>
+          </n-radio-group>
+        </n-form-item-grid-item>
+        <n-form-item-grid-item :span="12" label="创建时间" path="phone">
+          <n-input v-model:value="formModel.phone" />
+        </n-form-item-grid-item>
+        <n-form-item-grid-item :span="12" label="修改时间" path="email">
+          <n-input v-model:value="formModel.email" />
+        </n-form-item-grid-item>
+				<n-form-item-grid-item :span="12" label="创建用户ID" path="email">
+          <n-input v-model:value="formModel.email" />
+        </n-form-item-grid-item>
+        <n-form-item-grid-item :span="12" label="状态" path="userStatus">
+          <n-select v-model:value="formModel.userStatus" :options="userStatusOptions" />
+        </n-form-item-grid-item>
+      </n-grid>
+      <n-space class="w-full pt-16px" :size="24" justify="end">
+        <n-button class="w-72px" @click="closeModal">取消</n-button>
+        <n-button class="w-72px" type="primary" @click="handleSubmit">确定</n-button>
+      </n-space>
+    </n-form>
+  </n-modal>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, reactive, watch } from 'vue';
+import type { FormInst, FormItemRule } from 'naive-ui';
+import { genderOptions, userStatusOptions } from '@/constants';
+import { formRules, createRequiredFormRule } from '@/utils';
+
+export interface Props {
+  /** 弹窗可见性 */
+  visible: boolean;
+  /**
+   * 弹窗类型
+   * add: 新增
+   * edit: 编辑
+   */
+  type?: 'add' | 'edit' | 'delete';
+  /** 编辑的表格行数据 */
+  editData?: UserManagement.User | null;
+}
+
+export type ModalType = NonNullable<Props['type']>;
+
+defineOptions({ name: 'TableActionModal' });
+
+const props = withDefaults(defineProps<Props>(), {
+  type: 'add',
+  editData: null
+});
+
+interface Emits {
+  (e: 'update:visible', visible: boolean): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const modalVisible = computed({
+  get() {
+    return props.visible;
+  },
+  set(visible) {
+    emit('update:visible', visible);
+  }
+});
+const closeModal = () => {
+  modalVisible.value = false;
+};
+
+const title = computed(() => {
+  const titles: Record<ModalType, string> = {
+    add: '添加用户',
+    edit: '编辑用户'
+  };
+  return titles[props.type];
+});
+
+const formRef = ref<HTMLElement & FormInst>();
+
+type FormModel = Pick<UserManagement.User, 'userName' | 'age' | 'gender' | 'phone' | 'email' | 'userStatus'>;
+
+const formModel = reactive<FormModel>(createDefaultFormModel());
+
+const rules: Record<keyof FormModel, FormItemRule | FormItemRule[]> = {
+  userName: createRequiredFormRule('请输入用户名'),
+  age: createRequiredFormRule('请输入年龄'),
+  gender: createRequiredFormRule('请选择性别'),
+  phone: formRules.phone,
+  email: formRules.email,
+  userStatus: createRequiredFormRule('请选择用户状态')
+};
+
+function createDefaultFormModel(): FormModel {
+  return {
+    userName: '',
+    age: null,
+    gender: null,
+    phone: '',
+    email: null,
+    userStatus: null
+  };
+}
+
+function handleUpdateFormModel(model: Partial<FormModel>) {
+  Object.assign(formModel, model);
+}
+
+function handleUpdateFormModelByModalType() {
+  const handlers: Record<ModalType, () => void> = {
+    add: () => {
+      const defaultFormModel = createDefaultFormModel();
+      handleUpdateFormModel(defaultFormModel);
+    },
+    edit: () => {
+      if (props.editData) {
+        handleUpdateFormModel(props.editData);
+      }
+    }
+  };
+
+  handlers[props.type]();
+}
+
+async function handleSubmit() {
+  await formRef.value?.validate();
+  window.$message?.success('新增成功!');
+  closeModal();
+}
+
+watch(
+  () => props.visible,
+  newValue => {
+    if (newValue) {
+      handleUpdateFormModelByModalType();
+    }
+  }
+);
+</script>
+
+<style scoped></style>

+ 181 - 0
src/views/management/sort/crud.tsx

@@ -0,0 +1,181 @@
+import type { AddReq, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from '@fast-crud/fast-crud';
+import { dict } from '@fast-crud/fast-crud';
+import dayjs from 'dayjs';
+import * as api from './api';
+import { selectAll_1 } from './api';
+export default function createCrudOptions(): CreateCrudOptionsRet {
+	const pageRequest = async (query: any): Promise<any> => {
+    // 调用你自己的函数从接口获取数据
+    const data = await selectAll_1(query);
+    return {
+      rows: data,
+      total: data.length
+    };
+  };
+  const editRequest = async (ctx: EditReq) => {
+    const { form, row } = ctx;
+    form.id = row.id;
+    return api.UpdateObj(form);
+  };
+  const delRequest = async (ctx: DelReq) => {
+    const { row } = ctx;
+    return api.DelObj(row.id);
+  };
+
+  const addRequest = async (req: AddReq) => {
+    const { form } = req;
+    return api.AddObj(form);
+  };
+  return {
+    crudOptions: {
+      container: {
+        is: 'fs-layout-card'
+      },
+      request: {
+        pageRequest,
+        addRequest,
+        editRequest,
+        delRequest
+      },
+      columns: {
+        id: {
+          title: 'ID',
+          key: 'id',
+          type: 'number',
+					search: { show: true },
+          column: {
+            width: 50,
+						align:'center',
+          },
+          form: {
+            show: false
+          }
+        },
+				name: {
+          title: '部门名称',
+					search: { show: true },
+          key: 'name',
+          type: 'text',
+          column: {
+            width: 150,
+						align:'center',
+          },
+          form: {
+            show: true
+          }
+        },
+        // select: {
+        //   title: '状态',
+        //   search: { show: true },
+        //   type: 'dict-select',
+				// 	align: 'center',
+        //   // dict: dict({
+        //   //   url: '/mock/crud/demo/dict'
+        //   // })
+        // },
+        description: {
+          title: '部门地址',
+					key: 'description',
+          type: 'text',
+          search: { show: true },
+					column: {
+            width: 200,
+						align:'center',
+          },
+        },
+        createTime: {
+					key: 'createTime',
+          title: '创建时间',
+					type: 'text',
+					column: {
+            width: 250,
+						align:'center',
+          },
+					search: { show: true },
+          // naive 默认仅支持数字类型时间戳作为日期输入与输出
+          // 字符串类型的时间需要转换格式
+          valueBuilder(context) {
+            const { value, row, key } = context;
+            if (value) {
+              // naive 默认仅支持时间戳作为日期输入与输出
+              row[key] = dayjs(value).valueOf();
+            }
+          },
+          valueResolve(context) {
+            const { value, form, key } = context;
+            if (value) {
+              form[key] = dayjs(value).format('YYYY-MM-DD HH:mm:ss');
+            }
+          }
+        },
+        modifyTime: {
+          title: '修改时间',
+					key: 'modifyTime',
+					type: 'text',
+					align: 'center',
+					column: {
+            width: 250,
+						align:'center',
+          },
+          // naive 默认仅支持数字类型时间戳作为日期输入与输出
+          // 字符串类型的时间需要转换格式
+          valueBuilder(context) {
+            const { value, row, key } = context;
+            if (value) {
+              // naive 默认仅支持时间戳作为日期输入与输出
+              row[key] = dayjs(value).valueOf();
+            }
+          },
+          valueResolve(context) {
+            const { value, form, key } = context;
+            if (value) {
+              form[key] = dayjs(value).format('YYYY-MM-DD HH:mm:ss');
+            }
+          }
+        },
+				createUid: {
+          title: '创建用户ID',
+					key: 'createUid',
+          type: 'file-uploader',
+					column: {
+            width: 250,
+						align:'center',
+          },
+        },
+				disabled: {
+					key: 'disabled',
+					title: '状态',
+					type: 'dict-select',
+					column: {
+            width: 100,
+						align:'center',
+          },
+					dict: dict({
+            url: '/mock/crud/demo/dict'
+          }),
+          search: { show: true }
+        },
+        // richtext: {
+        //   title: '富文本',
+        //   type: 'editor-wang5',
+        //   column: {
+        //     // cell中不显示
+        //     show: false
+        //   },
+        //   form: {
+        //     col: {
+        //       // 横跨两列
+        //       span: 24
+        //     },
+        //     component: {
+        //       style: {
+        //         height: '300px'
+        //       }
+        //     }
+        //   }
+        // }
+      },
+
+    }
+  };
+}

+ 180 - 0
src/views/management/sort/index.vue

@@ -0,0 +1,180 @@
+<template>
+	<div class="h-full overflow-hidden">
+		<n-card title="课程分类" :bordered="false" class="rounded-16px shadow-sm">
+			<n-space class="pb-12px" justify="space-between">
+				<n-space>
+					<n-button type="primary" @click="addTableData">
+						<icon-ic-round-plus class="mr-4px text-20px" />
+						新增
+					</n-button>
+					<!-- <n-button type="error">
+            <icon-ic-round-delete class="mr-4px text-20px" />
+            删除
+          </n-button>
+          <n-button type="success">
+            <icon-uil:export class="mr-4px text-20px" />
+            导出Excel
+          </n-button> -->
+				</n-space>
+				<n-space align="center" :size="18">
+					<n-button size="small" type="primary" @click="getTableData">
+						<icon-mdi-refresh class="mr-4px text-16px" />
+						刷新表格
+					</n-button>
+					<column-setting v-model:columns="columns" />
+				</n-space>
+			</n-space>
+			<n-data-table :columns="columns" :data="tableData" :pagination="pagination" :row-key="rowKey"
+				@update:checked-row-keys="handleCheck" />
+
+			<table-action-add v-model:visible="visible" :type="modalType"  />
+		</n-card>
+	</div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue'
+import type { DataTableRowKey } from 'naive-ui'
+import { ref } from 'vue';
+import { useBoolean } from '@/hooks';
+import TableActionAdd from './components/table-action-add.vue'
+import type { ModalType } from './components/table-action-add.vue';
+import type { DataTableColumns, PaginationProps } from 'naive-ui';
+import { selectAll_1, addEasEduCategory } from '~/src/service/api/user';
+import type {AddEasEduCategoryParams} from '~/src/service/api/user';
+type RowData = {
+	key: number
+	name: string
+	age: string
+	address: string
+	description: string
+	createTime: string
+	modifyTime: string
+	createUid: number
+	disabled: string
+}
+const { bool: visible, setTrue: openModal } = useBoolean();
+
+const tableData = ref<any[]>([]);
+
+// const checkedRowKeysRef = ref<DataTableRowKey[]>([])
+const pagination: PaginationProps = ref({
+	page: 1,
+	pageSize: 10,
+	showSizePicker: true,
+	pageSizes: [10, 20, 50]
+	// onChange: (page: number) => {
+	//   // 处理页码变化
+	// },
+	// onUpdatePageSize: (pageSize: number) => {
+	//   // 处理每页显示数量变化
+	// }
+}).value;
+
+async function getTableData() {
+	// const pageNum = pagination.page as number;
+	// const pageSize = pagination.pageSize as number;
+	// const params: any = {};
+
+	selectAll_1().then(res => {
+		// console.log(res);
+		tableData.value = res.data as [];
+	});
+}
+const modalType = ref<ModalType>('add');
+
+function setModalType(type: ModalType) {
+  modalType.value = type;
+}
+
+const addData = ref<AddEasEduCategoryParams[]>([]);
+async function addTableData() {
+	// const pageNum = pagination.page as number;
+	// const pageSize = pagination.pageSize as number;
+	// const params: any = {};
+  openModal();
+  setModalType('add');
+	const params: AddEasEduCategoryParams = {};
+
+	addEasEduCategory(params).then(res => {
+		// console.log(res);
+		addData.value = res.data as [];
+	});
+}
+
+
+function init() {
+	getTableData();
+}
+
+// 初始化
+init();
+
+const createColumns = (): DataTableColumns<RowData> => [
+	{
+		type: 'selection',
+		align: 'center',
+	},
+	{
+		key: 'id',
+		title: "ID",
+		align: 'center'
+	},
+	{
+		key: 'name',
+		title: '学科名称',
+		align: 'center'
+	},
+	{
+		key: 'description',
+		title: '学科描述',
+		align: 'center'
+	},
+	{
+		key: 'createTime',
+		title: '创建时间',
+		align: 'center'
+	},
+	{
+		key: 'modifyTime',
+		title: '修改时间',
+		align: 'center'
+	},
+	{
+		key: 'createUid',
+		title: '创建用户ID',
+		align: 'center'
+	},
+	{
+		key: 'disabled',
+		title: '状态',
+		align: 'center'
+	}
+]
+
+
+export default defineComponent({
+	components:{
+		TableActionAdd,
+	},
+	setup() {
+		const checkedRowKeysRef = ref<DataTableRowKey[]>([])
+
+		return {
+			tableData,
+			modalType,
+			getTableData,
+			addTableData,
+			setModalType,
+			visible,
+			columns: createColumns(),
+			checkedRowKeys: checkedRowKeysRef,
+			pagination,
+			rowKey: (row: RowData) => row.name,
+			handleCheck(rowKeys: DataTableRowKey[]) {
+				checkedRowKeysRef.value = rowKeys
+			}
+		}
+	}
+})
+</script>

+ 126 - 0
src/views/management/sort/index.vuebak

@@ -0,0 +1,126 @@
+<template>
+	<div class="h-full overflow-hidden">
+		<n-card title="用户管理" :bordered="false" class="rounded-16px shadow-sm">
+			<n-space class="pb-12px" justify="space-between">
+				<!-- <n-space>
+          <n-button type="primary" @click="handleAddTable">
+            <icon-ic-round-plus class="mr-4px text-20px" />
+            新增
+          </n-button>
+          <n-button type="error">
+            <icon-ic-round-delete class="mr-4px text-20px" />
+            删除
+          </n-button>
+          <n-button type="success">
+            <icon-uil:export class="mr-4px text-20px" />
+            导出Excel
+          </n-button>
+        </n-space> -->
+				<!-- <n-space align="center" :size="18">
+          <n-button size="small" type="primary" @click="getTableData">
+            <icon-mdi-refresh class="mr-4px text-16px" :class="{ 'animate-spin': loading }" />
+            刷新表格
+          </n-button>
+          <column-setting v-model:columns="columns" />
+        </n-space> -->
+			</n-space>
+			<n-data-table :columns="columns" :data="tableData" :pagination="pagination"  />
+
+			<!-- <table-action-modal v-model:visible="visible" :type="modalType" :edit-data="editData" /> -->
+		</n-card>
+	</div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+import type { Ref } from 'vue';
+// import type { SelectAll_1Res } from './api';
+import type { DataTableColumns, PaginationProps } from 'naive-ui';
+// import { query } from '~/src/service/api/user';
+// import type { QueryParams } from '~/src/service/api/user';
+import { selectAll_1 } from '~/src/service/api/user';
+const tableData = ref<any[]>([]);
+const pagination: PaginationProps = ref({
+	page: 1,
+	pageSize: 10,
+	showSizePicker: true,
+	pageSizes: [10, 20, 50]
+	// onChange: (page: number) => {
+	//   // 处理页码变化
+	// },
+	// onUpdatePageSize: (pageSize: number) => {
+	//   // 处理每页显示数量变化
+	// }
+}).value;
+
+async function getTableData() {
+	// const pageNum = pagination.page as number;
+	// const pageSize = pagination.pageSize as number;
+	// const params: any = {};
+
+	selectAll_1().then(res => {
+		// console.log(res);
+		tableData.value = res.data as [];
+	});
+}
+
+type RowData = {
+	key: number
+	id: number
+	name: string
+	description: string
+	createTime: string
+	modifyTime: string
+	createUid: number
+	disabled: string
+}
+
+const columns: Ref<DataTableColumns<RowData>> = ref([
+	{
+		type: 'selection',
+		align: 'center',
+	},
+	{
+		key: 'id',
+		title: "ID",
+		align: 'center'
+	},
+	{
+		key: 'name',
+		title: '部门名称',
+		align: 'center'
+	},
+	{
+		key: 'description',
+		title: '部门地址',
+		align: 'center'
+	},
+	{
+		key: 'createTime',
+		title: '创建时间',
+		align: 'center'
+	},
+	{
+		key: 'modifyTime',
+		title: '修改时间',
+		align: 'center'
+	},
+	{
+		key: 'createUid',
+		title: '创建用户ID',
+		align: 'center'
+	},
+	{
+		key: 'disabled',
+		title: '状态',
+		align: 'center'
+	}
+]) as Ref<DataTableColumns<any>>;
+
+function init() {
+	getTableData();
+}
+
+// 初始化
+init();
+</script>

+ 9 - 0
src/views/management/usdt/index.vue

@@ -0,0 +1,9 @@
+<template>
+  <div>路由管理</div>
+</template>
+
+<script setup lang="ts">
+
+</script>
+
+<style scoped></style>

+ 1 - 1
src/views/management/user/components/table-action-modal.vue

@@ -2,7 +2,7 @@
   <n-modal v-model:show="modalVisible" preset="card" :title="title" class="w-700px">
     <n-form ref="formRef" label-placement="left" :label-width="80" :model="formModel" :rules="rules">
       <n-grid :cols="24" :x-gap="18">
-        <n-form-item-grid-item :span="12" label="用户名" path="userName">
+        <n-form-item-grid-item :span="12" label="id" path="userName">
           <n-input v-model:value="formModel.userName" />
         </n-form-item-grid-item>
         <n-form-item-grid-item :span="12" label="年龄" path="age">

+ 156 - 63
src/views/management/user/index.vue

@@ -1,105 +1,196 @@
 <template>
   <div class="h-full overflow-hidden">
-    <n-card title="权限管理" :bordered="false" class="rounded-16px shadow-sm">
-      <n-data-table :columns="columns" :data="tableData" :pagination="pagination" />
+    <n-card title="用户管理" :bordered="false" class="rounded-16px shadow-sm">
+      <n-space class="pb-12px" justify="space-between">
+        <n-space>
+          <n-button type="primary" @click="handleAddTable">
+            <icon-ic-round-plus class="mr-4px text-20px" />
+            新增
+          </n-button>
+          <n-button type="error">
+            <icon-ic-round-delete class="mr-4px text-20px" />
+            删除
+          </n-button>
+          <n-button type="success">
+            <icon-uil:export class="mr-4px text-20px" />
+            导出Excel
+          </n-button>
+        </n-space>
+        <n-space align="center" :size="18">
+          <n-button size="small" type="primary" @click="getTableData">
+            <icon-mdi-refresh class="mr-4px text-16px" :class="{ 'animate-spin': loading }" />
+            刷新表格
+          </n-button>
+          <column-setting v-model:columns="columns" />
+        </n-space>
+      </n-space>
+      <n-data-table :columns="columns" :data="tableData" :loading="loading" :pagination="pagination" />
+      <table-action-modal v-model:visible="visible" :type="modalType" :edit-data="editData" />
     </n-card>
   </div>
 </template>
 
-<script setup lang="ts">
-import { ref } from 'vue';
+<script setup lang="tsx">
+import { reactive, ref } from 'vue';
 import type { Ref } from 'vue';
+import { NButton, NPopconfirm, NSpace, NTag } from 'naive-ui';
 import type { DataTableColumns, PaginationProps } from 'naive-ui';
-import { query_1 } from '~/src/service/api/user';
-import type { Query_1Params } from '~/src/service/api/user';
+import { genderLabels, userStatusLabels } from '@/constants';
+import { fetchUserList } from '@/service';
+import { useBoolean, useLoading } from '@/hooks';
+import TableActionModal from './components/table-action-modal.vue';
+import type { ModalType } from './components/table-action-modal.vue';
+import ColumnSetting from './components/column-setting.vue';
 
-const tableData = ref<Query_1Params[]>([]);
-const pagination: PaginationProps = ref({
-  page: 1,
-  pageSize: 10,
-  showSizePicker: true,
-  pageSizes: [10, 20, 50]
-  // onChange: (page: number) => {
-  // 	// 处理页码变化
-  // },
-  // onUpdatePageSize: (pageSize: number) => {
-  // 	// 处理每页显示数量变化
-  // }
-}).value;
+const { loading, startLoading, endLoading } = useLoading(false);
+const { bool: visible, setTrue: openModal } = useBoolean();
+
+const tableData = ref<UserManagement.User[]>([]);
+function setTableData(data: UserManagement.User[]) {
+  tableData.value = data;
+}
 
 async function getTableData() {
-  const pageNum = pagination.page as number;
-  const pageSize = pagination.pageSize as number;
-
-  const params: Query_1Params = {
-    depname: '',
-    address: '',
-    phone: '',
-    email: '',
-    manager: '',
-    createTime: '',
-    modifyTime: '',
-    createUid: 0,
-    disabled: ''
-  };
-
-  query_1(pageNum, pageSize, params).then(res => {
-    console.log(res);
-    tableData.value = res.data as [];
-  });
+  startLoading();
+  const { data } = await fetchUserList();
+  if (data) {
+    setTimeout(() => {
+      setTableData(data);
+      endLoading();
+    }, 1000);
+  }
 }
 
-const columns: Ref<DataTableColumns<Query_1Params>> = ref([
+const columns: Ref<DataTableColumns<UserManagement.User>> = ref([
   {
     type: 'selection',
     align: 'center'
   },
   {
-    key: 'depname',
-    title: '部门名称',
+    key: 'index',
+    title: '序号',
     align: 'center'
   },
   {
-    key: 'address',
-    title: '部门地址',
+    key: 'userName',
+    title: '用户名',
     align: 'center'
   },
   {
-    key: 'phone',
-    title: '部门电话',
+    key: 'age',
+    title: '用户年龄',
     align: 'center'
   },
   {
-    key: 'email',
-    title: '部门电子邮箱',
-    align: 'center'
+    key: 'gender',
+    title: '性别',
+    align: 'center',
+    render: row => {
+      if (row.gender) {
+        const tagTypes: Record<UserManagement.GenderKey, NaiveUI.ThemeColor> = {
+          '0': 'success',
+          '1': 'warning'
+        };
+
+        return <NTag type={tagTypes[row.gender]}>{genderLabels[row.gender]}</NTag>;
+      }
+
+      return <span></span>;
+    }
   },
   {
-    key: 'manager',
-    title: '部门负责人',
+    key: 'phone',
+    title: '手机号码',
     align: 'center'
   },
   {
-    key: 'createTime',
-    title: '创建时间',
+    key: 'email',
+    title: '邮箱',
     align: 'center'
   },
   {
-    key: 'modifyTime',
-    title: '修改时间',
-    align: 'center'
+    key: 'userStatus',
+    title: '状态',
+    align: 'center',
+    render: row => {
+      if (row.userStatus) {
+        const tagTypes: Record<UserManagement.UserStatusKey, NaiveUI.ThemeColor> = {
+          '1': 'success',
+          '2': 'error',
+          '3': 'warning',
+          '4': 'default'
+        };
+
+        return <NTag type={tagTypes[row.userStatus]}>{userStatusLabels[row.userStatus]}</NTag>;
+      }
+      return <span></span>;
+    }
   },
   {
-    key: 'createUid',
-    title: '创建用户ID',
-    align: 'center'
+    key: 'actions',
+    title: '操作',
+    align: 'center',
+    render: row => {
+      return (
+        <NSpace justify={'center'}>
+          <NButton size={'small'} onClick={() => handleEditTable(row.id)}>
+            编辑
+          </NButton>
+          <NPopconfirm onPositiveClick={() => handleDeleteTable(row.id)}>
+            {{
+              default: () => '确认删除',
+              trigger: () => <NButton size={'small'}>删除</NButton>
+            }}
+          </NPopconfirm>
+        </NSpace>
+      );
+    }
+  }
+]) as Ref<DataTableColumns<UserManagement.User>>;
+
+const modalType = ref<ModalType>('add');
+
+function setModalType(type: ModalType) {
+  modalType.value = type;
+}
+
+const editData = ref<UserManagement.User | null>(null);
+
+function setEditData(data: UserManagement.User | null) {
+  editData.value = data;
+}
+
+function handleAddTable() {
+  openModal();
+  setModalType('add');
+}
+
+function handleEditTable(rowId: string) {
+  const findItem = tableData.value.find(item => item.id === rowId);
+  if (findItem) {
+    setEditData(findItem);
+  }
+  setModalType('edit');
+  openModal();
+}
+
+function handleDeleteTable(rowId: string) {
+  window.$message?.info(`点击了删除,rowId为${rowId}`);
+}
+
+const pagination: PaginationProps = reactive({
+  page: 1,
+  pageSize: 10,
+  showSizePicker: true,
+  pageSizes: [10, 15, 20, 25, 30],
+  onChange: (page: number) => {
+    pagination.page = page;
   },
-  {
-    key: 'disabled',
-    title: '状态',
-    align: 'center'
+  onUpdatePageSize: (pageSize: number) => {
+    pagination.pageSize = pageSize;
+    pagination.page = 1;
   }
-]) as Ref<DataTableColumns<Query_1Params>>;
+});
 
 function init() {
   getTableData();
@@ -108,3 +199,5 @@ function init() {
 // 初始化
 init();
 </script>
+
+<style scoped></style>

Some files were not shown because too many files changed in this diff