Jelajahi Sumber

Merge branch 'feature/3.x-analysis' into dev

gemercheung 2 tahun lalu
induk
melakukan
75519e7a59
100 mengubah file dengan 9515 tambahan dan 6811 penghapusan
  1. 37 0
      .cz-config.js
  2. 50 0
      mock/notification/list.ts
  3. 78 70
      package.json
  4. 3461 3173
      pnpm-lock.yaml
  5. 3 0
      readme.md
  6. 12 7
      src/api/corporation/modal.ts
  7. 2 2
      src/api/corporation/model.ts
  8. 89 0
      src/api/dashboard/analysis.ts
  9. 23 0
      src/api/dashboard/model.ts
  10. 12 0
      src/api/member/list.ts
  11. 19 19
      src/api/member/model.ts
  12. 29 28
      src/api/model/baseModel.ts
  13. 78 0
      src/api/notification/list.ts
  14. 33 0
      src/api/notification/model.ts
  15. 3 2
      src/api/scene/list.ts
  16. 3 2
      src/api/scene/live.ts
  17. 101 95
      src/api/scene/model.ts
  18. 32 2
      src/api/staff/list.ts
  19. 5 0
      src/api/staff/model.ts
  20. 83 72
      src/api/sys/model/userModel.ts
  21. 51 1
      src/api/sys/user.ts
  22. 61 54
      src/components/CountDown/src/CountdownInput.vue
  23. 8 3
      src/components/Cropper/src/CopperModal.vue
  24. 2 0
      src/components/Form/index.ts
  25. 11 10
      src/components/Form/src/BasicForm.vue
  26. 4 0
      src/components/Form/src/componentMap.ts
  27. 28 3
      src/components/Form/src/components/ApiCascader.vue
  28. 4 11
      src/components/Form/src/components/ApiSelect.vue
  29. 135 0
      src/components/Form/src/components/ApiTransfer.vue
  30. 90 0
      src/components/Form/src/components/ApiTree.vue
  31. 19 7
      src/components/Form/src/components/FormItem.vue
  32. 6 1
      src/components/Form/src/hooks/useAdvanced.ts
  33. 52 2
      src/components/Form/src/hooks/useFormEvents.ts
  34. 47 4
      src/components/Form/src/hooks/useFormValues.ts
  35. 2 1
      src/components/Form/src/types/form.ts
  36. 3 2
      src/components/Form/src/types/index.ts
  37. 1 1
      src/components/Table/src/components/TableImg.vue
  38. 1 0
      src/layouts/default/header/components/lock/LockModal.vue
  39. 60 20
      src/layouts/default/header/components/notify/index.vue
  40. 3 3
      src/layouts/default/header/index.vue
  41. 19 0
      src/locales/lang/zh-CN/antdLocale/DatePicker.ts
  42. 105 103
      src/locales/lang/zh-CN/sys.ts
  43. 11 10
      src/locales/lang/zh_CN.ts
  44. 69 69
      src/locales/useLocale.ts
  45. 3 2
      src/settings/componentSetting.ts
  46. 29 29
      src/settings/localeSetting.ts
  47. 25 23
      src/utils/dateUtil.ts
  48. 287 275
      src/utils/http/axios/index.ts
  49. 131 130
      src/views/advertisement/list.vue
  50. 98 97
      src/views/advertisement/listDrawer.vue
  51. 147 146
      src/views/advertisement/pads.vue
  52. 106 106
      src/views/advertisement/padsDrawer.vue
  53. 1 1
      src/views/corporation/AddCorporationModal.vue
  54. 140 140
      src/views/corporation/chargeModal.vue
  55. 48 18
      src/views/dashboard/analysis/components/VisitAnalysis.vue
  56. 51 13
      src/views/dashboard/analysis/components/VisitAnalysisBar.vue
  57. 37 16
      src/views/dashboard/analysis/components/props.ts
  58. 278 0
      src/views/dashboard/analysis/enterprise.vue
  59. 170 22
      src/views/dashboard/analysis/index.vue
  60. 0 4
      src/views/feedback/list.vue
  61. 193 195
      src/views/member/list.vue
  62. 319 0
      src/views/notification/addModal.vue
  63. 169 0
      src/views/notification/center.vue
  64. 197 0
      src/views/notification/list.vue
  65. 5 0
      src/views/notification/template.vue
  66. 0 4
      src/views/order/list.vue
  67. 116 116
      src/views/product/addModal.vue
  68. 171 170
      src/views/product/category.vue
  69. 3 0
      src/views/product/drawer.data.ts
  70. 2 1
      src/views/product/goodsSpecs.vue
  71. 1 0
      src/views/product/list.data.ts
  72. 1 4
      src/views/product/list.vue
  73. 347 347
      src/views/product/productDrawer.vue
  74. 225 224
      src/views/product/ref.vue
  75. 1 1
      src/views/rightsEnterprises/BindModal.vue
  76. 3 1
      src/views/rightsEnterprises/addCameraModal.vue
  77. 1 4
      src/views/rightsEnterprises/camera.vue
  78. 1 1
      src/views/rightsEnterprises/cameraBind.vue
  79. 1 4
      src/views/rightsEnterprises/cameraList.vue
  80. 1 4
      src/views/rightsEnterprises/enterprises.vue
  81. 2 5
      src/views/rightsEnterprises/list.vue
  82. 1 0
      src/views/rightsEnterprises/rightsList.vue
  83. 1 4
      src/views/rightsEnterprises/staff.vue
  84. 161 161
      src/views/scenes/downloadModal.vue
  85. 142 142
      src/views/scenes/editorModal.vue
  86. 3 7
      src/views/scenes/list.vue
  87. 7 11
      src/views/scenes/live.vue
  88. 122 14
      src/views/scenes/liveDrawer.vue
  89. 6 10
      src/views/scenes/room.vue
  90. 58 28
      src/views/scenes/roomDeital.vue
  91. 165 165
      src/views/setting/data.ts
  92. 1 1
      src/views/staff/delListModal.vue
  93. 82 15
      src/views/staff/detailsModal.vue
  94. 66 12
      src/views/staff/list.vue
  95. 99 98
      src/views/staff/setpaswordModal.vue
  96. 18 16
      src/views/sys/login/LoginForm.vue
  97. 158 104
      src/views/sys/login/RegisterForm.vue
  98. 161 134
      src/views/sys/login/useLogin.ts
  99. 9 9
      tests/server/package.json
  100. 0 0
      tests/server/pnpm-lock.yaml

+ 37 - 0
.cz-config.js

@@ -0,0 +1,37 @@
+module.exports = {
+  types: [
+    { value: 'feat', name: 'feat 🍄:    新增新的特性' },
+    { value: 'fix', name: 'fix 🐛:    修复 BUG' },
+    { value: 'docs', name: 'docs 📄:    修改文档、注释' },
+    {
+      value: 'refactor',
+      name: 'refactor 🎸:    代码重构,注意和特性、修复区分开',
+    },
+    { value: 'chore', name: 'chore 🧹:   构建过程或辅助工具的变动' },
+    { value: 'perf', name: 'perf ⚡:    提升性能' },
+    { value: 'test', name: 'test 👀:    添加一个测试' },
+    { value: 'tool', name: 'tool 🚗:    开发工具变动(构建、脚手架工具等)' },
+    { value: 'style', name: 'style ✂:    对代码格式的修改不影响逻辑' },
+    { value: 'revert', name: 'revert 🌝:     版本回滚' },
+    { value: 'update', name: 'update ⬆:    第三方库升级 ' },
+  ],
+
+  scopes: [{ name: '组件' }, { name: '样式' }, { name: '文档更改' }, { name: '其它变更' }],
+  allowTicketNumber: false,
+  isTicketNumberRequired: false,
+  ticketNumberPrefix: 'TICKET-',
+  ticketNumberRegExp: 'd{1,5}',
+  messages: {
+    type: '选择一种你的提交类型:',
+    scope: '选择一个scope (可选):',
+    customScope: 'Denote the SCOPE of this change:',
+    subject: '简要说明:\n',
+    body: '详细说明,使用"|"换行(可选):\n',
+    breaking: '非兼容性说明 (可选):\n',
+    footer: '关联关闭的issue,例如:#31, #34(可选):\n',
+    confirmCommit: '确定提交?',
+  },
+  allowCustomScopes: true,
+  allowBreakingChanges: ['新增', '修复'],
+  subjectLimit: 100,
+};

+ 50 - 0
mock/notification/list.ts

@@ -0,0 +1,50 @@
+import { MockMethod } from 'vite-plugin-mock';
+import { mock, Random } from 'mockjs';
+import { resultPageSuccess } from '../_util';
+Random.extend({
+  phone: function () {
+    const phonePrefixs = ['132', '135', '189']; // 自己写前缀哈
+    return this.pick(phonePrefixs) + mock(/\d{8}/); //Number()
+  },
+});
+// console.log(Random.phone());
+// 生成 1 - 10 个 随机手机号码
+
+const demoList = (() => {
+  const result: any[] = [];
+  for (let index = 0; index < 200; index++) {
+    const { phone } = mock({
+      phone: '@phone',
+    });
+
+    result.push({
+      id: `${index}`,
+      name: '@cname',
+      nickName: '@cname',
+      avatarUrl: `@image('400x400', '@color', '微信头像')`,
+      'gender|1': [0, 1, 2],
+      city: '@city',
+      phone: phone,
+      province: '@province',
+      country: '@region',
+      language: '',
+      address: '@county(true)',
+      creatTime: '@datetime',
+      birthday: '@datetime',
+      lastLogin: '@datetime',
+    });
+  }
+  return result;
+})();
+
+export default [
+  {
+    url: '/zfb/notification/list',
+    timeout: 1000,
+    method: 'get',
+    response: ({ query }) => {
+      const { page = 1, pageSize = 20 } = query;
+      return resultPageSuccess(page, pageSize, demoList);
+    },
+  },
+] as MockMethod[];

+ 78 - 70
package.json

@@ -26,130 +26,138 @@
     "test:br": "npx http-server dist --cors --brotli -c-1",
     "reinstall": "rimraf pnpm-lock.yaml && rimraf package.lock.json && rimraf node_modules && npm run bootstrap",
     "prepare": "node -e \"if(require('fs').existsSync('.git')){process.exit(1)}\" || husky install",
-    "gen:icon": "esno ./build/generate/icon/index.ts"
+    "gen:icon": "esno ./build/generate/icon/index.ts",
+    "commit": "git cz"
   },
   "dependencies": {
     "@ant-design/colors": "^6.0.0",
-    "@ant-design/icons-vue": "^6.0.1",
-    "@iconify/iconify": "^2.1.0",
+    "@ant-design/icons-vue": "^6.1.0",
+    "@iconify/iconify": "^2.2.1",
     "@logicflow/core": "^0.7.16",
     "@logicflow/extension": "^0.7.16",
-    "@vue/runtime-core": "^3.2.26",
-    "@vue/shared": "^3.2.26",
-    "@vueuse/core": "^7.4.1",
-    "@vueuse/shared": "^7.4.1",
+    "@pansy/china-division": "^2.0.0",
+    "@vue/runtime-core": "^3.2.39",
+    "@vue/shared": "^3.2.39",
+    "@vueuse/core": "^7.7.1",
+    "@vueuse/shared": "^7.7.1",
     "@zxcvbn-ts/core": "^1.2.0",
-    "ant-design-vue": "3.0.0-beta.7",
+    "ant-design-vue": "^3.2.12",
     "axios": "^0.24.0",
-    "codemirror": "^5.65.0",
+    "codemirror": "^5.65.8",
     "cropperjs": "^1.5.12",
     "crypto-js": "^4.1.1",
-    "dayjs": "^1.10.7",
-    "echarts": "^5.2.2",
+    "dayjs": "^1.11.5",
+    "echarts": "^5.3.3",
     "intro.js": "^4.3.0",
     "js-base64": "^3.7.2",
     "lodash-es": "^4.17.21",
     "mockjs": "^1.1.0",
-    "moment": "^2.29.1",
+    "moment": "^2.29.4",
     "nprogress": "^0.2.0",
-    "path-to-regexp": "^6.2.0",
+    "path-to-regexp": "^6.2.1",
     "pinia": "2.0.9",
     "print-js": "^1.6.0",
-    "qrcode": "^1.5.0",
-    "qs": "^6.10.2",
+    "qrcode": "^1.5.1",
+    "qs": "^6.11.0",
     "resize-observer-polyfill": "^1.5.1",
     "showdown": "^1.9.1",
-    "sortablejs": "^1.14.0",
-    "tinymce": "^5.10.2",
-    "vditor": "^3.8.10",
-    "vue": "^3.2.26",
-    "vue-i18n": "^9.1.9",
-    "vue-json-pretty": "^1.8.2",
-    "vue-router": "^4.0.12",
-    "vue-types": "^4.1.1",
-    "xlsx": "^0.17.4"
+    "sortablejs": "^1.15.0",
+    "tinymce": "^5.10.5",
+    "vditor": "^3.8.17",
+    "vue": "^3.2.39",
+    "vue-i18n": "^9.2.2",
+    "vue-json-pretty": "^1.9.2",
+    "vue-router": "^4.1.5",
+    "vue-types": "^4.2.1",
+    "xlsx": "^0.17.5"
   },
   "devDependencies": {
-    "@commitlint/cli": "^16.0.1",
-    "@commitlint/config-conventional": "^16.0.0",
-    "@iconify/json": "^2.0.16",
+    "@commitlint/cli": "^16.3.0",
+    "@commitlint/config-conventional": "^16.2.4",
+    "@iconify/json": "^2.1.109",
     "@purge-icons/generated": "^0.7.0",
     "@types/codemirror": "^5.60.5",
-    "@types/crypto-js": "^4.0.2",
+    "@types/crypto-js": "^4.1.1",
     "@types/fs-extra": "^9.0.13",
-    "@types/inquirer": "^8.1.3",
+    "@types/inquirer": "^8.2.3",
     "@types/intro.js": "^3.0.2",
-    "@types/jest": "^27.0.3",
-    "@types/lodash-es": "^4.17.5",
-    "@types/mockjs": "^1.0.4",
-    "@types/node": "^17.0.5",
+    "@types/jest": "^27.5.2",
+    "@types/lodash-es": "^4.17.6",
+    "@types/mockjs": "^1.0.6",
+    "@types/node": "^17.0.45",
     "@types/nprogress": "^0.2.0",
-    "@types/qrcode": "^1.4.2",
+    "@types/qrcode": "^1.5.0",
     "@types/qs": "^6.9.7",
     "@types/showdown": "^1.9.4",
-    "@types/sortablejs": "^1.10.7",
-    "@typescript-eslint/eslint-plugin": "^5.8.1",
-    "@typescript-eslint/parser": "^5.8.1",
-    "@vitejs/plugin-legacy": "^1.6.4",
-    "@vitejs/plugin-vue": "^2.0.1",
-    "@vitejs/plugin-vue-jsx": "^1.3.3",
+    "@types/sortablejs": "^1.15.0",
+    "@typescript-eslint/eslint-plugin": "^5.37.0",
+    "@typescript-eslint/parser": "^5.37.0",
+    "@vitejs/plugin-legacy": "^1.8.2",
+    "@vitejs/plugin-vue": "^2.3.4",
+    "@vitejs/plugin-vue-jsx": "^1.3.10",
     "@vue/compiler-sfc": "3.2.26",
-    "@vue/test-utils": "^2.0.0-rc.18",
-    "autoprefixer": "^10.4.0",
-    "commitizen": "^4.2.4",
+    "@vue/test-utils": "^2.0.2",
+    "autoprefixer": "^10.4.11",
+    "commitizen": "^4.2.5",
     "conventional-changelog-cli": "^2.2.2",
     "cross-env": "^7.0.3",
+    "cz-customizable": "^7.0.0",
     "dotenv": "^10.0.0",
-    "eslint": "^8.5.0",
-    "eslint-config-prettier": "^8.3.0",
-    "eslint-define-config": "^1.2.1",
-    "eslint-plugin-jest": "^25.3.2",
-    "eslint-plugin-prettier": "^4.0.0",
-    "eslint-plugin-vue": "^8.2.0",
+    "eslint": "^8.23.1",
+    "eslint-config-prettier": "^8.5.0",
+    "eslint-define-config": "^1.7.0",
+    "eslint-plugin-jest": "^25.7.0",
+    "eslint-plugin-prettier": "^4.2.1",
+    "eslint-plugin-vue": "^8.7.1",
     "esno": "^0.13.0",
-    "fs-extra": "^10.0.0",
+    "fs-extra": "^10.1.0",
     "husky": "^7.0.4",
-    "inquirer": "^8.2.0",
-    "jest": "^27.4.5",
-    "less": "^4.1.2",
+    "inquirer": "^8.2.4",
+    "jest": "^27.5.1",
+    "less": "^4.1.3",
     "lint-staged": "12.1.4",
     "npm-run-all": "^4.1.5",
-    "postcss": "^8.4.5",
-    "postcss-html": "^1.3.0",
+    "postcss": "^8.4.16",
+    "postcss-html": "^1.5.0",
     "postcss-less": "^5.0.0",
-    "prettier": "^2.5.1",
+    "prettier": "^2.7.1",
     "rimraf": "^3.0.2",
-    "rollup-plugin-visualizer": "^5.5.2",
-    "stylelint": "^14.2.0",
-    "stylelint-config-html": "^1.0.0",
+    "rollup-plugin-visualizer": "^5.8.1",
+    "stylelint": "^14.12.0",
+    "stylelint-config-html": "^1.1.0",
     "stylelint-config-prettier": "^9.0.3",
     "stylelint-config-recommended": "^6.0.0",
     "stylelint-config-standard": "^24.0.0",
     "stylelint-order": "^5.0.0",
-    "ts-jest": "^27.1.2",
-    "ts-node": "^10.4.0",
-    "typescript": "^4.5.4",
-    "vite": "^2.8.5",
+    "ts-jest": "^27.1.5",
+    "ts-node": "^10.9.1",
+    "typescript": "^4.8.3",
+    "vite": "^2.9.15",
     "vite-plugin-compression": "^0.4.0",
     "vite-plugin-html": "^2.1.2",
-    "vite-plugin-imagemin": "^0.5.1",
+    "vite-plugin-imagemin": "^0.5.3",
     "vite-plugin-mock": "^2.9.6",
     "vite-plugin-purge-icons": "^0.7.0",
-    "vite-plugin-pwa": "^0.11.12",
+    "vite-plugin-pwa": "^0.11.13",
     "vite-plugin-rewrite-all": "^0.1.2",
     "vite-plugin-style-import": "^1.4.1",
-    "vite-plugin-svg-icons": "^1.0.5",
-    "vite-plugin-theme": "^0.8.1",
+    "vite-plugin-svg-icons": "^1.1.0",
+    "vite-plugin-theme": "^0.8.6",
     "vite-plugin-vue-setup-extend": "^0.3.0",
-    "vite-plugin-windicss": "^1.6.1",
-    "vue-eslint-parser": "^8.0.1",
-    "vue-tsc": "^0.30.1"
+    "vite-plugin-windicss": "^1.8.8",
+    "vue-eslint-parser": "^8.3.0",
+    "vue-tsc": "^0.30.6"
   },
   "resolutions": {
     "bin-wrapper": "npm:bin-wrapper-china",
     "rollup": "^2.56.3",
     "gifsicle": "5.2.0"
   },
+  "config": {
+    "commitizen": {
+      "path": "cz-customizable"
+    }
+  },
   "repository": {
     "type": "git",
     "url": "git+http://face3d.4dage.com:7005/zhangyupeng/zfb_mp.git "

File diff ditekan karena terlalu besar
+ 3461 - 3173
pnpm-lock.yaml


+ 3 - 0
readme.md

@@ -0,0 +1,3 @@
+# 2022-9-14 对接事项
+
+时间 统一为: YYYY-MM-DD HH:mm:ss, 不使用 UTF ease 标准时间。部分使用 timestamp

+ 12 - 7
src/api/corporation/modal.ts

@@ -74,14 +74,19 @@ export const selectUserList = (params: selectUserListParam) =>
   });
 
 export const checkUserAddAble = (params: checkUserParam) =>
-  defHttp.post<Result>({
-    url: Api.checkUserAddAble,
-    params,
-    headers: {
-      // @ts-ignore
-      ignoreCancelToken: true,
+  defHttp.post<Result>(
+    {
+      url: Api.checkUserAddAble,
+      params,
+      headers: {
+        // @ts-ignore
+        ignoreCancelToken: true,
+      },
     },
-  });
+    {
+      useResult: true,
+    },
+  );
 
 export const selectCompanyById = (params: selectCompanyParam) =>
   defHttp.post<Result>({

+ 2 - 2
src/api/corporation/model.ts

@@ -36,8 +36,8 @@ export interface auditParam {
 }
 
 export interface addPointParam {
-  id: number | null;
-  idpoint: number | null;
+  id: number | string;
+  point: number | string;
 }
 export interface selectUserListParam {
   id: number | null;

+ 89 - 0
src/api/dashboard/analysis.ts

@@ -0,0 +1,89 @@
+import { defHttp } from '/@/utils/http/axios';
+// PageParams
+import { BasicStaticsParams, UserStaticsModel, BulletChatStaticsModel } from './model';
+import { Result } from '/#/axios';
+
+enum Api {
+  bulletChat = '/zfb-api/zfb/platform/bulletChat/list',
+  userStatics = '/zfb-api/zfb/platform/dashboard/liveRoom/userStatics',
+  bulletChatStatics = '/zfb-api/zfb/platform/dashboard/liveRoom/bulletChatStatics',
+  liveRoomStatics = '/zfb-api/zfb/platform/dashboard/company/liveRoomStatics',
+  bulletChatExport = '/zfb-api/zfb/platform/bulletChat/export',
+  companyChatExport = '/zfb-api/zfb/platform/dashboard/company/export',
+}
+
+//留言列表
+
+export const bulletChatApi = (params: BasicStaticsParams) =>
+  defHttp.post<Result>({
+    url: Api.bulletChat,
+    params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+
+//企业看板
+
+export const liveRoomStaticsApi = (params: BasicStaticsParams) =>
+  defHttp.post<UserStaticsModel>({
+    url: Api.liveRoomStatics,
+    params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+//留言统计
+export const bulletChatStaticsApi = (params: BasicStaticsParams) =>
+  defHttp.post<BulletChatStaticsModel>({
+    url: Api.bulletChatStatics,
+    params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+
+export const userStaticsApi = (params: BasicStaticsParams) =>
+  defHttp.post<UserStaticsModel>({
+    url: Api.userStatics,
+    params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+
+export const bulletChatExportApi = (params: BasicStaticsParams) =>
+  defHttp.post<Blob>(
+    {
+      url: Api.bulletChatExport,
+      params,
+      responseType: 'blob',
+      headers: {
+        // @ts-ignore
+        ignoreCancelToken: true,
+      },
+    },
+    {
+      errorMessageMode: 'none',
+    },
+  );
+
+export const companyChatExportApi = (params: BasicStaticsParams) =>
+  defHttp.post<Blob>(
+    {
+      url: Api.companyChatExport,
+      params,
+      responseType: 'blob',
+      headers: {
+        // @ts-ignore
+        ignoreCancelToken: true,
+      },
+    },
+    {
+      errorMessageMode: 'none',
+    },
+  );

+ 23 - 0
src/api/dashboard/model.ts

@@ -0,0 +1,23 @@
+import { BasicPageParams } from '/@/api/model/baseModel';
+/**
+ * @description: Request list interface parameters
+ */
+export type PageParams = BasicPageParams;
+
+export interface BasicStaticsParams extends PageParams {
+  liveRoomId?: string;
+  time: string[];
+}
+export type StaticItemType = {
+  date: string;
+  amount: string | number;
+};
+export interface UserStaticsModel {
+  viewStatics: StaticItemType[];
+  shareStatics: StaticItemType[];
+}
+
+export interface BulletChatStaticsModel {
+  bulletChatAmounts: StaticItemType[];
+  userAmount: StaticItemType[];
+}

+ 12 - 0
src/api/member/list.ts

@@ -1,8 +1,10 @@
 import { defHttp } from '/@/utils/http/axios';
 import { PageParams, MemberListGetResultModel } from './model';
+import { Result } from '/#/axios';
 
 enum Api {
   pageList = '/zfb-api/zfb/user/list',
+  unbindWechat = '/zfb-api/zfb/user/unbindWechat',
 }
 
 /**
@@ -18,3 +20,13 @@ export const ListApi = (params: PageParams) =>
       ignoreCancelToken: true,
     },
   });
+
+export const unbindWechatApi = (id: number | string) =>
+  defHttp.post<Result>({
+    url: Api.unbindWechat,
+    params: { id },
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });

+ 19 - 19
src/api/member/model.ts

@@ -1,19 +1,19 @@
-import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel';
-/**
- * @description: Request list interface parameters
- */
-export type PageParams = BasicPageParams;
-
-export interface MemberListItem {
-  id: number;
-  name: string;
-  image: string;
-  link: string;
-  createTime: string;
-  isShow: boolean;
-}
-
-/**
- * @description: Request list return value
- */
-export type MemberListGetResultModel = BasicFetchResult<MemberListItem>;
+import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel';
+/**
+ * @description: Request list interface parameters
+ */
+export type PageParams = BasicPageParams;
+
+export interface MemberListItem {
+  id: number;
+  name: string;
+  image: string;
+  link: string;
+  createTime: string;
+  isShow: boolean;
+}
+
+/**
+ * @description: Request list return value
+ */
+export type MemberListGetResultModel = BasicFetchResult<MemberListItem>;

+ 29 - 28
src/api/model/baseModel.ts

@@ -1,28 +1,29 @@
-export interface BasicPageParams {
-  page: number;
-  pageSize: number;
-}
-
-export interface BasicFetchResult<T> {
-  items: T[];
-  total: number;
-  endRow: number;
-  firstPage: number;
-  hasNextPage: boolean;
-  hasPreviousPage: boolean;
-  isFirstPage: boolean;
-  isLastPage: boolean;
-  lastPage: number;
-  list: T[];
-  navigateFirstPage: number;
-  navigateLastPage: number;
-  navigatePages: number;
-  navigatepageNums: number[];
-  nextPage: number;
-  pageNum: number;
-  pageSize: number;
-  pages: number;
-  prePage: number;
-  size: number;
-  startRow: number;
-}
+export interface BasicPageParams {
+  page: number;
+  pageSize?: number;
+  limit?: number;
+}
+
+export interface BasicFetchResult<T> {
+  items: T[];
+  total: number;
+  endRow: number;
+  firstPage: number;
+  hasNextPage: boolean;
+  hasPreviousPage: boolean;
+  isFirstPage: boolean;
+  isLastPage: boolean;
+  lastPage: number;
+  list: T[];
+  navigateFirstPage: number;
+  navigateLastPage: number;
+  navigatePages: number;
+  navigatepageNums: number[];
+  nextPage: number;
+  pageNum: number;
+  pageSize: number;
+  pages: number;
+  prePage: number;
+  size: number;
+  startRow: number;
+}

+ 78 - 0
src/api/notification/list.ts

@@ -0,0 +1,78 @@
+import { defHttp } from '/@/utils/http/axios';
+import {
+  PageParams,
+  ListGetResultModel,
+  NoticeParams,
+  UnreadModel,
+  DelNoticeParams,
+} from './model';
+import { Result } from '/#/axios';
+
+enum Api {
+  pageList = '/zfb-api/zfb/platform/notice/list',
+  addNotice = '/zfb-api/zfb/platform/notice/save',
+  delNotice = '/zfb-api/zfb/platform/notice/delete',
+  unreadNoticesList = '/zfb-api/zfb/platform/notice/unreadNoticesList',
+  readAll = '/zfb-api/zfb/platform/notice/readAll',
+}
+
+/**
+ * @description: Get sample list value
+ */
+
+export const listApi = (params: PageParams) =>
+  defHttp.post<ListGetResultModel>({
+    url: Api.pageList,
+    params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+
+export const addNoticeApi = (params: NoticeParams) =>
+  defHttp.post<Result>({
+    url: Api.addNotice,
+    params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+
+export const editNoticeApi = (params: NoticeParams) =>
+  defHttp.post<Result>({
+    url: Api.addNotice,
+    params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+
+export const deleteNoticeApi = (params: DelNoticeParams) =>
+  defHttp.post<Result>({
+    url: Api.delNotice,
+    params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+
+export const unreadNoticeApi = () =>
+  defHttp.post<UnreadModel>({
+    url: Api.unreadNoticesList,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+export const readAllApi = () =>
+  defHttp.post<Result>({
+    url: Api.readAll,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });

+ 33 - 0
src/api/notification/model.ts

@@ -0,0 +1,33 @@
+import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel';
+/**
+ * @description: Request list interface parameters
+ */
+export type PageParams = BasicPageParams;
+
+export interface MemberListItem {
+  id: number;
+  content: string;
+  type: string;
+  effectiveTime: string[];
+  createTime: string;
+}
+
+export interface NoticeParams {
+  id?: string;
+  title: string;
+  content: string;
+  type: string;
+  effectiveTime: string[];
+}
+export interface DelNoticeParams {
+  id: string;
+}
+
+export interface UnreadModel {
+  list: any[];
+  totalCount: number;
+}
+/**
+ * @description: Request list return value
+ */
+export type ListGetResultModel = BasicFetchResult<MemberListItem>;

+ 3 - 2
src/api/scene/list.ts

@@ -1,6 +1,7 @@
 import { defHttp } from '/@/utils/http/axios';
 import {
-  PageParams,
+  // PageParams,
+  ListParams,
   RentListGetResultModel,
   SceneEditParam,
   SceneDownloadParam,
@@ -86,7 +87,7 @@ export const listRoomsApi = (params: UpdateParams) =>
       ignoreCancelToken: true,
     },
   });
-export const ListApi = (params: PageParams) =>
+export const ListApi = (params: ListParams) =>
   defHttp.post<RentListGetResultModel>({
     url: Api.pageList,
     params,

+ 3 - 2
src/api/scene/live.ts

@@ -1,6 +1,7 @@
 import { defHttp } from '/@/utils/http/axios';
 import {
   PageParams,
+  ListParams,
   InfoParams,
   RentListGetResultModel,
   SceneLiveItem,
@@ -31,7 +32,7 @@ export type SceneLiveItemResult = SceneLiveItem;
  * @description: Get sample list value
  */
 
-export const ListApi = (params: PageParams) =>
+export const ListApi = (params: ListParams) =>
   defHttp.get<RentListGetResultModel>({
     url: Api.pageList,
     params,
@@ -106,7 +107,7 @@ export function uploadLiveVideoApi(
   );
 }
 
-export const getAllSceneApi = (params: PageParams) =>
+export const getAllSceneApi = (params: ListParams) =>
   defHttp.post<RentListGetResultModel>({
     url: Api.getAllScene,
     params,

+ 101 - 95
src/api/scene/model.ts

@@ -1,95 +1,101 @@
-import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel';
-/**
- * @description: Request list interface parameters
- */
-export type PageParams = BasicPageParams;
-export interface bindAnchorListParam {
-  brandId?: number | string;
-  canShow?: number;
-  type?: number;
-  userId?: number | string;
-}
-export interface InfoParams {
-  id?: number | string;
-  token?: string;
-}
-export interface UpdateParams {
-  sceneNum?: string;
-  isShow?: string;
-  appListPicUrl?: string;
-}
-
-export interface SceneDownloadParam {
-  sceneNum?: string;
-}
-export interface SceneEditParam {
-  sceneNum: string;
-  userName: string;
-}
-
-export interface SceneLiveItem {
-  address: string;
-  adminId: number;
-  appListPicUrl: string;
-  bindShowerId: number;
-  bindShowerName: string;
-  bindShowerNameList: string;
-  city: string;
-  contractPhone: string;
-  createTime: number;
-  createUserDeptId: number;
-  createUserId: number;
-  deleted: number;
-  id: number;
-  introduceVideo: string;
-  introduceVideoCover: string;
-  latitude: number;
-  liveRoomUrl: string;
-  livestreamStatus: number;
-  longitude: number;
-  name: string;
-  picList: string;
-  sceneName: string;
-  sceneNum: string;
-  sceneUrl: string;
-  shareWxQrCode: string;
-  simpleDesc: string;
-  sortOrder: number;
-  token: string;
-  type: number;
-  updateTime: number;
-  updateUserId: number;
-}
-
-export interface SceneProccessItem {
-  precent?: number;
-  status?: number;
-  url?: string;
-}
-
-export interface sceneItem {
-  appListPicUrl: string;
-  createTime: number;
-  createUserId: number;
-  deleted: number;
-  id: number;
-  isShow: number;
-  name: string;
-  sceneNum: string;
-  sceneUrl: string;
-  token: string;
-  updateTime: number;
-  updateUserId: number;
-}
-
-export interface SceneLiveModel {
-  brand: SceneLiveItem;
-  code?: number;
-}
-/**
- * @description: Request list return value
- */
-export type RentListGetResultModel = BasicFetchResult<SceneLiveItem>;
-export type LiveListGetResultModel = BasicFetchResult<SceneLiveItem>;
-export type GetDownloadProcessModel = BasicFetchResult<SceneProccessItem>;
-export type GetAllSceneModel = BasicFetchResult<sceneItem>;
+import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel';
+/**
+ * @description: Request list interface parameters
+ */
+export type PageParams = BasicPageParams;
+
+export interface ListParams extends PageParams {
+  sceneName?: string;
+  sceneNum?: string;
+}
+export interface bindAnchorListParam {
+  brandId?: number | string;
+  canShow?: number;
+  type?: number;
+  userId?: number | string;
+}
+export interface InfoParams {
+  id?: number | string;
+  token?: string;
+}
+export interface UpdateParams {
+  sceneNum?: string;
+  isShow?: string;
+  appListPicUrl?: string;
+}
+
+export interface SceneDownloadParam {
+  sceneNum?: string;
+}
+export interface SceneEditParam {
+  sceneNum: string;
+  userName: string;
+}
+
+export interface SceneLiveItem {
+  address: string;
+  adminId: number;
+  appListPicUrl: string;
+  bindShowerId: number;
+  bindShowerName: string;
+  bindShowerNameList: string;
+  city: string;
+  contractPhone: string;
+  createTime: number;
+  createUserDeptId: number;
+  createUserId: number;
+  deleted: number;
+  id: number;
+  introduceVideo: string;
+  introduceVideoCover: string;
+  latitude: number;
+  liveRoomUrl: string;
+  livestreamStatus: number;
+  longitude: number;
+  name: string;
+  picList: string;
+  sceneName: string;
+  sceneNum: string;
+  sceneUrl: string;
+  shareWxQrCode: string;
+  simpleDesc: string;
+  sortOrder: number;
+  token: string;
+  type: number;
+  updateTime: number;
+  updateUserId: number;
+  contactId: number;
+}
+
+export interface SceneProccessItem {
+  precent?: number;
+  status?: number;
+  url?: string;
+}
+
+export interface sceneItem {
+  appListPicUrl: string;
+  createTime: number;
+  createUserId: number;
+  deleted: number;
+  id: number;
+  isShow: number;
+  name: string;
+  sceneNum: string;
+  sceneUrl: string;
+  token: string;
+  updateTime: number;
+  updateUserId: number;
+}
+
+export interface SceneLiveModel {
+  brand: SceneLiveItem;
+  code?: number;
+}
+/**
+ * @description: Request list return value
+ */
+export type RentListGetResultModel = BasicFetchResult<SceneLiveItem>;
+export type LiveListGetResultModel = BasicFetchResult<SceneLiveItem>;
+export type GetDownloadProcessModel = BasicFetchResult<SceneProccessItem>;
+export type GetAllSceneModel = BasicFetchResult<sceneItem>;

+ 32 - 2
src/api/staff/list.ts

@@ -1,6 +1,6 @@
 import { defHttp } from '/@/utils/http/axios';
-import { PageParams, ListGetResultModel, DelParams, roleParams } from './model';
-import { Result } from '/#/axios';
+import { PageParams, ListGetResultModel, DelParams, roleParams, ExportParam } from './model';
+import { Result, UploadFileParams } from '/#/axios';
 
 enum Api {
   pageList = '/zfb-api/zfb/shop/sys/user/staffList',
@@ -13,6 +13,8 @@ enum Api {
   deleteStaff = '/zfb-api/zfb/shop/sys/user/deleteStaff',
   getNumByStaff = '/zfb-api/zfb/shop/sys/user/getNumByStaff',
   clean = '/zfb-api/zfb/loginOutByUser',
+  upload = '/zfb-api/zfb/sys/oss/upload',
+  staffExport = '/zfb-api/zfb/shop/sys/user/staffExport',
 }
 
 /**
@@ -126,3 +128,31 @@ export const updateApi = (params) =>
       ignoreCancelToken: true,
     },
   });
+export function uploadApi(
+  params: UploadFileParams,
+  // onUploadProgress: (progressEvent: ProgressEvent) => void,
+) {
+  return defHttp.uploadFile<Result>(
+    {
+      url: Api.upload,
+      // onUploadProgress,
+    },
+    params,
+  );
+}
+
+export const exportApi = (params: ExportParam) =>
+  defHttp.post<Blob>(
+    {
+      url: Api.staffExport,
+      params,
+      responseType: 'blob',
+      headers: {
+        // @ts-ignore
+        ignoreCancelToken: true,
+      },
+    },
+    {
+      errorMessageMode: 'none',
+    },
+  );

+ 5 - 0
src/api/staff/model.ts

@@ -4,6 +4,11 @@ import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel';
  */
 export type PageParams = BasicPageParams;
 
+export interface ExportParam extends PageParams {
+  staffName?: string;
+  staffPhone: string;
+}
+
 export interface DelParams {
   userId: number;
   toUserId: number;

+ 83 - 72
src/api/sys/model/userModel.ts

@@ -1,72 +1,83 @@
-import type { UserInfo } from '/#/store';
-/**
- * @description: Login interface parameters
- */
-export interface LoginParams {
-  userName: string;
-  userPassword: string;
-  captcha: string;
-}
-
-export interface RoleInfo {
-  userID?: string;
-  roleName: string;
-  value: string;
-  brandList: string[] | any;
-  canShow: string;
-  createTime: number;
-  createUserId: number;
-  deptExpirationDate: number;
-  deptId: number;
-  deptManagerPhoneNum: string;
-  deptName: string;
-  email: string;
-  fdkkPassword: string;
-  fdkkUser: string;
-  isPlatformStreamer: false;
-  mobile: string;
-  parentDeptId: number;
-  parentDeptName: number;
-  password: string;
-  roleId: number;
-  roleIdList: any[];
-  roleList: number;
-  status: number;
-  userId: number;
-  username: string;
-}
-
-/**
- * @description: Login interface return value
- */
-export interface LoginResultModel {
-  id?: string | number;
-  token: string;
-  role?: RoleInfo;
-  user: RoleInfo;
-}
-export type GetUserInfoModel = UserInfo;
-
-export interface updateUserInfoPasswordParam {
-  id: string | number;
-  password?: string;
-  newPassword?: string;
-}
-/**
- * @description: Get user information return value
- */
-// export interface GetUserInfoModel {
-//   roles: RoleInfo[];
-//   // 用户id
-//   id: string | number;
-
-//   // userId: string | number;
-//   // 用户名
-//   userName: string;
-//   // 真实名字
-//   realName: string;
-//   // 头像
-//   avatar: string;
-//   // 介绍
-//   desc?: string;
-// }
+import type { UserInfo } from '/#/store';
+/**
+ * @description: Login interface parameters
+ */
+export interface LoginParams {
+  userName: string;
+  userPassword: string;
+  captcha: string;
+}
+
+export interface SendcodeParams {
+  phone: string;
+  areaNum?: string;
+}
+
+export interface RegisterParams {
+  userName: string;
+  userPassword: string;
+  area: string;
+  code: string;
+}
+export interface RoleInfo {
+  userID?: string;
+  roleName: string;
+  value: string;
+  brandList: string[] | any;
+  canShow: string;
+  createTime: number;
+  createUserId: number;
+  deptExpirationDate: number;
+  deptId: number;
+  deptManagerPhoneNum: string;
+  deptName: string;
+  email: string;
+  fdkkPassword: string;
+  fdkkUser: string;
+  isPlatformStreamer: false;
+  mobile: string;
+  parentDeptId: number;
+  parentDeptName: number;
+  password: string;
+  roleId: number;
+  roleIdList: any[];
+  roleList: number;
+  status: number;
+  userId: number;
+  username: string;
+}
+
+/**
+ * @description: Login interface return value
+ */
+export interface LoginResultModel {
+  id?: string | number;
+  token: string;
+  role?: RoleInfo;
+  user: RoleInfo;
+}
+export type GetUserInfoModel = UserInfo;
+
+export interface updateUserInfoPasswordParam {
+  id: string | number;
+  password?: string;
+  newPassword?: string;
+}
+/**
+ * @description: Get user information return value
+ */
+// export interface GetUserInfoModel {
+//   roles: RoleInfo[];
+//   // 用户id
+//   id: string | number;
+
+//   // userId: string | number;
+//   // 用户名
+//   userName: string;
+//   // 真实名字
+//   realName: string;
+//   // 头像
+//   avatar: string;
+//   // 介绍
+//   desc?: string;
+// }

+ 51 - 1
src/api/sys/user.ts

@@ -1,6 +1,8 @@
 import { defHttp } from '/@/utils/http/axios';
 import {
   LoginParams,
+  SendcodeParams,
+  RegisterParams,
   LoginResultModel,
   GetUserInfoModel,
   updateUserInfoPasswordParam,
@@ -8,12 +10,14 @@ import {
 import { encodeStr } from '/@/utils/encodeUtil';
 import { ErrorMessageMode } from '/#/axios';
 import { ContentTypeEnum } from '/@/enums/httpEnum';
-
+import type { Result } from '/#/axios';
 // import { encode } from 'js-base64';
 
 enum Api {
   // Login = '/zfb-api/zfb/sys/login',
   Login = '/zfb-api/zfb/loginBackground',
+  Sendcode = '/zfb-api/zfb/phone/getSMForApp',
+  Register = '/zfb-api/zfb/platform/user/register',
   Logout = '/logout',
   updatePassword = '/zfb-api/zfb/user/updatePassword',
   GetUserInfo = '/zfb-api/zfb/shop/sys/user/infoAnon',
@@ -48,6 +52,52 @@ export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal')
   );
 }
 
+export function registerApi(params: RegisterParams) {
+  const paramData: RegisterParams = {
+    area: '86',
+    code: params.code,
+    userName: params.userName,
+    userPassword: encodeStr(window.btoa(params.userPassword)),
+  };
+
+  return defHttp.post<Result>(
+    {
+      url: Api.Register,
+      // params,
+      params: paramData,
+      headers: { 'Content-Type': ContentTypeEnum.JSON },
+    },
+    {
+      errorMessageMode: 'none',
+      useResult: true,
+    },
+  );
+}
+
+export async function sendCodeApi(params: SendcodeParams) {
+  const paramData: SendcodeParams = {
+    areaNum: '86',
+    phone: params.phone,
+  };
+  const res = await defHttp.post<Result>(
+    {
+      url: Api.Sendcode,
+      // params,
+      params: paramData,
+      headers: { 'Content-Type': ContentTypeEnum.JSON },
+    },
+    {
+      useResult: true,
+    },
+  );
+
+  if (res.code === 200) {
+    return Promise.resolve(true);
+  } else {
+    return Promise.resolve(false);
+  }
+}
+
 /**
  * @description: getUserInfo
  */

+ 61 - 54
src/components/CountDown/src/CountdownInput.vue

@@ -1,54 +1,61 @@
-<template>
-  <a-input v-bind="$attrs" :class="prefixCls" :size="size" :value="state">
-    <template #addonAfter>
-      <CountButton :size="size" :count="count" :value="state" :beforeStartFunc="sendCodeApi" />
-    </template>
-    <template #[item]="data" v-for="item in Object.keys($slots).filter((k) => k !== 'addonAfter')">
-      <slot :name="item" v-bind="data || {}"></slot>
-    </template>
-  </a-input>
-</template>
-<script lang="ts">
-  import { defineComponent, PropType } from 'vue';
-  import CountButton from './CountButton.vue';
-  import { useDesign } from '/@/hooks/web/useDesign';
-  import { useRuleFormItem } from '/@/hooks/component/useFormItem';
-
-  const props = {
-    value: { type: String },
-    size: { type: String, validator: (v) => ['default', 'large', 'small'].includes(v) },
-    count: { type: Number, default: 60 },
-    sendCodeApi: {
-      type: Function as PropType<() => Promise<boolean>>,
-      default: null,
-    },
-  };
-
-  export default defineComponent({
-    name: 'CountDownInput',
-    components: { CountButton },
-    inheritAttrs: false,
-    props,
-    setup(props) {
-      const { prefixCls } = useDesign('countdown-input');
-      const [state] = useRuleFormItem(props);
-
-      return { prefixCls, state };
-    },
-  });
-</script>
-<style lang="less">
-  @prefix-cls: ~'@{namespace}-countdown-input';
-
-  .@{prefix-cls} {
-    .ant-input-group-addon {
-      padding-right: 0;
-      background-color: transparent;
-      border: none;
-
-      button {
-        font-size: 14px;
-      }
-    }
-  }
-</style>
+<template>
+  <a-input v-bind="$attrs" :class="prefixCls" :size="size" :value="state">
+    <template #addonAfter>
+      <CountButton
+        :disabled="buttonDisabled"
+        :size="size"
+        :count="count"
+        :value="state"
+        :beforeStartFunc="sendCodeApi"
+      />
+    </template>
+    <template #[item]="data" v-for="item in Object.keys($slots).filter((k) => k !== 'addonAfter')">
+      <slot :name="item" v-bind="data || {}"></slot>
+    </template>
+  </a-input>
+</template>
+<script lang="ts">
+  import { defineComponent, PropType } from 'vue';
+  import CountButton from './CountButton.vue';
+  import { useDesign } from '/@/hooks/web/useDesign';
+  import { useRuleFormItem } from '/@/hooks/component/useFormItem';
+
+  const props = {
+    value: { type: String },
+    buttonDisabled: { type: Boolean, default: false },
+    size: { type: String, validator: (v) => ['default', 'large', 'small'].includes(v) },
+    count: { type: Number, default: 60 },
+    sendCodeApi: {
+      type: Function as PropType<() => Promise<boolean>>,
+      default: null,
+    },
+  };
+
+  export default defineComponent({
+    name: 'CountDownInput',
+    components: { CountButton },
+    inheritAttrs: false,
+    props,
+    setup(props) {
+      const { prefixCls } = useDesign('countdown-input');
+      const [state] = useRuleFormItem(props);
+
+      return { prefixCls, state };
+    },
+  });
+</script>
+<style lang="less">
+  @prefix-cls: ~'@{namespace}-countdown-input';
+
+  .@{prefix-cls} {
+    .ant-input-group-addon {
+      padding-right: 0;
+      background-color: transparent;
+      border: none;
+
+      button {
+        font-size: 14px;
+      }
+    }
+  }
+</style>

+ 8 - 3
src/components/Cropper/src/CopperModal.vue

@@ -6,6 +6,7 @@
     width="800px"
     :canFullscreen="false"
     @ok="handleOk"
+    @cancel="handleCancel"
     :okText="t('component.cropper.okText')"
   >
     <div :class="prefixCls">
@@ -156,9 +157,7 @@
       const { t } = useI18n();
 
       watchEffect(() => {
-        if (props.src) {
-          src.value = props.src;
-        }
+        src.value = props.src;
       });
 
       // Block upload
@@ -226,6 +225,11 @@
           }
         }
       }
+      function handleCancel() {
+        // debugger;
+        src.value = '';
+        previewSource.value = '';
+      }
 
       return {
         t,
@@ -238,6 +242,7 @@
         handleReady,
         handlerToolbar,
         handleOk,
+        handleCancel,
       };
     },
   });

+ 2 - 0
src/components/Form/index.ts

@@ -9,7 +9,9 @@ export { useForm } from './src/hooks/useForm';
 export { default as ApiSelect } from './src/components/ApiSelect.vue';
 export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue';
 export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue';
+export { default as ApiTree } from './src/components/ApiTree.vue';
 export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
 export { default as ApiCascader } from './src/components/ApiCascader.vue';
+export { default as ApiTransfer } from './src/components/ApiTransfer.vue';
 
 export { BasicForm };

+ 11 - 10
src/components/Form/src/BasicForm.vue

@@ -62,12 +62,13 @@
 
   import { basicProps } from './props';
   import { useDesign } from '/@/hooks/web/useDesign';
+  import { cloneDeep } from 'lodash-es';
 
   export default defineComponent({
     name: 'BasicForm',
     components: { FormItem, Form, Row, FormAction },
     props: basicProps,
-    emits: ['advanced-change', 'reset', 'submit', 'register'],
+    emits: ['advanced-change', 'reset', 'submit', 'register', 'field-value-change'],
     setup(props, { emit, attrs }) {
       const formModel = reactive<Recordable>({});
       const modalFn = useModalContext();
@@ -132,9 +133,11 @@
           }
         }
         if (unref(getProps).showAdvancedButton) {
-          return schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[];
+          return cloneDeep(
+            schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[],
+          );
         } else {
-          return schemas as FormSchema[];
+          return cloneDeep(schemas as FormSchema[]);
         }
       });
 
@@ -240,13 +243,11 @@
 
       function setFormModel(key: string, value: any) {
         formModel[key] = value;
-        // const { validateTrigger } = unref(getBindValue);
-        // console.log('key', key, schemas[3]);
-
-        // console.log('validateTrigger', validateTrigger, formModel);
-        // if (!validateTrigger || validateTrigger === 'change') {
-        //   validateFields([key]).catch((_) => {});
-        // }
+        const { validateTrigger } = unref(getBindValue);
+        if (!validateTrigger || validateTrigger === 'change') {
+          validateFields([key]).catch((_) => {});
+        }
+        emit('field-value-change', key, value);
       }
 
       function handleEnterPress(e: KeyboardEvent) {

+ 4 - 0
src/components/Form/src/componentMap.ts

@@ -24,8 +24,10 @@ import {
 import ApiRadioGroup from './components/ApiRadioGroup.vue';
 import RadioButtonGroup from './components/RadioButtonGroup.vue';
 import ApiSelect from './components/ApiSelect.vue';
+import ApiTree from './components/ApiTree.vue';
 import ApiTreeSelect from './components/ApiTreeSelect.vue';
 import ApiCascader from './components/ApiCascader.vue';
+import ApiTransfer from './components/ApiTransfer.vue';
 import { BasicUpload } from '/@/components/Upload';
 import { StrengthMeter } from '/@/components/StrengthMeter';
 import { IconPicker } from '/@/components/Icon';
@@ -43,6 +45,7 @@ componentMap.set('AutoComplete', AutoComplete);
 
 componentMap.set('Select', Select);
 componentMap.set('ApiSelect', ApiSelect);
+componentMap.set('ApiTree', ApiTree);
 componentMap.set('TreeSelect', TreeSelect);
 componentMap.set('ApiTreeSelect', ApiTreeSelect);
 componentMap.set('ApiRadioGroup', ApiRadioGroup);
@@ -55,6 +58,7 @@ componentMap.set('ApiCascader', ApiCascader);
 componentMap.set('Cascader', Cascader);
 componentMap.set('Slider', Slider);
 componentMap.set('Rate', Rate);
+componentMap.set('ApiTransfer', ApiTransfer);
 
 componentMap.set('DatePicker', DatePicker);
 componentMap.set('MonthPicker', DatePicker.MonthPicker);

+ 28 - 3
src/components/Form/src/components/ApiCascader.vue

@@ -26,7 +26,7 @@
   import { get, omit } from 'lodash-es';
   import { useRuleFormItem } from '/@/hooks/component/useFormItem';
   import { LoadingOutlined } from '@ant-design/icons-vue';
-
+  import { useI18n } from '/@/hooks/web/useI18n';
   interface Option {
     value: string;
     label: string;
@@ -76,7 +76,7 @@
       const loading = ref<boolean>(false);
       const emitData = ref<any[]>([]);
       const isFirstLoad = ref(true);
-
+      const { t } = useI18n();
       // Embedded in the form, just use the hook binding to perform form verification
       const [state] = useRuleFormItem(props, 'value', 'change', emitData);
 
@@ -131,6 +131,28 @@
         }
       }
 
+      const deepSearch = (
+        data: Option[],
+        value: string,
+        key = 'title',
+        sub = 'children',
+        tempObj = {} as any,
+      ) => {
+        if (value && data) {
+          data.find((node) => {
+            if (node[key] == value) {
+              tempObj.found = node;
+              return node;
+            }
+            return deepSearch(node[sub], value, key, sub, tempObj);
+          });
+          if (tempObj.found) {
+            return tempObj.found;
+          }
+        }
+        return false;
+      };
+
       async function loadData(selectedOptions: Option[]) {
         const targetOption = selectedOptions[selectedOptions.length - 1];
         targetOption.loading = true;
@@ -143,7 +165,9 @@
           });
           if (Array.isArray(res)) {
             const children = generatorOptions(res);
-            targetOption.children = children;
+            const targetChildren = deepSearch(children, targetOption.value, 'value');
+            // console.log('targetChildren', targetChildren.children);
+            targetOption.children = targetChildren.children;
             return;
           }
           if (props.resultField) {
@@ -188,6 +212,7 @@
         state,
         options,
         loading,
+        t,
         handleChange,
         loadData,
         handleRenderDisplay,

+ 4 - 11
src/components/Form/src/components/ApiSelect.vue

@@ -2,9 +2,7 @@
   <Select
     @dropdown-visible-change="handleFetch"
     v-bind="$attrs"
-    show-search
     @change="handleChange"
-    :filter-option="filterOption"
     :options="getOptions"
     v-model:value="state"
   >
@@ -69,18 +67,13 @@
       const emitData = ref<any[]>([]);
       const attrs = useAttrs();
       const { t } = useI18n();
-      const filterOption = (input, option) => {
-        try {
-          return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
-        } catch (error) {
-          return false;
-        }
-      };
+
       // Embedded in the form, just use the hook binding to perform form verification
       const [state] = useRuleFormItem(props, 'value', 'change', emitData);
 
       const getOptions = computed(() => {
         const { labelField, valueField, numberToString } = props;
+
         return unref(options).reduce((prev, next: Recordable) => {
           if (next) {
             const value = next[valueField];
@@ -123,7 +116,7 @@
           }
           emitChange();
         } catch (error) {
-          console.warn('error', error);
+          console.warn(error);
         } finally {
           loading.value = false;
         }
@@ -148,7 +141,7 @@
         emitData.value = args;
       }
 
-      return { state, attrs, getOptions, loading, t, handleFetch, handleChange, filterOption };
+      return { state, attrs, getOptions, loading, t, handleFetch, handleChange };
     },
   });
 </script>

+ 135 - 0
src/components/Form/src/components/ApiTransfer.vue

@@ -0,0 +1,135 @@
+<template>
+  <Transfer
+    :data-source="getdataSource"
+    show-search
+    :filter-option="filterOption"
+    :render="(item) => item.title"
+    :showSelectAll="showSelectAll"
+    :selectedKeys="selectedKeys"
+    :targetKeys="getTargetKeys"
+    :showSearch="showSearch"
+    @change="handleChange"
+  />
+</template>
+
+<script lang="ts">
+  import { computed, defineComponent, watch, ref, unref, watchEffect } from 'vue';
+  import { Transfer } from 'ant-design-vue';
+  import { isFunction } from '/@/utils/is';
+  import { get, omit } from 'lodash-es';
+  import { propTypes } from '/@/utils/propTypes';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { TransferDirection, TransferItem } from 'ant-design-vue/lib/transfer';
+  export default defineComponent({
+    name: 'ApiTransfer',
+    components: { Transfer },
+    props: {
+      value: { type: Array<string> },
+      api: {
+        type: Function as PropType<(arg?: Recordable) => Promise<TransferItem[]>>,
+        default: null,
+      },
+      params: { type: Object },
+      dataSource: { type: Array<TransferItem> },
+      immediate: propTypes.bool.def(true),
+      alwaysLoad: propTypes.bool.def(false),
+      afterFetch: { type: Function as PropType<Fn> },
+      resultField: propTypes.string.def(''),
+      labelField: propTypes.string.def('title'),
+      valueField: propTypes.string.def('key'),
+      showSearch: { type: Boolean, default: false },
+      disabled: { type: Boolean, default: false },
+      filterOption: {
+        type: Function as PropType<(inputValue: string, item: TransferItem) => boolean>,
+      },
+      selectedKeys: { type: Array<string> },
+      showSelectAll: { type: Boolean, default: false },
+      targetKeys: { type: Array<string> },
+    },
+    emits: ['options-change', 'change'],
+    setup(props, { attrs, emit }) {
+      const _dataSource = ref<TransferItem[]>([]);
+      const _targetKeys = ref<string[]>([]);
+      const { t } = useI18n();
+
+      const getAttrs = computed(() => {
+        return {
+          ...(!props.api ? { dataSource: unref(_dataSource) } : {}),
+          ...attrs,
+        };
+      });
+      const getdataSource = computed(() => {
+        const { labelField, valueField } = props;
+
+        return unref(_dataSource).reduce((prev, next: Recordable) => {
+          if (next) {
+            prev.push({
+              ...omit(next, [labelField, valueField]),
+              title: next[labelField],
+              key: next[valueField],
+            });
+          }
+          return prev;
+        }, [] as TransferItem[]);
+      });
+      const getTargetKeys = computed<string[]>(() => {
+        if (unref(_targetKeys).length > 0) {
+          return unref(_targetKeys);
+        }
+        if (Array.isArray(props.value)) {
+          return props.value;
+        }
+        return [];
+      });
+
+      function handleChange(keys: string[], direction: TransferDirection, moveKeys: string[]) {
+        _targetKeys.value = keys;
+        console.log(direction);
+        console.log(moveKeys);
+        emit('change', keys);
+      }
+
+      watchEffect(() => {
+        props.immediate && !props.alwaysLoad && fetch();
+      });
+
+      watch(
+        () => props.params,
+        () => {
+          fetch();
+        },
+        { deep: true },
+      );
+
+      async function fetch() {
+        const api = props.api;
+        if (!api || !isFunction(api)) {
+          if (Array.isArray(props.dataSource)) {
+            _dataSource.value = props.dataSource;
+          }
+          return;
+        }
+        _dataSource.value = [];
+        try {
+          const res = await api(props.params);
+          if (Array.isArray(res)) {
+            _dataSource.value = res;
+            emitChange();
+            return;
+          }
+          if (props.resultField) {
+            _dataSource.value = get(res, props.resultField) || [];
+          }
+          emitChange();
+        } catch (error) {
+          console.warn(error);
+        } finally {
+        }
+      }
+      function emitChange() {
+        emit('options-change', unref(getdataSource));
+      }
+      return { getTargetKeys, getdataSource, t, getAttrs, handleChange };
+    },
+  });
+</script>

+ 90 - 0
src/components/Form/src/components/ApiTree.vue

@@ -0,0 +1,90 @@
+<template>
+  <a-tree v-bind="getAttrs" @change="handleChange">
+    <template #[item]="data" v-for="item in Object.keys($slots)">
+      <slot :name="item" v-bind="data || {}"></slot>
+    </template>
+    <template #suffixIcon v-if="loading">
+      <LoadingOutlined spin />
+    </template>
+  </a-tree>
+</template>
+
+<script lang="ts">
+  import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue';
+  import { Tree } from 'ant-design-vue';
+  import { isArray, isFunction } from '/@/utils/is';
+  import { get } from 'lodash-es';
+  import { propTypes } from '/@/utils/propTypes';
+  import { LoadingOutlined } from '@ant-design/icons-vue';
+  export default defineComponent({
+    name: 'ApiTree',
+    components: { ATree: Tree, LoadingOutlined },
+    props: {
+      api: { type: Function as PropType<(arg?: Recordable) => Promise<Recordable>> },
+      params: { type: Object },
+      immediate: { type: Boolean, default: true },
+      resultField: propTypes.string.def(''),
+      afterFetch: { type: Function as PropType<Fn> },
+    },
+    emits: ['options-change', 'change'],
+    setup(props, { attrs, emit }) {
+      const treeData = ref<Recordable[]>([]);
+      const isFirstLoaded = ref<Boolean>(false);
+      const loading = ref(false);
+      const getAttrs = computed(() => {
+        return {
+          ...(props.api ? { treeData: unref(treeData) } : {}),
+          ...attrs,
+        };
+      });
+
+      function handleChange(...args) {
+        emit('change', ...args);
+      }
+
+      watch(
+        () => props.params,
+        () => {
+          !unref(isFirstLoaded) && fetch();
+        },
+        { deep: true },
+      );
+
+      watch(
+        () => props.immediate,
+        (v) => {
+          v && !isFirstLoaded.value && fetch();
+        },
+      );
+
+      onMounted(() => {
+        props.immediate && fetch();
+      });
+
+      async function fetch() {
+        const { api, afterFetch } = props;
+        if (!api || !isFunction(api)) return;
+        loading.value = true;
+        treeData.value = [];
+        let result;
+        try {
+          result = await api(props.params);
+        } catch (e) {
+          console.error(e);
+        }
+        if (afterFetch && isFunction(afterFetch)) {
+          result = afterFetch(result);
+        }
+        loading.value = false;
+        if (!result) return;
+        if (!isArray(result)) {
+          result = get(result, props.resultField);
+        }
+        treeData.value = (result as Recordable[]) || [];
+        isFirstLoaded.value = true;
+        emit('options-change', treeData.value);
+      }
+      return { getAttrs, loading, handleChange };
+    },
+  });
+</script>

+ 19 - 7
src/components/Form/src/components/FormItem.vue

@@ -1,17 +1,16 @@
 <script lang="tsx">
   import type { PropType, Ref } from 'vue';
-  import type { FormActionType, FormProps } from '../types/form';
-  import type { FormSchema } from '../types/form';
+  import { computed, defineComponent, toRefs, unref } from 'vue';
+  import type { FormActionType, FormProps, FormSchema } from '../types/form';
   import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
   import type { TableActionType } from '/@/components/Table';
-  import { defineComponent, computed, unref, toRefs } from 'vue';
-  import { Form, Col, Divider } from 'ant-design-vue';
+  import { Col, Divider, Form } from 'ant-design-vue';
   import { componentMap } from '../componentMap';
   import { BasicHelp } from '/@/components/Basic';
   import { isBoolean, isFunction, isNull } from '/@/utils/is';
   import { getSlot } from '/@/utils/helper/tsxHelper';
   import { createPlaceholderMessage, setComponentRuleType } from '../helper';
-  import { upperFirst, cloneDeep } from 'lodash-es';
+  import { cloneDeep, upperFirst } from 'lodash-es';
   import { useItemLabelWidth } from '../hooks/useLabelWidth';
   import { useI18n } from '/@/hooks/web/useI18n';
 
@@ -178,8 +177,21 @@
 
         const getRequired = isFunction(required) ? required(unref(getValues)) : required;
 
-        if ((!rules || rules.length === 0) && getRequired) {
-          rules = [{ required: getRequired, validator }];
+        /*
+         * 1、若设置了required属性,又没有其他的rules,就创建一个验证规则;
+         * 2、若设置了required属性,又存在其他的rules,则只rules中不存在required属性时,才添加验证required的规则
+         *     也就是说rules中的required,优先级大于required
+         */
+        if (getRequired) {
+          if (!rules || rules.length === 0) {
+            rules = [{ required: getRequired, validator }];
+          } else {
+            const requiredIndex: number = rules.findIndex((rule) => Reflect.has(rule, 'required'));
+
+            if (requiredIndex === -1) {
+              rules.push({ required: getRequired, validator });
+            }
+          }
         }
 
         const requiredRuleIndex: number = rules.findIndex(

+ 6 - 1
src/components/Form/src/hooks/useAdvanced.ts

@@ -1,6 +1,6 @@
 import type { ColEx } from '../types';
 import type { AdvanceState } from '../types/hooks';
-import type { ComputedRef, Ref } from 'vue';
+import { ComputedRef, getCurrentInstance, Ref } from 'vue';
 import type { FormProps, FormSchema } from '../types/form';
 import { computed, unref, watch } from 'vue';
 import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is';
@@ -26,6 +26,8 @@ export default function ({
   formModel,
   defaultValueRef,
 }: UseAdvancedContext) {
+  const vm = getCurrentInstance();
+
   const { realWidthRef, screenEnum, screenRef } = useBreakpoint();
 
   const getEmptySpan = computed((): number => {
@@ -150,6 +152,9 @@ export default function ({
       }
     }
 
+    // 确保页面发送更新
+    vm?.proxy?.$forceUpdate();
+
     advanceState.actionSpan = (realItemColSum % BASIC_COL_LEN) + unref(getEmptySpan);
 
     getAdvanced(unref(getProps).actionColOptions || { span: BASIC_COL_LEN }, itemColSum, true);

+ 52 - 2
src/components/Form/src/hooks/useFormEvents.ts

@@ -2,7 +2,7 @@ import type { ComputedRef, Ref } from 'vue';
 import type { FormProps, FormSchema, FormActionType } from '../types/form';
 import type { NamePath } from 'ant-design-vue/lib/form/interface';
 import { unref, toRaw, nextTick } from 'vue';
-import { isArray, isFunction, isObject, isString } from '/@/utils/is';
+import { isArray, isFunction, isObject, isString, isDef, isNullOrUnDef } from '/@/utils/is';
 import { deepMerge } from '/@/utils';
 import { dateItemType, handleInputNumberValue, defaultValueComponents } from '../helper';
 import { dateUtil } from '/@/utils/dateUtil';
@@ -39,7 +39,8 @@ export function useFormEvents({
     Object.keys(formModel).forEach((key) => {
       const schema = unref(getSchema).find((item) => item.field === key);
       const isInput = schema?.component && defaultValueComponents.includes(schema.component);
-      formModel[key] = isInput ? defaultValueRef.value[key] || '' : defaultValueRef.value[key];
+      const defaultValue = cloneDeep(defaultValueRef.value[key]);
+      formModel[key] = isInput ? defaultValue || '' : defaultValue;
     });
     nextTick(() => clearValidate());
 
@@ -55,6 +56,10 @@ export function useFormEvents({
       .map((item) => item.field)
       .filter(Boolean);
 
+    // key 支持 a.b.c 的嵌套写法
+    const delimiter = '.';
+    const nestKeyArray = fields.filter((item) => item.indexOf(delimiter) >= 0);
+
     const validKeys: string[] = [];
     Object.keys(values).forEach((key) => {
       const schema = unref(getSchema).find((item) => item.field === key);
@@ -85,6 +90,21 @@ export function useFormEvents({
           formModel[key] = value;
         }
         validKeys.push(key);
+      } else {
+        nestKeyArray.forEach((nestKey: string) => {
+          try {
+            const value = eval('values' + delimiter + nestKey);
+            if (isDef(value)) {
+              formModel[nestKey] = value;
+              validKeys.push(nestKey);
+            }
+          } catch (e) {
+            // key not exist
+            if (isDef(defaultValueRef.value[nestKey])) {
+              formModel[nestKey] = cloneDeep(defaultValueRef.value[nestKey]);
+            }
+          }
+        });
       }
     });
     validateFields(validKeys).catch((_) => {});
@@ -132,11 +152,14 @@ export function useFormEvents({
     if (!prefixField || index === -1 || first) {
       first ? schemaList.unshift(schema) : schemaList.push(schema);
       schemaRef.value = schemaList;
+      _setDefaultValue(schema);
       return;
     }
     if (index !== -1) {
       schemaList.splice(index + 1, 0, schema);
     }
+    _setDefaultValue(schema);
+
     schemaRef.value = schemaList;
   }
 
@@ -192,9 +215,36 @@ export function useFormEvents({
         }
       });
     });
+    _setDefaultValue(schema);
+
     schemaRef.value = uniqBy(schema, 'field');
   }
 
+  function _setDefaultValue(data: FormSchema | FormSchema[]) {
+    let schemas: FormSchema[] = [];
+    if (isObject(data)) {
+      schemas.push(data as FormSchema);
+    }
+    if (isArray(data)) {
+      schemas = [...data];
+    }
+
+    const obj: Recordable = {};
+    const currentFieldsValue = getFieldsValue();
+    schemas.forEach((item) => {
+      if (
+        item.component != 'Divider' &&
+        Reflect.has(item, 'field') &&
+        item.field &&
+        !isNullOrUnDef(item.defaultValue) &&
+        !(item.field in currentFieldsValue)
+      ) {
+        obj[item.field] = item.defaultValue;
+      }
+    });
+    setFieldsValue(obj);
+  }
+
   function getFieldsValue(): Recordable {
     const formEl = unref(formElRef);
     if (!formEl) return {};

+ 47 - 4
src/components/Form/src/hooks/useFormValues.ts

@@ -3,7 +3,7 @@ import { dateUtil } from '/@/utils/dateUtil';
 import { unref } from 'vue';
 import type { Ref, ComputedRef } from 'vue';
 import type { FormProps, FormSchema } from '../types/form';
-import { set } from 'lodash-es';
+import { cloneDeep, set } from 'lodash-es';
 
 interface UseFormValuesContext {
   defaultValueRef: Ref<any>;
@@ -11,6 +11,43 @@ interface UseFormValuesContext {
   getProps: ComputedRef<FormProps>;
   formModel: Recordable;
 }
+
+/**
+ * @desription deconstruct array-link key. This method will mutate the target.
+ */
+function tryDeconstructArray(key: string, value: any, target: Recordable) {
+  const pattern = /^\[(.+)\]$/;
+  if (pattern.test(key)) {
+    const match = key.match(pattern);
+    if (match && match[1]) {
+      const keys = match[1].split(',');
+      value = Array.isArray(value) ? value : [value];
+      keys.forEach((k, index) => {
+        set(target, k.trim(), value[index]);
+      });
+      return true;
+    }
+  }
+}
+
+/**
+ * @desription deconstruct object-link key. This method will mutate the target.
+ */
+function tryDeconstructObject(key: string, value: any, target: Recordable) {
+  const pattern = /^\{(.+)\}$/;
+  if (pattern.test(key)) {
+    const match = key.match(pattern);
+    if (match && match[1]) {
+      const keys = match[1].split(',');
+      value = isObject(value) ? value : {};
+      keys.forEach((k) => {
+        set(target, k.trim(), value[k.trim()]);
+      });
+      return true;
+    }
+  }
+}
+
 export function useFormValues({
   defaultValueRef,
   getSchema,
@@ -41,7 +78,10 @@ export function useFormValues({
       if (isString(value)) {
         value = value.trim();
       }
-      set(res, key, value);
+      if (!tryDeconstructArray(key, value, res) && !tryDeconstructObject(key, value, res)) {
+        // 没有解构成功的,按原样赋值
+        set(res, key, value);
+      }
     }
     return handleRangeTimeValue(res);
   }
@@ -78,10 +118,13 @@ export function useFormValues({
       const { defaultValue } = item;
       if (!isNullOrUnDef(defaultValue)) {
         obj[item.field] = defaultValue;
-        formModel[item.field] = defaultValue;
+
+        if (formModel[item.field] === undefined) {
+          formModel[item.field] = defaultValue;
+        }
       }
     });
-    defaultValueRef.value = obj;
+    defaultValueRef.value = cloneDeep(obj);
   }
 
   return { handleFormValues, initDefault };

+ 2 - 1
src/components/Form/src/types/form.ts

@@ -10,7 +10,7 @@ import type { RowProps } from 'ant-design-vue/lib/grid/Row';
 export type FieldMapToTime = [string, [string, string], string?][];
 
 export type Rule = RuleObject & {
-  trigger?: 'blur' | 'change' | 'blur' | ['change', 'blur', 'blur'];
+  trigger?: 'blur' | 'change' | ['change', 'blur'];
 };
 
 export interface RenderCallbackParams {
@@ -49,6 +49,7 @@ export type RegisterFn = (formInstance: FormActionType) => void;
 export type UseFormReturnType = [RegisterFn, FormActionType];
 
 export interface FormProps {
+  name?: string;
   layout?: 'vertical' | 'inline' | 'horizontal';
   // Form value
   model?: Recordable;

+ 3 - 2
src/components/Form/src/types/index.ts

@@ -91,6 +91,7 @@ export type ComponentType =
   | 'Select'
   | 'ApiSelect'
   | 'TreeSelect'
+  | 'ApiTree'
   | 'ApiTreeSelect'
   | 'ApiRadioGroup'
   | 'RadioButtonGroup'
@@ -112,5 +113,5 @@ export type ComponentType =
   | 'Render'
   | 'Slider'
   | 'Rate'
-  | 'Time'
-  | 'Divider';
+  | 'Divider'
+  | 'ApiTransfer';

+ 1 - 1
src/components/Table/src/components/TableImg.vue

@@ -57,7 +57,7 @@
       const getWrapStyle = computed((): CSSProperties => {
         const { size } = props;
         const s = `${size}px`;
-        return { height: s, width: s };
+        return { height: s, width: s, overflow: 'hidden' };
       });
 
       const { prefixCls } = useDesign('basic-table-img');

+ 1 - 0
src/layouts/default/header/components/lock/LockModal.vue

@@ -53,6 +53,7 @@
           {
             field: 'password',
             label: t('layout.header.lockScreenPassword'),
+            labelWidth: 80,
             colProps: {
               span: 24,
             },

+ 60 - 20
src/layouts/default/header/components/notify/index.vue

@@ -1,10 +1,10 @@
 <template>
   <div :class="prefixCls">
     <Popover title="" trigger="click" :overlayClassName="`${prefixCls}__overlay`">
-      <Badge :count="count" dot :numberStyle="numberStyle">
+      <Badge size="small" dot :count="count" :numberStyle="numberStyle" @click="onNoticeClick">
         <BellOutlined />
       </Badge>
-      <template #content>
+      <!-- <template #content>
         <Tabs>
           <template v-for="item in listData" :key="item.key">
             <TabPane>
@@ -12,44 +12,83 @@
                 {{ item.name }}
                 <span v-if="item.list.length !== 0">({{ item.list.length }})</span>
               </template>
-              <!-- 绑定title-click事件的通知列表中标题是“可点击”的-->
               <NoticeList :list="item.list" v-if="item.key === '1'" @title-click="onNoticeClick" />
               <NoticeList :list="item.list" v-else />
             </TabPane>
           </template>
         </Tabs>
-      </template>
+      </template> -->
     </Popover>
   </div>
 </template>
 <script lang="ts">
-  import { computed, defineComponent, ref } from 'vue';
-  import { Popover, Tabs, Badge } from 'ant-design-vue';
+  import { onMounted, defineComponent, ref, h } from 'vue';
+  // Tabs, Badge
+  import { Popover, Badge } from 'ant-design-vue';
   import { BellOutlined } from '@ant-design/icons-vue';
-  import { tabListData, ListItem } from './data';
-  import NoticeList from './NoticeList.vue';
+  import { tabListData } from './data';
+  // import NoticeList from './NoticeList.vue';
   import { useDesign } from '/@/hooks/web/useDesign';
+  // import { useMessage } from '/@/hooks/web/useMessage';
+  import { unreadNoticeApi, readAllApi } from '/@/api/notification/list';
+  import { useGo } from '/@/hooks/web/usePage';
   import { useMessage } from '/@/hooks/web/useMessage';
-
+  import { TableImg } from '/@/components/Table';
+  // Tabs, TabPane: Tabs.TabPane, Badge, NoticeList
   export default defineComponent({
-    components: { Popover, BellOutlined, Tabs, TabPane: Tabs.TabPane, Badge, NoticeList },
+    components: { Popover, BellOutlined, Badge },
     setup() {
       const { prefixCls } = useDesign('header-notify');
-      const { createMessage } = useMessage();
+      // const { createMessage } = useMessage();
+
       const listData = ref(tabListData);
+      const go = useGo();
+      const { createWarningModal } = useMessage();
 
-      const count = computed(() => {
-        let count = 0;
-        for (let i = 0; i < tabListData.length; i++) {
-          count += tabListData[i].list.length;
+      const count = ref(0);
+      async function fetchNotice() {
+        const unread = await unreadNoticeApi();
+        console.log('unread', unread.totalCount);
+        count.value = unread.totalCount;
+        interface Unread {
+          title: string;
+          type: number;
+          content: string;
         }
-        return count;
+        if (unread.list?.length) {
+          const list = unread.list[0] as unknown as Unread;
+          if (list.type === 0) {
+            createWarningModal({
+              title: list.title,
+              content: list.content,
+              onOk: async () => {
+                await readAllApi();
+                await fetchNotice();
+              },
+            });
+          }
+          if (list.type === 1) {
+            createWarningModal({
+              title: list.title,
+              content: h(TableImg, {
+                size: 330,
+                imgList: [list.content],
+              }),
+              onOk: async () => {
+                await readAllApi();
+                await fetchNotice();
+              },
+            });
+          }
+        }
+      }
+      onMounted(async () => {
+        fetchNotice();
       });
 
-      function onNoticeClick(record: ListItem) {
-        createMessage.success('你点击了通知,ID=' + record.id);
-        // 可以直接将其标记为已读(为标题添加删除线),此处演示的代码会切换删除线状态
-        record.titleDelete = !record.titleDelete;
+      function onNoticeClick() {
+        go('/notification');
+        count.value = 0;
       }
 
       return {
@@ -81,6 +120,7 @@
 
       .ant-badge-multiple-words {
         padding: 0 4px;
+        transform: translate(50%, 50%);
       }
 
       svg {

+ 3 - 3
src/layouts/default/header/index.vue

@@ -39,7 +39,7 @@
 
       <!-- <ErrorAction v-if="getUseErrorHandle" :class="`${prefixCls}-action__item error-action`" /> -->
 
-      <!-- <Notify v-if="getShowNotice" :class="`${prefixCls}-action__item notify-item`" /> -->
+      <Notify v-if="getShowNotice" :class="`${prefixCls}-action__item notify-item`" />
 
       <FullScreen v-if="getShowFullScreen" :class="`${prefixCls}-action__item fullscreen-item`" />
 
@@ -78,7 +78,7 @@
   import { SettingButtonPositionEnum } from '/@/enums/appEnum';
   import { AppLocalePicker } from '/@/components/Application';
   // Notify ErrorAction
-  import { UserDropDown, LayoutBreadcrumb, FullScreen } from './components';
+  import { UserDropDown, LayoutBreadcrumb, FullScreen, Notify } from './components';
   import { useAppInject } from '/@/hooks/web/useAppInject';
   import { useDesign } from '/@/hooks/web/useDesign';
 
@@ -98,7 +98,7 @@
       AppLocalePicker,
       FullScreen,
       Tag,
-      // Notify,
+      Notify,
       // AppSearch,
       // ErrorAction,
       SettingDrawer: createAsyncComponent(() => import('/@/layouts/default/setting/index.vue'), {

+ 19 - 0
src/locales/lang/zh-CN/antdLocale/DatePicker.ts

@@ -0,0 +1,19 @@
+export default {
+  lang: {
+    shortWeekDays: ['一', '二', '三', '四', '五', '六', '日'],
+    shortMonths: [
+      '1月',
+      '2月',
+      '3月',
+      '4月',
+      '5月',
+      '6月',
+      '7月',
+      '8月',
+      '9月',
+      '10月',
+      '11月',
+      '12月',
+    ],
+  },
+};

+ 105 - 103
src/locales/lang/zh-CN/sys.ts

@@ -1,103 +1,105 @@
-export default {
-  api: {
-    operationFailed: '操作失败',
-    errorTip: '错误提示',
-    errorMessage: '操作失败,系统异常!',
-    timeoutMessage: '登录超时,请重新登录!',
-    apiTimeoutMessage: '接口请求超时,请刷新页面重试!',
-    apiRequestFailed: '请求出错,请稍候重试',
-    networkException: '网络异常',
-    networkExceptionMsg: '网络异常,请检查您的网络连接是否正常!',
-
-    errMsg401: '用户没有权限(令牌、用户名、密码错误)!',
-    errMsg403: '用户得到授权,但是访问是被禁止的。!',
-    errMsg404: '网络请求错误,未找到该资源!',
-    errMsg405: '网络请求错误,请求方法未允许!',
-    errMsg408: '网络请求超时!',
-    errMsg500: '服务器错误,请联系管理员!',
-    errMsg501: '网络未实现!',
-    errMsg502: '网络错误!',
-    errMsg503: '服务不可用,服务器暂时过载或维护!',
-    errMsg504: '网络超时!',
-    errMsg505: 'http版本不支持该请求!',
-  },
-  app: { logoutTip: '温馨提醒', logoutMessage: '是否确认退出系统?', menuLoading: '菜单加载中...' },
-  errorLog: {
-    tableTitle: '错误日志列表',
-    tableColumnType: '类型',
-    tableColumnDate: '时间',
-    tableColumnFile: '文件',
-    tableColumnMsg: '错误信息',
-    tableColumnStackMsg: 'stack信息',
-
-    tableActionDesc: '详情',
-
-    modalTitle: '错误详情',
-
-    fireVueError: '点击触发vue错误',
-    fireResourceError: '点击触发资源加载错误',
-    fireAjaxError: '点击触发ajax错误',
-
-    enableMessage: '只在`/src/settings/projectSetting.ts` 内的useErrorHandle=true时生效.',
-  },
-  exception: {
-    backLogin: '返回登录',
-    backHome: '返回首页',
-    subTitle403: '抱歉,您无权访问此页面。',
-    subTitle404: '抱歉,您访问的页面不存在。',
-    subTitle500: '抱歉,服务器报告错误。',
-    noDataTitle: '当前页无数据',
-    networkErrorTitle: '网络错误',
-    networkErrorSubTitle: '抱歉,您的网络连接已断开,请检查您的网络!',
-  },
-  lock: {
-    unlock: '点击解锁',
-    alert: '锁屏密码错误',
-    backToLogin: '返回登录',
-    entry: '进入系统',
-    placeholder: '请输入锁屏密码或者用户密码',
-  },
-  login: {
-    backSignIn: '返回',
-    signInFormTitle: '登录',
-    mobileSignInFormTitle: '手机登录',
-    qrSignInFormTitle: '二维码登录',
-    signUpFormTitle: '注册',
-    forgetFormTitle: '重置密码',
-
-    signInTitle: '10分钟搞定实景VR',
-    signInDesc: '自助720度实景VR发布管理,自助、简单、安全、高效',
-    policy: '我同意xxx隐私政策',
-    scanSign: `扫码后点击"确认",即可完成登录`,
-
-    loginButton: '登录',
-    registerButton: '注册',
-    rememberMe: '记住我',
-    forgetPassword: '忘记密码?',
-    otherSignIn: '其他登录方式',
-
-    // notify
-    loginSuccessTitle: '登录成功',
-    loginSuccessDesc: '欢迎回来',
-
-    // placeholder
-    accountPlaceholder: '请输入账号',
-    passwordPlaceholder: '请输入密码',
-    passwordFormat: '输入密码格式有误',
-    smsPlaceholder: '请输入验证码',
-    mobilePlaceholder: '请输入手机号码',
-    policyPlaceholder: '勾选后才能注册',
-    diffPwd: '两次输入密码不一致',
-
-    userName: '账号',
-    password: '密码',
-    confirmPassword: '确认密码',
-    email: '邮箱',
-    smsCode: '短信验证码',
-    mobile: '手机号码',
-
-    loginDragValidate: '登录需要拖拽验证',
-    loginAgain: '请再次操作',
-    captcha: '验证码',
-  },
-};
+export default {
+  api: {
+    operationFailed: '操作失败',
+    errorTip: '错误提示',
+    errorMessage: '操作失败,系统异常!',
+    timeoutMessage: '登录超时,请重新登录!',
+    apiTimeoutMessage: '接口请求超时,请刷新页面重试!',
+    apiRequestFailed: '请求出错,请稍候重试',
+    networkException: '网络异常',
+    networkExceptionMsg: '网络异常,请检查您的网络连接是否正常!',
+
+    errMsg401: '用户没有权限(令牌、用户名、密码错误)!',
+    errMsg403: '用户得到授权,但是访问是被禁止的。!',
+    errMsg404: '网络请求错误,未找到该资源!',
+    errMsg405: '网络请求错误,请求方法未允许!',
+    errMsg408: '网络请求超时!',
+    errMsg500: '服务器错误,请联系管理员!',
+    errMsg501: '网络未实现!',
+    errMsg502: '网络错误!',
+    errMsg503: '服务不可用,服务器暂时过载或维护!',
+    errMsg504: '网络超时!',
+    errMsg505: 'http版本不支持该请求!',
+  },
+  app: { logoutTip: '温馨提醒', logoutMessage: '是否确认退出系统?', menuLoading: '菜单加载中...' },
+  errorLog: {
+    tableTitle: '错误日志列表',
+    tableColumnType: '类型',
+    tableColumnDate: '时间',
+    tableColumnFile: '文件',
+    tableColumnMsg: '错误信息',
+    tableColumnStackMsg: 'stack信息',
+
+    tableActionDesc: '详情',
+
+    modalTitle: '错误详情',
+
+    fireVueError: '点击触发vue错误',
+    fireResourceError: '点击触发资源加载错误',
+    fireAjaxError: '点击触发ajax错误',
+
+    enableMessage: '只在`/src/settings/projectSetting.ts` 内的useErrorHandle=true时生效.',
+  },
+  exception: {
+    backLogin: '返回登录',
+    backHome: '返回首页',
+    subTitle403: '抱歉,您无权访问此页面。',
+    subTitle404: '抱歉,您访问的页面不存在。',
+    subTitle500: '抱歉,服务器报告错误。',
+    noDataTitle: '当前页无数据',
+    networkErrorTitle: '网络错误',
+    networkErrorSubTitle: '抱歉,您的网络连接已断开,请检查您的网络!',
+  },
+  lock: {
+    unlock: '点击解锁',
+    alert: '锁屏密码错误',
+    backToLogin: '返回登录',
+    entry: '进入系统',
+    placeholder: '请输入锁屏密码或者用户密码',
+  },
+  login: {
+    backSignIn: '返回',
+    signInFormTitle: '登录',
+    mobileSignInFormTitle: '手机登录',
+    qrSignInFormTitle: '二维码登录',
+    signUpFormTitle: '注册',
+    forgetFormTitle: '重置密码',
+
+    signInTitle: '10分钟搞定实景VR',
+    signInDesc: '自助720度实景VR发布管理,自助、简单、安全、高效',
+    policy: '我同意xxx隐私政策',
+    scanSign: `扫码后点击"确认",即可完成登录`,
+
+    loginButton: '登录',
+    registerButton: '注册',
+    rememberMe: '记住我',
+    forgetPassword: '忘记密码?',
+    otherSignIn: '其他登录方式',
+
+    // notify
+    loginSuccessTitle: '登录成功',
+    loginSuccessDesc: '欢迎回来',
+
+    // placeholder
+    accountPlaceholder: '请输入账号',
+    passwordPlaceholder: '请输入密码',
+    passwordFormat: '输入密码格式有误',
+    smsPlaceholder: '请输入验证码',
+    mobilePlaceholder: '请输入手机号码',
+    policyPlaceholder: '勾选后才能注册',
+    diffPwd: '两次输入密码不一致',
+
+    mobileFormat: '输入手机格式有误',
+
+    userName: '账号',
+    password: '密码',
+    confirmPassword: '确认密码',
+    email: '邮箱',
+    smsCode: '短信验证码',
+    mobile: '手机号码',
+
+    loginDragValidate: '登录需要拖拽验证',
+    loginAgain: '请再次操作',
+    captcha: '验证码',
+  },
+};

+ 11 - 10
src/locales/lang/zh_CN.ts

@@ -1,10 +1,11 @@
-import { genMessage } from '../helper';
-import antdLocale from 'ant-design-vue/es/locale/zh_CN';
-
-const modules = import.meta.globEager('./zh-CN/**/*.ts');
-export default {
-  message: {
-    ...genMessage(modules, 'zh-CN'),
-    antdLocale,
-  },
-};
+import { genMessage } from '../helper';
+import antdLocale from 'ant-design-vue/es/locale/zh_CN';
+
+const modules = import.meta.globEager('./zh-CN/**/*.ts');
+export default {
+  message: {
+    ...genMessage(modules, 'zh-CN'),
+    antdLocale,
+  },
+  // momentLocaleName: 'zh-cn',
+};

+ 69 - 69
src/locales/useLocale.ts

@@ -1,69 +1,69 @@
-/**
- * Multi-language related operations
- */
-import type { LocaleType } from '/#/config';
-
-import { i18n } from './setupI18n';
-import { useLocaleStoreWithOut } from '/@/store/modules/locale';
-import { unref, computed } from 'vue';
-import { loadLocalePool, setHtmlPageLang } from './helper';
-
-interface LangModule {
-  message: Recordable;
-  dateLocale: Recordable;
-  dateLocaleName: string;
-}
-
-function setI18nLanguage(locale: LocaleType) {
-  const localeStore = useLocaleStoreWithOut();
-
-  if (i18n.mode === 'legacy') {
-    i18n.global.locale = locale;
-  } else {
-    (i18n.global.locale as any).value = locale;
-  }
-  localeStore.setLocaleInfo({ locale });
-  setHtmlPageLang(locale);
-}
-
-export function useLocale() {
-  const localeStore = useLocaleStoreWithOut();
-  const getLocale = computed(() => localeStore.getLocale);
-  const getShowLocalePicker = computed(() => localeStore.getShowPicker);
-
-  const getAntdLocale = computed((): any => {
-    return i18n.global.getLocaleMessage(unref(getLocale))?.antdLocale ?? {};
-  });
-
-  // Switching the language will change the locale of useI18n
-  // And submit to configuration modification
-  async function changeLocale(locale: LocaleType) {
-    const globalI18n = i18n.global;
-    const currentLocale = unref(globalI18n.locale);
-    if (currentLocale === locale) {
-      return locale;
-    }
-
-    if (loadLocalePool.includes(locale)) {
-      setI18nLanguage(locale);
-      return locale;
-    }
-    const langModule = ((await import(`./lang/${locale}.ts`)) as any).default as LangModule;
-    if (!langModule) return;
-
-    const { message } = langModule;
-
-    globalI18n.setLocaleMessage(locale, message);
-    loadLocalePool.push(locale);
-
-    setI18nLanguage(locale);
-    return locale;
-  }
-
-  return {
-    getLocale,
-    getShowLocalePicker,
-    changeLocale,
-    getAntdLocale,
-  };
-}
+/**
+ * Multi-language related operations
+ */
+import type { LocaleType } from '/#/config';
+
+import { i18n } from './setupI18n';
+import { useLocaleStoreWithOut } from '/@/store/modules/locale';
+import { unref, computed } from 'vue';
+import { loadLocalePool, setHtmlPageLang } from './helper';
+
+interface LangModule {
+  message: Recordable;
+  dateLocale: Recordable;
+  dateLocaleName: string;
+}
+
+function setI18nLanguage(locale: LocaleType) {
+  const localeStore = useLocaleStoreWithOut();
+
+  if (i18n.mode === 'legacy') {
+    i18n.global.locale = locale;
+  } else {
+    (i18n.global.locale as any).value = locale;
+  }
+  localeStore.setLocaleInfo({ locale });
+  setHtmlPageLang(locale);
+}
+
+export function useLocale() {
+  const localeStore = useLocaleStoreWithOut();
+  const getLocale = computed(() => localeStore.getLocale);
+  const getShowLocalePicker = computed(() => localeStore.getShowPicker);
+
+  const getAntdLocale = computed((): any => {
+    return i18n.global.getLocaleMessage(unref(getLocale))?.antdLocale ?? {};
+  });
+
+  // Switching the language will change the locale of useI18n
+  // And submit to configuration modification
+  async function changeLocale(locale: LocaleType) {
+    const globalI18n = i18n.global;
+    const currentLocale = unref(globalI18n.locale);
+    if (currentLocale === locale) {
+      return locale;
+    }
+
+    if (loadLocalePool.includes(locale)) {
+      setI18nLanguage(locale);
+      return locale;
+    }
+    const langModule = ((await import(`./lang/${locale}.ts`)) as any).default as LangModule;
+    if (!langModule) return;
+
+    const { message } = langModule;
+
+    globalI18n.setLocaleMessage(locale, message);
+    loadLocalePool.push(locale);
+
+    setI18nLanguage(locale);
+    return locale;
+  }
+
+  return {
+    getLocale,
+    getShowLocalePicker,
+    changeLocale,
+    getAntdLocale,
+  };
+}

+ 3 - 2
src/settings/componentSetting.ts

@@ -17,6 +17,7 @@ export default {
       // Total number of tables returned by the interface field name
       totalField: 'totalCount',
     },
+
     // Number of pages that can be selected
     pageSizeOptions: ['10', '20', '50', '80', '100'],
     // Default display quantity on one page
@@ -29,9 +30,9 @@ export default {
       if (field && order) {
         return {
           // The sort field passed to the backend you
-          field,
+          sidx: field,
           // Sorting method passed to the background asc/desc
-          order,
+          order: order.replace('end', ''), //用于适配后端的排序字段
         };
       } else {
         return {};

+ 29 - 29
src/settings/localeSetting.ts

@@ -1,29 +1,29 @@
-import type { DropMenu } from '../components/Dropdown';
-import type { LocaleSetting, LocaleType } from '/#/config';
-
-export const LOCALE: { [key: string]: LocaleType } = {
-  ZH_CN: 'zh_CN',
-  EN_US: 'en',
-};
-
-export const localeSetting: LocaleSetting = {
-  showPicker: false,
-  // Locale
-  locale: LOCALE.ZH_CN,
-  // Default locale
-  fallback: LOCALE.ZH_CN,
-  // available Locales
-  availableLocales: [LOCALE.ZH_CN, LOCALE.EN_US],
-};
-
-// locale list
-export const localeList: DropMenu[] = [
-  {
-    text: '简体中文',
-    event: LOCALE.ZH_CN,
-  },
-  {
-    text: 'English',
-    event: LOCALE.EN_US,
-  },
-];
+import type { DropMenu } from '../components/Dropdown';
+import type { LocaleSetting, LocaleType } from '/#/config';
+
+export const LOCALE: { [key: string]: LocaleType } = {
+  ZH_CN: 'zh_CN',
+  EN_US: 'en',
+};
+
+export const localeSetting: LocaleSetting = {
+  showPicker: false,
+  // Locale
+  locale: LOCALE.ZH_CN,
+  // Default locale
+  fallback: LOCALE.ZH_CN,
+  // available Locales
+  availableLocales: [LOCALE.ZH_CN, LOCALE.EN_US],
+};
+
+// locale list
+export const localeList: DropMenu[] = [
+  {
+    text: '简体中文',
+    event: LOCALE.ZH_CN,
+  },
+  {
+    text: 'English',
+    event: LOCALE.EN_US,
+  },
+];

+ 25 - 23
src/utils/dateUtil.ts

@@ -1,23 +1,25 @@
-/**
- * Independent time operation tool to facilitate subsequent switch to dayjs
- */
-import dayjs from 'dayjs';
-
-const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
-const DATE_FORMAT = 'YYYY-MM-DD';
-
-export function formatToDateTime(
-  date: dayjs.Dayjs | undefined = undefined,
-  format = DATE_TIME_FORMAT,
-): string {
-  return dayjs(date).format(format);
-}
-
-export function formatToDate(
-  date: dayjs.Dayjs | undefined = undefined,
-  format = DATE_FORMAT,
-): string {
-  return dayjs(date).format(format);
-}
-
-export const dateUtil = dayjs;
+/**
+ * Independent time operation tool to facilitate subsequent switch to dayjs
+ */
+import dayjs from 'dayjs';
+import duration from 'dayjs/plugin/duration';
+dayjs.extend(duration);
+
+const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
+const DATE_FORMAT = 'YYYY-MM-DD';
+
+export function formatToDateTime(
+  date: dayjs.Dayjs | undefined = undefined,
+  format = DATE_TIME_FORMAT,
+): string {
+  return dayjs(date).format(format);
+}
+
+export function formatToDate(
+  date: dayjs.Dayjs | undefined = undefined,
+  format = DATE_FORMAT,
+): string {
+  return dayjs(date).format(format);
+}
+
+export const dateUtil = dayjs;

+ 287 - 275
src/utils/http/axios/index.ts

@@ -1,275 +1,287 @@
-// axios配置  可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动
-// The axios configuration can be changed according to the project, just change the file, other files can be left unchanged
-
-import type { AxiosResponse } from 'axios';
-import { clone } from 'lodash-es';
-import type { RequestOptions, Result } from '/#/axios';
-import type { AxiosTransform, CreateAxiosOptions } from './axiosTransform';
-import { VAxios } from './Axios';
-import { checkStatus } from './checkStatus';
-import { useGlobSetting } from '/@/hooks/setting';
-import { useMessage } from '/@/hooks/web/useMessage';
-import { RequestEnum, ResultEnum, ContentTypeEnum } from '/@/enums/httpEnum';
-import { isString } from '/@/utils/is';
-import { getToken } from '/@/utils/auth';
-import { setObjToUrlParams, deepMerge } from '/@/utils';
-import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog';
-import { useI18n } from '/@/hooks/web/useI18n';
-import { joinTimestamp, formatRequestDate } from './helper';
-import { useUserStoreWithOut } from '/@/store/modules/user';
-
-const globSetting = useGlobSetting();
-const urlPrefix = globSetting.urlPrefix;
-const { createMessage, createErrorModal } = useMessage();
-
-/**
- * @description: 数据处理,方便区分多种处理方式
- */
-const transform: AxiosTransform = {
-  /**
-   * @description: 处理请求数据。如果数据不是预期格式,可直接抛出错误
-   */
-  transformRequestHook: (res: AxiosResponse<Result>, options: RequestOptions) => {
-    const { t } = useI18n();
-    const { isTransformResponse, isReturnNativeResponse } = options;
-    // 是否返回原生响应头 比如:需要获取响应头时使用该属性
-    if (isReturnNativeResponse) {
-      return res;
-    }
-    // 不进行任何处理,直接返回
-    // 用于页面代码可能需要直接获取code,data,message这些信息时开启
-    if (!isTransformResponse) {
-      return res.data;
-    }
-    // 错误的时候返回
-
-    const { data } = res;
-    if (!data) {
-      // return '[HTTP] Request has no return value';
-      throw new Error(t('sys.api.apiRequestFailed'));
-    }
-    //  这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式
-
-    const { code, error, message } = data;
-    // TODO
-    // 这里逻辑可以根据项目进行修改
-    const hasSuccess =
-      data &&
-      Reflect.has(data, 'code') &&
-      (code === ResultEnum.SUCCESS || code === ResultEnum.NORMAL);
-    if (hasSuccess) {
-      const converterResult = data.message;
-      const converterMessage = error;
-      Reflect.set(data, 'result', converterResult);
-      Reflect.set(data, 'message', converterMessage);
-      // data.result = converterResult;
-      // data.message = converterMessage;
-      delete data.error;
-      return data.result || data;
-    }
-
-    // 在此处根据自己项目的实际情况对不同的code执行不同的操作
-    // 如果不希望中断当前请求,请return数据,否则直接抛出异常即可
-    let timeoutMsg = '';
-    switch (code) {
-      case ResultEnum.TIMEOUT:
-        timeoutMsg = t('sys.api.timeoutMessage');
-        const userStore = useUserStoreWithOut();
-        userStore.setToken(undefined);
-        userStore.logout(true);
-        break;
-      case ResultEnum.JAVA_ERROR:
-        if (error) {
-          timeoutMsg = error;
-        }
-        break;
-      default:
-        if (message) {
-          timeoutMsg = message;
-        }
-        //TODO 由于后端HACKCODE error当信息string
-        if (error) {
-          timeoutMsg = error;
-        }
-        break;
-    }
-
-    // errorMessageMode=‘modal’的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
-    // errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示
-    if (options.errorMessageMode === 'modal') {
-      createErrorModal({ title: t('sys.api.errorTip'), content: timeoutMsg });
-    } else if (options.errorMessageMode === 'message') {
-      createMessage.error(timeoutMsg);
-    }
-    console.log('timeoutMsg', timeoutMsg);
-    throw new Error(timeoutMsg || t('sys.api.apiRequestFailed'));
-  },
-
-  // 请求之前处理config
-  beforeRequestHook: (config, options) => {
-    const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options;
-
-    if (joinPrefix) {
-      config.url = `${urlPrefix}${config.url}`;
-    }
-
-    if (apiUrl && isString(apiUrl)) {
-      config.url = `${apiUrl}${config.url}`;
-    }
-    const params = config.params || {};
-    const data = config.data || false;
-    formatDate && data && !isString(data) && formatRequestDate(data);
-    if (config.method?.toUpperCase() === RequestEnum.GET) {
-      if (!isString(params)) {
-        // 给 get 请求加上时间戳参数,避免从缓存中拿数据。
-        config.params = Object.assign(params || {}, joinTimestamp(joinTime, false));
-      } else {
-        // 兼容restful风格
-        config.url = config.url + params + `${joinTimestamp(joinTime, true)}`;
-        config.params = undefined;
-      }
-    } else {
-      if (!isString(params)) {
-        formatDate && formatRequestDate(params);
-        if (Reflect.has(config, 'data') && config.data && Object.keys(config.data).length > 0) {
-          config.data = data;
-          config.params = params;
-        } else {
-          // 非GET请求如果没有提供data,则将params视为data
-          config.data = params;
-          config.params = undefined;
-        }
-        if (joinParamsToUrl) {
-          config.url = setObjToUrlParams(
-            config.url as string,
-            Object.assign({}, config.params, config.data),
-          );
-        }
-      } else {
-        // 兼容restful风格
-        config.url = config.url + params;
-        config.params = undefined;
-      }
-    }
-    return config;
-  },
-
-  /**
-   * @description: 请求拦截器处理
-   */
-  requestInterceptors: (config, options) => {
-    // 请求之前处理config
-    const token = getToken();
-    if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
-      // jwt token
-      // (config as Recordable).headers.Authorization = options.authenticationScheme
-      //   ? `${options.authenticationScheme} ${token}`
-      //   : token;
-      (config as Recordable).headers.token = options.authenticationScheme
-        ? `${options.authenticationScheme} ${token}`
-        : token;
-    }
-    return config;
-  },
-
-  /**
-   * @description: 响应拦截器处理
-   */
-  responseInterceptors: (res: AxiosResponse<any>) => {
-    return res;
-  },
-
-  /**
-   * @description: 响应错误处理
-   */
-  responseInterceptorsCatch: (error: any) => {
-    const { t } = useI18n();
-    const errorLogStore = useErrorLogStoreWithOut();
-    errorLogStore.addAjaxErrorInfo(error);
-    const { response, code, message, config } = error || {};
-    const errorMessageMode = config?.requestOptions?.errorMessageMode || 'none';
-    const msg: string = response?.data?.error?.message ?? '';
-    const err: string = error?.toString?.() ?? '';
-    let errMessage = '';
-
-    try {
-      if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {
-        errMessage = t('sys.api.apiTimeoutMessage');
-      }
-      if (err?.includes('Network Error')) {
-        errMessage = t('sys.api.networkExceptionMsg');
-      }
-
-      if (errMessage) {
-        if (errorMessageMode === 'modal') {
-          createErrorModal({ title: t('sys.api.errorTip'), content: errMessage });
-        } else if (errorMessageMode === 'message') {
-          createMessage.error(errMessage);
-        }
-        return Promise.reject(error);
-      }
-    } catch (error) {
-      throw new Error(error as unknown as string);
-    }
-
-    checkStatus(error?.response?.status, msg, errorMessageMode);
-    return Promise.reject(error);
-  },
-};
-
-function createAxios(opt?: Partial<CreateAxiosOptions>) {
-  return new VAxios(
-    deepMerge(
-      {
-        // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes
-        // authentication schemes,e.g: Bearer
-        // authenticationScheme: 'Bearer',
-        authenticationScheme: '',
-        timeout: 10 * 1000,
-        // 基础接口地址
-        // baseURL: globSetting.apiUrl,
-
-        headers: { 'Content-Type': ContentTypeEnum.JSON },
-        // 如果是form-data格式
-        // headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED },
-        // 数据处理方式
-        transform: clone(transform),
-        // 配置项,下面的选项都可以在独立的接口请求中覆盖
-        requestOptions: {
-          // 默认将prefix 添加到url
-          joinPrefix: true,
-          // 是否返回原生响应头 比如:需要获取响应头时使用该属性
-          isReturnNativeResponse: false,
-          // 需要对返回数据进行处理
-          isTransformResponse: true,
-          // post请求的时候添加参数到url
-          joinParamsToUrl: false,
-          // 格式化提交参数时间
-          formatDate: true,
-          // 消息提示类型
-          errorMessageMode: 'message',
-          // 接口地址
-          apiUrl: globSetting.apiUrl,
-          // 接口拼接地址
-          urlPrefix: urlPrefix,
-          //  是否加入时间戳
-          joinTime: true,
-          // 忽略重复请求
-          ignoreCancelToken: true,
-          // 是否携带token
-          withToken: true,
-        },
-      },
-      opt || {},
-    ),
-  );
-}
-export const defHttp = createAxios();
-
-// other api url
-// export const otherHttp = createAxios({
-//   requestOptions: {
-//     apiUrl: 'xxx',
-//     urlPrefix: 'xxx',
-//   },
-// });
+// axios配置  可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动
+// The axios configuration can be changed according to the project, just change the file, other files can be left unchanged
+
+import type { AxiosResponse } from 'axios';
+import { clone } from 'lodash-es';
+import type { RequestOptions, Result } from '/#/axios';
+import type { AxiosTransform, CreateAxiosOptions } from './axiosTransform';
+import { VAxios } from './Axios';
+import { checkStatus } from './checkStatus';
+import { useGlobSetting } from '/@/hooks/setting';
+import { useMessage } from '/@/hooks/web/useMessage';
+import { RequestEnum, ResultEnum, ContentTypeEnum } from '/@/enums/httpEnum';
+import { isString } from '/@/utils/is';
+import { getToken } from '/@/utils/auth';
+import { setObjToUrlParams, deepMerge } from '/@/utils';
+import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog';
+import { useI18n } from '/@/hooks/web/useI18n';
+import { joinTimestamp, formatRequestDate } from './helper';
+import { useUserStoreWithOut } from '/@/store/modules/user';
+
+const globSetting = useGlobSetting();
+const urlPrefix = globSetting.urlPrefix;
+const { createMessage, createErrorModal } = useMessage();
+
+/**
+ * @description: 数据处理,方便区分多种处理方式
+ */
+const transform: AxiosTransform = {
+  /**
+   * @description: 处理请求数据。如果数据不是预期格式,可直接抛出错误
+   */
+  transformRequestHook: (res: AxiosResponse<Result>, options: RequestOptions) => {
+    const { t } = useI18n();
+    const { isTransformResponse, isReturnNativeResponse } = options;
+    // 是否返回原生响应头 比如:需要获取响应头时使用该属性
+    if (isReturnNativeResponse) {
+      return res;
+    }
+    // 不进行任何处理,直接返回
+    // 用于页面代码可能需要直接获取code,data,message这些信息时开启
+    if (!isTransformResponse) {
+      return res.data;
+    }
+    // 错误的时候返回
+
+    const { data } = res;
+    if (!data) {
+      // return '[HTTP] Request has no return value';
+      throw new Error(t('sys.api.apiRequestFailed'));
+    }
+    //  这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式
+
+    const { code, error, message } = data;
+    // TODO
+    // 这里逻辑可以根据项目进行修改
+    const hasSuccess =
+      data &&
+      Reflect.has(data, 'code') &&
+      (code === ResultEnum.SUCCESS || code === ResultEnum.NORMAL);
+    if (hasSuccess) {
+      const converterResult = data.message;
+      const converterMessage = error;
+      Reflect.set(data, 'result', converterResult);
+      Reflect.set(data, 'message', converterMessage);
+      // data.result = converterResult;
+      // data.message = converterMessage;
+      delete data.error;
+      //增加自定义result return
+      if (options.useResult) {
+        return data;
+      } else {
+        return data.result;
+      }
+    }
+
+    // 在此处根据自己项目的实际情况对不同的code执行不同的操作
+    // 如果不希望中断当前请求,请return数据,否则直接抛出异常即可
+    let timeoutMsg = '';
+    switch (code) {
+      case ResultEnum.TIMEOUT:
+        timeoutMsg = t('sys.api.timeoutMessage');
+        const userStore = useUserStoreWithOut();
+        userStore.setToken(undefined);
+        userStore.logout(true);
+        break;
+      case ResultEnum.JAVA_ERROR:
+        if (error) {
+          timeoutMsg = error;
+        }
+        break;
+      default:
+        if (message) {
+          timeoutMsg = message;
+        }
+        //TODO 由于后端HACKCODE error当信息string
+        if (error) {
+          timeoutMsg = error;
+        }
+        break;
+    }
+
+    // errorMessageMode=‘modal’的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
+    // errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示
+    if (options.errorMessageMode === 'modal') {
+      createErrorModal({ title: t('sys.api.errorTip'), content: timeoutMsg });
+    } else if (options.errorMessageMode === 'message') {
+      createMessage.error(timeoutMsg);
+    }
+    console.log('timeoutMsg', timeoutMsg);
+    if (options.useResult) {
+      return data;
+    }
+    if (res.config.responseType === 'blob') {
+      return data;
+    }
+
+    throw new Error(timeoutMsg || t('sys.api.apiRequestFailed'));
+  },
+
+  // 请求之前处理config
+  beforeRequestHook: (config, options) => {
+    const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options;
+
+    if (joinPrefix) {
+      config.url = `${urlPrefix}${config.url}`;
+    }
+
+    if (apiUrl && isString(apiUrl)) {
+      config.url = `${apiUrl}${config.url}`;
+    }
+    const params = config.params || {};
+    const data = config.data || false;
+    formatDate && data && !isString(data) && formatRequestDate(data);
+    if (config.method?.toUpperCase() === RequestEnum.GET) {
+      if (!isString(params)) {
+        // 给 get 请求加上时间戳参数,避免从缓存中拿数据。
+        config.params = Object.assign(params || {}, joinTimestamp(joinTime, false));
+      } else {
+        // 兼容restful风格
+        config.url = config.url + params + `${joinTimestamp(joinTime, true)}`;
+        config.params = undefined;
+      }
+    } else {
+      if (!isString(params)) {
+        formatDate && formatRequestDate(params);
+        if (Reflect.has(config, 'data') && config.data && Object.keys(config.data).length > 0) {
+          config.data = data;
+          config.params = params;
+        } else {
+          // 非GET请求如果没有提供data,则将params视为data
+          config.data = params;
+          config.params = undefined;
+        }
+        if (joinParamsToUrl) {
+          config.url = setObjToUrlParams(
+            config.url as string,
+            Object.assign({}, config.params, config.data),
+          );
+        }
+      } else {
+        // 兼容restful风格
+        config.url = config.url + params;
+        config.params = undefined;
+      }
+    }
+    return config;
+  },
+
+  /**
+   * @description: 请求拦截器处理
+   */
+  requestInterceptors: (config, options) => {
+    // 请求之前处理config
+    const token = getToken();
+    if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
+      // jwt token
+      // (config as Recordable).headers.Authorization = options.authenticationScheme
+      //   ? `${options.authenticationScheme} ${token}`
+      //   : token;
+      (config as Recordable).headers.token = options.authenticationScheme
+        ? `${options.authenticationScheme} ${token}`
+        : token;
+    }
+    return config;
+  },
+
+  /**
+   * @description: 响应拦截器处理
+   */
+  responseInterceptors: (res: AxiosResponse<any>) => {
+    return res;
+  },
+
+  /**
+   * @description: 响应错误处理
+   */
+  responseInterceptorsCatch: (error: any) => {
+    const { t } = useI18n();
+    const errorLogStore = useErrorLogStoreWithOut();
+    errorLogStore.addAjaxErrorInfo(error);
+    const { response, code, message, config } = error || {};
+    const errorMessageMode = config?.requestOptions?.errorMessageMode || 'none';
+    const msg: string = response?.data?.error?.message ?? '';
+    const err: string = error?.toString?.() ?? '';
+    let errMessage = '';
+
+    try {
+      if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {
+        errMessage = t('sys.api.apiTimeoutMessage');
+      }
+      if (err?.includes('Network Error')) {
+        errMessage = t('sys.api.networkExceptionMsg');
+      }
+
+      if (errMessage) {
+        if (errorMessageMode === 'modal') {
+          createErrorModal({ title: t('sys.api.errorTip'), content: errMessage });
+        } else if (errorMessageMode === 'message') {
+          createMessage.error(errMessage);
+        }
+        return Promise.reject(error);
+      }
+    } catch (error) {
+      throw new Error(error as unknown as string);
+    }
+
+    checkStatus(error?.response?.status, msg, errorMessageMode);
+    return Promise.reject(error);
+  },
+};
+
+function createAxios(opt?: Partial<CreateAxiosOptions>) {
+  return new VAxios(
+    deepMerge(
+      {
+        // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes
+        // authentication schemes,e.g: Bearer
+        // authenticationScheme: 'Bearer',
+        authenticationScheme: '',
+        timeout: 10 * 1000,
+        // 基础接口地址
+        // baseURL: globSetting.apiUrl,
+
+        headers: { 'Content-Type': ContentTypeEnum.JSON },
+        // 如果是form-data格式
+        // headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED },
+        // 数据处理方式
+        transform: clone(transform),
+        // 配置项,下面的选项都可以在独立的接口请求中覆盖
+        requestOptions: {
+          // 默认将prefix 添加到url
+          joinPrefix: true,
+          // 是否返回原生响应头 比如:需要获取响应头时使用该属性
+          isReturnNativeResponse: false,
+          // 需要对返回数据进行处理
+          isTransformResponse: true,
+          // post请求的时候添加参数到url
+          joinParamsToUrl: false,
+          // 格式化提交参数时间
+          formatDate: true,
+          // 消息提示类型
+          errorMessageMode: 'message',
+          // 接口地址
+          apiUrl: globSetting.apiUrl,
+          // 接口拼接地址
+          urlPrefix: urlPrefix,
+          //  是否加入时间戳
+          joinTime: true,
+          // 忽略重复请求
+          ignoreCancelToken: true,
+          // 是否携带token
+          withToken: true,
+        },
+      },
+      opt || {},
+    ),
+  );
+}
+export const defHttp = createAxios();
+
+// other api url
+// export const otherHttp = createAxios({
+//   requestOptions: {
+//     apiUrl: 'xxx',
+//     urlPrefix: 'xxx',
+//   },
+// });

+ 131 - 130
src/views/advertisement/list.vue

@@ -1,130 +1,131 @@
-<template>
-  <div class="p-4">
-    <BasicTable @register="registerTable">
-      <template #toolbar>
-        <a-button type="primary" @click="handleCreate">新增</a-button>
-      </template>
-      <template #time="{ record }">
-        <Time v-if="record.createTime" :value="record.createTime" mode="date" />
-      </template>
-      <template #img="{ record }">
-        <TableImg :size="200" :simpleShow="true" :imgList="[record.image]" />
-      </template>
-      <template #action="{ record }">
-        <TableAction
-          :actions="[
-            {
-              label: '编辑',
-              onClick: handleEdit.bind(null, record),
-            },
-            {
-              color: 'error',
-              label: '删除',
-              popConfirm: {
-                title: '是否确认删除',
-                confirm: deleteConfirm.bind(null, record),
-              },
-            },
-          ]"
-        />
-      </template>
-    </BasicTable>
-    <listDrawer @register="registerDrawer" @reload="reload" />
-  </div>
-</template>
-<script lang="ts">
-  import { defineComponent } from 'vue';
-  import { BasicTable, useTable, BasicColumn, TableAction, TableImg } from '/@/components/Table';
-  import { useMessage } from '/@/hooks/web/useMessage';
-  import { useDrawer } from '/@/components/Drawer';
-  import { Time } from '/@/components/Time';
-  import listDrawer from './listDrawer.vue';
-  import { ListApi, itemDeletelApi } from '/@/api/advertisement/list';
-  // param type 2
-  export default defineComponent({
-    components: { BasicTable, TableAction, TableImg, listDrawer, Time },
-    setup() {
-      const { createMessage } = useMessage();
-      const [registerDrawer, { openDrawer }] = useDrawer();
-      const columns: BasicColumn[] = [
-        {
-          title: '序号',
-          dataIndex: 'id',
-          fixed: 'left',
-          width: 100,
-        },
-        {
-          title: '封面',
-          dataIndex: 'image',
-          ellipsis: true,
-          slots: { customRender: 'img' },
-          width: 230,
-        },
-        {
-          title: '标题',
-          dataIndex: 'title',
-          width: 230,
-        },
-        {
-          title: '排序',
-          dataIndex: 'orderNum',
-          width: 100,
-        },
-        {
-          title: '编辑时间',
-          dataIndex: 'createTime',
-          slots: { customRender: 'time' },
-          width: 150,
-        },
-
-        {
-          title: '操作',
-          dataIndex: '',
-          fixed: 'right',
-          slots: { customRender: 'action' },
-          width: 130,
-        },
-      ];
-
-      // { getForm }
-      const [registerTable, { reload }] = useTable({
-        title: '轮播图',
-        api: ListApi,
-        columns: columns,
-        useSearchForm: false,
-
-        showTableSetting: true,
-        tableSetting: { fullScreen: true },
-        showIndexColumn: false,
-        rowKey: 'id',
-      });
-
-      function handleCreate() {
-        openDrawer(true, {
-          isUpdate: false,
-        });
-      }
-      function handleEdit(record: Recordable) {
-        openDrawer(true, {
-          record,
-          isUpdate: true,
-        });
-      }
-      function deleteConfirm(val) {
-        itemDeletelApi({ id: val.id }).then((res) => {
-          createMessage.success(res);
-          reload();
-        });
-      }
-      return {
-        deleteConfirm,
-        reload,
-        registerTable,
-        createMessage,
-        registerDrawer,
-        handleCreate,
-        handleEdit,
-      };
-    },
-  });
-</script>
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable">
+      <template #toolbar>
+        <a-button type="primary" @click="handleCreate">新增</a-button>
+      </template>
+      <template #time="{ record }">
+        <Time v-if="record.createTime" :value="record.createTime" mode="date" />
+      </template>
+      <template #img="{ record }">
+        <TableImg :size="200" :simpleShow="true" :imgList="[record.image]" />
+      </template>
+      <template #action="{ record }">
+        <TableAction
+          :actions="[
+            {
+              label: '编辑',
+              onClick: handleEdit.bind(null, record),
+            },
+            {
+              color: 'error',
+              label: '删除',
+              popConfirm: {
+                title: '是否确认删除',
+                confirm: deleteConfirm.bind(null, record),
+              },
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+    <listDrawer @register="registerDrawer" @reload="reload" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable, BasicColumn, TableAction, TableImg } from '/@/components/Table';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useDrawer } from '/@/components/Drawer';
+  import { Time } from '/@/components/Time';
+  import listDrawer from './listDrawer.vue';
+  import { ListApi, itemDeletelApi } from '/@/api/advertisement/list';
+  // param type 2
+  export default defineComponent({
+    components: { BasicTable, TableAction, TableImg, listDrawer, Time },
+    emits: ['register', 'reload'],
+    setup() {
+      const { createMessage } = useMessage();
+      const [registerDrawer, { openDrawer }] = useDrawer();
+      const columns: BasicColumn[] = [
+        {
+          title: '序号',
+          dataIndex: 'id',
+          fixed: 'left',
+          width: 100,
+        },
+        {
+          title: '封面',
+          dataIndex: 'image',
+          ellipsis: true,
+          slots: { customRender: 'img' },
+          width: 230,
+        },
+        {
+          title: '标题',
+          dataIndex: 'title',
+          width: 230,
+        },
+        {
+          title: '排序',
+          dataIndex: 'orderNum',
+          width: 100,
+        },
+        {
+          title: '编辑时间',
+          dataIndex: 'createTime',
+          slots: { customRender: 'time' },
+          width: 150,
+        },
+
+        {
+          title: '操作',
+          dataIndex: '',
+          fixed: 'right',
+          slots: { customRender: 'action' },
+          width: 130,
+        },
+      ];
+
+      // { getForm }
+      const [registerTable, { reload }] = useTable({
+        title: '轮播图',
+        api: ListApi,
+        columns: columns,
+        useSearchForm: false,
+
+        showTableSetting: true,
+        tableSetting: { fullScreen: true },
+        showIndexColumn: false,
+        rowKey: 'id',
+      });
+
+      function handleCreate() {
+        openDrawer(true, {
+          isUpdate: false,
+        });
+      }
+      function handleEdit(record: Recordable) {
+        openDrawer(true, {
+          record,
+          isUpdate: true,
+        });
+      }
+      function deleteConfirm(val) {
+        itemDeletelApi({ id: val.id }).then((res) => {
+          createMessage.success(res);
+          reload();
+        });
+      }
+      return {
+        deleteConfirm,
+        reload,
+        registerTable,
+        createMessage,
+        registerDrawer,
+        handleCreate,
+        handleEdit,
+      };
+    },
+  });
+</script>

+ 98 - 97
src/views/advertisement/listDrawer.vue

@@ -1,97 +1,98 @@
-<template>
-  <BasicDrawer
-    v-bind="$attrs"
-    width="50%"
-    :showDetailBack="false"
-    :title="getTitle"
-    @register="registerDrawer"
-  >
-    <BasicForm @register="registerForm" />
-  </BasicDrawer>
-</template>
-<script lang="ts">
-  import { defineComponent, ref, unref, computed } from 'vue';
-  import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
-  import { BasicForm, useForm } from '/@/components/Form';
-  import { useMessage } from '/@/hooks/web/useMessage';
-  import { schemas } from './schemas';
-  import { itemUpdateApi, itemSaveApi } from '/@/api/advertisement/list';
-  export default defineComponent({
-    components: { BasicDrawer, BasicForm },
-    setup(_, { emit }) {
-      const { createMessage } = useMessage();
-      const modelRef = ref({});
-      const isUpdate = ref(true);
-      const [registerForm, { validate, setProps, resetFields, setFieldsValue }] = useForm({
-        labelCol: {
-          span: 4,
-        },
-        wrapperCol: {
-          span: 18,
-        },
-        schemas: schemas,
-        actionColOptions: {
-          offset: 8,
-          span: 8,
-        },
-        submitButtonOptions: {
-          text: '提交',
-        },
-        submitFunc: summitAddDrawer,
-      });
-      const getTitle = computed(() => (!unref(isUpdate) ? '新增轮播资讯' : '编辑轮播资讯'));
-      const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
-        resetFields();
-        setDrawerProps({ confirmLoading: false });
-        isUpdate.value = !!data?.isUpdate;
-        modelRef.value = data.record;
-        if (unref(isUpdate)) {
-          console.log('data.record', data.record);
-          setFieldsValue({
-            ...data.record,
-            image: [data.record.image],
-          });
-        }
-      });
-
-      async function summitAddDrawer() {
-        try {
-          let data = await validate();
-          let httpApi = unref(isUpdate) ? itemUpdateApi : itemSaveApi;
-          setProps({
-            submitButtonOptions: {
-              loading: true,
-            },
-          });
-          console.log('data', data, modelRef.value);
-          httpApi({
-            ...modelRef.value,
-            ...data,
-            image: data.image[0],
-          }).then((res) => {
-            console.log('itemUpdateApi', res);
-            setProps({
-              submitButtonOptions: {
-                loading: false,
-              },
-            });
-            createMessage.success('提交成功!');
-            emit('reload');
-            closeDrawer();
-          });
-
-          // setTimeout(() => {
-
-          // }, 2000);
-        } catch (error) {}
-      }
-
-      return {
-        registerForm,
-        getTitle,
-        registerDrawer,
-        closeDrawer,
-      };
-    },
-  });
-</script>
+<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    width="50%"
+    :showDetailBack="false"
+    :title="getTitle"
+    @register="registerDrawer"
+  >
+    <BasicForm @register="registerForm" />
+  </BasicDrawer>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, unref, computed } from 'vue';
+  import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
+  import { BasicForm, useForm } from '/@/components/Form';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { schemas } from './schemas';
+  import { itemUpdateApi, itemSaveApi } from '/@/api/advertisement/list';
+  export default defineComponent({
+    components: { BasicDrawer, BasicForm },
+    emits: ['register', 'reload'],
+    setup(_, { emit }) {
+      const { createMessage } = useMessage();
+      const modelRef = ref({});
+      const isUpdate = ref(true);
+      const [registerForm, { validate, setProps, resetFields, setFieldsValue }] = useForm({
+        labelCol: {
+          span: 4,
+        },
+        wrapperCol: {
+          span: 18,
+        },
+        schemas: schemas,
+        actionColOptions: {
+          offset: 8,
+          span: 8,
+        },
+        submitButtonOptions: {
+          text: '提交',
+        },
+        submitFunc: summitAddDrawer,
+      });
+      const getTitle = computed(() => (!unref(isUpdate) ? '新增轮播资讯' : '编辑轮播资讯'));
+      const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
+        resetFields();
+        setDrawerProps({ confirmLoading: false });
+        isUpdate.value = !!data?.isUpdate;
+        modelRef.value = data.record;
+        if (unref(isUpdate)) {
+          console.log('data.record', data.record);
+          setFieldsValue({
+            ...data.record,
+            image: [data.record.image],
+          });
+        }
+      });
+
+      async function summitAddDrawer() {
+        try {
+          let data = await validate();
+          let httpApi = unref(isUpdate) ? itemUpdateApi : itemSaveApi;
+          setProps({
+            submitButtonOptions: {
+              loading: true,
+            },
+          });
+          console.log('data', data, modelRef.value);
+          httpApi({
+            ...modelRef.value,
+            ...data,
+            image: data.image[0],
+          }).then((res) => {
+            console.log('itemUpdateApi', res);
+            setProps({
+              submitButtonOptions: {
+                loading: false,
+              },
+            });
+            createMessage.success('提交成功!');
+            emit('reload');
+            closeDrawer();
+          });
+
+          // setTimeout(() => {
+
+          // }, 2000);
+        } catch (error) {}
+      }
+
+      return {
+        registerForm,
+        getTitle,
+        registerDrawer,
+        closeDrawer,
+      };
+    },
+  });
+</script>

+ 147 - 146
src/views/advertisement/pads.vue

@@ -1,146 +1,147 @@
-<template>
-  <div class="p-4">
-    <BasicTable @register="registerTable">
-      <template #toolbar>
-        <a-button type="primary" @click="handleCreate">新增</a-button>
-      </template>
-      <template #img="{ record }">
-        <TableImg :size="90" :simpleShow="true" :imgList="[record.image]" />
-      </template>
-      <template #action="{ record }">
-        <TableAction
-          :actions="[
-            {
-              label: '编辑',
-              onClick: handleEdit.bind(null, record),
-            },
-            {
-              color: 'error',
-              label: '删除',
-              popConfirm: {
-                title: '是否确认删除',
-                confirm: handleDelete.bind(null, record),
-              },
-            },
-          ]"
-        />
-      </template>
-    </BasicTable>
-    <PadsDrawer :type="type" @register="registerDrawer" @reload="reload" />
-  </div>
-</template>
-<script lang="ts">
-  import { defineComponent, h } from 'vue';
-  import { BasicTable, useTable, BasicColumn, TableAction, TableImg } from '/@/components/Table';
-  import { useMessage } from '/@/hooks/web/useMessage';
-  // import { Tag } from 'ant-design-vue';
-  import PadsDrawer from './padsDrawer.vue';
-  import { RecommendListApi, RecommendDeleteApi } from '/@/api/advertisement/list';
-  import { useDrawer } from '/@/components/Drawer';
-  import { Time } from '/@/components/Time';
-  import { useRoute } from 'vue-router';
-  import { useI18n } from '/@/hooks/web/useI18n';
-  // param type 2
-  export default defineComponent({
-    components: { BasicTable, TableAction, TableImg, PadsDrawer },
-    setup() {
-      const { t } = useI18n();
-      const { createMessage } = useMessage();
-      const [registerDrawer, { openDrawer }] = useDrawer();
-      const route = useRoute();
-      console.log('searchForm', route, route.fullPath);
-      const type = route.fullPath.split('/').pop(); //charAt(route.fullPath.length - 2) || '1';
-      const columns: BasicColumn[] = [
-        {
-          title: '排序',
-          dataIndex: 'orderNum',
-          fixed: 'left',
-          width: 100,
-        },
-        {
-          title: '封面',
-          dataIndex: 'image',
-          ellipsis: true,
-          slots: { customRender: 'img' },
-          width: 230,
-        },
-        {
-          title: '标题',
-          dataIndex: 'title',
-          width: 230,
-        },
-        {
-          title: '直播间链接',
-          dataIndex: 'sceneUrl',
-          width: 300,
-        },
-        {
-          title: '编辑时间',
-          dataIndex: 'createTime',
-          width: 150,
-          customRender: ({ record }) => {
-            return (
-              record.createTime &&
-              h(Time, {
-                value: record.createTime,
-                mode: 'datetime',
-              })
-            );
-          },
-        },
-
-        {
-          title: '操作',
-          dataIndex: '',
-          fixed: 'right',
-          slots: { customRender: 'action' },
-          width: 130,
-        },
-      ];
-
-      const [registerTable, { getForm, reload }] = useTable({
-        title: '推荐位',
-        api: RecommendListApi,
-        columns: columns,
-        // useSearchForm: true,
-        // formConfig: searchForm,
-        showTableSetting: true,
-        tableSetting: { fullScreen: true },
-        showIndexColumn: false,
-        rowKey: 'id',
-        searchInfo: {
-          type,
-        },
-      });
-      function handleCreate() {
-        openDrawer(true, {
-          isUpdate: false,
-          ...getForm().getFieldsValue(),
-        });
-      }
-      async function handleDelete(record: Recordable) {
-        let res = await RecommendDeleteApi({ id: record.id });
-        console.log(res);
-        createMessage.success(t('common.optSuccess'));
-        reload();
-      }
-      function handleEdit(record: Recordable) {
-        openDrawer(true, {
-          record,
-          ...getForm().getFieldsValue(),
-          isUpdate: true,
-        });
-      }
-      return {
-        registerTable,
-        createMessage,
-        registerDrawer,
-        handleCreate,
-        handleEdit,
-        reload,
-        type,
-        handleDelete,
-      };
-    },
-  });
-</script>
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable">
+      <template #toolbar>
+        <a-button type="primary" @click="handleCreate">新增</a-button>
+      </template>
+      <template #img="{ record }">
+        <TableImg :size="90" :simpleShow="true" :imgList="[record.image]" />
+      </template>
+      <template #action="{ record }">
+        <TableAction
+          :actions="[
+            {
+              label: '编辑',
+              onClick: handleEdit.bind(null, record),
+            },
+            {
+              color: 'error',
+              label: '删除',
+              popConfirm: {
+                title: '是否确认删除',
+                confirm: handleDelete.bind(null, record),
+              },
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+    <PadsDrawer :type="type" @register="registerDrawer" @reload="reload" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, h } from 'vue';
+  import { BasicTable, useTable, BasicColumn, TableAction, TableImg } from '/@/components/Table';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  // import { Tag } from 'ant-design-vue';
+  import PadsDrawer from './padsDrawer.vue';
+  import { RecommendListApi, RecommendDeleteApi } from '/@/api/advertisement/list';
+  import { useDrawer } from '/@/components/Drawer';
+  import { Time } from '/@/components/Time';
+  import { useRoute } from 'vue-router';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  // param type 2
+  export default defineComponent({
+    components: { BasicTable, TableAction, TableImg, PadsDrawer },
+    emits: ['register'],
+    setup() {
+      const { t } = useI18n();
+      const { createMessage } = useMessage();
+      const [registerDrawer, { openDrawer }] = useDrawer();
+      const route = useRoute();
+      console.log('searchForm', route, route.fullPath);
+      const type = route.fullPath.split('/').pop(); //charAt(route.fullPath.length - 2) || '1';
+      const columns: BasicColumn[] = [
+        {
+          title: '排序',
+          dataIndex: 'orderNum',
+          fixed: 'left',
+          width: 100,
+        },
+        {
+          title: '封面',
+          dataIndex: 'image',
+          ellipsis: true,
+          slots: { customRender: 'img' },
+          width: 230,
+        },
+        {
+          title: '标题',
+          dataIndex: 'title',
+          width: 230,
+        },
+        {
+          title: '直播间链接',
+          dataIndex: 'sceneUrl',
+          width: 300,
+        },
+        {
+          title: '编辑时间',
+          dataIndex: 'createTime',
+          width: 150,
+          customRender: ({ record }) => {
+            return (
+              record.createTime &&
+              h(Time, {
+                value: record.createTime,
+                mode: 'datetime',
+              })
+            );
+          },
+        },
+
+        {
+          title: '操作',
+          dataIndex: '',
+          fixed: 'right',
+          slots: { customRender: 'action' },
+          width: 130,
+        },
+      ];
+
+      const [registerTable, { getForm, reload }] = useTable({
+        title: '推荐位',
+        api: RecommendListApi,
+        columns: columns,
+        // useSearchForm: true,
+        // formConfig: searchForm,
+        showTableSetting: true,
+        tableSetting: { fullScreen: true },
+        showIndexColumn: false,
+        rowKey: 'id',
+        searchInfo: {
+          type,
+        },
+      });
+      function handleCreate() {
+        openDrawer(true, {
+          isUpdate: false,
+          ...getForm().getFieldsValue(),
+        });
+      }
+      async function handleDelete(record: Recordable) {
+        let res = await RecommendDeleteApi({ id: record.id });
+        console.log(res);
+        createMessage.success(t('common.optSuccess'));
+        reload();
+      }
+      function handleEdit(record: Recordable) {
+        openDrawer(true, {
+          record,
+          ...getForm().getFieldsValue(),
+          isUpdate: true,
+        });
+      }
+      return {
+        registerTable,
+        createMessage,
+        registerDrawer,
+        handleCreate,
+        handleEdit,
+        reload,
+        type,
+        handleDelete,
+      };
+    },
+  });
+</script>

+ 106 - 106
src/views/advertisement/padsDrawer.vue

@@ -1,106 +1,106 @@
-<template>
-  <BasicDrawer
-    v-bind="$attrs"
-    width="50%"
-    :showDetailBack="false"
-    :title="getTitle"
-    @register="registerDrawer"
-  >
-    <BasicForm @register="registerForm" />
-  </BasicDrawer>
-</template>
-<script lang="ts">
-  import { defineComponent, computed, ref, unref } from 'vue';
-  import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
-  import { BasicForm, useForm } from '/@/components/Form';
-  import { useMessage } from '/@/hooks/web/useMessage';
-  import { padsSchemas } from './schemas';
-  import { padsSaveApi, padsUpdateApi } from '/@/api/advertisement/list';
-
-  export default defineComponent({
-    components: { BasicDrawer, BasicForm },
-    props: {
-      type: {
-        type: String,
-        default: '0',
-      },
-    },
-    emits: ['reload'],
-    setup(props, { emit }) {
-      const { createMessage } = useMessage();
-      const modelRef = ref({
-        type: props.type,
-      });
-      const isUpdate = ref(true);
-      const [registerForm, { validate, setProps, resetFields, setFieldsValue }] = useForm({
-        labelCol: {
-          span: 4,
-        },
-        wrapperCol: {
-          span: 18,
-        },
-        schemas: padsSchemas,
-        actionColOptions: {
-          offset: 8,
-          span: 8,
-        },
-        submitButtonOptions: {
-          text: '提交',
-        },
-        submitFunc: summitAddDrawer,
-      });
-
-      async function summitAddDrawer() {
-        try {
-          let data = await validate();
-          let httpApi = unref(isUpdate) ? padsUpdateApi : padsSaveApi;
-          setProps({
-            submitButtonOptions: {
-              loading: true,
-            },
-          });
-          httpApi({
-            ...modelRef.value,
-            ...data,
-            image: data.image[0],
-          }).then((_) => {
-            setProps({
-              submitButtonOptions: {
-                loading: false,
-              },
-            });
-            createMessage.success('提交成功!');
-            emit('reload');
-            closeDrawer();
-          });
-        } catch (error) {
-          console.log('error', error);
-        }
-      }
-      const getTitle = computed(() => (!unref(isUpdate) ? '新增推荐位' : '编辑推荐位'));
-      const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
-        resetFields();
-        setDrawerProps({ confirmLoading: false });
-        isUpdate.value = !!data?.isUpdate;
-        modelRef.value = {
-          ...data.record,
-          type: props.type,
-        };
-
-        if (unref(isUpdate)) {
-          console.log('data.record', data.record);
-          setFieldsValue({
-            ...data.record,
-            image: [data.record.image],
-          });
-        }
-      });
-      return {
-        registerForm,
-        getTitle,
-        registerDrawer,
-        closeDrawer,
-      };
-    },
-  });
-</script>
+<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    width="50%"
+    :showDetailBack="false"
+    :title="getTitle"
+    @register="registerDrawer"
+  >
+    <BasicForm @register="registerForm" />
+  </BasicDrawer>
+</template>
+<script lang="ts">
+  import { defineComponent, computed, ref, unref } from 'vue';
+  import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
+  import { BasicForm, useForm } from '/@/components/Form';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { padsSchemas } from './schemas';
+  import { padsSaveApi, padsUpdateApi } from '/@/api/advertisement/list';
+
+  export default defineComponent({
+    components: { BasicDrawer, BasicForm },
+    props: {
+      type: {
+        type: String,
+        default: '0',
+      },
+    },
+    emits: ['register', 'reload'],
+    setup(props, { emit }) {
+      const { createMessage } = useMessage();
+      const modelRef = ref({
+        type: props.type,
+      });
+      const isUpdate = ref(true);
+      const [registerForm, { validate, setProps, resetFields, setFieldsValue }] = useForm({
+        labelCol: {
+          span: 4,
+        },
+        wrapperCol: {
+          span: 18,
+        },
+        schemas: padsSchemas,
+        actionColOptions: {
+          offset: 8,
+          span: 8,
+        },
+        submitButtonOptions: {
+          text: '提交',
+        },
+        submitFunc: summitAddDrawer,
+      });
+
+      async function summitAddDrawer() {
+        try {
+          let data = await validate();
+          let httpApi = unref(isUpdate) ? padsUpdateApi : padsSaveApi;
+          setProps({
+            submitButtonOptions: {
+              loading: true,
+            },
+          });
+          httpApi({
+            ...modelRef.value,
+            ...data,
+            image: data.image[0],
+          }).then((_) => {
+            setProps({
+              submitButtonOptions: {
+                loading: false,
+              },
+            });
+            createMessage.success('提交成功!');
+            emit('reload');
+            closeDrawer();
+          });
+        } catch (error) {
+          console.log('error', error);
+        }
+      }
+      const getTitle = computed(() => (!unref(isUpdate) ? '新增推荐位' : '编辑推荐位'));
+      const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
+        resetFields();
+        setDrawerProps({ confirmLoading: false });
+        isUpdate.value = !!data?.isUpdate;
+        modelRef.value = {
+          ...data.record,
+          type: props.type,
+        };
+
+        if (unref(isUpdate)) {
+          console.log('data.record', data.record);
+          setFieldsValue({
+            ...data.record,
+            image: [data.record.image],
+          });
+        }
+      });
+      return {
+        registerForm,
+        getTitle,
+        registerDrawer,
+        closeDrawer,
+      };
+    },
+  });
+</script>

+ 1 - 1
src/views/corporation/AddCorporationModal.vue

@@ -148,7 +148,7 @@
       label: t('routes.corporation.companyLogo'),
       required: true,
       rules: [{ required: true, message: '请选择上传文件' }],
-      helpMessage: '支持png  jpg图片格式',
+      helpMessage: '支持png jpg jpeg gif图片格式',
       itemProps: {
         validateTrigger: 'onBlur',
       },

+ 140 - 140
src/views/corporation/chargeModal.vue

@@ -1,140 +1,140 @@
-<template>
-  <BasicModal
-    v-bind="$attrs"
-    @register="register"
-    title="设备充值"
-    @ok="submit"
-    @visible-change="handleVisibleChange"
-  >
-    <div class="pt-3px pr-3px">
-      <BasicForm @register="registerForm" :model="modelRef">
-        <template #userName="{ record }">
-          {{ record.userName }}
-        </template>
-        <template #text="{ model, field }">
-          {{ model[field] }}
-        </template>
-      </BasicForm>
-    </div>
-  </BasicModal>
-</template>
-<script lang="ts">
-  import { defineComponent, ref, nextTick, inject } from 'vue';
-  import { BasicModal, useModalInner } from '/@/components/Modal';
-  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
-  import { useI18n } from '/@/hooks/web/useI18n';
-  import { addPoint } from '/@/api/corporation/modal';
-  import { useMessage } from '/@/hooks/web/useMessage';
-  const schemas: FormSchema[] = [
-    {
-      field: 'userName',
-      component: 'Input',
-      label: '充值账号',
-      labelWidth: 100,
-      slot: 'text',
-      colProps: {
-        span: 20,
-      },
-    },
-    {
-      field: 'point',
-      component: 'Input',
-      slot: 'text',
-      label: '当前余额',
-      labelWidth: 100,
-      colProps: {
-        span: 20,
-      },
-      componentProps: {
-        disabled: true,
-      },
-    },
-    {
-      field: 'addPointnumber',
-      component: 'InputNumber',
-      label: '充 值',
-      labelWidth: 100,
-      colProps: {
-        span: 20,
-      },
-      defaultValue: 0,
-    },
-  ];
-  export default defineComponent({
-    components: { BasicModal, BasicForm },
-    props: {
-      userData: { type: Object },
-    },
-    emits: ['register', 'update'],
-    setup(props, { emit }) {
-      const { t } = useI18n();
-      const { createConfirm, createMessage } = useMessage();
-      const reload = inject('tablereload');
-      const modelRef = ref({
-        id: 0,
-      });
-      const [registerForm, { setFieldsValue, resetFields, getFieldsValue }] = useForm({
-        labelWidth: 120,
-        schemas,
-        showActionButtonGroup: false,
-        actionColOptions: {
-          span: 24,
-        },
-      });
-
-      const [register, { closeModal }] = useModalInner((data) => {
-        data && onDataReceive(data);
-      });
-      async function saveTable() {
-        let { addPointnumber } = getFieldsValue();
-        let res = await addPoint({
-          id: modelRef.value.id,
-          point: Number(addPointnumber),
-        });
-        console.log('saveTable', res, addPoint);
-        createMessage.success(t('common.optSuccess'));
-        // reload();
-        emit('update');
-        closeModal();
-      }
-      async function submit() {
-        createConfirm({
-          iconType: 'warning',
-          title: '提示',
-          content: '此操作将对该账号进行充值, 是否继续?',
-          onOk: async () => {
-            saveTable();
-          },
-        });
-      }
-      function onDataReceive(data) {
-        console.log('Data Received', data.record);
-        // 方式1;
-        resetFields();
-        setFieldsValue({
-          ...data.record,
-        });
-
-        // // 方式2
-        modelRef.value = {
-          ...data.record,
-        };
-      }
-
-      function handleVisibleChange(v) {
-        v && props.userData && nextTick(() => onDataReceive(props.userData));
-      }
-
-      return {
-        register,
-        submit,
-        saveTable,
-        reload,
-        schemas,
-        registerForm,
-        modelRef,
-        handleVisibleChange,
-      };
-    },
-  });
-</script>
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="设备充值"
+    @ok="submit"
+    @visible-change="handleVisibleChange"
+  >
+    <div class="pt-3px pr-3px">
+      <BasicForm @register="registerForm" :model="modelRef">
+        <template #userName="{ record }">
+          {{ record.userName }}
+        </template>
+        <template #text="{ model, field }">
+          {{ model[field] }}
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, nextTick } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { addPoint } from '/@/api/corporation/modal';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  const schemas: FormSchema[] = [
+    {
+      field: 'userName',
+      component: 'Input',
+      label: '充值账号',
+      labelWidth: 100,
+      slot: 'text',
+      colProps: {
+        span: 20,
+      },
+    },
+    {
+      field: 'point',
+      component: 'Input',
+      slot: 'text',
+      label: '当前余额',
+      labelWidth: 100,
+      colProps: {
+        span: 20,
+      },
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      field: 'addPointnumber',
+      component: 'InputNumber',
+      label: '充 值',
+      labelWidth: 100,
+      colProps: {
+        span: 20,
+      },
+      defaultValue: 0,
+    },
+  ];
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['register', 'update'],
+    setup(props, { emit }) {
+      const { t } = useI18n();
+      const { createConfirm, createMessage } = useMessage();
+      // const reload = inject('tablereload');
+      const modelRef = ref({
+        id: 0,
+      });
+      const [registerForm, { setFieldsValue, resetFields, getFieldsValue, reload }] = useForm({
+        labelWidth: 120,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      async function saveTable() {
+        let { addPointnumber } = getFieldsValue();
+        let res = await addPoint({
+          id: modelRef.value.id,
+          point: Number(addPointnumber),
+        });
+        console.log('saveTable', res, addPoint);
+        createMessage.success(t('common.optSuccess'));
+        // reload();
+        emit('update');
+        closeModal();
+      }
+      async function submit() {
+        createConfirm({
+          iconType: 'warning',
+          title: '提示',
+          content: '此操作将对该账号进行充值, 是否继续?',
+          onOk: async () => {
+            saveTable();
+          },
+        });
+      }
+      function onDataReceive(data) {
+        console.log('Data Received', data.record);
+        // 方式1;
+        resetFields();
+        setFieldsValue({
+          ...data.record,
+        });
+
+        // // 方式2
+        modelRef.value = {
+          ...data.record,
+        };
+      }
+
+      function handleVisibleChange(v) {
+        v && props.userData && nextTick(() => onDataReceive(props.userData));
+      }
+
+      return {
+        register,
+        submit,
+        saveTable,
+        reload,
+        schemas,
+        registerForm,
+        modelRef,
+        handleVisibleChange,
+      };
+    },
+  });
+</script>

+ 48 - 18
src/views/dashboard/analysis/components/VisitAnalysis.vue

@@ -3,18 +3,24 @@
 </template>
 <script lang="ts">
   import { basicProps } from './props';
+  // import { dateUtil } from '/@/utils/dateUtil';
 </script>
 <script lang="ts" setup>
-  import { onMounted, ref, Ref } from 'vue';
+  import { ref, Ref, watch } from 'vue';
   import { useECharts } from '/@/hooks/web/useECharts';
 
-  defineProps({
+  const props = defineProps({
     ...basicProps,
   });
+  const bulletChatAmountsData = ref<number[]>([]);
+  const userAmountData = ref<number[]>([]);
+  const yixStringData = ref<string[]>([]);
+  const maxSize = ref(0);
+
   const chartRef = ref<HTMLDivElement | null>(null);
   const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
 
-  onMounted(() => {
+  function handleSetOptions() {
     setOptions({
       tooltip: {
         trigger: 'axis',
@@ -26,13 +32,12 @@
         },
       },
       legend: {
-        x: 'center',
-        y: 'bottom',
+        orient: 'horizontal',
+        bottom: 0,
       },
       xAxis: {
         type: 'category',
-        boundaryGap: false,
-        data: [...new Array(18)].map((_item, index) => `${index + 6}:00`),
+        data: yixStringData.value,
         splitLine: {
           show: true,
           lineStyle: {
@@ -48,7 +53,7 @@
       yAxis: [
         {
           type: 'value',
-          max: 80000,
+          max: maxSize.value,
           splitNumber: 4,
           axisTick: {
             show: false,
@@ -61,14 +66,11 @@
           },
         },
       ],
-      grid: { left: '1%', right: '1%', top: '2%', bottom: '10%', containLabel: true },
+      grid: { left: '2%', right: '2%', top: '2%', bottom: '10%', containLabel: true },
       series: [
         {
           smooth: true,
-          data: [
-            111, 222, 4000, 18000, 33333, 55555, 66666, 33333, 14000, 36000, 66666, 44444, 22222,
-            11111, 4000, 2000, 500, 333, 222, 111,
-          ],
+          data: userAmountData.value,
           type: 'line',
           areaStyle: {},
           name: '留言总人数',
@@ -78,10 +80,7 @@
         },
         {
           smooth: true,
-          data: [
-            33, 66, 88, 333, 3333, 5000, 18000, 3000, 1200, 13000, 22000, 11000, 2221, 1201, 390,
-            198, 60, 30, 22, 11,
-          ],
+          data: bulletChatAmountsData.value,
           type: 'line',
           name: '留言总数',
           areaStyle: {},
@@ -91,5 +90,36 @@
         },
       ],
     });
-  });
+  }
+
+  watch(
+    () => [props.bulletChatAmounts, props.userAmount],
+    ([data1, data2]) => {
+      bulletChatAmountsData.value = data1.reduce<number[]>(
+        (prev: number[], current) => prev.concat(Number(current.amount)),
+        [],
+      );
+
+      yixStringData.value = data1.reduce<string[]>(
+        (prev: string[], current) => prev.concat(current.date),
+        [],
+      );
+
+      userAmountData.value = data2.reduce<number[]>(
+        (prev: number[], current) => prev.concat(Number(current.amount)),
+        [],
+      );
+      console.log('viewStatics-data', bulletChatAmountsData.value);
+      const maxNumber = Math.max(...bulletChatAmountsData.value.concat(userAmountData.value));
+      const pow = Math.pow(10, maxNumber.toString().length - 1);
+      maxSize.value = maxNumber > 10 ? Math.floor(maxNumber / 10) * 10 + pow * 2 : 10;
+      console.log('maxSize', maxSize.value);
+      handleSetOptions();
+      // viewStaticsData.value = data;
+    },
+    {
+      immediate: true,
+      deep: true,
+    },
+  );
 </script>

+ 51 - 13
src/views/dashboard/analysis/components/VisitAnalysisBar.vue

@@ -3,17 +3,24 @@
 </template>
 <script lang="ts">
   import { basicProps } from './props';
+  // import { dateUtil } from '/@/utils/dateUtil';
 </script>
 <script lang="ts" setup>
-  import { onMounted, ref, Ref } from 'vue';
+  import { ref, Ref, watch } from 'vue';
+  // import type { dataItemType } from './props';
   import { useECharts } from '/@/hooks/web/useECharts';
-  defineProps({
+  const props = defineProps({
     ...basicProps,
   });
 
+  const viewStaticsData = ref<number[]>([]);
+  const shareStaticsData = ref<number[]>([]);
+  const yixStringData = ref<string[]>([]);
+  const maxSize = ref(0);
   const chartRef = ref<HTMLDivElement | null>(null);
   const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
-  onMounted(() => {
+
+  function handlesetOptions() {
     setOptions({
       tooltip: {
         trigger: 'axis',
@@ -25,35 +32,66 @@
         },
       },
       legend: {
-        x: 'center',
-        y: 'bottom',
+        orient: 'horizontal',
+        bottom: 0,
       },
-      grid: { left: '1%', right: '1%', top: '2%', bottom: '10%', containLabel: true },
+      grid: { left: '2%', right: '2%', top: '2%', bottom: '10%', containLabel: true },
       xAxis: {
         type: 'category',
-        data: [...new Array(30)].map((_item, index) => `${index + 1}日`),
+        // data: [...new Array(30)].map((_item, index) => `${index + 1}日`),
+        data: yixStringData.value,
       },
       yAxis: {
         type: 'value',
-        max: 8000,
+        max: maxSize.value,
         splitNumber: 4,
       },
       series: [
         {
-          data: [3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000, 3200, 4800],
+          data: viewStaticsData.value,
           type: 'bar',
-          itemStyle: { normal: { color: '#38a0ff' } },
+          itemStyle: { color: '#38a0ff' },
           barMaxWidth: 80,
           name: '用户浏览量',
         },
         {
-          data: [100, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000, 3200, 4800],
+          data: shareStaticsData.value,
           type: 'bar',
-          itemStyle: { normal: { color: '#4cca73' } },
+          itemStyle: { color: '#4cca73' },
           barMaxWidth: 80,
           name: '用户分享数',
         },
       ],
     });
-  });
+  }
+  // props.viewStatics,
+  watch(
+    () => [props.viewStatics, props.shareStatics],
+    ([data1, data2]) => {
+      console.log('viewStatics-data', data1);
+      viewStaticsData.value = data1.reduce<number[]>(
+        (prev: number[], current) => prev.concat(Number(current.amount)),
+        [],
+      );
+
+      yixStringData.value = data1.reduce<string[]>(
+        (prev: string[], current) => prev.concat(current.date),
+        [],
+      );
+      shareStaticsData.value = data2.reduce<number[]>(
+        (prev: number[], current) => prev.concat(Number(current.amount)),
+        [],
+      );
+
+      const maxNumber = Math.max(...viewStaticsData.value.concat(shareStaticsData.value));
+      const pow = Math.pow(10, maxNumber.toString().length - 1);
+      maxSize.value = maxNumber > 10 ? Math.floor(maxNumber / 10) * 10 + pow * 2 : 10;
+      console.log('maxSize', maxSize.value);
+      handlesetOptions();
+    },
+    {
+      immediate: true,
+      deep: true,
+    },
+  );
 </script>

+ 37 - 16
src/views/dashboard/analysis/components/props.ts

@@ -1,16 +1,37 @@
-import { PropType } from 'vue';
-
-export interface BasicProps {
-  width: string;
-  height: string;
-}
-export const basicProps = {
-  width: {
-    type: String as PropType<string>,
-    default: '100%',
-  },
-  height: {
-    type: String as PropType<string>,
-    default: '280px',
-  },
-};
+import { PropType } from 'vue';
+
+export interface BasicProps {
+  width: string;
+  height: string;
+}
+export type dataItemType = {
+  date: string;
+  amount: string | number;
+};
+
+export const basicProps = {
+  width: {
+    type: String as PropType<string>,
+    default: '100%',
+  },
+  height: {
+    type: String as PropType<string>,
+    default: '280px',
+  },
+  viewStatics: {
+    type: Array as PropType<Array<dataItemType>>,
+    default: [],
+  },
+  shareStatics: {
+    type: Array as PropType<Array<dataItemType>>,
+    default: [],
+  },
+  bulletChatAmounts: {
+    type: Array as PropType<Array<dataItemType>>,
+    default: [],
+  },
+  userAmount: {
+    type: Array as PropType<Array<dataItemType>>,
+    default: [],
+  },
+};

+ 278 - 0
src/views/dashboard/analysis/enterprise.vue

@@ -0,0 +1,278 @@
+<template>
+  <div class="p-4">
+    <!-- <GrowCard :loading="loading" class="enter-y" /> -->
+    <BasicTable @register="registerTable" :loading="loading">
+      <template #toolbar>
+        <a-button type="primary" @click="handleExport">导出数据</a-button>
+      </template>
+    </BasicTable>
+    <!-- <div class="md:flex enter-y">
+      <div class="md:w-1/2 enter-y">
+        <Card class="w-full">
+          <VisitAnalysisBar :loading="loading" />
+        </Card>
+      </div>
+      <div class="!md:mx-2"></div>
+      <Card class="md:w-1/2 enter-y">
+
+        <VisitAnalysis :loading="loading" />
+      </Card>
+    </div>
+    <div class="md:flex enter-y mt-4">
+      <Card class="md:w-1/2 md:w-full"> </Card>
+    </div> -->
+  </div>
+</template>
+<script lang="ts" setup>
+  import { ref } from 'vue';
+  import { BasicTable, useTable, BasicColumn, FormProps } from '/@/components/Table';
+  // import { useI18n } from '/@/hooks/web/useI18n';
+  // import GrowCard from './components/GrowCard.vue';
+  import {
+    liveRoomStaticsApi,
+    // bulletChatStaticsApi,
+    companyChatExportApi,
+  } from '/@/api/dashboard/analysis';
+
+  import { ListApi as ListCorporationApi } from '/@/api/corporation/list';
+  import { listRoomsApi } from '/@/api/scene/list';
+  import { ListApi as ListLiveApi } from '/@/api/scene/live';
+
+  import { dateUtil, formatToDate } from '/@/utils/dateUtil';
+
+  const loading = ref(true);
+  // const { t } = useI18n();
+  const today = formatToDate(dateUtil(new Date()));
+  const priorDate = formatToDate(dateUtil(new Date().setDate(new Date().getDate() - 30)));
+
+  const columns: BasicColumn[] = [
+    {
+      title: '企业名称',
+      dataIndex: 'companyName',
+      width: 120,
+    },
+    {
+      title: '房间名称',
+      dataIndex: 'liveRoomName',
+      width: 120,
+    },
+    {
+      title: '直播场景名称',
+      dataIndex: 'brandName',
+      width: 120,
+    },
+    {
+      title: '主持人',
+      dataIndex: 'anchorName',
+      width: 120,
+    },
+    {
+      title: '总观看人数',
+      dataIndex: 'spectatorNum',
+      // sorter: true,
+      width: 120,
+    },
+    {
+      title: '总直播时长(min)',
+      dataIndex: 'duration',
+      // sorter: true,
+      width: 120,
+    },
+    {
+      title: '总分享数',
+      dataIndex: 'shareNum',
+      // sorter: true,
+      width: 120,
+    },
+  ];
+  const searchForm: Partial<FormProps> = {
+    labelWidth: 100,
+    schemas: [
+      {
+        field: 'companyId',
+        label: '请选择企业',
+        component: 'ApiSelect',
+        required: false,
+        componentProps: {
+          api: ListCorporationApi,
+          resultField: 'list',
+          labelField: 'name',
+          valueField: 'id',
+          immediate: false,
+          showSearch: true,
+          optionFilterProp: 'label',
+          // onChange: function (_, opt) {
+          //   // Reflect.set(modalRecord, 'shippingName', opt.label);
+          // },
+          params: {
+            page: 1,
+            limit: 1000,
+          },
+        },
+        colProps: {
+          lg: 24,
+          md: 24,
+          xl: 12,
+          xxl: 12,
+          // xl: 5,
+          // xxl: 12,
+        },
+      },
+      {
+        field: 'liveRoomId',
+        label: '请选择房间',
+        component: 'ApiSelect',
+        colProps: {
+          lg: 24,
+          md: 24,
+          xl: 12,
+          xxl: 12,
+        },
+        componentProps: {
+          api: listRoomsApi,
+          resultField: 'list',
+          labelField: 'name',
+          valueField: 'id',
+          immediate: false,
+          showSearch: true,
+          optionFilterProp: 'label',
+          // onChange: function (_, opt) {
+          //   // Reflect.set(modalRecord, 'shippingName', opt.label);
+          // },
+          params: {
+            page: 1,
+            limit: 1000,
+            state: 103,
+          },
+        },
+      },
+      {
+        field: 'brandId',
+        label: '请选择直播场景',
+        labelWidth: 130,
+        component: 'ApiSelect',
+        colProps: {
+          lg: 24,
+          md: 24,
+          xl: 12,
+          xxl: 12,
+        },
+        componentProps: {
+          api: ListLiveApi,
+          resultField: 'list',
+          labelField: 'name',
+          valueField: 'id',
+          immediate: false,
+          showSearch: true,
+          optionFilterProp: 'label',
+          params: {
+            page: 1,
+            limit: 1000,
+          },
+        },
+      },
+      {
+        field: 'time',
+        label: '时间段',
+        defaultValue: [priorDate, today],
+        component: 'RangePicker',
+        colProps: {
+          lg: 24,
+          md: 24,
+          xl: 12,
+          xxl: 12,
+        },
+        componentProps: {
+          format: 'YYYY-MM-DD',
+          // placeholder: ['开始时间', '结束时间'],
+          // showTime: { format: 'HH:mm:ss' },
+          disabledDate(current) {
+            // console.log('current', current, date);
+            return current && current > dateUtil().endOf('day');
+          },
+        },
+        rules: [
+          {
+            required: true,
+            // @ts-ignore
+            validator: async (rule, value) => {
+              if (!value) {
+                return Promise.reject('请选择时间段');
+              }
+              const days = Math.abs(value[1].diff(value[0], 'days'));
+              console.log('days', days);
+              if (days > 30) {
+                return Promise.reject('选择时间段应小于30天');
+              }
+              if (days < 1) {
+                return Promise.reject('至少选择2天以上');
+              }
+              return Promise.resolve();
+            },
+
+            trigger: 'change',
+          },
+        ],
+      },
+    ],
+    resetFunc: handleReset,
+  };
+
+  const searchInfo = ref({
+    liveRoomId: '',
+    companyId: '',
+    brandId: '',
+    limit: 20,
+    page: 1,
+    time: [priorDate, today],
+  });
+
+  const [registerTable] = useTable({
+    title: '',
+    columns: columns,
+    useSearchForm: true,
+    formConfig: searchForm,
+    api: liveRoomStaticsApi,
+    beforeFetch: (data) => {
+      console.log('beforeFetch', data);
+      data.time = data.time.map((item) => formatToDate(item));
+      searchInfo.value = data;
+      return data;
+    },
+    afterFetch: function (data) {
+      console.log('data', data);
+      return data;
+    },
+    searchInfo: searchInfo,
+    handleSearchInfoFn(data) {
+      searchInfo.value = Object.assign(searchInfo.value, data);
+      console.log(searchInfo.value);
+    },
+  });
+
+  async function handleExport() {
+    const data = await companyChatExportApi(searchInfo.value);
+    const downloadBlob = new Blob([data], {
+      type: 'application/msexcel',
+    });
+    const url = URL.createObjectURL(downloadBlob);
+    const a: HTMLAnchorElement = document.createElement('a');
+    document.body.appendChild(a);
+    a.style.display = 'none';
+    a.href = url;
+    const name = new Date().getTime();
+    a.download = `${name}.csv`;
+    a.click();
+    window.URL.revokeObjectURL(url);
+  }
+  async function handleReset() {
+    searchInfo.value = {
+      liveRoomId: '',
+      companyId: '',
+      brandId: '',
+      limit: 20,
+      page: 1,
+      time: [priorDate, today],
+    };
+  }
+</script>

+ 170 - 22
src/views/dashboard/analysis/index.vue

@@ -6,20 +6,26 @@
         <div class="md:flex enter-y">
           <div class="md:w-1/2 enter-y">
             <Card class="w-full">
-              <VisitAnalysisBar :loading="loading" />
+              <VisitAnalysisBar
+                :loading="loading"
+                :viewStatics="viewStaticsData"
+                :shareStatics="shareStaticsData"
+              />
             </Card>
           </div>
           <div class="!md:mx-2"></div>
           <Card class="md:w-1/2 enter-y">
             <!-- <VisitAnalysis :loading="loading" /> -->
-            <VisitAnalysis :loading="loading" />
+            <VisitAnalysis
+              :loading="loading"
+              :bulletChatAmounts="bulletChatAmountsData"
+              :userAmount="userAmountData"
+            />
           </Card>
         </div>
       </template>
       <template #toolbar>
-        <!-- <a-button type="primary" @click="openAddModal">{{
-          t('routes.devices.addCamera')
-        }}</a-button> -->
+        <a-button type="primary" @click="handleExport">导出数据</a-button>
       </template>
     </BasicTable>
     <!-- <div class="md:flex enter-y">
@@ -40,46 +46,71 @@
   </div>
 </template>
 <script lang="ts" setup>
-  import { ref } from 'vue';
+  import { onMounted, ref } from 'vue';
   import { BasicTable, useTable, BasicColumn, FormProps } from '/@/components/Table';
   // import { useI18n } from '/@/hooks/web/useI18n';
   // import GrowCard from './components/GrowCard.vue';
   import { Card } from 'ant-design-vue';
   import VisitAnalysis from './components/VisitAnalysis.vue';
   import VisitAnalysisBar from './components/VisitAnalysisBar.vue';
+  import {
+    bulletChatApi,
+    userStaticsApi,
+    bulletChatStaticsApi,
+    bulletChatExportApi,
+  } from '/@/api/dashboard/analysis';
+  import { listRoomsApi } from '/@/api/scene/list';
+  import { dateUtil, formatToDate } from '/@/utils/dateUtil';
+  import { StaticItemType } from '/@/api/dashboard/model';
+
+  const today = formatToDate(dateUtil(new Date()));
+  const priorDate = formatToDate(dateUtil(new Date().setDate(new Date().getDate() - 30)));
 
   const loading = ref(true);
+  // UserStaticsModel
+
+  const viewStaticsData = ref<StaticItemType[]>([]);
+
+  const shareStaticsData = ref<StaticItemType[]>([]);
+
+  const bulletChatAmountsData = ref<StaticItemType[]>([]);
+  const userAmountData = ref<StaticItemType[]>([]);
   // const { t } = useI18n();
 
   const columns: BasicColumn[] = [
     {
       title: '微信昵称',
-      dataIndex: 'key1',
+      dataIndex: 'nickName',
       width: 120,
+      ellipsis: true,
+      customRender: ({ record }) => {
+        const { nickName } = record;
+        return nickName?.length ? nickName : ' -- ';
+      },
     },
     {
       title: '手机号',
-      dataIndex: 'key2',
+      dataIndex: 'userName',
       width: 120,
     },
     {
       title: '房间名称',
-      dataIndex: 'key3',
+      dataIndex: 'liveRoomName',
       width: 120,
     },
     {
       title: '在线时长(min)',
-      dataIndex: 'key4',
+      dataIndex: 'duration',
       width: 120,
     },
     {
       title: '留言条数',
-      dataIndex: 'key5',
+      dataIndex: 'bulletChatAmount',
       width: 120,
     },
     {
       title: '留言内容',
-      dataIndex: 'key6',
+      dataIndex: 'bulletChatContents',
       width: 320,
     },
   ];
@@ -87,40 +118,157 @@
     labelWidth: 100,
     schemas: [
       {
-        field: 'name',
-        label: '房间名称',
-        component: 'Input',
+        field: 'liveRoomId',
+        label: '全部房间',
+        component: 'ApiSelect',
+        // required: true,
         colProps: {
           xl: 5,
           xxl: 5,
         },
         componentProps: {
-          maxLength: 100,
+          api: listRoomsApi,
+          labelField: 'name',
+          resultField: 'list',
+          valueField: 'id',
+          immediate: false,
+          showSearch: true,
+          optionFilterProp: 'label',
+          params: {
+            page: 1,
+            limit: 1000,
+            state: 103,
+          },
         },
       },
       {
         field: 'time',
         label: '时间段',
         component: 'RangePicker',
+        defaultValue: [priorDate, today],
         colProps: {
-          xl: 5,
-          xxl: 5,
+          xl: 16,
+          xxl: 16,
         },
         componentProps: {
-          maxLength: 100,
+          format: 'YYYY-MM-DD',
+          showTime: false,
+          disabledDate(current) {
+            // console.log('current', current, date);
+            return current && current > dateUtil().endOf('day');
+          },
+          // onChange: (value) => {
+          //   console.log('onchange', value);
+          // },
         },
+        rules: [
+          {
+            required: true,
+            // @ts-ignore
+            validator: async (rule, value) => {
+              if (!value) {
+                return Promise.reject('请选择时间段');
+              }
+              const days = Math.abs(value[1].diff(value[0], 'days'));
+              console.log('days', days);
+              if (days > 30) {
+                return Promise.reject('选择时间段应小于30天');
+              }
+              if (days < 1) {
+                return Promise.reject('至少选择2天以上');
+              }
+              return Promise.resolve();
+            },
+
+            trigger: 'change',
+          },
+        ],
       },
     ],
+    resetFunc: handleReset,
   };
 
+  const searchInfo = ref({
+    liveRoomId: '',
+    limit: 20,
+    page: 1,
+    time: [priorDate, today],
+  });
+
   const [registerTable] = useTable({
     title: '房间留言状态',
     columns: columns,
     useSearchForm: true,
     formConfig: searchForm,
+    immediate: true,
+    api: bulletChatApi,
+    beforeFetch: (data) => {
+      console.log('beforeFetch', data);
+      data.time = data.time.map((item) => formatToDate(item));
+      handleStatic(data);
+      searchInfo.value = data;
+      return data;
+    },
+    afterFetch: function (data) {
+      console.log('afterFetch', arguments);
+      return data;
+    },
+    searchInfo: searchInfo,
+    handleSearchInfoFn(data) {
+      searchInfo.value = Object.assign(searchInfo.value, data);
+      console.log(searchInfo.value);
+      return;
+    },
   });
 
-  setTimeout(() => {
-    loading.value = false;
-  }, 1500);
+  onMounted(() => {
+    handleStatic();
+  });
+
+  async function handleStatic(search?: any) {
+    try {
+      loading.value = true;
+      const searchInfoParams = search || searchInfo.value;
+      const sData = await userStaticsApi(searchInfoParams);
+      const bData = await bulletChatStaticsApi(searchInfoParams);
+      console.log('sData', sData);
+      console.log('bData', sData);
+      viewStaticsData.value = sData.viewStatics;
+      shareStaticsData.value = sData.shareStatics;
+      userAmountData.value = bData.userAmount;
+      bulletChatAmountsData.value = bData.bulletChatAmounts;
+      loading.value = false;
+    } catch (error) {
+      loading.value = false;
+    }
+  }
+
+  async function handleExport() {
+    const data = await bulletChatExportApi(searchInfo.value);
+    const downloadBlob = new Blob([data], {
+      type: 'application/msexcel',
+    });
+    const url = URL.createObjectURL(downloadBlob);
+    const a: HTMLAnchorElement = document.createElement('a');
+    document.body.appendChild(a);
+    a.style.display = 'none';
+    a.href = url;
+    const name = new Date().getTime();
+    a.download = `${name}.csv`;
+    a.click();
+    window.URL.revokeObjectURL(url);
+  }
+
+  async function handleReset() {
+    searchInfo.value = {
+      liveRoomId: '',
+      limit: 20,
+      page: 1,
+      time: [priorDate, today],
+    };
+  }
+
+  // setTimeout(() => {
+  //   loading.value = false;
+  // }, 1500);
 </script>

+ 0 - 4
src/views/feedback/list.vue

@@ -130,10 +130,6 @@
         // rowKey: 'userId',
         pagination: { pageSize: 20 },
         bordered: true,
-        sortFn: (sortInfo) => {
-          let order = sortInfo.order && sortInfo.order.replace('end', '');
-          return { ...sortInfo, sidx: sortInfo.field, order: order };
-        },
         fetchSetting: {
           pageField: 'page',
           sizeField: 'limit',

+ 193 - 195
src/views/member/list.vue

@@ -1,195 +1,193 @@
-<!-- 
-avatar: "https://thirdwx.qlogo.cn/mmopen/vi_32/dTbT3X0Fm7BnUA4DoQhBy14smppF5eBibqPSOib3ou5elGuJ1eYZ9sib2ZALDMs1icYupWIxiaJwWETTV8we9brCHhQ/132"
-bindBrandId: null
-bindBrandName: null
-birthday: 631123200000
-brandId: null
-brandName: null
-city: ""
-gender: 1
-id: 25
-isAdmin: 0
-lastLoginIp: "117.136.32.65"
-lastLoginTime: 1631504271000
-levelName: null
-mobile: "13536501128"
-nickname: "波仔"
-password: "oeADe5U9uLeMYsNigq98zdu8J96A"
-registerIp: "117.136.32.65"
-registerTime: 1631504271000
-userLevelId: null
-username: "微信用户f8f4igi4j9ks"
-weixinOpenid: "oeADe5U9uLeMYsNigq98zdu8J96A" 
--->
-<template>
-  <div class="p-4">
-    <BasicTable @register="registerTable">
-      <template #toolbar> </template>
-      <template #avatar="{ record }">
-        <Avatar :size="80" :src="record.avatar" />
-      </template>
-
-      <template #gender="{ record }">
-        {{ renderGenderLabel(record.gender) }}
-      </template>
-
-      <template #birthday="{ record }">
-        <Time v-if="record.birthday" :value="record.birthday" mode="datetime" />
-      </template>
-
-      <template #lastLoginTime="{ record }">
-        <Time :value="record.lastLoginTime" mode="datetime" />
-      </template>
-
-      <template #action="{ record }">
-        <TableAction
-          :actions="[
-            {
-              label: '详情',
-              onClick: () => {
-                go(`/order/list/detail/${record.orderNo}`);
-              },
-            },
-            {
-              label: '打印',
-              color: 'error',
-              onClick: () => {
-                createMessage.info(`暂未接入`);
-              },
-            },
-          ]"
-        />
-      </template>
-    </BasicTable>
-  </div>
-</template>
-<script lang="ts">
-  import { defineComponent } from 'vue';
-  import { BasicTable, useTable, BasicColumn, FormProps, TableAction } from '/@/components/Table';
-  import { useMessage } from '/@/hooks/web/useMessage';
-  import { uploadApi } from '/@/api/sys/upload';
-
-  import { ListApi } from '/@/api/member/list';
-  import { useI18n } from '/@/hooks/web/useI18n';
-  // import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
-  import { useGo } from '/@/hooks/web/usePage';
-  import { Avatar } from 'ant-design-vue';
-  import { Time } from '/@/components/Time';
-
-  export default defineComponent({
-    components: { BasicTable, TableAction, Avatar, Time },
-    setup() {
-      const { createMessage } = useMessage();
-      const go = useGo();
-      const { t } = useI18n();
-      const columns: BasicColumn[] = [
-        {
-          title: 'ID',
-          dataIndex: 'id',
-          fixed: 'left',
-          width: 60,
-        },
-        {
-          title: '名称',
-          dataIndex: 'username',
-          // sorter: true,
-          width: 160,
-        },
-        {
-          title: '头像',
-          dataIndex: 'avatar',
-          slots: { customRender: 'avatar' },
-          width: 120,
-        },
-        {
-          title: '性别',
-          dataIndex: 'gender',
-          slots: { customRender: 'gender' },
-          // sorter: true,
-          width: 120,
-        },
-        {
-          title: '出生日期',
-          dataIndex: 'birthday',
-          slots: { customRender: 'birthday' },
-          width: 120,
-        },
-        {
-          title: '最后登录时间',
-          dataIndex: 'lastLoginTime',
-          slots: { customRender: 'lastLoginTime' },
-          // sorter: true,
-          width: 120,
-        },
-        {
-          title: '微信名',
-          dataIndex: 'nickname',
-          width: 120,
-        },
-        {
-          title: '手机号',
-          dataIndex: 'mobile',
-          width: 120,
-        },
-      ];
-
-      const searchForm: Partial<FormProps> = {
-        labelWidth: 100,
-        schemas: [
-          {
-            field: 'username',
-            label: '名称',
-            component: 'Input',
-            componentProps: {
-              maxLength: 100,
-            },
-            colProps: {
-              xl: 5,
-              xxl: 5,
-            },
-          },
-        ],
-      };
-
-      const [registerTable] = useTable({
-        title: '微信用户列表',
-        api: ListApi,
-        columns: columns,
-        useSearchForm: true,
-        formConfig: searchForm,
-        showTableSetting: true,
-        tableSetting: { fullScreen: true },
-        showIndexColumn: false,
-        rowKey: 'id',
-        pagination: { pageSize: 20 },
-        bordered: true,
-        fetchSetting: {
-          pageField: 'page',
-          sizeField: 'limit',
-          listField: 'list',
-          totalField: 'totalCount',
-        },
-      });
-
-      function renderGenderLabel(gender: number): string {
-        switch (gender) {
-          case 0:
-            return '女';
-          case 1:
-            return '男';
-          default:
-            return '未知';
-        }
-      }
-
-      return {
-        registerTable,
-        createMessage,
-        t,
-        go,
-        renderGenderLabel,
-        uploadApi: uploadApi as any,
-      };
-    },
-  });
-</script>
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable">
+      <template #toolbar> </template>
+      <template #avatar="{ record }">
+        <Avatar :size="80" :src="record.avatar" />
+      </template>
+
+      <template #gender="{ record }">
+        {{ renderGenderLabel(record.gender) }}
+      </template>
+
+      <template #birthday="{ record }">
+        <Time v-if="record.birthday" :value="record.birthday" mode="datetime" />
+      </template>
+
+      <template #lastLoginTime="{ record }">
+        <Time :value="record.lastLoginTime" mode="datetime" />
+      </template>
+
+      <template #action="{ record }">
+        <TableAction
+          :actions="[
+            {
+              color: 'error',
+              label: '删除',
+              popConfirm: {
+                title: '是否确认删除',
+                confirm: handleDelete.bind(null, record),
+              },
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable, BasicColumn, FormProps, TableAction } from '/@/components/Table';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { uploadApi } from '/@/api/sys/upload';
+
+  import { ListApi, unbindWechatApi } from '/@/api/member/list';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  // import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
+  import { useGo } from '/@/hooks/web/usePage';
+  import { Avatar } from 'ant-design-vue';
+  import { Time } from '/@/components/Time';
+
+  export default defineComponent({
+    components: { BasicTable, TableAction, Avatar, Time },
+    setup() {
+      const { createMessage } = useMessage();
+      const go = useGo();
+      const { t } = useI18n();
+      const columns: BasicColumn[] = [
+        {
+          title: 'ID',
+          dataIndex: 'id',
+          fixed: 'left',
+          width: 60,
+        },
+        {
+          title: '名称',
+          dataIndex: 'username',
+          // sorter: true,
+          width: 160,
+        },
+        {
+          title: '头像',
+          dataIndex: 'avatar',
+          slots: { customRender: 'avatar' },
+          width: 120,
+        },
+        {
+          title: '性别',
+          dataIndex: 'gender',
+          slots: { customRender: 'gender' },
+          // sorter: true,
+          width: 120,
+        },
+        {
+          title: '出生日期',
+          dataIndex: 'birthday',
+          slots: { customRender: 'birthday' },
+          width: 120,
+        },
+        {
+          title: '最后登录时间',
+          dataIndex: 'lastLoginTime',
+          slots: { customRender: 'lastLoginTime' },
+          // sorter: true,
+          width: 120,
+        },
+        {
+          title: '微信名',
+          dataIndex: 'nickname',
+          width: 120,
+        },
+        {
+          title: '手机号',
+          dataIndex: 'mobile',
+          width: 120,
+        },
+        {
+          title: '操作',
+          dataIndex: 'action',
+          slots: { customRender: 'action' },
+          width: 50,
+        },
+      ];
+
+      const searchForm: Partial<FormProps> = {
+        labelWidth: 100,
+        schemas: [
+          {
+            field: 'username',
+            label: '名称',
+            component: 'Input',
+            componentProps: {
+              maxLength: 100,
+            },
+            colProps: {
+              xl: 5,
+              xxl: 5,
+            },
+          },
+          {
+            field: 'mobile',
+            label: '手机号',
+            component: 'Input',
+            componentProps: {
+              maxLength: 100,
+            },
+            colProps: {
+              xl: 5,
+              xxl: 5,
+            },
+          },
+        ],
+      };
+
+      const [registerTable, { reload }] = useTable({
+        title: '微信用户列表',
+        api: ListApi,
+        columns: columns,
+        useSearchForm: true,
+        formConfig: searchForm,
+        showTableSetting: true,
+        tableSetting: { fullScreen: true },
+        showIndexColumn: false,
+        rowKey: 'id',
+        pagination: { pageSize: 20 },
+        bordered: true,
+        fetchSetting: {
+          pageField: 'page',
+          sizeField: 'limit',
+          listField: 'list',
+          totalField: 'totalCount',
+        },
+      });
+
+      function renderGenderLabel(gender: number): string {
+        switch (gender) {
+          case 0:
+            return '女';
+          case 1:
+            return '男';
+          default:
+            return '未知';
+        }
+      }
+      async function handleDelete(record: Recordable) {
+        console.log('record', record);
+        try {
+          await unbindWechatApi(record.id);
+        } catch (error) {}
+        reload();
+      }
+
+      return {
+        registerTable,
+        createMessage,
+        t,
+        go,
+        renderGenderLabel,
+        uploadApi: uploadApi as any,
+        handleDelete,
+      };
+    },
+  });
+</script>

+ 319 - 0
src/views/notification/addModal.vue

@@ -0,0 +1,319 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @cancel="resetFields"
+    @register="register"
+    :title="title"
+    okText="立刻发布"
+    width="60%"
+    min-width="600px"
+    :height="550"
+    :showOkBtn="!isView"
+    @ok="handleOk"
+  >
+    <div class="pt-3px pr-3px">
+      <BasicForm @register="registerForm" :model="modelRef" />
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  // computed
+  import { defineComponent, h, ref } from 'vue';
+
+  import { addNoticeApi, editNoticeApi } from '/@/api/notification/list';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  // import { ListAllCompanyApi } from '/@/api/corporation/list';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  // import { useUserStore } from '/@/store/modules/user';
+  // import { Tinymce } from '/@/components/Tinymce';
+  import { uploadApi } from '/@/api/advertisement/list';
+  import { dateUtil, formatToDateTime } from '/@/utils/dateUtil';
+  import { TableImg } from '/@/components/Table';
+
+  const { t } = useI18n();
+  const now = dateUtil();
+
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['register', 'ok', 'cancel'],
+    setup(_, { emit }) {
+      const modelRef = ref({});
+      const isView = ref(false);
+      // const userStore = useUserStore();
+      // const { getCheckRole } = userStore;
+      // const userinfo = computed(() => userStore.getUserInfo);
+      // const { companyId } = userinfo.value;
+
+      const schemas: FormSchema[] = [
+        {
+          field: 'id',
+          component: 'Input',
+          label: 'id',
+          show: false,
+        },
+
+        {
+          field: 'title',
+          component: 'Input',
+          label: '消息标题',
+          required: true,
+          componentProps: {
+            maxlength: 10,
+            showCount: true,
+          },
+          colProps: {
+            span: 22,
+          },
+        },
+        {
+          field: 'selectTime',
+          component: 'RangePicker',
+          componentProps: {
+            format: 'YYYY-MM-DD HH:mm',
+            showTime: true,
+            disabledDate(current) {
+              return current && current < now;
+            },
+            disabledTime(_) {
+              // if (type === 'start') {
+              //   return {
+              //     // ...Array(dateUtil().minute()).keys()
+              //     disabledHours: () => [...Array(dateUtil().hour()).keys()],
+              //     disabledMinutes: () => [],
+              //     disabledSeconds: () => [],
+              //   };
+              // }
+              return {
+                disabledHours: () => [...Array(dateUtil().hour()).keys()],
+                disabledMinutes: () => [],
+                disabledSeconds: () => [],
+              };
+            },
+          },
+          label: '时间段',
+          required: true,
+
+          rules: [
+            {
+              required: true,
+              // @ts-ignore
+              validator: async (rule, value) => {
+                if (!value) {
+                  return Promise.reject('请选择时间段');
+                }
+                // console.log('check', rule, value, record);
+                // // console.log('value[1]', value[1].unix());
+                if (value[0].unix() > value[1].unix()) {
+                  return Promise.reject('开始时间要小于结束时间');
+                }
+                if (value[0].unix() > value[1].unix() - 300) {
+                  return Promise.reject('时间段最小5分钟');
+                }
+                return Promise.resolve();
+              },
+
+              trigger: 'change',
+            },
+          ],
+          colProps: {
+            span: 22,
+          },
+        },
+        {
+          field: 'type',
+          component: 'RadioButtonGroup',
+          label: '类型',
+          required: true,
+          colProps: {
+            span: 22,
+          },
+          defaultValue: 0,
+          componentProps: {
+            options: [
+              { label: '文字', value: 0 },
+              { label: '图片', value: 1 },
+            ],
+          },
+        },
+        {
+          field: 'content1',
+          label: '内容',
+          required: true,
+          component: 'InputTextArea',
+          ifShow: ({ model }) => {
+            return model['type'] === 0;
+          },
+          colProps: {
+            span: 22,
+          },
+          componentProps: {
+            showCount: true,
+            maxlength: 200,
+            autoSize: {
+              minRows: 6,
+            },
+          },
+        },
+        {
+          field: 'content2',
+          label: '内容',
+          component: 'Upload',
+          required: true,
+          helpMessage: '支持png,jpg和gif格式的图片,建议分辨率为200*400,最大为5MB',
+          itemProps: {
+            validateTrigger: 'blur',
+          },
+          ifShow: ({ model }) => {
+            return model['type'] === 1 && !isView.value;
+          },
+          componentProps: {
+            api: uploadApi,
+            maxSize: 5,
+            emptyHidePreview: true,
+            maxNumber: 1,
+            accept: ['jpg', 'jpeg', 'gif', 'png'],
+            helpText: '支持png,jpg和gif格式的图片,建议分辨率为200*400,最大为5MB',
+            afterFetch: function (data) {
+              Reflect.set(data, 'url', data.message.url);
+              return data;
+            },
+          },
+
+          colProps: {
+            span: 22,
+          },
+        },
+        {
+          field: 'content2Image',
+          component: 'Input',
+          required: true,
+          ifShow: ({ model }) => {
+            return model['type'] === 1 && isView.value;
+          },
+          render: ({ model, field }) => {
+            return h(TableImg, {
+              imgList: [model[field]],
+              size: 160,
+              style: {
+                height: 160,
+              },
+            });
+          },
+          label: '内容',
+          colProps: {
+            span: 10,
+          },
+        },
+      ];
+      const title = ref('创新消息');
+      const { createMessage } = useMessage();
+      const [registerForm, { validate, resetFields, setFieldsValue, updateSchema }] = useForm({
+        fieldMapToTime: [
+          // 支持多个字段
+          ['selectTime', ['startTime', 'endTime'], 'YYYY-MM-DD HH:mm'],
+        ],
+        labelWidth: 120,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+      // closeModal
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      async function onDataReceive(data) {
+        console.log('onDataReceive', data);
+        // 方式1;
+        resetFields();
+        isView.value = false;
+        let updateSchemas = schemas.map((item: FormSchema) => {
+          item.componentProps = {
+            ...item.componentProps,
+            disabled: false,
+          };
+          return item;
+        });
+        if (data.isView) {
+          isView.value = true;
+          title.value = '查看消息';
+          setFieldsValue({
+            ...data,
+          });
+          if (data.startTime && data.endTime) {
+            setFieldsValue({
+              selectTime: [formatToDateTime(data.startTime), formatToDateTime(data.endTime)],
+            });
+          }
+          if (data.type === 0) {
+            setFieldsValue({
+              content1: data.content,
+            });
+          }
+          if (data.type === 1) {
+            setFieldsValue({
+              content2: [data.content],
+              content2Image: [data.content],
+            });
+          }
+          updateSchemas = schemas.map((item: FormSchema) => {
+            item.componentProps = {
+              ...item.componentProps,
+              disabled: true,
+            };
+            return item;
+          });
+        } else {
+          title.value = '创建消息';
+        }
+        await updateSchema(updateSchemas);
+      }
+
+      async function handleOk() {
+        let data = await validate();
+        if (
+          data.selectTime[0].valueOf() < dateUtil().valueOf() ||
+          data.selectTime[1].valueOf() < dateUtil().valueOf()
+        ) {
+          createMessage.warn('开始或结束时间不能少于当前时间');
+          return;
+        }
+        console.log('data', data);
+
+        const requestApi = data.id ? editNoticeApi : addNoticeApi;
+        let res = await requestApi({
+          title: data.title,
+          content: data.type === 0 ? data.content1 : data.content2[0],
+          type: String(data.type),
+          effectiveTime: [
+            formatToDateTime(data.selectTime[0]),
+            formatToDateTime(data.selectTime[1]),
+          ],
+          id: data.id,
+        });
+        console.log('res', res);
+        emit('ok', res);
+        createMessage.success(t('common.optSuccess'));
+        closeModal();
+        resetFields();
+      }
+
+      return {
+        register,
+        title,
+        schemas,
+        registerForm,
+        modelRef,
+        handleOk,
+        resetFields,
+        isView,
+      };
+    },
+  });
+</script>

+ 169 - 0
src/views/notification/center.vue

@@ -0,0 +1,169 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable">
+      <!-- <template #toolbar>
+        <a-button type="primary" @click="handleCreate">新增</a-button>
+      </template> -->
+      <template #avatar="{ record }">
+        <Avatar :size="80" :src="record.avatar" />
+      </template>
+
+      <template #content="{ record }">
+        <div v-if="record.type === 0" v-html="record.content"></div>
+        <!-- <img v-if="record.type === 1" :src="record.content" /> -->
+        <TableImg
+          v-if="record.type === 1"
+          :size="200"
+          :simpleShow="true"
+          :imgList="[record.content]"
+        />
+      </template>
+
+      <template #periodTime="{ record }">
+        <span v-if="record.startTime?.length && record.endTime?.length">
+          <Time :value="record.startTime" mode="datetime" /> 到
+          <Time :value="record.endTime" mode="datetime" />
+        </span>
+        <span v-else> -- </span>
+      </template>
+
+      <template #createTime="{ record }">
+        <Time :value="record.createTime" mode="datetime" />
+      </template>
+    </BasicTable>
+    <AddModal @register="registerAddModal" @ok="reload" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, onMounted } from 'vue';
+  import { BasicTable, useTable, BasicColumn, FormProps, TableImg } from '/@/components/Table';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { uploadApi } from '/@/api/sys/upload';
+  import { useModal } from '/@/components/Modal';
+  import { listApi, deleteNoticeApi, readAllApi } from '/@/api/notification/list';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useGo } from '/@/hooks/web/usePage';
+  import { Avatar } from 'ant-design-vue';
+  import { Time } from '/@/components/Time';
+  import AddModal from './addModal.vue';
+
+  export default defineComponent({
+    components: { BasicTable, Avatar, Time, AddModal, TableImg },
+    setup() {
+      const { createMessage } = useMessage();
+      const go = useGo();
+      const { t } = useI18n();
+      const [registerAddModal, { openModal: openAddModal }] = useModal();
+      const columns: BasicColumn[] = [
+        {
+          title: '序列',
+          dataIndex: 'id',
+          fixed: 'left',
+          width: 60,
+        },
+        {
+          title: '标题',
+          dataIndex: 'title',
+          // sorter: true,
+          width: 160,
+        },
+        {
+          title: '内容',
+          dataIndex: 'content',
+          slots: { customRender: 'content' },
+          // sorter: true,
+          width: 160,
+        },
+        {
+          title: '持续时间',
+          dataIndex: 'periodTime',
+          slots: { customRender: 'periodTime' },
+          // sorter: true,
+          width: 120,
+        },
+        {
+          title: '发布时间',
+          dataIndex: 'createTime',
+          slots: { customRender: 'createTime' },
+          // sorter: true,
+          width: 120,
+        },
+        // {
+        //   title: '操作',
+        //   dataIndex: '',
+        //   slots: { customRender: 'action' },
+        //   fixed: 'right',
+        //   width: 80,
+        // },
+      ];
+
+      const searchForm: Partial<FormProps> = {
+        labelWidth: 100,
+        schemas: [
+          {
+            field: 'title',
+            label: '标题',
+            component: 'Input',
+            componentProps: {
+              maxLength: 100,
+            },
+            colProps: {
+              xl: 5,
+              xxl: 5,
+            },
+          },
+        ],
+      };
+
+      const [registerTable, { reload }] = useTable({
+        title: '消息列表',
+        api: listApi,
+        columns: columns,
+        useSearchForm: true,
+        formConfig: searchForm,
+        showTableSetting: true,
+        tableSetting: { fullScreen: true },
+        showIndexColumn: false,
+        rowKey: 'id',
+        pagination: { pageSize: 20 },
+        bordered: true,
+        fetchSetting: {
+          pageField: 'page',
+          sizeField: 'limit',
+          listField: 'list',
+          totalField: 'totalCount',
+        },
+      });
+
+      function handleCreate(): void {
+        // openAddModal();
+        openAddModal(true, {});
+      }
+
+      onMounted(async () => {
+        await readAllApi();
+      });
+      async function handleDelete(record: Recordable) {
+        const data = await deleteNoticeApi({
+          id: record.id,
+        });
+        createMessage.success(t('common.optSuccess'));
+        reload();
+        console.log('data', data);
+      }
+
+      return {
+        registerTable,
+        createMessage,
+        t,
+        go,
+        uploadApi: uploadApi as any,
+        handleCreate,
+        reload,
+        registerAddModal,
+        openAddModal,
+        handleDelete,
+      };
+    },
+  });
+</script>

+ 197 - 0
src/views/notification/list.vue

@@ -0,0 +1,197 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable">
+      <template #toolbar>
+        <a-button type="primary" @click="handleCreate">新增</a-button>
+      </template>
+      <template #avatar="{ record }">
+        <Avatar :size="80" :src="record.avatar" />
+      </template>
+
+      <template #content="{ record }">
+        <div v-if="record.type === 0" v-html="record.content"></div>
+        <!-- <img v-if="record.type === 1" :src="record.content" /> -->
+        <TableImg
+          v-if="record.type === 1"
+          :size="200"
+          :simpleShow="true"
+          :imgList="[record.content]"
+        />
+      </template>
+
+      <template #periodTime="{ record }">
+        <span v-if="record.startTime?.length && record.endTime?.length">
+          <Time :value="record.startTime" mode="datetime" /> 到
+          <Time :value="record.endTime" mode="datetime" />
+        </span>
+        <span v-else> -- </span>
+      </template>
+
+      <template #createTime="{ record }">
+        <Time :value="record.createTime" mode="datetime" />
+      </template>
+
+      <template #action="{ record }">
+        <TableAction
+          :actions="[
+            {
+              label: '查看',
+              onClick: () => {
+                handleView(record);
+              },
+            },
+            {
+              color: 'error',
+              label: '删除',
+              popConfirm: {
+                title: '是否确认删除',
+                confirm: handleDelete.bind(null, record),
+              },
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+    <AddModal @register="registerAddModal" @ok="reload" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import {
+    BasicTable,
+    useTable,
+    BasicColumn,
+    FormProps,
+    TableAction,
+    TableImg,
+  } from '/@/components/Table';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { uploadApi } from '/@/api/sys/upload';
+  import { useModal } from '/@/components/Modal';
+  import { listApi, deleteNoticeApi } from '/@/api/notification/list';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useGo } from '/@/hooks/web/usePage';
+  import { Avatar } from 'ant-design-vue';
+  import { Time } from '/@/components/Time';
+  import AddModal from './addModal.vue';
+
+  export default defineComponent({
+    components: { BasicTable, TableAction, Avatar, Time, AddModal, TableImg },
+    setup() {
+      const { createMessage } = useMessage();
+      const go = useGo();
+      const { t } = useI18n();
+      const [registerAddModal, { openModal: openAddModal }] = useModal();
+      const columns: BasicColumn[] = [
+        {
+          title: '序列',
+          dataIndex: 'id',
+          fixed: 'left',
+          width: 60,
+        },
+        {
+          title: '标题',
+          dataIndex: 'title',
+          // sorter: true,
+          width: 160,
+        },
+        {
+          title: '内容',
+          dataIndex: 'content',
+          slots: { customRender: 'content' },
+          // sorter: true,
+          width: 160,
+        },
+        {
+          title: '持续时间',
+          dataIndex: 'periodTime',
+          slots: { customRender: 'periodTime' },
+          // sorter: true,
+          width: 120,
+        },
+        {
+          title: '发布时间',
+          dataIndex: 'createTime',
+          slots: { customRender: 'createTime' },
+          // sorter: true,
+          width: 120,
+        },
+        {
+          title: '操作',
+          dataIndex: '',
+          slots: { customRender: 'action' },
+          fixed: 'right',
+          width: 80,
+        },
+      ];
+
+      const searchForm: Partial<FormProps> = {
+        labelWidth: 100,
+        schemas: [
+          {
+            field: 'title',
+            label: '标题',
+            component: 'Input',
+            componentProps: {
+              maxLength: 100,
+            },
+            colProps: {
+              xl: 5,
+              xxl: 5,
+            },
+          },
+        ],
+      };
+
+      const [registerTable, { reload }] = useTable({
+        title: '消息列表',
+        api: listApi,
+        columns: columns,
+        useSearchForm: true,
+        formConfig: searchForm,
+        showTableSetting: true,
+        tableSetting: { fullScreen: true },
+        showIndexColumn: false,
+        rowKey: 'id',
+        pagination: { pageSize: 20 },
+        bordered: true,
+        fetchSetting: {
+          pageField: 'page',
+          sizeField: 'limit',
+          listField: 'list',
+          totalField: 'totalCount',
+        },
+      });
+
+      function handleCreate(): void {
+        // openAddModal();
+        openAddModal(true, { isView: false });
+      }
+      function handleView(record: Recordable): void {
+        openAddModal(true, { ...record, isView: true });
+      }
+      async function handleDelete(record: Recordable) {
+        const data = await deleteNoticeApi({
+          id: record.id,
+        });
+        createMessage.success(t('common.optSuccess'));
+        reload();
+        console.log('data', data);
+      }
+
+      return {
+        registerTable,
+        createMessage,
+        t,
+        go,
+        uploadApi: uploadApi as any,
+        handleCreate,
+        reload,
+        registerAddModal,
+        openAddModal,
+        handleDelete,
+        handleView,
+      };
+    },
+  });
+</script>

+ 5 - 0
src/views/notification/template.vue

@@ -0,0 +1,5 @@
+<template>
+  <div> 设备管理 </div>
+</template>
+
+<script lang="ts" setup></script>

+ 0 - 4
src/views/order/list.vue

@@ -302,10 +302,6 @@
           totalField: 'totalCount',
         },
         bordered: true,
-        sortFn: (sortInfo) => {
-          let order = sortInfo.order && sortInfo.order.replace('end', '');
-          return { ...sortInfo, sidx: sortInfo.field, order: order };
-        },
       });
 
       function renderOrderTypeLabel(type: number): string {

+ 116 - 116
src/views/product/addModal.vue

@@ -1,116 +1,116 @@
-<template>
-  <BasicModal
-    v-bind="$attrs"
-    @register="register"
-    @cancel="resetFields"
-    title="新 增"
-    @ok="handleOk"
-  >
-    <div class="pt-3px pr-3px">
-      <BasicForm @register="registerForm" :model="model" />
-    </div>
-  </BasicModal>
-</template>
-<script lang="ts">
-  import { defineComponent, ref } from 'vue';
-  import { saveItemApi } from '/@/api/product/category';
-  import { BasicModal, useModalInner } from '/@/components/Modal';
-  import { useMessage } from '/@/hooks/web/useMessage';
-  import { useI18n } from '/@/hooks/web/useI18n';
-  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
-  const schemas: FormSchema[] = [
-    {
-      field: 'name',
-      component: 'Input',
-      label: '商品属性',
-      colProps: {
-        span: 18,
-      },
-      required: true,
-      componentProps: {
-        maxLength: 15,
-      },
-    },
-    {
-      field: 'sortOrder',
-      component: 'InputNumber',
-      label: '排序',
-      defaultValue: 1,
-      rules: [
-        {
-          required: false,
-          // @ts-ignore
-          validator: async (rule, value) => {
-            if (value && (value < 0 || value > 1000)) {
-              return Promise.reject('请输入正确的排序');
-            }
-            return Promise.resolve();
-          },
-          trigger: 'change',
-        },
-      ],
-      colProps: {
-        span: 24,
-      },
-    },
-  ];
-  export default defineComponent({
-    components: { BasicModal, BasicForm },
-    props: {
-      userData: { type: Object },
-    },
-    emits: ['saveadd'],
-    setup(_, context) {
-      const modelRef = ref({});
-      const { createMessage } = useMessage();
-      const { t } = useI18n();
-      const [
-        registerForm,
-        {
-          // setFieldsValue,
-          validate,
-          resetFields,
-          // getFieldsValue,
-        },
-      ] = useForm({
-        labelWidth: 120,
-        schemas,
-        showActionButtonGroup: false,
-        actionColOptions: {
-          span: 24,
-        },
-      });
-
-      const [register, { closeModal }] = useModalInner((data) => {
-        data && onDataReceive(data);
-      });
-
-      function onDataReceive(data) {
-        console.log('Data Received', data);
-        // 方式1;
-        // setFieldsValue({
-        //   field2: data.data,
-        //   field1: data.info,
-        // });
-
-        // // 方式2
-        modelRef.value = { field2: data.data, field1: data.info };
-
-        // setProps({
-        //   model:{ field2: data.data, field1: data.info }
-        // })
-      }
-
-      async function handleOk() {
-        let data = await validate();
-        let res = await saveItemApi(data);
-        context && context.emit('saveadd', res);
-        createMessage.success(t('common.optSuccess'));
-        closeModal();
-        resetFields();
-      }
-
-      return { register, resetFields, schemas, registerForm, model: modelRef, handleOk, t };
-    },
-  });
-</script>
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    @cancel="resetFields"
+    title="新 增"
+    @ok="handleOk"
+  >
+    <div class="pt-3px pr-3px">
+      <BasicForm @register="registerForm" :model="model" />
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { saveItemApi } from '/@/api/product/category';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  const schemas: FormSchema[] = [
+    {
+      field: 'name',
+      component: 'Input',
+      label: '商品属性',
+      colProps: {
+        span: 18,
+      },
+      required: true,
+      componentProps: {
+        maxLength: 15,
+      },
+    },
+    {
+      field: 'sortOrder',
+      component: 'InputNumber',
+      label: '排序',
+      defaultValue: 1,
+      rules: [
+        {
+          required: false,
+          // @ts-ignore
+          validator: async (rule, value) => {
+            if (value && (value < 0 || value > 1000)) {
+              return Promise.reject('请输入正确的排序');
+            }
+            return Promise.resolve();
+          },
+          trigger: 'change',
+        },
+      ],
+      colProps: {
+        span: 24,
+      },
+    },
+  ];
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['register', 'saveadd'],
+    setup(_, context) {
+      const modelRef = ref({});
+      const { createMessage } = useMessage();
+      const { t } = useI18n();
+      const [
+        registerForm,
+        {
+          // setFieldsValue,
+          validate,
+          resetFields,
+          // getFieldsValue,
+        },
+      ] = useForm({
+        labelWidth: 120,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+
+      function onDataReceive(data) {
+        console.log('Data Received', data);
+        // 方式1;
+        // setFieldsValue({
+        //   field2: data.data,
+        //   field1: data.info,
+        // });
+
+        // // 方式2
+        modelRef.value = { field2: data.data, field1: data.info };
+
+        // setProps({
+        //   model:{ field2: data.data, field1: data.info }
+        // })
+      }
+
+      async function handleOk() {
+        let data = await validate();
+        let res = await saveItemApi(data);
+        context && context.emit('saveadd', res);
+        createMessage.success(t('common.optSuccess'));
+        closeModal();
+        resetFields();
+      }
+
+      return { register, resetFields, schemas, registerForm, model: modelRef, handleOk, t };
+    },
+  });
+</script>

+ 171 - 170
src/views/product/category.vue

@@ -1,170 +1,171 @@
-<template>
-  <div>
-    <BasicTable @register="registerTable">
-      <template #toolbar v-if="!getCheckRole('tourist')">
-        <a-button primary color="error" @click="handleCreate"> 新增商品分类</a-button>
-        <!-- <a-button ghost color="warning" @click="expandAll">展开全部</a-button>
-        <a-button type="primary" @click="collapseAll">折叠全部</a-button> -->
-      </template>
-      <template #action="{ record }">
-        <TableAction
-          :actions="[
-            {
-              label: '编辑',
-              onClick: handleEdit.bind(null, record),
-            },
-            {
-              color: 'error',
-              label: '删除',
-              popConfirm: {
-                title: '是否确认删除',
-                confirm: handleDelete.bind(null, record),
-              },
-            },
-          ]"
-        />
-      </template>
-    </BasicTable>
-    <AddCategoryModal @register="registeraddCategoryModal" @update="reload" />
-    <EditCategoryModal @register="registerEditCategoryModal" @update="reload" />
-  </div>
-</template>
-
-<script lang="ts">
-  import { defineComponent, h, nextTick } from 'vue';
-  import { BasicTable, useTable, BasicColumn, TableAction } from '/@/components/Table';
-  import { categoryApi, deleteCategoryApi } from '/@/api/product/category';
-  import { Tag } from 'ant-design-vue';
-
-  import { useI18n } from '/@/hooks/web/useI18n';
-  import { useMessage } from '/@/hooks/web/useMessage';
-  import { makeTree } from '/@/utils/treeUtils';
-  import { useUserStore } from '/@/store/modules/user';
-
-  import AddCategoryModal from './addCategoryModal.vue';
-  import EditCategoryModal from './editCategoryModal.vue';
-  import { useModal } from '/@/components/Modal';
-
-  export default defineComponent({
-    components: { BasicTable, TableAction, AddCategoryModal, EditCategoryModal },
-    setup() {
-      const { createMessage } = useMessage();
-      const userStore = useUserStore();
-      const { getCheckRole } = userStore;
-      const { t } = useI18n();
-
-      const columns: BasicColumn[] = [
-        {
-          title: 'ID',
-          dataIndex: 'id',
-          fixed: 'left',
-          width: 100,
-        },
-        {
-          title: '分类名称',
-          dataIndex: 'name',
-          width: 200,
-          align: 'left',
-        },
-        {
-          title: '排序',
-          dataIndex: 'sortOrder',
-          width: 50,
-        },
-        {
-          title: '级数',
-          dataIndex: 'level',
-          width: 50,
-        },
-        {
-          title: '状态',
-          dataIndex: 'isShow',
-          width: 80,
-          customRender: ({ record }) => {
-            const status = record.isShow;
-            const enable = status === 1;
-            const color = enable ? 'green' : 'red';
-            const text = enable ? '显示' : '不显示';
-            return h(Tag, { color: color }, () => text);
-          },
-        },
-        // {
-        //   title: '创建时间',
-        //   dataIndex: 'createTime',
-        //   width: 180,
-        // },
-      ];
-      const [registeraddCategoryModal, { openModal: openAddCategoryModal }] = useModal();
-      const [registerEditCategoryModal, { openModal: openEditategoryModal }] = useModal();
-      const [registerTable, { reload, expandAll, collapseAll }] = useTable({
-        title: '商品分类',
-        api: categoryApi,
-        columns: columns,
-        isTreeTable: true,
-        useSearchForm: true,
-        formConfig: {
-          labelWidth: 100,
-          schemas: [
-            {
-              field: 'name',
-              label: '分类名称',
-              component: 'Input',
-              componentProps: {
-                placeholder: '请输入分类名称',
-                maxLength: 100,
-              },
-              colProps: {
-                xl: 6,
-                xxl: 6,
-              },
-            },
-          ],
-        },
-        showTableSetting: true,
-        tableSetting: { fullScreen: true },
-        showIndexColumn: true,
-        pagination: false,
-        rowKey: 'id',
-        bordered: true,
-        actionColumn: {
-          ifShow: !getCheckRole('tourist'),
-          width: 100,
-          title: '操作',
-          dataIndex: 'action',
-          slots: { customRender: 'action' },
-          fixed: undefined,
-        },
-        afterFetch(data) {
-          const treeData = makeTree(data);
-          return treeData;
-        },
-      });
-
-      function handleCreate(record: Recordable) {
-        openAddCategoryModal(true, record);
-      }
-      function handleEdit(record: Recordable) {
-        openEditategoryModal(true, record);
-      }
-      async function handleDelete(record: Recordable) {
-        await deleteCategoryApi([record.id]);
-        createMessage.success(t('common.optSuccess'));
-        nextTick(reload);
-      }
-      return {
-        registerTable,
-        createMessage,
-        t,
-        handleCreate,
-        handleEdit,
-        handleDelete,
-        expandAll,
-        collapseAll,
-        registeraddCategoryModal,
-        registerEditCategoryModal,
-        reload,
-        getCheckRole,
-      };
-    },
-  });
-</script>
+<template>
+  <div>
+    <BasicTable @register="registerTable">
+      <template #toolbar v-if="!getCheckRole('tourist')">
+        <a-button primary color="error" @click="handleCreate"> 新增商品分类</a-button>
+        <!-- <a-button ghost color="warning" @click="expandAll">展开全部</a-button>
+        <a-button type="primary" @click="collapseAll">折叠全部</a-button> -->
+      </template>
+      <template #action="{ record }">
+        <TableAction
+          :actions="[
+            {
+              label: '编辑',
+              onClick: handleEdit.bind(null, record),
+            },
+            {
+              color: 'error',
+              label: '删除',
+              popConfirm: {
+                title: '是否确认删除',
+                confirm: handleDelete.bind(null, record),
+              },
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+    <AddCategoryModal @register="registeraddCategoryModal" @update="reload" />
+    <EditCategoryModal @register="registerEditCategoryModal" @update="reload" />
+  </div>
+</template>
+
+<script lang="ts">
+  import { defineComponent, h, nextTick } from 'vue';
+  import { BasicTable, useTable, BasicColumn, TableAction } from '/@/components/Table';
+  import { categoryApi, deleteCategoryApi } from '/@/api/product/category';
+  import { Tag } from 'ant-design-vue';
+
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { makeTree } from '/@/utils/treeUtils';
+  import { useUserStore } from '/@/store/modules/user';
+
+  import AddCategoryModal from './addCategoryModal.vue';
+  import EditCategoryModal from './editCategoryModal.vue';
+  import { useModal } from '/@/components/Modal';
+
+  export default defineComponent({
+    components: { BasicTable, TableAction, AddCategoryModal, EditCategoryModal },
+    emits: ['register'],
+    setup() {
+      const { createMessage } = useMessage();
+      const userStore = useUserStore();
+      const { getCheckRole } = userStore;
+      const { t } = useI18n();
+
+      const columns: BasicColumn[] = [
+        {
+          title: 'ID',
+          dataIndex: 'id',
+          fixed: 'left',
+          width: 100,
+        },
+        {
+          title: '分类名称',
+          dataIndex: 'name',
+          width: 200,
+          align: 'left',
+        },
+        {
+          title: '排序',
+          dataIndex: 'sortOrder',
+          width: 50,
+        },
+        {
+          title: '级数',
+          dataIndex: 'level',
+          width: 50,
+        },
+        {
+          title: '状态',
+          dataIndex: 'isShow',
+          width: 80,
+          customRender: ({ record }) => {
+            const status = record.isShow;
+            const enable = status === 1;
+            const color = enable ? 'green' : 'red';
+            const text = enable ? '显示' : '不显示';
+            return h(Tag, { color: color }, () => text);
+          },
+        },
+        // {
+        //   title: '创建时间',
+        //   dataIndex: 'createTime',
+        //   width: 180,
+        // },
+      ];
+      const [registeraddCategoryModal, { openModal: openAddCategoryModal }] = useModal();
+      const [registerEditCategoryModal, { openModal: openEditategoryModal }] = useModal();
+      const [registerTable, { reload, expandAll, collapseAll }] = useTable({
+        title: '商品分类',
+        api: categoryApi,
+        columns: columns,
+        isTreeTable: true,
+        useSearchForm: true,
+        formConfig: {
+          labelWidth: 100,
+          schemas: [
+            {
+              field: 'name',
+              label: '分类名称',
+              component: 'Input',
+              componentProps: {
+                placeholder: '请输入分类名称',
+                maxLength: 100,
+              },
+              colProps: {
+                xl: 6,
+                xxl: 6,
+              },
+            },
+          ],
+        },
+        showTableSetting: true,
+        tableSetting: { fullScreen: true },
+        showIndexColumn: true,
+        pagination: false,
+        rowKey: 'id',
+        bordered: true,
+        actionColumn: {
+          ifShow: !getCheckRole('tourist'),
+          width: 100,
+          title: '操作',
+          dataIndex: 'action',
+          slots: { customRender: 'action' },
+          fixed: undefined,
+        },
+        afterFetch(data) {
+          const treeData = makeTree(data);
+          return treeData;
+        },
+      });
+
+      function handleCreate(record: Recordable) {
+        openAddCategoryModal(true, record);
+      }
+      function handleEdit(record: Recordable) {
+        openEditategoryModal(true, record);
+      }
+      async function handleDelete(record: Recordable) {
+        await deleteCategoryApi([record.id]);
+        createMessage.success(t('common.optSuccess'));
+        nextTick(reload);
+      }
+      return {
+        registerTable,
+        createMessage,
+        t,
+        handleCreate,
+        handleEdit,
+        handleDelete,
+        expandAll,
+        collapseAll,
+        registeraddCategoryModal,
+        registerEditCategoryModal,
+        reload,
+        getCheckRole,
+      };
+    },
+  });
+</script>

+ 3 - 0
src/views/product/drawer.data.ts

@@ -21,6 +21,7 @@ export const formSchema: FormSchema[] = [
         return treeData;
       },
       showSearch: true,
+      optionFilterProp: 'label',
       filterTreeNode: (searchVal, treeNode) => {
         return treeNode.title.includes(searchVal);
       },
@@ -70,6 +71,8 @@ export const formSchema: FormSchema[] = [
       labelField: 'name',
       valueField: 'id',
       immediate: true,
+      showSearch: true,
+      optionFilterProp: 'label',
       params: {
         page: 1,
         limit: 1000,

+ 2 - 1
src/views/product/goodsSpecs.vue

@@ -73,7 +73,7 @@
         default: () => {},
       },
     },
-    emits: ['update', 'editdata'],
+    emits: ['update', 'editdata', 'register'],
     setup(props, { emit }) {
       let ggList = reactive({
         goodsNumber: 1,
@@ -109,6 +109,7 @@
                 value: 'id',
               },
               showSearch: true,
+              optionFilterProp: 'label',
               filterTreeNode: (searchVal, treeNode) => {
                 return treeNode.title.includes(searchVal);
               },

+ 1 - 0
src/views/product/list.data.ts

@@ -60,6 +60,7 @@ export const columns: BasicColumn[] = [
     title: '购买链接',
     dataIndex: 'realShopUrl',
     slots: { customRender: 'realShopUrl' },
+    ellipsis: true,
     width: 150,
   },
   {

+ 1 - 4
src/views/product/list.vue

@@ -84,6 +84,7 @@
 
   export default defineComponent({
     components: { BasicTable, TableAction, ProductDrawer, Time, PopConfirmButton },
+    emits: ['register'],
     setup() {
       const { createMessage } = useMessage();
       const go = useGo();
@@ -110,10 +111,6 @@
           listField: 'list',
           totalField: 'totalCount',
         },
-        sortFn: (sortInfo) => {
-          let order = sortInfo.order && sortInfo.order.replace('end', '');
-          return { ...sortInfo, sidx: sortInfo.field, order: order };
-        },
       });
 
       function handleEdit(record: Recordable) {

+ 347 - 347
src/views/product/productDrawer.vue

@@ -1,347 +1,347 @@
-<template>
-  <BasicDrawer
-    v-bind="$attrs"
-    @register="registerDrawer"
-    :isDetail="true"
-    :showDetailBack="false"
-    showFooter
-    :title="getTitle"
-    @ok="handleSubmit"
-  >
-    <BasicForm @register="registerForm">
-      <template #goodsgg>
-        <goodsSpecs :key="editData.id" :editdata="editData" @update="setEditData" ref="goodsRef" />
-      </template>
-      <template #detailed>
-        <table class="detailed">
-          <thead>
-            <tr style="" v-show="editData.productList.length > 0">
-              <th style="min-width: 60px">{{ editData.categoryName }}</th>
-              <th>库存</th>
-              <th>规格编码</th>
-              <th>市场价</th>
-              <th>销售价</th>
-            </tr>
-          </thead>
-          <tbody>
-            <tr v-for="item in editData.productList" :key="item.uuidLink">
-              <template v-if="editData.productList">
-                <td
-                  :title="item.name"
-                  style="
-                    max-width: 120px;
-                    overflow: hidden;
-                    white-space: nowrap;
-                    text-overflow: ellipsis;
-                  "
-                  >{{ item.name }}</td
-                >
-                <td
-                  ><InputNumber
-                    type="number"
-                    :maxlength="8"
-                    :max="100000000"
-                    :min="0"
-                    v-model:value="item.goodsNumber"
-                /></td>
-                <td
-                  ><Input
-                    type="text"
-                    :maxlength="8"
-                    :max="100000000"
-                    :min="0"
-                    v-model:value="item.goodsSn"
-                /></td>
-                <td
-                  ><InputNumber
-                    type="number"
-                    :maxlength="8"
-                    :max="100000000"
-                    :min="0"
-                    v-model:value="item.marketPrice"
-                /></td>
-                <td
-                  ><InputNumber
-                    type="number"
-                    :maxlength="8"
-                    :max="100000000"
-                    :min="0"
-                    v-model:value="item.retailPrice"
-                /></td>
-              </template>
-            </tr>
-          </tbody>
-        </table>
-      </template>
-      <!-- <template #formItem>
-        <BasicForm @register="itemRegister" @submit="handleSubmit">
-          <template #add="{ field }">
-            <a-button v-if="Number(field) === 0" @click="add">+</a-button>
-            <a-button v-if="field > 0" @click="del.bind(null, field)">-</a-button>
-          </template>
-        </BasicForm>
-      </template> -->
-    </BasicForm>
-  </BasicDrawer>
-</template>
-<script lang="ts">
-  import { defineComponent, ref, computed, unref } from 'vue';
-  import { BasicForm, useForm } from '/@/components/Form/index';
-  import { Input, InputNumber } from 'ant-design-vue';
-  import { formSchema } from './drawer.data';
-  import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
-  import goodsSpecs from './goodsSpecs.vue';
-  import { InfoApi, UpdateSaleApi, SaveSaleApi } from '/@/api/product/list';
-  import { useMessage } from '/@/hooks/web/useMessage';
-  import { useI18n } from '/@/hooks/web/useI18n';
-  import { useUserStore } from '/@/store/modules/user';
-  // import { getMenuList } from '/@/api/system/system';
-  import { makeTree, TreeNode } from '/@/utils/treeUtils';
-  import { categoryApi } from '/@/api/product/category';
-
-  interface EditDataType {
-    categoryName: string;
-    productList: ProductListItem[];
-    goodsSpecificationList?: goodsSpecificationList[];
-    id: number;
-  }
-  interface goodsSpecificationList {
-    brandName: string;
-    goodsId?: number;
-    goodsName: string;
-    id?: number;
-    picUrl: string;
-    specificationId?: number;
-    specificationName: string;
-    uuid: string;
-    val?: string | number | [];
-    value: string;
-  }
-  interface ProductListItem {
-    uuidLink: string;
-    goodsNumber?: string;
-    goodsSn: string;
-    retailPrice: string;
-    marketPrice: string;
-    name?: string;
-  }
-
-  export default defineComponent({
-    name: 'ProductDrawer',
-    components: { BasicDrawer, BasicForm, goodsSpecs, Input, InputNumber },
-    emits: ['reload', 'register'],
-    setup(_, { emit }) {
-      const isUpdate = ref(true);
-      const goodsRef = ref();
-      const { createMessage } = useMessage();
-      const userStore = useUserStore();
-      const userinfo = computed(() => userStore.getUserInfo);
-      const { t } = useI18n();
-
-      let editData = ref<EditDataType>({
-        categoryName: '',
-        productList: [],
-        id: 0,
-      });
-
-      const [registerForm, { resetFields, updateSchema, setFieldsValue, validate }] = useForm({
-        labelWidth: 120,
-        schemas: formSchema,
-        showActionButtonGroup: false,
-        baseColProps: { lg: 18, md: 18, offset: 1 },
-      });
-      const [itemRegister, {}] = useForm({
-        // formItemValue :validate appendSchemaByField, removeSchemaByFiled,
-        schemas: [
-          {
-            field: 'goodsId',
-            label: '规格名',
-            component: 'ApiTreeSelect',
-            componentProps: {
-              api: async (params) => {
-                const res = (await categoryApi(params)) as any as TreeNode[];
-                const treeData = makeTree(res);
-                return treeData;
-              },
-              maxLength: 15,
-              fieldNames: {
-                label: 'name',
-                key: 'id',
-                value: 'id',
-              },
-            },
-          },
-          {
-            field: 'goodsSn',
-            component: 'Input',
-            label: '字段0',
-            colProps: {
-              span: 8,
-            },
-            required: true,
-          },
-          {
-            field: '0',
-            component: 'Input',
-            label: ' ',
-            colProps: {
-              span: 8,
-            },
-            slot: 'add',
-          },
-        ],
-        labelWidth: 100,
-        actionColOptions: { span: 24 },
-      });
-      const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
-        resetFields();
-        setDrawerProps({ confirmLoading: false });
-        isUpdate.value = !!data?.isUpdate;
-        editData.value = {
-          categoryName: '',
-          productList: [],
-          id: 0,
-        };
-        updateSchema({
-          field: 'brandId',
-          componentProps: {
-            disabled: unref(isUpdate) ? true : false,
-          },
-        });
-        if (unref(isUpdate)) {
-          let res = await InfoApi(data.record.id);
-          editData.value = {
-            ...res,
-            productList: res.productList.map((ele) => {
-              console.log('editData', ele, res.goodsSpecificationList);
-              ele.name = res.goodsSpecificationList.find(
-                (element) => element.uuid == ele.uuidLink,
-              )?.specificationName;
-              return ele;
-            }),
-          };
-          setFieldsValue({
-            ...res,
-            isOnSale: res.isOnSale === 1,
-            listPicUrl:
-              res.listPicUrl && res.listPicUrl.split('#$#').map((ele) => ele.split('?')[0]),
-            primaryPicUrl:
-              res.primaryPicUrl && res.primaryPicUrl.split('#$#').map((ele) => ele.split('?')[0]),
-            // primaryPicUrl: res.primaryPicUrl && [encodeURI(res.primaryPicUrl.split('?')[0])],
-            steamRoom: data.record?.steamRoom?.name,
-          });
-        }
-      });
-      function setEditData(value) {
-        let productList = editData.value.productList || [];
-        value.productList.map((ele) => {
-          if (!productList.some((element) => ele.uuidLink == element.uuidLink)) {
-            //判断新增
-            productList.push({
-              uuidLink: ele.uuidLink,
-              name: ele.name,
-              goodsNumber: '',
-              goodsSn: '',
-              marketPrice: '',
-              retailPrice: '',
-            });
-          } else {
-            //本来就有的 修改赋值
-            productList.forEach((setEle) => {
-              if (ele.uuidLink == setEle.uuidLink) {
-                setEle.name = ele.name;
-              }
-            });
-          }
-        });
-        productList = productList.filter((ele) =>
-          value.productList.some((element) => element.uuidLink == ele.uuidLink),
-        );
-        console.log('setEditData', productList);
-        editData.value = {
-          ...editData.value,
-          ...value,
-          productList,
-        };
-        setFieldsValue({
-          productList: productList,
-        });
-      }
-      const getTitle = computed(() => (!unref(isUpdate) ? '新增商品' : '编辑商品'));
-
-      async function handleSubmit() {
-        const { companyId, id } = userinfo.value;
-        try {
-          const values = await validate();
-          console.log('values', values);
-          let apiData = {
-            ...editData.value,
-            ...values,
-            retailPrice:
-              editData.value.productList &&
-              Math.min(...editData.value.productList.map((ele) => Number(ele.retailPrice))),
-            marketPrice:
-              editData.value.productList &&
-              Math.min(...editData.value.productList.map((ele) => Number(ele.marketPrice))),
-            isOnSale: values.isOnSale ? 1 : 0,
-          };
-
-          let requresApi = isUpdate.value ? UpdateSaleApi : SaveSaleApi;
-          if (!isUpdate.value) {
-            apiData.createUserDeptId = companyId;
-            apiData.createUserId = id;
-          }
-          let subData = await requresApi({
-            ...apiData,
-            primaryPicUrl: apiData.primaryPicUrl.join('#$#'),
-            listPicUrl: apiData.listPicUrl.join('#$#'),
-          });
-          console.log('setFieldsValue', subData);
-          setDrawerProps({ confirmLoading: true });
-          // TODO custom api
-          console.log(values);
-          createMessage.success(t('common.optSuccess'));
-          closeDrawer();
-          emit('reload');
-        } finally {
-          setDrawerProps({ confirmLoading: false });
-        }
-      }
-
-      return {
-        goodsRef,
-        registerDrawer,
-        registerForm,
-        itemRegister,
-        getTitle,
-        editData,
-        handleSubmit,
-        setEditData,
-      };
-    },
-  });
-</script>
-
-<style scoped lang="less">
-  :deep(.detailed) {
-    .title {
-      display: flex;
-      span,
-      input,
-      div {
-        flex-grow: 1;
-      }
-      input {
-        display: inline-block;
-      }
-      .name {
-        flex-grow: 2;
-      }
-    }
-    tr {
-      th {
-        font-weight: 500;
-      }
-    }
-  }
-</style>
+<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    @register="registerDrawer"
+    :isDetail="true"
+    :showDetailBack="false"
+    showFooter
+    :title="getTitle"
+    @ok="handleSubmit"
+  >
+    <BasicForm @register="registerForm">
+      <template #goodsgg>
+        <goodsSpecs :key="editData.id" :editdata="editData" @update="setEditData" ref="goodsRef" />
+      </template>
+      <template #detailed>
+        <table class="detailed">
+          <thead>
+            <tr style="" v-show="editData.productList.length > 0">
+              <th style="min-width: 60px">{{ editData.categoryName }}</th>
+              <th>库存</th>
+              <th>规格编码</th>
+              <th>市场价</th>
+              <th>销售价</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr v-for="item in editData.productList" :key="item.uuidLink">
+              <template v-if="editData.productList">
+                <td
+                  :title="item.name"
+                  style="
+                    max-width: 120px;
+                    overflow: hidden;
+                    white-space: nowrap;
+                    text-overflow: ellipsis;
+                  "
+                  >{{ item.name }}</td
+                >
+                <td
+                  ><InputNumber
+                    type="number"
+                    :maxlength="8"
+                    :max="100000000"
+                    :min="0"
+                    v-model:value="item.goodsNumber"
+                /></td>
+                <td
+                  ><Input
+                    type="text"
+                    :maxlength="8"
+                    :max="100000000"
+                    :min="0"
+                    v-model:value="item.goodsSn"
+                /></td>
+                <td
+                  ><InputNumber
+                    type="number"
+                    :maxlength="8"
+                    :max="100000000"
+                    :min="0"
+                    v-model:value="item.marketPrice"
+                /></td>
+                <td
+                  ><InputNumber
+                    type="number"
+                    :maxlength="8"
+                    :max="100000000"
+                    :min="0"
+                    v-model:value="item.retailPrice"
+                /></td>
+              </template>
+            </tr>
+          </tbody>
+        </table>
+      </template>
+      <!-- <template #formItem>
+        <BasicForm @register="itemRegister" @submit="handleSubmit">
+          <template #add="{ field }">
+            <a-button v-if="Number(field) === 0" @click="add">+</a-button>
+            <a-button v-if="field > 0" @click="del.bind(null, field)">-</a-button>
+          </template>
+        </BasicForm>
+      </template> -->
+    </BasicForm>
+  </BasicDrawer>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, computed, unref } from 'vue';
+  import { BasicForm, useForm } from '/@/components/Form/index';
+  import { Input, InputNumber } from 'ant-design-vue';
+  import { formSchema } from './drawer.data';
+  import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
+  import goodsSpecs from './goodsSpecs.vue';
+  import { InfoApi, UpdateSaleApi, SaveSaleApi } from '/@/api/product/list';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useUserStore } from '/@/store/modules/user';
+  // import { getMenuList } from '/@/api/system/system';
+  import { makeTree, TreeNode } from '/@/utils/treeUtils';
+  import { categoryApi } from '/@/api/product/category';
+
+  interface EditDataType {
+    categoryName: string;
+    productList: ProductListItem[];
+    goodsSpecificationList?: goodsSpecificationList[];
+    id: number;
+  }
+  interface goodsSpecificationList {
+    brandName: string;
+    goodsId?: number;
+    goodsName: string;
+    id?: number;
+    picUrl: string;
+    specificationId?: number;
+    specificationName: string;
+    uuid: string;
+    val?: string | number | [];
+    value: string;
+  }
+  interface ProductListItem {
+    uuidLink: string;
+    goodsNumber?: string;
+    goodsSn: string;
+    retailPrice: string;
+    marketPrice: string;
+    name?: string;
+  }
+
+  export default defineComponent({
+    name: 'ProductDrawer',
+    components: { BasicDrawer, BasicForm, goodsSpecs, Input, InputNumber },
+    emits: ['reload', 'register'],
+    setup(_, { emit }) {
+      const isUpdate = ref(true);
+      const goodsRef = ref();
+      const { createMessage } = useMessage();
+      const userStore = useUserStore();
+      const userinfo = computed(() => userStore.getUserInfo);
+      const { t } = useI18n();
+
+      let editData = ref<EditDataType>({
+        categoryName: '',
+        productList: [],
+        id: 0,
+      });
+
+      const [registerForm, { resetFields, updateSchema, setFieldsValue, validate }] = useForm({
+        labelWidth: 120,
+        schemas: formSchema,
+        showActionButtonGroup: false,
+        baseColProps: { lg: 18, md: 18, offset: 1 },
+      });
+      const [itemRegister, {}] = useForm({
+        // formItemValue :validate appendSchemaByField, removeSchemaByFiled,
+        schemas: [
+          {
+            field: 'goodsId',
+            label: '规格名',
+            component: 'ApiTreeSelect',
+            componentProps: {
+              api: async (params) => {
+                const res = (await categoryApi(params)) as any as TreeNode[];
+                const treeData = makeTree(res);
+                return treeData;
+              },
+              maxLength: 15,
+              fieldNames: {
+                label: 'name',
+                key: 'id',
+                value: 'id',
+              },
+            },
+          },
+          {
+            field: 'goodsSn',
+            component: 'Input',
+            label: '字段0',
+            colProps: {
+              span: 8,
+            },
+            required: true,
+          },
+          {
+            field: '0',
+            component: 'Input',
+            label: ' ',
+            colProps: {
+              span: 8,
+            },
+            slot: 'add',
+          },
+        ],
+        labelWidth: 100,
+        actionColOptions: { span: 24 },
+      });
+      const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
+        resetFields();
+        setDrawerProps({ confirmLoading: false });
+        isUpdate.value = !!data?.isUpdate;
+        editData.value = {
+          categoryName: '',
+          productList: [],
+          id: 0,
+        };
+        updateSchema({
+          field: 'brandId',
+          componentProps: {
+            disabled: unref(isUpdate) ? true : false,
+          },
+        });
+        if (unref(isUpdate)) {
+          let res = await InfoApi(data.record.id);
+          editData.value = {
+            ...res,
+            productList: res.productList.map((ele) => {
+              console.log('editData', ele, res.goodsSpecificationList);
+              ele.name = res.goodsSpecificationList.find(
+                (element) => element.uuid == ele.uuidLink,
+              )?.specificationName;
+              return ele;
+            }),
+          };
+          setFieldsValue({
+            ...res,
+            isOnSale: res.isOnSale === 1,
+            listPicUrl:
+              res.listPicUrl && res.listPicUrl.split('#$#').map((ele) => ele.split('?')[0]),
+            primaryPicUrl:
+              res.primaryPicUrl && res.primaryPicUrl.split('#$#').map((ele) => ele.split('?')[0]),
+            // primaryPicUrl: res.primaryPicUrl && [encodeURI(res.primaryPicUrl.split('?')[0])],
+            steamRoom: data.record?.steamRoom?.name,
+          });
+        }
+      });
+      function setEditData(value) {
+        let productList = editData.value.productList || [];
+        value.productList.map((ele) => {
+          if (!productList.some((element) => ele.uuidLink == element.uuidLink)) {
+            //判断新增
+            productList.push({
+              uuidLink: ele.uuidLink,
+              name: ele.name,
+              goodsNumber: '',
+              goodsSn: '',
+              marketPrice: '',
+              retailPrice: '',
+            });
+          } else {
+            //本来就有的 修改赋值
+            productList.forEach((setEle) => {
+              if (ele.uuidLink == setEle.uuidLink) {
+                setEle.name = ele.name;
+              }
+            });
+          }
+        });
+        productList = productList.filter((ele) =>
+          value.productList.some((element) => element.uuidLink == ele.uuidLink),
+        );
+        console.log('setEditData', productList);
+        editData.value = {
+          ...editData.value,
+          ...value,
+          productList,
+        };
+        setFieldsValue({
+          productList: productList,
+        });
+      }
+      const getTitle = computed(() => (!unref(isUpdate) ? '新增商品' : '编辑商品'));
+
+      async function handleSubmit() {
+        const { companyId, id } = userinfo.value;
+        try {
+          const values = await validate();
+          console.log('values', values);
+          let apiData = {
+            ...editData.value,
+            ...values,
+            retailPrice:
+              editData.value.productList &&
+              Math.min(...editData.value.productList.map((ele) => Number(ele.retailPrice))),
+            marketPrice:
+              editData.value.productList &&
+              Math.min(...editData.value.productList.map((ele) => Number(ele.marketPrice))),
+            isOnSale: values.isOnSale ? 1 : 0,
+          };
+
+          let requresApi = isUpdate.value ? UpdateSaleApi : SaveSaleApi;
+          if (!isUpdate.value) {
+            apiData.createUserDeptId = companyId;
+            apiData.createUserId = id;
+          }
+          let subData = await requresApi({
+            ...apiData,
+            primaryPicUrl: apiData.primaryPicUrl.join('#$#'),
+            listPicUrl: apiData.listPicUrl.join('#$#'),
+          });
+          console.log('setFieldsValue', subData);
+          setDrawerProps({ confirmLoading: true });
+          // TODO custom api
+          console.log(values);
+          createMessage.success(t('common.optSuccess'));
+          closeDrawer();
+          emit('reload');
+        } finally {
+          setDrawerProps({ confirmLoading: false });
+        }
+      }
+
+      return {
+        goodsRef,
+        registerDrawer,
+        registerForm,
+        itemRegister,
+        getTitle,
+        editData,
+        handleSubmit,
+        setEditData,
+      };
+    },
+  });
+</script>
+
+<style scoped lang="less">
+  :deep(.detailed) {
+    .title {
+      display: flex;
+      span,
+      input,
+      div {
+        flex-grow: 1;
+      }
+      input {
+        display: inline-block;
+      }
+      .name {
+        flex-grow: 2;
+      }
+    }
+    tr {
+      th {
+        font-weight: 500;
+      }
+    }
+  }
+</style>

+ 225 - 224
src/views/product/ref.vue

@@ -1,224 +1,225 @@
-<template>
-  <div>
-    <BasicTable @register="registerTable">
-      <template #toolbar>
-        <a-button v-show="!getCheckRole('tourist')" type="primary" @click="handleCreate">
-          新增商品属性</a-button
-        >
-      </template>
-      <template #action="{ record, column }">
-        <TableAction :actions="createActions(record, column)" />
-      </template>
-    </BasicTable>
-    <addModal @register="registerModal" @saveadd="handleSaveAdd" />
-  </div>
-</template>
-
-<script lang="ts">
-  import { defineComponent, ref } from 'vue'; // h
-  import {
-    BasicTable,
-    useTable,
-    BasicColumn,
-    TableAction,
-    EditRecordRow,
-    ActionItem,
-  } from '/@/components/Table';
-  import { attributeListApi, attributeDeleteApi, updateItemApi } from '/@/api/product/category';
-  import { cloneDeep } from 'lodash-es';
-  // import { Tag } from 'ant-design-vue';
-  import { useI18n } from '/@/hooks/web/useI18n';
-  import { useModal } from '/@/components/Modal';
-  import addModal from './addModal.vue';
-  import { useMessage } from '/@/hooks/web/useMessage';
-  import { useUserStore } from '/@/store/modules/user';
-
-  export default defineComponent({
-    components: { BasicTable, TableAction, addModal },
-    setup() {
-      const { createConfirm, createMessage: msg } = useMessage();
-      const [registerModal, { openModal }] = useModal();
-      const currentEditKeyRef = ref('');
-      const userStore = useUserStore();
-      const { getCheckRole } = userStore;
-      const { t } = useI18n();
-      console.log('registerModal', registerModal);
-      const columns: BasicColumn[] = [
-        {
-          title: '商品属性',
-          editRow: true,
-          editRule: async (text) => {
-            if (!text) {
-              return '请输入商品属性';
-            }
-            if (text && text.length > 15) {
-              return t('routes.corporation.maxlength');
-            }
-            return '';
-          },
-          dataIndex: 'name',
-          fixed: 'left',
-          width: 100,
-        },
-        {
-          title: '排序',
-          dataIndex: 'sortOrder',
-          editComponent: 'InputNumber',
-          editRule: async (text) => {
-            if (text == null || text < 0) {
-              return '请输入正确排序';
-            }
-            return '';
-          },
-          editRow: true,
-          width: 50,
-        },
-      ];
-
-      const [registerTable, { expandAll, reload, collapseAll }] = useTable({
-        title: '商品属性',
-        api: attributeListApi,
-        columns: columns,
-        useSearchForm: true,
-        showIndexColumn: false,
-        // rowSelection: { type: 'checkbox' },
-        rowKey: 'id',
-        bordered: true,
-        showIndexColumn: true,
-        fetchSetting: {
-          pageField: 'page',
-          sizeField: 'limit',
-          listField: 'list',
-          totalField: 'totalCount',
-        },
-        formConfig: {
-          labelWidth: 66,
-          schemas: [
-            {
-              field: `name`,
-              label: `商品属性`,
-              component: 'Input',
-              colProps: {
-                xl: 6,
-                xxl: 6,
-              },
-              componentProps: {
-                maxLength: 15,
-              },
-            },
-          ],
-        },
-        actionColumn: {
-          ifShow: !getCheckRole('tourist'),
-          width: 160,
-          title: '操作',
-          dataIndex: 'action',
-          slots: { customRender: 'action' },
-        },
-      });
-      async function handleCreate() {
-        openModal(true);
-      }
-      function handleEdit(record: EditRecordRow) {
-        currentEditKeyRef.value = record.key;
-        record.onEdit?.(true);
-      }
-      function handleDelete(record) {
-        createConfirm({
-          iconType: 'warning',
-          title: '警告',
-          content: `此操作将对${record.name}进行删除, 是否继续?`,
-          onOk: async () => {
-            let res = await attributeDeleteApi(record.id);
-            console.log('res', res);
-            reload();
-          },
-        });
-      }
-      function handleCancel(record: EditRecordRow) {
-        currentEditKeyRef.value = '';
-        record.onEdit?.(false, false);
-      }
-      async function handleSaveAdd() {
-        console.log('handleSaveAdd');
-        reload();
-      }
-
-      async function handleSave(record: EditRecordRow) {
-        // 校验
-        msg.loading({ content: '正在保存...', duration: 0, key: 'saving' });
-        const valid = await record.onValid?.();
-        console.log('ref', valid);
-        if (valid) {
-          try {
-            const data = cloneDeep(record.editValueRefs);
-            console.log(data);
-            //TODO 此处将数据提交给服务器保存
-            // ...
-            updateItemApi({
-              id: record.id,
-              ...data,
-            });
-
-            // 保存之后提交编辑状态
-            const pass = await record.onEdit?.(false, true);
-            if (pass) {
-              currentEditKeyRef.value = '';
-            }
-            msg.success({ content: '数据已保存', key: 'saving' });
-            reload();
-          } catch (error) {
-            msg.error({ content: '保存失败', key: 'saving' });
-          }
-        } else {
-          msg.error({ content: '请填写正确的数据', key: 'saving' });
-        }
-      }
-      function createActions(record: EditRecordRow, column: BasicColumn): ActionItem[] {
-        // console.log('editable', record);
-        if (!record.editable) {
-          return [
-            {
-              label: '编辑',
-              disabled: currentEditKeyRef.value ? currentEditKeyRef.value !== record.key : false,
-              onClick: handleEdit.bind(null, record),
-            },
-            {
-              label: '删除',
-              color: 'error',
-              disabled: currentEditKeyRef.value ? currentEditKeyRef.value !== record.key : false,
-              onClick: handleDelete.bind(null, record),
-            },
-          ];
-        }
-        return [
-          {
-            label: '保存',
-            onClick: handleSave.bind(null, record, column),
-          },
-          {
-            label: '取消',
-            popConfirm: {
-              title: '是否取消编辑',
-              confirm: handleCancel.bind(null, record, column),
-            },
-          },
-        ];
-      }
-      return {
-        registerTable,
-        createActions,
-        t,
-        registerModal,
-        handleSaveAdd,
-        handleCreate,
-        handleEdit,
-        handleDelete,
-        expandAll,
-        collapseAll,
-        getCheckRole,
-        reload,
-      };
-    },
-  });
-</script>
+<template>
+  <div>
+    <BasicTable @register="registerTable">
+      <template #toolbar>
+        <a-button v-show="!getCheckRole('tourist')" type="primary" @click="handleCreate">
+          新增商品属性</a-button
+        >
+      </template>
+      <template #action="{ record, column }">
+        <TableAction :actions="createActions(record, column)" />
+      </template>
+    </BasicTable>
+    <addModal @register="registerModal" @saveadd="handleSaveAdd" />
+  </div>
+</template>
+
+<script lang="ts">
+  import { defineComponent, ref } from 'vue'; // h
+  import {
+    BasicTable,
+    useTable,
+    BasicColumn,
+    TableAction,
+    EditRecordRow,
+    ActionItem,
+  } from '/@/components/Table';
+  import { attributeListApi, attributeDeleteApi, updateItemApi } from '/@/api/product/category';
+  import { cloneDeep } from 'lodash-es';
+  // import { Tag } from 'ant-design-vue';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useModal } from '/@/components/Modal';
+  import addModal from './addModal.vue';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useUserStore } from '/@/store/modules/user';
+
+  export default defineComponent({
+    components: { BasicTable, TableAction, addModal },
+    emits: ['register'],
+    setup() {
+      const { createConfirm, createMessage: msg } = useMessage();
+      const [registerModal, { openModal }] = useModal();
+      const currentEditKeyRef = ref('');
+      const userStore = useUserStore();
+      const { getCheckRole } = userStore;
+      const { t } = useI18n();
+      // console.log('registerModal', registerModal);
+      const columns: BasicColumn[] = [
+        {
+          title: '商品属性',
+          editRow: true,
+          editRule: async (text) => {
+            if (!text) {
+              return '请输入商品属性';
+            }
+            if (text && text.length > 15) {
+              return t('routes.corporation.maxlength');
+            }
+            return '';
+          },
+          dataIndex: 'name',
+          fixed: 'left',
+          width: 100,
+        },
+        {
+          title: '排序',
+          dataIndex: 'sortOrder',
+          editComponent: 'InputNumber',
+          editRule: async (text) => {
+            if (text == null || text < 0) {
+              return '请输入正确排序';
+            }
+            return '';
+          },
+          editRow: true,
+          width: 50,
+        },
+      ];
+
+      const [registerTable, { expandAll, reload, collapseAll }] = useTable({
+        title: '商品属性',
+        api: attributeListApi,
+        columns: columns,
+        useSearchForm: true,
+        showIndexColumn: false,
+        // rowSelection: { type: 'checkbox' },
+        // rowKey: 'id',
+        bordered: true,
+        fetchSetting: {
+          pageField: 'page',
+          sizeField: 'limit',
+          listField: 'list',
+          totalField: 'totalCount',
+        },
+        formConfig: {
+          labelWidth: 66,
+          schemas: [
+            {
+              field: `name`,
+              label: `商品属性`,
+              component: 'Input',
+              colProps: {
+                xl: 6,
+                xxl: 6,
+              },
+              componentProps: {
+                maxLength: 15,
+              },
+            },
+          ],
+        },
+        actionColumn: {
+          ifShow: !getCheckRole('tourist'),
+          width: 160,
+          title: '操作',
+          dataIndex: 'action',
+          slots: { customRender: 'action' },
+        },
+      });
+      async function handleCreate() {
+        openModal(true);
+      }
+      function handleEdit(record: EditRecordRow) {
+        currentEditKeyRef.value = record.key;
+        record.onEdit?.(true);
+      }
+      function handleDelete(record) {
+        createConfirm({
+          iconType: 'warning',
+          title: '警告',
+          content: `此操作将对${record.name}进行删除, 是否继续?`,
+          onOk: async () => {
+            let res = await attributeDeleteApi(record.id);
+            console.log('res', res);
+            reload();
+          },
+        });
+      }
+      function handleCancel(record: EditRecordRow) {
+        currentEditKeyRef.value = '';
+        record.onEdit?.(false, false);
+        reload();
+      }
+      async function handleSaveAdd() {
+        console.log('handleSaveAdd');
+        reload();
+      }
+
+      async function handleSave(record: EditRecordRow) {
+        // 校验
+        msg.loading({ content: '正在保存...', duration: 0, key: 'saving' });
+        const valid = await record.onValid?.();
+        console.log('ref', valid);
+        if (valid) {
+          try {
+            const data = cloneDeep(record.editValueRefs);
+            console.log(data);
+            //TODO 此处将数据提交给服务器保存
+            // ...
+            updateItemApi({
+              id: record.id,
+              ...data,
+            });
+
+            // 保存之后提交编辑状态
+            const pass = await record.onEdit?.(false, true);
+            if (pass) {
+              currentEditKeyRef.value = '';
+            }
+            msg.success({ content: '数据已保存', key: 'saving' });
+            reload();
+          } catch (error) {
+            msg.error({ content: '保存失败', key: 'saving' });
+          }
+        } else {
+          msg.error({ content: '请填写正确的数据', key: 'saving' });
+        }
+      }
+      function createActions(record: EditRecordRow, column: BasicColumn): ActionItem[] {
+        // console.log('editable', record);
+        if (!record.editable) {
+          return [
+            {
+              label: '编辑',
+              disabled: currentEditKeyRef.value ? currentEditKeyRef.value !== record.key : false,
+              onClick: handleEdit.bind(null, record),
+            },
+            {
+              label: '删除',
+              color: 'error',
+              disabled: currentEditKeyRef.value ? currentEditKeyRef.value !== record.key : false,
+              onClick: handleDelete.bind(null, record),
+            },
+          ];
+        }
+        return [
+          {
+            label: '保存',
+            onClick: handleSave.bind(null, record, column),
+          },
+          {
+            label: '取消',
+            popConfirm: {
+              title: '是否取消编辑',
+              confirm: handleCancel.bind(null, record, column),
+            },
+          },
+        ];
+      }
+      return {
+        registerTable,
+        createActions,
+        t,
+        registerModal,
+        handleSaveAdd,
+        handleCreate,
+        handleEdit,
+        handleDelete,
+        expandAll,
+        collapseAll,
+        getCheckRole,
+        reload,
+      };
+    },
+  });
+</script>

+ 1 - 1
src/views/rightsEnterprises/BindModal.vue

@@ -37,7 +37,7 @@
     props: {
       userData: { type: Object },
     },
-    emits: ['ok'],
+    emits: ['ok', 'register'],
     setup(_, context) {
       const modelRef = ref({
         isSee: false,

+ 3 - 1
src/views/rightsEnterprises/addCameraModal.vue

@@ -31,7 +31,7 @@
     props: {
       userData: { type: Object },
     },
-    emits: ['ok'],
+    emits: ['ok', 'register'],
     setup(_, context) {
       const modelRef = ref({
         isSee: false,
@@ -58,6 +58,7 @@
             resultField: 'list',
             labelField: 'name',
             valueField: 'id',
+            optionFilterProp: 'label',
             immediate: true,
             params: {
               page: 1,
@@ -89,6 +90,7 @@
             resultField: 'list',
             labelField: 'name',
             valueField: 'id',
+            optionFilterProp: 'label',
             immediate: true,
             params: {
               page: 1,

+ 1 - 4
src/views/rightsEnterprises/camera.vue

@@ -73,6 +73,7 @@
         },
       },
     },
+    emits: ['register'],
     setup(props) {
       const [register, { openModal }] = useModal();
       const surplusSubNum = ref({
@@ -219,10 +220,6 @@
           });
         },
         bordered: true,
-        sortFn: (sortInfo) => {
-          let order = sortInfo.order && sortInfo.order.replace('end', '');
-          return { ...sortInfo, sidx: sortInfo.field, order: order };
-        },
       });
 
       function renderStatus(type: number): string {

+ 1 - 1
src/views/rightsEnterprises/cameraBind.vue

@@ -23,7 +23,7 @@
     props: {
       userData: { type: Object },
     },
-    emits: ['update'],
+    emits: ['register', 'update'],
     setup(_, context) {
       const { t } = useI18n();
       const modelRef = ref({

+ 1 - 4
src/views/rightsEnterprises/cameraList.vue

@@ -74,6 +74,7 @@
       ProductDrawer,
       addCameraModal,
     },
+    emits: ['register', 'update'],
     setup() {
       const router = useRouter();
       const [register, { openModal }] = useModal();
@@ -194,10 +195,6 @@
           };
         },
         bordered: true,
-        sortFn: (sortInfo) => {
-          let order = sortInfo.order && sortInfo.order.replace('end', '');
-          return { ...sortInfo, sidx: sortInfo.field, order: order };
-        },
       });
 
       function renderRoleType(type: number): string {

+ 1 - 4
src/views/rightsEnterprises/enterprises.vue

@@ -65,6 +65,7 @@
       Alert,
       addModal,
     },
+    emits: ['register'],
     setup() {
       const [register, { openModal }] = useModal();
       const surplusSubNum = ref({
@@ -161,10 +162,6 @@
           };
         },
         bordered: true,
-        sortFn: (sortInfo) => {
-          let order = sortInfo.order && sortInfo.order.replace('end', '');
-          return { ...sortInfo, sidx: sortInfo.field, order: order };
-        },
       });
 
       function renderRoleType(type: number): string {

+ 2 - 5
src/views/rightsEnterprises/list.vue

@@ -30,7 +30,7 @@
             {
               color: 'error',
               label: '删除',
-              ifShow: !record.userId,
+              ifShow: !record.userId && getCheckRole([RoleEnum.PLAT_ADMIN, RoleEnum.SUPER]),
               popConfirm: {
                 title: '是否确认删除',
                 confirm: deleteConfirm.bind(null, record),
@@ -88,6 +88,7 @@
         },
       },
     },
+    emits: ['register', 'reload'],
     setup(props) {
       const [register, { openModal }] = useModal();
       const surplusSubNum = ref({
@@ -235,10 +236,6 @@
           return T;
         },
         bordered: true,
-        sortFn: (sortInfo) => {
-          let order = sortInfo.order && sortInfo.order.replace('end', '');
-          return { ...sortInfo, sidx: sortInfo.field, order: order };
-        },
       });
 
       function renderStatus(type: number): string {

+ 1 - 0
src/views/rightsEnterprises/rightsList.vue

@@ -71,6 +71,7 @@
       addModal,
       ProductDrawer,
     },
+    emits: ['register'],
     setup() {
       const router = useRouter();
       const [register, { openModal }] = useModal();

+ 1 - 4
src/views/rightsEnterprises/staff.vue

@@ -68,6 +68,7 @@
       addModal,
       // DelListModal,
     },
+    emits: ['register'],
     setup() {
       const [register, { openModal }] = useModal();
       const surplusSubNum = ref({
@@ -203,10 +204,6 @@
           return T;
         },
         bordered: true,
-        sortFn: (sortInfo) => {
-          let order = sortInfo.order && sortInfo.order.replace('end', '');
-          return { ...sortInfo, sidx: sortInfo.field, order: order };
-        },
       });
 
       function renderStatus(type: number): string {

+ 161 - 161
src/views/scenes/downloadModal.vue

@@ -1,161 +1,161 @@
-<template>
-  <BasicModal
-    v-bind="$attrs"
-    @register="register"
-    :title="t('routes.scenes.downloadScene')"
-    :showCancelBtn="false"
-    :okText="downloadInfo.isDownloaded ? t('common.okText') : t('routes.scenes.cancelDownload')"
-    @ok="handleSubmit"
-    @cancel="cancelDownload"
-  >
-    <div class="pt-20px">
-      <BasicForm @register="registerForm">
-        <template #label="{ model, field }">
-          {{ model[field] }}
-        </template>
-        <template #process> {{ downloadInfo.process }} % </template>
-        <template #status> {{ downloadInfo.status }} </template>
-      </BasicForm>
-    </div>
-    <template #centerFooter>
-      <!-- <a-button>xxxx</a-button> -->
-    </template>
-  </BasicModal>
-</template>
-<script lang="ts">
-  import { defineComponent, reactive, ref, watch } from 'vue';
-  import { BasicModal, useModalInner } from '/@/components/Modal';
-  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
-  // import { BasicTable, useTable, BasicColumn, FormSchema } from '/@/components/Table';
-  // import { useMessage } from '/@/hooks/web/useMessage';
-  // import { checkUserAddAble } from '/@/api/corporation/modal';
-  import { useI18n } from '/@/hooks/web/useI18n';
-  import { getDownloadProcessApi } from '/@/api/scene/list';
-  // import { bindAnchorListParam } from '/@/api/scene/model';
-  // import { Time } from '/@/components/Time';
-  // import { useUserStore } from '/@/store/modules/user';
-  import {
-    downloadByUrl,
-    // downloadByData,
-    // downloadByBase64,
-    // downloadByOnlineUrl,
-  } from '/@/utils/file/download';
-
-  const schemas: FormSchema[] = [
-    {
-      field: 'sceneName',
-      label: '场景名称:',
-      component: 'Input',
-      slot: 'label',
-    },
-    {
-      field: 'process',
-      label: '下载进度:',
-      component: 'Input',
-      slot: 'process',
-    },
-    {
-      field: 'status',
-      label: '状态:',
-      component: 'Input',
-      slot: 'status',
-    },
-  ];
-
-  export default defineComponent({
-    components: { BasicModal, BasicForm },
-    props: {
-      userData: { type: Object },
-    },
-    emits: ['register', 'success'],
-    setup() {
-      const { t } = useI18n();
-      // const { createMessage } = useMessage();
-      const sceneNum = ref('');
-      const finishDowloadUrl = ref('');
-      const downloadInfo = reactive<Recordable>({});
-      downloadInfo.timer = null;
-      downloadInfo.process = 0;
-      downloadInfo.status = '下载中';
-      downloadInfo.isDownloaded = false;
-
-      const [registerForm, { setFieldsValue }] = useForm({
-        schemas: schemas,
-        labelWidth: 120,
-        showActionButtonGroup: false,
-
-        actionColOptions: {
-          span: 24,
-        },
-        // submitFunc: handleSubmit,
-      });
-      const [register, { closeModal }] = useModalInner((data) => {
-        data && onDataReceive(data);
-      });
-
-      function onDataReceive(data) {
-        console.log('Data Received', data, data.num);
-
-        setFieldsValue({
-          ...data,
-        });
-
-        sceneNum.value = data.num;
-      }
-      const handleSubmit = async () => {
-        try {
-          cancelDownload();
-          closeModal();
-          if (downloadInfo.isDownloaded) {
-            downloadByUrl({
-              url: finishDowloadUrl.value as any as string,
-              target: '_self',
-            });
-          }
-        } catch (error) {}
-      };
-      async function getDownloadInfo(sceneNum: string) {
-        downloadInfo.timer = setInterval(async () => {
-          const res = await getDownloadProcessApi({ sceneNum });
-          console.log('res', res);
-          const percent = res.percent && Math.round(res.percent);
-          downloadInfo.process = percent;
-          if (res.status === 1000) {
-            downloadInfo.status = '获取中';
-          }
-          if (res.status === 1002 && res.url) {
-            cancelDownload();
-            finishDowloadUrl.value = res.url;
-            downloadInfo.isDownloaded = true;
-            downloadInfo.status = '获取成功';
-            handleSubmit();
-          }
-        }, 2000);
-      }
-      function cancelDownload() {
-        clearInterval(downloadInfo.timer);
-        sceneNum.value = '';
-      }
-      watch(
-        () => sceneNum.value,
-        () => {
-          console.log('sceneNum', sceneNum.value);
-          if (sceneNum.value) {
-            getDownloadInfo(sceneNum.value);
-          }
-        },
-      );
-
-      return {
-        t,
-        register,
-        schemas,
-        handleSubmit,
-        closeModal,
-        registerForm,
-        downloadInfo,
-        cancelDownload,
-      };
-    },
-  });
-</script>
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    :title="t('routes.scenes.downloadScene')"
+    :showCancelBtn="false"
+    :okText="downloadInfo.isDownloaded ? t('common.okText') : t('routes.scenes.cancelDownload')"
+    @ok="handleSubmit"
+    @cancel="cancelDownload"
+  >
+    <div class="pt-20px">
+      <BasicForm @register="registerForm">
+        <template #label="{ model, field }">
+          {{ model[field] }}
+        </template>
+        <template #process> {{ downloadInfo.process }} % </template>
+        <template #status> {{ downloadInfo.status }} </template>
+      </BasicForm>
+    </div>
+    <template #centerFooter>
+      <!-- <a-button>xxxx</a-button> -->
+    </template>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, reactive, ref, watch } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  // import { BasicTable, useTable, BasicColumn, FormSchema } from '/@/components/Table';
+  // import { useMessage } from '/@/hooks/web/useMessage';
+  // import { checkUserAddAble } from '/@/api/corporation/modal';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { getDownloadProcessApi } from '/@/api/scene/list';
+  // import { bindAnchorListParam } from '/@/api/scene/model';
+  // import { Time } from '/@/components/Time';
+  // import { useUserStore } from '/@/store/modules/user';
+  import {
+    downloadByUrl,
+    // downloadByData,
+    // downloadByBase64,
+    // downloadByOnlineUrl,
+  } from '/@/utils/file/download';
+
+  const schemas: FormSchema[] = [
+    {
+      field: 'sceneName',
+      label: '场景名称:',
+      component: 'Input',
+      slot: 'label',
+    },
+    {
+      field: 'process',
+      label: '下载进度:',
+      component: 'Input',
+      slot: 'process',
+    },
+    {
+      field: 'status',
+      label: '状态:',
+      component: 'Input',
+      slot: 'status',
+    },
+  ];
+
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['register', 'success'],
+    setup() {
+      const { t } = useI18n();
+      // const { createMessage } = useMessage();
+      const sceneNum = ref('');
+      const finishDowloadUrl = ref('');
+      const downloadInfo = reactive<Recordable>({});
+      downloadInfo.timer = null;
+      downloadInfo.process = 0;
+      downloadInfo.status = '下载中';
+      downloadInfo.isDownloaded = false;
+
+      const [registerForm, { setFieldsValue }] = useForm({
+        schemas: schemas,
+        labelWidth: 120,
+        showActionButtonGroup: false,
+
+        actionColOptions: {
+          span: 24,
+        },
+        // submitFunc: handleSubmit,
+      });
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+
+      function onDataReceive(data) {
+        console.log('Data Received', data, data.num);
+
+        setFieldsValue({
+          ...data,
+        });
+
+        sceneNum.value = data.num;
+      }
+      const handleSubmit = async () => {
+        try {
+          cancelDownload();
+          closeModal();
+          if (downloadInfo.isDownloaded) {
+            downloadByUrl({
+              url: finishDowloadUrl.value as any as string,
+              target: '_self',
+            });
+          }
+        } catch (error) {}
+      };
+      async function getDownloadInfo(sceneNum: string) {
+        downloadInfo.timer = setInterval(async () => {
+          const res = await getDownloadProcessApi({ sceneNum });
+          console.log('res', res);
+          const percent = res.percent && Math.round(res.percent);
+          downloadInfo.process = percent;
+          if (res.status === 1000) {
+            downloadInfo.status = '获取中';
+          }
+          if (res.status === 1002 && res.url) {
+            cancelDownload();
+            finishDowloadUrl.value = res.url;
+            downloadInfo.isDownloaded = true;
+            downloadInfo.status = '获取成功';
+            handleSubmit();
+          }
+        }, 2000);
+      }
+      function cancelDownload() {
+        clearInterval(downloadInfo.timer);
+        sceneNum.value = '';
+      }
+      watch(
+        () => sceneNum.value,
+        () => {
+          console.log('sceneNum', sceneNum.value);
+          if (sceneNum.value) {
+            getDownloadInfo(sceneNum.value);
+          }
+        },
+      );
+
+      return {
+        t,
+        register,
+        schemas,
+        handleSubmit,
+        closeModal,
+        registerForm,
+        downloadInfo,
+        cancelDownload,
+      };
+    },
+  });
+</script>

+ 142 - 142
src/views/scenes/editorModal.vue

@@ -1,142 +1,142 @@
-<template>
-  <BasicModal
-    v-bind="$attrs"
-    @cancel="resetFields"
-    @register="register"
-    title="编辑场景"
-    @ok="handleOk"
-  >
-    <div class="pt-3px pr-3px">
-      <BasicForm @register="registerForm">
-        <template #text="{ model, field }">
-          {{ model[field] }}
-        </template>
-      </BasicForm>
-    </div>
-  </BasicModal>
-</template>
-<script lang="ts">
-  import { defineComponent, ref, unref } from 'vue';
-  import { UpdateApi } from '/@/api/scene/list';
-  import { uploadLiveApi } from '/@/api/scene/live';
-  import { BasicModal, useModalInner } from '/@/components/Modal';
-  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
-  import { useI18n } from '/@/hooks/web/useI18n';
-  import { useMessage } from '/@/hooks/web/useMessage';
-  const { t } = useI18n();
-
-  export default defineComponent({
-    components: { BasicModal, BasicForm },
-    props: {
-      userData: { type: Object },
-    },
-    emits: ['reload'],
-    setup(_, context) {
-      const modelRef = ref({
-        toUserId: 0,
-        userId: 0,
-      });
-      const options = ref([]);
-      const { createMessage } = useMessage();
-      const schemas: FormSchema[] = [
-        {
-          field: 'sceneNum',
-          component: 'Input',
-          label: 'sceneNum',
-          show: false,
-        },
-        {
-          field: 'sceneName',
-          component: 'Input',
-          label: '场景名称',
-          slot: 'text',
-          colProps: {
-            span: 22,
-          },
-        },
-        {
-          field: 'picList',
-          label: '场景封面',
-          component: 'Upload',
-          required: true,
-          itemProps: {
-            validateTrigger: 'blur',
-          },
-          componentProps: {
-            api: uploadLiveApi,
-            maxSize: 5,
-            emptyHidePreview: true,
-            maxNumber: 1,
-            accept: ['jpg', 'jpeg', 'gif', 'png'],
-            afterFetch: function (data) {
-              Reflect.set(data, 'url', data.message.url);
-              return data;
-            },
-          },
-
-          colProps: {
-            span: 20,
-          },
-        },
-        {
-          field: 'isShow',
-          component: 'RadioGroup',
-          label: '是否显示在小程序',
-          required: true,
-          defaultValue: 0,
-          componentProps: {
-            options: [
-              {
-                label: '是',
-                value: 1,
-                key: 1,
-              },
-              {
-                label: '否',
-                value: 0,
-                key: 0,
-              },
-            ],
-          },
-        },
-      ];
-      const [registerForm, { validate, resetFields, setFieldsValue }] = useForm({
-        labelWidth: 150,
-        schemas,
-        showActionButtonGroup: false,
-        actionColOptions: {
-          span: 20,
-        },
-      });
-
-      const [register, { closeModal }] = useModalInner((data) => {
-        data && onDataReceive(data);
-      });
-      function onDataReceive(data) {
-        data = unref(data);
-        console.log('onDataReceive', data);
-        setFieldsValue({
-          ...data,
-          sceneNum: data.num,
-          picList: [data.appListPicUrl],
-        });
-        // 方式1;
-      }
-
-      async function handleOk() {
-        let data = await validate();
-        console.log('data', data);
-        let res = await UpdateApi({
-          ...data,
-          appListPicUrl: data.picList[0],
-        });
-        context && context.emit('reload', res);
-        createMessage.success(t('common.optSuccess'));
-        closeModal();
-        resetFields();
-      }
-
-      return { options, register, registerForm, model: modelRef, handleOk, resetFields };
-    },
-  });
-</script>
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @cancel="resetFields"
+    @register="register"
+    title="编辑场景"
+    @ok="handleOk"
+  >
+    <div class="pt-3px pr-3px">
+      <BasicForm @register="registerForm">
+        <template #text="{ model, field }">
+          {{ model[field] }}
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, unref } from 'vue';
+  import { UpdateApi } from '/@/api/scene/list';
+  import { uploadLiveApi } from '/@/api/scene/live';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  const { t } = useI18n();
+
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['reload', 'register'],
+    setup(_, context) {
+      const modelRef = ref({
+        toUserId: 0,
+        userId: 0,
+      });
+      const options = ref([]);
+      const { createMessage } = useMessage();
+      const schemas: FormSchema[] = [
+        {
+          field: 'sceneNum',
+          component: 'Input',
+          label: 'sceneNum',
+          show: false,
+        },
+        {
+          field: 'sceneName',
+          component: 'Input',
+          label: '场景名称',
+          slot: 'text',
+          colProps: {
+            span: 22,
+          },
+        },
+        {
+          field: 'picList',
+          label: '场景封面',
+          component: 'Upload',
+          required: true,
+          itemProps: {
+            validateTrigger: 'blur',
+          },
+          componentProps: {
+            api: uploadLiveApi,
+            maxSize: 5,
+            emptyHidePreview: true,
+            maxNumber: 1,
+            accept: ['jpg', 'jpeg', 'gif', 'png'],
+            afterFetch: function (data) {
+              Reflect.set(data, 'url', data.message.url);
+              return data;
+            },
+          },
+
+          colProps: {
+            span: 20,
+          },
+        },
+        {
+          field: 'isShow',
+          component: 'RadioGroup',
+          label: '是否显示在小程序',
+          required: true,
+          defaultValue: 0,
+          componentProps: {
+            options: [
+              {
+                label: '是',
+                value: 1,
+                key: 1,
+              },
+              {
+                label: '否',
+                value: 0,
+                key: 0,
+              },
+            ],
+          },
+        },
+      ];
+      const [registerForm, { validate, resetFields, setFieldsValue }] = useForm({
+        labelWidth: 150,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 20,
+        },
+      });
+
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      function onDataReceive(data) {
+        data = unref(data);
+        console.log('onDataReceive', data);
+        setFieldsValue({
+          ...data,
+          sceneNum: data.num,
+          picList: [data.appListPicUrl],
+        });
+        // 方式1;
+      }
+
+      async function handleOk() {
+        let data = await validate();
+        console.log('data', data);
+        let res = await UpdateApi({
+          ...data,
+          appListPicUrl: data.picList[0],
+        });
+        context && context.emit('reload', res);
+        createMessage.success(t('common.optSuccess'));
+        closeModal();
+        resetFields();
+      }
+
+      return { options, register, registerForm, model: modelRef, handleOk, resetFields };
+    },
+  });
+</script>

+ 3 - 7
src/views/scenes/list.vue

@@ -92,6 +92,7 @@
 
   export default defineComponent({
     components: { BasicTable, TableAction, TableImg, DownloadModal, EditorModal },
+    emits: ['register'],
     setup() {
       const { createMessage } = useMessage();
       const [registerDownloadModal, { openModal: openDownloadModal }] = useModal();
@@ -147,14 +148,9 @@
           width: 180,
         },
         {
-          title: '小程序' + t('routes.scenes.isShow'),
-          dataIndex: 'isShow',
+          title: '项目编号',
+          dataIndex: 'workNumber',
           width: 180,
-          customRender: ({ record }) => {
-            const enable = record.isShow == 1;
-            const text = enable ? t('common.yes') : t('common.no');
-            return text;
-          },
         },
         {
           title: t('routes.scenes.status'),

+ 7 - 11
src/views/scenes/live.vue

@@ -52,7 +52,7 @@
       </template>
     </BasicTable>
     <bindModal @register="registerBindModal" @success="reload" />
-    <addLiveModal @register="registeraddLiveModal" />
+    <!-- <addLiveModal @register="registeraddLiveModal" /> -->
     <LiveDrawer @register="registerLiveDrawer" @success="reload" />
   </div>
 </template>
@@ -81,6 +81,7 @@
   import LiveDrawer from './liveDrawer.vue';
   export default defineComponent({
     components: { BasicTable, TableAction, TableImg, bindModal, LiveDrawer },
+    emits: ['register'],
     setup() {
       const { createMessage } = useMessage();
       const userStore = useUserStore();
@@ -139,7 +140,11 @@
           ellipsis: false,
           width: 180,
         },
-
+        {
+          title: '绑定联系人',
+          dataIndex: 'contactName',
+          width: 180,
+        },
         {
           title: t('routes.scenes.status'),
           dataIndex: 'payStatus',
@@ -274,15 +279,6 @@
           listField: 'list',
           totalField: 'totalCount',
         },
-        sortFn: (sortInfo) => {
-          console.log('sortInfo', sortInfo);
-          let order = sortInfo.order && sortInfo.order.replace('end', '');
-          return { ...sortInfo, sidx: sortInfo.field, order: order };
-        },
-        // defSort: {
-        //   field: 'order',
-        //   order: 'asc',
-        // },
       });
 
       function handleBindAnchor(record: Recordable) {

+ 122 - 14
src/views/scenes/liveDrawer.vue

@@ -51,13 +51,14 @@
   </BasicDrawer>
 </template>
 <script lang="ts">
-  import { defineComponent, ref, computed, unref, nextTick, reactive } from 'vue';
+  import { defineComponent, ref, computed, unref, nextTick, reactive, watch } from 'vue';
   import { BasicForm, useForm, FormSchema } from '/@/components/Form/index';
   // import { Card } from 'ant-design-vue';
   import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
   import { useMessage } from '/@/hooks/web/useMessage';
   import { useI18n } from '/@/hooks/web/useI18n';
   import { useUserStore } from '/@/store/modules/user';
+  import type { UserInfo } from '/#/store';
   import { data as CascaderData, getCode } from '/@/utils/cascaderData';
   import { ListApi } from '/@/api/scene/list';
   import { Select } from 'ant-design-vue';
@@ -72,6 +73,18 @@
     // LiveSceneDeleteApi,
   } from '/@/api/scene/live';
   import { useScript } from '/@/hooks/web/useScript';
+  import { staffListApi } from '/@/api/staff/list';
+  import { SceneLiveItem } from '/@/api/scene/model';
+  import { isBoolean } from '/@/utils/is';
+  // import cascaderOptions, { DivisionUtil } from '@pansy/china-division';
+
+  // const divisionUtil = new DivisionUtil(cascaderOptions);
+  // let ProvincesData = divisionUtil.getProvinces();
+  // ProvincesData.map((n) => {
+  //   const cities = divisionUtil.getChildrenByCode(n.value);
+  //   n.children = cities;
+  // });
+  // console.log('ProvincesData', ProvincesData);
   const A_MAP_URL = 'https://webapi.amap.com/maps?v=2.0&key=e661b00bdf2c44cccf71ef6070ef41b8';
 
   // const A_MAP_URL = 'https://webapi.amap.com/maps?v=2.0&key=5a2d384532ae531bf99bd8487c4f03d2';
@@ -92,6 +105,9 @@
       const token = userStore.getToken;
       const userinfo = computed(() => userStore.getUserInfo);
       const wrapRef = ref<HTMLDivElement | null>(null);
+      const isDeaultContactUser = ref<UserInfo | false>(false);
+      const editRecord = ref<SceneLiveItem | boolean>(false);
+
       const sceneNumOption = reactive({
         list: [],
         allList: [],
@@ -249,6 +265,7 @@
           },
           componentProps: {
             api: () => {
+              // return ProvincesData;
               return CascaderData;
             },
             apiParamKey: 'provinceCode',
@@ -373,12 +390,48 @@
             ],
           },
         },
+        // {
+        //   field: 'contractPhone',
+        //   component: 'Input',
+        //   label: t('routes.scenes.contractPhone'),
+        //   required: true,
+        //   helpMessage: ['支持填写400(400-xxx-xxxx)热线、手机号等联系方式'],
+        // },
         {
-          field: 'contractPhone',
-          component: 'Input',
-          label: t('routes.scenes.contractPhone'),
+          field: 'contactId',
+          component: 'ApiSelect',
+          label: '场景联系人',
           required: true,
-          helpMessage: ['支持填写400(400-xxx-xxxx)热线、手机号等联系方式'],
+          componentProps: {
+            api: staffListApi,
+            resultField: 'list',
+            labelField: 'nickName',
+            valueField: 'id',
+            immediate: true,
+            showSearch: true,
+            optionFilterProp: 'label',
+            onOptionsChange: async (data) => {
+              const isDeaultUser = data.find((i) => i.value === Number(userinfo.value.id));
+              if (isDeaultUser && !isDeaultContactUser.value) {
+                isDeaultUser.id = Number(isDeaultUser.value);
+                isDeaultContactUser.value = isDeaultUser;
+              }
+            },
+            onChange: function (model, opt) {
+              console.log('model', model);
+              console.log('opt', opt);
+              // Reflect.set(modalRecord, 'shippingName', opt.label);
+            },
+            params: {
+              page: 1,
+              limit: 1000,
+            },
+            required: true,
+          },
+          itemProps: {
+            validateFirst: true,
+            validateTrigger: 'select',
+          },
         },
       ];
       // updateSchema, validate
@@ -402,15 +455,17 @@
           limit: 100,
           sceneNum: record && record.sceneNum,
         });
+        // @ts-ignore
         sceneNumOption.list = Option.list.map((ele) => {
           return {
             ...ele,
             label: ele.sceneName,
+            // @ts-ignore
             value: ele.num,
           };
         });
         sceneNumOption.allList = sceneNumOption.list;
-        console.log('isUpdate', isUpdate.value, Option.list);
+        console.log('isUpdate', isUpdate.value, data);
         await updateSchema({
           field: 'sceneNum',
           componentProps: {
@@ -430,10 +485,14 @@
             },
           },
         });
+
+        // console.log('isDeaultContactUser', isDeaultContactUser.value?.id);
+
         if (unref(isUpdate)) {
           try {
             const res = await getLiveInfoApi({ id: record.id, token });
-            let setDAta: infoItem = {
+            // console.log('edit', res);
+            let setDAta = reactive<infoItem>({
               province: '',
               city: '',
               district: '',
@@ -441,8 +500,9 @@
               picList: [],
               appListPicUrl: [],
               introduceVideo: [],
-            };
-            let ssq = res.address.split(' ');
+            });
+
+            let ssq = res.address?.split(' ');
             try {
               let ssqlist = ssq[0].split(',');
               setDAta.province = ssqlist[0];
@@ -450,7 +510,7 @@
               setDAta.district = ssqlist[2];
               setDAta.address = ssq[1];
               setDAta.location = ssqlist;
-              setDAta.picList = res.picList.split('#$#');
+              setDAta.picList = res.picList?.split('#$#') || [];
               setDAta.appListPicUrl = [res.appListPicUrl];
               setDAta.introduceVideo = [res.introduceVideo];
               defaultAddress.address = ssq[1];
@@ -461,22 +521,66 @@
               defaultAddress.lng = res.longitude;
               defaultAddress.lat = res.latitude;
               detailAddr.value = ssq[2];
-            } catch (error) {}
-            myData.introduceVideoCover = res.introduceVideoCover;
+            } catch (error) {
+              console.warn('edit-error', error);
+            }
+            myData.introduceVideoCover = res.introduceVideoCover || '';
             myData.sceneUrl = res.sceneUrl;
             await setFieldsValue({
               ...res,
               ...setDAta,
             });
             myData.introduceVideoCover = res.introduceVideoCover;
+
+            editRecord.value = res;
           } catch (error) {
             console.error(error);
           }
+        } else {
+          editRecord.value = false;
         }
         myData.id = (record && record.id) || false;
+
+        // if (isDeaultContactUser.value?.id) {
+        //   console.log('hey');
+        //   await setFieldsValue({
+        //     contactId: isDeaultContactUser.value.id,
+        //   });
+        // }
+
         initMap();
       });
 
+      watch(
+        () => [editRecord, isDeaultContactUser],
+        async ([edit, user]) => {
+          console.log('update', edit.value);
+          // if (!edit.value && user.value) {
+          //   await setFieldsValue({
+          //     contactId: user.value.id,
+          //   });
+          // }
+          //
+          if (!isBoolean(edit.value) && !isBoolean(user.value)) {
+            if (user.value.contactId === '') {
+              await setFieldsValue({
+                contactId: user.value.id,
+              });
+            }
+          }
+          //新增
+          if (isBoolean(edit.value) && !isBoolean(user.value)) {
+            await setFieldsValue({
+              contactId: user.value.id,
+            });
+          }
+        },
+        {
+          deep: true,
+          immediate: true,
+        },
+      );
+
       async function initMap() {
         console.log('initMap');
         await toPromise();
@@ -554,9 +658,11 @@
           limit: 100,
           page: 1,
         });
+        // @ts-ignore
         sceneNumOption.list = list.map((ele) => {
           return {
             ...ele,
+            // @ts-ignore
             value: ele.num,
             label: ele.sceneName,
           };
@@ -631,7 +737,7 @@
       async function handleSubmit() {
         const { companyId, id } = userinfo.value;
         const { lng, lat } = defaultAddress;
-        let requerApi = isUpdate.value ? brandUpdateApi : addSave;
+        let requestApi = isUpdate.value ? brandUpdateApi : addSave;
         try {
           map && map.destroy();
           const values = await validate();
@@ -654,7 +760,7 @@
           if (myData.id) {
             apiData.id = myData.id;
           }
-          await requerApi(apiData);
+          await requestApi(apiData);
           resetFields();
           closeDrawer();
           createMessage.success(t('common.optSuccess'));
@@ -666,6 +772,8 @@
       async function handleClose() {
         map && map.destroy();
         resetFields();
+        isDeaultContactUser.value = false;
+        editRecord.value = false;
         closeDrawer();
       }
 

+ 6 - 10
src/views/scenes/room.vue

@@ -42,7 +42,7 @@
         <TableAction :actions="renderActions(record)" />
       </template>
     </BasicTable>
-    <roomDeital @success="reload" @register="registerModal" @submit="handleAddUser" />
+    <roomDetail @success="reload" @register="registerModal" />
   </div>
 </template>
 <script lang="ts">
@@ -62,11 +62,12 @@
   import { useI18n } from '/@/hooks/web/useI18n';
   import { useUserStore } from '/@/store/modules/user';
   import { useModal } from '/@/components/Modal';
-  import roomDeital from './roomDeital.vue';
+  import roomDetail from './roomDetail.vue';
   import { listRoomsApi, dismissRoom, deleteRoom } from '/@/api/scene/list';
 
   export default defineComponent({
-    components: { BasicTable, TableAction, TableImg, roomDeital, Time },
+    components: { BasicTable, TableAction, TableImg, roomDetail, Time },
+    emits: ['register'],
     setup() {
       const { createMessage, createConfirm } = useMessage();
       const userStore = useUserStore();
@@ -290,11 +291,6 @@
           listField: 'list',
           totalField: 'totalCount',
         },
-        sortFn: (sortInfo) => {
-          console.log('sortInfo', sortInfo);
-          let order = sortInfo.order && sortInfo.order.replace('end', '');
-          return { ...sortInfo, sidx: sortInfo.field, order: order };
-        },
       });
 
       function handleBindAnchor(record: Recordable) {
@@ -314,10 +310,10 @@
           record: {
             ...toRaw(record),
             launchPeriod: [
-              record.effectiveEndTime &&
-                dayjs(record.effectiveEndTime).format('YYYY-MM-DD HH:mm:ss'),
               record.effectiveStartTime &&
                 dayjs(record.effectiveStartTime).format('YYYY-MM-DD HH:mm:ss'),
+              record.effectiveEndTime &&
+                dayjs(record.effectiveEndTime).format('YYYY-MM-DD HH:mm:ss'),
             ],
           },
         });

+ 58 - 28
src/views/scenes/roomDeital.vue

@@ -29,8 +29,9 @@
   import { staffListApi } from '/@/api/staff/list';
   import { ListApi } from '/@/api/scene/live';
   import { bindUserList, createOrUpdate } from '/@/api/scene/list';
-  import dayjs from 'dayjs';
+  import { dateUtil } from '/@/utils/dateUtil';
   import { cloneDeep } from 'lodash-es';
+  const now = dateUtil();
   const { t } = useI18n();
   export default defineComponent({
     components: { BasicModal, BasicForm },
@@ -103,6 +104,7 @@
             showSearch: true,
             resultField: 'list',
             labelField: 'name',
+            optionFilterProp: 'label',
             valueField: 'id',
             immediate: true,
             params: {
@@ -129,6 +131,7 @@
             disabled: true,
             resultField: 'list',
             labelField: 'userAndNick',
+            optionFilterProp: 'label',
             valueField: 'id',
             immediate: false,
             params: {
@@ -150,29 +153,23 @@
               required: true,
               // @ts-ignore
               validator: async (rule, value) => {
-                if (!value) {
-                  return Promise.reject('请选择开播时间段');
-                }
-                if (modalTitle.value == '查看') {
+                if (isPropsData.isSetData) {
                   return Promise.resolve();
                 }
-                let start, end;
-                if (dayjs(value[0]).isAfter(dayjs(value[1]))) {
-                  start = dayjs(value[1]);
-                  end = dayjs(value[0]);
-                } else {
-                  end = dayjs(value[1]);
-                  start = dayjs(value[0]);
+                if (!value) {
+                  return Promise.reject('请选择时间段');
                 }
-                console.log('start', start.format('YYYY-MM-DD'), end.format('YYYY-MM-DD'), value);
-                if (end.isSame(start, 'minute')) {
-                  return Promise.reject('结束时间应大于开始时间');
-                } else if (dayjs().isAfter(dayjs(end))) {
-                  return Promise.reject('结束时间应大于当前时间');
-                } else {
-                  return Promise.resolve();
+                // console.log('check', rule, value, record);
+                console.log('value[1]', value[1].unix(), value);
+                if (value[0].unix() > value[1].unix()) {
+                  return Promise.reject('开始时间要小于结束时间');
                 }
+                if (value[0].unix() > value[1].unix() - 300) {
+                  return Promise.reject('时间段最小5分钟');
+                }
+                return Promise.resolve();
               },
+
               trigger: 'change',
             },
           ],
@@ -183,9 +180,26 @@
                 incrementEndTime: '',
               });
             },
-            // valueFormat: 'YYYY-MM-DD HH:mm',
             format: 'YYYY-MM-DD HH:mm',
             showTime: true,
+            disabledDate(current) {
+              return current && current < now;
+            },
+            disabledTime(_) {
+              // if (type === 'start') {
+              //   return {
+              //     // ...Array(dateUtil().minute()).keys()
+              //     disabledHours: () => [...Array(dateUtil().hour()).keys()],
+              //     disabledMinutes: () => [],
+              //     disabledSeconds: () => [],
+              //   };
+              // }
+              return {
+                disabledHours: () => [...Array(dateUtil().hour()).keys()],
+                disabledMinutes: () => [],
+                disabledSeconds: () => [],
+              };
+            },
             // disabledDate: validateStartTime,
           },
         },
@@ -219,6 +233,7 @@
           componentProps: {
             api: staffListApi,
             showSearch: true,
+            optionFilterProp: 'label',
             resultField: 'list',
             labelField: 'nickName',
             valueField: 'id',
@@ -270,6 +285,7 @@
         modalTitle.value = data.isSetData ? '查看' : data.isUpdate ? '编辑' : '新增';
         // const  = ref('编辑');
         isPropsData.record = data.record || {};
+        console.log('setFieldsValue', data.record);
         if (data.isUpdate) {
           //编辑
           setFieldsValue({
@@ -296,10 +312,11 @@
         } else if (data.isSetData) {
           //查看
           let setSchemas = [];
-          console.log('setFieldsValue', data.record);
           schemas.map((ele) => {
             let setSchemasItem = {
               ...ele,
+              required: false,
+              rules: [],
               componentProps: {
                 ...ele.componentProps,
                 disabled: true,
@@ -331,6 +348,17 @@
         const values = await validate();
         const { launchPeriod } = values;
         console.log('validate', values, launchPeriod);
+        if (isPropsData.isSetData) {
+          closeModal();
+          return;
+        }
+        if (
+          values.launchPeriod[0].valueOf() < dateUtil().valueOf() ||
+          values.launchPeriod[1].valueOf() < dateUtil().valueOf()
+        ) {
+          createMessage.warn('开始或结束时间不能少于当前时间');
+          return;
+        }
         let apiData = {
           ...values,
           effectiveEndTime:
@@ -368,6 +396,7 @@
           setFieldsValue({
             anchorUserId: userinfo.value.id,
           });
+          UserIdChange(userinfo.value.id);
         } else if (!disabled) {
           setFieldsValue({
             anchorUserId: '',
@@ -405,6 +434,7 @@
             },
             showSearch: true,
             resultField: 'list',
+            optionFilterProp: 'label',
             labelField: 'nickName',
             valueField: 'id',
             immediate: true,
@@ -441,11 +471,11 @@
   });
 </script>
 <style lang="less">
-  .ant-form-item-control-input {
-    div {
-      div {
-        width: 100%;
-      }
-    }
-  }
+  // .ant-form-item-control-input {
+  //   div {
+  //     div {
+  //       width: 100%;
+  //     }
+  //   }
+  // }
 </style>

+ 165 - 165
src/views/setting/data.ts

@@ -1,165 +1,165 @@
-import { FormSchema } from '/@/components/Form/index';
-
-export interface ListItem {
-  key: string;
-  title: string;
-  description: string;
-  extra?: string;
-  avatar?: string;
-  color?: string;
-}
-
-// tab的list
-export const settingList = [
-  {
-    key: '1',
-    name: '基本设置',
-    component: 'BaseSetting',
-  },
-  // {
-  //   key: '2',
-  //   name: '安全设置',
-  //   component: 'SecureSetting',
-  // },
-  // {
-  //   key: '3',
-  //   name: '账号绑定',
-  //   component: 'AccountBind',
-  // },
-  // {
-  //   key: '4',
-  //   name: '新消息通知',
-  //   component: 'MsgNotify',
-  // },
-];
-
-// 基础设置 form
-export const baseSetschemas: FormSchema[] = [
-  {
-    field: 'password',
-    component: 'StrengthMeter',
-    label: '旧密码',
-    labelWidth: 60,
-    required: true,
-    // colProps: { span: 18 },
-  },
-  {
-    field: 'newPassword',
-    component: 'StrengthMeter',
-    label: '新密码',
-    labelWidth: 60,
-    required: true,
-    // colProps: { span: 18 },
-  },
-  // {
-  //   field: 'email',
-  //   component: 'Input',
-  //   label: '邮箱',
-  //   colProps: { span: 18 },
-  // },
-  // {
-  //   field: 'name',
-  //   component: 'Input',
-  //   label: '昵称',
-  //   colProps: { span: 18 },
-  // },
-  // {
-  //   field: 'introduction',
-  //   component: 'InputTextArea',
-  //   label: '个人简介',
-  //   colProps: { span: 18 },
-  // },
-  // {
-  //   field: 'phone',
-  //   component: 'Input',
-  //   label: '联系电话',
-  //   colProps: { span: 18 },
-  // },
-  // {
-  //   field: 'address',
-  //   component: 'Input',
-  //   label: '所在地区',
-  //   colProps: { span: 18 },
-  // },
-];
-
-// 安全设置 list
-export const secureSettingList: ListItem[] = [
-  {
-    key: '1',
-    title: '账户密码',
-    description: '当前密码强度::强',
-    extra: '修改',
-  },
-  {
-    key: '2',
-    title: '密保手机',
-    description: '已绑定手机::138****8293',
-    extra: '修改',
-  },
-  {
-    key: '3',
-    title: '密保问题',
-    description: '未设置密保问题,密保问题可有效保护账户安全',
-    extra: '修改',
-  },
-  {
-    key: '4',
-    title: '备用邮箱',
-    description: '已绑定邮箱::ant***sign.com',
-    extra: '修改',
-  },
-  {
-    key: '5',
-    title: 'MFA 设备',
-    description: '未绑定 MFA 设备,绑定后,可以进行二次确认',
-    extra: '修改',
-  },
-];
-
-// 账号绑定 list
-export const accountBindList: ListItem[] = [
-  {
-    key: '1',
-    title: '绑定淘宝',
-    description: '当前未绑定淘宝账号',
-    extra: '绑定',
-    avatar: 'ri:taobao-fill',
-    color: '#ff4000',
-  },
-  {
-    key: '2',
-    title: '绑定支付宝',
-    description: '当前未绑定支付宝账号',
-    extra: '绑定',
-    avatar: 'fa-brands:alipay',
-    color: '#2eabff',
-  },
-  {
-    key: '3',
-    title: '绑定钉钉',
-    description: '当前未绑定钉钉账号',
-    extra: '绑定',
-    avatar: 'ri:dingding-fill',
-    color: '#2eabff',
-  },
-];
-
-// 新消息通知 list
-export const msgNotifyList: ListItem[] = [
-  {
-    key: '1',
-    title: '账户密码',
-    description: '其他用户的消息将以站内信的形式通知',
-  },
-  {
-    key: '2',
-    title: '系统消息',
-    description: '系统消息将以站内信的形式通知',
-  },
-  {
-    key: '3',
-    title: '待办任务',
-    description: '待办任务将以站内信的形式通知',
-  },
-];
+import { FormSchema } from '/@/components/Form/index';
+
+export interface ListItem {
+  key: string;
+  title: string;
+  description: string;
+  extra?: string;
+  avatar?: string;
+  color?: string;
+}
+
+// tab的list
+export const settingList = [
+  {
+    key: '1',
+    name: '基本设置',
+    component: 'BaseSetting',
+  },
+  // {
+  //   key: '2',
+  //   name: '安全设置',
+  //   component: 'SecureSetting',
+  // },
+  // {
+  //   key: '3',
+  //   name: '账号绑定',
+  //   component: 'AccountBind',
+  // },
+  // {
+  //   key: '4',
+  //   name: '新消息通知',
+  //   component: 'MsgNotify',
+  // },
+];
+
+// 基础设置 form
+export const baseSetschemas: FormSchema[] = [
+  {
+    field: 'password',
+    component: 'StrengthMeter',
+    label: '旧密码',
+    labelWidth: 60,
+    required: true,
+    colProps: { span: 24 },
+  },
+  {
+    field: 'newPassword',
+    component: 'StrengthMeter',
+    label: '新密码',
+    labelWidth: 60,
+    required: true,
+    colProps: { span: 24 },
+  },
+  // {
+  //   field: 'email',
+  //   component: 'Input',
+  //   label: '邮箱',
+  //   colProps: { span: 18 },
+  // },
+  // {
+  //   field: 'name',
+  //   component: 'Input',
+  //   label: '昵称',
+  //   colProps: { span: 18 },
+  // },
+  // {
+  //   field: 'introduction',
+  //   component: 'InputTextArea',
+  //   label: '个人简介',
+  //   colProps: { span: 18 },
+  // },
+  // {
+  //   field: 'phone',
+  //   component: 'Input',
+  //   label: '联系电话',
+  //   colProps: { span: 18 },
+  // },
+  // {
+  //   field: 'address',
+  //   component: 'Input',
+  //   label: '所在地区',
+  //   colProps: { span: 18 },
+  // },
+];
+
+// 安全设置 list
+export const secureSettingList: ListItem[] = [
+  {
+    key: '1',
+    title: '账户密码',
+    description: '当前密码强度::强',
+    extra: '修改',
+  },
+  {
+    key: '2',
+    title: '密保手机',
+    description: '已绑定手机::138****8293',
+    extra: '修改',
+  },
+  {
+    key: '3',
+    title: '密保问题',
+    description: '未设置密保问题,密保问题可有效保护账户安全',
+    extra: '修改',
+  },
+  {
+    key: '4',
+    title: '备用邮箱',
+    description: '已绑定邮箱::ant***sign.com',
+    extra: '修改',
+  },
+  {
+    key: '5',
+    title: 'MFA 设备',
+    description: '未绑定 MFA 设备,绑定后,可以进行二次确认',
+    extra: '修改',
+  },
+];
+
+// 账号绑定 list
+export const accountBindList: ListItem[] = [
+  {
+    key: '1',
+    title: '绑定淘宝',
+    description: '当前未绑定淘宝账号',
+    extra: '绑定',
+    avatar: 'ri:taobao-fill',
+    color: '#ff4000',
+  },
+  {
+    key: '2',
+    title: '绑定支付宝',
+    description: '当前未绑定支付宝账号',
+    extra: '绑定',
+    avatar: 'fa-brands:alipay',
+    color: '#2eabff',
+  },
+  {
+    key: '3',
+    title: '绑定钉钉',
+    description: '当前未绑定钉钉账号',
+    extra: '绑定',
+    avatar: 'ri:dingding-fill',
+    color: '#2eabff',
+  },
+];
+
+// 新消息通知 list
+export const msgNotifyList: ListItem[] = [
+  {
+    key: '1',
+    title: '账户密码',
+    description: '其他用户的消息将以站内信的形式通知',
+  },
+  {
+    key: '2',
+    title: '系统消息',
+    description: '系统消息将以站内信的形式通知',
+  },
+  {
+    key: '3',
+    title: '待办任务',
+    description: '待办任务将以站内信的形式通知',
+  },
+];

+ 1 - 1
src/views/staff/delListModal.vue

@@ -29,7 +29,7 @@
     props: {
       userData: { type: Object },
     },
-    emits: ['reload'],
+    emits: ['register', 'reload'],
     setup(_, context) {
       const modelRef = ref({
         toUserId: 0,

+ 82 - 15
src/views/staff/detailsModal.vue

@@ -12,8 +12,14 @@
   </BasicModal>
 </template>
 <script lang="ts">
-  import { defineComponent, ref, computed } from 'vue';
-  import { checkUserApi, saveApi, updateApi, getRoleListByParam } from '/@/api/staff/list'; //roleLIstApi
+  import { defineComponent, ref, computed, h } from 'vue';
+  import {
+    checkUserApi,
+    saveApi,
+    updateApi,
+    getRoleListByParam,
+    uploadApi,
+  } from '/@/api/staff/list'; //roleLIstApi
   import { getAllList } from '/@/api/rightsEnterprises/list';
   import { BasicModal, useModalInner } from '/@/components/Modal';
   import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
@@ -21,13 +27,15 @@
   import { useI18n } from '/@/hooks/web/useI18n';
   import { useMessage } from '/@/hooks/web/useMessage';
   import { useUserStore } from '/@/store/modules/user';
+  import { CropperAvatar } from '/@/components/Cropper';
+  import { Result } from '/#/axios';
   const { t } = useI18n();
   export default defineComponent({
     components: { BasicModal, BasicForm },
     props: {
       userData: { type: Object },
     },
-    emits: ['ok'],
+    emits: ['register', 'ok'],
     setup(_, context) {
       const modelRef = ref({});
       const userStore = useUserStore();
@@ -100,13 +108,14 @@
           },
           componentProps: {
             maxLength: 15,
+            placeholder: '请输入2-15长度的员工名称',
           },
           rules: [
             {
               required: true,
               // @ts-ignore
               validator: async (rule, value) => {
-                var reg_tel = /^[a-zA-Z\u4e00-\u9fa5]+$/;
+                var reg_tel = /^[a-zA-Z0-9\u4e00-\u9fa5]{2,15}$/;
                 // var reg = /\S+@\S+\.\S+/;
                 if (!value) {
                   return Promise.reject('请输入员工名称');
@@ -139,6 +148,7 @@
             resultField: 'list',
             labelField: 'name',
             valueField: 'id',
+            optionFilterProp: 'label',
             immediate: true,
             params: {
               page: 1,
@@ -201,20 +211,66 @@
             required: true,
           },
         },
+        {
+          field: 'head',
+          label: '员工头像',
+          component: 'Upload',
+          required: false,
+          // defaultValue: 1,
+          itemProps: {
+            validateTrigger: 'blur',
+          },
+          helpMessage: '推荐大小:400 * 400 像素',
+          render: ({ model, field }) => {
+            return h(CropperAvatar, {
+              value: model[field],
+              maxlength: 200,
+              uploadApi: uploadApi,
+              showBtn: false,
+              maxNumber: 1,
+              accept: ['jpg', 'jpeg', 'gif', 'png'],
+              onChange: (_, source: Result) => {
+                console.log('onChange', source.message);
+                model[field] = source.message;
+              },
+              showImageUpload: true,
+            });
+          },
+          // componentProps: {
+          //   api: uploadApi,
+          //   maxNumber: 1,
+          //   accept: ['jpg', 'jpeg', 'gif', 'png'],
+          //   afterFetch: function (data) {
+          //     console.log('data', data);
+          //     Reflect.set(data, 'url', data.message);
+          //     return data;
+          //   },
+          // },
+        },
         // {
-        //   field: 'status',
-        //   label: '状态',
-        //   component: 'RadioButtonGroup',
+        //   field: 'avatar',
+        //   component: 'Upload',
+        //   label: '员工头像',
         //   required: true,
-        //   defaultValue: 1,
+        //   rules: [{ required: true, message: '请选择上传文件' }],
+        //   helpMessage: '推荐大小:400 * 400 像素',
         //   itemProps: {
-        //     validateTrigger: 'blur',
+        //     validateTrigger: 'onBlur',
         //   },
-        //   componentProps: {
-        //     options: [
-        //       { label: '是', value: 1 },
-        //       { label: '否', value: 0 },
-        //     ],
+        //   // componentProps: {
+        //   //   api: uploadLogoApi,
+        //   //   maxNumber: 1,
+        //   //   maxSize: 10,
+        //   //   accept: ['jpg', 'jpeg', 'gif', 'png'],
+        //   //   afterFetch: function (data) {
+        //   //     console.log('data', data);
+        //   //     Reflect.set(data, 'url', data.message);
+        //   //     return data;
+        //   //   },
+        //   // },
+
+        //   colProps: {
+        //     span: 22,
         //   },
         // },
         {
@@ -241,14 +297,16 @@
       function onDataReceive(data) {
         // 方式1;
         console.log('useModalInner', data);
+        resetFields();
         setFieldsValue({
           ...data,
           roleId: data.roleId != 2 ? data.roleId : '',
         });
-        let setSchema = [
+        let setSchema: FormSchema[] = [
           {
             field: 'roleId',
             component: 'ApiSelect',
+            label: '角色',
             componentProps: {
               disabled: data.roleId == 2 ? false : data.id ? true : false,
               api: getRoleListByParam,
@@ -262,18 +320,24 @@
           },
           {
             field: 'phone',
+            label: '手机号',
+            component: 'Input',
             componentProps: {
               disabled: getCheckRole('plat_admin') ? false : data.id ? true : false,
             },
           },
           {
             field: 'companyId',
+            label: '公司',
+            component: 'ApiSelect',
             componentProps: {
               disabled: data.id ? (data.roleId != 2 ? true : false) : false,
             },
           },
           {
             field: 'permList',
+            component: 'ApiSelect',
+            label: '权益',
             componentProps: {
               disabled: data.roleId == 2 ? false : data.id ? true : false,
               params: {
@@ -307,6 +371,7 @@
         let res = await requestApi({
           ...data,
           userName: data.phone,
+          head: data.head,
           phone: data.phone,
           nickName: data.nickName,
           roleId: data.roleId,
@@ -318,6 +383,7 @@
         closeModal();
         resetFields();
       }
+      async function handleUpclose() {}
 
       return {
         register,
@@ -328,6 +394,7 @@
         handleOk,
         resetFields,
         permListOptions,
+        handleUpclose,
       };
     },
   });

+ 66 - 12
src/views/staff/list.vue

@@ -8,6 +8,12 @@
           @click="handleCreate"
           >新增</a-button
         >
+        <a-button
+          v-power="[RoleEnum.COMPANY_ADMIN, RoleEnum.PLAT_ADMIN]"
+          type="error"
+          @click="handleExport"
+          >导出数据</a-button
+        >
       </template>
       <template #headerTop v-if="getCheckRole([RoleEnum.COMPANY_ADMIN])">
         <Alert
@@ -24,6 +30,14 @@
       <template #createTime="{ record }">
         <Time :value="record.createTime" mode="datetime" />
       </template>
+      <template #head="{ record }">
+        <TableImg
+          :size="80"
+          :simpleShow="true"
+          style="border-radius: 50%"
+          :imgList="[record.head || headerImg]"
+        />
+      </template>
       <!-- , -->
       <template #action="{ record }">
         <TableAction
@@ -55,8 +69,16 @@
   </div>
 </template>
 <script lang="ts">
-  import { defineComponent, computed, onMounted, ref } from 'vue';
-  import { BasicTable, useTable, BasicColumn, FormProps, TableAction } from '/@/components/Table';
+  import headerImg from '/@/assets/images/header.jpg';
+  import { defineComponent, computed, onMounted, ref, reactive } from 'vue';
+  import {
+    BasicTable,
+    useTable,
+    BasicColumn,
+    FormProps,
+    TableAction,
+    TableImg,
+  } from '/@/components/Table';
   import { useMessage } from '/@/hooks/web/useMessage';
   import { useModal } from '/@/components/Modal';
   import { uploadApi } from '/@/api/sys/upload';
@@ -65,13 +87,14 @@
   import DelListModal from './delListModal.vue';
   import { Alert } from 'ant-design-vue';
   // import { h } from 'vue';
-  import { ListApi, delApi, preDelApi, getNumByStaff } from '/@/api/staff/list';
+  import { ListApi, delApi, preDelApi, getNumByStaff, exportApi } from '/@/api/staff/list';
   import { useI18n } from '/@/hooks/web/useI18n';
   // import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
   import { RoleEnum } from '/@/enums/roleEnum';
   import { useGo } from '/@/hooks/web/usePage';
   import { Time } from '/@/components/Time';
   import { useUserStore } from '/@/store/modules/user';
+  import { ExportParams } from '/@/api/staff/model';
   export default defineComponent({
     components: {
       BasicTable,
@@ -81,15 +104,17 @@
       DetailsModal,
       DelListModal,
       Alert,
+      TableImg,
     },
+    emits: ['register'],
     setup() {
       const [register, { openModal }] = useModal();
       const surplusSubNum = ref({
         lookNum: 0,
         shotNum: 0,
       });
-      const [registerDetail, { openModal: openDetaileModal }] = useModal();
-      const [registerDelList, { openModal: openDelListeModal }] = useModal();
+      const [registerDetail, { openModal: openDetailModal }] = useModal();
+      const [registerDelList, { openModal: openDelListModal }] = useModal();
       const { createConfirm, createMessage } = useMessage();
       const userStore = useUserStore();
       const { getCheckRole } = userStore;
@@ -101,6 +126,11 @@
         getNumByStaffData();
       });
 
+      let searchInfo: ExportParams = reactive({
+        staffName: '',
+        staffPhone: '',
+      });
+
       const columns: BasicColumn[] = [
         {
           title: 'ID',
@@ -146,6 +176,12 @@
             return record.permList && record.permList.join();
           },
         },
+        {
+          title: '头像',
+          dataIndex: 'head',
+          slots: { customRender: 'head' },
+          width: 130,
+        },
 
         {
           title: t('routes.staff.createTime'),
@@ -209,9 +245,9 @@
           return T;
         },
         bordered: true,
-        sortFn: (sortInfo) => {
-          let order = sortInfo.order && sortInfo.order.replace('end', '');
-          return { ...sortInfo, sidx: sortInfo.field, order: order };
+        searchInfo: searchInfo,
+        handleSearchInfoFn(data) {
+          searchInfo = Object.assign(searchInfo, data);
         },
       });
 
@@ -246,10 +282,10 @@
         // ) {
         //   return createMessage.error('新增失败,可添加员工数量为0个');
         // }
-        openDetaileModal(true, {});
+        openDetailModal(true, {});
       }
       function handleEdit(record: Recordable) {
-        openDetaileModal(true, record);
+        openDetailModal(true, record);
       }
       function getNumByStaffData() {
         getNumByStaff({}).then((res) => {
@@ -260,7 +296,7 @@
       async function handleDelete(record) {
         let check = await preDelApi(record.id); //
         if (Array.isArray(check)) {
-          return openDelListeModal(true, {
+          return openDelListModal(true, {
             ...record,
             option: check,
           });
@@ -280,13 +316,30 @@
           },
         });
       }
+      async function handleExport() {
+        const data = await exportApi(searchInfo);
+        const downloadBlob = new Blob([data], {
+          type: 'application/msexcel',
+        });
+        const url = URL.createObjectURL(downloadBlob);
+        const a: HTMLAnchorElement = document.createElement('a');
+        document.body.appendChild(a);
+        a.style.display = 'none';
+        a.href = url;
+        const name = new Date().getTime();
+        a.download = `${name}.csv`;
+        a.click();
+        window.URL.revokeObjectURL(url);
+      }
+
       return {
         registerTable,
         registerDetail,
         registerDelList,
-        openDelListeModal,
+        openDelListModal,
         createMessage,
         handDelconfirm,
+        headerImg,
         t,
         reload,
         go,
@@ -302,6 +355,7 @@
         surplusSubNum,
         getCheckRole,
         getNumByStaffData,
+        handleExport,
       };
     },
   });

+ 99 - 98
src/views/staff/setpaswordModal.vue

@@ -1,98 +1,99 @@
-<template>
-  <BasicModal
-    v-bind="$attrs"
-    @register="register"
-    @ok="handSubmit"
-    :title="t('routes.staff.updateBtn')"
-    @visible-change="handleVisibleChange"
-    @cancel="resetFields"
-  >
-    <div class="pt-3px pr-3px">
-      <BasicForm @register="registerForm" />
-    </div>
-  </BasicModal>
-</template>
-<script lang="ts">
-  import { useI18n } from '/@/hooks/web/useI18n';
-  import { encodeStr } from '/@/utils/encodeUtil';
-  import { defineComponent, nextTick } from 'vue';
-  import { BasicModal, useModalInner } from '/@/components/Modal';
-  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
-  import { updatePasswordApi } from '/@/api/sys/user';
-  import { useMessage } from '/@/hooks/web/useMessage';
-
-  const { createMessage } = useMessage();
-
-  export default defineComponent({
-    components: { BasicModal, BasicForm },
-    props: {
-      userData: { type: Object },
-    },
-    setup(props) {
-      const { t } = useI18n();
-      const schemas: FormSchema[] = [
-        {
-          field: 'id',
-          component: 'Input',
-          label: 'id',
-          show: false,
-        },
-        {
-          field: 'password',
-          component: 'StrengthMeter',
-          label: t('routes.staff.password'),
-          labelWidth: 120,
-          required: true,
-          colProps: { span: 18 },
-        },
-      ];
-
-      const [
-        registerForm,
-        {
-          setFieldsValue,
-          validate,
-          resetFields,
-          // setProps
-        },
-      ] = useForm({
-        labelWidth: 120,
-        schemas,
-        showActionButtonGroup: false,
-        actionColOptions: {
-          span: 24,
-        },
-      });
-
-      const [register, { closeModal }] = useModalInner((data) => {
-        data && onDataReceive(data);
-      });
-
-      function onDataReceive(data) {
-        setFieldsValue({
-          id: data.id,
-        });
-      }
-      async function handSubmit() {
-        const { id, password } = await validate();
-        await updatePasswordApi({ id, newPassword: encodeStr(window.btoa(password)) });
-        createMessage.success(t('common.optSuccess'));
-        resetFields();
-        closeModal();
-      }
-      function handleVisibleChange(v) {
-        v && props.userData && nextTick(() => onDataReceive(props.userData));
-      }
-
-      return {
-        handSubmit,
-        register,
-        t,
-        schemas,
-        registerForm,
-        handleVisibleChange,
-        resetFields,
-      };
-    },
-  });
-</script>
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    @ok="handSubmit"
+    :title="t('routes.staff.updateBtn')"
+    @visible-change="handleVisibleChange"
+    @cancel="resetFields"
+  >
+    <div class="pt-3px pr-3px">
+      <BasicForm @register="registerForm" />
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { encodeStr } from '/@/utils/encodeUtil';
+  import { defineComponent, nextTick } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { updatePasswordApi } from '/@/api/sys/user';
+  import { useMessage } from '/@/hooks/web/useMessage';
+
+  const { createMessage } = useMessage();
+
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['register'],
+    setup(props) {
+      const { t } = useI18n();
+      const schemas: FormSchema[] = [
+        {
+          field: 'id',
+          component: 'Input',
+          label: 'id',
+          show: false,
+        },
+        {
+          field: 'password',
+          component: 'StrengthMeter',
+          label: t('routes.staff.password'),
+          labelWidth: 120,
+          required: true,
+          colProps: { span: 18 },
+        },
+      ];
+
+      const [
+        registerForm,
+        {
+          setFieldsValue,
+          validate,
+          resetFields,
+          // setProps
+        },
+      ] = useForm({
+        labelWidth: 120,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+
+      function onDataReceive(data) {
+        setFieldsValue({
+          id: data.id,
+        });
+      }
+      async function handSubmit() {
+        const { id, password } = await validate();
+        await updatePasswordApi({ id, newPassword: encodeStr(window.btoa(password)) });
+        createMessage.success(t('common.optSuccess'));
+        resetFields();
+        closeModal();
+      }
+      function handleVisibleChange(v) {
+        v && props.userData && nextTick(() => onDataReceive(props.userData));
+      }
+
+      return {
+        handSubmit,
+        register,
+        t,
+        schemas,
+        registerForm,
+        handleVisibleChange,
+        resetFields,
+      };
+    },
+  });
+</script>

+ 18 - 16
src/views/sys/login/LoginForm.vue

@@ -16,16 +16,8 @@
         class="fix-auto-fill"
       />
     </FormItem>
-    <FormItem name="password" class="enter-x">
-      <InputPassword
-        size="large"
-        visibilityToggle
-        v-model:value="formData.password"
-        :placeholder="t('sys.login.password')"
-      />
-    </FormItem>
 
-    <!-- <ARow class="enter-x">
+    <!-- <ARow class="enter-x captcha-container">
       <ACol :span="12">
         <FormItem name="captcha">
           <Input
@@ -34,12 +26,18 @@
             :placeholder="t('sys.login.captcha')"
             class="captcha"
           />
+          <a-button class="btn" size="small" type="primary"> 获取手机验证码 </a-button>
         </FormItem>
       </ACol>
-      <ACol :span="12" :style="{ 'text-align': 'right' }">
-        <img class="captcha-img" :src="`http://192.168.0.47:8190/captcha.jpg?t=${Date.now()}`" />
-      </ACol>
     </ARow> -->
+    <FormItem name="password" class="enter-x">
+      <InputPassword
+        size="large"
+        visibilityToggle
+        v-model:value="formData.password"
+        :placeholder="t('sys.login.password')"
+      />
+    </FormItem>
 
     <FormItem name="drag" class="enter-x" :style="{ 'text-align': 'right' }">
       <BasicDragVerify
@@ -71,9 +69,9 @@
       <Button type="primary" size="large" block @click="handleLogin" :loading="loading">
         {{ t('sys.login.loginButton') }}
       </Button>
-      <!-- <Button size="large" class="mt-4 enter-x" block @click="handleRegister">
+      <Button size="large" class="mt-4 enter-x" block @click="handleRegister">
         {{ t('sys.login.registerButton') }}
-      </Button> -->
+      </Button>
     </FormItem>
 
     <!-- <ARow class="enter-x">
@@ -138,7 +136,7 @@
   const { prefixCls } = useDesign('login');
   const userStore = useUserStore();
   // setLoginState
-  const { getLoginState } = useLoginState();
+  const { getLoginState, setLoginState } = useLoginState();
   const { getFormRules } = useFormRules();
 
   const formRef = ref();
@@ -150,7 +148,7 @@
     account: '',
     password: '',
     // password: encodeStr(window.btoa('zfb123456')),
-    // captcha: '',
+    captcha: '',
   });
 
   const { validForm } = useFormValid(formRef);
@@ -164,6 +162,10 @@
     isDragValidate.value = true;
   }
 
+  async function handleRegister() {
+    setLoginState(LoginStateEnum.REGISTER);
+  }
+
   async function handleLogin() {
     const data = await validForm();
     if (!data) return;

+ 158 - 104
src/views/sys/login/RegisterForm.vue

@@ -1,104 +1,158 @@
-<template>
-  <template v-if="getShow">
-    <LoginFormTitle class="enter-x" />
-    <Form class="p-4 enter-x" :model="formData" :rules="getFormRules" ref="formRef">
-      <FormItem name="account" class="enter-x">
-        <Input
-          class="fix-auto-fill"
-          size="large"
-          v-model:value="formData.account"
-          :placeholder="t('sys.login.userName')"
-        />
-      </FormItem>
-      <FormItem name="mobile" class="enter-x">
-        <Input
-          size="large"
-          v-model:value="formData.mobile"
-          :placeholder="t('sys.login.mobile')"
-          class="fix-auto-fill"
-        />
-      </FormItem>
-      <FormItem name="sms" class="enter-x">
-        <CountdownInput
-          size="large"
-          class="fix-auto-fill"
-          v-model:value="formData.sms"
-          :placeholder="t('sys.login.smsCode')"
-        />
-      </FormItem>
-      <FormItem name="password" class="enter-x">
-        <StrengthMeter
-          size="large"
-          v-model:value="formData.password"
-          :placeholder="t('sys.login.password')"
-        />
-      </FormItem>
-      <FormItem name="confirmPassword" class="enter-x">
-        <InputPassword
-          size="large"
-          visibilityToggle
-          v-model:value="formData.confirmPassword"
-          :placeholder="t('sys.login.confirmPassword')"
-        />
-      </FormItem>
-
-      <FormItem class="enter-x" name="policy">
-        <!-- No logic, you need to deal with it yourself -->
-        <Checkbox v-model:checked="formData.policy" size="small">
-          {{ t('sys.login.policy') }}
-        </Checkbox>
-      </FormItem>
-
-      <Button
-        type="primary"
-        class="enter-x"
-        size="large"
-        block
-        @click="handleRegister"
-        :loading="loading"
-      >
-        {{ t('sys.login.registerButton') }}
-      </Button>
-      <Button size="large" block class="mt-4 enter-x" @click="handleBackLogin">
-        {{ t('sys.login.backSignIn') }}
-      </Button>
-    </Form>
-  </template>
-</template>
-<script lang="ts" setup>
-  import { reactive, ref, unref, computed } from 'vue';
-  import LoginFormTitle from './LoginFormTitle.vue';
-  import { Form, Input, Button, Checkbox } from 'ant-design-vue';
-  import { StrengthMeter } from '/@/components/StrengthMeter';
-  import { CountdownInput } from '/@/components/CountDown';
-  import { useI18n } from '/@/hooks/web/useI18n';
-  import { useLoginState, useFormRules, useFormValid, LoginStateEnum } from './useLogin';
-
-  const FormItem = Form.Item;
-  const InputPassword = Input.Password;
-  const { t } = useI18n();
-  const { handleBackLogin, getLoginState } = useLoginState();
-
-  const formRef = ref();
-  const loading = ref(false);
-
-  const formData = reactive({
-    account: '',
-    password: '',
-    confirmPassword: '',
-    mobile: '',
-    sms: '',
-    policy: false,
-  });
-
-  const { getFormRules } = useFormRules(formData);
-  const { validForm } = useFormValid(formRef);
-
-  const getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER);
-
-  async function handleRegister() {
-    const data = await validForm();
-    if (!data) return;
-    console.log(data);
-  }
-</script>
+<template>
+  <template v-if="getShow">
+    <LoginFormTitle class="enter-x" />
+    <Form class="p-4 enter-x" :model="formData" :rules="getFormRules" ref="formRef">
+      <FormItem name="userName" class="enter-x">
+        <Input
+          class="fix-auto-fill"
+          size="large"
+          autocomplete="off"
+          :readonly="preventAutoFill"
+          v-model:value="formData.userName"
+          placeholder="请输入手机号"
+        />
+      </FormItem>
+      <!-- <FormItem name="mobile" class="enter-x">
+        <Input
+          size="large"
+          v-model:value="formData.mobile"
+          :placeholder="t('sys.login.mobile')"
+          class="fix-auto-fill"
+        />
+      </FormItem> -->
+      <!-- :disabled="!reg.test(formData.userName)" -->
+      <FormItem name="code" class="enter-x">
+        <CountdownInput
+          size="large"
+          :buttonDisabled="!reg.test(formData.userName)"
+          autocomplete="off"
+          class="fix-auto-fill"
+          :count="60"
+          :readonly="preventAutoFill"
+          :sendCodeApi="
+            () => {
+              return reg.test(formData.userName)
+                ? sendCodeApi({
+                    phone: formData.userName,
+                  })
+                : falseSendcode;
+            }
+          "
+          v-model:value="formData.code"
+          placeholder="请输入手机验证码"
+        />
+      </FormItem>
+      <FormItem name="userPassword" class="enter-x">
+        <StrengthMeter
+          size="large"
+          autocomplete="off"
+          :readonly="preventAutoFill"
+          v-model:value="formData.userPassword"
+          placeholder="请输入账号密码"
+        />
+      </FormItem>
+      <!-- <FormItem name="confirmPassword" class="enter-x">
+        <InputPassword
+          size="large"
+          visibilityToggle
+          v-model:value="formData.confirmPassword"
+          :placeholder="t('sys.login.confirmPassword')"
+        />
+      </FormItem> -->
+      <!-- 
+      <FormItem class="enter-x" name="policy">
+        <Checkbox v-model:checked="formData.policy" size="small">
+          {{ t('sys.login.policy') }}
+        </Checkbox>
+      </FormItem> -->
+
+      <Button
+        type="primary"
+        class="enter-x"
+        size="large"
+        block
+        @click="handleRegister"
+        :loading="loading"
+      >
+        {{ t('sys.login.registerButton') }}
+      </Button>
+      <Button size="large" block class="mt-4 enter-x" @click="handleBackLogin">
+        {{ t('sys.login.backSignIn') }}
+      </Button>
+    </Form>
+  </template>
+</template>
+<script lang="ts" setup>
+  import { reactive, ref, unref, computed, watch } from 'vue';
+  import LoginFormTitle from './LoginFormTitle.vue';
+  // Checkbox
+  import { Form, Input, Button } from 'ant-design-vue';
+  import { StrengthMeter } from '/@/components/StrengthMeter';
+  import { CountdownInput } from '/@/components/CountDown';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useLoginState, useFormRules, useFormValid, LoginStateEnum } from './useLogin';
+  import { sendCodeApi, registerApi } from '/@/api/sys/user';
+  import { useMessage } from '/@/hooks/web/useMessage';
+
+  const FormItem = Form.Item;
+  // const InputPassword = Input.Password;
+  const { t } = useI18n();
+  const { handleBackLogin, getLoginState } = useLoginState();
+
+  const formRef = ref();
+  const loading = ref(false);
+  const preventAutoFill = ref(true);
+  const formData = reactive({
+    userName: '',
+    userPassword: '',
+    // confirmPassword: '',
+    // mobile: '',
+    area: '86',
+    code: '',
+    // policy: false,
+  });
+  const { createMessage } = useMessage();
+  const falseSendcode = ref(
+    new Promise<boolean>((r) => {
+      return r(false);
+    }),
+  );
+  const reg = ref(new RegExp(/^1\d{10}$/));
+  const { getFormRules } = useFormRules(formData);
+  const { validForm } = useFormValid(formRef);
+
+  const getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER);
+
+  watch(
+    () => getShow,
+    (data) => {
+      console.log('getShow', data.value);
+      if (data.value) {
+        console.log('getFormRules', getFormRules);
+        setTimeout(() => {
+          preventAutoFill.value = !data.value;
+          formData.userName = '';
+          formData.userPassword = '';
+          formData.code = '';
+        }, 500);
+      }
+    },
+    {
+      immediate: true,
+      deep: true,
+    },
+  );
+
+  async function handleRegister() {
+    console.log('getFormRules', getFormRules);
+    const data = await validForm();
+    if (!data) return;
+    console.log(data);
+    const res = await registerApi(data);
+    if (res.code === 200) {
+      handleBackLogin();
+    } else {
+      createMessage.error(res.message);
+    }
+  }
+</script>

+ 161 - 134
src/views/sys/login/useLogin.ts

@@ -1,134 +1,161 @@
-import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
-import type { RuleObject } from 'ant-design-vue/lib/form/interface';
-import { ref, computed, unref, Ref } from 'vue';
-import { useI18n } from '/@/hooks/web/useI18n';
-
-export enum LoginStateEnum {
-  LOGIN,
-  REGISTER,
-  RESET_PASSWORD,
-  MOBILE,
-  QR_CODE,
-}
-
-const currentState = ref(LoginStateEnum.LOGIN);
-
-export function useLoginState() {
-  function setLoginState(state: LoginStateEnum) {
-    currentState.value = state;
-  }
-
-  const getLoginState = computed(() => currentState.value);
-
-  function handleBackLogin() {
-    setLoginState(LoginStateEnum.LOGIN);
-  }
-
-  return { setLoginState, getLoginState, handleBackLogin };
-}
-
-export function useFormValid<T extends Object = any>(formRef: Ref<any>) {
-  async function validForm() {
-    const form = unref(formRef);
-    if (!form) return;
-    const data = await form.validate();
-    return data as T;
-  }
-
-  return { validForm };
-}
-
-export function useFormRules(formData?: Recordable) {
-  const { t } = useI18n();
-
-  const getAccountFormRule = computed(() => createRule(t('sys.login.accountPlaceholder')));
-  const getPasswordFormRule = computed(() => {
-    return [
-      ...createRule(t('sys.login.passwordPlaceholder')),
-      { validator: validatePassword(), trigger: 'change' },
-    ];
-  });
-  const getSmsFormRule = computed(() => createRule(t('sys.login.smsPlaceholder')));
-  const getMobileFormRule = computed(() => createRule(t('sys.login.mobilePlaceholder')));
-
-  const validatePolicy = async (_: RuleObject, value: boolean) => {
-    return !value ? Promise.reject(t('sys.login.policyPlaceholder')) : Promise.resolve();
-  };
-  const validatePassword = () => {
-    return async (_: RuleObject, value: string) => {
-      const reg = new RegExp('[\\u4E00-\\u9FFF]+', 'g');
-      if (reg.test(value)) {
-        return Promise.reject(t('sys.login.passwordFormat'));
-      }
-      return Promise.resolve();
-    };
-  };
-  const validateConfirmPassword = (password: string) => {
-    return async (_: RuleObject, value: string) => {
-      if (!value) {
-        return Promise.reject(t('sys.login.passwordPlaceholder'));
-      }
-      if (value !== password) {
-        return Promise.reject(t('sys.login.diffPwd'));
-      }
-      return Promise.resolve();
-    };
-  };
-
-  const getFormRules = computed((): { [k: string]: ValidationRule | ValidationRule[] } => {
-    const accountFormRule = unref(getAccountFormRule);
-    const passwordFormRule = unref(getPasswordFormRule);
-    const smsFormRule = unref(getSmsFormRule);
-    const mobileFormRule = unref(getMobileFormRule);
-
-    const mobileRule = {
-      sms: smsFormRule,
-      mobile: mobileFormRule,
-    };
-    switch (unref(currentState)) {
-      // register form rules
-      case LoginStateEnum.REGISTER:
-        return {
-          account: accountFormRule,
-          password: [
-            ...passwordFormRule,
-            { validator: validatePassword(formData?.password), trigger: 'change' },
-          ],
-          confirmPassword: [
-            { validator: validateConfirmPassword(formData?.password), trigger: 'change' },
-          ],
-          policy: [{ validator: validatePolicy, trigger: 'change' }],
-          ...mobileRule,
-        };
-
-      // reset password form rules
-      case LoginStateEnum.RESET_PASSWORD:
-        return {
-          account: accountFormRule,
-          ...mobileRule,
-        };
-
-      // mobile form rules
-      case LoginStateEnum.MOBILE:
-        return mobileRule;
-
-      // login form rules
-      default:
-        return {
-          account: accountFormRule,
-          password: passwordFormRule,
-        };
-    }
-  });
-  return { getFormRules };
-}
-
-function createRule(message: string) {
-  return [
-    {
-      required: true,
-      message,
-      trigger: 'change',
-    },
-  ];
-}
+import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
+import type { RuleObject } from 'ant-design-vue/lib/form/interface';
+import { ref, computed, unref, Ref } from 'vue';
+import { useI18n } from '/@/hooks/web/useI18n';
+
+export enum LoginStateEnum {
+  LOGIN,
+  REGISTER,
+  RESET_PASSWORD,
+  MOBILE,
+  QR_CODE,
+}
+
+const currentState = ref(LoginStateEnum.LOGIN);
+
+export function useLoginState() {
+  function setLoginState(state: LoginStateEnum) {
+    currentState.value = state;
+  }
+
+  const getLoginState = computed(() => currentState.value);
+
+  function handleBackLogin() {
+    setLoginState(LoginStateEnum.LOGIN);
+  }
+
+  return { setLoginState, getLoginState, handleBackLogin };
+}
+
+export function useFormValid<T extends Object = any>(formRef: Ref<any>) {
+  async function validForm() {
+    const form = unref(formRef);
+    if (!form) return;
+    const data = await form.validate();
+    return data as T;
+  }
+
+  return { validForm };
+}
+// formData?: Recordable
+export function useFormRules() {
+  const { t } = useI18n();
+
+  const getAccountFormRule = computed(() => createRule(t('sys.login.accountPlaceholder')));
+  const getPasswordFormRule = computed(() => {
+    return [
+      ...createRule(t('sys.login.passwordPlaceholder')),
+      { validator: validatePassword(), trigger: 'change' },
+    ];
+  });
+  const getRegisterPasswordFormRule = computed(() => {
+    return [
+      ...createRule(t('sys.login.passwordPlaceholder')),
+      { validator: registerValidatePassword(), trigger: 'change' },
+    ];
+  });
+  const getSmsFormRule = computed(() => createRule(t('sys.login.smsPlaceholder')));
+  // const getMobileFormRule = computed(() => createRule(t('sys.login.mobilePlaceholder')));
+  // console.log('formData', formData);
+  const getMobileFormRule = computed(() => {
+    return [
+      ...createRule(t('sys.login.mobilePlaceholder')),
+      { validator: validateMobile(), trigger: 'change' },
+    ];
+  });
+
+  // const validatePolicy = async (_: RuleObject, value: boolean) => {
+  //   return !value ? Promise.reject(t('sys.login.policyPlaceholder')) : Promise.resolve();
+  // };
+  const validateMobile = () => {
+    return async (_: RuleObject, value: string) => {
+      const reg = new RegExp(/^1\d{10}$/);
+      if (!reg.test(value)) {
+        if (value) {
+          return Promise.reject(t('sys.login.mobileFormat'));
+        }
+      }
+      return Promise.resolve();
+    };
+  };
+  const validatePassword = () => {
+    return async (_: RuleObject, value: string) => {
+      const reg = new RegExp('[\\u4E00-\\u9FFF]+', 'g');
+      if (reg.test(value)) {
+        return Promise.reject(t('sys.login.passwordFormat'));
+      }
+      return Promise.resolve();
+    };
+  };
+  const registerValidatePassword = () => {
+    return async (_: RuleObject, value: string) => {
+      // const reg = new RegExp('[\\u4E00-\\u9FFF]+', 'g');
+      const reg = /^(?=.*[0-9\!@#\$%\^&\*])(?=.*[a-zA-Z]).{8,16}$/;
+      if (!reg.test(value) && value?.length) {
+        return Promise.reject('请输入8-16位数字和字母组合密码');
+      }
+      return Promise.resolve();
+    };
+  };
+  // const validateConfirmPassword = (password: string) => {
+  //   return async (_: RuleObject, value: string) => {
+  //     if (!value) {
+  //       return Promise.reject(t('sys.login.passwordPlaceholder'));
+  //     }
+  //     if (value !== password) {
+  //       return Promise.reject(t('sys.login.diffPwd'));
+  //     }
+  //     return Promise.resolve();
+  //   };
+  // };
+
+  const getFormRules = computed((): { [k: string]: ValidationRule | ValidationRule[] } => {
+    const accountFormRule = unref(getAccountFormRule);
+    const passwordFormRule = unref(getPasswordFormRule);
+    const registerPasswordFormRule = unref(getRegisterPasswordFormRule);
+    const smsFormRule = unref(getSmsFormRule);
+    const mobileFormRule = unref(getMobileFormRule);
+
+    const mobileRule = {
+      code: smsFormRule,
+    };
+    switch (unref(currentState)) {
+      // register form rules
+      case LoginStateEnum.REGISTER:
+        return {
+          userName: mobileFormRule,
+          userPassword: [...registerPasswordFormRule],
+          ...mobileRule,
+        };
+
+      // // reset password form rules
+      // case LoginStateEnum.RESET_PASSWORD:
+      //   return {
+      //     account: accountFormRule,
+      //     ...mobileRule,
+      //   };
+
+      // // mobile form rules
+      // case LoginStateEnum.MOBILE:
+      //   return mobileRule;
+
+      // login form rules
+      default:
+        return {
+          account: accountFormRule,
+          password: passwordFormRule,
+        };
+    }
+  });
+  return { getFormRules };
+}
+
+function createRule(message: string) {
+  return [
+    {
+      required: true,
+      message,
+      trigger: 'change',
+    },
+  ];
+}

+ 9 - 9
tests/server/package.json

@@ -10,7 +10,7 @@
     "stop": "npx pm2 stop ecosystem.config.js"
   },
   "dependencies": {
-    "fs-extra": "^10.0.0",
+    "fs-extra": "^10.1.0",
     "koa": "^2.13.4",
     "koa-body": "^4.2.0",
     "koa-bodyparser": "^4.3.0",
@@ -21,16 +21,16 @@
     "koa2-cors": "^2.0.6"
   },
   "devDependencies": {
-    "@types/koa": "^2.13.4",
+    "@types/koa": "^2.13.5",
     "@types/koa-bodyparser": "^5.0.2",
     "@types/koa-router": "^7.4.4",
-    "@types/node": "^16.11.6",
-    "nodemon": "^2.0.14",
-    "pm2": "^5.1.2",
+    "@types/node": "^16.11.59",
+    "nodemon": "^2.0.20",
+    "pm2": "^5.2.0",
     "rimraf": "^3.0.2",
-    "ts-node": "^10.4.0",
-    "tsconfig-paths": "^3.11.0",
-    "tsup": "^5.5.0",
-    "typescript": "^4.4.4"
+    "ts-node": "^10.9.1",
+    "tsconfig-paths": "^3.14.1",
+    "tsup": "^5.12.9",
+    "typescript": "^4.8.3"
   }
 }

+ 0 - 0
tests/server/pnpm-lock.yaml


Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini