From cd3350bd2c05d0501e44df017af3e1479f71b9f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=91=E6=96=87=E5=8F=AF?= <1041367524@qq.com> Date: Fri, 1 Apr 2022 10:46:11 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20API=E8=B0=83=E7=94=A8=E9=87=8D?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.beta | 2 + .gitlab-ci.yml | 4 + README.md | 11 +- TODO.md | 12 ++ package.json | 7 +- src/api/auth.js | 4 +- src/api/system/user.js | 78 +++------ src/components/ElUploadImage.vue | 147 +++++++++++++++++ src/components/TableList.vue | 4 +- src/components/extra/ElImage.vue | 75 ++++++--- src/components/extra/ElUploadImage.vue | 212 ------------------------- src/layouts/components/aside.vue | 16 +- src/layouts/components/menu.vue | 18 ++- src/layouts/components/view.vue | 14 +- src/plugins/global-api.js | 24 +++ src/router/index.js | 2 +- src/router/modules/system.js | 16 +- src/store/modules/{ => core}/auth.js | 0 src/store/modules/{ => core}/layout.js | 4 +- src/store/modules/{ => core}/local.js | 0 src/store/modules/demo.js | 20 --- src/store/modules/system/user.js | 17 +- src/utils/request.js | 46 +++--- src/views/global/login.vue | 43 ++--- src/views/home/index.vue | 38 +---- src/views/system/user/form.vue | 102 ++++++------ src/views/system/user/index.vue | 117 ++++++-------- vite.config.js | 3 +- 28 files changed, 490 insertions(+), 546 deletions(-) create mode 100644 .env.beta create mode 100644 .gitlab-ci.yml create mode 100644 src/components/ElUploadImage.vue delete mode 100644 src/components/extra/ElUploadImage.vue rename src/store/modules/{ => core}/auth.js (100%) rename src/store/modules/{ => core}/layout.js (95%) rename src/store/modules/{ => core}/local.js (100%) delete mode 100644 src/store/modules/demo.js diff --git a/.env.beta b/.env.beta new file mode 100644 index 0000000..96f5616 --- /dev/null +++ b/.env.beta @@ -0,0 +1,2 @@ +VITE_BASE_URL=https://gateway.mashibing.cn +VITE_REQUEST_TIMEOUT=20000 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..6b3218f --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,4 @@ +include: + - project: 'yunwei/cicd/xuexipingtai/entrypoint' + ref: main + file: '/main.yml' diff --git a/README.md b/README.md index b8e6920..78bd51f 100644 --- a/README.md +++ b/README.md @@ -163,13 +163,16 @@ proxy.$copy('hello world'); | copy | 复制文本 | | download | 下载指定地址文件 | | excel | 导出 excel 文件并下载 | +| dict | 字典查询 | +| name | 昵称和姓名拼接显示 | ## 全局组件 -| 名称 | 功能介绍 | -| --------- | ------------ | -| ElEditor | 富文本编辑器 | -| TableList | 通用列表组件 | +| 名称 | 功能介绍 | +| ------------- | ------------ | +| ElEditor | 富文本编辑器 | +| ElUploadImage | 图片上传 | +| TableList | 通用列表组件 | ## 心得总结 diff --git a/TODO.md b/TODO.md index f14ba7d..12c9623 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,9 @@ +# 待解决 + +## 多个路由引用同一个 vue 页面时,打开这几个路由后就会有多个 TAB 页,切换 TAB 页时因为这几个路由引用的其实是同一个组件,所以初始数据会保持一致。比如新增页面应该是没有数据的,编辑数据需要回显,但是先打开新增页面再打开编辑页面,新增页面就会同步为编辑页面。 + +# 踩坑日记 + ## perttier 保存时不会自动格式化属性排序、需要执行命令才能格式化 > xwk 2022.3.23 @@ -20,6 +26,12 @@ > xwk 0328 > el-tabs 使用 label 插槽,应该是 ElementPlus 的 BUG,[Github 已有 Issues](https://github.com/element-plus/element-plus/issues/6839) +> ElementPlus 已在 2.1.7 版本修复了这个 BUG + +## Uncaught (in promise) TypeError: Cannot read properties of null (reading 'shapeFlag') + +> xwk 0330 +> 对 props 使用对象展开符可能会展开 null ## 偶现 SVG 图标颜色渲染异常 diff --git a/package.json b/package.json index ce04835..b28dd06 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "scripts": { "dev": "vite", "build:test": "vite build --mode test", - "build:preview": "vite build --mode preview", + "build:beta": "vite build --mode beta", "build:prod": "vite build --mode prod", "preview": "vite preview", "prepare": "husky install", @@ -20,14 +20,15 @@ "@vueup/vue-quill": "^1.0.0-beta.8", "axios": "^0.26.1", "dayjs": "^1.11.0", - "element-plus": "^2.1.2", + "element-plus": "^2.1.7", "lodash": "^4.17.21", "qs": "^6.10.3", "quill-image-uploader": "^1.2.2", "sortablejs": "^1.14.0", "vue": "^3.2.25", "vue-router": "^4.0.14", - "vuex": "^4.0.2" + "vuex": "^4.0.2", + "vuex-persistedstate": "^4.1.0" }, "devDependencies": { "@commitlint/cli": "^13.2.1", diff --git a/src/api/auth.js b/src/api/auth.js index 0f2d6bb..976174c 100644 --- a/src/api/auth.js +++ b/src/api/auth.js @@ -11,7 +11,7 @@ export function sendSmsCode(params) { // 登录 export async function login(data) { return request({ - url: '/uaa/sso/appManageLogin', + url: '/user/loginByPasswordToB', method: 'post', data, }); @@ -19,7 +19,7 @@ export async function login(data) { // 获取用户信息 export function getUserInfo() { return request({ - url: '/uc/user/v1/info/token', + url: '/user/current', method: 'get', }); } diff --git a/src/api/system/user.js b/src/api/system/user.js index 7512a7b..25b0474 100644 --- a/src/api/system/user.js +++ b/src/api/system/user.js @@ -1,59 +1,29 @@ -const mock = (data) => - new Promise((resolve) => { - setTimeout(() => { - resolve(data); - }, Math.random() * 1500 + 500); +import request from '@/utils/request.js'; +export const search = (params) => { + return request({ + url: '/employee', + method: 'get', + params, }); -let list = [ - { - id: 1, - username: 'user001', - nickname: '张三', - sex: 1, - avatar: 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic%2Ffd%2Ff1%2Fda%2Ffdf1dacb8ff0b8f13ed29bcbee42f328.jpeg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1650201540&t=ba213738d8f11e79302fab71602856f2', - loginTime: Date.now(), - enabled: true, - }, - { - id: 2, - username: 'user003', - nickname: '李四', - sex: 0, - avatar: 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic%2Ffd%2Ff1%2Fda%2Ffdf1dacb8ff0b8f13ed29bcbee42f328.jpeg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1650201540&t=ba213738d8f11e79302fab71602856f2', - loginTime: Date.now(), - enabled: true, - }, - { - id: 3, - username: 'user003', - nickname: '王五', - sex: 1, - avatar: 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic%2Ffd%2Ff1%2Fda%2Ffdf1dacb8ff0b8f13ed29bcbee42f328.jpeg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1650201540&t=ba213738d8f11e79302fab71602856f2', - loginTime: Date.now(), - enabled: false, - }, -]; -export const findUserList = (data) => { - console.info(data); - return mock({ content: list, totalElements: list.length }); }; -export const createUser = (data) => { - data = { - id: new Date().getTime(), - ...data, - }; - list.push(data); - return mock(data); -}; -export const updateUser = (data) => { - let old = list.find((item) => item.id === data.id); - Object.assign(old, data); - return mock(old); +export const create = (data) => { + return request({ + url: '/employee', + method: 'post', + data, + }); }; -export const removeUser = (ids) => { - list = list.filter((item) => !ids.includes(item.id)); - return mock(true); +export const update = (data) => { + return request({ + url: '/employee', + method: 'put', + data, + }); }; -export const getUserDetail = (id) => { - return mock(list.find((item) => item.id === id)); +export const remove = (params) => { + return request({ + url: '/employee', + method: 'delete', + params, + }); }; diff --git a/src/components/ElUploadImage.vue b/src/components/ElUploadImage.vue new file mode 100644 index 0000000..43c61fd --- /dev/null +++ b/src/components/ElUploadImage.vue @@ -0,0 +1,147 @@ + + + diff --git a/src/components/TableList.vue b/src/components/TableList.vue index 7ac12e1..23c6a20 100644 --- a/src/components/TableList.vue +++ b/src/components/TableList.vue @@ -2,8 +2,8 @@ - diff --git a/src/components/extra/ElUploadImage.vue b/src/components/extra/ElUploadImage.vue deleted file mode 100644 index 60ef9d7..0000000 --- a/src/components/extra/ElUploadImage.vue +++ /dev/null @@ -1,212 +0,0 @@ - - - diff --git a/src/layouts/components/aside.vue b/src/layouts/components/aside.vue index 90ba8a4..438832c 100644 --- a/src/layouts/components/aside.vue +++ b/src/layouts/components/aside.vue @@ -12,7 +12,7 @@ diff --git a/src/layouts/components/menu.vue b/src/layouts/components/menu.vue index ceeaf69..c8dbad3 100644 --- a/src/layouts/components/menu.vue +++ b/src/layouts/components/menu.vue @@ -10,8 +10,8 @@ - + diff --git a/src/plugins/global-api.js b/src/plugins/global-api.js index 1b620ce..1fecb3d 100644 --- a/src/plugins/global-api.js +++ b/src/plugins/global-api.js @@ -64,7 +64,31 @@ export const excel = (fileName, data) => { navigator.msSaveBlob(blob, fileName); } }; +/** + * 字典查询 + * @param {*} dict 字典数据 + * @param {*} value 字典值 + * @param {*} opts 配置 + * @returns 字典标签 + */ +export const dict = (dict, value, opts = {}) => { + opts = { label: 'label', value: 'value', default: '未知', ...opts }; + return (dict ? dict.value || dict : []).find((item) => item[opts.value] === value)?.[opts.label] || opts.default; +}; +/** + * 显示姓名 + * @param {*} nickname 昵称 + * @param {*} realname 真实姓名 + * @returns 组合显示 + */ +export const name = (nickname, realname) => { + return realname ? `${nickname}(${realname})` : nickname; +}; export default (app) => { app.config.globalProperties.$copy = copy; + app.config.globalProperties.$download = download; + app.config.globalProperties.$excel = excel; + app.config.globalProperties.$dict = dict; + app.config.globalProperties.$name = name; }; diff --git a/src/router/index.js b/src/router/index.js index fb85255..40a8c9e 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -81,10 +81,10 @@ router.onError((error, to) => { router.beforeEach(async (to, from, next) => { if (store.state.local.token) { if (!store.state.auth.permission.length) { + await store.dispatch('auth/getUserInfo'); if (config.useLocalRouter) { store.commit('auth/setPermission', routes); } else { - await store.dispatch('auth/getUserInfo'); await store.dispatch('auth/getPermission'); } next({ ...to, replace: true }); diff --git a/src/router/modules/system.js b/src/router/modules/system.js index dd51137..7a38efc 100644 --- a/src/router/modules/system.js +++ b/src/router/modules/system.js @@ -15,7 +15,7 @@ export default [ component: () => import('@/views/system/user/index.vue'), meta: { title: '用户管理', - icon: 'user-1-fill', + icon: 'Avatar', }, children: [ { @@ -24,7 +24,7 @@ export default [ component: () => import('@/views/system/user/form.vue'), meta: { title: '创建用户', - icon: 'user-1-fill', + icon: 'Avatar', hidden: true, }, }, @@ -34,17 +34,7 @@ export default [ component: () => import('@/views/system/user/form.vue'), meta: { title: '编辑用户', - icon: 'user-1-fill', - hidden: true, - }, - }, - { - path: 'detail/:id', - name: 'UserDetail', - component: () => import('@/views/system/user/form.vue'), - meta: { - title: '用户详情', - icon: 'user-1-fill', + icon: 'Avatar', hidden: true, }, }, diff --git a/src/store/modules/auth.js b/src/store/modules/core/auth.js similarity index 100% rename from src/store/modules/auth.js rename to src/store/modules/core/auth.js diff --git a/src/store/modules/layout.js b/src/store/modules/core/layout.js similarity index 95% rename from src/store/modules/layout.js rename to src/store/modules/core/layout.js index b99a0d4..ed987fd 100644 --- a/src/store/modules/layout.js +++ b/src/store/modules/core/layout.js @@ -16,9 +16,9 @@ const getters = { return rootState.auth.permission.filter((item) => item.meta.global !== true); }, menuList: (state) => { - return state.menuList.filter((item) => item.meta.hidden !== true); + return state.menuList?.filter((item) => item.meta.hidden !== true); }, - collapseMenu: (state, getters) => state.collapseMenu || getters.menuList.length < 2, + collapseMenu: (state, getters) => state.collapseMenu || getters.menuList?.length < 2, activeAsideName: (state, getters) => getters.asideList.find((item) => item.name === state.activeAside)?.meta.title, returnUrl: (state) => { let res = state.returnUrl || '/'; diff --git a/src/store/modules/local.js b/src/store/modules/core/local.js similarity index 100% rename from src/store/modules/local.js rename to src/store/modules/core/local.js diff --git a/src/store/modules/demo.js b/src/store/modules/demo.js deleted file mode 100644 index 2819b02..0000000 --- a/src/store/modules/demo.js +++ /dev/null @@ -1,20 +0,0 @@ -const state = () => ({ - count: 0, -}); -const getters = { - doubleCount: (state) => state.count * 2, -}; -const mutations = { - add: (state, num = 1) => (state.count += num), -}; -const actions = { - clear: ({ state, commit }) => { - commit('add', state.count * -1); - }, -}; -export default { - state, - getters, - mutations, - actions, -}; diff --git a/src/store/modules/system/user.js b/src/store/modules/system/user.js index c864f45..e376a9f 100644 --- a/src/store/modules/system/user.js +++ b/src/store/modules/system/user.js @@ -20,7 +20,7 @@ const mutations = { }; const actions = { search: async ({ state, commit, rootGetters }) => { - let res = await api.findUserList({ ...rootGetters['local/page'](state.code), ...state.condition }); + let res = await api.search({ ...rootGetters['local/page'](state.code), ...state.condition }); if (res) { commit('setList', res.content); commit('setTotal', res.totalElements); @@ -39,15 +39,18 @@ const actions = { ], }); }, - detail: async (context, id) => { - let res = await api.getUserDetail(id); + detail: async ({ dispatch }, id) => { + if (!state.list.length) { + await dispatch('search'); + } + let res = state.list.find((item) => item.id === id); if (!res) { ElMessage.error('加载详情失败'); } return res; }, save: async ({ dispatch }, data) => { - let save = data.id ? api.updateUser : api.createUser; + let save = data.id ? api.update : api.create; let res = await save(data); if (res) { ElMessage.success('保存成功'); @@ -57,13 +60,13 @@ const actions = { } return res; }, - remove: async ({ dispatch }, ids) => { - if (!ids.length) { + remove: async ({ dispatch }, idList) => { + if (!idList.length) { ElMessage.warning('请选择要删除的数据'); } else { try { await ElMessageBox.confirm('数据删除后无法恢复,确定要删除吗?', '危险操作'); - let res = await api.removeUser(ids); + let res = await api.remove({ idList }); if (res) { ElMessage.success('删除成功'); dispatch('search'); diff --git a/src/utils/request.js b/src/utils/request.js index aae5e0a..b53e97c 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -1,34 +1,44 @@ import config from '@/configs'; +import { ElMessage } from '@/plugins/element-plus'; import store from '@/store'; import qs from 'qs'; -import { ElMessage } from '@/plugins/element-plus'; -const handleResponse = async ({ config, headers, data, status }) => { +const handleResponse = async ({ config: requestConfig, headers, data, status }) => { if ( ['application/octet-stream', 'application/zip'].indexOf(headers['content-type']) !== -1 || - config['down'] === 'file' + requestConfig['down'] === 'file' ) { return data; } let code = data.code || status; - console.info('[api]', code, config.method, config.url, data.data); - if (code !== 200) { - ElMessage.error(data.msg || '服务器异常'); + console.info('[api]', code, requestConfig.method, requestConfig.url, data.data); + if (code !== 'SUCCESS') { + ElMessage.error(data.message || '服务器异常'); switch (code) { - case 500: - case 501: + case 'SYSTEM_ERROR': //系统异常 + case 'INTERFACE_SYSTEM_ERROR': // 外部接口调用异常 + case 'CONNECT_TIME_OUT': // 业务连接处理超时 + case 'NULL_ARGUMENT': // 参数为空 + case 'ILLEGAL_ARGUMENT': // 参数不合法 + case 'ILLEGAL_CONFIGURATION': // 非法配置 + case 'ILLEGAL_STATE': // 非法状态 + case 'ENUM_CODE_ERROR': // 错误的枚举编码 + case 'LOGIC_ERROR': // 逻辑错误 + case 'CONCURRENT_ERROR': // 并发异常 + case 'ILLEGAL_OPERATION': // 非法操作 + case 'REPETITIVE_OPERATION': // 重复操作 + case 'RESOURCE_NOT_FOUND': // 资源不存在 + case 'RESOURCE_ALREADY_EXIST': // 资源已存在 + case 'TYPE_UN_MATCH': // 类型不匹配 + case 'FILE_NOT_EXIST': // 文件不存在 + case 'LIMIT_BLOCK': // 请求被限流 + case 'BIZ_ERROR': // 业务处理异常 break; - case 524: - case 525: - case 50008: - case 50012: - case 50014: + case 'TOKEN_EXPIRE': // TOKEN过期 + case 'TOKEN_FAIL': // TOKEN失效 + case 'NO_OPERATE_PERMISSION': // 无操作权限 store.dispatch('auth/logout'); break; - case 404: - case 9999: - console.warn('接口9999', config); - break; default: } } @@ -47,7 +57,7 @@ instance.interceptors.request.use( (config) => { const token = store.state.local.token; if (token) { - config.headers['Authorization'] = `Bearer ${token}`; + config.headers['Authorization'] = token; } if (config.data && config.headers['Content-Type'] === 'application/x-www-form-urlencoded;charset=UTF-8') { config.data = qs.stringify(config.data); diff --git a/src/views/global/login.vue b/src/views/global/login.vue index 68993cc..04d0a2f 100644 --- a/src/views/global/login.vue +++ b/src/views/global/login.vue @@ -21,7 +21,7 @@ - + 立即登录 @@ -42,40 +42,41 @@ + - + diff --git a/src/views/system/user/form.vue b/src/views/system/user/form.vue index 0c7e9a0..86f07d7 100644 --- a/src/views/system/user/form.vue +++ b/src/views/system/user/form.vue @@ -4,39 +4,38 @@ ref="refsForm" v-loading="loading" class="form-content" - :disabled="route.name === 'UserDetail'" label-width="100px" :model="form" :rules="rules" > - - + + - - + + - - + + - - + + - - + + + + + + + + + + + @@ -52,46 +51,47 @@ const submitting = ref(false); const refsForm = ref(null); const form = reactive({ - username: null, - nickname: null, - sex: 0, - avatar: null, - enabled: true, + id: null, + name: null, + platform: null, + voucherClueUrls: [], + phone: null, + qqNo: null, + wechatNo: null, + addFriendsTime: null, + remarks: null, }); const rules = reactive({ - username: [{ required: true, message: '用户名不能为空' }], - nickname: [{ required: true, message: '昵称不能为空' }], - sex: [{ required: true, message: '性别不能为空' }], - avatar: [{ required: true, message: '头像不能为空' }], - enabled: [{ required: true, message: '状态不能为空' }], + name: [{ required: true, message: '客户名称不能为空' }], }); - const opts = computed(() => store.state.user.opts); + const opts = computed(() => store.state.customerPool.opts); if (!unref(opts).init) { - store.dispatch('user/load'); + store.dispatch('customerPool/load'); } - /* 详情 */ - watch( - () => [route.name, route.params.id], - async (value) => { - // 不论当前是哪个路由只要名字相同的参数改变就会触发watch,所以需要加上当前路由名字判断,或者可以取独一无二的路由参数名 - if (['UpdateUser', 'UserDetail'].includes(value[0]) && value[1]) { - loading.value = true; - let res = await store.dispatch('user/detail', +value[1]); - if (res) { - Object.assign(form, res); - } - loading.value = false; + /* 数据 */ + const handleLoad = async () => { + if (route.params.id) { + const id = +route.params.id; + if (form.id !== id) { + let res = await store.dispatch('customerPool/detail', id); + Object.assign(form, res); } - }, - { immediate: true } - ); + } + }; + onActivated(handleLoad); /* 交互 */ const handleSave = async () => { submitting.value = true; try { await unref(refsForm).validate(); - let res = await store.dispatch('user/save', unref(form)); + let data = { ...unref(form) }; + data.voucherClueUrls = data.voucherClueUrls || []; + data.voucherClueUrl = data.voucherClueUrls.join(','); + let res = await store.dispatch('customerPool/save', data); if (res) { + if (!data.id) { + unref(refsForm).resetFields(); + } handleClose(); } } catch (e) { @@ -108,7 +108,7 @@ } }; const handleClose = () => { - router.push({ name: 'UserManagement' }); + router.push({ name: 'CustomerPool' }); }; diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index 94389ea..82c2bd3 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -4,6 +4,7 @@ :code="code" :config="config" :data="list" + :operation="['create', 'search']" :reset="handleReset" title="用户" :total="total" @@ -13,8 +14,11 @@ > @@ -22,6 +26,7 @@