From 26e15ef2e81fbc6577fb9d1cac305244b133de3a Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 11 Mar 2023 00:46:21 +0800 Subject: [PATCH 001/184] =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9A=E4=BC=98?= =?UTF-8?q?=E5=8C=96=20config=20=E7=9A=84=E5=88=97=E8=A1=A8=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ContentWrap/src/ContentWrap.vue | 2 +- src/views/infra/config/index.vue | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/ContentWrap/src/ContentWrap.vue b/src/components/ContentWrap/src/ContentWrap.vue index 51e50410..6890e214 100644 --- a/src/components/ContentWrap/src/ContentWrap.vue +++ b/src/components/ContentWrap/src/ContentWrap.vue @@ -13,7 +13,7 @@ defineProps({ </script> <template> - <ElCard :class="[prefixCls, 'mb-20px']" shadow="never"> + <ElCard :class="[prefixCls, 'mb-15px']" shadow="never"> <template v-if="title" #header> <div class="flex items-center"> <span class="text-16px font-700">{{ title }}</span> diff --git a/src/views/infra/config/index.vue b/src/views/infra/config/index.vue index e75b09da..4c35d485 100644 --- a/src/views/infra/config/index.vue +++ b/src/views/infra/config/index.vue @@ -1,7 +1,7 @@ <template> + <!-- 搜索 --> <content-wrap> - <!-- 搜索工作栏 --> - <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px"> + <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true"> <el-form-item label="参数名称" prop="name"> <el-input v-model="queryParams.name" @@ -56,8 +56,10 @@ </el-button> </el-form-item> </el-form> + </content-wrap> - <!-- 列表 --> + <!-- 列表 --> + <content-wrap> <el-table v-loading="loading" :data="list" align="center"> <el-table-column label="参数主键" align="center" prop="id" /> <el-table-column label="参数分类" align="center" prop="category" /> From 6359ffe7661212aff50dc8eca520950d26ad50ef Mon Sep 17 00:00:00 2001 From: dlarmor <121919810@qq.com> Date: Sat, 11 Mar 2023 15:54:45 +0800 Subject: [PATCH 002/184] =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9A=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E8=AE=BE=E6=96=BD=20->=20=E6=95=B0=E6=8D=AE=E6=BA=90?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/infra/dataSourceConfig/index.ts | 30 +-- .../dataSourceConfig/dataSourceConfig.data.ts | 52 ---- src/views/infra/dataSourceConfig/form.vue | 114 +++++++++ src/views/infra/dataSourceConfig/index.vue | 223 +++++++----------- 4 files changed, 215 insertions(+), 204 deletions(-) delete mode 100644 src/views/infra/dataSourceConfig/dataSourceConfig.data.ts create mode 100644 src/views/infra/dataSourceConfig/form.vue diff --git a/src/api/infra/dataSourceConfig/index.ts b/src/api/infra/dataSourceConfig/index.ts index c0f9f624..b413f345 100644 --- a/src/api/infra/dataSourceConfig/index.ts +++ b/src/api/infra/dataSourceConfig/index.ts @@ -1,35 +1,35 @@ import request from '@/config/axios' export interface DataSourceConfigVO { - id: number + id: number | undefined name: string url: string username: string password: string - createTime: Date -} - -// 查询数据源配置列表 -export const getDataSourceConfigListApi = () => { - return request.get({ url: '/infra/data-source-config/list' }) -} - -// 查询数据源配置详情 -export const getDataSourceConfigApi = (id: number) => { - return request.get({ url: '/infra/data-source-config/get?id=' + id }) + createTime?: Date } // 新增数据源配置 -export const createDataSourceConfigApi = (data: DataSourceConfigVO) => { +export const createDataSourceConfig = (data: DataSourceConfigVO) => { return request.post({ url: '/infra/data-source-config/create', data }) } // 修改数据源配置 -export const updateDataSourceConfigApi = (data: DataSourceConfigVO) => { +export const updateDataSourceConfig = (data: DataSourceConfigVO) => { return request.put({ url: '/infra/data-source-config/update', data }) } // 删除数据源配置 -export const deleteDataSourceConfigApi = (id: number) => { +export const deleteDataSourceConfig = (id: number) => { return request.delete({ url: '/infra/data-source-config/delete?id=' + id }) } + +// 查询数据源配置详情 +export const getDataSourceConfig = (id: number) => { + return request.get({ url: '/infra/data-source-config/get?id=' + id }) +} + +// 查询数据源配置列表 +export const getDataSourceConfigList = () => { + return request.get({ url: '/infra/data-source-config/list' }) +} diff --git a/src/views/infra/dataSourceConfig/dataSourceConfig.data.ts b/src/views/infra/dataSourceConfig/dataSourceConfig.data.ts deleted file mode 100644 index a790ad17..00000000 --- a/src/views/infra/dataSourceConfig/dataSourceConfig.data.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -// 国际化 -const { t } = useI18n() -// 表单校验 -export const rules = reactive({ - name: [required], - url: [required], - username: [required], - password: [required] -}) -// 新增 + 修改 -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'seq', - action: true, - columns: [ - { - title: '数据源名称', - field: 'name' - }, - { - title: '数据源连接', - field: 'url', - form: { - component: 'Input', - componentProps: { - type: 'textarea', - rows: 4 - }, - colProps: { - span: 24 - } - } - }, - { - title: '用户名', - field: 'username' - }, - { - title: '密码', - field: 'password', - isTable: false - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) diff --git a/src/views/infra/dataSourceConfig/form.vue b/src/views/infra/dataSourceConfig/form.vue new file mode 100644 index 00000000..e0cac78b --- /dev/null +++ b/src/views/infra/dataSourceConfig/form.vue @@ -0,0 +1,114 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="100px" + v-loading="formLoading" + > + <el-form-item label="数据源名称" prop="name"> + <el-input v-model="formData.name" placeholder="请输入参数名称" /> + </el-form-item> + <el-form-item label="数据源连接" prop="url"> + <el-input v-model="formData.url" placeholder="请输入数据源连接" /> + </el-form-item> + <el-form-item label="用户名" prop="username"> + <el-input v-model="formData.username" placeholder="请输入用户名" /> + </el-form-item> + <el-form-item label="密码" prop="password"> + <el-input v-model="formData.password" placeholder="请输入密码" /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig' +import { DataSourceConfigVO } from '@/api/infra/dataSourceConfig' +import { isNullOrUnDef } from '@/utils/is' +import { omit } from 'lodash-es' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref<DataSourceConfigVO>({ + id: undefined, + name: '', + url: '', + username: '', + password: '' +}) +const formRules = reactive({ + name: [{ required: true, message: '数据源名称不能为空', trigger: 'blur' }], + url: [{ required: true, message: '数据源连接不能为空', trigger: 'blur' }], + username: [{ required: true, message: '用户名不能为空', trigger: 'blur' }], + password: [{ required: true, message: '密码不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (!isNullOrUnDef(id)) { + formLoading.value = true + try { + formData.value = await DataSourceConfigApi.getDataSourceConfig(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = omit(unref(formData), 'createTime') + if (formType.value === 'create') { + await DataSourceConfigApi.createDataSourceConfig(data) + message.success(t('common.createSuccess')) + } else { + await DataSourceConfigApi.updateDataSourceConfig(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + name: '', + url: '', + username: '', + password: '' + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/infra/dataSourceConfig/index.vue b/src/views/infra/dataSourceConfig/index.vue index 1e3db49d..e08a6f01 100644 --- a/src/views/infra/dataSourceConfig/index.vue +++ b/src/views/infra/dataSourceConfig/index.vue @@ -1,145 +1,94 @@ <template> - <ContentWrap> + <content-wrap> + <!-- 搜索工作栏 --> + <el-form :inline="true" label-width="68px"> + <el-form-item> + <el-button type="primary" @click="openModal('create')" v-hasPermi="['infra:config:create']"> + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> + </el-form-item> + </el-form> + <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['infra:data-source-config:create']" - @click="handleCreate()" - /> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:修改 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['infra:data-source-config:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['infra:data-source-config:query']" - @click="handleDetail(row.id)" - /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['infra:data-source-config:delete']" - @click="deleteData(row.id)" - /> - </template> - </XTable> - </ContentWrap> - <XModal v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(添加 / 修改) --> - <Form - v-if="['create', 'update'].includes(actionType)" - :schema="allSchemas.formSchema" - :rules="rules" - ref="formRef" - /> - <!-- 对话框(详情) --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - /> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="loading" - @click="submitForm()" + <el-table v-loading="loading" :data="list" align="center"> + <el-table-column label="主键编号" align="center" prop="id" /> + <el-table-column label="数据源名称" align="center" prop="name" /> + <el-table-column label="数据源连接" align="center" prop="url" :show-overflow-tooltip="true" /> + <el-table-column label="用户名" align="center" prop="username" /> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" /> - <!-- 按钮:关闭 --> - <XButton :loading="loading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openModal('update', scope.row.id)" + v-hasPermi="['infra:config:update']" + > + 编辑 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['infra:config:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + </content-wrap> + + <!-- 表单弹窗:添加/修改 --> + <data-source-config-form ref="modalRef" @success="getList" /> </template> <script setup lang="ts" name="DataSourceConfig"> -import type { FormExpose } from '@/components/Form' -// 业务相关的 import -import * as DataSourceConfiggApi from '@/api/infra/dataSourceConfig' -import { rules, allSchemas } from './dataSourceConfig.data' - -const { t } = useI18n() // 国际化 +import { dateFormatter } from '@/utils/formatTime' +import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig' +import DataSourceConfigForm from './form.vue' const message = useMessage() // 消息弹窗 -// 列表相关的变量 -const [registerTable, { reload, deleteData }] = useXTable({ - allSchemas: allSchemas, - isList: true, - getListApi: DataSourceConfiggApi.getDataSourceConfigListApi, - deleteApi: DataSourceConfiggApi.deleteDataSourceConfigApi +const { t } = useI18n() // 国际化 + +const loading = ref(true) // 列表的加载中 +const list = ref([]) // 列表的数据 + +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + list.value = await DataSourceConfigApi.getDataSourceConfigList() + } finally { + loading.value = false + } +} + +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, id) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await DataSourceConfigApi.deleteDataSourceConfig(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +/** 初始化 **/ +onMounted(() => { + getList() }) -// ========== CRUD 相关 ========== -const loading = ref(false) // 遮罩层 -const actionType = ref('') // 操作按钮的类型 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const formRef = ref<FormExpose>() // 表单 Ref -const detailData = ref() // 详情 Ref - -// 设置标题 -const setDialogTile = (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true -} - -// 新增操作 -const handleCreate = () => { - setDialogTile('create') -} - -// 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - // 设置数据 - const res = await DataSourceConfiggApi.getDataSourceConfigApi(rowId) - unref(formRef)?.setValues(res) -} - -// 详情操作 -const handleDetail = async (rowId: number) => { - // 设置数据 - const res = await DataSourceConfiggApi.getDataSourceConfigApi(rowId) - detailData.value = res - setDialogTile('detail') -} - -// 提交按钮 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - loading.value = true - // 提交请求 - try { - const data = unref(formRef)?.formModel as DataSourceConfiggApi.DataSourceConfigVO - if (actionType.value === 'create') { - await DataSourceConfiggApi.createDataSourceConfigApi(data) - message.success(t('common.createSuccess')) - } else { - await DataSourceConfiggApi.updateDataSourceConfigApi(data) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false - } finally { - loading.value = false - // 刷新列表 - await reload() - } - } - }) -} </script> From 8eb87481421e42d45bfbf275d6e27a5abd2e8948 Mon Sep 17 00:00:00 2001 From: dlarmor <121919810@qq.com> Date: Sat, 11 Mar 2023 16:00:01 +0800 Subject: [PATCH 003/184] =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9A=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E8=AE=BE=E6=96=BD=20->=20=E6=95=B0=E6=8D=AE=E6=BA=90?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=8A=9F=E8=83=BD=20=E6=9D=83=E9=99=90?= =?UTF-8?q?=E6=A0=87=E8=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/infra/dataSourceConfig/index.vue | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/views/infra/dataSourceConfig/index.vue b/src/views/infra/dataSourceConfig/index.vue index e08a6f01..2138033e 100644 --- a/src/views/infra/dataSourceConfig/index.vue +++ b/src/views/infra/dataSourceConfig/index.vue @@ -3,7 +3,11 @@ <!-- 搜索工作栏 --> <el-form :inline="true" label-width="68px"> <el-form-item> - <el-button type="primary" @click="openModal('create')" v-hasPermi="['infra:config:create']"> + <el-button + type="primary" + @click="openModal('create')" + v-hasPermi="['infra:data-source-config:create']" + > <Icon icon="ep:plus" class="mr-5px" /> 新增 </el-button> </el-form-item> @@ -28,7 +32,7 @@ link type="primary" @click="openModal('update', scope.row.id)" - v-hasPermi="['infra:config:update']" + v-hasPermi="['infra:data-source-config:update']" > 编辑 </el-button> @@ -36,7 +40,7 @@ link type="danger" @click="handleDelete(scope.row.id)" - v-hasPermi="['infra:config:delete']" + v-hasPermi="['infra:data-source-config:delete']" > 删除 </el-button> From a1292fbf0ab3f212e533b32c44f974c94680d958 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 11 Mar 2023 17:57:22 +0800 Subject: [PATCH 004/184] =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9A=E5=BE=AE?= =?UTF-8?q?=E8=B0=83=E3=80=90=E6=95=B0=E6=8D=AE=E6=BA=90=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E3=80=91=E7=9A=84=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/infra/config/index.ts | 9 +-------- src/views/infra/dataSourceConfig/form.vue | 6 ++---- src/views/infra/dataSourceConfig/index.vue | 8 ++++++-- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/api/infra/config/index.ts b/src/api/infra/config/index.ts index c6d3772e..07fb52fd 100644 --- a/src/api/infra/config/index.ts +++ b/src/api/infra/config/index.ts @@ -12,13 +12,6 @@ export interface ConfigVO { createTime: Date } -export interface ConfigExportReqVO { - name?: string - key?: string - type?: number - createTime?: Date[] -} - // 查询参数列表 export const getConfigPage = (params: PageParam) => { return request.get({ url: '/infra/config/page', params }) @@ -50,6 +43,6 @@ export const deleteConfig = (id: number) => { } // 导出参数 -export const exportConfigApi = (params: ConfigExportReqVO) => { +export const exportConfigApi = (params) => { return request.download({ url: '/infra/config/export', params }) } diff --git a/src/views/infra/dataSourceConfig/form.vue b/src/views/infra/dataSourceConfig/form.vue index e0cac78b..b050a1c8 100644 --- a/src/views/infra/dataSourceConfig/form.vue +++ b/src/views/infra/dataSourceConfig/form.vue @@ -31,8 +31,6 @@ <script setup lang="ts"> import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig' import { DataSourceConfigVO } from '@/api/infra/dataSourceConfig' -import { isNullOrUnDef } from '@/utils/is' -import { omit } from 'lodash-es' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -63,7 +61,7 @@ const openModal = async (type: string, id?: number) => { formType.value = type resetForm() // 修改时,设置数据 - if (!isNullOrUnDef(id)) { + if (id) { formLoading.value = true try { formData.value = await DataSourceConfigApi.getDataSourceConfig(id) @@ -84,7 +82,7 @@ const submitForm = async () => { // 提交请求 formLoading.value = true try { - const data = omit(unref(formData), 'createTime') + const data = formData.value as DataSourceConfigApi.DataSourceConfigVO if (formType.value === 'create') { await DataSourceConfigApi.createDataSourceConfig(data) message.success(t('common.createSuccess')) diff --git a/src/views/infra/dataSourceConfig/index.vue b/src/views/infra/dataSourceConfig/index.vue index 2138033e..ff44cdd5 100644 --- a/src/views/infra/dataSourceConfig/index.vue +++ b/src/views/infra/dataSourceConfig/index.vue @@ -1,7 +1,7 @@ <template> <content-wrap> <!-- 搜索工作栏 --> - <el-form :inline="true" label-width="68px"> + <el-form class="-mb-15px" :inline="true"> <el-form-item> <el-button type="primary" @@ -12,8 +12,10 @@ </el-button> </el-form-item> </el-form> + </content-wrap> - <!-- 列表 --> + <!-- 列表 --> + <content-wrap> <el-table v-loading="loading" :data="list" align="center"> <el-table-column label="主键编号" align="center" prop="id" /> <el-table-column label="数据源名称" align="center" prop="name" /> @@ -33,6 +35,7 @@ type="primary" @click="openModal('update', scope.row.id)" v-hasPermi="['infra:data-source-config:update']" + :disabled="scope.row.id === 0" > 编辑 </el-button> @@ -41,6 +44,7 @@ type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['infra:data-source-config:delete']" + :disabled="scope.row.id === 0" > 删除 </el-button> From f1313b1f1c08a5e10fe4e309e82c97f911d5505c Mon Sep 17 00:00:00 2001 From: dlarmor <121919810@qq.com> Date: Tue, 14 Mar 2023 10:47:52 +0800 Subject: [PATCH 005/184] =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9A=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E8=AE=BE=E6=96=BD=20->=20=E6=96=87=E4=BB=B6=E7=AE=A1?= =?UTF-8?q?=E7=90=86=20=E6=96=87=E4=BB=B6=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/locales/en.ts | 3 +- src/locales/zh-CN.ts | 3 +- src/views/infra/file/form.vue | 82 ++++++++++ src/views/infra/file/index.vue | 162 ++++++++++++++++++++ src/views/infra/fileList/fileList.data.ts | 52 ------- src/views/infra/fileList/index.vue | 173 ---------------------- 6 files changed, 248 insertions(+), 227 deletions(-) create mode 100644 src/views/infra/file/form.vue create mode 100644 src/views/infra/file/index.vue delete mode 100644 src/views/infra/fileList/fileList.data.ts delete mode 100644 src/views/infra/fileList/index.vue diff --git a/src/locales/en.ts b/src/locales/en.ts index 1600a19f..4f4d4895 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -297,7 +297,8 @@ export default { typeCreate: 'Dict Type Create', typeUpdate: 'Dict Type Eidt', dataCreate: 'Dict Data Create', - dataUpdate: 'Dict Data Eidt' + dataUpdate: 'Dict Data Eidt', + fileUpload: 'File Upload' }, dialog: { dialog: 'Dialog', diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 7c5742c4..6f46f1ab 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -297,7 +297,8 @@ export default { typeCreate: '字典类型新增', typeUpdate: '字典类型编辑', dataCreate: '字典数据新增', - dataUpdate: '字典数据编辑' + dataUpdate: '字典数据编辑', + fileUpload: '上传文件' }, dialog: { dialog: '弹窗', diff --git a/src/views/infra/file/form.vue b/src/views/infra/file/form.vue new file mode 100644 index 00000000..8550d440 --- /dev/null +++ b/src/views/infra/file/form.vue @@ -0,0 +1,82 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-upload + ref="uploadRef" + :limit="1" + accept=".jpg, .png, .gif" + :auto-upload="false" + drag + :headers="headers" + :action="url" + :data="data" + :disabled="formLoading" + :on-change="handleFileChange" + :on-progress="handleFileUploadProgress" + :on-success="handleFileSuccess" + > + <i class="el-icon-upload"></i> + <div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em> </div> + <template #tip> + <div class="el-upload__tip" style="color: red"> + 提示:仅允许导入 jpg、png、gif 格式文件! + </div> + </template> + </el-upload> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitFileForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> + +<script setup lang="ts"> +import { Dialog } from '@/components/Dialog' +import { getAccessToken } from '@/utils/auth' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const url = import.meta.env.VITE_UPLOAD_URL +const headers = { Authorization: 'Bearer ' + getAccessToken() } +const data = ref({ path: '' }) +const uploadRef = ref() + +/** 打开弹窗 */ +const openModal = async () => { + modelVisible.value = true + modelTitle.value = t('action.fileUpload') +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +/** 处理上传的文件发生变化 */ +const handleFileChange = (file) => { + data.value.path = file.name +} +/** 处理文件上传中 */ +const handleFileUploadProgress = () => { + formLoading.value = true // 禁止修改 +} +/** 发起文件上传 */ +const submitFileForm = () => { + unref(uploadRef)?.submit() +} +/** 文件上传成功处理 */ +const handleFileSuccess = () => { + // 清理 + modelVisible.value = false + formLoading.value = false + unref(uploadRef)?.clearFiles() + // 提示成功,并刷新 + message.success(t('common.createSuccess')) + emit('success') +} +</script> + +<style scoped></style> diff --git a/src/views/infra/file/index.vue b/src/views/infra/file/index.vue new file mode 100644 index 00000000..77beb3e5 --- /dev/null +++ b/src/views/infra/file/index.vue @@ -0,0 +1,162 @@ +<template> + <!-- 搜索 --> + <content-wrap> + <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true"> + <el-form-item label="文件路径" prop="path"> + <el-input + v-model="queryParams.path" + placeholder="请输入文件路径" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="文件类型" prop="type" width="80"> + <el-input + v-model="queryParams.type" + placeholder="请输入文件类型" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + value-format="YYYY-MM-DD HH:mm:ss" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button type="primary" @click="openModal"> + <Icon icon="ep:upload" class="mr-5px" /> 上传文件 + </el-button> + </el-form-item> + </el-form> + </content-wrap> + + <!-- 列表 --> + <content-wrap> + <el-table v-loading="loading" :data="list" align="center"> + <el-table-column label="文件名" align="center" prop="name" :show-overflow-tooltip="true" /> + <el-table-column label="文件路径" align="center" prop="path" :show-overflow-tooltip="true" /> + <el-table-column label="URL" align="center" prop="url" :show-overflow-tooltip="true" /> + <el-table-column + label="文件大小" + align="center" + prop="size" + width="120" + :formatter="sizeFormat" + /> + <el-table-column label="文件类型" align="center" prop="type" width="180px" /> + <el-table-column + label="上传时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['infra:config:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + + <!-- 表单弹窗:添加/修改 --> + <file-upload-form ref="modalRef" @success="getList" /> +</template> +<script setup lang="ts" name="Config"> +import { dateFormatter } from '@/utils/formatTime' +import * as FileApi from '@/api/infra/fileList' +import FileUploadForm from './form.vue' +const message = useMessage() // 消息弹窗 +const { t } = useI18n() // 国际化 + +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + name: undefined, + type: undefined, + createTime: [] +}) +const queryFormRef = ref() // 搜索的表单 + +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await FileApi.getFilePageApi(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = () => { + modalRef.value.openModal() +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await FileApi.deleteFileApi(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +const sizeFormat = (row) => { + const unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + const srcSize = parseFloat(row.size) + const index = Math.floor(Math.log(srcSize) / Math.log(1024)) + const size = srcSize / Math.pow(1024, index) + const sizeStr = size.toFixed(2) //保留的小数位数 + return sizeStr + ' ' + unitArr[index] +} +/** 初始化 **/ +onMounted(() => { + getList() +}) +</script> diff --git a/src/views/infra/fileList/fileList.data.ts b/src/views/infra/fileList/fileList.data.ts deleted file mode 100644 index 29be6dae..00000000 --- a/src/views/infra/fileList/fileList.data.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -const { t } = useI18n() // 国际化 - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'seq', - action: true, - columns: [ - { - title: '文件名', - field: 'name' - }, - { - title: '文件路径', - field: 'path', - isSearch: true - }, - { - title: 'URL', - field: 'url', - table: { - cellRender: { - name: 'XPreview' - } - } - }, - { - title: '文件大小', - field: 'size', - formatter: 'formatSize' - }, - { - title: '文件类型', - field: 'type', - isSearch: true - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) diff --git a/src/views/infra/fileList/index.vue b/src/views/infra/fileList/index.vue deleted file mode 100644 index cda8b68d..00000000 --- a/src/views/infra/fileList/index.vue +++ /dev/null @@ -1,173 +0,0 @@ -<template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <XButton - type="primary" - preIcon="ep:upload" - title="上传文件" - @click="uploadDialogVisible = true" - /> - </template> - <template #actionbtns_default="{ row }"> - <XTextButton - preIcon="ep:copy-document" - :title="t('common.copy')" - @click="handleCopy(row.url)" - /> - <XTextButton preIcon="ep:view" :title="t('action.detail')" @click="handleDetail(row)" /> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['infra:file:delete']" - @click="deleteData(row.id)" - /> - </template> - </XTable> - </ContentWrap> - <XModal v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(详情) --> - <Descriptions :schema="allSchemas.detailSchema" :data="detailData"> - <template #url="{ row }"> - <el-image - v-if="row.type === 'jpg' || 'png' || 'gif'" - style="width: 100px; height: 100px" - :src="row.url" - :key="row.url" - lazy - /> - <span>{{ row.url }}</span> - </template> - </Descriptions> - <!-- 操作按钮 --> - <template #footer> - <XButton :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> - <XModal v-model="uploadDialogVisible" :title="uploadDialogTitle"> - <el-upload - ref="uploadRef" - :action="updateUrl + '?updateSupport=' + updateSupport" - :headers="uploadHeaders" - :drag="true" - :limit="1" - :multiple="true" - :show-file-list="true" - :disabled="uploadDisabled" - :before-upload="beforeUpload" - :on-exceed="handleExceed" - :on-success="handleFileSuccess" - :on-error="excelUploadError" - :auto-upload="false" - accept=".jpg, .png, .gif" - > - <Icon icon="ep:upload-filled" /> - <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> - <template #tip> - <div class="el-upload__tip">请上传 .jpg, .png, .gif 标准格式文件</div> - </template> - </el-upload> - <template #footer> - <!-- 按钮:保存 --> - <XButton - type="primary" - preIcon="ep:upload-filled" - :title="t('action.save')" - @click="submitFileForm()" - /> - <!-- 按钮:关闭 --> - <XButton :title="t('dialog.close')" @click="uploadDialogVisible = false" /> - </template> - </XModal> -</template> -<script setup lang="ts" name="FileList"> -import type { UploadInstance, UploadRawFile } from 'element-plus' -// 业务相关的 import -import { allSchemas } from './fileList.data' -import * as FileApi from '@/api/infra/fileList' -import { getAccessToken, getTenantId } from '@/utils/auth' -import { useClipboard } from '@vueuse/core' - -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 - -// 列表相关的变量 -const [registerTable, { reload, deleteData }] = useXTable({ - allSchemas: allSchemas, - getListApi: FileApi.getFilePageApi, - deleteApi: FileApi.deleteFileApi -}) - -const detailData = ref() // 详情 Ref -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('') // 弹出层标题 -const uploadDialogVisible = ref(false) -const uploadDialogTitle = ref('上传') -const updateSupport = ref(0) -const uploadDisabled = ref(false) -const uploadRef = ref<UploadInstance>() -let updateUrl = import.meta.env.VITE_UPLOAD_URL -const uploadHeaders = ref() -// 文件上传之前判断 -const beforeUpload = (file: UploadRawFile) => { - const isImg = file.type === 'image/jpeg' || 'image/gif' || 'image/png' - const isLt5M = file.size / 1024 / 1024 < 5 - if (!isImg) message.error('上传文件只能是 jpeg / gif / png 格式!') - if (!isLt5M) message.error('上传文件大小不能超过 5MB!') - return isImg && isLt5M -} -// 处理上传的文件发生变化 -// const handleFileChange = (uploadFile: UploadFile): void => { -// uploadRef.value.data.path = uploadFile.name -// } -// 文件上传 -const submitFileForm = () => { - uploadHeaders.value = { - Authorization: 'Bearer ' + getAccessToken(), - 'tenant-id': getTenantId() - } - uploadDisabled.value = true - uploadRef.value!.submit() -} -// 文件上传成功 -const handleFileSuccess = async (response: any): Promise<void> => { - if (response.code !== 0) { - message.error(response.msg) - return - } - message.success('上传成功') - uploadDialogVisible.value = false - uploadDisabled.value = false - await reload() -} -// 文件数超出提示 -const handleExceed = (): void => { - message.error('最多只能上传一个文件!') -} -// 上传错误提示 -const excelUploadError = (): void => { - message.error('导入数据失败,请您重新上传!') -} - -// 详情操作 -const handleDetail = (row: FileApi.FileVO) => { - // 设置数据 - detailData.value = row - dialogTitle.value = t('action.detail') - dialogVisible.value = true -} - -// ========== 复制相关 ========== -const handleCopy = async (text: string) => { - const { copy, copied, isSupported } = useClipboard({ source: text }) - if (!isSupported) { - message.error(t('common.copyError')) - } else { - await copy() - if (unref(copied)) { - message.success(t('common.copySuccess')) - } - } -} -</script> From fee8245f4ed758fa42a0ef39ca7f65a7b4b59f3f Mon Sep 17 00:00:00 2001 From: dlarmor <121919810@qq.com> Date: Tue, 14 Mar 2023 11:45:37 +0800 Subject: [PATCH 006/184] =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9A=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E8=AE=BE=E6=96=BD=20->=20=E6=96=87=E4=BB=B6=E7=AE=A1?= =?UTF-8?q?=E7=90=86=20=E6=96=87=E4=BB=B6=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/infra/fileConfig/fileConfig.data.ts | 77 ---- src/views/infra/fileConfig/form.vue | 195 ++++++++ src/views/infra/fileConfig/index.vue | 433 +++++++----------- 3 files changed, 365 insertions(+), 340 deletions(-) delete mode 100644 src/views/infra/fileConfig/fileConfig.data.ts create mode 100644 src/views/infra/fileConfig/form.vue diff --git a/src/views/infra/fileConfig/fileConfig.data.ts b/src/views/infra/fileConfig/fileConfig.data.ts deleted file mode 100644 index d0f64869..00000000 --- a/src/views/infra/fileConfig/fileConfig.data.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -const { t } = useI18n() // 国际化 - -// 表单校验 -export const rules = reactive({ - name: [required], - storage: [required], - config: { - basePath: [required], - host: [required], - port: [required], - username: [required], - password: [required], - mode: [required], - endpoint: [required], - bucket: [required], - accessKey: [required], - accessSecret: [required], - domain: [required] - } -}) - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'seq', - primaryTitle: '配置编号', - action: true, - actionWidth: '400px', - columns: [ - { - title: '配置名', - field: 'name', - isSearch: true - }, - { - title: '存储器', - field: 'storage', - dictType: DICT_TYPE.INFRA_FILE_STORAGE, - dictClass: 'number', - isSearch: true - }, - { - title: '主配置', - field: 'master', - dictType: DICT_TYPE.INFRA_BOOLEAN_STRING, - dictClass: 'boolean' - }, - { - title: t('form.remark'), - field: 'remark', - form: { - component: 'Input', - componentProps: { - type: 'textarea', - rows: 4 - }, - colProps: { - span: 24 - } - } - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) diff --git a/src/views/infra/fileConfig/form.vue b/src/views/infra/fileConfig/form.vue new file mode 100644 index 00000000..ee7bd238 --- /dev/null +++ b/src/views/infra/fileConfig/form.vue @@ -0,0 +1,195 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="120px" + v-loading="formLoading" + > + <el-form-item label="配置名" prop="name"> + <el-input v-model="formData.name" placeholder="请输入配置名" /> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="formData.remark" placeholder="请输入备注" /> + </el-form-item> + <el-form-item label="存储器" prop="storage"> + <el-select + v-model="formData.storage" + placeholder="请选择存储器" + :disabled="formData.id !== undefined" + > + <el-option + v-for="dict in getDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)" + :key="dict.value" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + <!-- DB --> + <!-- Local / FTP / SFTP --> + <el-form-item + v-if="formData.storage >= 10 && formData.storage <= 12" + label="基础路径" + prop="config.basePath" + > + <el-input v-model="formData.config.basePath" placeholder="请输入基础路径" /> + </el-form-item> + <el-form-item + v-if="formData.storage >= 11 && formData.storage <= 12" + label="主机地址" + prop="config.host" + > + <el-input v-model="formData.config.host" placeholder="请输入主机地址" /> + </el-form-item> + <el-form-item + v-if="formData.storage >= 11 && formData.storage <= 12" + label="主机端口" + prop="config.port" + > + <el-input-number :min="0" v-model="formData.config.port" placeholder="请输入主机端口" /> + </el-form-item> + <el-form-item + v-if="formData.storage >= 11 && formData.storage <= 12" + label="用户名" + prop="config.username" + > + <el-input v-model="formData.config.username" placeholder="请输入密码" /> + </el-form-item> + <el-form-item + v-if="formData.storage >= 11 && formData.storage <= 12" + label="密码" + prop="config.password" + > + <el-input v-model="formData.config.password" placeholder="请输入密码" /> + </el-form-item> + <el-form-item v-if="formData.storage === 11" label="连接模式" prop="config.mode"> + <el-radio-group v-model="formData.config.mode"> + <el-radio key="Active" label="Active">主动模式</el-radio> + <el-radio key="Passive" label="Passive">主动模式</el-radio> + </el-radio-group> + </el-form-item> + <!-- S3 --> + <el-form-item v-if="formData.storage === 20" label="节点地址" prop="config.endpoint"> + <el-input v-model="formData.config.endpoint" placeholder="请输入节点地址" /> + </el-form-item> + <el-form-item v-if="formData.storage === 20" label="存储 bucket" prop="config.bucket"> + <el-input v-model="formData.config.bucket" placeholder="请输入 bucket" /> + </el-form-item> + <el-form-item v-if="formData.storage === 20" label="accessKey" prop="config.accessKey"> + <el-input v-model="formData.config.accessKey" placeholder="请输入 accessKey" /> + </el-form-item> + <el-form-item v-if="formData.storage === 20" label="accessSecret" prop="config.accessSecret"> + <el-input v-model="formData.config.accessSecret" placeholder="请输入 accessSecret" /> + </el-form-item> + <!-- 通用 --> + <el-form-item v-if="formData.storage === 20" label="自定义域名"> + <!-- 无需参数校验,所以去掉 prop --> + <el-input v-model="formData.config.domain" placeholder="请输入自定义域名" /> + </el-form-item> + <el-form-item v-else-if="formData.storage" label="自定义域名" prop="config.domain"> + <el-input v-model="formData.config.domain" placeholder="请输入自定义域名" /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import * as FileConfigApi from '@/api/infra/fileConfig' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: undefined, + name: '', + storage: '', + remark: '', + config: {} +}) +const formRules = reactive({ + name: [{ required: true, message: '配置名不能为空', trigger: 'blur' }], + storage: [{ required: true, message: '存储器不能为空', trigger: 'change' }], + config: { + basePath: [{ required: true, message: '基础路径不能为空', trigger: 'blur' }], + host: [{ required: true, message: '主机地址不能为空', trigger: 'blur' }], + port: [{ required: true, message: '主机端口不能为空', trigger: 'blur' }], + username: [{ required: true, message: '用户名不能为空', trigger: 'blur' }], + password: [{ required: true, message: '密码不能为空', trigger: 'blur' }], + mode: [{ required: true, message: '连接模式不能为空', trigger: 'change' }], + endpoint: [{ required: true, message: '节点地址不能为空', trigger: 'blur' }], + bucket: [{ required: true, message: '存储 bucket 不能为空', trigger: 'blur' }], + accessKey: [{ required: true, message: 'accessKey 不能为空', trigger: 'blur' }], + accessSecret: [{ required: true, message: 'accessSecret 不能为空', trigger: 'blur' }], + domain: [{ required: true, message: '自定义域名不能为空', trigger: 'blur' }] + } +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await FileConfigApi.getFileConfigApi(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as FileConfigApi.FileConfigVO + if (formType.value === 'create') { + await FileConfigApi.createFileConfigApi(data) + message.success(t('common.createSuccess')) + } else { + await FileConfigApi.updateFileConfigApi(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + name: '', + storage: '', + remark: '', + config: {} + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/infra/fileConfig/index.vue b/src/views/infra/fileConfig/index.vue index 9d796a65..4416b094 100644 --- a/src/views/infra/fileConfig/index.vue +++ b/src/views/infra/fileConfig/index.vue @@ -1,294 +1,201 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <!-- 操作:新增 --> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['infra:file-config:create']" - @click="handleCreate(formRef)" - /> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:编辑 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['infra:file-config:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['infra:file-config:query']" - @click="handleDetail(row.id)" - /> - <!-- 操作:主配置 --> - <XTextButton - preIcon="ep:flag" - title="主配置" - v-hasPermi="['infra:file-config:update']" - @click="handleMaster(row)" - /> - <!-- 操作:测试 --> - <XTextButton preIcon="ep:share" :title="t('action.test')" @click="handleTest(row.id)" /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['infra:file-config:delete']" - @click="deleteData(row.id)" - /> - </template> - </XTable> - </ContentWrap> - <XModal v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(添加 / 修改) --> - <el-form - ref="formRef" - v-if="['create', 'update'].includes(actionType)" - :model="form" - :rules="rules" - label-width="120px" - > + <!-- 搜索 --> + <content-wrap> + <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true"> <el-form-item label="配置名" prop="name"> - <el-input v-model="form.name" placeholder="请输入配置名" /> - </el-form-item> - <el-form-item label="备注" prop="remark"> - <el-input v-model="form.remark" placeholder="请输入备注" /> + <el-input + v-model="queryParams.name" + placeholder="请输入配置名" + clearable + @keyup.enter="handleQuery" + /> </el-form-item> <el-form-item label="存储器" prop="storage"> - <el-select v-model="form.storage" placeholder="请选择存储器" :disabled="form.id !== 0"> + <el-select v-model="queryParams.storage" placeholder="请选择存储器" clearable> <el-option - v-for="(dict, index) in getIntDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)" - :key="index" + v-for="dict in getDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)" + :key="parseInt(dict.value)" :label="dict.label" - :value="dict.value" + :value="parseInt(dict.value)" /> </el-select> </el-form-item> - <!-- DB --> - <!-- Local / FTP / SFTP --> - <el-form-item - v-if="form.storage >= 10 && form.storage <= 12" - label="基础路径" - prop="config.basePath" - > - <el-input v-model="form.config.basePath" placeholder="请输入基础路径" /> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + value-format="YYYY-MM-DD HH:mm:ss" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + /> </el-form-item> - <el-form-item - v-if="form.storage >= 11 && form.storage <= 12" - label="主机地址" - prop="config.host" - > - <el-input v-model="form.config.host" placeholder="请输入主机地址" /> - </el-form-item> - <el-form-item - v-if="form.storage >= 11 && form.storage <= 12" - label="主机端口" - prop="config.port" - > - <el-input-number :min="0" v-model="form.config.port" placeholder="请输入主机端口" /> - </el-form-item> - <el-form-item - v-if="form.storage >= 11 && form.storage <= 12" - label="用户名" - prop="config.username" - > - <el-input v-model="form.config.username" placeholder="请输入密码" /> - </el-form-item> - <el-form-item - v-if="form.storage >= 11 && form.storage <= 12" - label="密码" - prop="config.password" - > - <el-input v-model="form.config.password" placeholder="请输入密码" /> - </el-form-item> - <el-form-item v-if="form.storage === 11" label="连接模式" prop="config.mode"> - <el-radio-group v-model="form.config.mode"> - <el-radio key="Active" label="Active">主动模式</el-radio> - <el-radio key="Passive" label="Passive">主动模式</el-radio> - </el-radio-group> - </el-form-item> - <!-- S3 --> - <el-form-item v-if="form.storage === 20" label="节点地址" prop="config.endpoint"> - <el-input v-model="form.config.endpoint" placeholder="请输入节点地址" /> - </el-form-item> - <el-form-item v-if="form.storage === 20" label="存储 bucket" prop="config.bucket"> - <el-input v-model="form.config.bucket" placeholder="请输入 bucket" /> - </el-form-item> - <el-form-item v-if="form.storage === 20" label="accessKey" prop="config.accessKey"> - <el-input v-model="form.config.accessKey" placeholder="请输入 accessKey" /> - </el-form-item> - <el-form-item v-if="form.storage === 20" label="accessSecret" prop="config.accessSecret"> - <el-input v-model="form.config.accessSecret" placeholder="请输入 accessSecret" /> - </el-form-item> - <!-- 通用 --> - <el-form-item v-if="form.storage === 20" label="自定义域名"> - <!-- 无需参数校验,所以去掉 prop --> - <el-input v-model="form.config.domain" placeholder="请输入自定义域名" /> - </el-form-item> - <el-form-item v-else-if="form.storage" label="自定义域名" prop="config.domain"> - <el-input v-model="form.config.domain" placeholder="请输入自定义域名" /> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button + type="primary" + @click="openModal('create')" + v-hasPermi="['infra:file-config:create']" + > + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> </el-form-item> </el-form> - <!-- 对话框(详情) --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - /> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="actionLoading" - @click="submitForm(formRef)" + </content-wrap> + + <!-- 列表 --> + <content-wrap> + <el-table v-loading="loading" :data="list" align="center"> + <el-table-column label="编号" align="center" prop="id" /> + <el-table-column label="配置名" align="center" prop="name" /> + <el-table-column label="存储器" align="center" prop="storage"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.INFRA_FILE_STORAGE" :value="scope.row.storage" /> + </template> + </el-table-column> + <el-table-column label="备注" align="center" prop="remark" /> + <el-table-column label="主配置" align="center" prop="primary"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" /> + </template> + </el-table-column> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" /> - <!-- 按钮:关闭 --> - <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openModal('update', scope.row.id)" + v-hasPermi="['infra:file-config:update']" + > + 编辑 + </el-button> + <el-button + link + type="primary" + :disabled="scope.row.master" + @click="handleMaster(scope.row.id)" + v-hasPermi="['infra:file-config:update']" + > + 主配置 + </el-button> + <el-button link type="primary" @click="handleTest(scope.row.id)"> 测试 </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['infra:config:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + + <!-- 表单弹窗:添加/修改 --> + <file-config-form ref="modalRef" @success="getList" /> </template> -<script setup lang="ts" name="FileConfig"> -import type { FormInstance } from 'element-plus' -// 业务相关的 import +<script setup lang="ts" name="Config"> import * as FileConfigApi from '@/api/infra/fileConfig' -import { rules, allSchemas } from './fileConfig.data' -import { getIntDictOptions, DICT_TYPE } from '@/utils/dict' - -const { t } = useI18n() // 国际化 +import FileConfigForm from './form.vue' +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' const message = useMessage() // 消息弹窗 -// 列表相关的变量 -const [registerTable, { reload, deleteData }] = useXTable({ - allSchemas: allSchemas, - getListApi: FileConfigApi.getFileConfigPageApi, - deleteApi: FileConfigApi.deleteFileConfigApi -}) +const { t } = useI18n() // 国际化 -// ========== CRUD 相关 ========== -const actionLoading = ref(false) // 遮罩层 -const actionType = ref('') // 操作按钮的类型 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const formRef = ref<FormInstance>() // 表单 Ref -const detailData = ref() // 详情 Ref -const form = ref<FileConfigApi.FileConfigVO>({ - id: 0, - name: '', - storage: 0, - master: false, - visible: false, - config: { - basePath: '', - host: '', - port: 0, - username: '', - password: '', - mode: '', - endpoint: '', - bucket: '', - accessKey: '', - accessSecret: '', - domain: '' - }, - remark: '', - createTime: new Date() +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + name: undefined, + storage: undefined, + createTime: [] }) -// 设置标题 -const setDialogTile = (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true -} +const queryFormRef = ref() // 搜索的表单 -// 新增操作 -const handleCreate = (formEl: FormInstance | undefined) => { - setDialogTile('create') - formEl?.resetFields() - form.value = { - id: 0, - name: '', - storage: 0, - master: false, - visible: false, - config: { - basePath: '', - host: '', - port: 0, - username: '', - password: '', - mode: '', - endpoint: '', - bucket: '', - accessKey: '', - accessSecret: '', - domain: '' - }, - remark: '', - createTime: new Date() +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await FileConfigApi.getFileConfigPageApi(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false } } -// 修改操作 -const handleUpdate = async (rowId: number) => { - // 设置数据 - const res = await FileConfigApi.getFileConfigApi(rowId) - form.value = res - setDialogTile('update') +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() } -// 详情操作 -const handleDetail = async (rowId: number) => { - setDialogTile('detail') - // 设置数据 - const res = await FileConfigApi.getFileConfigApi(rowId) - detailData.value = res +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() } -// 主配置操作 -const handleMaster = (row: FileConfigApi.FileConfigVO) => { +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, id) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await FileConfigApi.deleteFileConfigApi(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} +/** 主配置按钮操作 */ +const handleMaster = (id) => { message - .confirm('是否确认修改配置【 ' + row.name + ' 】为主配置?', t('common.reminder')) - .then(async () => { - await FileConfigApi.updateFileConfigMasterApi(row.id) - await reload() + .confirm('是否确认修改配置编号为"' + id + '"的数据项为主配置?') + .then(function () { + return FileConfigApi.updateFileConfigMasterApi(id) }) + .then(() => { + getList() + message.success(t('common.updateSuccess')) + }) + .catch(() => {}) } - -const handleTest = async (rowId: number) => { - const res = await FileConfigApi.testFileConfigApi(rowId) - message.alert('测试通过,上传文件成功!访问地址:' + res) -} - -// 提交按钮 -const submitForm = async (formEl: FormInstance | undefined) => { - if (!formEl) return - formEl.validate(async (valid) => { - if (valid) { - actionLoading.value = true - // 提交请求 - try { - if (actionType.value === 'create') { - await FileConfigApi.createFileConfigApi(form.value) - message.success(t('common.createSuccess')) - } else { - await FileConfigApi.updateFileConfigApi(form.value) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false - } finally { - actionLoading.value = false - await reload() - } - } - }) +/** 测试按钮操作 */ +const handleTest = (id) => { + FileConfigApi.testFileConfigApi(id) + .then((response) => { + message.alert('测试通过,上传文件成功!访问地址:' + response) + }) + .catch(() => {}) } +/** 初始化 **/ +onMounted(() => { + getList() +}) </script> From fe2fa21d44c93b419dbe55bc0b1cd6172aaaccc0 Mon Sep 17 00:00:00 2001 From: dlarmor <121919810@qq.com> Date: Tue, 14 Mar 2023 11:48:35 +0800 Subject: [PATCH 007/184] =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9A=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E8=AE=BE=E6=96=BD=20->=20=E6=96=87=E4=BB=B6=E7=AE=A1?= =?UTF-8?q?=E7=90=86=20=E6=8E=A5=E5=8F=A3=E6=96=87=E4=BB=B6=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/infra/{fileList => file}/index.ts | 15 ++------------- src/api/infra/fileConfig/index.ts | 14 +++++++------- src/views/infra/fileConfig/form.vue | 6 +++--- src/views/infra/fileConfig/index.vue | 8 ++++---- 4 files changed, 16 insertions(+), 27 deletions(-) rename src/api/infra/{fileList => file}/index.ts (54%) diff --git a/src/api/infra/fileList/index.ts b/src/api/infra/file/index.ts similarity index 54% rename from src/api/infra/fileList/index.ts rename to src/api/infra/file/index.ts index 48b3c991..f64bc0d6 100644 --- a/src/api/infra/fileList/index.ts +++ b/src/api/infra/file/index.ts @@ -1,16 +1,5 @@ import request from '@/config/axios' -export interface FileVO { - id: number - configId: number - path: string - name: string - url: string - size: string - type: string - createTime: Date -} - export interface FilePageReqVO extends PageParam { path?: string type?: string @@ -18,11 +7,11 @@ export interface FilePageReqVO extends PageParam { } // 查询文件列表 -export const getFilePageApi = (params: FilePageReqVO) => { +export const getFilePage = (params: FilePageReqVO) => { return request.get({ url: '/infra/file/page', params }) } // 删除文件 -export const deleteFileApi = (id: number) => { +export const deleteFile = (id: number) => { return request.delete({ url: '/infra/file/delete?id=' + id }) } diff --git a/src/api/infra/fileConfig/index.ts b/src/api/infra/fileConfig/index.ts index 2151141c..2f096706 100644 --- a/src/api/infra/fileConfig/index.ts +++ b/src/api/infra/fileConfig/index.ts @@ -31,36 +31,36 @@ export interface FileConfigPageReqVO extends PageParam { } // 查询文件配置列表 -export const getFileConfigPageApi = (params: FileConfigPageReqVO) => { +export const getFileConfigPage = (params: FileConfigPageReqVO) => { return request.get({ url: '/infra/file-config/page', params }) } // 查询文件配置详情 -export const getFileConfigApi = (id: number) => { +export const getFileConfig = (id: number) => { return request.get({ url: '/infra/file-config/get?id=' + id }) } // 更新文件配置为主配置 -export const updateFileConfigMasterApi = (id: number) => { +export const updateFileConfigMaster = (id: number) => { return request.put({ url: '/infra/file-config/update-master?id=' + id }) } // 新增文件配置 -export const createFileConfigApi = (data: FileConfigVO) => { +export const createFileConfig = (data: FileConfigVO) => { return request.post({ url: '/infra/file-config/create', data }) } // 修改文件配置 -export const updateFileConfigApi = (data: FileConfigVO) => { +export const updateFileConfig = (data: FileConfigVO) => { return request.put({ url: '/infra/file-config/update', data }) } // 删除文件配置 -export const deleteFileConfigApi = (id: number) => { +export const deleteFileConfig = (id: number) => { return request.delete({ url: '/infra/file-config/delete?id=' + id }) } // 测试文件配置 -export const testFileConfigApi = (id: number) => { +export const testFileConfig = (id: number) => { return request.get({ url: '/infra/file-config/test?id=' + id }) } diff --git a/src/views/infra/fileConfig/form.vue b/src/views/infra/fileConfig/form.vue index ee7bd238..a23aac9f 100644 --- a/src/views/infra/fileConfig/form.vue +++ b/src/views/infra/fileConfig/form.vue @@ -147,7 +147,7 @@ const openModal = async (type: string, id?: number) => { if (id) { formLoading.value = true try { - formData.value = await FileConfigApi.getFileConfigApi(id) + formData.value = await FileConfigApi.getFileConfig(id) } finally { formLoading.value = false } @@ -167,10 +167,10 @@ const submitForm = async () => { try { const data = formData.value as unknown as FileConfigApi.FileConfigVO if (formType.value === 'create') { - await FileConfigApi.createFileConfigApi(data) + await FileConfigApi.createFileConfig(data) message.success(t('common.createSuccess')) } else { - await FileConfigApi.updateFileConfigApi(data) + await FileConfigApi.updateFileConfig(data) message.success(t('common.updateSuccess')) } modelVisible.value = false diff --git a/src/views/infra/fileConfig/index.vue b/src/views/infra/fileConfig/index.vue index 4416b094..3b683e4e 100644 --- a/src/views/infra/fileConfig/index.vue +++ b/src/views/infra/fileConfig/index.vue @@ -135,7 +135,7 @@ const queryFormRef = ref() // 搜索的表单 const getList = async () => { loading.value = true try { - const data = await FileConfigApi.getFileConfigPageApi(queryParams) + const data = await FileConfigApi.getFileConfigPage(queryParams) list.value = data.list total.value = data.total } finally { @@ -167,7 +167,7 @@ const handleDelete = async (id: number) => { // 删除的二次确认 await message.delConfirm() // 发起删除 - await FileConfigApi.deleteFileConfigApi(id) + await FileConfigApi.deleteFileConfig(id) message.success(t('common.delSuccess')) // 刷新列表 await getList() @@ -178,7 +178,7 @@ const handleMaster = (id) => { message .confirm('是否确认修改配置编号为"' + id + '"的数据项为主配置?') .then(function () { - return FileConfigApi.updateFileConfigMasterApi(id) + return FileConfigApi.updateFileConfigMaster(id) }) .then(() => { getList() @@ -188,7 +188,7 @@ const handleMaster = (id) => { } /** 测试按钮操作 */ const handleTest = (id) => { - FileConfigApi.testFileConfigApi(id) + FileConfigApi.testFileConfig(id) .then((response) => { message.alert('测试通过,上传文件成功!访问地址:' + response) }) From 1a7f89a8e23a41a0f313afd5e0b9c81bd2dbb7bb Mon Sep 17 00:00:00 2001 From: dlarmor <121919810@qq.com> Date: Tue, 14 Mar 2023 11:50:31 +0800 Subject: [PATCH 008/184] =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9A=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E8=AE=BE=E6=96=BD=20->=20=E6=96=87=E4=BB=B6=E7=AE=A1?= =?UTF-8?q?=E7=90=86=20=E6=8E=A5=E5=8F=A3=E6=96=87=E4=BB=B6=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/infra/file/index.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/views/infra/file/index.vue b/src/views/infra/file/index.vue index 77beb3e5..0f63ea28 100644 --- a/src/views/infra/file/index.vue +++ b/src/views/infra/file/index.vue @@ -87,7 +87,7 @@ </template> <script setup lang="ts" name="Config"> import { dateFormatter } from '@/utils/formatTime' -import * as FileApi from '@/api/infra/fileList' +import * as FileApi from '@/api/infra/file' import FileUploadForm from './form.vue' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -108,7 +108,7 @@ const queryFormRef = ref() // 搜索的表单 const getList = async () => { loading.value = true try { - const data = await FileApi.getFilePageApi(queryParams) + const data = await FileApi.getFilePage(queryParams) list.value = data.list total.value = data.total } finally { @@ -140,7 +140,7 @@ const handleDelete = async (id: number) => { // 删除的二次确认 await message.delConfirm() // 发起删除 - await FileApi.deleteFileApi(id) + await FileApi.deleteFile(id) message.success(t('common.delSuccess')) // 刷新列表 await getList() From d46812483f72a23bacfa646e11db077c3507c888 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Tue, 14 Mar 2023 21:08:00 +0800 Subject: [PATCH 009/184] =?UTF-8?q?file=20=E9=87=8D=E6=9E=84=E7=9A=84=20re?= =?UTF-8?q?view=20=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/infra/fileConfig/index.ts | 9 ++------- src/views/infra/file/form.vue | 5 +++-- src/views/infra/file/index.vue | 2 ++ src/views/infra/fileConfig/index.vue | 7 ++++++- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/api/infra/fileConfig/index.ts b/src/api/infra/fileConfig/index.ts index 2f096706..d6c00996 100644 --- a/src/api/infra/fileConfig/index.ts +++ b/src/api/infra/fileConfig/index.ts @@ -13,6 +13,7 @@ export interface FileClientConfig { accessSecret?: string domain: string } + export interface FileConfigVO { id: number name: string @@ -24,14 +25,8 @@ export interface FileConfigVO { createTime: Date } -export interface FileConfigPageReqVO extends PageParam { - name?: string - storage?: number - createTime?: Date[] -} - // 查询文件配置列表 -export const getFileConfigPage = (params: FileConfigPageReqVO) => { +export const getFileConfigPage = (params: PageParam) => { return request.get({ url: '/infra/file-config/page', params }) } diff --git a/src/views/infra/file/form.vue b/src/views/infra/file/form.vue index 8550d440..15ef02fd 100644 --- a/src/views/infra/file/form.vue +++ b/src/views/infra/file/form.vue @@ -59,14 +59,17 @@ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成 const handleFileChange = (file) => { data.value.path = file.name } + /** 处理文件上传中 */ const handleFileUploadProgress = () => { formLoading.value = true // 禁止修改 } + /** 发起文件上传 */ const submitFileForm = () => { unref(uploadRef)?.submit() } + /** 文件上传成功处理 */ const handleFileSuccess = () => { // 清理 @@ -78,5 +81,3 @@ const handleFileSuccess = () => { emit('success') } </script> - -<style scoped></style> diff --git a/src/views/infra/file/index.vue b/src/views/infra/file/index.vue index 0f63ea28..7c88ffb0 100644 --- a/src/views/infra/file/index.vue +++ b/src/views/infra/file/index.vue @@ -147,6 +147,7 @@ const handleDelete = async (id: number) => { } catch {} } +// TODO 写到 utils/index.ts 中 const sizeFormat = (row) => { const unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] const srcSize = parseFloat(row.size) @@ -155,6 +156,7 @@ const sizeFormat = (row) => { const sizeStr = size.toFixed(2) //保留的小数位数 return sizeStr + ' ' + unitArr[index] } + /** 初始化 **/ onMounted(() => { getList() diff --git a/src/views/infra/fileConfig/index.vue b/src/views/infra/fileConfig/index.vue index 3b683e4e..4145d7a0 100644 --- a/src/views/infra/fileConfig/index.vue +++ b/src/views/infra/fileConfig/index.vue @@ -68,7 +68,7 @@ width="180" :formatter="dateFormatter" /> - <el-table-column label="操作" align="center"> + <el-table-column label="操作" align="center" width="240px"> <template #default="scope"> <el-button link @@ -173,8 +173,10 @@ const handleDelete = async (id: number) => { await getList() } catch {} } + /** 主配置按钮操作 */ const handleMaster = (id) => { + // TODO 改成 await 的形式 message .confirm('是否确认修改配置编号为"' + id + '"的数据项为主配置?') .then(function () { @@ -186,14 +188,17 @@ const handleMaster = (id) => { }) .catch(() => {}) } + /** 测试按钮操作 */ const handleTest = (id) => { + // TODO 改成 await 的形式 FileConfigApi.testFileConfig(id) .then((response) => { message.alert('测试通过,上传文件成功!访问地址:' + response) }) .catch(() => {}) } + /** 初始化 **/ onMounted(() => { getList() From b6e3a583ff1e3cb9f4ad0347dc2c38d403e3a3dd Mon Sep 17 00:00:00 2001 From: dlarmor <121919810@qq.com> Date: Wed, 15 Mar 2023 10:07:27 +0800 Subject: [PATCH 010/184] file finished todo --- src/utils/index.ts | 9 ++++++++ src/views/infra/file/index.vue | 13 ++---------- src/views/infra/fileConfig/index.vue | 31 +++++++++++----------------- 3 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/utils/index.ts b/src/utils/index.ts index d3058473..c86d4cd8 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -137,3 +137,12 @@ export const generateUUID = () => { return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16) }) } + +export const fileSizeFormatter = (row) => { + const unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + const srcSize = parseFloat(row.size) + const index = Math.floor(Math.log(srcSize) / Math.log(1024)) + const size = srcSize / Math.pow(1024, index) + const sizeStr = size.toFixed(2) //保留的小数位数 + return sizeStr + ' ' + unitArr[index] +} diff --git a/src/views/infra/file/index.vue b/src/views/infra/file/index.vue index 7c88ffb0..5137ec3e 100644 --- a/src/views/infra/file/index.vue +++ b/src/views/infra/file/index.vue @@ -50,7 +50,7 @@ align="center" prop="size" width="120" - :formatter="sizeFormat" + :formatter="fileSizeFormatter" /> <el-table-column label="文件类型" align="center" prop="type" width="180px" /> <el-table-column @@ -86,6 +86,7 @@ <file-upload-form ref="modalRef" @success="getList" /> </template> <script setup lang="ts" name="Config"> +import { fileSizeFormatter } from '@/utils' import { dateFormatter } from '@/utils/formatTime' import * as FileApi from '@/api/infra/file' import FileUploadForm from './form.vue' @@ -147,16 +148,6 @@ const handleDelete = async (id: number) => { } catch {} } -// TODO 写到 utils/index.ts 中 -const sizeFormat = (row) => { - const unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] - const srcSize = parseFloat(row.size) - const index = Math.floor(Math.log(srcSize) / Math.log(1024)) - const size = srcSize / Math.pow(1024, index) - const sizeStr = size.toFixed(2) //保留的小数位数 - return sizeStr + ' ' + unitArr[index] -} - /** 初始化 **/ onMounted(() => { getList() diff --git a/src/views/infra/fileConfig/index.vue b/src/views/infra/fileConfig/index.vue index 4145d7a0..e38e3a50 100644 --- a/src/views/infra/fileConfig/index.vue +++ b/src/views/infra/fileConfig/index.vue @@ -175,28 +175,21 @@ const handleDelete = async (id: number) => { } /** 主配置按钮操作 */ -const handleMaster = (id) => { - // TODO 改成 await 的形式 - message - .confirm('是否确认修改配置编号为"' + id + '"的数据项为主配置?') - .then(function () { - return FileConfigApi.updateFileConfigMaster(id) - }) - .then(() => { - getList() - message.success(t('common.updateSuccess')) - }) - .catch(() => {}) +const handleMaster = async (id) => { + try { + await message.confirm('是否确认修改配置编号为"' + id + '"的数据项为主配置?') + await FileConfigApi.updateFileConfigMaster(id) + message.success(t('common.updateSuccess')) + await getList() + } catch {} } /** 测试按钮操作 */ -const handleTest = (id) => { - // TODO 改成 await 的形式 - FileConfigApi.testFileConfig(id) - .then((response) => { - message.alert('测试通过,上传文件成功!访问地址:' + response) - }) - .catch(() => {}) +const handleTest = async (id) => { + try { + const response = await FileConfigApi.testFileConfig(id) + message.alert('测试通过,上传文件成功!访问地址:' + response) + } catch {} } /** 初始化 **/ From 11d4e2ea177a5c3d1522448e1eb2ab45eab54052 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Wed, 15 Mar 2023 20:35:18 +0800 Subject: [PATCH 011/184] =?UTF-8?q?=E8=A1=A5=E5=85=A8=20fileSizeFormatter?= =?UTF-8?q?=20=E7=9A=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/index.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/utils/index.ts b/src/utils/index.ts index c86d4cd8..e016c1e2 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -138,9 +138,18 @@ export const generateUUID = () => { }) } -export const fileSizeFormatter = (row) => { +/** + * element plus 的文件大小 Formatter 实现 + * + * @param row 行数据 + * @param column 字段 + * @param cellValue 字段值 + */ +// @ts-ignore +export const fileSizeFormatter = (row, column, cellValue) => { + const fileSize = cellValue const unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] - const srcSize = parseFloat(row.size) + const srcSize = parseFloat(fileSize) const index = Math.floor(Math.log(srcSize) / Math.log(1024)) const size = srcSize / Math.pow(1024, index) const sizeStr = size.toFixed(2) //保留的小数位数 From 4c143c1c6cd011305d4e0edbeb009f520a5b9cfa Mon Sep 17 00:00:00 2001 From: xiaobai <2511883673@qq.com> Date: Wed, 15 Mar 2023 22:20:12 +0800 Subject: [PATCH 012/184] =?UTF-8?q?=E7=9F=AD=E4=BF=A1=E6=B8=A0=E9=81=93?= =?UTF-8?q?=E7=BF=BB=E5=86=99vue2->vue3=2020230315?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/sms/smsChannel/index.vue | 321 +++++++++++------- .../system/sms/smsChannel/sms.channel.data.ts | 63 ---- 2 files changed, 200 insertions(+), 184 deletions(-) delete mode 100644 src/views/system/sms/smsChannel/sms.channel.data.ts diff --git a/src/views/system/sms/smsChannel/index.vue b/src/views/system/sms/smsChannel/index.vue index ee47a51a..94a46b7d 100644 --- a/src/views/system/sms/smsChannel/index.vue +++ b/src/views/system/sms/smsChannel/index.vue @@ -1,147 +1,226 @@ <template> <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <!-- 操作:新增 --> - <template #toolbar_buttons> - <XButton + <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px"> + <el-form-item label="短信签名" prop="signature"> + <el-input + v-model="queryParams.signature" + placeholder="请输入短信签名" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="启用状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="请选择启用状态" clearable> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + value-format="yyyy-MM-dd HH:mm:ss" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" + @click="openModal('create')" v-hasPermi="['system:sms-channel:create']" - @click="handleCreate()" - /> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:修改 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['system:sms-channel:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['system:sms-channel:query']" - @click="handleDetail(row.id)" - /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['system:sms-channel:delete']" - @click="deleteData(row.id)" - /> - </template> - </XTable> - </ContentWrap> + > + <Icon icon="ep:plus" class="mr-5px" /> 新增</el-button + > + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['system:sms-channel:export']" + > + <Icon icon="ep:download" class="mr-5px" /> 导出</el-button + > + </el-form-item> + </el-form> - <XModal id="smsChannel" v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(添加 / 修改) --> - <Form - v-if="['create', 'update'].includes(actionType)" - :schema="allSchemas.formSchema" - :rules="rules" - ref="formRef" - /> - <!-- 对话框(详情) --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - /> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="actionLoading" - @click="submitForm()" + <!-- 列表 --> + <el-table v-loading="loading" :data="list" align="center"> + <el-table-column label="编号" align="center" prop="id" /> + <el-table-column label="短信签名" align="center" prop="signature" /> + <el-table-column label="渠道编码" align="center" prop="code"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="scope.row.code" /> + </template> + </el-table-column> + <el-table-column label="启用状态" align="center" prop="status"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> + </template> + </el-table-column> + <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" /> + <el-table-column + label="短信 API 的账号" + align="center" + prop="apiKey" + :show-overflow-tooltip="true" /> - <!-- 按钮:关闭 --> - <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> + <el-table-column + label="短信 API 的密钥" + align="center" + prop="apiSecret" + :show-overflow-tooltip="true" + /> + <el-table-column + label="短信发送回调 URL" + align="center" + prop="callbackUrl" + :show-overflow-tooltip="true" + /> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openModal('update', scope.row.id)" + v-hasPermi="['system:sms-channel:update']" + > + 编辑 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['system:sms-channel:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </ContentWrap> + <!-- 表单弹窗:添加/修改 --> + <SmsChannelForm ref="modalRef" @success="getList" /> </template> <script setup lang="ts" name="SmsChannel"> -import type { FormExpose } from '@/components/Form' // 业务相关的 import import * as SmsChannelApi from '@/api/system/sms/smsChannel' -import { rules, allSchemas } from './sms.channel.data' +//格式化时间 +import { dateFormatter } from '@/utils/formatTime' +//字典 +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +//表单弹窗:添加/修改 +import SmsChannelForm from './form.vue' +//下载 +// import download from '@/utils/download' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 -// 列表相关的变量 -const [registerTable, { reload, deleteData }] = useXTable({ - allSchemas: allSchemas, - getListApi: SmsChannelApi.getSmsChannelPageApi, - deleteApi: SmsChannelApi.deleteSmsChannelApi +// 列表的加载中 +const loading = ref(true) +//搜索的表单 +const queryFormRef = ref() +// 列表的总页数 +const total = ref(0) +// 列表的数据 +const list = ref([]) +//导出的加载中 +const exportLoading = ref(false) +//查询参数 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + signature: undefined, + status: undefined, + createTime: [] }) -// 弹窗相关的变量 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const actionType = ref('') // 操作按钮的类型 -const actionLoading = ref(false) // 按钮 Loading -const formRef = ref<FormExpose>() // 表单 Ref -const detailData = ref() // 详情 Ref - -// 设置标题 -const setDialogTile = (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + // 执行查询 + try { + const data = await SmsChannelApi.getSmsChannelPageApi(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } } -// 新增操作 -const handleCreate = () => { - setDialogTile('create') +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() } -// 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - // 设置数据 - const res = await SmsChannelApi.getSmsChannelApi(rowId) - unref(formRef)?.setValues(res) +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() } -// 详情操作 -const handleDetail = async (rowId: number) => { - setDialogTile('detail') - const res = await SmsChannelApi.getSmsChannelApi(rowId) - detailData.value = res +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, id) } -// 提交按钮 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - actionLoading.value = true - // 提交请求 - try { - const data = unref(formRef)?.formModel as SmsChannelApi.SmsChannelVO - if (actionType.value === 'create') { - await SmsChannelApi.createSmsChannelApi(data) - message.success(t('common.createSuccess')) - } else { - await SmsChannelApi.updateSmsChannelApi(data) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false - } finally { - actionLoading.value = false - // 刷新列表 - await reload() - } - } - }) +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + await message.info('该功能目前不支持') + //导出功能先不考虑 + // const data = await SmsChannelApi.exportSmsChanelApi(queryParams) + // download.excel(data, '短信渠道.xls') + } catch { + } finally { + exportLoading.value = false + } } + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await SmsChannelApi.deleteSmsChannelApi(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +/** 初始化 **/ +onMounted(() => { + getList() +}) </script> diff --git a/src/views/system/sms/smsChannel/sms.channel.data.ts b/src/views/system/sms/smsChannel/sms.channel.data.ts deleted file mode 100644 index d3a807ed..00000000 --- a/src/views/system/sms/smsChannel/sms.channel.data.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -const { t } = useI18n() // 国际化 - -// 表单校验 -export const rules = reactive({ - signature: [required], - code: [required], - apiKey: [required], - status: [required] -}) - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'id', - primaryTitle: '渠道编号', - action: true, - columns: [ - { - title: '短信签名', - field: 'signature', - isSearch: true - }, - { - title: '渠道编码', - field: 'code', - dictType: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, - isSearch: true - }, - { - title: t('common.status'), - field: 'status', - dictType: DICT_TYPE.COMMON_STATUS, - dictClass: 'number', - isSearch: true - }, - { - title: '短信 API 的账号', - field: 'apiKey' - }, - { - title: '短信 API 的密钥', - field: 'apiSecret' - }, - { - title: '短信发送回调 URL', - field: 'callbackUrl' - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From a94172d7a5cccbd7f79e4f41102eaca2a37f7842 Mon Sep 17 00:00:00 2001 From: xiaobai <2511883673@qq.com> Date: Wed, 15 Mar 2023 22:20:28 +0800 Subject: [PATCH 013/184] =?UTF-8?q?=E7=9F=AD=E4=BF=A1=E6=B8=A0=E9=81=93?= =?UTF-8?q?=E7=BF=BB=E5=86=99vue2->vue3=2020230315?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/sms/smsChannel/form.vue | 137 +++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 src/views/system/sms/smsChannel/form.vue diff --git a/src/views/system/sms/smsChannel/form.vue b/src/views/system/sms/smsChannel/form.vue new file mode 100644 index 00000000..7c20a90d --- /dev/null +++ b/src/views/system/sms/smsChannel/form.vue @@ -0,0 +1,137 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form ref="formRef" :model="form" :rules="rules" label-width="130px" v-loading="formLoading"> + <el-form-item label="短信签名" prop="signature"> + <el-input v-model="form.signature" placeholder="请输入短信签名" /> + </el-form-item> + <el-form-item label="渠道编码" prop="code"> + <el-select v-model="form.code" placeholder="请选择渠道编码" clearable> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="启用状态"> + <el-radio-group v-model="form.status"> + <el-radio + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="parseInt(dict.value)" + >{{ dict.label }}</el-radio + > + </el-radio-group> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="form.remark" placeholder="请输入备注" /> + </el-form-item> + <el-form-item label="短信 API 的账号" prop="apiKey"> + <el-input v-model="form.apiKey" placeholder="请输入短信 API 的账号" /> + </el-form-item> + <el-form-item label="短信 API 的密钥" prop="apiSecret"> + <el-input v-model="form.apiSecret" placeholder="请输入短信 API 的密钥" /> + </el-form-item> + <el-form-item label="短信发送回调 URL" prop="callbackUrl"> + <el-input v-model="form.callbackUrl" placeholder="请输入短信发送回调 URL" /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import * as SmsChannelApi from '@/api/system/sms/smsChannel' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const form = ref({ + id: undefined, + signature: '', + code: '', + status: '', + remark: '', + apiKey: '', + apiSecret: '', + callbackUrl: '' +}) +const rules = reactive({ + signature: [{ required: true, message: '短信签名不能为空', trigger: 'blur' }], + code: [{ required: true, message: '渠道编码不能为空', trigger: 'blur' }], + status: [{ required: true, message: '启用状态不能为空', trigger: 'blur' }], + apiKey: [{ required: true, message: '短信 API 的账号不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + form.value = await SmsChannelApi.getSmsChannelApi(id) + console.log(form) + } finally { + formLoading.value = false + } + } +} + +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = unref(formRef)?.formModel as SmsChannelApi.SmsChannelVO + if (formType.value === 'create') { + await SmsChannelApi.createSmsChannelApi(data) + message.success(t('common.createSuccess')) + } else { + await SmsChannelApi.updateSmsChannelApi(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + form.value = { + id: undefined, + signature: '', + code: '', + status: '', + remark: '', + apiKey: '', + apiSecret: '', + callbackUrl: '' + } + formRef.value?.resetFields() +} +</script> From f1a80fe558ea459f37af552487e4c8cb40488e30 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Fri, 17 Mar 2023 00:02:45 +0800 Subject: [PATCH 014/184] =?UTF-8?q?vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E9=82=AE=E4=BB=B6=E8=B4=A6=E5=8F=B7=E7=9A=84=E5=88=97=E8=A1=A8?= =?UTF-8?q?=20+=20=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/mail/account/account.data.ts | 102 +++++------ src/views/system/mail/account/index.vue | 167 +++--------------- 2 files changed, 77 insertions(+), 192 deletions(-) diff --git a/src/views/system/mail/account/account.data.ts b/src/views/system/mail/account/account.data.ts index bd05ce4f..8555a7e3 100644 --- a/src/views/system/mail/account/account.data.ts +++ b/src/views/system/mail/account/account.data.ts @@ -1,10 +1,12 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' +import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas' +import { DictTag } from '@/components/DictTag' +import { TableColumn } from '@/types/table' +import { dateFormatter } from '@/utils/formatTime' const { t } = useI18n() // 国际化 // 表单校验 export const rules = reactive({ - // mail: [required], mail: [ { required: true, message: t('profile.rules.mail'), trigger: 'blur' }, { @@ -21,55 +23,49 @@ export const rules = reactive({ }) // CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', // 默认的主键 ID - primaryTitle: '编号', - primaryType: 'id', - action: true, - actionWidth: '200', // 3 个按钮默认 200,如有删减对应增减即可 - columns: [ - { - title: '邮箱', - field: 'mail', - isSearch: true - }, - { - title: '用户名', - field: 'username', - isSearch: true - }, - { - title: '密码', - field: 'password', - isTable: false - }, - { - title: 'SMTP 服务器域名', - field: 'host' - }, - { - title: 'SMTP 服务器端口', - field: 'port', - form: { - component: 'InputNumber', - value: 465 - } - }, - { - title: '是否开启 SSL', - field: 'sslEnable', - dictType: DICT_TYPE.INFRA_BOOLEAN_STRING, - dictClass: 'boolean' - }, - { - title: '创建时间', - field: 'createTime', - isForm: false, - formatter: 'formatDate', - table: { - width: 180 - } +const crudSchemas = reactive<CrudSchema[]>([ + { + label: '邮箱', + field: 'mail', + isSearch: true + }, + { + label: '用户名', + field: 'username', + isSearch: true + }, + { + label: '密码', + field: 'password', + isTable: false + }, + { + label: 'SMTP 服务器域名', + field: 'host' + }, + { + label: 'SMTP 服务器端口', + field: 'port', + form: { + component: 'InputNumber', + value: 465 } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) + }, + { + label: '是否开启 SSL', + field: 'sslEnable', + formatter: (_: Recordable, __: TableColumn, cellValue: boolean) => { + return h(DictTag, { + type: DICT_TYPE.INFRA_BOOLEAN_STRING, + value: cellValue + }) + } + }, + { + label: '创建时间', + field: 'createTime', + isForm: false, + formatter: dateFormatter + } +]) +export const { allSchemas } = useCrudSchemas(crudSchemas) diff --git a/src/views/system/mail/account/index.vue b/src/views/system/mail/account/index.vue index 7c4ad0f9..821c7c73 100644 --- a/src/views/system/mail/account/index.vue +++ b/src/views/system/mail/account/index.vue @@ -1,151 +1,40 @@ <template> <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <!-- 操作:新增 --> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['system:mail-account:create']" - @click="handleCreate()" - /> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:修改 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['system:mail-account:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['system:mail-account:query']" - @click="handleDetail(row.id)" - /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['system:mail-account:delete']" - @click="deleteData(row.id)" - /> - </template> - </XTable> + <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" /> + </ContentWrap> + + <ContentWrap> + <Table + v-model:pageSize="tableObject.pageSize" + v-model:currentPage="tableObject.currentPage" + :columns="allSchemas.tableColumns" + :data="tableObject.tableList" + :loading="tableObject.loading" + :pagination="{ + total: tableObject.total + }" + @register="register" + > + <template #action="{ row }"> + <ElButton type="danger" @click="delData(row, false)"> + {{ t('exampleDemo.del') }} + </ElButton> + </template> + </Table> </ContentWrap> - <!-- 弹窗 --> - <XModal id="mailAccountModel" :loading="modelLoading" v-model="modelVisible" :title="modelTitle"> - <!-- 表单:添加/修改 --> - <Form - ref="formRef" - v-if="['create', 'update'].includes(actionType)" - :schema="allSchemas.formSchema" - :rules="rules" - /> - <!-- 表单:详情 --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - /> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="actionLoading" - @click="submitForm()" - /> - <!-- 按钮:关闭 --> - <XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" /> - </template> - </XModal> </template> <script setup lang="ts" name="MailAccount"> -import { FormExpose } from '@/components/Form' -// 业务相关的 import -import { rules, allSchemas } from './account.data' +import { allSchemas } from './account.data' +import { useTable } from '@/hooks/web/useTable' +import { Table } from '@/components/Table' import * as MailAccountApi from '@/api/system/mail/account' -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 - -// 列表相关的变量 -const [registerTable, { reload, deleteData }] = useXTable({ - allSchemas: allSchemas, +const { register, tableObject, methods } = useTable<MailAccountApi.MailAccountVO>({ getListApi: MailAccountApi.getMailAccountPageApi, - deleteApi: MailAccountApi.deleteMailAccountApi + delListApi: MailAccountApi.deleteMailAccountApi }) -// 弹窗相关的变量 -const modelVisible = ref(false) // 是否显示弹出层 -const modelTitle = ref('edit') // 弹出层标题 -const modelLoading = ref(false) // 弹出层loading -const actionType = ref('') // 操作按钮的类型 -const actionLoading = ref(false) // 按钮 Loading -const formRef = ref<FormExpose>() // 表单 Ref -const detailData = ref() // 详情 Ref +const { getList, setSearchParams } = methods -// 设置标题 -const setDialogTile = (type: string) => { - modelLoading.value = true - modelTitle.value = t('action.' + type) - actionType.value = type - modelVisible.value = true -} - -// 新增操作 -const handleCreate = () => { - setDialogTile('create') - modelLoading.value = false -} - -// 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - // 设置数据 - const res = await MailAccountApi.getMailAccountApi(rowId) - unref(formRef)?.setValues(res) - modelLoading.value = false -} - -// 详情操作 -const handleDetail = async (rowId: number) => { - setDialogTile('detail') - const res = await MailAccountApi.getMailAccountApi(rowId) - detailData.value = res - modelLoading.value = false -} - -// 提交按钮 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - actionLoading.value = true - // 提交请求 - try { - const data = unref(formRef)?.formModel as MailAccountApi.MailAccountVO - if (actionType.value === 'create') { - await MailAccountApi.createMailAccountApi(data) - message.success(t('common.createSuccess')) - } else { - await MailAccountApi.updateMailAccountApi(data) - message.success(t('common.updateSuccess')) - } - modelVisible.value = false - } finally { - actionLoading.value = false - // 刷新列表 - await reload() - } - } - }) -} +getList() </script> From 262874a117b05d0057950b493eb72b91ac12e71b Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Fri, 17 Mar 2023 01:35:38 +0800 Subject: [PATCH 015/184] =?UTF-8?q?vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E9=82=AE=E4=BB=B6=E8=B4=A6=E5=8F=B7=E7=9A=84=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=20+=20=E4=BF=AE=E6=94=B9=20+=20=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/mail/account/index.ts | 7 +- src/views/system/mail/account/account.data.ts | 15 ++++ src/views/system/mail/account/form.vue | 68 +++++++++++++++++++ src/views/system/mail/account/index.vue | 44 ++++++++++-- 4 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 src/views/system/mail/account/form.vue diff --git a/src/api/system/mail/account/index.ts b/src/api/system/mail/account/index.ts index 8b662a70..7f3b5382 100644 --- a/src/api/system/mail/account/index.ts +++ b/src/api/system/mail/account/index.ts @@ -10,13 +10,8 @@ export interface MailAccountVO { sslEnable: boolean } -export interface MailAccountPageReqVO extends PageParam { - mail?: string - username?: string -} - // 查询邮箱账号列表 -export const getMailAccountPageApi = async (params: MailAccountPageReqVO) => { +export const getMailAccountPageApi = async (params: PageParam) => { return await request.get({ url: '/system/mail-account/page', params }) } diff --git a/src/views/system/mail/account/account.data.ts b/src/views/system/mail/account/account.data.ts index 8555a7e3..5d018cb9 100644 --- a/src/views/system/mail/account/account.data.ts +++ b/src/views/system/mail/account/account.data.ts @@ -2,6 +2,7 @@ import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas' import { DictTag } from '@/components/DictTag' import { TableColumn } from '@/types/table' import { dateFormatter } from '@/utils/formatTime' +import { getBoolDictOptions } from '@/utils/dict' const { t } = useI18n() // 国际化 @@ -59,6 +60,12 @@ const crudSchemas = reactive<CrudSchema[]>([ type: DICT_TYPE.INFRA_BOOLEAN_STRING, value: cellValue }) + }, + form: { + component: 'Radio', + componentProps: { + options: getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING) + } } }, { @@ -66,6 +73,14 @@ const crudSchemas = reactive<CrudSchema[]>([ field: 'createTime', isForm: false, formatter: dateFormatter + }, + { + label: '操作', + field: 'action', + width: '260px', + form: { + show: false + } } ]) export const { allSchemas } = useCrudSchemas(crudSchemas) diff --git a/src/views/system/mail/account/form.vue b/src/views/system/mail/account/form.vue new file mode 100644 index 00000000..ba6a31d8 --- /dev/null +++ b/src/views/system/mail/account/form.vue @@ -0,0 +1,68 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <!-- TODO 芋艿:loading --> + <Form ref="formRef" :schema="allSchemas.formSchema" :rules="rules" :isCol="false" /> + <template #footer> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import * as MailAccountApi from '@/api/system/mail/account' +import { rules, allSchemas } from './account.data' + +const formRef = ref() // 表单 Ref +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + // resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + const data = await MailAccountApi.getMailAccountApi(id) + formRef.value.setValues(data) + } finally { + formLoading.value = false + } + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.getElFormRef().validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formRef.value.formModel as MailAccountApi.MailAccountVO + if (formType.value === 'create') { + await MailAccountApi.createMailAccountApi(data) + message.success(t('common.createSuccess')) + } else { + await MailAccountApi.updateMailAccountApi(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} +</script> diff --git a/src/views/system/mail/account/index.vue b/src/views/system/mail/account/index.vue index 821c7c73..8bc29f6d 100644 --- a/src/views/system/mail/account/index.vue +++ b/src/views/system/mail/account/index.vue @@ -1,8 +1,17 @@ <template> <ContentWrap> + <!-- TODO @芋艿:setSearchParams --> <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" /> </ContentWrap> + <el-button + type="primary" + @click="openModal('create')" + v-hasPermi="['system:mail-account:create']" + > + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> + <ContentWrap> <Table v-model:pageSize="tableObject.pageSize" @@ -16,18 +25,37 @@ @register="register" > <template #action="{ row }"> - <ElButton type="danger" @click="delData(row, false)"> - {{ t('exampleDemo.del') }} - </ElButton> + <el-button + link + type="primary" + @click="openModal('update', row.id)" + v-hasPermi="['system:mail-account:update']" + > + 编辑 + </el-button> + <el-button + link + type="danger" + v-hasPermi="['system:mail-account:delete']" + @click="delList(row.id, false)" + > + 删除 + </el-button> </template> </Table> </ContentWrap> + + <!-- 表单弹窗:添加/修改 --> + <mail-account-form ref="modalRef" @success="getList" /> </template> <script setup lang="ts" name="MailAccount"> import { allSchemas } from './account.data' import { useTable } from '@/hooks/web/useTable' -import { Table } from '@/components/Table' import * as MailAccountApi from '@/api/system/mail/account' +import MailAccountForm from './form.vue' + +// const { t } = useI18n() // 国际化 +// const message = useMessage() // 消息弹窗 const { register, tableObject, methods } = useTable<MailAccountApi.MailAccountVO>({ getListApi: MailAccountApi.getMailAccountPageApi, @@ -36,5 +64,13 @@ const { register, tableObject, methods } = useTable<MailAccountApi.MailAccountVO const { getList, setSearchParams } = methods +const { delList } = methods + +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, id) +} + getList() </script> From 714dd661c40e0aafb91f51223695d646affe29af Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Fri, 17 Mar 2023 21:48:00 +0800 Subject: [PATCH 016/184] =?UTF-8?q?Search=20=E7=BB=84=E4=BB=B6=EF=BC=9A1?= =?UTF-8?q?=EF=BC=89=E4=BF=AE=E6=94=B9=20search=20=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E7=9A=84=E6=A0=B7=E5=BC=8F=EF=BC=9B2=EF=BC=89=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=20search=20=E6=8C=89=E9=92=AE=E5=90=8E=E7=9A=84=20act?= =?UTF-8?q?ionMore=20slot=EF=BC=9B3=EF=BC=89=E4=BF=AE=E6=94=B9=20Form=20?= =?UTF-8?q?=E5=90=91=E4=B8=8B=E5=87=8F=E5=B0=91=2015px=20=E9=97=B4?= =?UTF-8?q?=E8=B7=9D=EF=BC=8C=E6=9B=B4=E7=B4=A7=E5=87=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Search/src/Search.vue | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/Search/src/Search.vue b/src/components/Search/src/Search.vue index 80f2b37c..cb3d5dea 100644 --- a/src/components/Search/src/Search.vue +++ b/src/components/Search/src/Search.vue @@ -98,6 +98,7 @@ const setVisible = () => { </script> <template> + <!-- update by 芋艿:class="-mb-15px" 用于降低和 ContentWrap 组件的底部距离,避免空隙过大 --> <Form :is-custom="false" :label-width="labelWidth" @@ -106,21 +107,26 @@ const setVisible = () => { :is-col="isCol" :schema="newSchema" @register="register" + class="-mb-15px" > <template #action> <div v-if="layout === 'inline'"> - <ElButton v-if="showSearch" type="primary" @click="search"> + <!-- update by 芋艿:去除搜索的 type="primary",颜色变淡一点 --> + <ElButton v-if="showSearch" @click="search"> <Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }} </ElButton> + <!-- update by 芋艿:将 icon="ep:refresh-right" 修改成 icon="ep:refresh",和 ruoyi-vue 搜索保持一致 --> <ElButton v-if="showReset" @click="reset"> - <Icon icon="ep:refresh-right" class="mr-5px" /> + <Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }} </ElButton> <ElButton v-if="expand" text @click="setVisible"> {{ t(visible ? 'common.shrink' : 'common.expand') }} <Icon :icon="visible ? 'ep:arrow-up' : 'ep:arrow-down'" /> </ElButton> + <!-- add by 芋艿:补充在搜索后的按钮 --> + <slot name="actionMore"></slot> </div> </template> <template #[name] v-for="name in Object.keys($slots)" :key="name" @@ -142,6 +148,8 @@ const setVisible = () => { {{ t(visible ? 'common.shrink' : 'common.expand') }} <Icon :icon="visible ? 'ep:arrow-up' : 'ep:arrow-down'" /> </ElButton> + <!-- add by 芋艿:补充在搜索后的按钮 --> + <slot name="actionMore"></slot> </div> </template> </template> From 96e0ce98663eb69e854d1c1d2895a5ac34701521 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Fri, 17 Mar 2023 22:32:25 +0800 Subject: [PATCH 017/184] =?UTF-8?q?Table=20=E7=BB=84=E4=BB=B6=EF=BC=9A1?= =?UTF-8?q?=EF=BC=89=E5=A2=9E=E5=8A=A0=20tableMethods=20=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=EF=BC=8C=E6=8F=90=E5=8D=87=E4=BB=A3=E7=A0=81=E7=9A=84=E5=8F=AF?= =?UTF-8?q?=E9=98=85=E8=AF=BB=E6=80=A7=EF=BC=9B2=EF=BC=89Table=20=E5=92=8C?= =?UTF-8?q?=20Pagination=20=E7=BB=84=E4=BB=B6=E7=9A=84=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E7=BB=9F=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Pagination/index.vue | 2 +- src/components/Table/src/Table.vue | 6 ++++-- src/hooks/web/useTable.ts | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/Pagination/index.vue b/src/components/Pagination/index.vue index 9fc35068..788809a5 100644 --- a/src/components/Pagination/index.vue +++ b/src/components/Pagination/index.vue @@ -5,7 +5,7 @@ class="float-right mt-15px mb-15px" :background="true" layout="total, sizes, prev, pager, next, jumper" - :page-sizes="[10, 20, 30, 50]" + :page-sizes="[10, 20, 30, 50, 100]" v-model:current-page="currentPage" v-model:page-size="pageSize" :pager-count="pagerCount" diff --git a/src/components/Table/src/Table.vue b/src/components/Table/src/Table.vue index 66969c99..8ca59684 100644 --- a/src/components/Table/src/Table.vue +++ b/src/components/Table/src/Table.vue @@ -104,11 +104,12 @@ export default defineComponent({ }) const pagination = computed(() => { + // update by 芋艿:保持和 Pagination 组件的逻辑一致 return Object.assign( { small: false, background: true, - pagerCount: 5, + pagerCount: document.body.clientWidth < 992 ? 5 : 7, layout: 'total, sizes, prev, pager, next, jumper', pageSizes: [10, 20, 30, 50, 100], disabled: false, @@ -283,10 +284,11 @@ export default defineComponent({ }} </ElTable> {unref(getProps).pagination ? ( + // update by 芋艿:保持和 Pagination 组件一致 <ElPagination v-model:pageSize={pageSizeRef.value} v-model:currentPage={currentPageRef.value} - class="mt-10px" + class="float-right mt-15px mb-15px" {...unref(pagination)} ></ElPagination> ) : undefined} diff --git a/src/hooks/web/useTable.ts b/src/hooks/web/useTable.ts index 7a9b1afe..3f0d5055 100644 --- a/src/hooks/web/useTable.ts +++ b/src/hooks/web/useTable.ts @@ -218,6 +218,8 @@ export const useTable = <T = any>(config?: UseTableConfig<T>) => { register, elTableRef, tableObject, - methods + methods, + // add by 芋艿:返回 tableMethods 属性,和 tableObject 更统一 + tableMethods: methods } } From 3f2a77f2d239f3e4d43818fe651f25cb0a5d55b7 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Fri, 17 Mar 2023 22:33:30 +0800 Subject: [PATCH 018/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E9=82=AE=E4=BB=B6=E8=B4=A6=E5=8F=B7=E7=9A=84=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E9=83=A8=E5=88=86=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/mail/account/index.ts | 12 ++-- src/views/system/mail/account/account.data.ts | 1 - src/views/system/mail/account/form.vue | 6 +- src/views/system/mail/account/index.vue | 61 +++++++++++-------- src/views/system/mail/log/index.vue | 2 +- src/views/system/mail/template/index.vue | 2 +- 6 files changed, 46 insertions(+), 38 deletions(-) diff --git a/src/api/system/mail/account/index.ts b/src/api/system/mail/account/index.ts index 7f3b5382..9e10c92a 100644 --- a/src/api/system/mail/account/index.ts +++ b/src/api/system/mail/account/index.ts @@ -11,31 +11,31 @@ export interface MailAccountVO { } // 查询邮箱账号列表 -export const getMailAccountPageApi = async (params: PageParam) => { +export const getMailAccountPage = async (params: PageParam) => { return await request.get({ url: '/system/mail-account/page', params }) } // 查询邮箱账号详情 -export const getMailAccountApi = async (id: number) => { +export const getMailAccount = async (id: number) => { return await request.get({ url: '/system/mail-account/get?id=' + id }) } // 新增邮箱账号 -export const createMailAccountApi = async (data: MailAccountVO) => { +export const createMailAccount = async (data: MailAccountVO) => { return await request.post({ url: '/system/mail-account/create', data }) } // 修改邮箱账号 -export const updateMailAccountApi = async (data: MailAccountVO) => { +export const updateMailAccount = async (data: MailAccountVO) => { return await request.put({ url: '/system/mail-account/update', data }) } // 删除邮箱账号 -export const deleteMailAccountApi = async (id: number) => { +export const deleteMailAccount = async (id: number) => { return await request.delete({ url: '/system/mail-account/delete?id=' + id }) } // 获得邮箱账号精简列表 -export const getSimpleMailAccounts = async () => { +export const getSimpleMailAccountList = async () => { return request.get({ url: '/system/mail-account/list-all-simple' }) } diff --git a/src/views/system/mail/account/account.data.ts b/src/views/system/mail/account/account.data.ts index 5d018cb9..24017dc0 100644 --- a/src/views/system/mail/account/account.data.ts +++ b/src/views/system/mail/account/account.data.ts @@ -77,7 +77,6 @@ const crudSchemas = reactive<CrudSchema[]>([ { label: '操作', field: 'action', - width: '260px', form: { show: false } diff --git a/src/views/system/mail/account/form.vue b/src/views/system/mail/account/form.vue index ba6a31d8..6160dd98 100644 --- a/src/views/system/mail/account/form.vue +++ b/src/views/system/mail/account/form.vue @@ -31,7 +31,7 @@ const openModal = async (type: string, id?: number) => { if (id) { formLoading.value = true try { - const data = await MailAccountApi.getMailAccountApi(id) + const data = await MailAccountApi.getMailAccount(id) formRef.value.setValues(data) } finally { formLoading.value = false @@ -52,10 +52,10 @@ const submitForm = async () => { try { const data = formRef.value.formModel as MailAccountApi.MailAccountVO if (formType.value === 'create') { - await MailAccountApi.createMailAccountApi(data) + await MailAccountApi.createMailAccount(data) message.success(t('common.createSuccess')) } else { - await MailAccountApi.updateMailAccountApi(data) + await MailAccountApi.updateMailAccount(data) message.success(t('common.updateSuccess')) } modelVisible.value = false diff --git a/src/views/system/mail/account/index.vue b/src/views/system/mail/account/index.vue index 8bc29f6d..ec88d60b 100644 --- a/src/views/system/mail/account/index.vue +++ b/src/views/system/mail/account/index.vue @@ -1,28 +1,31 @@ <template> + <!-- 搜索工作栏 --> <ContentWrap> - <!-- TODO @芋艿:setSearchParams --> - <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" /> + <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams"> + <!-- 新增等操作按钮 --> + <template #actionMore> + <el-button + type="primary" + @click="openModal('create')" + v-hasPermi="['system:mail-account:create']" + > + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> + </template> + </Search> </ContentWrap> - <el-button - type="primary" - @click="openModal('create')" - v-hasPermi="['system:mail-account:create']" - > - <Icon icon="ep:plus" class="mr-5px" /> 新增 - </el-button> - + <!-- 列表 --> <ContentWrap> <Table - v-model:pageSize="tableObject.pageSize" - v-model:currentPage="tableObject.currentPage" :columns="allSchemas.tableColumns" :data="tableObject.tableList" :loading="tableObject.loading" :pagination="{ total: tableObject.total }" - @register="register" + v-model:pageSize="tableObject.pageSize" + v-model:currentPage="tableObject.currentPage" > <template #action="{ row }"> <el-button @@ -37,7 +40,7 @@ link type="danger" v-hasPermi="['system:mail-account:delete']" - @click="delList(row.id, false)" + @click="handleDelete(row.id)" > 删除 </el-button> @@ -46,7 +49,7 @@ </ContentWrap> <!-- 表单弹窗:添加/修改 --> - <mail-account-form ref="modalRef" @success="getList" /> + <MailAccountForm ref="modalRef" @success="getList" /> </template> <script setup lang="ts" name="MailAccount"> import { allSchemas } from './account.data' @@ -54,17 +57,15 @@ import { useTable } from '@/hooks/web/useTable' import * as MailAccountApi from '@/api/system/mail/account' import MailAccountForm from './form.vue' -// const { t } = useI18n() // 国际化 -// const message = useMessage() // 消息弹窗 - -const { register, tableObject, methods } = useTable<MailAccountApi.MailAccountVO>({ - getListApi: MailAccountApi.getMailAccountPageApi, - delListApi: MailAccountApi.deleteMailAccountApi +// https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable +// tableObject:表格的属性对象,可获得分页大小、条数等属性 +// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作 +const { tableObject, tableMethods } = useTable({ + getListApi: MailAccountApi.getMailAccountPage, // 分页接口 + delListApi: MailAccountApi.deleteMailAccount // 删除接口 }) - -const { getList, setSearchParams } = methods - -const { delList } = methods +// 获得表格的各种操作 +const { getList, setSearchParams } = tableMethods /** 添加/修改操作 */ const modalRef = ref() @@ -72,5 +73,13 @@ const openModal = (type: string, id?: number) => { modalRef.value.openModal(type, id) } -getList() +/** 删除按钮操作 */ +const handleDelete = (id: number) => { + tableMethods.delList(id, false) +} + +/** 初始化 **/ +onMounted(() => { + getList() +}) </script> diff --git a/src/views/system/mail/log/index.vue b/src/views/system/mail/log/index.vue index e147c52c..bbc0ba00 100644 --- a/src/views/system/mail/log/index.vue +++ b/src/views/system/mail/log/index.vue @@ -91,7 +91,7 @@ const handleDetail = async (rowId: number) => { // ========== 初始化 ========== onMounted(() => { - MailAccountApi.getSimpleMailAccounts().then((data) => { + MailAccountApi.getSimpleMailAccountList().then((data) => { accountOptions.value = data }) }) diff --git a/src/views/system/mail/template/index.vue b/src/views/system/mail/template/index.vue index cb4a7e5d..fab6dc67 100644 --- a/src/views/system/mail/template/index.vue +++ b/src/views/system/mail/template/index.vue @@ -266,7 +266,7 @@ const sendTest = async () => { // ========== 初始化 ========== onMounted(() => { - MailAccountApi.getSimpleMailAccounts().then((data) => { + MailAccountApi.getSimpleMailAccountList().then((data) => { accountOptions.value = data }) }) From 4aeee485826303b406e6a48a05944166c0b5492b Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Fri, 17 Mar 2023 22:58:49 +0800 Subject: [PATCH 019/184] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20useCrudSchemas?= =?UTF-8?q?=E3=80=81useTable=20=E7=9A=84=20autoimport=20=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/vite/index.ts | 5 ++++- src/types/auto-imports.d.ts | 2 ++ src/views/system/mail/account/account.data.ts | 2 +- src/views/system/mail/account/index.vue | 3 +-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/build/vite/index.ts b/build/vite/index.ts index 304ce0d5..53f66ec0 100644 --- a/build/vite/index.ts +++ b/build/vite/index.ts @@ -39,11 +39,14 @@ export function createVitePlugins(VITE_APP_TITLE: string) { imports: [ 'vue', 'vue-router', + // 可额外添加需要 autoImport 的组件 { '@/hooks/web/useI18n': ['useI18n'], - '@/hooks/web/useXTable': ['useXTable'], '@/hooks/web/useMessage': ['useMessage'], + '@/hooks/web/useXTable': ['useXTable'], '@/hooks/web/useVxeCrudSchemas': ['useVxeCrudSchemas'], + '@/hooks/web/useTable': ['useTable'], + '@/hooks/web/useCrudSchemas': ['useCrudSchemas'], '@/utils/formRules': ['required'], '@/utils/dict': ['DICT_TYPE'] } diff --git a/src/types/auto-imports.d.ts b/src/types/auto-imports.d.ts index 7c9f5ff1..75cf16d9 100644 --- a/src/types/auto-imports.d.ts +++ b/src/types/auto-imports.d.ts @@ -52,6 +52,7 @@ declare global { const triggerRef: typeof import('vue')['triggerRef'] const unref: typeof import('vue')['unref'] const useAttrs: typeof import('vue')['useAttrs'] + const useCrudSchemas: typeof import('@/hooks/web/useCrudSchemas')['useCrudSchemas'] const useCssModule: typeof import('vue')['useCssModule'] const useCssVars: typeof import('vue')['useCssVars'] const useI18n: typeof import('@/hooks/web/useI18n')['useI18n'] @@ -60,6 +61,7 @@ declare global { const useRoute: typeof import('vue-router')['useRoute'] const useRouter: typeof import('vue-router')['useRouter'] const useSlots: typeof import('vue')['useSlots'] + const useTable: typeof import('@/hooks/web/useTable')['useTable'] const useVxeCrudSchemas: typeof import('@/hooks/web/useVxeCrudSchemas')['useVxeCrudSchemas'] const useXTable: typeof import('@/hooks/web/useXTable')['useXTable'] const watch: typeof import('vue')['watch'] diff --git a/src/views/system/mail/account/account.data.ts b/src/views/system/mail/account/account.data.ts index 24017dc0..31856296 100644 --- a/src/views/system/mail/account/account.data.ts +++ b/src/views/system/mail/account/account.data.ts @@ -1,4 +1,4 @@ -import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas' +import type { CrudSchema } from '@/hooks/web/useCrudSchemas' import { DictTag } from '@/components/DictTag' import { TableColumn } from '@/types/table' import { dateFormatter } from '@/utils/formatTime' diff --git a/src/views/system/mail/account/index.vue b/src/views/system/mail/account/index.vue index ec88d60b..bb1a0a2a 100644 --- a/src/views/system/mail/account/index.vue +++ b/src/views/system/mail/account/index.vue @@ -53,13 +53,12 @@ </template> <script setup lang="ts" name="MailAccount"> import { allSchemas } from './account.data' -import { useTable } from '@/hooks/web/useTable' import * as MailAccountApi from '@/api/system/mail/account' import MailAccountForm from './form.vue' -// https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable // tableObject:表格的属性对象,可获得分页大小、条数等属性 // tableMethods:表格的操作对象,可进行获得分页、删除记录等操作 +// 详细可见:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable const { tableObject, tableMethods } = useTable({ getListApi: MailAccountApi.getMailAccountPage, // 分页接口 delListApi: MailAccountApi.deleteMailAccount // 删除接口 From a784bc7db27515d053aca451c104d220e4742657 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Fri, 17 Mar 2023 23:23:24 +0800 Subject: [PATCH 020/184] =?UTF-8?q?Form=20=E7=BB=84=E4=BB=B6=EF=BC=9A?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20vLoading=20=E5=8A=A0=E8=BD=BD=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Form/src/Form.vue | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/Form/src/Form.vue b/src/components/Form/src/Form.vue index 5e5120c3..c1121641 100644 --- a/src/components/Form/src/Form.vue +++ b/src/components/Form/src/Form.vue @@ -35,7 +35,8 @@ export default defineComponent({ default: () => [] }, // 是否需要栅格布局 - isCol: propTypes.bool.def(true), + // update by 芋艿:将 true 改成 false,因为项目更常用这种方式 + isCol: propTypes.bool.def(false), // 表单数据对象 model: { type: Object as PropType<Recordable>, @@ -46,7 +47,9 @@ export default defineComponent({ // 是否自定义内容 isCustom: propTypes.bool.def(false), // 表单label宽度 - labelWidth: propTypes.oneOfType([String, Number]).def('auto') + labelWidth: propTypes.oneOfType([String, Number]).def('auto'), + // 是否 loading 数据中 add by 芋艿 + vLoading: propTypes.bool.def(false) }, emits: ['register'], setup(props, { slots, expose, emit }) { @@ -280,6 +283,7 @@ export default defineComponent({ {...getFormBindValue()} model={props.isCustom ? props.model : formModel} class={prefixCls} + v-loading={props.vLoading} > {{ // 如果需要自定义,就什么都不渲染,而是提供默认插槽 From e42a9aa60cebbc6462ed9e34230f61e3fee727aa Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Fri, 17 Mar 2023 23:24:21 +0800 Subject: [PATCH 021/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=AE=8C=E5=96=84=E5=AE=8C=20=E9=82=AE?= =?UTF-8?q?=E4=BB=B6=E8=B4=A6=E5=8F=B7=20=E7=9A=84=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/mail/account/account.data.ts | 2 +- src/views/system/mail/account/form.vue | 5 ++--- src/views/system/mail/account/index.vue | 10 +++++----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/views/system/mail/account/account.data.ts b/src/views/system/mail/account/account.data.ts index 31856296..85a27d85 100644 --- a/src/views/system/mail/account/account.data.ts +++ b/src/views/system/mail/account/account.data.ts @@ -23,7 +23,7 @@ export const rules = reactive({ sslEnable: [required] }) -// CrudSchema +// CrudSchema:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/hooks/useCrudSchemas.html const crudSchemas = reactive<CrudSchema[]>([ { label: '邮箱', diff --git a/src/views/system/mail/account/form.vue b/src/views/system/mail/account/form.vue index 6160dd98..70683830 100644 --- a/src/views/system/mail/account/form.vue +++ b/src/views/system/mail/account/form.vue @@ -1,7 +1,6 @@ <template> <Dialog :title="modelTitle" v-model="modelVisible"> - <!-- TODO 芋艿:loading --> - <Form ref="formRef" :schema="allSchemas.formSchema" :rules="rules" :isCol="false" /> + <Form ref="formRef" :schema="allSchemas.formSchema" :rules="rules" v-loading="formLoading" /> <template #footer> <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> <el-button @click="modelVisible = false">取 消</el-button> @@ -12,7 +11,6 @@ import * as MailAccountApi from '@/api/system/mail/account' import { rules, allSchemas } from './account.data' -const formRef = ref() // 表单 Ref const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -20,6 +18,7 @@ const modelVisible = ref(false) // 弹窗的是否展示 const modelTitle = ref('') // 弹窗的标题 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formRef = ref() // 表单 Ref /** 打开弹窗 */ const openModal = async (type: string, id?: number) => { diff --git a/src/views/system/mail/account/index.vue b/src/views/system/mail/account/index.vue index bb1a0a2a..1f80684f 100644 --- a/src/views/system/mail/account/index.vue +++ b/src/views/system/mail/account/index.vue @@ -1,6 +1,6 @@ <template> <!-- 搜索工作栏 --> - <ContentWrap> + <content-wrap> <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams"> <!-- 新增等操作按钮 --> <template #actionMore> @@ -13,10 +13,10 @@ </el-button> </template> </Search> - </ContentWrap> + </content-wrap> <!-- 列表 --> - <ContentWrap> + <content-wrap> <Table :columns="allSchemas.tableColumns" :data="tableObject.tableList" @@ -46,10 +46,10 @@ </el-button> </template> </Table> - </ContentWrap> + </content-wrap> <!-- 表单弹窗:添加/修改 --> - <MailAccountForm ref="modalRef" @success="getList" /> + <mail-account-form ref="modalRef" @success="getList" /> </template> <script setup lang="ts" name="MailAccount"> import { allSchemas } from './account.data' From 90cd829737b9ddbc0c6b23f76f83b8b0f8be9646 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 18 Mar 2023 00:42:25 +0800 Subject: [PATCH 022/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E9=82=AE=E4=BB=B6=E6=A8=A1=E7=89=88=E7=9A=84=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/infra/dataSourceConfig/form.vue | 3 +- src/views/system/mail/account/account.data.ts | 4 +- src/views/system/mail/template/index.vue | 319 ++++-------------- .../system/mail/template/template.data.ts | 168 ++++----- 4 files changed, 155 insertions(+), 339 deletions(-) diff --git a/src/views/infra/dataSourceConfig/form.vue b/src/views/infra/dataSourceConfig/form.vue index b050a1c8..ea699b57 100644 --- a/src/views/infra/dataSourceConfig/form.vue +++ b/src/views/infra/dataSourceConfig/form.vue @@ -30,7 +30,6 @@ </template> <script setup lang="ts"> import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig' -import { DataSourceConfigVO } from '@/api/infra/dataSourceConfig' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -39,7 +38,7 @@ const modelVisible = ref(false) // 弹窗的是否展示 const modelTitle = ref('') // 弹窗的标题 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formType = ref('') // 表单的类型:create - 新增;update - 修改 -const formData = ref<DataSourceConfigVO>({ +const formData = ref<DataSourceConfigApi.DataSourceConfigVO>({ id: undefined, name: '', url: '', diff --git a/src/views/system/mail/account/account.data.ts b/src/views/system/mail/account/account.data.ts index 85a27d85..532a6a08 100644 --- a/src/views/system/mail/account/account.data.ts +++ b/src/views/system/mail/account/account.data.ts @@ -77,9 +77,7 @@ const crudSchemas = reactive<CrudSchema[]>([ { label: '操作', field: 'action', - form: { - show: false - } + isForm: false } ]) export const { allSchemas } = useCrudSchemas(crudSchemas) diff --git a/src/views/system/mail/template/index.vue b/src/views/system/mail/template/index.vue index fab6dc67..d9684e86 100644 --- a/src/views/system/mail/template/index.vue +++ b/src/views/system/mail/template/index.vue @@ -1,273 +1,84 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #accountId_search> - <el-select v-model="queryParams.accountId"> - <el-option :key="undefined" label="全部" :value="undefined" /> - <el-option - v-for="item in accountOptions" - :key="item.id" - :label="item.mail" - :value="item.id" - /> - </el-select> - </template> - <template #toolbar_buttons> - <!-- 操作:新增 --> - <XButton + <!-- 搜索工作栏 --> + <content-wrap> + <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams"> + <!-- 新增等操作按钮 --> + <template #actionMore> + <el-button type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['system:mail-template:create']" - @click="handleCreate()" - /> + @click="openModal('create')" + v-hasPermi="['system:mail-account:create']" + > + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> </template> - <template #accountId_default="{ row }"> - <span>{{ accountOptions.find((account) => account.id === row.accountId)?.mail }}</span> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:测试短信 --> - <XTextButton - preIcon="ep:cpu" - :title="t('action.test')" - v-hasPermi="['system:mail-template:send-mail']" - @click="handleSendMail(row)" - /> - <!-- 操作:修改 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['system:mail-template:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['system:mail-template:query']" - @click="handleDetail(row.id)" - /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['system:mail-template:delete']" - @click="deleteData(row.id)" - /> - </template> - </XTable> - </ContentWrap> + </Search> + </content-wrap> - <!-- 添加/修改/详情的弹窗 --> - <XModal id="mailTemplateModel" :loading="modelLoading" v-model="modelVisible" :title="modelTitle"> - <!-- 表单:添加/修改 --> - <Form - ref="formRef" - v-if="['create', 'update'].includes(actionType)" - :schema="allSchemas.formSchema" - :rules="rules" + <!-- 列表 --> + <content-wrap> + <Table + :columns="allSchemas.tableColumns" + :data="tableObject.tableList" + :loading="tableObject.loading" + :pagination="{ + total: tableObject.total + }" + v-model:pageSize="tableObject.pageSize" + v-model:currentPage="tableObject.currentPage" > - <template #accountId="form"> - <el-select v-model="form.accountId"> - <el-option - v-for="item in accountOptions" - :key="item.id" - :label="item.mail" - :value="item.id" - /> - </el-select> + <template #action="{ row }"> + <el-button + link + type="primary" + @click="openModal('update', row.id)" + v-hasPermi="['system:mail-template:update']" + > + 编辑 + </el-button> + <el-button + link + type="danger" + v-hasPermi="['system:mail-template:delete']" + @click="handleDelete(row.id)" + > + 删除 + </el-button> </template> - </Form> - <!-- 表单:详情 --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - /> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="actionLoading" - @click="submitForm()" - /> - <!-- 按钮:关闭 --> - <XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" /> - </template> - </XModal> + </Table> + </content-wrap> - <!-- 测试邮件的弹窗 --> - <XModal id="sendTest" v-model="sendVisible" title="测试"> - <el-form :model="sendForm" :rules="sendRules" label-width="200px" label-position="top"> - <el-form-item label="模板内容" prop="content"> - <Editor :model-value="sendForm.content" readonly height="150px" /> - </el-form-item> - <el-form-item label="收件邮箱" prop="mail"> - <el-input v-model="sendForm.mail" placeholder="请输入收件邮箱" /> - </el-form-item> - <el-form-item - v-for="param in sendForm.params" - :key="param" - :label="'参数 {' + param + '}'" - :prop="'templateParams.' + param" - > - <el-input - v-model="sendForm.templateParams[param]" - :placeholder="'请输入 ' + param + ' 参数'" - /> - </el-form-item> - </el-form> - <!-- 操作按钮 --> - <template #footer> - <XButton - type="primary" - :title="t('action.test')" - :loading="actionLoading" - @click="sendTest()" - /> - <XButton :title="t('dialog.close')" @click="sendVisible = false" /> - </template> - </XModal> + <!-- 表单弹窗:添加/修改 --> + <!-- <mail-template-form ref="modalRef" @success="getList" />--> </template> <script setup lang="ts" name="MailTemplate"> -import { FormExpose } from '@/components/Form' -// 业务相关的 import -import { rules, allSchemas } from './template.data' +import { allSchemas } from './template.data' import * as MailTemplateApi from '@/api/system/mail/template' -import * as MailAccountApi from '@/api/system/mail/account' +// import MailAccountForm from './form.vue' -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 - -// 列表相关的变量 -const queryParams = reactive({ - accountId: null +// tableObject:表格的属性对象,可获得分页大小、条数等属性 +// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作 +// 详细可见:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable +const { tableObject, tableMethods } = useTable({ + getListApi: MailTemplateApi.getMailTemplatePageApi, // 分页接口 + delListApi: MailTemplateApi.deleteMailTemplateApi // 删除接口 }) -const [registerTable, { reload, deleteData }] = useXTable({ - allSchemas: allSchemas, - params: queryParams, - getListApi: MailTemplateApi.getMailTemplatePageApi, - deleteApi: MailTemplateApi.deleteMailTemplateApi -}) -const accountOptions = ref<any[]>([]) // 账号下拉选项 +// 获得表格的各种操作 +const { getList, setSearchParams } = tableMethods -// 弹窗相关的变量 -const modelVisible = ref(false) // 是否显示弹出层 -const modelTitle = ref('edit') // 弹出层标题 -const modelLoading = ref(false) // 弹出层loading -const actionType = ref('') // 操作按钮的类型 -const actionLoading = ref(false) // 按钮 Loading -const formRef = ref<FormExpose>() // 表单 Ref -const detailData = ref() // 详情 Ref +/** 添加/修改操作 */ +// const modalRef = ref() +// const openModal = (type: string, id?: number) => { +// modalRef.value.openModal(type, id) +// } -// 设置标题 -const setDialogTile = (type: string) => { - modelLoading.value = true - modelTitle.value = t('action.' + type) - actionType.value = type - modelVisible.value = true +/** 删除按钮操作 */ +const handleDelete = (id: number) => { + tableMethods.delList(id, false) } -// 新增操作 -const handleCreate = () => { - setDialogTile('create') - modelLoading.value = false -} - -// 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - // 设置数据 - const res = await MailTemplateApi.getMailTemplateApi(rowId) - unref(formRef)?.setValues(res) - modelLoading.value = false -} - -// 详情操作 -const handleDetail = async (rowId: number) => { - setDialogTile('detail') - const res = await MailTemplateApi.getMailTemplateApi(rowId) - detailData.value = res - modelLoading.value = false -} - -// 提交按钮 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - actionLoading.value = true - // 提交请求 - try { - const data = unref(formRef)?.formModel as MailTemplateApi.MailTemplateVO - if (actionType.value === 'create') { - await MailTemplateApi.createMailTemplateApi(data) - message.success(t('common.createSuccess')) - } else { - await MailTemplateApi.updateMailTemplateApi(data) - message.success(t('common.updateSuccess')) - } - modelVisible.value = false - } finally { - actionLoading.value = false - // 刷新列表 - await reload() - } - } - }) -} - -// ========== 测试相关 ========== -const sendForm = ref({ - content: '', - params: {}, - mail: '', - templateCode: '', - templateParams: {} -}) -const sendRules = ref({ - mail: [{ required: true, message: '邮箱不能为空', trigger: 'blur' }], - templateCode: [{ required: true, message: '模版编号不能为空', trigger: 'blur' }], - templateParams: {} -}) -const sendVisible = ref(false) - -const handleSendMail = (row: any) => { - sendForm.value.content = row.content - sendForm.value.params = row.params - sendForm.value.templateCode = row.code - sendForm.value.templateParams = row.params.reduce(function (obj, item) { - obj[item] = undefined - return obj - }, {}) - sendRules.value.templateParams = row.params.reduce(function (obj, item) { - obj[item] = { required: true, message: '参数 ' + item + ' 不能为空', trigger: 'change' } - return obj - }, {}) - sendVisible.value = true -} - -const sendTest = async () => { - const data: MailTemplateApi.MailSendReqVO = { - mail: sendForm.value.mail, - templateCode: sendForm.value.templateCode, - templateParams: sendForm.value.templateParams as unknown as Map<string, Object> - } - const res = await MailTemplateApi.sendMailApi(data) - if (res) { - message.success('提交发送成功!发送结果,见发送日志编号:' + res) - } - sendVisible.value = false -} - -// ========== 初始化 ========== +/** 初始化 **/ onMounted(() => { - MailAccountApi.getSimpleMailAccountList().then((data) => { - accountOptions.value = data - }) + getList() }) </script> diff --git a/src/views/system/mail/template/template.data.ts b/src/views/system/mail/template/template.data.ts index 32522e33..0f4c75f7 100644 --- a/src/views/system/mail/template/template.data.ts +++ b/src/views/system/mail/template/template.data.ts @@ -1,98 +1,106 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' +import type { CrudSchema } from '@/hooks/web/useCrudSchemas' +import { dateFormatter } from '@/utils/formatTime' +import { TableColumn } from '@/types/table' +import { DictTag } from '@/components/DictTag' +import * as MailAccountApi from '@/api/system/mail/account' + +const accounts = await MailAccountApi.getSimpleMailAccountList() // 表单校验 export const rules = reactive({ name: [required], code: [required], accountId: [required], - title: [required], + label: [required], content: [required], params: [required], status: [required] }) -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', // 默认的主键ID - primaryTitle: '编号', // 默认显示的值 - primaryType: null, - action: true, - actionWidth: '260', - columns: [ - { - title: '模板编码', - field: 'code', - isSearch: true - }, - { - title: '模板名称', - field: 'name', - isSearch: true - }, - { - title: '模板标题', - field: 'title' - }, - { - title: '模板内容', - field: 'content', - form: { - component: 'Editor', - colProps: { - span: 24 - }, - componentProps: { - valueHtml: '' - } - } - }, - { - title: '邮箱账号', - field: 'accountId', - isSearch: true, - table: { - width: 200, - slots: { - default: 'accountId_default' - } +// CrudSchema:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/hooks/useCrudSchemas.html +const crudSchemas = reactive<CrudSchema[]>([ + { + label: '模板编码', + field: 'code', + isSearch: true + }, + { + label: '模板名称', + field: 'name', + isSearch: true + }, + { + label: '模板标题', + field: 'title' + }, + { + label: '模板内容', + field: 'content', + form: { + component: 'Editor', + colProps: { + span: 24 }, - search: { - slots: { - default: 'accountId_search' - } + componentProps: { + valueHtml: '' } + } + }, + { + label: '邮箱账号', + field: 'accountId', + isSearch: true, + width: '200px', + formatter: (_: Recordable, __: TableColumn, cellValue: number) => { + return accounts.find((account) => account.id === cellValue)?.mail }, - { - title: '发送人名称', - field: 'nickname' - }, - { - title: '开启状态', - field: 'status', - isSearch: true, - dictType: DICT_TYPE.COMMON_STATUS, - dictClass: 'number' - }, - { - title: '备注', - field: 'remark', - isTable: false - }, - { - title: '创建时间', - field: 'createTime', - isForm: false, - formatter: 'formatDate', - table: { - width: 180 + search: { + show: true, + component: 'Select', + api: () => { + return accounts }, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' + componentProps: { + optionsAlias: { + labelField: 'mail', + valueField: 'id' } } } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) + }, + { + label: '发送人名称', + field: 'nickname' + }, + { + label: '开启状态', + field: 'status', + isSearch: true, + formatter: (_: Recordable, __: TableColumn, cellValue: number) => { + return h(DictTag, { + type: DICT_TYPE.COMMON_STATUS, + value: cellValue + }) + }, + dictType: DICT_TYPE.COMMON_STATUS, + dictClass: 'number' + }, + { + label: '备注', + field: 'remark', + isTable: false + }, + { + label: '创建时间', + field: 'createTime', + isForm: false, + formatter: dateFormatter, + search: { + show: true, + itemRender: { + name: 'XDataTimePicker' + } + } + } +]) +export const { allSchemas } = useCrudSchemas(crudSchemas) From 4d03e46142ea2712b01f0a628d458c14cd9b11d2 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 18 Mar 2023 01:23:07 +0800 Subject: [PATCH 023/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E9=82=AE=E4=BB=B6=E6=A8=A1=E7=89=88=E7=9A=84=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/infra/config/index.vue | 1 - src/views/infra/file/index.vue | 1 - src/views/infra/fileConfig/index.vue | 1 - src/views/system/mail/template/template.data.ts | 7 +++++-- src/views/system/sms/smsChannel/index.vue | 3 +-- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/views/infra/config/index.vue b/src/views/infra/config/index.vue index 4c35d485..0136a9d5 100644 --- a/src/views/infra/config/index.vue +++ b/src/views/infra/config/index.vue @@ -33,7 +33,6 @@ v-model="queryParams.createTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange" - range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" diff --git a/src/views/infra/file/index.vue b/src/views/infra/file/index.vue index 5137ec3e..adf9d3a4 100644 --- a/src/views/infra/file/index.vue +++ b/src/views/infra/file/index.vue @@ -23,7 +23,6 @@ v-model="queryParams.createTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange" - range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" diff --git a/src/views/infra/fileConfig/index.vue b/src/views/infra/fileConfig/index.vue index e38e3a50..ed0cfed7 100644 --- a/src/views/infra/fileConfig/index.vue +++ b/src/views/infra/fileConfig/index.vue @@ -25,7 +25,6 @@ v-model="queryParams.createTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange" - range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" diff --git a/src/views/system/mail/template/template.data.ts b/src/views/system/mail/template/template.data.ts index 0f4c75f7..aec8c401 100644 --- a/src/views/system/mail/template/template.data.ts +++ b/src/views/system/mail/template/template.data.ts @@ -97,8 +97,11 @@ const crudSchemas = reactive<CrudSchema[]>([ formatter: dateFormatter, search: { show: true, - itemRender: { - name: 'XDataTimePicker' + component: 'DatePicker', + componentProps: { + valueFormat: 'YYYY-MM-DD HH:mm:ss', + type: 'daterange', + defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')] } } } diff --git a/src/views/system/sms/smsChannel/index.vue b/src/views/system/sms/smsChannel/index.vue index 94a46b7d..bac94f25 100644 --- a/src/views/system/sms/smsChannel/index.vue +++ b/src/views/system/sms/smsChannel/index.vue @@ -22,9 +22,8 @@ <el-form-item label="创建时间" prop="createTime"> <el-date-picker v-model="queryParams.createTime" - value-format="yyyy-MM-dd HH:mm:ss" + value-format="YYYY-MM-DD HH:mm:ss" type="daterange" - range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" From ba327cd8aeff7870f82cb8e2389a2915eabf588c Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 18 Mar 2023 10:39:19 +0800 Subject: [PATCH 024/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E9=82=AE=E4=BB=B6=E6=A8=A1=E7=89=88=E7=9A=84=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=92=8C=E4=BF=AE=E6=94=B9=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/mail/template/index.ts | 20 ++---- src/views/system/mail/account/form.vue | 1 - src/views/system/mail/template/form.vue | 66 +++++++++++++++++++ src/views/system/mail/template/index.vue | 16 ++--- .../system/mail/template/template.data.ts | 23 +++++-- 5 files changed, 98 insertions(+), 28 deletions(-) create mode 100644 src/views/system/mail/template/form.vue diff --git a/src/api/system/mail/template/index.ts b/src/api/system/mail/template/index.ts index c044ddd4..fb7ce5ea 100644 --- a/src/api/system/mail/template/index.ts +++ b/src/api/system/mail/template/index.ts @@ -13,14 +13,6 @@ export interface MailTemplateVO { remark: string } -export interface MailTemplatePageReqVO extends PageParam { - name?: string - code?: string - accountId?: number - status?: number - createTime?: Date[] -} - export interface MailSendReqVO { mail: string templateCode: string @@ -28,31 +20,31 @@ export interface MailSendReqVO { } // 查询邮件模版列表 -export const getMailTemplatePageApi = async (params: MailTemplatePageReqVO) => { +export const getMailTemplatePage = async (params: PageParam) => { return await request.get({ url: '/system/mail-template/page', params }) } // 查询邮件模版详情 -export const getMailTemplateApi = async (id: number) => { +export const getMailTemplate = async (id: number) => { return await request.get({ url: '/system/mail-template/get?id=' + id }) } // 新增邮件模版 -export const createMailTemplateApi = async (data: MailTemplateVO) => { +export const createMailTemplate = async (data: MailTemplateVO) => { return await request.post({ url: '/system/mail-template/create', data }) } // 修改邮件模版 -export const updateMailTemplateApi = async (data: MailTemplateVO) => { +export const updateMailTemplate = async (data: MailTemplateVO) => { return await request.put({ url: '/system/mail-template/update', data }) } // 删除邮件模版 -export const deleteMailTemplateApi = async (id: number) => { +export const deleteMailTemplate = async (id: number) => { return await request.delete({ url: '/system/mail-template/delete?id=' + id }) } // 发送邮件 -export const sendMailApi = (data: MailSendReqVO) => { +export const sendMail = (data: MailSendReqVO) => { return request.post({ url: '/system/mail-template/send-mail', data }) } diff --git a/src/views/system/mail/account/form.vue b/src/views/system/mail/account/form.vue index 70683830..78d7cf0d 100644 --- a/src/views/system/mail/account/form.vue +++ b/src/views/system/mail/account/form.vue @@ -25,7 +25,6 @@ const openModal = async (type: string, id?: number) => { modelVisible.value = true modelTitle.value = t('action.' + type) formType.value = type - // resetForm() // 修改时,设置数据 if (id) { formLoading.value = true diff --git a/src/views/system/mail/template/form.vue b/src/views/system/mail/template/form.vue new file mode 100644 index 00000000..92f3c5c1 --- /dev/null +++ b/src/views/system/mail/template/form.vue @@ -0,0 +1,66 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible" :scroll="true" :width="800" :max-height="500"> + <Form ref="formRef" :schema="allSchemas.formSchema" :rules="rules" v-loading="formLoading" /> + <template #footer> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import * as MailTemplateApi from '@/api/system/mail/template' +import { rules, allSchemas } from './template.data' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + const data = await MailTemplateApi.getMailTemplate(id) + formRef.value.setValues(data) + } finally { + formLoading.value = false + } + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.getElFormRef().validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formRef.value.formModel as MailTemplateApi.MailTemplateVO + if (formType.value === 'create') { + await MailTemplateApi.createMailTemplate(data) + message.success(t('common.createSuccess')) + } else { + await MailTemplateApi.updateMailTemplate(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} +</script> diff --git a/src/views/system/mail/template/index.vue b/src/views/system/mail/template/index.vue index d9684e86..ef8d800d 100644 --- a/src/views/system/mail/template/index.vue +++ b/src/views/system/mail/template/index.vue @@ -49,28 +49,28 @@ </content-wrap> <!-- 表单弹窗:添加/修改 --> - <!-- <mail-template-form ref="modalRef" @success="getList" />--> + <mail-template-form ref="modalRef" @success="getList" /> </template> <script setup lang="ts" name="MailTemplate"> import { allSchemas } from './template.data' import * as MailTemplateApi from '@/api/system/mail/template' -// import MailAccountForm from './form.vue' +import MailTemplateForm from './form.vue' // tableObject:表格的属性对象,可获得分页大小、条数等属性 // tableMethods:表格的操作对象,可进行获得分页、删除记录等操作 // 详细可见:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable const { tableObject, tableMethods } = useTable({ - getListApi: MailTemplateApi.getMailTemplatePageApi, // 分页接口 - delListApi: MailTemplateApi.deleteMailTemplateApi // 删除接口 + getListApi: MailTemplateApi.getMailTemplatePage, // 分页接口 + delListApi: MailTemplateApi.deleteMailTemplate // 删除接口 }) // 获得表格的各种操作 const { getList, setSearchParams } = tableMethods /** 添加/修改操作 */ -// const modalRef = ref() -// const openModal = (type: string, id?: number) => { -// modalRef.value.openModal(type, id) -// } +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, id) +} /** 删除按钮操作 */ const handleDelete = (id: number) => { diff --git a/src/views/system/mail/template/template.data.ts b/src/views/system/mail/template/template.data.ts index aec8c401..129fa4e5 100644 --- a/src/views/system/mail/template/template.data.ts +++ b/src/views/system/mail/template/template.data.ts @@ -42,14 +42,14 @@ const crudSchemas = reactive<CrudSchema[]>([ span: 24 }, componentProps: { - valueHtml: '' + valueHtml: '', + height: 200 } } }, { label: '邮箱账号', field: 'accountId', - isSearch: true, width: '200px', formatter: (_: Recordable, __: TableColumn, cellValue: number) => { return accounts.find((account) => account.id === cellValue)?.mail @@ -57,9 +57,17 @@ const crudSchemas = reactive<CrudSchema[]>([ search: { show: true, component: 'Select', - api: () => { - return accounts - }, + api: () => accounts, + componentProps: { + optionsAlias: { + labelField: 'mail', + valueField: 'id' + } + } + }, + form: { + component: 'Select', + api: () => accounts, componentProps: { optionsAlias: { labelField: 'mail', @@ -104,6 +112,11 @@ const crudSchemas = reactive<CrudSchema[]>([ defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')] } } + }, + { + label: '操作', + field: 'action', + isForm: false } ]) export const { allSchemas } = useCrudSchemas(crudSchemas) From 7d22328d6907635f8eb7e7606db65a84d4933608 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 18 Mar 2023 11:02:40 +0800 Subject: [PATCH 025/184] =?UTF-8?q?Table=20=E7=BB=84=E4=BB=B6=EF=BC=9A?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AF=B9=E5=AD=97=E5=85=B8=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/web/useCrudSchemas.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/hooks/web/useCrudSchemas.ts b/src/hooks/web/useCrudSchemas.ts index 543111ca..66c5eb0a 100644 --- a/src/hooks/web/useCrudSchemas.ts +++ b/src/hooks/web/useCrudSchemas.ts @@ -8,6 +8,7 @@ import { FormSchema } from '@/types/form' import { TableColumn } from '@/types/table' import { DescriptionsSchema } from '@/types/descriptions' import { ComponentOptions, ComponentProps } from '@/types/components' +import { DictTag } from '@/components/DictTag' export type CrudSchema = Omit<TableColumn, 'children'> & { isSearch?: boolean // 是否在查询显示 @@ -151,6 +152,15 @@ const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => { const tableColumns = treeMap<CrudSchema>(crudSchema, { conversion: (schema: CrudSchema) => { if (schema?.isTable !== false && schema?.table?.show !== false) { + // add by 芋艿:增加对 dict 字典数据的支持 + if (!schema.formatter && schema.dictType) { + schema.formatter = (_: Recordable, __: TableColumn, cellValue: any) => { + return h(DictTag, { + type: schema.dictType!, // ! 表示一定不为空 + value: cellValue + }) + } + } return { ...schema.table, ...schema From e8d83d4718380a4e7b9ecde399e443e50a297533 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 18 Mar 2023 11:40:55 +0800 Subject: [PATCH 026/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=AE=8C=E5=96=84=E5=AE=8C=20=E9=82=AE?= =?UTF-8?q?=E4=BB=B6=E6=A8=A1=E7=89=88=20=E7=9A=84=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/mail/account/account.data.ts | 16 +-- src/views/system/mail/template/index.vue | 18 +++ src/views/system/mail/template/send.vue | 115 ++++++++++++++++++ .../system/mail/template/template.data.ts | 10 -- 4 files changed, 136 insertions(+), 23 deletions(-) create mode 100644 src/views/system/mail/template/send.vue diff --git a/src/views/system/mail/account/account.data.ts b/src/views/system/mail/account/account.data.ts index 532a6a08..f01c39f2 100644 --- a/src/views/system/mail/account/account.data.ts +++ b/src/views/system/mail/account/account.data.ts @@ -1,8 +1,5 @@ import type { CrudSchema } from '@/hooks/web/useCrudSchemas' -import { DictTag } from '@/components/DictTag' -import { TableColumn } from '@/types/table' import { dateFormatter } from '@/utils/formatTime' -import { getBoolDictOptions } from '@/utils/dict' const { t } = useI18n() // 国际化 @@ -55,17 +52,10 @@ const crudSchemas = reactive<CrudSchema[]>([ { label: '是否开启 SSL', field: 'sslEnable', - formatter: (_: Recordable, __: TableColumn, cellValue: boolean) => { - return h(DictTag, { - type: DICT_TYPE.INFRA_BOOLEAN_STRING, - value: cellValue - }) - }, + dictType: DICT_TYPE.INFRA_BOOLEAN_STRING, + dictClass: 'boolean', form: { - component: 'Radio', - componentProps: { - options: getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING) - } + component: 'Radio' } }, { diff --git a/src/views/system/mail/template/index.vue b/src/views/system/mail/template/index.vue index ef8d800d..b9a5040b 100644 --- a/src/views/system/mail/template/index.vue +++ b/src/views/system/mail/template/index.vue @@ -28,6 +28,14 @@ v-model:currentPage="tableObject.currentPage" > <template #action="{ row }"> + <el-button + link + type="primary" + @click="openSend(row.id)" + v-hasPermi="['system:mail-template:send-mail']" + > + 测试 + </el-button> <el-button link type="primary" @@ -50,11 +58,15 @@ <!-- 表单弹窗:添加/修改 --> <mail-template-form ref="modalRef" @success="getList" /> + + <!-- 表单弹窗:发送测试 --> + <mail-template-send ref="sendRef" /> </template> <script setup lang="ts" name="MailTemplate"> import { allSchemas } from './template.data' import * as MailTemplateApi from '@/api/system/mail/template' import MailTemplateForm from './form.vue' +import MailTemplateSend from './send.vue' // tableObject:表格的属性对象,可获得分页大小、条数等属性 // tableMethods:表格的操作对象,可进行获得分页、删除记录等操作 @@ -77,6 +89,12 @@ const handleDelete = (id: number) => { tableMethods.delList(id, false) } +/** 发送测试操作 */ +const sendRef = ref() +const openSend = (id: number) => { + sendRef.value.openModal(id) +} + /** 初始化 **/ onMounted(() => { getList() diff --git a/src/views/system/mail/template/send.vue b/src/views/system/mail/template/send.vue new file mode 100644 index 00000000..94aaf004 --- /dev/null +++ b/src/views/system/mail/template/send.vue @@ -0,0 +1,115 @@ +<template> + <Dialog title="测试" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="120px" + v-loading="formLoading" + > + <el-form-item label="模板内容" prop="content"> + <Editor :model-value="formData.content" readonly height="150px" /> + </el-form-item> + <el-form-item label="收件邮箱" prop="mail"> + <el-input v-model="formData.mail" placeholder="请输入收件邮箱" /> + </el-form-item> + <el-form-item + v-for="param in formData.params" + :key="param" + :label="'参数 {' + param + '}'" + :prop="'templateParams.' + param" + > + <el-input + v-model="formData.templateParams[param]" + :placeholder="'请输入 ' + param + ' 参数'" + /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import * as MailTemplateApi from '@/api/system/mail/template' + +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formData = ref({ + content: '', + params: {}, + mail: '', + templateCode: '', + templateParams: new Map() +}) +const formRules = reactive({ + mail: [{ required: true, message: '邮箱不能为空', trigger: 'blur' }], + templateCode: [{ required: true, message: '模版编号不能为空', trigger: 'blur' }], + templateParams: {} +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async (id: number) => { + modelVisible.value = true + resetForm() + // 设置数据 + formLoading.value = true + try { + const data = await MailTemplateApi.getMailTemplate(id) + // 设置动态表单 + formData.value.content = data.content + formData.value.params = data.params + formData.value.templateCode = data.code + formData.value.templateParams = data.params.reduce((obj, item) => { + obj[item] = '' // 给每个动态属性赋值,避免无法读取 + return obj + }, {}) + formRules.templateParams = data.params.reduce((obj, item) => { + obj[item] = { required: true, message: '参数 ' + item + ' 不能为空', trigger: 'blur' } + return obj + }, {}) + } finally { + formLoading.value = false + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as MailTemplateApi.MailSendReqVO + const logId = await MailTemplateApi.sendMail(data) + if (logId) { + message.success('提交发送成功!发送结果,见发送日志编号:' + logId) + } + modelVisible.value = false + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + content: '', + params: {}, + mail: '', + templateCode: '', + templateParams: new Map() + } + formRules.templateParams = {} + formRef.value?.resetFields() +} +</script> diff --git a/src/views/system/mail/template/template.data.ts b/src/views/system/mail/template/template.data.ts index 129fa4e5..bfdd5cad 100644 --- a/src/views/system/mail/template/template.data.ts +++ b/src/views/system/mail/template/template.data.ts @@ -1,7 +1,6 @@ import type { CrudSchema } from '@/hooks/web/useCrudSchemas' import { dateFormatter } from '@/utils/formatTime' import { TableColumn } from '@/types/table' -import { DictTag } from '@/components/DictTag' import * as MailAccountApi from '@/api/system/mail/account' const accounts = await MailAccountApi.getSimpleMailAccountList() @@ -38,9 +37,6 @@ const crudSchemas = reactive<CrudSchema[]>([ field: 'content', form: { component: 'Editor', - colProps: { - span: 24 - }, componentProps: { valueHtml: '', height: 200 @@ -84,12 +80,6 @@ const crudSchemas = reactive<CrudSchema[]>([ label: '开启状态', field: 'status', isSearch: true, - formatter: (_: Recordable, __: TableColumn, cellValue: number) => { - return h(DictTag, { - type: DICT_TYPE.COMMON_STATUS, - value: cellValue - }) - }, dictType: DICT_TYPE.COMMON_STATUS, dictClass: 'number' }, From 3c75d6065ddd91180c7b5de0100dc3738ca64448 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 18 Mar 2023 12:24:21 +0800 Subject: [PATCH 027/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E9=82=AE=E4=BB=B6=E6=97=A5=E5=BF=97=E7=9A=84=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/mail/log/index.vue | 127 ++++----- src/views/system/mail/log/log.data.ts | 242 +++++++++--------- .../system/mail/template/template.data.ts | 1 + 3 files changed, 169 insertions(+), 201 deletions(-) diff --git a/src/views/system/mail/log/index.vue b/src/views/system/mail/log/index.vue index bbc0ba00..a8b0c6c1 100644 --- a/src/views/system/mail/log/index.vue +++ b/src/views/system/mail/log/index.vue @@ -1,98 +1,59 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #accountId_search> - <el-select v-model="queryParams.accountId"> - <el-option :key="undefined" label="全部" :value="undefined" /> - <el-option - v-for="item in accountOptions" - :key="item.id" - :label="item.mail" - :value="item.id" - /> - </el-select> - </template> - <template #toMail_default="{ row }"> - <div>{{ row.toMail }}</div> - <div v-if="row.userType && row.userId"> - <DictTag :type="DICT_TYPE.USER_TYPE" :value="row.userType" />{{ '(' + row.userId + ')' }} - </div> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" + <!-- 搜索工作栏 --> + <content-wrap> + <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" /> + </content-wrap> + + <!-- 列表 --> + <content-wrap> + <Table + :columns="allSchemas.tableColumns" + :data="tableObject.tableList" + :loading="tableObject.loading" + :pagination="{ + total: tableObject.total + }" + v-model:pageSize="tableObject.pageSize" + v-model:currentPage="tableObject.currentPage" + > + <template #action="{ row }"> + <el-button + link + type="primary" + @click="openModal('update', row.id)" v-hasPermi="['system:mail-log:query']" - @click="handleDetail(row.id)" - /> + > + 详情 + </el-button> </template> - </XTable> - </ContentWrap> - <!-- 弹窗 --> - <XModal id="mailLogModel" :loading="modelLoading" v-model="modelVisible" :title="modelTitle"> - <!-- 表单:详情 --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - /> - <template #footer> - <!-- 按钮:关闭 --> - <XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" /> - </template> - </XModal> + </Table> + </content-wrap> + + <!-- 表单弹窗:添加/修改 --> + <!-- <mail-account-form ref="modalRef" @success="getList" />--> </template> <script setup lang="ts" name="MailLog"> -// 业务相关的 import -import { DICT_TYPE } from '@/utils/dict' import { allSchemas } from './log.data' import * as MailLogApi from '@/api/system/mail/log' -import * as MailAccountApi from '@/api/system/mail/account' +// import MailAccountForm from './form.vue' -const { t } = useI18n() // 国际化 - -// 列表相关的变量 -const queryParams = reactive({ - accountId: null +// tableObject:表格的属性对象,可获得分页大小、条数等属性 +// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作 +// 详细可见:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable +const { tableObject, tableMethods } = useTable({ + getListApi: MailLogApi.getMailLogPageApi // 分页接口 }) -const [registerTable] = useXTable({ - allSchemas: allSchemas, - topActionSlots: false, - params: queryParams, - getListApi: MailLogApi.getMailLogPageApi -}) -const accountOptions = ref<any[]>([]) // 账号下拉选项 +// 获得表格的各种操作 +const { getList, setSearchParams } = tableMethods -// 弹窗相关的变量 -const modelVisible = ref(false) // 是否显示弹出层 -const modelTitle = ref('edit') // 弹出层标题 -const modelLoading = ref(false) // 弹出层loading -const actionType = ref('') // 操作按钮的类型 -const actionLoading = ref(false) // 按钮 Loading -const detailData = ref() // 详情 Ref - -// 设置标题 -const setDialogTile = (type: string) => { - modelLoading.value = true - modelTitle.value = t('action.' + type) - actionType.value = type - modelVisible.value = true +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, id) } -// 详情操作 -const handleDetail = async (rowId: number) => { - setDialogTile('detail') - const res = await MailLogApi.getMailLogApi(rowId) - detailData.value = res - modelLoading.value = false -} - -// ========== 初始化 ========== +/** 初始化 **/ onMounted(() => { - MailAccountApi.getSimpleMailAccountList().then((data) => { - accountOptions.value = data - }) + getList() }) </script> diff --git a/src/views/system/mail/log/log.data.ts b/src/views/system/mail/log/log.data.ts index d389bce5..e3679535 100644 --- a/src/views/system/mail/log/log.data.ts +++ b/src/views/system/mail/log/log.data.ts @@ -1,121 +1,127 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' +import type { CrudSchema } from '@/hooks/web/useCrudSchemas' +import { dateFormatter } from '@/utils/formatTime' +import * as MailAccountApi from '@/api/system/mail/account' -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryTitle: '编号', - primaryType: 'id', - action: true, - actionWidth: '70', - columns: [ - { - title: '发送时间', - field: 'sendTime', - table: { - width: 180 - }, - formatter: 'formatDate', - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } +// 邮箱账号的列表 +const accounts = await MailAccountApi.getSimpleMailAccountList() + +// CrudSchema:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/hooks/useCrudSchemas.html +const crudSchemas = reactive<CrudSchema[]>([ + { + label: '编号', + field: 'id' + }, + { + label: '发送时间', + field: 'sendTime', + formatter: dateFormatter, + search: { + show: true, + component: 'DatePicker', + componentProps: { + valueFormat: 'YYYY-MM-DD HH:mm:ss', + type: 'daterange', + defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')] } - }, - { - title: '接收邮箱', - field: 'toMail', - isSearch: true, - table: { - width: 180, - slots: { - default: 'toMail_default' - } - } - }, - { - title: '用户编号', - field: 'userId', - isSearch: true, - isTable: false - }, - { - title: '用户类型', - field: 'userType', - dictType: DICT_TYPE.USER_TYPE, - dictClass: 'number', - isSearch: true, - isTable: false - }, - { - title: '邮件标题', - field: 'templateTitle' - }, - { - title: '邮件内容', - field: 'templateContent', - isTable: false - }, - { - title: '邮箱参数', - field: 'templateParams', - isTable: false - }, - { - title: '发送状态', - field: 'sendStatus', - dictType: DICT_TYPE.SYSTEM_MAIL_SEND_STATUS, - dictClass: 'string', - isSearch: true - }, - { - title: '邮箱账号', - field: 'accountId', - isSearch: true, - isTable: false, - search: { - slots: { - default: 'accountId_search' - } - } - }, - { - title: '发送邮箱地址', - field: 'fromMail', - table: { - title: '邮箱账号' - } - }, - { - title: '模板编号', - field: 'templateId', - isSearch: true - }, - { - title: '模板编码', - field: 'templateCode', - isTable: false - }, - { - title: '模版发送人名称', - field: 'templateNickname', - isTable: false - }, - { - title: '发送返回的消息编号', - field: 'sendMessageId', - isTable: false - }, - { - title: '发送异常', - field: 'sendException', - isTable: false - }, - { - title: '创建时间', - field: 'createTime', - isTable: false } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) + }, + { + label: '接收邮箱', + field: 'toMail' + }, + { + label: '用户编号', + field: 'userId', + isSearch: true, + isTable: false + }, + { + label: '用户类型', + field: 'userType', + dictType: DICT_TYPE.USER_TYPE, + dictClass: 'number', + isSearch: true, + isTable: false + }, + { + label: '邮件标题', + field: 'templateTitle' + }, + { + label: '邮件内容', + field: 'templateContent', + isTable: false + }, + { + label: '邮箱参数', + field: 'templateParams', + isTable: false + }, + { + label: '发送状态', + field: 'sendStatus', + dictType: DICT_TYPE.SYSTEM_MAIL_SEND_STATUS, + dictClass: 'string', + isSearch: true + }, + { + label: '邮箱账号', + field: 'accountId', + isTable: false, + search: { + show: true, + component: 'Select', + api: () => accounts, + componentProps: { + optionsAlias: { + labelField: 'mail', + valueField: 'id' + } + } + } + }, + { + label: '发送邮箱地址', + field: 'fromMail', + table: { + label: '邮箱账号' + } + }, + { + label: '模板编号', + field: 'templateId', + isSearch: true + }, + { + label: '模板编码', + field: 'templateCode', + isTable: false + }, + { + label: '模版发送人名称', + field: 'templateNickname', + isTable: false + }, + { + label: '发送返回的消息编号', + field: 'sendMessageId', + isTable: false + }, + { + label: '发送异常', + field: 'sendException', + isTable: false + }, + { + label: '创建时间', + field: 'createTime', + isTable: false, + formatter: dateFormatter + }, + { + label: '操作', + field: 'action', + isForm: false + } +]) +export const { allSchemas } = useCrudSchemas(crudSchemas) diff --git a/src/views/system/mail/template/template.data.ts b/src/views/system/mail/template/template.data.ts index bfdd5cad..ba76dc27 100644 --- a/src/views/system/mail/template/template.data.ts +++ b/src/views/system/mail/template/template.data.ts @@ -3,6 +3,7 @@ import { dateFormatter } from '@/utils/formatTime' import { TableColumn } from '@/types/table' import * as MailAccountApi from '@/api/system/mail/account' +// 邮箱账号的列表 const accounts = await MailAccountApi.getSimpleMailAccountList() // 表单校验 From 0f4c74fef0bf2959abd0963a95e5ad0f289aa524 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 18 Mar 2023 13:56:17 +0800 Subject: [PATCH 028/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E9=82=AE=E4=BB=B6=E6=97=A5=E5=BF=97=E7=9A=84=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/mail/log/index.ts | 14 ++---------- src/types/descriptions.d.ts | 4 ++-- src/views/system/mail/log/detail.vue | 31 +++++++++++++++++++++++++++ src/views/system/mail/log/index.vue | 12 +++++------ src/views/system/mail/log/log.data.ts | 10 +++++++-- 5 files changed, 49 insertions(+), 22 deletions(-) create mode 100644 src/views/system/mail/log/detail.vue diff --git a/src/api/system/mail/log/index.ts b/src/api/system/mail/log/index.ts index 9c6c60eb..13172a72 100644 --- a/src/api/system/mail/log/index.ts +++ b/src/api/system/mail/log/index.ts @@ -19,22 +19,12 @@ export interface MailLogVO { sendException: string } -export interface MailLogPageReqVO extends PageParam { - userId?: number - userType?: number - toMail?: string - accountId?: number - templateId?: number - sendStatus?: number - sendTime?: Date[] -} - // 查询邮件日志列表 -export const getMailLogPageApi = async (params: MailLogPageReqVO) => { +export const getMailLogPage = async (params: PageParam) => { return await request.get({ url: '/system/mail-log/page', params }) } // 查询邮件日志详情 -export const getMailLogApi = async (id: number) => { +export const getMailLog = async (id: number) => { return await request.get({ url: '/system/mail-log/get?id=' + id }) } diff --git a/src/types/descriptions.d.ts b/src/types/descriptions.d.ts index 48835e9c..35c0b81b 100644 --- a/src/types/descriptions.d.ts +++ b/src/types/descriptions.d.ts @@ -8,6 +8,6 @@ export interface DescriptionsSchema { labelAlign?: 'left' | 'center' | 'right' className?: string labelClassName?: string - dateFormat?: string - dictType?: string + dateFormat?: string // add by 星语:支持时间的格式化 + dictType?: string // add by 星语:支持 dict 字典数据 } diff --git a/src/views/system/mail/log/detail.vue b/src/views/system/mail/log/detail.vue new file mode 100644 index 00000000..1795f751 --- /dev/null +++ b/src/views/system/mail/log/detail.vue @@ -0,0 +1,31 @@ +<template> + <Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="500"> + <Descriptions :schema="allSchemas.detailSchema" :data="detailData"> + <!-- 展示 HTML 内容 --> + <template #templateContent="{ row }"> + <div v-html="row.templateContent"></div> + </template> + </Descriptions> + </Dialog> +</template> +<script setup lang="ts"> +import * as MailLogApi from '@/api/system/mail/log' +import { allSchemas } from './log.data' + +const modelVisible = ref(false) // 弹窗的是否展示 +const detailLoading = ref(false) // 表单的加载中 +const detailData = ref() // 详情数据 + +/** 打开弹窗 */ +const openModal = async (id: number) => { + modelVisible.value = true + // 设置数据 + detailLoading.value = true + try { + detailData.value = await MailLogApi.getMailLog(id) + } finally { + detailLoading.value = false + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +</script> diff --git a/src/views/system/mail/log/index.vue b/src/views/system/mail/log/index.vue index a8b0c6c1..f8d98b83 100644 --- a/src/views/system/mail/log/index.vue +++ b/src/views/system/mail/log/index.vue @@ -20,7 +20,7 @@ <el-button link type="primary" - @click="openModal('update', row.id)" + @click="openModal(row.id)" v-hasPermi="['system:mail-log:query']" > 详情 @@ -30,26 +30,26 @@ </content-wrap> <!-- 表单弹窗:添加/修改 --> - <!-- <mail-account-form ref="modalRef" @success="getList" />--> + <mail-log-detail ref="modalRef" @success="getList" /> </template> <script setup lang="ts" name="MailLog"> import { allSchemas } from './log.data' import * as MailLogApi from '@/api/system/mail/log' -// import MailAccountForm from './form.vue' +import MailLogDetail from './detail.vue' // tableObject:表格的属性对象,可获得分页大小、条数等属性 // tableMethods:表格的操作对象,可进行获得分页、删除记录等操作 // 详细可见:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable const { tableObject, tableMethods } = useTable({ - getListApi: MailLogApi.getMailLogPageApi // 分页接口 + getListApi: MailLogApi.getMailLogPage // 分页接口 }) // 获得表格的各种操作 const { getList, setSearchParams } = tableMethods /** 添加/修改操作 */ const modalRef = ref() -const openModal = (type: string, id?: number) => { - modalRef.value.openModal(type, id) +const openModal = (id: number) => { + modalRef.value.openModal(id) } /** 初始化 **/ diff --git a/src/views/system/mail/log/log.data.ts b/src/views/system/mail/log/log.data.ts index e3679535..8ea96926 100644 --- a/src/views/system/mail/log/log.data.ts +++ b/src/views/system/mail/log/log.data.ts @@ -23,6 +23,9 @@ const crudSchemas = reactive<CrudSchema[]>([ type: 'daterange', defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')] } + }, + detail: { + dateFormat: 'YYYY-MM-DD HH:mm:ss' } }, { @@ -116,12 +119,15 @@ const crudSchemas = reactive<CrudSchema[]>([ label: '创建时间', field: 'createTime', isTable: false, - formatter: dateFormatter + formatter: dateFormatter, + detail: { + dateFormat: 'YYYY-MM-DD HH:mm:ss' + } }, { label: '操作', field: 'action', - isForm: false + isDetail: false } ]) export const { allSchemas } = useCrudSchemas(crudSchemas) From c9a30e6fbf36ef8f3d8fa3be1ab3306163f1cdc5 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 18 Mar 2023 19:00:58 +0800 Subject: [PATCH 029/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E6=97=A5=E5=BF=97=E7=9A=84=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/operatelog/index.ts | 12 +- src/views/infra/config/index.vue | 2 +- src/views/system/notice/index.vue | 8 +- src/views/system/operatelog/index.vue | 233 +++++++++++++++++++------- 4 files changed, 184 insertions(+), 71 deletions(-) diff --git a/src/api/system/operatelog/index.ts b/src/api/system/operatelog/index.ts index 5fd31dba..848a5333 100644 --- a/src/api/system/operatelog/index.ts +++ b/src/api/system/operatelog/index.ts @@ -23,19 +23,11 @@ export type OperateLogVO = { resultData: string } -export interface OperateLogPageReqVO extends PageParam { - module?: string - userNickname?: string - type?: number - success?: boolean - startTime?: Date[] -} - // 查询操作日志列表 -export const getOperateLogPageApi = (params: OperateLogPageReqVO) => { +export const getOperateLogPage = (params: PageParam) => { return request.get({ url: '/system/operate-log/page', params }) } // 导出操作日志 -export const exportOperateLogApi = (params: OperateLogPageReqVO) => { +export const exportOperateLog = (params) => { return request.download({ url: '/system/operate-log/export', params }) } diff --git a/src/views/infra/config/index.vue b/src/views/infra/config/index.vue index 0136a9d5..11fc8fe0 100644 --- a/src/views/infra/config/index.vue +++ b/src/views/infra/config/index.vue @@ -59,7 +59,7 @@ <!-- 列表 --> <content-wrap> - <el-table v-loading="loading" :data="list" align="center"> + <el-table v-loading="loading" :data="list"> <el-table-column label="参数主键" align="center" prop="id" /> <el-table-column label="参数分类" align="center" prop="category" /> <el-table-column label="参数名称" align="center" prop="name" :show-overflow-tooltip="true" /> diff --git a/src/views/system/notice/index.vue b/src/views/system/notice/index.vue index 8add0eab..e36f3aac 100644 --- a/src/views/system/notice/index.vue +++ b/src/views/system/notice/index.vue @@ -1,7 +1,7 @@ <template> <content-wrap> <!-- 搜索工作栏 --> - <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px"> + <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true"> <el-form-item label="公告标题" prop="title"> <el-input v-model="queryParams.title" @@ -32,9 +32,11 @@ </el-button> </el-form-item> </el-form> + </content-wrap> - <!-- 列表 --> - <el-table v-loading="loading" :data="list" align="center"> + <!-- 列表 --> + <content-wrap> + <el-table v-loading="loading" :data="list"> <el-table-column label="公告编号" align="center" prop="id" /> <el-table-column label="公告标题" align="center" prop="title" /> <el-table-column label="公告类型" align="center" prop="type"> diff --git a/src/views/system/operatelog/index.vue b/src/views/system/operatelog/index.vue index 7f1e9bb4..f4de18a2 100644 --- a/src/views/system/operatelog/index.vue +++ b/src/views/system/operatelog/index.vue @@ -1,67 +1,186 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <!-- 操作:新增 --> - <XButton - type="warning" - preIcon="ep:download" - :title="t('action.export')" - v-hasPermi="['system:operate-log:export']" - @click="exportList('操作日志.xls')" + <content-wrap> + <!-- 搜索工作栏 --> + <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true"> + <el-form-item label="系统模块" prop="module"> + <el-input + v-model="queryParams.module" + placeholder="请输入系统模块" + clearable + @keyup.enter="handleQuery" /> - </template> - <template #duration="{ row }"> - <span>{{ row.duration + 'ms' }}</span> - </template> - <template #resultCode="{ row }"> - <span>{{ row.resultCode === 0 ? '成功' : '失败' }}</span> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:详情 --> - <XTextButton preIcon="ep:view" :title="t('action.detail')" @click="handleDetail(row)" /> - </template> - </XTable> - </ContentWrap> - <!-- 弹窗 --> - <XModal id="postModel" v-model="dialogVisible" :title="t('action.detail')"> - <!-- 对话框(详情) --> - <Descriptions :schema="allSchemas.detailSchema" :data="detailData"> - <template #resultCode="{ row }"> - <span>{{ row.resultCode === 0 ? '成功' : '失败' }}</span> - </template> - <template #duration="{ row }"> - <span>{{ row.duration + 'ms' }}</span> - </template> - </Descriptions> - <template #footer> - <!-- 按钮:关闭 --> - <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> + </el-form-item> + <el-form-item label="操作人员" prop="userNickname"> + <el-input + v-model="queryParams.userNickname" + placeholder="请输入操作人员" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="类型" prop="type"> + <el-select v-model="queryParams.type" placeholder="操作类型" clearable> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.INFRA_CONFIG_TYPE)" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + <el-form-item label="状态" prop="success"> + <el-select v-model="queryParams.success" placeholder="操作状态" clearable> + <el-option :key="true" label="成功" :value="true" /> + <el-option :key="false" label="失败" :value="false" /> + </el-select> + </el-form-item> + <el-form-item label="操作时间" prop="startTime"> + <el-date-picker + v-model="queryParams.startTime" + value-format="YYYY-MM-DD HH:mm:ss" + type="daterange" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['infra:config:export']" + > + <Icon icon="ep:download" class="mr-5px" /> 导出 + </el-button> + </el-form-item> + </el-form> + </content-wrap> + + <!-- 列表 --> + <content-wrap> + <el-table v-loading="loading" :data="list"> + <el-table-column label="日志编号" align="center" prop="id" /> + <el-table-column label="操作模块" align="center" prop="module" width="180" /> + <el-table-column label="操作名" align="center" prop="name" width="180" /> + <el-table-column label="操作类型" align="center" prop="type"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.SYSTEM_OPERATE_TYPE" :value="scope.row.type" /> + </template> + </el-table-column> + <el-table-column label="操作人" align="center" prop="userNickname" /> + <el-table-column label="操作结果" align="center" prop="status"> + <template #default="scope"> + <span>{{ scope.row.resultCode === 0 ? '成功' : '失败' }}</span> + </template> + </el-table-column> + <el-table-column + label="操作时间" + align="center" + prop="startTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column label="执行时长" align="center" prop="startTime"> + <template #default="scope"> + <span>{{ scope.row.duration }} ms</span> + </template> + </el-table-column> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openModal('update', scope.row.id)" + v-hasPermi="['infra:config:query']" + > + 详情 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> </template> <script setup lang="ts" name="OperateLog"> -// 业务相关的 import +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' +import download from '@/utils/download' import * as OperateLogApi from '@/api/system/operatelog' -import { allSchemas } from './operatelog.data' +// import ConfigForm from './form.vue' +const message = useMessage() // 消息弹窗 -const { t } = useI18n() // 国际化 -// 列表相关的变量 -const [registerTable, { exportList }] = useXTable({ - allSchemas: allSchemas, - getListApi: OperateLogApi.getOperateLogPageApi, - exportListApi: OperateLogApi.exportOperateLogApi +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + module: undefined, + userNickname: undefined, + type: undefined, + success: undefined, + startTime: [] }) +const queryFormRef = ref() // 搜索的表单 +const exportLoading = ref(false) // 导出的加载中 -// 弹窗相关的变量 -const dialogVisible = ref(false) // 是否显示弹出层 -const actionLoading = ref(false) // 按钮 Loading -const detailData = ref() // 详情 Ref -// 详情 -const handleDetail = (row: OperateLogApi.OperateLogVO) => { - // 设置数据 - detailData.value = row - dialogVisible.value = true +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await OperateLogApi.getOperateLogPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } } + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +// const modalRef = ref() +// const openModal = (type: string, id?: number) => { +// modalRef.value.openModal(type, id) +// } + +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await OperateLogApi.exportOperateLog(queryParams) + download.excel(data, '操作日志.xls') + } catch { + } finally { + exportLoading.value = false + } +} + +/** 初始化 **/ +onMounted(() => { + getList() +}) </script> From e669dffddc29e335300dd0953cc2580b0e0ec796 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 18 Mar 2023 20:09:30 +0800 Subject: [PATCH 030/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E6=97=A5=E5=BF=97=E7=9A=84=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/formatTime.ts | 52 +-------- src/views/system/mail/log/index.vue | 6 +- src/views/system/operatelog/detail.vue | 80 +++++++++++++ src/views/system/operatelog/index.vue | 19 ++-- .../system/operatelog/operatelog.data.ts | 106 ------------------ 5 files changed, 95 insertions(+), 168 deletions(-) create mode 100644 src/views/system/operatelog/detail.vue delete mode 100644 src/views/system/operatelog/operatelog.data.ts diff --git a/src/utils/formatTime.ts b/src/utils/formatTime.ts index 39671279..2582beee 100644 --- a/src/utils/formatTime.ts +++ b/src/utils/formatTime.ts @@ -12,57 +12,7 @@ import dayjs from 'dayjs' * @returns 返回拼接后的时间字符串 */ export function formatDate(date: Date, format: string): string { - const we = date.getDay() // 星期 - const z = getWeek(date) // 周 - const qut = Math.floor((date.getMonth() + 3) / 3).toString() // 季度 - const opt: { [key: string]: string } = { - 'Y+': date.getFullYear().toString(), // 年 - 'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始,要+1) - 'd+': date.getDate().toString(), // 日 - 'H+': date.getHours().toString(), // 时 - 'M+': date.getMinutes().toString(), // 分 - 'S+': date.getSeconds().toString(), // 秒 - 'q+': qut // 季度 - } - // 中文数字 (星期) - const week: { [key: string]: string } = { - '0': '日', - '1': '一', - '2': '二', - '3': '三', - '4': '四', - '5': '五', - '6': '六' - } - // 中文数字(季度) - const quarter: { [key: string]: string } = { - '1': '一', - '2': '二', - '3': '三', - '4': '四' - } - if (/(W+)/.test(format)) - format = format.replace( - RegExp.$1, - RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we] - ) - if (/(Q+)/.test(format)) - format = format.replace( - RegExp.$1, - RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut] - ) - if (/(Z+)/.test(format)) - format = format.replace(RegExp.$1, RegExp.$1.length == 3 ? '第' + z + '周' : z + '') - for (const k in opt) { - const r = new RegExp('(' + k + ')').exec(format) - // 若输入的长度不为1,则前面补零 - if (r) - format = format.replace( - r[1], - RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0') - ) - } - return format + return dayjs(date).format(format) } /** diff --git a/src/views/system/mail/log/index.vue b/src/views/system/mail/log/index.vue index f8d98b83..5373b71d 100644 --- a/src/views/system/mail/log/index.vue +++ b/src/views/system/mail/log/index.vue @@ -29,8 +29,8 @@ </Table> </content-wrap> - <!-- 表单弹窗:添加/修改 --> - <mail-log-detail ref="modalRef" @success="getList" /> + <!-- 表单弹窗:详情 --> + <mail-log-detail ref="modalRef" /> </template> <script setup lang="ts" name="MailLog"> import { allSchemas } from './log.data' @@ -46,7 +46,7 @@ const { tableObject, tableMethods } = useTable({ // 获得表格的各种操作 const { getList, setSearchParams } = tableMethods -/** 添加/修改操作 */ +/** 详情操作 */ const modalRef = ref() const openModal = (id: number) => { modalRef.value.openModal(id) diff --git a/src/views/system/operatelog/detail.vue b/src/views/system/operatelog/detail.vue new file mode 100644 index 00000000..6c856e95 --- /dev/null +++ b/src/views/system/operatelog/detail.vue @@ -0,0 +1,80 @@ +<template> + <Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="500" width="800"> + <el-descriptions border :column="1"> + <el-descriptions-item label="日志主键" min-width="120"> + {{ detailData.id }} + </el-descriptions-item> + <el-descriptions-item label="链路追踪"> + {{ detailData.traceId }} + </el-descriptions-item> + <el-descriptions-item label="操作人编号"> + {{ detailData.userId }} + </el-descriptions-item> + <el-descriptions-item label="操作人名字"> + {{ detailData.userNickname }} + </el-descriptions-item> + <el-descriptions-item label="操作人 IP"> + {{ detailData.userIp }} + </el-descriptions-item> + <el-descriptions-item label="操作人 UA"> + {{ detailData.userAgent }} + </el-descriptions-item> + <el-descriptions-item label="操作模块"> + {{ detailData.module }} + </el-descriptions-item> + <el-descriptions-item label="操作名"> + {{ detailData.name }} + </el-descriptions-item> + <el-descriptions-item label="操作内容" v-if="detailData.content"> + {{ detailData.content }} + </el-descriptions-item> + <el-descriptions-item label="操作拓展参数" v-if="detailData.exts"> + {{ detailData.exts }} + </el-descriptions-item> + <el-descriptions-item label="请求 URL"> + {{ detailData.requestMethod }} {{ detailData.requestUrl }} + </el-descriptions-item> + <el-descriptions-item label="Java 方法名"> + {{ detailData.javaMethod }} + </el-descriptions-item> + <el-descriptions-item label="Java 方法参数"> + {{ detailData.javaMethodArgs }} + </el-descriptions-item> + <el-descriptions-item label="操作时间"> + {{ formatDate(detailData.startTime, 'YYYY-MM-DD HH:mm:ss') }} + </el-descriptions-item> + <el-descriptions-item label="执行时长">{{ detailData.duration }} ms</el-descriptions-item> + <el-descriptions-item label="操作结果"> + <div v-if="detailData.resultCode === 0">正常</div> + <div v-else>失败({{ detailData.resultCode }})</div> + </el-descriptions-item> + <el-descriptions-item label="操作结果" v-if="detailData.resultCode === 0"> + {{ detailData.resultData }} + </el-descriptions-item> + <el-descriptions-item label="失败提示" v-if="detailData.resultCode > 0"> + {{ detailData.resultMsg }} + </el-descriptions-item> + </el-descriptions> + </Dialog> +</template> +<script setup lang="ts"> +import { formatDate } from '@/utils/formatTime' +import * as OperateLogApi from '@/api/system/operatelog' + +const modelVisible = ref(false) // 弹窗的是否展示 +const detailLoading = ref(false) // 表单的加载中 +const detailData = ref() // 详情数据 + +/** 打开弹窗 */ +const openModal = async (data: OperateLogApi.OperateLogVO) => { + modelVisible.value = true + // 设置数据 + detailLoading.value = true + try { + detailData.value = data + } finally { + detailLoading.value = false + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +</script> diff --git a/src/views/system/operatelog/index.vue b/src/views/system/operatelog/index.vue index f4de18a2..f9667912 100644 --- a/src/views/system/operatelog/index.vue +++ b/src/views/system/operatelog/index.vue @@ -21,7 +21,7 @@ <el-form-item label="类型" prop="type"> <el-select v-model="queryParams.type" placeholder="操作类型" clearable> <el-option - v-for="dict in getDictOptions(DICT_TYPE.INFRA_CONFIG_TYPE)" + v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_OPERATE_TYPE)" :key="parseInt(dict.value)" :label="dict.label" :value="parseInt(dict.value)" @@ -94,7 +94,7 @@ <el-button link type="primary" - @click="openModal('update', scope.row.id)" + @click="openModal(scope.row)" v-hasPermi="['infra:config:query']" > 详情 @@ -110,13 +110,16 @@ @pagination="getList" /> </content-wrap> + + <!-- 表单弹窗:详情 --> + <operate-log-detail ref="modalRef" /> </template> <script setup lang="ts" name="OperateLog"> import { DICT_TYPE, getDictOptions } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' import * as OperateLogApi from '@/api/system/operatelog' -// import ConfigForm from './form.vue' +import OperateLogDetail from './detail.vue' const message = useMessage() // 消息弹窗 const loading = ref(true) // 列表的加载中 @@ -158,11 +161,11 @@ const resetQuery = () => { handleQuery() } -/** 添加/修改操作 */ -// const modalRef = ref() -// const openModal = (type: string, id?: number) => { -// modalRef.value.openModal(type, id) -// } +/** 详情操作 */ +const modalRef = ref() +const openModal = (data: OperateLogApi.OperateLogVO) => { + modalRef.value.openModal(data) +} /** 导出按钮操作 */ const handleExport = async () => { diff --git a/src/views/system/operatelog/operatelog.data.ts b/src/views/system/operatelog/operatelog.data.ts deleted file mode 100644 index f8b4ef90..00000000 --- a/src/views/system/operatelog/operatelog.data.ts +++ /dev/null @@ -1,106 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' - -const { t } = useI18n() // 国际化 - -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'id', - primaryTitle: '日志编号', - action: true, - actionWidth: '80px', - columns: [ - { - title: '操作模块', - field: 'module', - isSearch: true - }, - { - title: '操作名', - field: 'name' - }, - { - title: '操作类型', - field: 'type', - dictType: DICT_TYPE.SYSTEM_OPERATE_TYPE, - dictClass: 'number', - isSearch: true - }, - { - title: '请求方法名', - field: 'requestMethod', - isTable: false - }, - { - title: '请求地址', - field: 'requestUrl', - isTable: false - }, - { - title: '操作人员', - field: 'userNickname', - isSearch: true - }, - { - title: '操作明细', - field: 'content', - isTable: false - }, - { - title: '用户 IP', - field: 'userIp', - isTable: false - }, - { - title: 'userAgent', - field: 'userAgent' - }, - { - title: '操作结果', - field: 'resultCode', - table: { - slots: { - default: 'resultCode' - } - } - }, - { - title: '操作结果', - field: 'success', - isTable: false, - isDetail: false, - search: { - show: true, - itemRender: { - name: '$select', - props: { placeholder: t('common.selectText') }, - options: [ - { label: '成功', value: 'true' }, - { label: '失败', value: 'false' } - ] - } - } - }, - { - title: '操作日期', - field: 'startTime', - formatter: 'formatDate', - isForm: false, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - }, - { - title: '执行时长', - field: 'duration', - table: { - slots: { - default: 'duration' - } - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From c56a887767fd1a132b01d6ca15d5a00dbd6f0777 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 18 Mar 2023 21:45:58 +0800 Subject: [PATCH 031/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E5=9C=B0=E5=8C=BA=E6=8E=A5=E5=85=A5=20TableV2=20=E9=AB=98?= =?UTF-8?q?=E6=80=A7=E8=83=BD=E8=A1=A8=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/area/index.ts | 11 ++++++++ src/views/system/area/index.vue | 46 +++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/api/system/area/index.ts create mode 100644 src/views/system/area/index.vue diff --git a/src/api/system/area/index.ts b/src/api/system/area/index.ts new file mode 100644 index 00000000..f77ce0d1 --- /dev/null +++ b/src/api/system/area/index.ts @@ -0,0 +1,11 @@ +import request from '@/config/axios' + +// 获得地区树 +export const getAreaTree = async () => { + return await request.get({ url: '/system/area/tree' }) +} + +// 获得 IP 对应的地区名 +export const getDeptApi = async (ip: string) => { + return await request.get({ url: '/system/area/get-by-ip?ip=' + ip }) +} diff --git a/src/views/system/area/index.vue b/src/views/system/area/index.vue new file mode 100644 index 00000000..dcb659c3 --- /dev/null +++ b/src/views/system/area/index.vue @@ -0,0 +1,46 @@ +<template> + <ContentWrap> + <div style="width: 100%; height: 500px"> + <el-auto-resizer> + <template #default="{ height, width }"> + <el-table-v2 + :columns="columns" + :data="tableData" + :width="width" + :height="height" + fixed + expand-column-key="id" + /> + </template> + </el-auto-resizer> + </div> + </ContentWrap> +</template> +<script setup lang="tsx"> +import { Column, ElTableV2 } from 'element-plus' +import * as AreaApi from '@/api/system/area' + +const columns: Column[] = [ + { + key: 'id', + dataKey: 'id', // 需要渲染当前列的数据字段,如{id:9527,name:'Mike'},则填id + title: '编号', // 显示在单元格表头的文本 + width: 200, // 当前列的宽度,必须设置 + fixed: true // 是否固定列 + }, + { + key: 'name', + dataKey: 'name', + title: '地名', + width: 200 + } +] + +const tableData = ref([]) + +const getList = async () => { + tableData.value = await AreaApi.getAreaTree() +} + +getList() +</script> From 2cf384d7abfeb0d30adf6b0b7a5a02bd9baace21 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 18 Mar 2023 22:29:08 +0800 Subject: [PATCH 032/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E5=9C=B0=E5=8C=BA=E6=8E=A5=E5=85=A5=20TableV2=20=E9=AB=98?= =?UTF-8?q?=E6=80=A7=E8=83=BD=E8=A1=A8=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/area/index.ts | 2 +- src/types/auto-components.d.ts | 1 + src/views/system/area/form.vue | 71 +++++++++++++++++++++++++++++++++ src/views/system/area/index.vue | 57 ++++++++++++++++++-------- 4 files changed, 114 insertions(+), 17 deletions(-) create mode 100644 src/views/system/area/form.vue diff --git a/src/api/system/area/index.ts b/src/api/system/area/index.ts index f77ce0d1..e91a4997 100644 --- a/src/api/system/area/index.ts +++ b/src/api/system/area/index.ts @@ -6,6 +6,6 @@ export const getAreaTree = async () => { } // 获得 IP 对应的地区名 -export const getDeptApi = async (ip: string) => { +export const getAreaByIp = async (ip: string) => { return await request.get({ url: '/system/area/get-by-ip?ip=' + ip }) } diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index 7b10d679..be71517c 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -73,6 +73,7 @@ declare module '@vue/runtime-core' { ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElTable: typeof import('element-plus/es')['ElTable'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] + ElTableV2: typeof import('element-plus/es')['ElTableV2'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] ElTag: typeof import('element-plus/es')['ElTag'] diff --git a/src/views/system/area/form.vue b/src/views/system/area/form.vue new file mode 100644 index 00000000..4e61fe32 --- /dev/null +++ b/src/views/system/area/form.vue @@ -0,0 +1,71 @@ +<template> + <Dialog title="IP 查询" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="IP" prop="ip"> + <el-input v-model="formData.ip" placeholder="请输入 IP 地址" /> + </el-form-item> + <el-form-item label="地址" prop="result"> + <el-input v-model="formData.result" readonly placeholder="展示查询 IP 结果" /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import * as AreaApi from '@/api/system/area' +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const formLoading = ref(false) // 表单的加载中:提交的按钮禁用 +const formData = ref({ + ip: '', + result: undefined +}) +const formRules = reactive({ + ip: [{ required: true, message: 'IP 地址不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async () => { + modelVisible.value = true + resetForm() +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + formData.value.result = await AreaApi.getAreaByIp(formData.value.ip!.trim()) + message.success('查询成功') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + ip: '', + result: undefined + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/system/area/index.vue b/src/views/system/area/index.vue index dcb659c3..f9d4c57d 100644 --- a/src/views/system/area/index.vue +++ b/src/views/system/area/index.vue @@ -1,46 +1,71 @@ <template> - <ContentWrap> - <div style="width: 100%; height: 500px"> + <!-- 操作栏 --> + <content-wrap> + <el-button type="primary" @click="openModal()"> + <Icon icon="ep:plus" class="mr-5px" /> IP 查询 + </el-button> + </content-wrap> + + <!-- 列表 --> + <content-wrap> + <div style="width: 100%; height: 700px"> + <!-- AutoResizer 自动调节大小 --> <el-auto-resizer> <template #default="{ height, width }"> + <!-- Virtualized Table 虚拟化表格:高性能,解决表格在大数据量下的卡顿问题 --> <el-table-v2 :columns="columns" - :data="tableData" + :data="list" :width="width" :height="height" - fixed expand-column-key="id" /> </template> </el-auto-resizer> </div> - </ContentWrap> + </content-wrap> + + <!-- 表单弹窗:添加/修改 --> + <area-form ref="modalRef" /> </template> -<script setup lang="tsx"> -import { Column, ElTableV2 } from 'element-plus' +<script setup lang="tsx" name="Area"> +import type { Column } from 'element-plus' +import AreaForm from './form.vue' import * as AreaApi from '@/api/system/area' +// 表格的 column 字段 const columns: Column[] = [ { - key: 'id', - dataKey: 'id', // 需要渲染当前列的数据字段,如{id:9527,name:'Mike'},则填id + dataKey: 'id', // 需要渲染当前列的数据字段。例如说:{id:9527, name:'Mike'},则填 id title: '编号', // 显示在单元格表头的文本 - width: 200, // 当前列的宽度,必须设置 - fixed: true // 是否固定列 + width: 400, // 当前列的宽度,必须设置 + fixed: true, // 是否固定列 + key: 'id' // 树形展开对应的 key }, { - key: 'name', dataKey: 'name', title: '地名', width: 200 } ] +// 表格的数据 +const list = ref([]) -const tableData = ref([]) - +/** + * 获得数据列表 + */ const getList = async () => { - tableData.value = await AreaApi.getAreaTree() + list.value = await AreaApi.getAreaTree() } -getList() +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = () => { + modalRef.value.openModal() +} + +/** 初始化 **/ +onMounted(() => { + getList() +}) </script> From 15ac1d9d8b08e17f6e282e8d493923f636004491 Mon Sep 17 00:00:00 2001 From: admin <> Date: Sun, 19 Mar 2023 13:04:59 +0800 Subject: [PATCH 033/184] =?UTF-8?q?=E9=87=87=E7=94=A8ep=E9=87=8D=E5=86=99d?= =?UTF-8?q?ept=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/auto-components.d.ts | 14 -- src/views/system/dept/dept.data.ts | 84 -------- src/views/system/dept/form.vue | 195 ++++++++++++++++++ src/views/system/dept/index.vue | 308 +++++++++++++++-------------- 4 files changed, 352 insertions(+), 249 deletions(-) delete mode 100644 src/views/system/dept/dept.data.ts create mode 100644 src/views/system/dept/form.vue diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index be71517c..8452fba6 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -23,8 +23,6 @@ declare module '@vue/runtime-core' { DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default'] Echart: typeof import('./../components/Echart/src/Echart.vue')['default'] Editor: typeof import('./../components/Editor/src/Editor.vue')['default'] - ElAutoResizer: typeof import('element-plus/es')['ElAutoResizer'] - ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElBadge: typeof import('element-plus/es')['ElBadge'] ElButton: typeof import('element-plus/es')['ElButton'] ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] @@ -35,7 +33,6 @@ declare module '@vue/runtime-core' { ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] ElCollapseTransition: typeof import('element-plus/es')['ElCollapseTransition'] ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] - ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] ElDialog: typeof import('element-plus/es')['ElDialog'] @@ -54,7 +51,6 @@ declare module '@vue/runtime-core' { ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] - ElImage: typeof import('element-plus/es')['ElImage'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] @@ -62,26 +58,16 @@ declare module '@vue/runtime-core' { ElOption: typeof import('element-plus/es')['ElOption'] ElPagination: typeof import('element-plus/es')['ElPagination'] ElPopover: typeof import('element-plus/es')['ElPopover'] - ElRadio: typeof import('element-plus/es')['ElRadio'] - ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] - ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRow: typeof import('element-plus/es')['ElRow'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElSelect: typeof import('element-plus/es')['ElSelect'] ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] - ElSpace: typeof import('element-plus/es')['ElSpace'] ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElTable: typeof import('element-plus/es')['ElTable'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] - ElTableV2: typeof import('element-plus/es')['ElTableV2'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] - ElTag: typeof import('element-plus/es')['ElTag'] - ElTimeline: typeof import('element-plus/es')['ElTimeline'] - ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] - ElTransfer: typeof import('element-plus/es')['ElTransfer'] - ElTree: typeof import('element-plus/es')['ElTree'] ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElUpload: typeof import('element-plus/es')['ElUpload'] Error: typeof import('./../components/Error/src/Error.vue')['default'] diff --git a/src/views/system/dept/dept.data.ts b/src/views/system/dept/dept.data.ts deleted file mode 100644 index c6945841..00000000 --- a/src/views/system/dept/dept.data.ts +++ /dev/null @@ -1,84 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' - -const { t } = useI18n() // 国际化 - -// 表单校验 -export const rules = reactive({ - name: [required], - sort: [required], - // email: [required], - email: [ - { required: true, message: t('profile.rules.mail'), trigger: 'blur' }, - { - type: 'email', - message: t('profile.rules.truemail'), - trigger: ['blur', 'change'] - } - ], - phone: [ - { - len: 11, - trigger: 'blur', - message: '请输入正确的手机号码' - } - ] -}) - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: null, - action: true, - columns: [ - { - title: '上级部门', - field: 'parentId', - isTable: false - }, - { - title: '部门名称', - field: 'name', - isSearch: true, - table: { - treeNode: true, - align: 'left' - } - }, - { - title: '负责人', - field: 'leaderUserId', - table: { - slots: { - default: 'leaderUserId_default' - } - } - }, - { - title: '联系电话', - field: 'phone' - }, - { - title: '邮箱', - field: 'email', - isTable: false - }, - { - title: '显示排序', - field: 'sort' - }, - { - title: t('common.status'), - field: 'status', - dictType: DICT_TYPE.COMMON_STATUS, - dictClass: 'number', - isSearch: true - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) diff --git a/src/views/system/dept/form.vue b/src/views/system/dept/form.vue new file mode 100644 index 00000000..fbbcab59 --- /dev/null +++ b/src/views/system/dept/form.vue @@ -0,0 +1,195 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible" width="800"> + <el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px"> + <el-row> + <el-col :span="24" v-if="formData.parentId !== 0"> + <el-form-item label="上级部门" prop="parentId"> + <el-tree-select + v-model="formData.parentId" + :data="deptOptions" + :props="{ value: 'id', label: 'name', children: 'children' }" + value-key="deptId" + placeholder="选择上级部门" + check-strictly + /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="部门名称" prop="name"> + <el-input v-model="formData.name" placeholder="请输入部门名称" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="显示排序" prop="sort"> + <el-input-number v-model="formData.sort" controls-position="right" :min="0" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="负责人" prop="leaderUserId"> + <el-select + v-model="formData.leaderUserId" + placeholder="请输入负责人" + clearable + style="width: 100%" + > + <el-option + v-for="item in props.userOption" + :key="item.id" + :label="item.nickname" + :value="item.id" + /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="联系电话" prop="phone"> + <el-input v-model="formData.phone" placeholder="请输入联系电话" maxlength="11" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="邮箱" prop="email"> + <el-input v-model="formData.email" placeholder="请输入邮箱" maxlength="50" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="状态" prop="status"> + <el-select v-model="formData.status" placeholder="请选择状态" clearable> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + </el-form> + + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> + +<script setup lang="ts"> +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import * as DeptApi from '@/api/system/dept' +import { UserVO } from '@/api/system/user' +import { handleTree } from '@/utils/tree' +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formRef = ref() // 表单 Ref +const deptOptions = ref() // 树形结构 + +const formData = ref({ + id: undefined, + title: '', + parentId: undefined, + name: undefined, + sort: undefined, + leaderUserId: undefined, + phone: undefined, + email: undefined, + status: undefined +}) +const props = defineProps({ + userOption: { + type: Array, + default: () => [] as UserVO[] + } +}) + +const formRules = reactive({ + parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }], + name: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }], + order: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }], + email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }], + phone: [ + { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' } + ] +}) + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + console.log(id) + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await DeptApi.getDeptApi(id) + } finally { + formLoading.value = false + } + } +} + +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as DeptApi.DeptVO + if (formType.value === 'create') { + await DeptApi.createDeptApi(data) + message.success(t('common.createSuccess')) + } else { + await DeptApi.updateDeptApi(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + title: '', + parentId: undefined, + name: undefined, + sort: undefined, + leaderUserId: undefined, + phone: undefined, + email: undefined, + status: undefined + } + formRef.value?.resetFields() +} + +// 获取下拉框[上级]的数据 +const getTree = async () => { + deptOptions.value = [] + const res = await DeptApi.listSimpleDeptApi() + let dept: Tree = { id: 0, name: '顶级部门', children: [] } + dept.children = handleTree(res) + deptOptions.value.push(dept) +} + +// ========== 初始化 ========== +onMounted(async () => { + await getTree() +}) +</script> diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue index 3b182e2a..bfd12256 100644 --- a/src/views/system/dept/index.vue +++ b/src/views/system/dept/index.vue @@ -1,174 +1,180 @@ <template> <ContentWrap> - <!-- 列表 --> - <XTable ref="xGrid" @register="registerTable" show-overflow> - <template #toolbar_buttons> - <!-- 操作:新增 --> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['system:dept:create']" - @click="handleCreate()" - /> - <XButton title="展开所有" @click="xGrid?.Ref.setAllTreeExpand(true)" /> - <XButton title="关闭所有" @click="xGrid?.Ref.clearTreeExpand()" /> - </template> - <template #leaderUserId_default="{ row }"> - <span>{{ userNicknameFormat(row) }}</span> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:修改 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['system:dept:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['system:dept:delete']" - @click="deleteData(row.id)" - /> - </template> - </XTable> - </ContentWrap> - <!-- 添加或修改菜单对话框 --> - <XModal id="deptModel" v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(添加 / 修改) --> - <Form ref="formRef" :schema="allSchemas.formSchema" :rules="rules"> - <template #parentId="form"> - <el-tree-select - node-key="id" - v-model="form['parentId']" - :props="defaultProps" - :data="deptOptions" - :default-expanded-keys="[100]" - check-strictly - /> - </template> - <template #leaderUserId="form"> - <el-select v-model="form['leaderUserId']"> + <!-- 搜索工作栏 --> + <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px"> + <el-form-item label="部门名称" prop="title"> + <el-input v-model="queryParams.name" placeholder="请输入部门名称" clearable /> + </el-form-item> + <el-form-item label="部门状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="请选择" clearable> <el-option - v-for="item in userOption" - :key="item.id" - :label="item.nickname" - :value="item.id" + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" /> </el-select> - </template> - </Form> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :loading="actionLoading" - @click="submitForm()" - :title="t('action.save')" + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + </el-form-item> + </el-form> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + @click="openModal('create')" + v-hasPermi="['system:dept:create']" + ><Icon icon="ep:plus" class="mr-5px" /> 新增</el-button + > + </el-col> + <el-col :span="1.5"> + <el-button type="danger" plain @click="toggleExpandAll" + ><Icon icon="ep:sort" class="mr-5px" /> 展开/折叠</el-button + > + </el-col> + </el-row> + <!-- 列表 --> + <el-table + v-if="refreshTable" + v-loading="loading" + :data="deptDatas" + row-key="id" + :default-expand-all="isExpandAll" + :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" + > + <el-table-column prop="name" label="部门名称" width="260" /> + <el-table-column prop="leader" label="负责人" :formatter="userNicknameFormat" width="120" /> + <el-table-column prop="sort" label="排序" width="200" /> + <el-table-column prop="status" label="状态" width="100"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> + </template> + </el-table-column> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" /> - <!-- 按钮:关闭 --> - <XButton :loading="actionLoading" @click="dialogVisible = false" :title="t('dialog.close')" /> - </template> - </XModal> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-button + link + type="primary" + icon="el-icon-edit" + @click="openModal('update', scope.row.id)" + v-hasPermi="['system:dept:update']" + >修改</el-button + > + <el-button + v-if="scope.row.parentId !== 0" + link + type="danger" + icon="el-icon-delete" + @click="handleDelete(scope.row.id)" + v-hasPermi="['system:dept:delete']" + >删除</el-button + > + </template> + </el-table-column> + </el-table> + </ContentWrap> + + <!-- 添加或修改部门对话框 --> + <dept-form ref="modalRef" @success="getList" :userOption="userOption" /> </template> <script setup lang="ts" name="Dept"> -import { handleTree, defaultProps } from '@/utils/tree' -import type { FormExpose } from '@/components/Form' -import { allSchemas, rules } from './dept.data' +import { handleTree } from '@/utils/tree' import * as DeptApi from '@/api/system/dept' import { getListSimpleUsersApi, UserVO } from '@/api/system/user' - -const { t } = useI18n() // 国际化 +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import DeptForm from './form.vue' +import { dateFormatter } from '@/utils/formatTime' const message = useMessage() // 消息弹窗 -// 列表相关的变量 -const xGrid = ref<any>() // 列表 Grid Ref -const treeConfig = { - transform: true, - rowField: 'id', - parentField: 'parentId', - expandAll: true -} +const { t } = useI18n() // 国际化 +// 搜索变量 +const queryParams = reactive({ + title: '', + name: undefined, + status: undefined, + pageNo: 1, + pageSize: 100 +}) -// 弹窗相关的变量 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const actionType = ref('') // 操作按钮的类型 -const actionLoading = ref(false) // 遮罩层 -const formRef = ref<FormExpose>() // 表单 Ref -const deptOptions = ref() // 树形结构 +// 搜索的表单 +const queryFormRef = ref() +// 数据变量 +const deptDatas = ref() const userOption = ref<UserVO[]>([]) - +// 是否展开,默认全部展开 +const isExpandAll = ref(true) +// 重新渲染表格状态 +const refreshTable = ref(true) +// 列表的加载中 +const loading = ref(true) +//获取用户列表 const getUserList = async () => { const res = await getListSimpleUsersApi() userOption.value = res } -// 获取下拉框[上级]的数据 -const getTree = async () => { - deptOptions.value = [] - const res = await DeptApi.listSimpleDeptApi() - let dept: Tree = { id: 0, name: '顶级部门', children: [] } - dept.children = handleTree(res) - deptOptions.value.push(dept) -} -const [registerTable, { reload, deleteData }] = useXTable({ - allSchemas: allSchemas, - treeConfig: treeConfig, - getListApi: DeptApi.getDeptPageApi, - deleteApi: DeptApi.deleteDeptApi -}) -// ========== 新增/修改 ========== -// 设置标题 -const setDialogTile = (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true -} - -// 新增操作 -const handleCreate = async () => { - setDialogTile('create') -} - -// 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - // 设置数据 - const res = await DeptApi.getDeptApi(rowId) - await nextTick() - unref(formRef)?.setValues(res) -} - -// 提交新增/修改的表单 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - actionLoading.value = true - // 提交请求 - try { - const data = unref(formRef)?.formModel as DeptApi.DeptVO - if (actionType.value === 'create') { - await DeptApi.createDeptApi(data) - message.success(t('common.createSuccess')) - } else if (actionType.value === 'update') { - await DeptApi.updateDeptApi(data) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false - } finally { - actionLoading.value = false - await getTree() - await reload() - } - } +/** 展开/折叠操作 */ +const toggleExpandAll = () => { + refreshTable.value = false + isExpandAll.value = !isExpandAll.value + console.log(isExpandAll.value) + nextTick(() => { + refreshTable.value = true }) } +/** 搜索按钮操作 */ +const handleQuery = () => { + getList() +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await DeptApi.deleteDeptApi(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +/** 查询部门列表 */ +const getList = async () => { + loading.value = true + try { + const res = await DeptApi.getDeptPageApi(queryParams) + deptDatas.value = handleTree(res) + } finally { + loading.value = false + } +} +/** 重置按钮操作 */ +const resetQuery = () => { + queryParams.pageNo = 1 + queryParams.name = undefined + queryParams.status = undefined + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, id) +} + const userNicknameFormat = (row) => { if (!row || !row.leaderUserId) { return '未设置' @@ -184,6 +190,6 @@ const userNicknameFormat = (row) => { // ========== 初始化 ========== onMounted(async () => { await getUserList() - await getTree() + await getList() }) </script> From 26a11d77aaec3321cde6a23beb06bc56936e5cba Mon Sep 17 00:00:00 2001 From: syd <syidong@aliyun.com> Date: Sun, 19 Mar 2023 14:40:31 +0800 Subject: [PATCH 034/184] =?UTF-8?q?vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E6=95=8F=E6=84=9F=E8=AF=8D=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/sensitiveWord/form.vue | 129 +++++++ src/views/system/sensitiveWord/index.vue | 360 ++++++++++-------- .../sensitiveWord/sensitiveWord.data.ts | 74 ---- 3 files changed, 325 insertions(+), 238 deletions(-) create mode 100644 src/views/system/sensitiveWord/form.vue delete mode 100644 src/views/system/sensitiveWord/sensitiveWord.data.ts diff --git a/src/views/system/sensitiveWord/form.vue b/src/views/system/sensitiveWord/form.vue new file mode 100644 index 00000000..a0ed7a21 --- /dev/null +++ b/src/views/system/sensitiveWord/form.vue @@ -0,0 +1,129 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="敏感词" prop="name"> + <el-input v-model="formData.name" placeholder="请输入敏感词" /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-radio-group v-model="formData.status"> + <el-radio + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="dict.value" + > + {{ dict.label }} + </el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="备注" prop="description"> + <el-input v-model="formData.description" placeholder="请输入内容" /> + </el-form-item> + <el-form-item label="标签" prop="tags"> + <el-select + v-model="formData.tags" + multiple + filterable + allow-create + placeholder="请选择文章标签" + style="width: 380px" + > + <el-option v-for="tag in tags" :key="tag" :label="tag" :value="tag" /> + </el-select> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import * as SensitiveWordApi from '@/api/system/sensitiveWord' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const tags = ref([]) +const formData = ref({ + id: undefined, + name: '', + status: true, + description: '', + tags: [] +}) +const formRules = reactive({ + name: [{ required: true, message: '敏感词不能为空', trigger: 'blur' }], + tags: [{ required: true, message: '标签不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await SensitiveWordApi.getSensitiveWordApi(id) + console.log(formData.value) + } finally { + formLoading.value = false + } + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as SensitiveWordApi.SensitiveWordVO + if (formType.value === 'create') { + await SensitiveWordApi.createSensitiveWordApi(data) + message.success(t('common.createSuccess')) + } else { + await SensitiveWordApi.updateSensitiveWordApi(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + name: '', + status: true, + description: '', + tags: [] + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/system/sensitiveWord/index.vue b/src/views/system/sensitiveWord/index.vue index c3de895d..0eb8287d 100644 --- a/src/views/system/sensitiveWord/index.vue +++ b/src/views/system/sensitiveWord/index.vue @@ -1,191 +1,223 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <!-- 操作:新增 --> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['system:sensitive-word:create']" - @click="handleCreate()" + <!-- 搜索 --> + <content-wrap> + <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true"> + <el-form-item label="敏感词" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入敏感词" + clearable + @keyup.enter="handleQuery" /> - <!-- 操作:导出 --> - <XButton - type="warning" - preIcon="ep:download" - :title="t('action.export')" - v-hasPermi="['system:sensitive-word:export']" - @click="exportList('敏感词数据.xls')" - /> - </template> - <template #tags_default="{ row }"> - <el-tag - :disable-transitions="true" - :key="index" - v-for="(tag, index) in row.tags" - :index="index" + </el-form-item> + <el-form-item label="标签" prop="tag"> + <el-select + v-model="queryParams.tag" + placeholder="请选择标签" + clearable + @keyup.enter="handleQuery" > - {{ tag }} - </el-tag> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:修改 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['system:sensitive-word:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['system:sensitive-word:update']" - @click="handleDetail(row.id)" - /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['system:sensitive-word:delete']" - @click="deleteData(row.id)" - /> - </template> - </XTable> - </ContentWrap> - - <XModal v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(添加 / 修改) --> - <Form - v-if="['create', 'update'].includes(actionType)" - :schema="allSchemas.formSchema" - :rules="rules" - ref="formRef" - > - <template #tags="form"> - <el-select v-model="form['tags']" multiple placeholder="请选择"> - <el-option v-for="item in tagsOptions" :key="item" :label="item" :value="item" /> + <el-option v-for="tag in tags" :key="tag" :label="tag" :value="tag" /> </el-select> - </template> - </Form> - <!-- 对话框(详情) --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - > - <template #tags="{ row }"> - <el-tag - :disable-transitions="true" - :key="index" - v-for="(tag, index) in row.tags" - :index="index" + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="请选择启用状态" clearable> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + value-format="YYYY-MM-DD HH:mm:ss" + type="daterange" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + /> + </el-form-item> + + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button + type="primary" + @click="openModal('create')" + v-hasPermi="['system:sensitive-word:create']" > - {{ tag }} - </el-tag> - </template> - </Descriptions> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="actionLoading" - @click="submitForm()" + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['system:sensitive-word:export']" + > + <Icon icon="ep:download" class="mr-5px" /> 导出 + </el-button> + </el-form-item> + </el-form> + </content-wrap> + + <!-- 列表 --> + <content-wrap> + <el-table v-loading="loading" :data="list"> + <el-table-column label="编号" align="center" prop="id" /> + <el-table-column label="敏感词" align="center" prop="name" /> + <el-table-column label="状态" align="center" prop="status"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> + </template> + </el-table-column> + <el-table-column label="描述" align="center" prop="description" /> + <el-table-column label="标签" align="center" prop="tags"> + <template #default="scope"> + <el-tag + :disable-transitions="true" + :key="index" + v-for="(tag, index) in scope.row.tags" + :index="index" + > + {{ tag }} + </el-tag> + </template> + </el-table-column> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" /> - <!-- 按钮:关闭 --> - <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openModal('update', scope.row.id)" + v-hasPermi="['infra:config:update']" + > + 编辑 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['infra:config:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + + <!-- 表单弹窗:添加/修改 --> + <config-form ref="modalRef" @success="getList" /> </template> <script setup lang="ts" name="SensitiveWord"> -import type { FormExpose } from '@/components/Form' +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' +import download from '@/utils/download' import * as SensitiveWordApi from '@/api/system/sensitiveWord' -import { rules, allSchemas } from './sensitiveWord.data' - -const { t } = useI18n() // 国际化 +import ConfigForm from './form.vue' const message = useMessage() // 消息弹窗 -// 列表相关的变量 -const [registerTable, { reload, deleteData, exportList }] = useXTable({ - allSchemas: allSchemas, - getListApi: SensitiveWordApi.getSensitiveWordPageApi, - deleteApi: SensitiveWordApi.deleteSensitiveWordApi, - exportListApi: SensitiveWordApi.exportSensitiveWordApi +const { t } = useI18n() // 国际化 + +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const tags = ref([]) +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + name: undefined, + tag: undefined, + status: undefined, + createTime: [] }) -const actionLoading = ref(false) // 遮罩层 -const actionType = ref('') // 操作按钮的类型 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const formRef = ref<FormExpose>() // 表单 Ref -const detailData = ref() // 详情 Ref +const queryFormRef = ref() // 搜索的表单 +const exportLoading = ref(false) // 导出的加载中 -// 获取标签 -const tagsOptions = ref() +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await SensitiveWordApi.getSensitiveWordPageApi(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 初始化标签select*/ const getTags = async () => { - const res = await SensitiveWordApi.getSensitiveWordTagsApi() - tagsOptions.value = res + const data = await SensitiveWordApi.getSensitiveWordTagsApi() + tags.value = data } -// 设置标题 -const setDialogTile = (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() } -// 新增操作 -const handleCreate = () => { - setDialogTile('create') +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() } -// 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - // 设置数据 - const res = await SensitiveWordApi.getSensitiveWordApi(rowId) - unref(formRef)?.setValues(res) +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, id) } -// 详情操作 -const handleDetail = async (rowId: number) => { - setDialogTile('detail') - const res = await SensitiveWordApi.getSensitiveWordApi(rowId) - detailData.value = res +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await SensitiveWordApi.deleteSensitiveWordApi(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} } -// 提交按钮 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - actionLoading.value = true - // 提交请求 - try { - const data = unref(formRef)?.formModel as SensitiveWordApi.SensitiveWordVO - if (actionType.value === 'create') { - await SensitiveWordApi.createSensitiveWordApi(data) - message.success(t('common.createSuccess')) - } else { - await SensitiveWordApi.updateSensitiveWordApi(data) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false - } finally { - actionLoading.value = false - // 刷新列表 - await reload() - } - } - }) +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await SensitiveWordApi.exportSensitiveWordApi(queryParams) + download.excel(data, '敏感词.xls') + } catch { + } finally { + exportLoading.value = false + } } -// ========== 初始化 ========== -onMounted(async () => { - await getTags() +/** 初始化 **/ +onMounted(() => { + getTags() + getList() }) </script> diff --git a/src/views/system/sensitiveWord/sensitiveWord.data.ts b/src/views/system/sensitiveWord/sensitiveWord.data.ts deleted file mode 100644 index d21bb94c..00000000 --- a/src/views/system/sensitiveWord/sensitiveWord.data.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -const { t } = useI18n() // 国际化 - -// 表单校验 -export const rules = reactive({ - name: [required], - tags: [required] -}) - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'id', - primaryTitle: '敏感词编号', - action: true, - columns: [ - { - title: '敏感词', - field: 'name', - isSearch: true - }, - { - title: '标签', - field: 'tag', - isTable: false, - isForm: false, - isDetail: false, - isSearch: true - }, - { - title: '标签', - field: 'tags', - table: { - slots: { - default: 'tags_default' - } - } - }, - { - title: t('common.status'), - field: 'status', - dictType: DICT_TYPE.COMMON_STATUS, - dictClass: 'number', - isSearch: true - }, - { - title: '描述', - field: 'description', - form: { - component: 'Input', - componentProps: { - type: 'textarea', - rows: 4 - }, - colProps: { - span: 24 - } - } - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From 5d062d5877ec0fffd525ab2360870706a9330072 Mon Sep 17 00:00:00 2001 From: Theo <koutianyu@163.com> Date: Sun, 19 Mar 2023 16:48:27 +0800 Subject: [PATCH 035/184] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=AD=97=E5=85=B8?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/dict/types.ts | 18 +- src/router/modules/remaining.ts | 25 ++ src/views/system/dict/data.form.vue | 182 ++++++++++++ src/views/system/dict/data.vue | 203 +++++++++++++ src/views/system/dict/dict.data.ts | 104 ------- src/views/system/dict/dict.type.ts | 65 ----- src/views/system/dict/form.vue | 122 ++++++++ src/views/system/dict/index.vue | 436 ++++++++++++---------------- 8 files changed, 736 insertions(+), 419 deletions(-) create mode 100644 src/views/system/dict/data.form.vue create mode 100644 src/views/system/dict/data.vue delete mode 100644 src/views/system/dict/dict.data.ts delete mode 100644 src/views/system/dict/dict.type.ts create mode 100644 src/views/system/dict/form.vue diff --git a/src/api/system/dict/types.ts b/src/api/system/dict/types.ts index b630dccb..3421cd23 100644 --- a/src/api/system/dict/types.ts +++ b/src/api/system/dict/types.ts @@ -1,16 +1,18 @@ export type DictTypeVO = { - id: number + id: number | undefined name: string type: string - status: number + status: number | undefined remark: string createTime: Date } export type DictTypePageReqVO = { + pageNo: number + pageSize: number name: string type: string - status: number + status: number | undefined createTime: Date[] } @@ -22,8 +24,8 @@ export type DictTypeExportReqVO = { } export type DictDataVO = { - id: number - sort: number + id: number | undefined + sort: number | undefined label: string value: string dictType: string @@ -31,12 +33,14 @@ export type DictDataVO = { colorType: string cssClass: string remark: string - createTime: Date + createTime: Date | undefined } export type DictDataPageReqVO = { + pageNo: number + pageSize: number label: string dictType: string - status: number + status: number | undefined } export type DictDataExportReqVO = { diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index 43375961..d5970267 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -104,6 +104,31 @@ const remainingRouter: AppRouteRecordRaw[] = [ } ] }, + + { + path: '/dict', + component: Layout, + name: 'dict', + meta: { + hidden: true + }, + children: [ + { + path: 'type/data/:dictType', + component: () => import('@/views/system/dict/data.vue'), + name: 'data', + meta: { + title: '字典数据', + noCache: true, + hidden: true, + canTo: true, + icon: '', + activeMenu: 'system/dict/data' + } + } + ] + }, + { path: '/codegen', component: Layout, diff --git a/src/views/system/dict/data.form.vue b/src/views/system/dict/data.form.vue new file mode 100644 index 00000000..5c85b946 --- /dev/null +++ b/src/views/system/dict/data.form.vue @@ -0,0 +1,182 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="字典类型" prop="type"> + <el-input + :disabled="typeof formData.id !== 'undefined'" + v-model="formData.dictType" + placeholder="请输入参数名称" + /> + </el-form-item> + <el-form-item label="数据标签" prop="label"> + <el-input v-model="formData.label" placeholder="请输入数据标签" /> + </el-form-item> + + <el-form-item label="数据键值" prop="value"> + <el-input v-model="formData.value" placeholder="请输入数据键值" /> + </el-form-item> + <el-form-item label="显示排序" prop="sort"> + <el-input-number v-model="formData.sort" controls-position="right" :min="0" /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-radio-group v-model="formData.status"> + <el-radio + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="parseInt(dict.value)" + :label="parseInt(dict.value)" + >{{ dict.label }}</el-radio + > + </el-radio-group> + </el-form-item> + <el-form-item label="颜色类型" prop="colorType"> + <el-select v-model="formData.colorType"> + <el-option + v-for="item in colorTypeOptions" + :key="item.value" + :label="item.label + '(' + item.value + ')'" + :value="item.value" + /> + </el-select> + </el-form-item> + <el-form-item label="CSS Class" prop="cssClass"> + <el-input v-model="formData.cssClass" placeholder="请输入 CSS Class" /> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="formData.remark" type="textarea" placeholder="请输入内容" /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getDictOptions } from '@/utils/dict' + +import * as DictDataApi from '@/api/system/dict/dict.data' +import { DictDataVO } from '@/api/system/dict/types' +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: undefined, + sort: 0, + label: '', + value: '', + dictType: '', + status: 0, + colorType: '', + cssClass: '', + remark: '', + createTime: undefined +}) +const formRules = reactive({ + label: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }], + value: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }], + sort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +// 数据标签回显样式 + +const colorTypeOptions = readonly([ + { + value: 'default', + label: '默认' + }, + { + value: 'primary', + label: '主要' + }, + { + value: 'success', + label: '成功' + }, + { + value: 'info', + label: '信息' + }, + { + value: 'warning', + label: '警告' + }, + { + value: 'danger', + label: '危险' + } +]) + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await DictDataApi.getDictDataApi(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as DictDataVO + if (formType.value === 'create') { + await DictDataApi.createDictDataApi(data) + message.success(t('common.createSuccess')) + } else { + await DictDataApi.updateDictDataApi(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + sort: undefined, + label: '', + value: '', + dictType: '', + status: 0, + colorType: '', + cssClass: '', + remark: '', + createTime: undefined + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/system/dict/data.vue b/src/views/system/dict/data.vue new file mode 100644 index 00000000..2c1b373a --- /dev/null +++ b/src/views/system/dict/data.vue @@ -0,0 +1,203 @@ +<template> + <content-wrap> + <el-form :model="queryParams" ref="queryFormRef" size="small" :inline="true" label-width="68px"> + <el-form-item label="字典名称" prop="dictType"> + <el-select v-model="queryParams.dictType"> + <el-option + v-for="item in simpleDictList" + :key="item.id" + :label="item.name" + :value="item.type" + /> + </el-select> + </el-form-item> + <el-form-item label="字典标签" prop="label"> + <el-input + v-model="queryParams.label" + placeholder="请输入字典标签" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="数据状态" clearable> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button type="primary" @click="openModal('create')" v-hasPermi="['system:dict:create']"> + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['system:dict:export']" + > + <Icon icon="ep:download" class="mr-5px" /> 导出 + </el-button> + </el-form-item> + </el-form> + <!-- 列表 --> + <el-table v-loading="loading" :data="list"> + <el-table-column label="字典编码" align="center" prop="id" /> + <el-table-column label="字典标签" align="center" prop="label" /> + <el-table-column label="字典键值" align="center" prop="value" /> + <el-table-column label="字典排序" align="center" prop="sort" /> + <el-table-column label="状态" align="center" prop="status"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> + </template> + </el-table-column> + <el-table-column label="颜色类型" align="center" prop="colorType" /> + <el-table-column label="CSS Class" align="center" prop="cssClass" /> + <el-table-column + label="备注" + align="center" + prop="remark" + :show-overflow-tooltip="tableTooltipConfig" + /> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" + /> + + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + @click="openModal('update', scope.row.id)" + v-hasPermi="['system:dict:update']" + ><Icon icon="ic:outline-mode" class="mr-5px" />修改</el-button + > + <el-button link @click="handleDelete(scope.row.id)" v-hasPermi="['system:dict:delete']" + ><Icon icon="material-symbols:delete-forever-sharp" class="mr-5px" />删除</el-button + > + </template> + </el-table-column> + </el-table> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + <!-- 表单弹窗:添加/修改 --> + <data-form ref="modalRef" @success="getList" /> +</template> + +<script setup lang="ts" name="Data"> +import * as DictDataApi from '@/api/system/dict/dict.data' +import { listSimpleDictTypeApi } from '@/api/system/dict/dict.type' +import { getDictOptions, DICT_TYPE } from '@/utils/dict' +import download from '@/utils/download' +import { dateFormatter } from '@/utils/formatTime' +import DataForm from './data.form.vue' +import { DictTypeVO } from '@/api/system/dict/types' +import { useRoute } from 'vue-router' +const message = useMessage() // 消息弹窗 +const { t } = useI18n() // 国际化 + +const route = useRoute() + +const simpleDictList = ref<DictTypeVO[]>() + +const tableTooltipConfig = readonly({ + appendTo: 'body' +}) + +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + label: '', + status: undefined, + dictType: route.params.dictType +}) +const queryFormRef = ref() // 搜索的表单 +const exportLoading = ref(false) // 导出的加载中 + +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await DictDataApi.getDictDataPageApi(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} +// 查询字典(精简)列表 +const getSimpleDictList = async () => { + const data = await listSimpleDictTypeApi() + simpleDictList.value = data +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, id) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await DictDataApi.deleteDictDataApi(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await DictDataApi.exportDictDataApi(queryParams) + download.excel(data, '字典数据.xls') + } catch { + } finally { + exportLoading.value = false + } +} + +/** 初始化 **/ +onMounted(() => { + getList() +}) +// 查询字典(精简)列表 +getSimpleDictList() +</script> diff --git a/src/views/system/dict/dict.data.ts b/src/views/system/dict/dict.data.ts deleted file mode 100644 index cdfe4538..00000000 --- a/src/views/system/dict/dict.data.ts +++ /dev/null @@ -1,104 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -// 国际化 -const { t } = useI18n() -// 表单校验 -export const dictDataRules = reactive({ - label: [required], - value: [required], - sort: [required] -}) -// crudSchemas -export const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: null, - action: true, - actionWidth: '140px', - searchSpan: 12, - columns: [ - { - title: '字典类型', - field: 'dictType', - isTable: false, - isForm: false - }, - { - title: '数据标签', - field: 'label', - isSearch: true - }, - { - title: '数据键值', - field: 'value' - }, - // { - // title: '标签类型', - // field: 'colorType', - // form: { - // component: 'Select', - // componentProps: { - // options: [ - // { - // label: 'default', - // value: '' - // }, - // { - // label: 'success', - // value: 'success' - // }, - // { - // label: 'info', - // value: 'info' - // }, - // { - // label: 'warning', - // value: 'warning' - // }, - // { - // label: 'danger', - // value: 'danger' - // } - // ] - // } - // }, - // isTable: false - // }, - { - title: '颜色', - field: 'cssClass', - isTable: false, - form: { - component: 'ColorPicker', - componentProps: { - predefine: ['#ffffff', '#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#909399', '#c71585'] - } - } - }, - { - title: '显示排序', - field: 'sort', - isTable: false - }, - { - title: t('common.status'), - field: 'status', - dictType: DICT_TYPE.COMMON_STATUS, - dictClass: 'number' - }, - { - title: t('form.remark'), - field: 'remark', - form: { - component: 'Input', - componentProps: { - type: 'textarea', - rows: 4 - }, - colProps: { - span: 24 - } - }, - isTable: false - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) diff --git a/src/views/system/dict/dict.type.ts b/src/views/system/dict/dict.type.ts deleted file mode 100644 index 73b5a021..00000000 --- a/src/views/system/dict/dict.type.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -const { t } = useI18n() // 国际化 - -// 表单校验 -export const dictTypeRules = reactive({ - name: [required], - type: [required] -}) -// 新增 + 修改 -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: null, - action: true, - actionWidth: '140px', - searchSpan: 12, - columns: [ - { - title: '字典名称', - field: 'name', - isSearch: true - }, - { - title: '字典类型', - field: 'type', - isSearch: true - }, - { - title: t('common.status'), - field: 'status', - dictType: DICT_TYPE.COMMON_STATUS, - dictClass: 'number', - table: { - width: 70 - } - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false, - isTable: false, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - }, - { - title: t('form.remark'), - field: 'remark', - isTable: false, - form: { - componentProps: { - type: 'textarea', - rows: 4 - }, - colProps: { - span: 24 - } - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) diff --git a/src/views/system/dict/form.vue b/src/views/system/dict/form.vue new file mode 100644 index 00000000..28b42c9d --- /dev/null +++ b/src/views/system/dict/form.vue @@ -0,0 +1,122 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="字典名称" prop="name"> + <el-input v-model="formData.name" placeholder="请输入字典名称" /> + </el-form-item> + <el-form-item label="字典类型" prop="type"> + <el-input + :disabled="typeof formData.id !== 'undefined'" + v-model="formData.type" + placeholder="请输入参数名称" + /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-radio-group v-model="formData.status"> + <el-radio + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="parseInt(dict.value)" + :label="parseInt(dict.value)" + >{{ dict.label }}</el-radio + > + </el-radio-group> + </el-form-item> + + <el-form-item label="备注" prop="remark"> + <el-input v-model="formData.remark" type="textarea" placeholder="请输入内容" /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import * as DictTypeApi from '@/api/system/dict/dict.type' +import { DictTypeVO } from '@/api/system/dict/types' +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: undefined, + name: '', + type: '', + status: 0, + remark: '' +}) +const formRules = reactive({ + name: [{ required: true, message: '字典名称不能为空', trigger: 'blur' }], + type: [{ required: true, message: '字典类型不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await DictTypeApi.getDictTypeApi(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as DictTypeVO + if (formType.value === 'create') { + await DictTypeApi.createDictTypeApi(data) + message.success(t('common.createSuccess')) + } else { + await DictTypeApi.updateDictTypeApi(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + type: '', + name: '', + status: undefined, + remark: '' + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/system/dict/index.vue b/src/views/system/dict/index.vue index 713fde97..367cc9d4 100644 --- a/src/views/system/dict/index.vue +++ b/src/views/system/dict/index.vue @@ -1,257 +1,207 @@ <template> - <div class="flex"> - <!-- ====== 字典分类 ====== --> - <el-card class="w-1/2 dict" :gutter="12" shadow="always"> - <template #header> - <div class="card-header"> - <span>字典分类</span> - </div> - </template> - <XTable @register="registerType" @cell-click="cellClickEvent"> - <!-- 操作:新增类型 --> - <template #toolbar_buttons> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['system:dict:create']" - @click="handleTypeCreate()" + <content-wrap> + <el-form :model="queryParams" ref="queryFormRef" size="small" :inline="true" label-width="68px"> + <el-form-item label="字典名称" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入字典名称" + clearable + style="width: 240px" + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="字典类型" prop="type"> + <el-input + v-model="queryParams.type" + placeholder="请输入字典类型" + clearable + style="width: 240px" + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select + v-model="queryParams.status" + placeholder="字典状态" + clearable + style="width: 240px" + > + <el-option + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="parseInt(dict.value)" + :label="dict.label" + :value="dict.value" /> + </el-select> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + style="width: 240px" + value-format="yyyy-MM-dd HH:mm:ss" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button type="primary" @click="openModal('create')" v-hasPermi="['system:dict:create']"> + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['system:dict:export']" + > + <Icon icon="ep:download" class="mr-5px" /> 导出 + </el-button> + </el-form-item> + </el-form> + + <!-- 列表 --> + <el-table v-loading="loading" :data="list"> + <el-table-column label="字典编号" align="center" prop="id" /> + <el-table-column label="字典名称" align="center" prop="name" show-overflow-tooltip /> + <el-table-column label="字典类型" align="center" show-overflow-tooltip> + <template #default="scope"> + <div> + <router-link :to="'/dict/type/data/' + scope.row.type"> + <span>{{ scope.row.type }}</span> + </router-link> + </div> </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:编辑类型 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['system:dict:update']" - @click="handleTypeUpdate(row.id)" - /> - <!-- 操作:删除类型 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['system:dict:delete']" - @click="typeDeleteData(row.id)" - /> + </el-table-column> + <el-table-column label="状态" align="center" prop="status"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> </template> - </XTable> - <!-- @星语:分页和列表重叠在一起了 --> - </el-card> - <!-- ====== 字典数据 ====== --> - <el-card class="w-1/2 dict ml-3" :gutter="12" shadow="hover"> - <template #header> - <div class="card-header"> - <span>字典数据</span> - </div> - </template> - <!-- 列表 --> - <div v-if="!tableTypeSelect"> - <span>请从左侧选择</span> - </div> - <div v-if="tableTypeSelect"> - <!-- 列表 --> - <XTable @register="registerData"> - <!-- 操作:新增数据 --> - <template #toolbar_buttons> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['system:dict:create']" - @click="handleDataCreate()" - /> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:修改数据 --> - <XTextButton - v-hasPermi="['system:dict:update']" - preIcon="ep:edit" - :title="t('action.edit')" - @click="handleDataUpdate(row.id)" - /> - <!-- 操作:删除数据 --> - <XTextButton - v-hasPermi="['system:dict:delete']" - preIcon="ep:delete" - :title="t('action.del')" - @click="dataDeleteData(row.id)" - /> - </template> - </XTable> - </div> - </el-card> - <XModal id="dictModel" v-model="dialogVisible" :title="dialogTitle"> - <Form - v-if="['typeCreate', 'typeUpdate'].includes(actionType)" - :schema="DictTypeSchemas.allSchemas.formSchema" - :rules="DictTypeSchemas.dictTypeRules" - ref="typeFormRef" - > - <template #type> - <template v-if="actionType == 'typeUpdate'"> - <el-tag>{{ dictTypeValue }}</el-tag> - </template> - <template v-else><el-input v-model="dictTypeValue" /> </template> - </template> - </Form> - <Form - v-if="['dataCreate', 'dataUpdate'].includes(actionType)" - :schema="DictDataSchemas.allSchemas.formSchema" - :rules="DictDataSchemas.dictDataRules" - ref="dataFormRef" + </el-table-column> + <el-table-column label="备注" align="center" prop="remark" /> + <el-table-column + label="创建时间" + :formatter="dateFormatter" + align="center" + prop="createTime" + width="180" /> - <!-- 操作按钮 --> - <template #footer> - <XButton - v-if="['typeCreate', 'typeUpdate'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="actionLoading" - @click="submitTypeForm" - /> - <XButton - v-if="['dataCreate', 'dataUpdate'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="actionLoading" - @click="submitDataForm" - /> - <XButton :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> - </div> + + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + @click="openModal('update', scope.row.id)" + v-hasPermi="['system:dict:update']" + ><Icon icon="ic:outline-mode" class="mr-5px" />修改</el-button + > + <el-button link @click="handleDelete(scope.row.id)" v-hasPermi="['system:dict:delete']" + ><Icon icon="material-symbols:delete-forever-sharp" class="mr-5px" />删除</el-button + > + </template> + </el-table-column> + </el-table> + + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + <!-- 表单弹窗:添加/修改 --> + <dict-type-form ref="modalRef" @success="getList" /> </template> + <script setup lang="ts" name="Dict"> -import { VxeTableEvents } from 'vxe-table' -import type { FormExpose } from '@/components/Form' -import * as DictTypeSchemas from './dict.type' -import * as DictDataSchemas from './dict.data' import * as DictTypeApi from '@/api/system/dict/dict.type' -import * as DictDataApi from '@/api/system/dict/dict.data' -import { DictDataVO, DictTypeVO } from '@/api/system/dict/types' - -const { t } = useI18n() // 国际化 +import { getDictOptions, DICT_TYPE } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' +import DictTypeForm from './form.vue' +import { DictTypePageReqVO } from '@/api/system/dict/types' +import download from '@/utils/download' const message = useMessage() // 消息弹窗 +const { t } = useI18n() // 国际化 -const [registerType, { reload: typeGetList, deleteData: typeDeleteData }] = useXTable({ - allSchemas: DictTypeSchemas.allSchemas, - getListApi: DictTypeApi.getDictTypePageApi, - deleteApi: DictTypeApi.deleteDictTypeApi +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 + +const list = ref([]) // 字典表格数据 +const queryParams = reactive<DictTypePageReqVO>({ + pageNo: 1, + pageSize: 10, + name: '', + type: '', + status: undefined, + createTime: [] }) +const queryFormRef = ref() // 搜索的表单 +const exportLoading = ref(false) // 导出的加载中 -const queryParams = reactive({ - dictType: null +/** 查询字典类型列表 */ +const getList = async () => { + loading.value = true + try { + const data = await DictTypeApi.getDictTypePageApi(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, id) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await DictTypeApi.deleteDictTypeApi(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await DictTypeApi.exportDictTypeApi(queryParams) + download.excel(data, '字典类型.xls') + } catch { + } finally { + exportLoading.value = false + } +} + +/** 初始化 **/ +onMounted(() => { + getList() }) -const [registerData, { reload: dataGetList, deleteData: dataDeleteData }] = useXTable({ - allSchemas: DictDataSchemas.allSchemas, - params: queryParams, - getListApi: DictDataApi.getDictDataPageApi, - deleteApi: DictDataApi.deleteDictDataApi -}) -// ========== 字典分类列表相关 ========== -const dictTypeValue = ref('') -// 字典分类修改操作 -const handleTypeCreate = () => { - dictTypeValue.value = '' - setDialogTile('typeCreate') -} -const handleTypeUpdate = async (rowId: number) => { - setDialogTile('typeUpdate') - // 设置数据 - const res = await DictTypeApi.getDictTypeApi(rowId) - dictTypeValue.value = res.type - unref(typeFormRef)?.setValues(res) -} - -// 字典数据修改操作 -const handleDataCreate = () => { - setDialogTile('dataCreate') -} -const handleDataUpdate = async (rowId: number) => { - setDialogTile('dataUpdate') - // 设置数据 - const res = await DictDataApi.getDictDataApi(rowId) - unref(dataFormRef)?.setValues(res) -} -// 字典分类点击行事件 -const parentType = ref('') -const tableTypeSelect = ref(false) -const cellClickEvent: VxeTableEvents.CellClick = async ({ row }) => { - tableTypeSelect.value = true - queryParams.dictType = row['type'] - await nextTick() - await dataGetList() - parentType.value = row['type'] -} -// 弹出框 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const actionLoading = ref(false) // 遮罩层 -const typeFormRef = ref<FormExpose>() // 分类表单 Ref -const dataFormRef = ref<FormExpose>() // 数据表单 Ref -const actionType = ref('') // 操作按钮的类型 - -// 设置标题 -const setDialogTile = (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true -} - -// 同步dictTypeValue到form 否则导致表单验证不通过 -watch(dictTypeValue, (val) => { - unref(typeFormRef)?.setValues({ type: val }) -}) - -// 提交按钮 -const submitTypeForm = async () => { - const elForm = unref(typeFormRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid && dictTypeValue.value != '') { - actionLoading.value = true - // 提交请求 - try { - const data = unref(typeFormRef)?.formModel as DictTypeVO - if (actionType.value === 'typeCreate') { - data.type = dictTypeValue.value - await DictTypeApi.createDictTypeApi(data) - message.success(t('common.createSuccess')) - } else if (actionType.value === 'typeUpdate') { - await DictTypeApi.updateDictTypeApi(data) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false - } finally { - actionLoading.value = false - typeGetList() - } - } - }) -} -const submitDataForm = async () => { - const elForm = unref(dataFormRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - actionLoading.value = true - // 提交请求 - try { - const data = unref(dataFormRef)?.formModel as DictDataVO - if (actionType.value === 'dataCreate') { - data.dictType = parentType.value - await DictDataApi.createDictDataApi(data) - message.success(t('common.createSuccess')) - } else if (actionType.value === 'dataUpdate') { - await DictDataApi.updateDictDataApi(data) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false - } finally { - actionLoading.value = false - dataGetList() - } - } - }) -} </script> From 188329355353db8a3925089cc8a385eb4f67e812 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 19 Mar 2023 20:56:11 +0800 Subject: [PATCH 036/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E4=BB=A4=E7=89=8C=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/oauth2/token.ts | 10 +- src/views/system/oauth2/token/index.vue | 188 ++++++++++++++------ src/views/system/oauth2/token/token.data.ts | 48 ----- 3 files changed, 137 insertions(+), 109 deletions(-) delete mode 100644 src/views/system/oauth2/token/token.data.ts diff --git a/src/api/system/oauth2/token.ts b/src/api/system/oauth2/token.ts index dc7c44f5..8e9dca1e 100644 --- a/src/api/system/oauth2/token.ts +++ b/src/api/system/oauth2/token.ts @@ -11,18 +11,12 @@ export interface OAuth2TokenVO { expiresTime: Date } -export interface OAuth2TokenPageReqVO extends PageParam { - userId?: number - userType?: number - clientId?: string -} - // 查询 token列表 -export const getAccessTokenPageApi = (params: OAuth2TokenPageReqVO) => { +export const getAccessTokenPage = (params: PageParam) => { return request.get({ url: '/system/oauth2-token/page', params }) } // 删除 token -export const deleteAccessTokenApi = (accessToken: number) => { +export const deleteAccessToken = (accessToken: number) => { return request.delete({ url: '/system/oauth2-token/delete?accessToken=' + accessToken }) } diff --git a/src/views/system/oauth2/token/index.vue b/src/views/system/oauth2/token/index.vue index f0dfbd9c..e8c730e0 100644 --- a/src/views/system/oauth2/token/index.vue +++ b/src/views/system/oauth2/token/index.vue @@ -1,64 +1,146 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #actionbtns_default="{ row }"> - <!-- 操作:详情 --> - <XTextButton preIcon="ep:view" :title="t('action.detail')" @click="handleDetail(row)" /> - <!-- 操作:登出 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.logout')" - v-hasPermi="['system:oauth2-token:delete']" - @click="handleForceLogout(row.id)" + <content-wrap> + <!-- 搜索工作栏 --> + <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true"> + <el-form-item label="用户编号" prop="userId"> + <el-input + v-model="queryParams.userId" + placeholder="请输入用户编号" + clearable + @keyup.enter="handleQuery" /> - </template> - </XTable> - </ContentWrap> - <XModal v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(详情) --> - <Descriptions :schema="allSchemas.detailSchema" :data="detailData" /> - <!-- 操作按钮 --> - <template #footer> - <XButton :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> + </el-form-item> + <el-form-item label="用户类型" prop="userType"> + <el-select v-model="queryParams.userType" placeholder="请选择用户类型" clearable> + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.USER_TYPE)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="客户端编号" prop="clientId"> + <el-input + v-model="queryParams.clientId" + placeholder="请输入客户端编号" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + </el-form-item> + </el-form> + </content-wrap> + + <!-- 列表 --> + <content-wrap> + <el-table v-loading="loading" :data="list"> + <el-table-column label="访问令牌" align="center" prop="accessToken" width="300" /> + <el-table-column label="刷新令牌" align="center" prop="refreshToken" width="300" /> + <el-table-column label="用户编号" align="center" prop="userId" /> + <el-table-column label="用户类型" align="center" prop="userType"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" /> + </template> + </el-table-column> + <el-table-column + label="过期时间" + align="center" + prop="expiresTime" + :formatter="dateFormatter" + width="180" + /> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + :formatter="dateFormatter" + width="180" + /> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="danger" + @click="handleForceLogout(scope.row.id)" + v-hasPermi="['system:oauth2-token:delete']" + > + 强退 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> </template> -<script setup lang="ts" name="Token"> -import { allSchemas } from './token.data' -import * as TokenApi from '@/api/system/oauth2/token' -const { t } = useI18n() // 国际化 +<script setup lang="ts" name="Oauth2AccessToken"> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' +import * as OAuth2AccessTokenApi from '@/api/system/oauth2/token' const message = useMessage() // 消息弹窗 -// 列表相关的变量 -const [registerTable, { reload }] = useXTable({ - allSchemas: allSchemas, - topActionSlots: false, - getListApi: TokenApi.getAccessTokenPageApi +const { t } = useI18n() // 国际化 + +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + userId: null, + userType: null, + clientId: null }) +const queryFormRef = ref() // 搜索的表单 -// ========== 详情相关 ========== -const detailData = ref() // 详情 Ref -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref(t('action.detail')) // 弹出层标题 -// 详情 -const handleDetail = async (row: TokenApi.OAuth2TokenVO) => { - // 设置数据 - detailData.value = row - dialogVisible.value = true +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await OAuth2AccessTokenApi.getAccessTokenPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } } -// 强退操作 -const handleForceLogout = (rowId: number) => { - message - .confirm('是否要强制退出用户') - .then(async () => { - await TokenApi.deleteAccessTokenApi(rowId) - message.success(t('common.success')) - }) - .finally(async () => { - // 刷新列表 - await reload() - }) +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() } + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 强制退出操作 */ +const handleForceLogout = async (id: number) => { + try { + // 删除的二次确认 + await message.confirm('是否要强制退出用户') + // 发起删除 + await OAuth2AccessTokenApi.deleteAccessToken(id) + message.success(t('common.success')) + // 刷新列表 + await getList() + } catch {} +} + +/** 初始化 **/ +onMounted(() => { + getList() +}) </script> diff --git a/src/views/system/oauth2/token/token.data.ts b/src/views/system/oauth2/token/token.data.ts deleted file mode 100644 index 8a0e8486..00000000 --- a/src/views/system/oauth2/token/token.data.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -const { t } = useI18n() // 国际化 -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: null, - action: true, - columns: [ - { - title: '用户编号', - field: 'userId', - isSearch: true - }, - { - title: '访问令牌', - field: 'accessToken' - }, - { - title: '刷新令牌', - field: 'refreshToken' - }, - { - title: '客户端编号', - field: 'clientId', - isSearch: true - }, - { - title: '用户类型', - field: 'userType', - dictType: DICT_TYPE.USER_TYPE, - dictClass: 'number', - isSearch: true - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false - }, - { - title: '过期时间', - field: 'expiresTime', - formatter: 'formatDate', - isForm: false - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From cf959599c6425b4f821394cbe3d16b4abb4ae59d Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 19 Mar 2023 21:30:53 +0800 Subject: [PATCH 037/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9ARevie?= =?UTF-8?q?w=20=E6=95=8F=E6=84=9F=E8=AF=8D=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/sensitiveWord/form.vue | 11 ++++++----- src/views/system/sensitiveWord/index.vue | 24 +++++++++++++----------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/views/system/sensitiveWord/form.vue b/src/views/system/sensitiveWord/form.vue index a0ed7a21..1dccd656 100644 --- a/src/views/system/sensitiveWord/form.vue +++ b/src/views/system/sensitiveWord/form.vue @@ -48,6 +48,7 @@ <script setup lang="ts"> import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import * as SensitiveWordApi from '@/api/system/sensitiveWord' +import { CommonStatusEnum } from '@/utils/constants' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -56,11 +57,10 @@ const modelVisible = ref(false) // 弹窗的是否展示 const modelTitle = ref('') // 弹窗的标题 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formType = ref('') // 表单的类型:create - 新增;update - 修改 -const tags = ref([]) const formData = ref({ id: undefined, name: '', - status: true, + status: CommonStatusEnum.ENABLE, description: '', tags: [] }) @@ -69,6 +69,7 @@ const formRules = reactive({ tags: [{ required: true, message: '标签不能为空', trigger: 'blur' }] }) const formRef = ref() // 表单 Ref +const tags = ref([]) // todo @blue-syd:在 openModal 里加载下 /** 打开弹窗 */ const openModal = async (type: string, id?: number) => { @@ -101,10 +102,10 @@ const submitForm = async () => { try { const data = formData.value as unknown as SensitiveWordApi.SensitiveWordVO if (formType.value === 'create') { - await SensitiveWordApi.createSensitiveWordApi(data) + await SensitiveWordApi.createSensitiveWordApi(data) // TODO @blue-syd:去掉 API 后缀 message.success(t('common.createSuccess')) } else { - await SensitiveWordApi.updateSensitiveWordApi(data) + await SensitiveWordApi.updateSensitiveWordApi(data) // TODO @blue-syd:去掉 API 后缀 message.success(t('common.updateSuccess')) } modelVisible.value = false @@ -120,7 +121,7 @@ const resetForm = () => { formData.value = { id: undefined, name: '', - status: true, + status: CommonStatusEnum.ENABLE, description: '', tags: [] } diff --git a/src/views/system/sensitiveWord/index.vue b/src/views/system/sensitiveWord/index.vue index 0eb8287d..93ea29f1 100644 --- a/src/views/system/sensitiveWord/index.vue +++ b/src/views/system/sensitiveWord/index.vue @@ -40,7 +40,6 @@ :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" /> </el-form-item> - <el-form-item> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> @@ -82,9 +81,11 @@ :key="index" v-for="(tag, index) in scope.row.tags" :index="index" + class="mr-5px" > {{ tag }} </el-tag> + </template> </el-table-column> <el-table-column @@ -132,14 +133,13 @@ import { DICT_TYPE, getDictOptions } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' import * as SensitiveWordApi from '@/api/system/sensitiveWord' -import ConfigForm from './form.vue' +import ConfigForm from './form.vue' // TODO @blue-syd:组件名不对 const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 const list = ref([]) // 列表的数据 -const tags = ref([]) const queryParams = reactive({ pageNo: 1, pageSize: 10, @@ -150,12 +150,13 @@ const queryParams = reactive({ }) const queryFormRef = ref() // 搜索的表单 const exportLoading = ref(false) // 导出的加载中 +const tags = ref([]) /** 查询参数列表 */ const getList = async () => { loading.value = true try { - const data = await SensitiveWordApi.getSensitiveWordPageApi(queryParams) + const data = await SensitiveWordApi.getSensitiveWordPageApi(queryParams) // TODO @blue-syd:去掉 API 后缀哈 list.value = data.list total.value = data.total } finally { @@ -163,12 +164,6 @@ const getList = async () => { } } -/** 初始化标签select*/ -const getTags = async () => { - const data = await SensitiveWordApi.getSensitiveWordTagsApi() - tags.value = data -} - /** 搜索按钮操作 */ const handleQuery = () => { queryParams.pageNo = 1 @@ -187,6 +182,8 @@ const openModal = (type: string, id?: number) => { modalRef.value.openModal(type, id) } +// TODO @blue-syd:还少一个【测试】按钮的功能,参见 http://dashboard.yudao.iocoder.cn/system/sensitive-word + /** 删除按钮操作 */ const handleDelete = async (id: number) => { try { @@ -207,7 +204,7 @@ const handleExport = async () => { await message.exportConfirm() // 发起导出 exportLoading.value = true - const data = await SensitiveWordApi.exportSensitiveWordApi(queryParams) + const data = await SensitiveWordApi.exportSensitiveWordApi(queryParams) // TODO @blue-syd:去掉 API 后缀哈 download.excel(data, '敏感词.xls') } catch { } finally { @@ -215,6 +212,11 @@ const handleExport = async () => { } } +/** 获得 Tag 标签列表 */ +const getTags = async () => { + tags.value = await SensitiveWordApi.getSensitiveWordTagsApi() // TODO @blue-syd:去掉 API 后缀哈 +} + /** 初始化 **/ onMounted(() => { getTags() From 1846ba150ebbb98fa8e6db3b0439a5dcb7a93f20 Mon Sep 17 00:00:00 2001 From: admin <> Date: Sun, 19 Mar 2023 21:44:13 +0800 Subject: [PATCH 038/184] =?UTF-8?q?=E9=87=8D=E6=96=B0dept=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/dept/form.vue | 13 ++++--------- src/views/system/dept/index.vue | 21 +++++++++------------ 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/views/system/dept/form.vue b/src/views/system/dept/form.vue index fbbcab59..8d9405f3 100644 --- a/src/views/system/dept/form.vue +++ b/src/views/system/dept/form.vue @@ -33,7 +33,7 @@ style="width: 100%" > <el-option - v-for="item in props.userOption" + v-for="item in userList" :key="item.id" :label="item.nickname" :value="item.id" @@ -88,6 +88,7 @@ const formLoading = ref(false) // 表单的加载中:1)修改时的数据加 const formType = ref('') // 表单的类型:create - 新增;update - 修改 const formRef = ref() // 表单 Ref const deptOptions = ref() // 树形结构 +const userList = ref() // 负责人列表选项结构 const formData = ref({ id: undefined, @@ -100,12 +101,6 @@ const formData = ref({ email: undefined, status: undefined }) -const props = defineProps({ - userOption: { - type: Array, - default: () => [] as UserVO[] - } -}) const formRules = reactive({ parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }], @@ -118,12 +113,12 @@ const formRules = reactive({ }) /** 打开弹窗 */ -const openModal = async (type: string, id?: number) => { +const openModal = async (type: string, id?: number, userOption?: UserVO[]) => { + userList.value = userOption modelVisible.value = true modelTitle.value = t('action.' + type) formType.value = type resetForm() - console.log(id) // 修改时,设置数据 if (id) { formLoading.value = true diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue index bfd12256..d5f54d19 100644 --- a/src/views/system/dept/index.vue +++ b/src/views/system/dept/index.vue @@ -85,7 +85,7 @@ </ContentWrap> <!-- 添加或修改部门对话框 --> - <dept-form ref="modalRef" @success="getList" :userOption="userOption" /> + <dept-form ref="modalRef" @success="getList" /> </template> <script setup lang="ts" name="Dept"> import { handleTree } from '@/utils/tree' @@ -105,17 +105,14 @@ const queryParams = reactive({ pageSize: 100 }) -// 搜索的表单 -const queryFormRef = ref() -// 数据变量 -const deptDatas = ref() +const queryFormRef = ref() // 搜索的表单 +const deptDatas = ref() // 数据变量 const userOption = ref<UserVO[]>([]) -// 是否展开,默认全部展开 -const isExpandAll = ref(true) -// 重新渲染表格状态 -const refreshTable = ref(true) -// 列表的加载中 -const loading = ref(true) + +const isExpandAll = ref(true) // 是否展开,默认全部展开 +const refreshTable = ref(true) // 重新渲染表格状态 +const loading = ref(true) // 列表的加载中 + //获取用户列表 const getUserList = async () => { const res = await getListSimpleUsersApi() @@ -172,7 +169,7 @@ const resetQuery = () => { /** 添加/修改操作 */ const modalRef = ref() const openModal = (type: string, id?: number) => { - modalRef.value.openModal(type, id) + modalRef.value.openModal(type, id, userOption.value) } const userNicknameFormat = (row) => { From d034e9b2b51851e5f0497646ef4f7d5ee77d00ba Mon Sep 17 00:00:00 2001 From: admin <> Date: Sun, 19 Mar 2023 21:56:23 +0800 Subject: [PATCH 039/184] =?UTF-8?q?=E9=87=8D=E5=86=99dept?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- src/types/auto-components.d.ts | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0b05e6a6..ba12c396 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ dist-ssr *.local /dist* *-lock.* -pnpm-debug +pnpm-debug \ No newline at end of file diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index 8452fba6..be71517c 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -23,6 +23,8 @@ declare module '@vue/runtime-core' { DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default'] Echart: typeof import('./../components/Echart/src/Echart.vue')['default'] Editor: typeof import('./../components/Editor/src/Editor.vue')['default'] + ElAutoResizer: typeof import('element-plus/es')['ElAutoResizer'] + ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElBadge: typeof import('element-plus/es')['ElBadge'] ElButton: typeof import('element-plus/es')['ElButton'] ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] @@ -33,6 +35,7 @@ declare module '@vue/runtime-core' { ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] ElCollapseTransition: typeof import('element-plus/es')['ElCollapseTransition'] ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] + ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] ElDialog: typeof import('element-plus/es')['ElDialog'] @@ -51,6 +54,7 @@ declare module '@vue/runtime-core' { ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] + ElImage: typeof import('element-plus/es')['ElImage'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] @@ -58,16 +62,26 @@ declare module '@vue/runtime-core' { ElOption: typeof import('element-plus/es')['ElOption'] ElPagination: typeof import('element-plus/es')['ElPagination'] ElPopover: typeof import('element-plus/es')['ElPopover'] + ElRadio: typeof import('element-plus/es')['ElRadio'] + ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] + ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRow: typeof import('element-plus/es')['ElRow'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElSelect: typeof import('element-plus/es')['ElSelect'] ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] + ElSpace: typeof import('element-plus/es')['ElSpace'] ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElTable: typeof import('element-plus/es')['ElTable'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] + ElTableV2: typeof import('element-plus/es')['ElTableV2'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] + ElTag: typeof import('element-plus/es')['ElTag'] + ElTimeline: typeof import('element-plus/es')['ElTimeline'] + ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] + ElTransfer: typeof import('element-plus/es')['ElTransfer'] + ElTree: typeof import('element-plus/es')['ElTree'] ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElUpload: typeof import('element-plus/es')['ElUpload'] Error: typeof import('./../components/Error/src/Error.vue')['default'] From 40bd2c57f8088962a968a13c891e7e5ea5b87632 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 19 Mar 2023 22:14:55 +0800 Subject: [PATCH 040/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9ARevie?= =?UTF-8?q?w=20=E5=AD=97=E5=85=B8=E6=95=B0=E6=8D=AE=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/dict/dict.data.ts | 29 +++++--- src/api/system/dict/dict.type.ts | 24 ++++--- src/api/system/dict/types.ts | 50 -------------- src/store/modules/dict.ts | 6 +- .../codegen/components/CloumInfoForm.vue | 4 +- src/views/system/dict/data.form.vue | 25 +++---- src/views/system/dict/data.vue | 67 ++++++++++--------- src/views/system/dict/form.vue | 15 +++-- src/views/system/dict/index.vue | 55 ++++++++------- 9 files changed, 128 insertions(+), 147 deletions(-) delete mode 100644 src/api/system/dict/types.ts diff --git a/src/api/system/dict/dict.data.ts b/src/api/system/dict/dict.data.ts index fd97a2dc..6d981326 100644 --- a/src/api/system/dict/dict.data.ts +++ b/src/api/system/dict/dict.data.ts @@ -1,36 +1,49 @@ import request from '@/config/axios' -import type { DictDataVO, DictDataPageReqVO, DictDataExportReqVO } from './types' + +export type DictDataVO = { + id: number | undefined + sort: number | undefined + label: string + value: string + dictType: string + status: number + colorType: string + cssClass: string + remark: string + createTime: Date +} // 查询字典数据(精简)列表 -export const listSimpleDictDataApi = () => { +export const listSimpleDictData = () => { return request.get({ url: '/system/dict-data/list-all-simple' }) } // 查询字典数据列表 -export const getDictDataPageApi = (params: DictDataPageReqVO) => { +export const getDictDataPage = (params: PageParam) => { return request.get({ url: '/system/dict-data/page', params }) } // 查询字典数据详情 -export const getDictDataApi = (id: number) => { +export const getDictData = (id: number) => { return request.get({ url: '/system/dict-data/get?id=' + id }) } // 新增字典数据 -export const createDictDataApi = (data: DictDataVO) => { +export const createDictData = (data: DictDataVO) => { return request.post({ url: '/system/dict-data/create', data }) } // 修改字典数据 -export const updateDictDataApi = (data: DictDataVO) => { +export const updateDictData = (data: DictDataVO) => { return request.put({ url: '/system/dict-data/update', data }) } // 删除字典数据 -export const deleteDictDataApi = (id: number) => { +export const deleteDictData = (id: number) => { return request.delete({ url: '/system/dict-data/delete?id=' + id }) } + // 导出字典类型数据 -export const exportDictDataApi = (params: DictDataExportReqVO) => { +export const exportDictDataApi = (params) => { return request.get({ url: '/system/dict-data/export', params }) } diff --git a/src/api/system/dict/dict.type.ts b/src/api/system/dict/dict.type.ts index 22e6411e..0d9faeb2 100644 --- a/src/api/system/dict/dict.type.ts +++ b/src/api/system/dict/dict.type.ts @@ -1,36 +1,44 @@ import request from '@/config/axios' -import type { DictTypeVO, DictTypePageReqVO, DictTypeExportReqVO } from './types' + +export type DictTypeVO = { + id: number | undefined + name: string + type: string + status: number + remark: string + createTime: Date +} // 查询字典(精简)列表 -export const listSimpleDictTypeApi = () => { +export const listSimpleDictType = () => { return request.get({ url: '/system/dict-type/list-all-simple' }) } // 查询字典列表 -export const getDictTypePageApi = (params: DictTypePageReqVO) => { +export const getDictTypePage = (params: PageParam) => { return request.get({ url: '/system/dict-type/page', params }) } // 查询字典详情 -export const getDictTypeApi = (id: number) => { +export const getDictType = (id: number) => { return request.get({ url: '/system/dict-type/get?id=' + id }) } // 新增字典 -export const createDictTypeApi = (data: DictTypeVO) => { +export const createDictType = (data: DictTypeVO) => { return request.post({ url: '/system/dict-type/create', data }) } // 修改字典 -export const updateDictTypeApi = (data: DictTypeVO) => { +export const updateDictType = (data: DictTypeVO) => { return request.put({ url: '/system/dict-type/update', data }) } // 删除字典 -export const deleteDictTypeApi = (id: number) => { +export const deleteDictType = (id: number) => { return request.delete({ url: '/system/dict-type/delete?id=' + id }) } // 导出字典类型 -export const exportDictTypeApi = (params: DictTypeExportReqVO) => { +export const exportDictType = (params) => { return request.get({ url: '/system/dict-type/export', params }) } diff --git a/src/api/system/dict/types.ts b/src/api/system/dict/types.ts deleted file mode 100644 index 3421cd23..00000000 --- a/src/api/system/dict/types.ts +++ /dev/null @@ -1,50 +0,0 @@ -export type DictTypeVO = { - id: number | undefined - name: string - type: string - status: number | undefined - remark: string - createTime: Date -} - -export type DictTypePageReqVO = { - pageNo: number - pageSize: number - name: string - type: string - status: number | undefined - createTime: Date[] -} - -export type DictTypeExportReqVO = { - name: string - type: string - status: number - createTime: Date[] -} - -export type DictDataVO = { - id: number | undefined - sort: number | undefined - label: string - value: string - dictType: string - status: number - colorType: string - cssClass: string - remark: string - createTime: Date | undefined -} -export type DictDataPageReqVO = { - pageNo: number - pageSize: number - label: string - dictType: string - status: number | undefined -} - -export type DictDataExportReqVO = { - label: string - dictType: string - status: number -} diff --git a/src/store/modules/dict.ts b/src/store/modules/dict.ts index 4e9ab39c..2d393e43 100644 --- a/src/store/modules/dict.ts +++ b/src/store/modules/dict.ts @@ -3,7 +3,7 @@ import { store } from '../index' import { DictDataVO } from '@/api/system/dict/types' import { CACHE_KEY, useCache } from '@/hooks/web/useCache' const { wsCache } = useCache('sessionStorage') -import { listSimpleDictDataApi } from '@/api/system/dict/dict.data' +import { listSimpleDictData } from '@/api/system/dict/dict.data' export interface DictValueType { value: any @@ -44,7 +44,7 @@ export const useDictStore = defineStore('dict', { this.dictMap = dictMap this.isSetDict = true } else { - const res = await listSimpleDictDataApi() + const res = await listSimpleDictData() // 设置数据 const dictDataMap = new Map<string, any>() res.forEach((dictData: DictDataVO) => { @@ -74,7 +74,7 @@ export const useDictStore = defineStore('dict', { }, async resetDict() { wsCache.delete(CACHE_KEY.DICT_CACHE) - const res = await listSimpleDictDataApi() + const res = await listSimpleDictData() // 设置数据 const dictDataMap = new Map<string, any>() res.forEach((dictData: DictDataVO) => { diff --git a/src/views/infra/codegen/components/CloumInfoForm.vue b/src/views/infra/codegen/components/CloumInfoForm.vue index 0b6ea697..5a60c546 100644 --- a/src/views/infra/codegen/components/CloumInfoForm.vue +++ b/src/views/infra/codegen/components/CloumInfoForm.vue @@ -114,7 +114,7 @@ import { PropType } from 'vue' import { DictTypeVO } from '@/api/system/dict/types' import { CodegenColumnVO } from '@/api/infra/codegen/types' -import { listSimpleDictTypeApi } from '@/api/system/dict/dict.type' +import { listSimpleDictType } from '@/api/system/dict/dict.type' const props = defineProps({ info: { @@ -125,7 +125,7 @@ const props = defineProps({ /** 查询字典下拉列表 */ const dictOptions = ref<DictTypeVO[]>() const getDictOptions = async () => { - const res = await listSimpleDictTypeApi() + const res = await listSimpleDictType() dictOptions.value = res } onMounted(async () => { diff --git a/src/views/system/dict/data.form.vue b/src/views/system/dict/data.form.vue index 5c85b946..c0b70b8e 100644 --- a/src/views/system/dict/data.form.vue +++ b/src/views/system/dict/data.form.vue @@ -17,7 +17,6 @@ <el-form-item label="数据标签" prop="label"> <el-input v-model="formData.label" placeholder="请输入数据标签" /> </el-form-item> - <el-form-item label="数据键值" prop="value"> <el-input v-model="formData.value" placeholder="请输入数据键值" /> </el-form-item> @@ -61,9 +60,8 @@ </template> <script setup lang="ts"> import { DICT_TYPE, getDictOptions } from '@/utils/dict' - import * as DictDataApi from '@/api/system/dict/dict.data' -import { DictDataVO } from '@/api/system/dict/types' +import { CommonStatusEnum } from '@/utils/constants' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -73,15 +71,14 @@ const formLoading = ref(false) // 表单的加载中:1)修改时的数据加 const formType = ref('') // 表单的类型:create - 新增;update - 修改 const formData = ref({ id: undefined, - sort: 0, + sort: undefined, label: '', value: '', dictType: '', - status: 0, + status: CommonStatusEnum.ENABLE, colorType: '', cssClass: '', - remark: '', - createTime: undefined + remark: '' }) const formRules = reactive({ label: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }], @@ -91,7 +88,6 @@ const formRules = reactive({ const formRef = ref() // 表单 Ref // 数据标签回显样式 - const colorTypeOptions = readonly([ { value: 'default', @@ -129,7 +125,7 @@ const openModal = async (type: string, id?: number) => { if (id) { formLoading.value = true try { - formData.value = await DictDataApi.getDictDataApi(id) + formData.value = await DictDataApi.getDictData(id) } finally { formLoading.value = false } @@ -147,12 +143,12 @@ const submitForm = async () => { // 提交请求 formLoading.value = true try { - const data = formData.value as DictDataVO + const data = formData.value as DictDataApi.DictDataVO if (formType.value === 'create') { - await DictDataApi.createDictDataApi(data) + await DictDataApi.createDictData(data) message.success(t('common.createSuccess')) } else { - await DictDataApi.updateDictDataApi(data) + await DictDataApi.updateDictData(data) message.success(t('common.updateSuccess')) } modelVisible.value = false @@ -171,11 +167,10 @@ const resetForm = () => { label: '', value: '', dictType: '', - status: 0, + status: CommonStatusEnum.ENABLE, colorType: '', cssClass: '', - remark: '', - createTime: undefined + remark: '' } formRef.value?.resetFields() } diff --git a/src/views/system/dict/data.vue b/src/views/system/dict/data.vue index 2c1b373a..e45dcfec 100644 --- a/src/views/system/dict/data.vue +++ b/src/views/system/dict/data.vue @@ -1,6 +1,12 @@ <template> <content-wrap> - <el-form :model="queryParams" ref="queryFormRef" size="small" :inline="true" label-width="68px"> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > <el-form-item label="字典名称" prop="dictType"> <el-select v-model="queryParams.dictType"> <el-option @@ -46,7 +52,10 @@ </el-button> </el-form-item> </el-form> - <!-- 列表 --> + </content-wrap> + + <!-- 列表 --> + <content-wrap> <el-table v-loading="loading" :data="list"> <el-table-column label="字典编码" align="center" prop="id" /> <el-table-column label="字典标签" align="center" prop="label" /> @@ -59,12 +68,7 @@ </el-table-column> <el-table-column label="颜色类型" align="center" prop="colorType" /> <el-table-column label="CSS Class" align="center" prop="cssClass" /> - <el-table-column - label="备注" - align="center" - prop="remark" - :show-overflow-tooltip="tableTooltipConfig" - /> + <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip /> <el-table-column label="创建时间" align="center" @@ -72,21 +76,28 @@ width="180" :formatter="dateFormatter" /> - <el-table-column label="操作" align="center"> <template #default="scope"> <el-button link + type="primary" @click="openModal('update', scope.row.id)" v-hasPermi="['system:dict:update']" - ><Icon icon="ic:outline-mode" class="mr-5px" />修改</el-button > - <el-button link @click="handleDelete(scope.row.id)" v-hasPermi="['system:dict:delete']" - ><Icon icon="material-symbols:delete-forever-sharp" class="mr-5px" />删除</el-button + 修改 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['system:dict:delete']" > + 删除 + </el-button> </template> </el-table-column> </el-table> + <!-- 分页 --> <Pagination :total="total" v-model:page="queryParams.pageNo" @@ -94,30 +105,24 @@ @pagination="getList" /> </content-wrap> + <!-- 表单弹窗:添加/修改 --> <data-form ref="modalRef" @success="getList" /> </template> - <script setup lang="ts" name="Data"> import * as DictDataApi from '@/api/system/dict/dict.data' -import { listSimpleDictTypeApi } from '@/api/system/dict/dict.type' +import * as DictTypeApi from '@/api/system/dict/dict.data' import { getDictOptions, DICT_TYPE } from '@/utils/dict' import download from '@/utils/download' import { dateFormatter } from '@/utils/formatTime' import DataForm from './data.form.vue' -import { DictTypeVO } from '@/api/system/dict/types' -import { useRoute } from 'vue-router' +import type { DictTypeVO } from '@/api/system/dict/dict.type' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 - -const route = useRoute() +const route = useRoute() // 路由 const simpleDictList = ref<DictTypeVO[]>() -const tableTooltipConfig = readonly({ - appendTo: 'body' -}) - const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 const list = ref([]) // 列表的数据 @@ -135,18 +140,13 @@ const exportLoading = ref(false) // 导出的加载中 const getList = async () => { loading.value = true try { - const data = await DictDataApi.getDictDataPageApi(queryParams) + const data = await DictDataApi.getDictDataPage(queryParams) list.value = data.list total.value = data.total } finally { loading.value = false } } -// 查询字典(精简)列表 -const getSimpleDictList = async () => { - const data = await listSimpleDictTypeApi() - simpleDictList.value = data -} /** 搜索按钮操作 */ const handleQuery = () => { @@ -172,7 +172,7 @@ const handleDelete = async (id: number) => { // 删除的二次确认 await message.delConfirm() // 发起删除 - await DictDataApi.deleteDictDataApi(id) + await DictDataApi.deleteDictData(id) message.success(t('common.delSuccess')) // 刷新列表 await getList() @@ -194,10 +194,15 @@ const handleExport = async () => { } } +/** 查询字典(精简)列表 */ +const getSimpleDictList = async () => { + simpleDictList.value = await DictTypeApi.listSimpleDictData() +} + /** 初始化 **/ onMounted(() => { getList() + // 查询字典(精简)列表 + getSimpleDictList() }) -// 查询字典(精简)列表 -getSimpleDictList() </script> diff --git a/src/views/system/dict/form.vue b/src/views/system/dict/form.vue index 28b42c9d..af02c597 100644 --- a/src/views/system/dict/form.vue +++ b/src/views/system/dict/form.vue @@ -43,7 +43,8 @@ <script setup lang="ts"> import { DICT_TYPE, getDictOptions } from '@/utils/dict' import * as DictTypeApi from '@/api/system/dict/dict.type' -import { DictTypeVO } from '@/api/system/dict/types' +import { CommonStatusEnum } from '@/utils/constants' + const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -55,7 +56,7 @@ const formData = ref({ id: undefined, name: '', type: '', - status: 0, + status: CommonStatusEnum.ENABLE, remark: '' }) const formRules = reactive({ @@ -74,7 +75,7 @@ const openModal = async (type: string, id?: number) => { if (id) { formLoading.value = true try { - formData.value = await DictTypeApi.getDictTypeApi(id) + formData.value = await DictTypeApi.getDictType(id) } finally { formLoading.value = false } @@ -92,12 +93,12 @@ const submitForm = async () => { // 提交请求 formLoading.value = true try { - const data = formData.value as DictTypeVO + const data = formData.value as DictTypeApi.DictTypeVO if (formType.value === 'create') { - await DictTypeApi.createDictTypeApi(data) + await DictTypeApi.createDictType(data) message.success(t('common.createSuccess')) } else { - await DictTypeApi.updateDictTypeApi(data) + await DictTypeApi.updateDictType(data) message.success(t('common.updateSuccess')) } modelVisible.value = false @@ -114,7 +115,7 @@ const resetForm = () => { id: undefined, type: '', name: '', - status: undefined, + status: CommonStatusEnum.ENABLE, remark: '' } formRef.value?.resetFields() diff --git a/src/views/system/dict/index.vue b/src/views/system/dict/index.vue index 367cc9d4..a029d2ee 100644 --- a/src/views/system/dict/index.vue +++ b/src/views/system/dict/index.vue @@ -1,6 +1,13 @@ <template> + <!-- 搜索工作栏 --> <content-wrap> - <el-form :model="queryParams" ref="queryFormRef" size="small" :inline="true" label-width="68px"> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > <el-form-item label="字典名称" prop="name"> <el-input v-model="queryParams.name" @@ -63,20 +70,14 @@ </el-button> </el-form-item> </el-form> + </content-wrap> - <!-- 列表 --> + <!-- 列表 --> + <content-wrap> <el-table v-loading="loading" :data="list"> <el-table-column label="字典编号" align="center" prop="id" /> <el-table-column label="字典名称" align="center" prop="name" show-overflow-tooltip /> - <el-table-column label="字典类型" align="center" show-overflow-tooltip> - <template #default="scope"> - <div> - <router-link :to="'/dict/type/data/' + scope.row.type"> - <span>{{ scope.row.type }}</span> - </router-link> - </div> - </template> - </el-table-column> + <el-table-column label="字典类型" align="center" prop="type" width="300" /> <el-table-column label="状态" align="center" prop="status"> <template #default="scope"> <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> @@ -90,22 +91,31 @@ prop="createTime" width="180" /> - <el-table-column label="操作" align="center"> <template #default="scope"> <el-button link + type="primary" @click="openModal('update', scope.row.id)" v-hasPermi="['system:dict:update']" - ><Icon icon="ic:outline-mode" class="mr-5px" />修改</el-button > - <el-button link @click="handleDelete(scope.row.id)" v-hasPermi="['system:dict:delete']" - ><Icon icon="material-symbols:delete-forever-sharp" class="mr-5px" />删除</el-button + 修改 + </el-button> + <router-link :to="'/dict/type/data/' + scope.row.type"> + <el-button link type="primary">数据</el-button> + </router-link> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['system:dict:delete']" > + 删除 + </el-button> </template> </el-table-column> </el-table> - + <!-- 分页 --> <Pagination :total="total" v-model:page="queryParams.pageNo" @@ -113,25 +123,24 @@ @pagination="getList" /> </content-wrap> + <!-- 表单弹窗:添加/修改 --> <dict-type-form ref="modalRef" @success="getList" /> </template> <script setup lang="ts" name="Dict"> -import * as DictTypeApi from '@/api/system/dict/dict.type' import { getDictOptions, DICT_TYPE } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' +import * as DictTypeApi from '@/api/system/dict/dict.type' import DictTypeForm from './form.vue' -import { DictTypePageReqVO } from '@/api/system/dict/types' import download from '@/utils/download' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 - const list = ref([]) // 字典表格数据 -const queryParams = reactive<DictTypePageReqVO>({ +const queryParams = reactive({ pageNo: 1, pageSize: 10, name: '', @@ -146,7 +155,7 @@ const exportLoading = ref(false) // 导出的加载中 const getList = async () => { loading.value = true try { - const data = await DictTypeApi.getDictTypePageApi(queryParams) + const data = await DictTypeApi.getDictTypePage(queryParams) list.value = data.list total.value = data.total } finally { @@ -178,7 +187,7 @@ const handleDelete = async (id: number) => { // 删除的二次确认 await message.delConfirm() // 发起删除 - await DictTypeApi.deleteDictTypeApi(id) + await DictTypeApi.deleteDictType(id) message.success(t('common.delSuccess')) // 刷新列表 await getList() @@ -192,7 +201,7 @@ const handleExport = async () => { await message.exportConfirm() // 发起导出 exportLoading.value = true - const data = await DictTypeApi.exportDictTypeApi(queryParams) + const data = await DictTypeApi.exportDictType(queryParams) download.excel(data, '字典类型.xls') } catch { } finally { From 030ecd3137f626854e3c4c4fc713e297ccbd5f41 Mon Sep 17 00:00:00 2001 From: zhangjl <zhangjinlong1211@126.com> Date: Sun, 19 Mar 2023 23:20:49 +0800 Subject: [PATCH 041/184] =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9A=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=A0=81=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/errorCode/index.ts | 4 +- src/views/system/errorCode/errorCode.data.ts | 54 ---- src/views/system/errorCode/form.vue | 114 +++++++ src/views/system/errorCode/index.vue | 321 +++++++++++-------- 4 files changed, 312 insertions(+), 181 deletions(-) delete mode 100644 src/views/system/errorCode/errorCode.data.ts create mode 100644 src/views/system/errorCode/form.vue diff --git a/src/api/system/errorCode/index.ts b/src/api/system/errorCode/index.ts index 0e000794..65eabd3a 100644 --- a/src/api/system/errorCode/index.ts +++ b/src/api/system/errorCode/index.ts @@ -1,10 +1,10 @@ import request from '@/config/axios' export interface ErrorCodeVO { - id: number + id: number | undefined type: number applicationName: string - code: number + code: number | undefined message: string memo: string createTime: Date diff --git a/src/views/system/errorCode/errorCode.data.ts b/src/views/system/errorCode/errorCode.data.ts deleted file mode 100644 index 4736068b..00000000 --- a/src/views/system/errorCode/errorCode.data.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -const { t } = useI18n() // 国际化 - -// 表单校验 -export const rules = reactive({ - applicationName: [required], - code: [required], - message: [required] -}) - -// 新增 + 修改 -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'id', - primaryTitle: '编号', - action: true, - columns: [ - { - title: '错误码类型', - field: 'type', - dictType: DICT_TYPE.SYSTEM_ERROR_CODE_TYPE, - dictClass: 'number', - isSearch: true - }, - { - title: '应用名', - field: 'applicationName', - isSearch: true - }, - { - title: '错误码编码', - field: 'code', - isSearch: true - }, - { - title: '错误码错误提示', - field: 'message', - isSearch: true - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) diff --git a/src/views/system/errorCode/form.vue b/src/views/system/errorCode/form.vue new file mode 100644 index 00000000..5e6a0bc8 --- /dev/null +++ b/src/views/system/errorCode/form.vue @@ -0,0 +1,114 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="100px" + v-loading="formLoading" + > + <el-form-item label="应用名" prop="applicationName"> + <el-input v-model="formData.applicationName" placeholder="请输入应用名" clearable /> + </el-form-item> + <el-form-item label="错误码编码" prop="code"> + <el-input v-model="formData.code" placeholder="请输入错误码编码" clearable /> + </el-form-item> + <el-form-item label="错误码提示" prop="message"> + <el-input v-model="formData.message" placeholder="请输入错误码提示" clearable /> + </el-form-item> + <el-form-item label="备注" prop="memo"> + <el-input v-model="formData.memo" placeholder="请输入备注" clearable /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import * as ErrorCodeApi from '@/api/system/errorCode' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +// 表单参数 +const formData = ref({ + id: undefined, + code: undefined, + applicationName: '', + message: '', + memo: '' +}) +// 表单校验 +const formRules = reactive({ + applicationName: [{ required: true, message: '应用名不能为空', trigger: 'blur' }], + code: [{ required: true, message: '错误码编码不能为空', trigger: 'blur' }], + message: [{ required: true, message: '错误码提示不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await ErrorCodeApi.getErrorCodeApi(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 + +/** 提交按钮 */ +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as ErrorCodeApi.ErrorCodeVO + if (formType.value === 'create') { + await ErrorCodeApi.createErrorCodeApi(data) + message.success(t('common.createSuccess')) + } else { + await ErrorCodeApi.updateErrorCodeApi(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 表单重置 */ +const resetForm = () => { + formData.value = { + id: undefined, + applicationName: '', + code: undefined, + message: '', + memo: '' + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/system/errorCode/index.vue b/src/views/system/errorCode/index.vue index 1ff3d103..8f7dac33 100644 --- a/src/views/system/errorCode/index.vue +++ b/src/views/system/errorCode/index.vue @@ -1,145 +1,216 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <!-- 操作:新增 --> - <template #toolbar_buttons> - <XButton + <!-- 搜索工作栏 --> + <content-wrap> + <el-form :model="queryParams" ref="queryFormRef" class="-mb-15px" :inline="true"> + <el-form-item label="错误码类型" prop="type"> + <el-select v-model="queryParams.type" placeholder="请选择错误码类型" clearable> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_ERROR_CODE_TYPE)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="应用名" prop="applicationName"> + <el-input + v-model="queryParams.applicationName" + placeholder="请输入应用名" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="错误码编码" prop="code"> + <el-input + v-model="queryParams.code" + placeholder="请输入错误码编码" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="错误码提示" prop="message"> + <el-input + v-model="queryParams.message" + placeholder="请输入错误码提示" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + value-format="YYYY-MM-DD HH:mm:ss" + type="daterange" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" + @click="openModal('create')" v-hasPermi="['system:error-code:create']" - @click="handleCreate()" - /> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:修改 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['system:error-code:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['system:error-code:query']" - @click="handleDetail(row.id)" - /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['system:error-code:delete']" - @click="deleteData(row.id)" - /> - </template> - </XTable> - </ContentWrap> - <!-- 弹窗 --> - <XModal id="errorCodeModel" v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(添加 / 修改) --> - <Form - v-if="['create', 'update'].includes(actionType)" - :schema="allSchemas.formSchema" - :rules="rules" - ref="formRef" - /> - <!-- 对话框(详情) --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - /> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="actionLoading" - @click="submitForm()" + > + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['system:error-code:export']" + > + <Icon icon="ep:download" class="mr-5px" /> 导出 + </el-button> + </el-form-item> + </el-form> + </content-wrap> + <!-- 列表 --> + <content-wrap> + <el-table v-loading="loading" :data="list"> + <el-table-column label="编号" align="center" prop="id" /> + <el-table-column label="类型" align="center" prop="type" width="80"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.SYSTEM_ERROR_CODE_TYPE" :value="scope.row.type" /> + </template> + </el-table-column> + <el-table-column label="应用名" align="center" prop="applicationName" width="200" /> + <el-table-column label="错误码编码" align="center" prop="code" width="120" /> + <el-table-column label="错误码提示" align="center" prop="message" width="300" /> + <el-table-column label="备注" align="center" prop="memo" width="200" /> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" /> - <!-- 按钮:关闭 --> - <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openModal('update', scope.row.id)" + v-hasPermi="['system:error-code:update']" + > + 编辑 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['system:error-code:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页组件 --> + <pagination + v-show="total > 0" + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + <!-- 表单弹窗:添加/修改 --> + <error-code-form ref="modalRef" @success="getList" /> </template> + <script setup lang="ts" name="ErrorCode"> -import type { FormExpose } from '@/components/Form' -// 业务相关的 import -import { rules, allSchemas } from './errorCode.data' import * as ErrorCodeApi from '@/api/system/errorCode' - -const { t } = useI18n() // 国际化 +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' +import ErrorCodeForm from './form.vue' +import download from '@/utils/download' const message = useMessage() // 消息弹窗 -// 列表相关的变量 -const [registerTable, { reload, deleteData }] = useXTable({ - allSchemas: allSchemas, - getListApi: ErrorCodeApi.getErrorCodePageApi, - deleteApi: ErrorCodeApi.deleteErrorCodeApi +const { t } = useI18n() // 国际化 + +// 遮罩层 +const loading = ref(true) +// 导出遮罩层 +const exportLoading = ref(false) +// 总条数 +const total = ref(0) +// 错误码列表 +const list = ref([]) +// 查询参数 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + type: undefined, + applicationName: undefined, + code: undefined, + message: undefined, + createTime: [] }) -// 弹窗相关的变量 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const actionType = ref('') // 操作按钮的类型 -const actionLoading = ref(false) // 按钮 Loading -const formRef = ref<FormExpose>() // 表单 Ref -const detailData = ref() // 详情 Ref +// 搜索的表单 +const queryFormRef = ref() -// 设置标题 -const setDialogTile = (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true +/** 查询列表 */ +const getList = async () => { + loading.value = true + // 执行查询 + try { + const data = await ErrorCodeApi.getErrorCodePageApi(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } } -// 新增操作 -const handleCreate = () => { - setDialogTile('create') +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() } -// 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - // 设置数据 - const res = await ErrorCodeApi.getErrorCodeApi(rowId) - unref(formRef)?.setValues(res) +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() } -// 详情操作 -const handleDetail = async (rowId: number) => { - setDialogTile('detail') - // 设置数据 - const res = await ErrorCodeApi.getErrorCodeApi(rowId) - detailData.value = res +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, id) } -// 提交新增/修改的表单 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - actionLoading.value = true - // 提交请求 - try { - const data = unref(formRef)?.formModel as ErrorCodeApi.ErrorCodeVO - if (actionType.value === 'create') { - await ErrorCodeApi.createErrorCodeApi(data) - message.success(t('common.createSuccess')) - } else { - await ErrorCodeApi.updateErrorCodeApi(data) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false - } finally { - actionLoading.value = false - // 刷新列表 - await reload() - } - } - }) +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + await ErrorCodeApi.deleteErrorCodeApi(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} } +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await ErrorCodeApi.excelErrorCodeApi(queryParams) + download.excel(data, '参数配置.xls') + } catch { + } finally { + exportLoading.value = false + } +} + +/** 初始化 **/ +onMounted(() => { + getList() +}) </script> From c40de0330225e1ef81c6c9644c57499773258eda Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Mon, 20 Mar 2023 00:20:24 +0800 Subject: [PATCH 042/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=20form=20=E7=9A=84=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/infra/config/index.vue | 18 +++++++++++++-- src/views/system/dict/data.vue | 24 ++++++++------------ src/views/system/dict/index.vue | 13 ++++------- src/views/system/notice/index.vue | 16 ++++++++++++-- src/views/system/oauth2/token/index.vue | 17 +++++++++++++-- src/views/system/operatelog/index.vue | 29 ++++++++++++++++++++----- 6 files changed, 82 insertions(+), 35 deletions(-) diff --git a/src/views/infra/config/index.vue b/src/views/infra/config/index.vue index 11fc8fe0..fcc3c12c 100644 --- a/src/views/infra/config/index.vue +++ b/src/views/infra/config/index.vue @@ -1,13 +1,20 @@ <template> <!-- 搜索 --> <content-wrap> - <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true"> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > <el-form-item label="参数名称" prop="name"> <el-input v-model="queryParams.name" placeholder="请输入参数名称" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="参数键名" prop="key"> @@ -16,10 +23,16 @@ placeholder="请输入参数键名" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="系统内置" prop="type"> - <el-select v-model="queryParams.type" placeholder="请选择系统内置" clearable> + <el-select + v-model="queryParams.type" + placeholder="请选择系统内置" + clearable + class="!w-240px" + > <el-option v-for="dict in getDictOptions(DICT_TYPE.INFRA_CONFIG_TYPE)" :key="parseInt(dict.value)" @@ -36,6 +49,7 @@ start-placeholder="开始日期" end-placeholder="结束日期" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="!w-240px" /> </el-form-item> <el-form-item> diff --git a/src/views/system/dict/data.vue b/src/views/system/dict/data.vue index e45dcfec..7b159481 100644 --- a/src/views/system/dict/data.vue +++ b/src/views/system/dict/data.vue @@ -8,13 +8,8 @@ label-width="68px" > <el-form-item label="字典名称" prop="dictType"> - <el-select v-model="queryParams.dictType"> - <el-option - v-for="item in simpleDictList" - :key="item.id" - :label="item.name" - :value="item.type" - /> + <el-select v-model="queryParams.dictType" class="!w-240px"> + <el-option v-for="item in dicts" :key="item.type" :label="item.name" :value="item.type" /> </el-select> </el-form-item> <el-form-item label="字典标签" prop="label"> @@ -23,10 +18,11 @@ placeholder="请输入字典标签" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="状态" prop="status"> - <el-select v-model="queryParams.status" placeholder="数据状态" clearable> + <el-select v-model="queryParams.status" placeholder="数据状态" clearable class="!w-240px"> <el-option v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" @@ -111,18 +107,15 @@ </template> <script setup lang="ts" name="Data"> import * as DictDataApi from '@/api/system/dict/dict.data' -import * as DictTypeApi from '@/api/system/dict/dict.data' +import * as DictTypeApi from '@/api/system/dict/dict.type' import { getDictOptions, DICT_TYPE } from '@/utils/dict' import download from '@/utils/download' import { dateFormatter } from '@/utils/formatTime' import DataForm from './data.form.vue' -import type { DictTypeVO } from '@/api/system/dict/dict.type' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 const route = useRoute() // 路由 -const simpleDictList = ref<DictTypeVO[]>() - const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 const list = ref([]) // 列表的数据 @@ -135,6 +128,7 @@ const queryParams = reactive({ }) const queryFormRef = ref() // 搜索的表单 const exportLoading = ref(false) // 导出的加载中 +const dicts = ref<DictTypeApi.DictTypeVO[]>() // 字典类型的列表 /** 查询参数列表 */ const getList = async () => { @@ -195,14 +189,14 @@ const handleExport = async () => { } /** 查询字典(精简)列表 */ -const getSimpleDictList = async () => { - simpleDictList.value = await DictTypeApi.listSimpleDictData() +const getDictList = async () => { + dicts.value = await DictTypeApi.listSimpleDictType() } /** 初始化 **/ onMounted(() => { getList() // 查询字典(精简)列表 - getSimpleDictList() + getDictList() }) </script> diff --git a/src/views/system/dict/index.vue b/src/views/system/dict/index.vue index a029d2ee..4e304bf5 100644 --- a/src/views/system/dict/index.vue +++ b/src/views/system/dict/index.vue @@ -13,8 +13,8 @@ v-model="queryParams.name" placeholder="请输入字典名称" clearable - style="width: 240px" @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="字典类型" prop="type"> @@ -22,17 +22,12 @@ v-model="queryParams.type" placeholder="请输入字典类型" clearable - style="width: 240px" @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="状态" prop="status"> - <el-select - v-model="queryParams.status" - placeholder="字典状态" - clearable - style="width: 240px" - > + <el-select v-model="queryParams.status" placeholder="字典状态" clearable class="!w-240px"> <el-option v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" :key="parseInt(dict.value)" @@ -44,13 +39,13 @@ <el-form-item label="创建时间" prop="createTime"> <el-date-picker v-model="queryParams.createTime" - style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="!w-240px" /> </el-form-item> <el-form-item> diff --git a/src/views/system/notice/index.vue b/src/views/system/notice/index.vue index e36f3aac..65076f9f 100644 --- a/src/views/system/notice/index.vue +++ b/src/views/system/notice/index.vue @@ -1,17 +1,29 @@ <template> <content-wrap> <!-- 搜索工作栏 --> - <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true"> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > <el-form-item label="公告标题" prop="title"> <el-input v-model="queryParams.title" placeholder="请输入公告标题" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="公告状态" prop="status"> - <el-select v-model="queryParams.status" placeholder="请选择公告状态" clearable> + <el-select + v-model="queryParams.status" + placeholder="请选择公告状态" + clearable + class="!w-240px" + > <el-option v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" :key="parseInt(dict.value)" diff --git a/src/views/system/oauth2/token/index.vue b/src/views/system/oauth2/token/index.vue index e8c730e0..7e8aca22 100644 --- a/src/views/system/oauth2/token/index.vue +++ b/src/views/system/oauth2/token/index.vue @@ -1,17 +1,29 @@ <template> <content-wrap> <!-- 搜索工作栏 --> - <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true"> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="90px" + > <el-form-item label="用户编号" prop="userId"> <el-input v-model="queryParams.userId" placeholder="请输入用户编号" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="用户类型" prop="userType"> - <el-select v-model="queryParams.userType" placeholder="请选择用户类型" clearable> + <el-select + v-model="queryParams.userType" + placeholder="请选择用户类型" + clearable + class="!w-240px" + > <el-option v-for="dict in getIntDictOptions(DICT_TYPE.USER_TYPE)" :key="dict.value" @@ -26,6 +38,7 @@ placeholder="请输入客户端编号" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item> diff --git a/src/views/system/operatelog/index.vue b/src/views/system/operatelog/index.vue index f9667912..d7490ba8 100644 --- a/src/views/system/operatelog/index.vue +++ b/src/views/system/operatelog/index.vue @@ -1,13 +1,20 @@ <template> <content-wrap> <!-- 搜索工作栏 --> - <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true"> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > <el-form-item label="系统模块" prop="module"> <el-input v-model="queryParams.module" placeholder="请输入系统模块" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="操作人员" prop="userNickname"> @@ -16,10 +23,16 @@ placeholder="请输入操作人员" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> - <el-form-item label="类型" prop="type"> - <el-select v-model="queryParams.type" placeholder="操作类型" clearable> + <el-form-item label="操作类型" prop="type"> + <el-select + v-model="queryParams.type" + placeholder="请选择操作类型" + clearable + class="!w-240px" + > <el-option v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_OPERATE_TYPE)" :key="parseInt(dict.value)" @@ -28,8 +41,13 @@ /> </el-select> </el-form-item> - <el-form-item label="状态" prop="success"> - <el-select v-model="queryParams.success" placeholder="操作状态" clearable> + <el-form-item label="操作状态" prop="success"> + <el-select + v-model="queryParams.success" + placeholder="请选择操作状态" + clearable + class="!w-240px" + > <el-option :key="true" label="成功" :value="true" /> <el-option :key="false" label="失败" :value="false" /> </el-select> @@ -42,6 +60,7 @@ start-placeholder="开始日期" end-placeholder="结束日期" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="!w-240px" /> </el-form-item> <el-form-item> From 812300cc04050ee0357a0ceea11ec7d5e377909f Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Mon, 20 Mar 2023 08:53:28 +0800 Subject: [PATCH 043/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E9=94=99=E8=AF=AF=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/errorCode/form.vue | 2 -- src/views/system/errorCode/index.vue | 17 +++++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/views/system/errorCode/form.vue b/src/views/system/errorCode/form.vue index 5e6a0bc8..9544c6ab 100644 --- a/src/views/system/errorCode/form.vue +++ b/src/views/system/errorCode/form.vue @@ -74,8 +74,6 @@ defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 /** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 - -/** 提交按钮 */ const submitForm = async () => { // 校验表单 if (!formRef) return diff --git a/src/views/system/errorCode/index.vue b/src/views/system/errorCode/index.vue index 8f7dac33..fc152903 100644 --- a/src/views/system/errorCode/index.vue +++ b/src/views/system/errorCode/index.vue @@ -1,7 +1,13 @@ <template> <!-- 搜索工作栏 --> <content-wrap> - <el-form :model="queryParams" ref="queryFormRef" class="-mb-15px" :inline="true"> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="90px" + > <el-form-item label="错误码类型" prop="type"> <el-select v-model="queryParams.type" placeholder="请选择错误码类型" clearable> <el-option @@ -9,6 +15,7 @@ :key="dict.value" :label="dict.label" :value="dict.value" + class="!w-240px" /> </el-select> </el-form-item> @@ -18,6 +25,7 @@ placeholder="请输入应用名" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="错误码编码" prop="code"> @@ -34,6 +42,7 @@ placeholder="请输入错误码提示" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="创建时间" prop="createTime"> @@ -44,6 +53,7 @@ start-placeholder="开始日期" end-placeholder="结束日期" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="!w-240px" /> </el-form-item> <el-form-item> @@ -68,6 +78,7 @@ </el-form-item> </el-form> </content-wrap> + <!-- 列表 --> <content-wrap> <el-table v-loading="loading" :data="list"> @@ -118,6 +129,7 @@ @pagination="getList" /> </content-wrap> + <!-- 表单弹窗:添加/修改 --> <error-code-form ref="modalRef" @success="getList" /> </template> @@ -194,6 +206,7 @@ const handleDelete = async (id: number) => { await getList() } catch {} } + /** 导出按钮操作 */ const handleExport = async () => { try { @@ -202,7 +215,7 @@ const handleExport = async () => { // 发起导出 exportLoading.value = true const data = await ErrorCodeApi.excelErrorCodeApi(queryParams) - download.excel(data, '参数配置.xls') + download.excel(data, '错误码.xls') } catch { } finally { exportLoading.value = false From d88fda7d3bdf1b14d97e75308ae04e4071d55191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=9C=E6=96=B9=E7=99=BD?= <457776125@qq.com> Date: Tue, 21 Mar 2023 09:22:31 +0800 Subject: [PATCH 044/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E7=AE=A1=E7=90=86->=E7=A7=9F=E6=88=B7?= =?UTF-8?q?=E7=AE=A1=E7=90=86:=E7=A7=9F=E6=88=B7=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/tenant/form.vue | 204 ++++++++++++++ src/views/system/tenant/index.vue | 428 +++++++++++++++++------------- 2 files changed, 447 insertions(+), 185 deletions(-) create mode 100644 src/views/system/tenant/form.vue diff --git a/src/views/system/tenant/form.vue b/src/views/system/tenant/form.vue new file mode 100644 index 00000000..c50f6ffc --- /dev/null +++ b/src/views/system/tenant/form.vue @@ -0,0 +1,204 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible" width="50%"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="80px" + v-loading="formLoading" + > + <el-row> + <el-col :span="10"> + <el-form-item label="租户名" prop="name"> + <el-input v-model="formData.name" placeholder="请输入租户名" /> + </el-form-item> + </el-col> + <el-col :span="10" :offset="2"> + <el-form-item label="租户套餐" prop="packageId"> + <el-select v-model="formData.packageId" placeholder="请选择租户套餐" clearable> + <el-option + v-for="item in packageList" + :key="item.id" + :label="item.name" + :value="item.id" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="10"> + <el-form-item label="联系人" prop="contactName"> + <el-input v-model="formData.contactName" placeholder="请输入联系人" /> + </el-form-item> + </el-col> + <el-col :span="10" :offset="2"> + <el-form-item label="联系手机" prop="contactMobile"> + <el-input v-model="formData.contactMobile" placeholder="请输入联系手机" /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="10"> + <el-form-item v-if="formData.id === undefined" label="用户名称" prop="username"> + <el-input v-model="formData.username" placeholder="请输入用户名称" /> + </el-form-item> + </el-col> + <el-col :span="10" :offset="2"> + <el-form-item v-if="formData.id === undefined" label="用户密码" prop="password"> + <el-input + v-model="formData.password" + placeholder="请输入用户密码" + type="password" + show-password + /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="10"> + <el-form-item label="账号额度" prop="accountCount"> + <el-input-number + v-model="formData.accountCount" + placeholder="请输入账号额度" + controls-position="right" + :min="0" + /> + </el-form-item> + </el-col> + <el-col :span="10" :offset="2"> + <el-form-item label="过期时间" prop="expireTime"> + <el-date-picker + clearable + v-model="formData.expireTime" + type="date" + value-format="x" + placeholder="请选择过期时间" + /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="10"> + <el-form-item label="绑定域名" prop="domain"> + <el-input v-model="formData.domain" placeholder="请输入绑定域名" /> + </el-form-item> + </el-col> + <el-col :span="10" :offset="2"> + <el-form-item label="租户状态" prop="status"> + <el-radio-group v-model="formData.status"> + <el-radio + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="dict.value" + >{{ dict.label }} + </el-radio> + </el-radio-group> + </el-form-item> + </el-col> + </el-row> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import * as TenantApi from '@/api/system/tenant' +import { CommonStatusEnum } from '@/utils/constants' +import { getTenantPackageList as getTenantPackageListApi } from '@/api/system/tenantPackage' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: undefined, + name: undefined, + packageId: undefined, + contactName: undefined, + contactMobile: undefined, + accountCount: undefined, + expireTime: undefined, + domain: undefined, + status: CommonStatusEnum.ENABLE +}) +const formRules = reactive({ + name: [{ required: true, message: '租户名不能为空', trigger: 'blur' }], + packageId: [{ required: true, message: '租户套餐不能为空', trigger: 'blur' }], + contactName: [{ required: true, message: '联系人不能为空', trigger: 'blur' }], + status: [{ required: true, message: '租户状态不能为空', trigger: 'blur' }], + accountCount: [{ required: true, message: '账号额度不能为空', trigger: 'blur' }], + expireTime: [{ required: true, message: '过期时间不能为空', trigger: 'blur' }], + domain: [{ required: true, message: '绑定域名不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref +const packageList = ref([]) // 租户套餐 + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await TenantApi.getTenantApi(id) + } finally { + formLoading.value = false + } + } + packageList.value = await getTenantPackageListApi() +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as TenantApi.TenantVO + if (formType.value === 'create') { + await TenantApi.createTenantApi(data) + message.success(t('common.createSuccess')) + } else { + await TenantApi.updateTenantApi(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + name: undefined, + packageId: undefined, + contactName: undefined, + contactMobile: undefined, + accountCount: undefined, + expireTime: undefined, + domain: undefined, + status: CommonStatusEnum.ENABLE + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/system/tenant/index.vue b/src/views/system/tenant/index.vue index bb1ca1a3..08ff5228 100644 --- a/src/views/system/tenant/index.vue +++ b/src/views/system/tenant/index.vue @@ -1,197 +1,255 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <!-- 操作:新增 --> - <XButton + <!-- 搜索 --> + <content-wrap> + <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true"> + <el-form-item label="租户名" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入租户名" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="联系人" prop="contactName"> + <el-input + v-model="queryParams.contactName" + placeholder="请输入联系人" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="联系手机" prop="contactMobile"> + <el-input + v-model="queryParams.contactMobile" + placeholder="请输入联系手机" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="租户状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="请选择租户状态" clearable> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + value-format="YYYY-MM-DD HH:mm:ss" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + /> + </el-form-item> + + <el-form-item> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px" /> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px" /> + 重置 + </el-button> + <el-button type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" + @click="openModal('create')" v-hasPermi="['system:tenant:create']" - @click="handleCreate()" - /> - <XButton - type="warning" - preIcon="ep:download" - :title="t('action.export')" + > + <Icon icon="ep:plus" class="mr-5px" /> + 新增 + </el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" v-hasPermi="['system:tenant:export']" - @click="exportList('租户列表.xls')" - /> - </template> - <template #accountCount_default="{ row }"> - <el-tag> {{ row.accountCount }} </el-tag> - </template> - <template #packageId_default="{ row }"> - <el-tag v-if="row.packageId === 0" type="danger">系统租户</el-tag> - <el-tag v-else type="success"> {{ getPackageName(row.packageId) }} </el-tag> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:修改 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['system:tenant:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['system:tenant:update']" - @click="handleDetail(row.id)" - /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['system:tenant:delete']" - @click="deleteData(row.id)" - /> - </template> - </XTable> - </ContentWrap> - <XModal v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(添加 / 修改) --> - <Form - v-if="['create', 'update'].includes(actionType)" - :schema="allSchemas.formSchema" - :rules="rules" - ref="formRef" - /> - <!-- 对话框(详情) --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - > - <template #packageId="{ row }"> - <el-tag v-if="row.packageId === 0" type="danger">系统租户</el-tag> - <el-tag v-else type="success"> {{ getPackageName(row.packageId) }} </el-tag> - </template> - </Descriptions> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="actionLoading" - @click="submitForm()" + > + <Icon icon="ep:download" class="mr-5px" /> + 导出 + </el-button> + </el-form-item> + </el-form> + </content-wrap> + + <!-- 列表 --> + <content-wrap> + <el-table v-loading="loading" :data="list" align="center"> + <el-table-column label="租户编号" align="center" prop="id" /> + <el-table-column label="租户名" align="center" prop="name" /> + <el-table-column label="租户套餐" align="center" prop="packageId"> + <template #default="scope"> + <el-tag v-if="scope.row.packageId === 0" type="danger">系统租户</el-tag> + <template v-else v-for="item in packageList"> + <el-tag type="success" :key="item.id" v-if="item.id === scope.row.packageId" + >{{ item.name }} + </el-tag> + </template> + </template> + </el-table-column> + <el-table-column label="联系人" align="center" prop="contactName" /> + <el-table-column label="联系手机" align="center" prop="contactMobile" /> + <el-table-column label="账号额度" align="center" prop="accountCount"> + <template #default="scope"> + <el-tag>{{ scope.row.accountCount }}</el-tag> + </template> + </el-table-column> + <el-table-column + label="过期时间" + align="center" + prop="expireTime" + width="180" + :formatter="dateFormatter" /> - <!-- 按钮:关闭 --> - <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> + <el-table-column label="绑定域名" align="center" prop="domain" width="180" /> + <el-table-column label="租户状态" align="center" prop="status"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> + </template> + </el-table-column> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column label="操作" align="center" min-width="110" fixed="right"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openModal('update', scope.row.id)" + v-hasPermi="['system:tenant:update']" + > + 编辑 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['system:tenant:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + + <!-- 表单弹窗:添加/修改 --> + <tenant-form ref="modalRef" @success="getList" /> </template> <script setup lang="ts" name="Tenant"> -import type { FormExpose } from '@/components/Form' +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' +import download from '@/utils/download' import * as TenantApi from '@/api/system/tenant' -import { rules, allSchemas, tenantPackageOption } from './tenant.data' +import { getTenantPackageList as getTenantPackageListApi } from '@/api/system/tenantPackage' +import TenantForm from './form.vue' +import ContentWrap from '@/components/ContentWrap/src/ContentWrap.vue' +import DictTag from '@/components/DictTag/src/DictTag.vue' -const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 -// 列表相关的变量 -const [registerTable, { reload, deleteData, exportList }] = useXTable({ - allSchemas: allSchemas, - getListApi: TenantApi.getTenantPageApi, - deleteApi: TenantApi.deleteTenantApi, - exportListApi: TenantApi.exportTenantApi +const { t } = useI18n() // 国际化 + +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const packageList = ref([]) //租户套餐列表 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + name: undefined, + contactName: undefined, + contactMobile: undefined, + status: undefined, + createTime: [] +}) //查询参数对象 +const queryFormRef = ref() // 搜索的表单 +const exportLoading = ref(false) // 导出的加载中 + +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await TenantApi.getTenantPageApi(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, id) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await TenantApi.deleteTenantApi(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await TenantApi.exportTenantApi(queryParams) + download.excel(data, '参数配置.xls') + } catch { + } finally { + exportLoading.value = false + } +} + +/**获取租户套餐**/ +const getTenantPackageList = async () => { + const data = await getTenantPackageListApi() + packageList.value = data +} + +/** 初始化 **/ +onMounted(() => { + getList() + getTenantPackageList() }) - -const actionLoading = ref(false) // 遮罩层 -const actionType = ref('') // 操作按钮的类型 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const formRef = ref<FormExpose>() // 表单 Ref -const detailData = ref() // 详情 Ref -const getPackageName = (packageId: number) => { - for (let item of tenantPackageOption) { - if (item.value === packageId) { - return item.label - } - } - return '未知套餐' -} - -// 设置标题 -const setDialogTile = (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true -} - -// 新增操作 -const handleCreate = async () => { - // 重置表单 - setDialogTile('create') - await nextTick() - console.log(allSchemas.formSchema, 'allSchemas.formSchema') - if (allSchemas.formSchema[4].field !== 'username') { - unref(formRef)?.addSchema( - { - field: 'username', - label: '用户名称', - component: 'Input' - }, - 0 - ) - unref(formRef)?.addSchema( - { - field: 'password', - label: '用户密码', - component: 'InputPassword' - }, - 1 - ) - } -} - -// 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - await nextTick() - unref(formRef)?.delSchema('username') - unref(formRef)?.delSchema('password') - // 设置数据 - const res = await TenantApi.getTenantApi(rowId) - unref(formRef)?.setValues(res) -} - -// 详情操作 -const handleDetail = async (rowId: number) => { - // 设置数据 - const res = await TenantApi.getTenantApi(rowId) - detailData.value = res - setDialogTile('detail') -} - -// 提交按钮 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - actionLoading.value = true - // 提交请求 - try { - const data = unref(formRef)?.formModel as TenantApi.TenantVO - if (actionType.value === 'create') { - await TenantApi.createTenantApi(data) - message.success(t('common.createSuccess')) - } else { - await TenantApi.updateTenantApi(data) - message.success(t('common.updateSuccess')) - } - // 操作成功,重新加载列表 - dialogVisible.value = false - } finally { - actionLoading.value = false - // 刷新列表 - await reload() - } - } - }) -} </script> From 33a9b5af863f2bb8ee60f72bbe277f2fe3bfff9d Mon Sep 17 00:00:00 2001 From: lour6498 <lour6498@gmail.com> Date: Tue, 21 Mar 2023 10:00:32 +0800 Subject: [PATCH 045/184] =?UTF-8?q?fix(=E6=95=B0=E6=8D=AE=E6=BA=90?= =?UTF-8?q?=E9=85=8D=E7=BD=AE):=20=E9=A1=B5=E9=9D=A2api=E6=9C=AA=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/infra/codegen/components/ImportTable.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/views/infra/codegen/components/ImportTable.vue b/src/views/infra/codegen/components/ImportTable.vue index 38a81541..3d56d5fc 100644 --- a/src/views/infra/codegen/components/ImportTable.vue +++ b/src/views/infra/codegen/components/ImportTable.vue @@ -52,7 +52,7 @@ import { VxeTableInstance } from 'vxe-table' import type { DatabaseTableVO } from '@/api/infra/codegen/types' import { getSchemaTableListApi, createCodegenListApi } from '@/api/infra/codegen' -import { getDataSourceConfigListApi, DataSourceConfigVO } from '@/api/infra/dataSourceConfig' +import { getDataSourceConfigList, DataSourceConfigVO } from '@/api/infra/dataSourceConfig' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -63,11 +63,11 @@ const dbLoading = ref(true) const queryParams = reactive({ name: undefined, comment: undefined, - dataSourceConfigId: 0 + dataSourceConfigId: 0 as number | undefined }) const dataSourceConfigs = ref<DataSourceConfigVO[]>([]) const show = async () => { - const res = await getDataSourceConfigListApi() + const res = await getDataSourceConfigList() dataSourceConfigs.value = res queryParams.dataSourceConfigId = dataSourceConfigs.value[0].id visible.value = true From a7503322107ad890908c9986e23949af9c7acdd7 Mon Sep 17 00:00:00 2001 From: lour6498 <lour6498@gmail.com> Date: Tue, 21 Mar 2023 10:01:06 +0800 Subject: [PATCH 046/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/loginLog/index.ts | 4 +- src/styles/index.scss | 6 + src/views/system/loginlog/detail.vue | 49 +++++ src/views/system/loginlog/index.vue | 213 ++++++++++++++++----- src/views/system/loginlog/loginLog.data.ts | 53 ----- 5 files changed, 225 insertions(+), 100 deletions(-) create mode 100644 src/views/system/loginlog/detail.vue delete mode 100644 src/views/system/loginlog/loginLog.data.ts diff --git a/src/api/system/loginLog/index.ts b/src/api/system/loginLog/index.ts index cadaeaf3..5b06e753 100644 --- a/src/api/system/loginLog/index.ts +++ b/src/api/system/loginLog/index.ts @@ -21,10 +21,10 @@ export interface LoginLogReqVO extends PageParam { } // 查询登录日志列表 -export const getLoginLogPageApi = (params: LoginLogReqVO) => { +export const getLoginLogPage = (params: LoginLogReqVO) => { return request.get({ url: '/system/login-log/page', params }) } // 导出登录日志 -export const exportLoginLogApi = (params: LoginLogReqVO) => { +export const exportLoginLog = (params: LoginLogReqVO) => { return request.download({ url: '/system/login-log/export', params }) } diff --git a/src/styles/index.scss b/src/styles/index.scss index 39c4c4da..02c187a5 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -26,3 +26,9 @@ border-left-color: var(--el-color-primary); } } + +// 添加表头样式 +.el-table.yudao-table { + --el-table-header-bg-color: #f8f8f9; + --el-table-header-text-color: #606266; +} diff --git a/src/views/system/loginlog/detail.vue b/src/views/system/loginlog/detail.vue new file mode 100644 index 00000000..8372d2e6 --- /dev/null +++ b/src/views/system/loginlog/detail.vue @@ -0,0 +1,49 @@ +<template> + <Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="500" width="800"> + <el-descriptions border :column="1"> + <el-descriptions-item label="日志编号" min-width="120"> + {{ detailData.id }} + </el-descriptions-item> + <el-descriptions-item label="操作类型"> + <dict-tag :type="DICT_TYPE.SYSTEM_LOGIN_TYPE" :value="detailData.logType" /> + </el-descriptions-item> + <el-descriptions-item label="用户名称"> + {{ detailData.username }} + </el-descriptions-item> + <el-descriptions-item label="登录地址"> + {{ detailData.userIp }} + </el-descriptions-item> + <el-descriptions-item label="浏览器"> + {{ detailData.userAgent }} + </el-descriptions-item> + <el-descriptions-item label="登陆结果"> + <dict-tag :type="DICT_TYPE.SYSTEM_LOGIN_RESULT" :value="detailData.result" /> + </el-descriptions-item> + <el-descriptions-item label="登录日期"> + {{ formatDate(detailData.createTime, 'YYYY-MM-DD HH:mm:ss') }} + </el-descriptions-item> + </el-descriptions> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE } from '@/utils/dict' +import { formatDate } from '@/utils/formatTime' +import * as LoginLogApi from '@/api/system/loginLog' + +const modelVisible = ref(false) // 弹窗的是否展示 +const detailLoading = ref(false) // 表单的加载中 +const detailData = ref() // 详情数据 + +/** 打开弹窗 */ +const openModal = async (data: LoginLogApi.LoginLogVO) => { + modelVisible.value = true + // 设置数据 + detailLoading.value = true + try { + detailData.value = data + } finally { + detailLoading.value = false + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +</script> diff --git a/src/views/system/loginlog/index.vue b/src/views/system/loginlog/index.vue index f2bb8c67..f5a695fc 100644 --- a/src/views/system/loginlog/index.vue +++ b/src/views/system/loginlog/index.vue @@ -1,53 +1,176 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <!-- 操作:导出 --> - <template #toolbar_buttons> - <XButton - type="warning" - preIcon="ep:download" - :title="t('action.export')" - @click="exportList('登录列表.xls')" + <content-wrap> + <!-- 搜索工作栏 --> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="用户名称" prop="username"> + <el-input + v-model="queryParams.username" + placeholder="请输入用户名称" + clearable + @keyup.enter="handleQuery" + class="!w-240px" /> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:详情 --> - <XTextButton preIcon="ep:view" :title="t('action.detail')" @click="handleDetail(row)" /> - </template> - </XTable> - </ContentWrap> - <!-- 弹窗 --> - <XModal id="postModel" v-model="dialogVisible" :title="dialogTitle"> - <!-- 表单:详情 --> - <Descriptions :schema="allSchemas.detailSchema" :data="detailData" /> - <template #footer> - <!-- 按钮:关闭 --> - <XButton :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> + </el-form-item> + <el-form-item label="登录地址" prop="userIp"> + <el-input + v-model="queryParams.userIp" + placeholder="请输入登录地址" + clearable + @keyup.enter="handleQuery" + class="!w-240px" + /> + </el-form-item> + <el-form-item label="登录日期" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + value-format="YYYY-MM-DD HH:mm:ss" + type="daterange" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['infra:config:export']" + > + <Icon icon="ep:download" class="mr-5px" /> 导出 + </el-button> + </el-form-item> + </el-form> + </content-wrap> + + <!-- 列表 --> + <content-wrap> + <el-table class="yudao-table" v-loading="loading" :data="list"> + <el-table-column label="日志编号" align="center" prop="id" /> + <el-table-column label="操作类型" align="center" prop="logType"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.SYSTEM_LOGIN_TYPE" :value="scope.row.logType" /> + </template> + </el-table-column> + <el-table-column label="用户名称" align="center" prop="username" width="180" /> + <el-table-column label="登录地址" align="center" prop="userIp" width="180" /> + + <el-table-column label="浏览器" align="center" prop="userAgent" /> + <el-table-column label="登陆结果" align="center" prop="result"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.SYSTEM_LOGIN_RESULT" :value="scope.row.result" /> + </template> + </el-table-column> + <el-table-column + label="登录日期" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openModal(scope.row)" + v-hasPermi="['infra:config:query']" + > + 详情 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + + <!-- 表单弹窗:详情 --> + <login-log-detail ref="modalRef" /> </template> -<script setup lang="ts" name="Loginlog"> -// 业务相关的 import -import { allSchemas } from './loginLog.data' -import { getLoginLogPageApi, exportLoginLogApi, LoginLogVO } from '@/api/system/loginLog' +<script setup lang="ts" name="LoginLog"> +import { DICT_TYPE } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' +import download from '@/utils/download' +import * as LoginLogApi from '@/api/system/loginLog' +import LoginLogDetail from './detail.vue' +const message = useMessage() // 消息弹窗 -const { t } = useI18n() // 国际化 -// 列表相关的变量 -const [registerTable, { exportList }] = useXTable({ - allSchemas: allSchemas, - getListApi: getLoginLogPageApi, - exportListApi: exportLoginLogApi +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + username: undefined, + userIp: undefined, + createTime: [] }) +const queryFormRef = ref() // 搜索的表单 +const exportLoading = ref(false) // 导出的加载中 -// 详情操作 -const detailData = ref() // 详情 Ref -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref(t('action.detail')) // 弹出层标题 -// 详情 -const handleDetail = async (row: LoginLogVO) => { - // 设置数据 - detailData.value = row - dialogVisible.value = true +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await LoginLogApi.getLoginLogPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } } + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 详情操作 */ +const modalRef = ref() +const openModal = (data: LoginLogApi.LoginLogVO) => { + modalRef.value.openModal(data) +} + +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await LoginLogApi.exportLoginLog(queryParams) + download.excel(data, '登录日志.xls') + } catch { + } finally { + exportLoading.value = false + } +} + +/** 初始化 **/ +onMounted(() => { + getList() +}) </script> diff --git a/src/views/system/loginlog/loginLog.data.ts b/src/views/system/loginlog/loginLog.data.ts deleted file mode 100644 index c0a51fbe..00000000 --- a/src/views/system/loginlog/loginLog.data.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'id', - primaryTitle: '日志编号', - action: true, - actionWidth: '100px', - columns: [ - { - title: '日志类型', - field: 'logType', - dictType: DICT_TYPE.SYSTEM_LOGIN_TYPE, - dictClass: 'number' - }, - { - title: '用户名称', - field: 'username', - isSearch: true - }, - { - title: '登录地址', - field: 'userIp', - isSearch: true - }, - { - title: '浏览器', - field: 'userAgent' - }, - { - title: '登陆结果', - field: 'result', - dictType: DICT_TYPE.SYSTEM_LOGIN_RESULT, - dictClass: 'number' - }, - { - title: '登录日期', - field: 'createTime', - formatter: 'formatDate', - table: { - width: 150 - }, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From 7697e5c358436a1ff86b013918bff74ad99481f7 Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Tue, 21 Mar 2023 16:17:44 +0800 Subject: [PATCH 047/184] =?UTF-8?q?add:=20=E6=B7=BB=E5=8A=A0=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E6=8C=87=E5=AE=9A=E5=AD=97=E5=85=B8=E6=8C=87=E5=AE=9A?= =?UTF-8?q?=E5=80=BC=E5=AF=B9=E5=BA=94=E7=9A=84label=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/dict.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/utils/dict.ts b/src/utils/dict.ts index 15e57ff2..4f5d63fb 100644 --- a/src/utils/dict.ts +++ b/src/utils/dict.ts @@ -69,7 +69,16 @@ export const getDictObj = (dictType: string, value: any) => { } }) } - +export const getDictLabel = (dictType: string, value: any) => { + const dictOptions: DictDataType[] = getDictOptions(dictType) + const dictLabel = ref('') + dictOptions.forEach((dict: DictDataType) => { + if (dict.value === value) { + dictLabel.value = dict.label + } + }) + return dictLabel.value +} export enum DICT_TYPE { USER_TYPE = 'user_type', COMMON_STATUS = 'common_status', From 9adf80c9141db8260d5f0b42b8f884d78a2954b8 Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Tue, 21 Mar 2023 16:20:03 +0800 Subject: [PATCH 048/184] =?UTF-8?q?add:=20=E6=94=B9=E9=80=A0vue2=E4=B8=AD?= =?UTF-8?q?=E7=9A=84RightToolbar=E5=88=B0vue3=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/RightToolbar/index.ts | 9 ++ src/components/RightToolbar/src/index.vue | 104 ++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 src/components/RightToolbar/index.ts create mode 100644 src/components/RightToolbar/src/index.vue diff --git a/src/components/RightToolbar/index.ts b/src/components/RightToolbar/index.ts new file mode 100644 index 00000000..eb9d1112 --- /dev/null +++ b/src/components/RightToolbar/index.ts @@ -0,0 +1,9 @@ +import RightToolbar from './src/index.vue' + +export interface columnsType { + key?: number + label?: string + visible?: boolean +} + +export { RightToolbar } diff --git a/src/components/RightToolbar/src/index.vue b/src/components/RightToolbar/src/index.vue new file mode 100644 index 00000000..11e021dc --- /dev/null +++ b/src/components/RightToolbar/src/index.vue @@ -0,0 +1,104 @@ +<template> + <div :style="style"> + <el-row justify="end"> + <el-tooltip + class="item" + effect="dark" + :content="showSearch ? '隐藏搜索' : '显示搜索'" + placement="top" + v-if="search" + > + <el-button circle @click="toggleSearch()"> + <Icon icon="ep:search" /> + </el-button> + </el-tooltip> + <el-tooltip class="item" effect="dark" content="刷新" placement="top"> + <el-button circle @click="refresh()"> + <Icon icon="ep:refresh" /> + </el-button> + </el-tooltip> + <el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="isColumns"> + <el-button circle @click="showColumn()"> + <Icon icon="ep:menu" /> + </el-button> + </el-tooltip> + </el-row> + <el-dialog :title="title" v-model="open" append-to-body> + <el-transfer + :titles="['显示', '隐藏']" + v-model="value" + :data="columns" + @change="dataChange" + /> + </el-dialog> + </div> +</template> +<script lang="ts" setup name="RightToolbar"> +import type { CSSProperties } from 'vue' +import type { columnsType } from '@/components/RightToolbar' +import { propTypes } from '@/utils/propTypes' +// 显隐数据 +const value = ref<number[]>([]) +// 弹出层标题 +const title = ref('显示/隐藏') +// 是否显示弹出层 +const open = ref(false) + +const props = defineProps({ + showSearch: propTypes.bool.def(true), + columns: { + type: Array as PropType<columnsType[]>, + default: () => [] + }, + search: propTypes.bool.def(true), + gutter: propTypes.number.def(10) +}) +const isColumns = computed(() => props.columns?.length > 0) +const style = computed((): CSSProperties => { + const ret: CSSProperties = {} + if (props.gutter) { + ret.marginRight = `${props.gutter / 2}px` + } + return ret +}) +const emit = defineEmits(['update:showSearch', 'queryTable']) +// 搜索 +const toggleSearch = () => { + emit('update:showSearch', !props.showSearch) +} +// 刷新 +const refresh = () => { + emit('queryTable') +} +// 右侧列表元素变化 +const dataChange = (data: number[]) => { + props.columns.forEach((item) => { + const key: number = item.key! + item.visible = !data.includes(key) + }) +} +// 打开显隐列dialog +const showColumn = () => { + open.value = true +} +// 显隐列初始默认隐藏列 +const init = () => { + props.columns.forEach((item, index) => { + if (item.visible === false) { + value.value.push(index) + } + }) +} +init() +</script> +<style lang="scss" scoped> +:deep(.el-transfer__button) { + border-radius: 50%; + padding: 12px; + display: block; + margin-left: 0px; +} +:deep(.el-transfer__button:first-child) { + margin-bottom: 10px; +} +</style> From c1b1ffa46706bd2b7d3e21c78167a4eb1342bf12 Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Tue, 21 Mar 2023 16:21:23 +0800 Subject: [PATCH 049/184] =?UTF-8?q?add:=20=E6=94=B9=E9=80=A0vue2=E4=B8=AD?= =?UTF-8?q?=E7=9A=84RightToolbar=E5=88=B0vue3=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/index.ts b/src/components/index.ts index 19b2aac6..97c2b4b0 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -9,6 +9,7 @@ import { XButton, XTextButton } from '@/components/XButton' import { DictTag } from '@/components/DictTag' import { ContentWrap } from '@/components/ContentWrap' import { Descriptions } from '@/components/Descriptions' +import { RightToolbar } from '@/components/RightToolbar' export const setupGlobCom = (app: App<Element>): void => { app.component('Icon', Icon) @@ -22,4 +23,5 @@ export const setupGlobCom = (app: App<Element>): void => { app.component('DictTag', DictTag) app.component('ContentWrap', ContentWrap) app.component('Descriptions', Descriptions) + app.component('RightToolbar', RightToolbar) } From 6e90bbe2e1b03f8f4f9c05d4256f291d21eb639a Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Tue, 21 Mar 2023 16:29:11 +0800 Subject: [PATCH 050/184] =?UTF-8?q?add:=20=E6=94=B9=E9=80=A0vue2=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E7=9F=AD=E4=BF=A1=E6=A8=A1=E6=9D=BF=E5=88=B0vue3?= =?UTF-8?q?=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/sms/smsChannel/index.ts | 6 + src/api/system/sms/smsTemplate/index.ts | 22 +- src/locales/zh-CN.ts | 9 +- src/views/system/sms/smsTemplate/form.vue | 262 +++++++++ src/views/system/sms/smsTemplate/index.vue | 532 +++++++++++------- .../sms/smsTemplate/sms.template.data.ts | 107 ---- 6 files changed, 607 insertions(+), 331 deletions(-) create mode 100644 src/views/system/sms/smsTemplate/form.vue delete mode 100644 src/views/system/sms/smsTemplate/sms.template.data.ts diff --git a/src/api/system/sms/smsChannel/index.ts b/src/api/system/sms/smsChannel/index.ts index 176d075f..7c8ccea9 100644 --- a/src/api/system/sms/smsChannel/index.ts +++ b/src/api/system/sms/smsChannel/index.ts @@ -12,6 +12,12 @@ export interface SmsChannelVO { createTime: Date } +export interface SmsChannelListVO { + id: number + code: string + signature: string +} + export interface SmsChannelPageReqVO extends PageParam { signature?: string code?: string diff --git a/src/api/system/sms/smsTemplate/index.ts b/src/api/system/sms/smsTemplate/index.ts index 0433fe3a..55a61762 100644 --- a/src/api/system/sms/smsTemplate/index.ts +++ b/src/api/system/sms/smsTemplate/index.ts @@ -1,18 +1,18 @@ import request from '@/config/axios' export interface SmsTemplateVO { - id: number - type: number - status: number + id: number | null + type: number | null + status: number | null code: string name: string content: string remark: string apiTemplateId: string - channelId: number - channelCode: string - params: string[] - createTime: Date + channelId: number | null + channelCode?: string + params?: string[] + createTime?: Date } export interface SendSmsReqVO { @@ -21,13 +21,13 @@ export interface SendSmsReqVO { templateParams: Map<String, Object> } -export interface SmsTemplatePageReqVO { - type?: number - status?: number +export interface SmsTemplatePageReqVO extends PageParam { + type?: number | null + status?: number | null code?: string content?: string apiTemplateId?: string - channelId?: number + channelId?: number | null createTime?: Date[] } diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 7c5742c4..985ffb7c 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -302,7 +302,14 @@ export default { dialog: { dialog: '弹窗', open: '打开', - close: '关闭' + close: '关闭', + sms: { + template: { + addTitle: '添加短信模板', + updtaeTitle: '修改短信模板', + sendSms: '发送短信' + } + } }, sys: { api: { diff --git a/src/views/system/sms/smsTemplate/form.vue b/src/views/system/sms/smsTemplate/form.vue new file mode 100644 index 00000000..193ae2e3 --- /dev/null +++ b/src/views/system/sms/smsTemplate/form.vue @@ -0,0 +1,262 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + v-if="['template.addTitle', 'template.updtaeTitle'].includes(formType)" + ref="formRef" + :model="formData" + :rules="formRules" + label-width="140px" + > + <el-form-item label="短信渠道编号" prop="channelId"> + <el-select v-model="formData.channelId" placeholder="请选择短信渠道编号"> + <el-option + v-for="channel in channelOptions" + :key="channel.id" + :value="channel.id" + :label=" + channel.signature + optionLabel(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, channel.code) + " + /> + </el-select> + </el-form-item> + <el-form-item label="短信类型" prop="type"> + <el-select v-model="formData.type" placeholder="请选择短信类型"> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE)" + :key="dict.value" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + <el-form-item label="模板编号" prop="code"> + <el-input v-model="formData.code" placeholder="请输入模板编号" /> + </el-form-item> + <el-form-item label="模板名称" prop="name"> + <el-input v-model="formData.name" placeholder="请输入模板名称" /> + </el-form-item> + <el-form-item label="模板内容" prop="content"> + <el-input type="textarea" v-model="formData.content" placeholder="请输入模板内容" /> + </el-form-item> + <el-form-item label="开启状态" prop="status"> + <el-radio-group v-model="formData.status"> + <el-radio + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="parseInt(dict.value)" + >{{ dict.label }}</el-radio + > + </el-radio-group> + </el-form-item> + <el-form-item label="短信 API 模板编号" prop="apiTemplateId"> + <el-input v-model="formData.apiTemplateId" placeholder="请输入短信 API 的模板编号" /> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="formData.remark" placeholder="请输入备注" /> + </el-form-item> + </el-form> + <el-form + v-if="formType === 'template.sendSms'" + ref="sendSmsFormRef" + :model="sendSmsForm" + :rules="sendSmsRules" + > + <el-form-item label="模板内容" prop="content"> + <el-input + v-model="sendSmsForm.content" + type="textarea" + placeholder="请输入模板内容" + readonly + /> + </el-form-item> + <el-form-item label="手机号" prop="mobile"> + <el-input v-model="sendSmsForm.mobile" placeholder="请输入手机号" /> + </el-form-item> + <el-form-item + v-for="param in sendSmsForm.params" + :key="param" + :label="'参数 {' + param + '}'" + :prop="'templateParams.' + param" + > + <el-input + v-model="sendSmsForm.templateParams[param]" + :placeholder="'请输入 ' + param + ' 参数'" + /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts" name="SmsTemplateFrom"> +import { DICT_TYPE, getDictOptions, getDictLabel } from '@/utils/dict' +import * as templateApi from '@/api/system/sms/smsTemplate' +import * as SmsChannelApi from '@/api/system/sms/smsChannel' +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +defineProps({ + channelOptions: { + type: Array as PropType<SmsChannelApi.SmsChannelListVO[]>, + define: () => {} + } +}) + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型 +const formData = ref<templateApi.SmsTemplateVO>({ + id: null, + type: null, + status: null, + code: '', + name: '', + content: '', + remark: '', + apiTemplateId: '', + channelId: null +}) +const formRules = reactive({ + type: [{ required: true, message: '短信类型不能为空', trigger: 'change' }], + status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }], + code: [{ required: true, message: '模板编码不能为空', trigger: 'blur' }], + name: [{ required: true, message: '模板名称不能为空', trigger: 'blur' }], + content: [{ required: true, message: '模板内容不能为空', trigger: 'blur' }], + apiTemplateId: [{ required: true, message: '短信 API 的模板编号不能为空', trigger: 'blur' }], + channelId: [{ required: true, message: '短信渠道编号不能为空', trigger: 'change' }] +}) + +const formRef = ref() // 表单 Ref +const optionLabel = computed( + () => (type: string, code: string) => `【${getDictLabel(type, code)}】` +) +// 发送短信表单相关 +const sendSmsForm = ref({ + content: '', + params: {}, + mobile: '', + templateCode: '', + templateParams: {} +}) +const sendSmsRules = reactive({ + mobile: [{ required: true, message: '手机不能为空', trigger: 'blur' }], + templateCode: [{ required: true, message: '模版编码不能为空', trigger: 'blur' }], + templateParams: {} +}) +const sendSmsFormRef = ref() +/** 打开弹窗 */ +interface openModalOption { + type: string + id?: '' + row?: any +} +const openModal = async (option: openModalOption) => { + modelVisible.value = true + modelTitle.value = t('dialog.sms.' + option.type) + formType.value = option.type + resetForm() + resetSendSms() + // 短信测试 + if (option.row) { + sendSmsForm.value.content = option.row.content + sendSmsForm.value.params = option.row.params + sendSmsForm.value.templateCode = option.row.code + sendSmsForm.value.templateParams = option.row.params.reduce(function (obj, item) { + obj[item] = undefined + return obj + }, {}) + sendSmsRules.templateParams = option.row.params.reduce(function (obj, item) { + obj[item] = { required: true, message: '参数 ' + item + ' 不能为空', trigger: 'change' } + return obj + }, {}) + } + + // 修改时,设置数据 + if (option.id) { + formLoading.value = true + try { + formData.value = await templateApi.getSmsTemplateApi(option.id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + if (['template.addTitle', 'template.updtaeTitle'].includes(formType.value)) { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as templateApi.SmsTemplateVO + if (formType.value === 'template.addTitle') { + await templateApi.createSmsTemplateApi(data) + message.success(t('common.createSuccess')) + } else { + await templateApi.updateSmsTemplateApi(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } + } + if (formType.value === 'template.sendSms') { + sendSmsTest() + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: null, + type: null, + status: null, + code: '', + name: '', + content: '', + remark: '', + apiTemplateId: '', + channelId: null + } + formRef.value?.resetFields() +} +/** 重置发送短信的表单 */ +const resetSendSms = () => { + // 根据 row 重置表单 + sendSmsForm.value = { + content: '', + params: {}, + mobile: '', + templateCode: '', + templateParams: {} + } + sendSmsFormRef.value?.resetFields() +} +/** 发送短信 */ +const sendSmsTest = async () => { + const data: templateApi.SendSmsReqVO = { + mobile: sendSmsForm.value.mobile, + templateCode: sendSmsForm.value.templateCode, + templateParams: sendSmsForm.value.templateParams as unknown as Map<string, Object> + } + const res = await templateApi.sendSmsApi(data) + if (res) { + message.success('提交发送成功!发送结果,见发送日志编号:' + res) + } + formLoading.value = false +} +</script> diff --git a/src/views/system/sms/smsTemplate/index.vue b/src/views/system/sms/smsTemplate/index.vue index bbc7c863..a7d6ded1 100644 --- a/src/views/system/sms/smsTemplate/index.vue +++ b/src/views/system/sms/smsTemplate/index.vue @@ -1,232 +1,340 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <!-- 操作:新增 --> - <template #toolbar_buttons> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['system:sms-channel:create']" - @click="handleCreate()" - /> - </template> - <template #actionbtns_default="{ row }"> - <XTextButton - preIcon="ep:cpu" - :title="t('action.test')" - v-hasPermi="['system:sms-template:send-sms']" - @click="handleSendSms(row)" - /> - <!-- 操作:修改 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['system:sms-template:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['system:sms-template:query']" - @click="handleDetail(row.id)" - /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['system:sms-template:delete']" - @click="deleteData(row.id)" - /> - </template> - </XTable> - </ContentWrap> - <XModal id="smsTemplate" v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(添加 / 修改) --> - <Form - v-if="['create', 'update'].includes(actionType)" - :schema="allSchemas.formSchema" - :rules="rules" - ref="formRef" - /> - <!-- 对话框(详情) --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - /> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="actionLoading" - @click="submitForm()" - /> - <!-- 按钮:关闭 --> - <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> - <XModal id="sendTest" v-model="sendVisible" title="测试"> - <el-form :model="sendSmsForm" :rules="sendSmsRules" label-width="200px" label-position="top"> - <el-form-item label="模板内容" prop="content"> + <content-wrap> + <!-- 搜索工作栏 --> + <el-form + :model="queryParams" + ref="queryForm" + :inline="true" + v-show="showSearch" + label-width="150px" + > + <el-form-item label="短信类型" prop="type"> + <el-select v-model="queryParams.type" placeholder="请选择短信类型" clearable> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="开启状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="请选择开启状态" clearable> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="模板编码" prop="code"> <el-input - v-model="sendSmsForm.content" - type="textarea" - placeholder="请输入模板内容" - readonly + v-model="queryParams.code" + placeholder="请输入模板编码" + clearable + @keyup.enter="handleQuery" /> </el-form-item> - <el-form-item label="手机号" prop="mobile"> - <el-input v-model="sendSmsForm.mobile" placeholder="请输入手机号" /> - </el-form-item> - <el-form-item - v-for="param in sendSmsForm.params" - :key="param" - :label="'参数 {' + param + '}'" - :prop="'templateParams.' + param" - > + <el-form-item label="短信 API 的模板编号" prop="apiTemplateId"> <el-input - v-model="sendSmsForm.templateParams[param]" - :placeholder="'请输入 ' + param + ' 参数'" + v-model="queryParams.apiTemplateId" + placeholder="请输入短信 API 的模板编号" + clearable + @keyup.enter="handleQuery" /> </el-form-item> + <el-form-item label="短信渠道" prop="channelId"> + <el-select v-model="queryParams.channelId" placeholder="请选择短信渠道" clearable> + <el-option + v-for="channel in channelOptions" + :key="channel.id" + :value="channel.id" + :label=" + channel.signature + + '【' + + getDictObj(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, channel.code) + + '】' + " + /> + </el-select> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + style="width: 240px" + value-format="yyyy-MM-dd HH:mm:ss" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="['00:00:00', '23:59:59']" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + </el-form-item> </el-form> - <!-- 操作按钮 --> - <template #footer> - <XButton - type="primary" - :title="t('action.test')" - :loading="actionLoading" - @click="sendSmsTest()" + <!-- 操作工具栏 --> + <el-row> + <el-col :span="12"> + <el-row :gutter="10"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + @click="handleAdd('template.addTitle')" + v-hasPermi="['system:sms-template:create']" + ><Icon icon="ep:plus" class="mr-5px" />新增</el-button + > + </el-col> + <el-col :span="1.5"> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['system:sms-template:export']" + > + <Icon icon="ep:download" class="mr-5px" /> 导出 + </el-button> + </el-col> + </el-row> + </el-col> + <el-col :span="12"> + <right-toolbar v-model:showSearch="showSearch" @query-table="getList" /> + </el-col> + </el-row> + <!-- 列表 --> + <el-table v-loading="loading" :data="templateList" align="center"> + <el-table-column + label="模板编码" + align="center" + prop="code" + width="120" + :show-overflow-tooltip="true" /> - <XButton :title="t('dialog.close')" @click="sendVisible = false" /> - </template> - </XModal> + <el-table-column + label="模板名称" + align="center" + prop="name" + width="120" + :show-overflow-tooltip="true" + /> + <el-table-column + label="模板内容" + align="center" + prop="content" + width="200" + :show-overflow-tooltip="true" + /> + <el-table-column label="短信类型" align="center" prop="type"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE" :value="scope.row.type" /> + </template> + </el-table-column> + <el-table-column label="状态" align="center" prop="status" width="80"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> + </template> + </el-table-column> + <el-table-column label="备注" align="center" prop="remark" /> + <el-table-column + label="短信 API 的模板编号" + align="center" + prop="apiTemplateId" + width="200" + :show-overflow-tooltip="true" + /> + <el-table-column label="短信渠道" align="center" width="120"> + <template #default="scope"> + <div>{{ formatChannelSignature(scope.row.channelId) }}</div> + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="scope.row.channelCode" /> + </template> + </el-table-column> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column + label="操作" + align="center" + class-name="small-padding fixed-width" + width="210" + fixed="right" + > + <template #default="scope"> + <el-button + link + type="primary" + @click="handleSendSms('template.sendSms', scope.row)" + v-hasPermi="['system:sms-template:send-sms']" + ><Icon icon="ep:share" class="mr-3px" />测试</el-button + > + <el-button + link + type="primary" + @click="handleUpdate('template.updtaeTitle', scope.row)" + v-hasPermi="['system:sms-template:update']" + ><Icon icon="ep:edit" class="mr-3px" />修改</el-button + > + <el-button + link + type="primary" + @click="handleDelete(scope.row)" + v-hasPermi="['system:sms-template:delete']" + ><Icon icon="ep:delete" class="mr-3px" />删除</el-button + > + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + + <!-- 表单弹窗:添加/修改 --> + <SmsTemplateFrom ref="modalRef" :channelOptions="channelOptions" @success="getList" /> </template> <script setup lang="ts" name="SmsTemplate"> -import type { FormExpose } from '@/components/Form' -// 业务相关的 import -import * as SmsTemplateApi from '@/api/system/sms/smsTemplate' -import { rules, allSchemas } from './sms.template.data' - -const { t } = useI18n() // 国际化 +import { DICT_TYPE, getDictOptions, getDictObj } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' +import * as templateApi from '@/api/system/sms/smsTemplate' +import * as SmsChannelApi from '@/api/system/sms/smsChannel' +import download from '@/utils/download' +import SmsTemplateFrom from './form.vue' const message = useMessage() // 消息弹窗 +// const { t } = useI18n() // 国际化 -// 列表相关的变量 -const [registerTable, { reload, deleteData }] = useXTable({ - allSchemas: allSchemas, - getListApi: SmsTemplateApi.getSmsTemplatePageApi, - deleteApi: SmsTemplateApi.deleteSmsTemplateApi +// 弹出层ref +const modalRef = ref() +// 遮罩层 +const loading = ref(true) +// 导出遮罩层 +// const exportLoading = ref(false) +// 显示搜索条件 +const showSearch = ref(true) +// 表单ref +const queryForm = ref() +// 查询参数 +const queryParams = ref<templateApi.SmsTemplatePageReqVO>({ + pageNo: 1, + pageSize: 10, + type: null, + status: null, + code: '', + content: '', + apiTemplateId: '', + channelId: null, + createTime: [] }) - -// 弹窗相关的变量 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const actionType = ref('') // 操作按钮的类型 -const actionLoading = ref(false) // 按钮 Loading -const formRef = ref<FormExpose>() // 表单 Ref -const detailData = ref() // 详情 Ref - -// 设置标题 -const setDialogTile = (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true -} - -// 新增操作 -const handleCreate = () => { - setDialogTile('create') -} - -// 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - // 设置数据 - const res = await SmsTemplateApi.getSmsTemplateApi(rowId) - unref(formRef)?.setValues(res) -} - -// 详情操作 -const handleDetail = async (rowId: number) => { - setDialogTile('detail') - // 设置数据 - const res = await SmsTemplateApi.getSmsTemplateApi(rowId) - detailData.value = res -} - -// 提交按钮 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - actionLoading.value = true - // 提交请求 - try { - const data = unref(formRef)?.formModel as SmsTemplateApi.SmsTemplateVO - if (actionType.value === 'create') { - await SmsTemplateApi.createSmsTemplateApi(data) - message.success(t('common.createSuccess')) - } else { - await SmsTemplateApi.updateSmsTemplateApi(data) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false - } finally { - actionLoading.value = false - // 刷新列表 - await reload() - } - } +// 总条数 +const total = ref(0) +// 短信模板列表 +const templateList = ref([]) +/** 查询列表 */ +const getList = () => { + loading.value = true + // 执行查询 + templateApi.getSmsTemplatePageApi(queryParams.value).then((response) => { + templateList.value = response.list + total.value = response.total + loading.value = false }) } - -// ========== 测试相关 ========== -const sendSmsForm = ref({ - content: '', - params: {}, - mobile: '', - templateCode: '', - templateParams: {} +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.value.pageNo = 1 + getList() +} +/** 重置按钮操作 */ +const resetQuery = () => { + resetForm() + handleQuery() +} +/** 重置搜索表单 */ +const resetForm = () => { + queryParams.value = { + pageNo: 1, + pageSize: 10, + type: null, + status: null, + code: '', + content: '', + apiTemplateId: '', + channelId: null, + createTime: [] + } + queryForm.value?.resetFields() +} +// 短信渠道 +const channelOptions = ref<SmsChannelApi.SmsChannelListVO[]>([]) +onMounted(() => { + SmsChannelApi.getSimpleSmsChannels().then((res) => { + channelOptions.value = res + }) }) -const sendSmsRules = ref({ - mobile: [{ required: true, message: '手机不能为空', trigger: 'blur' }], - templateCode: [{ required: true, message: '模版编号不能为空', trigger: 'blur' }], - templateParams: {} -}) -const sendVisible = ref(false) - -const handleSendSms = (row: any) => { - sendSmsForm.value.content = row.content - sendSmsForm.value.params = row.params - sendSmsForm.value.templateCode = row.code - sendSmsForm.value.templateParams = row.params.reduce(function (obj, item) { - obj[item] = undefined - return obj - }, {}) - sendSmsRules.value.templateParams = row.params.reduce(function (obj, item) { - obj[item] = { required: true, message: '参数 ' + item + ' 不能为空', trigger: 'change' } - return obj - }, {}) - sendVisible.value = true +/** 格式化短信渠道 */ +const formatChannelSignature = (channelId: number) => { + channelOptions.value.forEach((item) => { + if (item.id === channelId) { + return item.signature + } + }) + return '找不到签名:' + channelId +} +/** 新增按钮操作 */ +const handleAdd = (type: string) => { + modalRef.value.openModal({ type }) +} +/** 修改按钮操作 */ +// const handleUpdate = (row) => {} +const exportLoading = ref(false) +/** 导出按钮操作 */ +const handleExport = () => { + // 处理查询参数 + let params = { ...queryParams.value } as templateApi.SmsTemplateExportReqVO + // 执行导出 + message + .confirm('是否确认导出所有短信模板数据项?', '警告') + .then(() => { + exportLoading.value = true + return templateApi.exportPostApi(params) + }) + .then((response) => { + download.excel(response, '短信模板.xls') + exportLoading.value = false + }) + .catch(() => {}) +} +/** 发送短信按钮 */ +const handleSendSms = (type, row: any) => { + modalRef.value.openModal({ type, row }) +} +const handleUpdate = (type: string, { id }: { id: number }) => { + modalRef.value.openModal({ type, id }) +} +/** 删除按钮操作 */ +const handleDelete = ({ id }: { id: number }) => { + message + .confirm('是否确认删除短信模板编号为"' + id + '"的数据项?') + .then(function () { + return templateApi.deleteSmsTemplateApi(id) + }) + .then(() => { + getList() + message.success('删除成功') + }) + .catch(() => {}) } -const sendSmsTest = async () => { - const data: SmsTemplateApi.SendSmsReqVO = { - mobile: sendSmsForm.value.mobile, - templateCode: sendSmsForm.value.templateCode, - templateParams: sendSmsForm.value.templateParams as unknown as Map<string, Object> - } - const res = await SmsTemplateApi.sendSmsApi(data) - if (res) { - message.success('提交发送成功!发送结果,见发送日志编号:' + res) - } - sendVisible.value = false -} +getList() </script> diff --git a/src/views/system/sms/smsTemplate/sms.template.data.ts b/src/views/system/sms/smsTemplate/sms.template.data.ts deleted file mode 100644 index 6178d6c2..00000000 --- a/src/views/system/sms/smsTemplate/sms.template.data.ts +++ /dev/null @@ -1,107 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -import * as smsApi from '@/api/system/sms/smsChannel' -const { t } = useI18n() // 国际化 -const tenantPackageOption = [] -const getTenantPackageOptions = async () => { - const res = await smsApi.getSimpleSmsChannels() - console.log(res, 'resresres') - res.forEach((tenantPackage: TenantPackageVO) => { - tenantPackageOption.push({ - key: tenantPackage.id, - value: tenantPackage.id, - label: tenantPackage.signature - }) - }) -} -getTenantPackageOptions() -// 表单校验 -export const rules = reactive({ - type: [required], - status: [required], - code: [required], - name: [required], - content: [required], - apiTemplateId: [required], - channelId: [required] -}) - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'id', - primaryTitle: '模板编号', - action: true, - actionWidth: '280', - columns: [ - { - title: '短信渠道编码', - field: 'channelId', - isSearch: false, - isForm: true, - isTable: false, - form: { - component: 'Select', - componentProps: { - options: tenantPackageOption - } - } - }, - { - title: '模板编码', - field: 'code', - isSearch: true - }, - { - title: '模板名称', - field: 'name', - isSearch: true - }, - { - title: '模板内容', - field: 'content' - }, - { - title: '短信 API 的模板编号', - field: 'apiTemplateId', - isSearch: true - }, - { - title: '短信类型', - field: 'type', - dictType: DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE, - dictClass: 'number', - isSearch: true, - table: { - width: 80 - } - }, - { - title: t('common.status'), - field: 'status', - dictType: DICT_TYPE.COMMON_STATUS, - dictClass: 'number', - isSearch: true, - table: { - width: 80 - } - }, - { - title: t('form.remark'), - field: 'remark', - isTable: false - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From 16c4bb5dca18177a2c78d0de8573df870e58aeff Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Tue, 21 Mar 2023 16:46:16 +0800 Subject: [PATCH 051/184] =?UTF-8?q?update:=20=E4=BF=AE=E5=A4=8D=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=9F=AD=E4=BF=A1=E5=8F=91=E9=80=81=E6=88=90=E5=8A=9F?= =?UTF-8?q?=E5=90=8E=E5=BC=B9=E7=AA=97=E4=B8=8D=E5=85=B3=E9=97=AD=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/auto-components.d.ts | 13 +++---------- src/types/auto-imports.d.ts | 2 +- src/views/system/sms/smsTemplate/form.vue | 11 ++++++++--- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index 7b10d679..d6c922f8 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -1,7 +1,5 @@ -/* eslint-disable */ -/* prettier-ignore */ -// @ts-nocheck -// Generated by unplugin-vue-components +// generated by unplugin-vue-components +// We suggest you to commit this file into source control // Read more: https://github.com/vuejs/core/pull/3399 import '@vue/runtime-core' @@ -23,8 +21,6 @@ declare module '@vue/runtime-core' { DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default'] Echart: typeof import('./../components/Echart/src/Echart.vue')['default'] Editor: typeof import('./../components/Editor/src/Editor.vue')['default'] - ElAutoResizer: typeof import('element-plus/es')['ElAutoResizer'] - ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElBadge: typeof import('element-plus/es')['ElBadge'] ElButton: typeof import('element-plus/es')['ElButton'] ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] @@ -54,7 +50,6 @@ declare module '@vue/runtime-core' { ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] - ElImage: typeof import('element-plus/es')['ElImage'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] @@ -69,15 +64,12 @@ declare module '@vue/runtime-core' { ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElSelect: typeof import('element-plus/es')['ElSelect'] ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] - ElSpace: typeof import('element-plus/es')['ElSpace'] ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElTable: typeof import('element-plus/es')['ElTable'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] ElTag: typeof import('element-plus/es')['ElTag'] - ElTimeline: typeof import('element-plus/es')['ElTimeline'] - ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTransfer: typeof import('element-plus/es')['ElTransfer'] ElTree: typeof import('element-plus/es')['ElTree'] @@ -105,6 +97,7 @@ declare module '@vue/runtime-core' { ScriptTask: typeof import('./../components/bpmnProcessDesigner/package/penal/task/task-components/ScriptTask.vue')['default'] Search: typeof import('./../components/Search/src/Search.vue')['default'] SignalAndMessage: typeof import('./../components/bpmnProcessDesigner/package/penal/signal-message/SignalAndMessage.vue')['default'] + Src: typeof import('./../components/RightToolbar/src/index.vue')['default'] Sticky: typeof import('./../components/Sticky/src/Sticky.vue')['default'] Table: typeof import('./../components/Table/src/Table.vue')['default'] Tooltip: typeof import('./../components/Tooltip/src/Tooltip.vue')['default'] diff --git a/src/types/auto-imports.d.ts b/src/types/auto-imports.d.ts index 7c9f5ff1..bd749b8e 100644 --- a/src/types/auto-imports.d.ts +++ b/src/types/auto-imports.d.ts @@ -70,5 +70,5 @@ declare global { // for type re-export declare global { // @ts-ignore - export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue' + export type { Component,ComponentPublicInstance,ComputedRef,InjectionKey,PropType,Ref,VNode } from 'vue' } diff --git a/src/views/system/sms/smsTemplate/form.vue b/src/views/system/sms/smsTemplate/form.vue index 193ae2e3..4c9825f9 100644 --- a/src/views/system/sms/smsTemplate/form.vue +++ b/src/views/system/sms/smsTemplate/form.vue @@ -1,5 +1,6 @@ <template> <Dialog :title="modelTitle" v-model="modelVisible"> + <!-- 修改/新增 --> <el-form v-if="['template.addTitle', 'template.updtaeTitle'].includes(formType)" ref="formRef" @@ -55,6 +56,7 @@ <el-input v-model="formData.remark" placeholder="请输入备注" /> </el-form-item> </el-form> + <!-- 短信测试 --> <el-form v-if="formType === 'template.sendSms'" ref="sendSmsFormRef" @@ -152,7 +154,9 @@ const sendSmsFormRef = ref() /** 打开弹窗 */ interface openModalOption { type: string + // 编辑传id id?: '' + // 短信测试传row row?: any } const openModal = async (option: openModalOption) => { @@ -188,16 +192,16 @@ const openModal = async (option: openModalOption) => { } defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 -/** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +/** 提交表单 */ const submitForm = async () => { + formLoading.value = true + // 提交请求 if (['template.addTitle', 'template.updtaeTitle'].includes(formType.value)) { // 校验表单 if (!formRef) return const valid = await formRef.value.validate() if (!valid) return - // 提交请求 - formLoading.value = true try { const data = formData.value as templateApi.SmsTemplateVO if (formType.value === 'template.addTitle') { @@ -258,5 +262,6 @@ const sendSmsTest = async () => { message.success('提交发送成功!发送结果,见发送日志编号:' + res) } formLoading.value = false + modelVisible.value = false } </script> From 7d1d87a1ac5943ff6b27be738f191ae82e95895b Mon Sep 17 00:00:00 2001 From: dtsz <314942997@qq.com> Date: Tue, 21 Mar 2023 20:18:07 +0800 Subject: [PATCH 052/184] =?UTF-8?q?=E9=94=99=E8=AF=AF=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/infra/apiErrorLog/index.ts | 4 +- .../infra/apiErrorLog/apiErrorLog.data.ts | 76 ----- src/views/infra/apiErrorLog/detail.vue | 76 +++++ src/views/infra/apiErrorLog/index.vue | 311 +++++++++++++----- 4 files changed, 305 insertions(+), 162 deletions(-) delete mode 100644 src/views/infra/apiErrorLog/apiErrorLog.data.ts create mode 100644 src/views/infra/apiErrorLog/detail.vue diff --git a/src/api/infra/apiErrorLog/index.ts b/src/api/infra/apiErrorLog/index.ts index 06515c36..779cfd61 100644 --- a/src/api/infra/apiErrorLog/index.ts +++ b/src/api/infra/apiErrorLog/index.ts @@ -46,7 +46,7 @@ export interface ApiErrorLogExportReqVO { } // 查询列表API 访问日志 -export const getApiErrorLogPageApi = (params: ApiErrorLogPageReqVO) => { +export const getApiErrorLogPageApi = (params: PageParam) => { return request.get({ url: '/infra/api-error-log/page', params }) } @@ -58,7 +58,7 @@ export const updateApiErrorLogPageApi = (id: number, processStatus: number) => { } // 导出API 访问日志 -export const exportApiErrorLogApi = (params: ApiErrorLogExportReqVO) => { +export const exportApiErrorLogApi = (params) => { return request.download({ url: '/infra/api-error-log/export-excel', params diff --git a/src/views/infra/apiErrorLog/apiErrorLog.data.ts b/src/views/infra/apiErrorLog/apiErrorLog.data.ts deleted file mode 100644 index a539c167..00000000 --- a/src/views/infra/apiErrorLog/apiErrorLog.data.ts +++ /dev/null @@ -1,76 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'id', - primaryTitle: '日志编号', - action: true, - actionWidth: '300', - columns: [ - { - title: '链路追踪', - field: 'traceId', - isTable: false - }, - { - title: '用户编号', - field: 'userId', - isSearch: true - }, - { - title: '用户类型', - field: 'userType', - dictType: DICT_TYPE.USER_TYPE, - isSearch: true - }, - { - title: '应用名', - field: 'applicationName', - isSearch: true - }, - { - title: '请求方法名', - field: 'requestMethod' - }, - { - title: '请求地址', - field: 'requestUrl', - isSearch: true - }, - { - title: '异常发生时间', - field: 'exceptionTime', - formatter: 'formatDate', - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - }, - { - title: '异常名', - field: 'exceptionName' - }, - { - title: '处理状态', - field: 'processStatus', - dictType: DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS, - dictClass: 'number', - isSearch: true - }, - { - title: '处理人', - field: 'processUserId', - isTable: false - }, - { - title: '处理时间', - field: 'processTime', - formatter: 'formatDate', - isTable: false - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) diff --git a/src/views/infra/apiErrorLog/detail.vue b/src/views/infra/apiErrorLog/detail.vue new file mode 100644 index 00000000..9ede0f1b --- /dev/null +++ b/src/views/infra/apiErrorLog/detail.vue @@ -0,0 +1,76 @@ +<template> + <Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="500" width="800"> + <el-descriptions border :column="1"> + <el-descriptions-item label="日志主键" min-width="120"> + {{ detailData.id }} + </el-descriptions-item> + <el-descriptions-item label="链路追踪"> + {{ detailData.traceId }} + </el-descriptions-item> + <el-descriptions-item label="应用名"> + {{ detailData.applicationName }} + </el-descriptions-item> + <el-descriptions-item label="用户信息"> + {{ detailData.userId }} | + <dict-tag :type="DICT_TYPE.USER_TYPE" :value="detailData.userType" /> + | {{ detailData.userIp }} | {{ detailData.userAgent }} + </el-descriptions-item> + <el-descriptions-item label="请求信息"> + {{ detailData.requestMethod }} | {{ detailData.requestUrl }} + </el-descriptions-item> + <el-descriptions-item label="请求参数"> + {{ detailData.requestParams }} + </el-descriptions-item> + <el-descriptions-item label="异常时间"> + {{ formatDate(detailData.exceptionTime, 'YYYY-MM-DD HH:mm:ss') }} + </el-descriptions-item> + <el-descriptions-item label="异常名"> + {{ detailData.exceptionName }} + </el-descriptions-item> + <el-descriptions-item label="异常名" v-if="detailData.exceptionStackTrace"> + <el-input + type="textarea" + :readonly="true" + :autosize="{ maxRows: 20 }" + v-model="detailData.exceptionStackTrace" + /> + </el-descriptions-item> + <el-descriptions-item label="处理状态"> + <dict-tag + :type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS" + :value="detailData.processStatus" + /> + </el-descriptions-item> + <el-descriptions-item label="处理人" v-if="detailData.processUserId"> + {{ detailData.processUserId }} + </el-descriptions-item> + <el-descriptions-item label="处理时间" v-if="detailData.processTime"> + {{ formatDate(detailData.processTime, 'YYYY-MM-DD HH:mm:ss') }} + </el-descriptions-item> + </el-descriptions> + </Dialog> +</template> + +<script setup lang="ts"> +import { DICT_TYPE } from '@/utils/dict' +import { formatDate } from '@/utils/formatTime' +import * as ApiErrorLog from '@/api/infra/apiErrorLog' + +const modelVisible = ref(false) // 弹窗的是否展示 +const detailLoading = ref(false) // 表单的加载中 +const detailData = ref() // 详情数据 + +/** 打开弹窗 */ +const openModal = async (data: ApiErrorLog.ApiErrorLogVO) => { + modelVisible.value = true + // 设置数据 + detailLoading.value = true + try { + detailData.value = data + } finally { + detailLoading.value = false + } +} + +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +</script> diff --git a/src/views/infra/apiErrorLog/index.vue b/src/views/infra/apiErrorLog/index.vue index 4193351a..664546cb 100644 --- a/src/views/infra/apiErrorLog/index.vue +++ b/src/views/infra/apiErrorLog/index.vue @@ -1,99 +1,242 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <!-- 操作:导出 --> - <template #toolbar_buttons> - <XButton - type="warning" - preIcon="ep:download" - :title="t('action.export')" - @click="exportList('错误数据.xls')" + <content-wrap> + <!-- 搜索工作栏 --> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="用户编号" prop="userId"> + <el-input + v-model="queryParams.userId" + placeholder="请输入用户编号" + clearable + @keyup.enter="handleQuery" + class="!w-240px" /> - </template> - <template #duration_default="{ row }"> - <span>{{ row.duration + 'ms' }}</span> - </template> - <template #resultCode_default="{ row }"> - <span>{{ row.resultCode === 0 ? '成功' : '失败(' + row.resultMsg + ')' }}</span> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['infra:api-access-log:query']" - @click="handleDetail(row)" + </el-form-item> + <el-form-item label="用户类型" prop="userType"> + <el-select + v-model="queryParams.userType" + placeholder="请选择用户类型" + clearable + class="!w-240px" + > + <el-option + v-for="dict in getDictOptions(DICT_TYPE.USER_TYPE)" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + <el-form-item label="应用名" prop="applicationName"> + <el-input + v-model="queryParams.applicationName" + placeholder="请输入应用名" + clearable + @keyup.enter="handleQuery" + class="!w-240px" /> - <XTextButton - preIcon="ep:cpu" - title="已处理" - v-if="row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT" - v-hasPermi="['infra:api-error-log:update-status']" - @click="handleProcessClick(row, InfraApiErrorLogProcessStatusEnum.DONE, '已处理')" + </el-form-item> + <el-form-item label="异常时间" prop="exceptionTime"> + <el-date-picker + v-model="queryParams.exceptionTime" + value-format="yyyy-MM-dd HH:mm:ss" + type="daterange" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="!w-240px" /> - <XTextButton - preIcon="ep:mute-notification" - title="已忽略" - v-if="row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT" - v-hasPermi="['infra:api-error-log:update-status']" - @click="handleProcessClick(row, InfraApiErrorLogProcessStatusEnum.IGNORE, '已忽略')" - /> - </template> - </XTable> - </ContentWrap> - <XModal v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(详情) --> - <Descriptions :schema="allSchemas.detailSchema" :data="detailData" /> - <!-- 操作按钮 --> - <template #footer> - <XButton :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> + </el-form-item> + <el-form-item label="处理状态" prop="processStatus"> + <el-select v-model="queryParams.processStatus" placeholder="请选择处理状态" clearable> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS)" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['infra:api-error-log:export']" + > + <Icon icon="ep:download" class="mr-5px" /> 导出 + </el-button> + </el-form-item> + </el-form> + </content-wrap> + <!-- 列表 --> + <content-wrap> + <el-table v-loading="loading" :data="list"> + <el-table-column label="日志编号" align="center" prop="id" /> + <el-table-column label="用户编号" align="center" prop="userId" /> + <el-table-column label="用户类型" align="center" prop="userType"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" /> + </template> + </el-table-column> + <el-table-column label="应用名" align="center" prop="applicationName" /> + <el-table-column label="请求方法名" align="center" prop="requestMethod" /> + <el-table-column label="请求地址" align="center" prop="requestUrl" width="250" /> + <el-table-column label="异常发生时间" align="center" prop="exceptionTime" width="180"> + <template #default="scope"> + <span>{{ scope.row.exceptionTime }}</span> + </template> + </el-table-column> + <el-table-column label="异常名" align="center" prop="exceptionName" width="250" /> + <el-table-column label="处理状态" align="center" prop="processStatus"> + <template #default="scope"> + <dict-tag + :type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS" + :value="scope.row.processStatus" + /> + </template> + </el-table-column> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openModal(scope.row)" + v-hasPermi="['infra:api-access-log:query']" + > + 详细 + </el-button> + <el-button + link + type="primary" + v-if="scope.row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT" + @click=" + handleProcessClick(InfraApiErrorLogProcessStatusEnum.DONE, '已处理', scope.row.id) + " + v-hasPermi="['infra:api-error-log:update-status']" + > + 已处理 + </el-button> + <el-button + link + type="primary" + icon="el-icon-check" + v-if="scope.row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT" + @click=" + handleProcessClick(InfraApiErrorLogProcessStatusEnum.IGNORE, '已忽略', scope.row.id) + " + v-hasPermi="['infra:api-error-log:update-status']" + > + 已忽略 + </el-button> + </template> + </el-table-column> + </el-table> + + <!-- 分页组件 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + + <!-- 表单弹窗:详情 --> + <api-error-log-detail ref="modalRef" /> </template> + <script setup lang="ts" name="ApiErrorLog"> -import { allSchemas } from './apiErrorLog.data' +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import download from '@/utils/download' import * as ApiErrorLogApi from '@/api/infra/apiErrorLog' +import ApiErrorLogDetail from './detail.vue' import { InfraApiErrorLogProcessStatusEnum } from '@/utils/constants' +const message = useMessage() // 消息弹窗 -const { t } = useI18n() // 国际化 -const message = useMessage() - -// ========== 列表相关 ========== -const [registerTable, { reload, exportList }] = useXTable({ - allSchemas: allSchemas, - getListApi: ApiErrorLogApi.getApiErrorLogPageApi, - exportListApi: ApiErrorLogApi.exportApiErrorLogApi +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + userId: null, + userType: null, + applicationName: null, + requestUrl: null, + processStatus: null, + exceptionTime: [] }) -// ========== 详情相关 ========== -const detailData = ref() // 详情 Ref -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('') // 弹出层标题 +const queryFormRef = ref() // 搜索的表单 +const exportLoading = ref(false) // 导出的加载中 -// 详情操作 -const handleDetail = (row: ApiErrorLogApi.ApiErrorLogVO) => { - // 设置数据 - detailData.value = row - dialogTitle.value = t('action.detail') - dialogVisible.value = true +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await ApiErrorLogApi.getApiErrorLogPageApi(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() } -// 异常处理操作 -const handleProcessClick = ( - row: ApiErrorLogApi.ApiErrorLogVO, - processSttatus: number, - type: string -) => { - message - .confirm('确认标记为' + type + '?', t('common.reminder')) - .then(async () => { - await ApiErrorLogApi.updateApiErrorLogPageApi(row.id, processSttatus) - message.success(t('common.updateSuccess')) - }) - .finally(async () => { - // 刷新列表 - await reload() - }) - .catch(() => {}) +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() } + +/** 详情操作 */ +const modalRef = ref() +const openModal = (data: ApiErrorLogApi.ApiErrorLogVO) => { + modalRef.value.openModal(data) +} + +/** 处理已处理 / 已忽略的操作 **/ +const handleProcessClick = async (processStatus: number, type: string, id: number) => { + try { + // 操作的二次确认 + await message.confirm('确认标记为' + type + '?') + // 执行操作 + await ApiErrorLogApi.updateApiErrorLogPageApi(id, processStatus) + await message.success(type) + // 刷新列表 + await getList() + } catch {} +} + +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await ApiErrorLogApi.exportApiErrorLogApi(queryParams) + download.excel(data, '操作日志.xls') + } catch { + } finally { + exportLoading.value = false + } +} + +/** 初始化 **/ +onMounted(() => { + getList() +}) </script> From 6d8ed5ee3c6163a44cdb985d35b8a9293cdc75d4 Mon Sep 17 00:00:00 2001 From: fengjingtao <fessor@139.com> Date: Tue, 21 Mar 2023 23:33:40 +0800 Subject: [PATCH 053/184] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E9=87=8D=E5=86=99=E5=B0=9D=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/user/index.vue | 1385 ++++++++++++++++++------------- 1 file changed, 825 insertions(+), 560 deletions(-) diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index 2f9ba9b0..c0f922e8 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -1,576 +1,841 @@ <template> - <div class="flex"> - <el-card class="w-1/5 user" :gutter="12" shadow="always"> - <template #header> - <div class="card-header"> - <span>部门列表</span> - <XTextButton title="修改部门" @click="handleDeptEdit()" /> + <div class="app-container"> + <!-- 搜索工作栏 --> + <el-row :gutter="20"> + <!--部门数据--> + <el-col :span="4" :xs="24"> + <div class="head-container"> + <el-input + v-model="deptName" + placeholder="请输入部门名称" + clearable + size="small" + prefix-icon="el-icon-search" + style="margin-bottom: 20px" + /> </div> - </template> - <el-input v-model="filterText" placeholder="搜索部门" /> - <el-scrollbar height="650"> - <el-tree - ref="treeRef" - node-key="id" - default-expand-all - :data="deptOptions" - :props="defaultProps" - :highlight-current="true" - :filter-node-method="filterNode" - :expand-on-click-node="false" - @node-click="handleDeptNodeClick" - /> - </el-scrollbar> - </el-card> - <el-card class="w-4/5 user" style="margin-left: 10px" :gutter="12" shadow="hover"> - <template #header> - <div class="card-header"> - <span>{{ tableTitle }}</span> + <div class="head-container"> + <el-tree + :data="deptOptions" + :props="defaultProps" + :expand-on-click-node="false" + :filter-node-method="filterNode" + ref="tree" + default-expand-all + highlight-current + @node-click="handleNodeClick" + /> </div> - </template> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <!-- 操作:新增 --> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['system:user:create']" - @click="handleCreate()" - /> - <!-- 操作:导入用户 --> - <XButton - type="warning" - preIcon="ep:upload" - :title="t('action.import')" - v-hasPermi="['system:user:import']" - @click="importDialogVisible = true" - /> - <!-- 操作:导出用户 --> - <XButton - type="warning" - preIcon="ep:download" - :title="t('action.export')" - v-hasPermi="['system:user:export']" - @click="exportList('用户数据.xls')" - /> - </template> - <template #status_default="{ row }"> - <el-switch - v-model="row.status" - :active-value="0" - :inactive-value="1" - @change="handleStatusChange(row)" - /> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:编辑 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['system:user:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['system:user:update']" - @click="handleDetail(row.id)" - /> - <el-dropdown - class="p-0.5" - v-hasPermi="[ - 'system:user:update-password', - 'system:permission:assign-user-role', - 'system:user:delete' - ]" - > - <XTextButton :title="t('action.more')" postIcon="ep:arrow-down" /> - <template #dropdown> - <el-dropdown-menu> - <el-dropdown-item> - <!-- 操作:重置密码 --> - <XTextButton - preIcon="ep:key" - title="重置密码" - v-hasPermi="['system:user:update-password']" - @click="handleResetPwd(row)" - /> - </el-dropdown-item> - <el-dropdown-item> - <!-- 操作:分配角色 --> - <XTextButton - preIcon="ep:key" - title="分配角色" - v-hasPermi="['system:permission:assign-user-role']" - @click="handleRole(row)" - /> - </el-dropdown-item> - <el-dropdown-item> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['system:user:delete']" - @click="deleteData(row.id)" - /> - </el-dropdown-item> - </el-dropdown-menu> - </template> - </el-dropdown> - </template> - </XTable> - </el-card> - </div> - <XModal v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(添加 / 修改) --> - <Form - v-if="['create', 'update'].includes(actionType)" - :rules="rules" - :schema="allSchemas.formSchema" - ref="formRef" - > - <template #deptId="form"> - <el-tree-select - node-key="id" - v-model="form['deptId']" - :props="defaultProps" - :data="deptOptions" - check-strictly - /> - </template> - <template #postIds="form"> - <el-select v-model="form['postIds']" multiple :placeholder="t('common.selectText')"> - <el-option - v-for="item in postOptions" - :key="item.id" - :label="item.name" - :value="(item.id as unknown as number)" - /> - </el-select> - </template> - </Form> - <!-- 对话框(详情) --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - > - <template #deptId="{ row }"> - <el-tag>{{ dataFormater(row.deptId) }}</el-tag> - </template> - <template #postIds="{ row }"> - <template v-if="row.postIds !== ''"> - <el-tag v-for="(post, index) in row.postIds" :key="index" index=""> - <template v-for="postObj in postOptions"> - {{ post === postObj.id ? postObj.name : '' }} - </template> - </el-tag> - </template> - <template v-else> </template> - </template> - </Descriptions> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="loading" - @click="submitForm()" - /> - <!-- 按钮:关闭 --> - <XButton :loading="loading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> - <!-- 分配用户角色 --> - <XModal v-model="roleDialogVisible" title="分配角色"> - <el-form :model="userRole" label-width="140px" :inline="true"> - <el-form-item label="用户名称"> - <el-tag>{{ userRole.username }}</el-tag> - </el-form-item> - <el-form-item label="用户昵称"> - <el-tag>{{ userRole.nickname }}</el-tag> - </el-form-item> - <el-form-item label="角色"> - <el-transfer - v-model="userRole.roleIds" - :titles="['角色列表', '已选择']" - :props="{ - key: 'id', - label: 'name' - }" - :data="roleOptions" - /> - </el-form-item> - </el-form> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton type="primary" :title="t('action.save')" :loading="loading" @click="submitRole()" /> - <!-- 按钮:关闭 --> - <XButton :title="t('dialog.close')" @click="roleDialogVisible = false" /> - </template> - </XModal> - <!-- 导入 --> - <XModal v-model="importDialogVisible" :title="importDialogTitle"> - <el-form class="drawer-multiColumn-form" label-width="150px"> - <el-form-item label="模板下载 :"> - <XButton type="primary" prefix="ep:download" title="点击下载" @click="handleImportTemp()" /> - </el-form-item> - <el-form-item label="文件上传 :"> - <el-upload - ref="uploadRef" - :action="updateUrl + '?updateSupport=' + updateSupport" - :headers="uploadHeaders" - :drag="true" - :limit="1" - :multiple="true" - :show-file-list="true" - :disabled="uploadDisabled" - :before-upload="beforeExcelUpload" - :on-exceed="handleExceed" - :on-success="handleFileSuccess" - :on-error="excelUploadError" - :auto-upload="false" - accept="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + </el-col> + <!--用户数据--> + <el-col :span="20" :xs="24"> + <el-form + :model="queryParams" + ref="queryForm" + size="small" + :inline="true" + v-show="showSearch" + label-width="68px" > - <Icon icon="ep:upload-filled" /> - <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> - <template #tip> - <div class="el-upload__tip">请上传 .xls , .xlsx 标准格式文件</div> - </template> - </el-upload> - </el-form-item> - <el-form-item label="是否更新已经存在的用户数据:"> - <el-checkbox v-model="updateSupport" /> - </el-form-item> - </el-form> - <template #footer> - <!-- 按钮:保存 --> - <XButton - type="warning" - preIcon="ep:upload-filled" - :title="t('action.save')" - @click="submitFileForm()" - /> - <!-- 按钮:关闭 --> - <XButton :title="t('dialog.close')" @click="importDialogVisible = false" /> - </template> - </XModal> + <el-form-item label="用户名称" prop="username"> + <el-input + v-model="queryParams.username" + placeholder="请输入用户名称" + clearable + style="width: 240px" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="手机号码" prop="mobile"> + <el-input + v-model="queryParams.mobile" + placeholder="请输入手机号码" + clearable + style="width: 240px" + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select + v-model="queryParams.status" + placeholder="用户状态" + clearable + style="width: 240px" + > + <el-option + v-for="dict in statusDictDatas" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + style="width: 240px" + value-format="yyyy-MM-dd HH:mm:ss" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="['00:00:00', '23:59:59']" + /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="el-icon-plus" + size="mini" + @click="handleAdd" + v-hasPermi="['system:user:create']" + >新增</el-button + > + </el-col> + <el-col :span="1.5"> + <el-button + type="info" + icon="el-icon-upload2" + size="mini" + @click="handleImport" + v-hasPermi="['system:user:import']" + >导入</el-button + > + </el-col> + <el-col :span="1.5"> + <el-button + type="warning" + icon="el-icon-download" + size="mini" + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['system:user:export']" + >导出</el-button + > + </el-col> + <right-toolbar + :showSearch.sync="showSearch" + @queryTable="getList" + :columns="columns" + ></right-toolbar> + </el-row> + + <el-table v-loading="loading" :data="userList"> + <el-table-column + label="用户编号" + align="center" + key="id" + prop="id" + v-if="columns[0].visible" + /> + <el-table-column + label="用户名称" + align="center" + key="username" + prop="username" + v-if="columns[1].visible" + :show-overflow-tooltip="true" + /> + <el-table-column + label="用户昵称" + align="center" + key="nickname" + prop="nickname" + v-if="columns[2].visible" + :show-overflow-tooltip="true" + /> + <el-table-column + label="部门" + align="center" + key="deptName" + prop="dept.name" + v-if="columns[3].visible" + :show-overflow-tooltip="true" + /> + <el-table-column + label="手机号码" + align="center" + key="mobile" + prop="mobile" + v-if="columns[4].visible" + width="120" + /> + <el-table-column label="状态" key="status" v-if="columns[5].visible" align="center"> + <template v-slot="scope"> + <el-switch + v-model="scope.row.status" + :active-value="0" + :inactive-value="1" + @change="handleStatusChange(scope.row)" + /> + </template> + </el-table-column> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + v-if="columns[6].visible" + width="160" + > + <template v-slot="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column + label="操作" + align="center" + width="160" + class-name="small-padding fixed-width" + > + <template v-slot="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-edit" + @click="handleUpdate(scope.row)" + v-hasPermi="['system:user:update']" + >修改</el-button + > + <el-dropdown + @command="(command) => handleCommand(command, scope.$index, scope.row)" + v-hasPermi="[ + 'system:user:delete', + 'system:user:update-password', + 'system:permission:assign-user-role' + ]" + > + <el-button size="mini" type="text" icon="el-icon-d-arrow-right">更多</el-button> + <el-dropdown-menu slot="dropdown"> + <el-dropdown-item + command="handleDelete" + v-if="scope.row.id !== 1" + size="mini" + type="text" + icon="el-icon-delete" + v-hasPermi="['system:user:delete']" + >删除</el-dropdown-item + > + <el-dropdown-item + command="handleResetPwd" + size="mini" + type="text" + icon="el-icon-key" + v-hasPermi="['system:user:update-password']" + >重置密码</el-dropdown-item + > + <el-dropdown-item + command="handleRole" + size="mini" + type="text" + icon="el-icon-circle-check" + v-hasPermi="['system:permission:assign-user-role']" + >分配角色</el-dropdown-item + > + </el-dropdown-menu> + </el-dropdown> + </template> + </el-table-column> + </el-table> + + <pagination + v-show="total > 0" + :total="total" + :page.sync="queryParams.pageNo" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + </el-col> + </el-row> + + <!-- 添加或修改参数配置对话框 --> + <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body> + <el-form ref="form" :model="form" :rules="rules" label-width="80px"> + <el-row> + <el-col :span="12"> + <el-form-item label="用户昵称" prop="nickname"> + <el-input v-model="form.nickname" placeholder="请输入用户昵称" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="归属部门" prop="deptId"> + <treeselect + v-model="form.deptId" + :options="deptOptions" + :show-count="true" + :clearable="false" + placeholder="请选择归属部门" + :normalizer="normalizer" + /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="手机号码" prop="mobile"> + <el-input v-model="form.mobile" placeholder="请输入手机号码" maxlength="11" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="邮箱" prop="email"> + <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item v-if="form.id === undefined" label="用户名称" prop="username"> + <el-input v-model="form.username" placeholder="请输入用户名称" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item v-if="form.id === undefined" label="用户密码" prop="password"> + <el-input + v-model="form.password" + placeholder="请输入用户密码" + type="password" + show-password + /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="用户性别"> + <el-select v-model="form.sex" placeholder="请选择"> + <el-option + v-for="dict in sexDictDatas" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="岗位"> + <el-select v-model="form.postIds" multiple placeholder="请选择"> + <el-option + v-for="item in postOptions" + :key="item.id" + :label="item.name" + :value="item.id" + ></el-option> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="24"> + <el-form-item label="备注"> + <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input> + </el-form-item> + </el-col> + </el-row> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </el-dialog> + + <!-- 用户导入对话框 --> + <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body> + <el-upload + ref="upload" + :limit="1" + accept=".xlsx, .xls" + :headers="upload.headers" + :action="upload.url + '?updateSupport=' + upload.updateSupport" + :disabled="upload.isUploading" + :on-progress="handleFileUploadProgress" + :on-success="handleFileSuccess" + :auto-upload="false" + drag + > + <i class="el-icon-upload"></i> + <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> + <div class="el-upload__tip text-center" slot="tip"> + <div class="el-upload__tip" slot="tip"> + <el-checkbox v-model="upload.updateSupport" /> 是否更新已经存在的用户数据 + </div> + <span>仅允许导入xls、xlsx格式文件。</span> + <el-link + type="primary" + :underline="false" + style="font-size: 12px; vertical-align: baseline" + @click="importTemplate" + >下载模板</el-link + > + </div> + </el-upload> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitFileForm">确 定</el-button> + <el-button @click="upload.open = false">取 消</el-button> + </div> + </el-dialog> + + <!-- 分配角色 --> + <el-dialog title="分配角色" :visible.sync="openRole" width="500px" append-to-body> + <el-form :model="form" label-width="80px"> + <el-form-item label="用户名称"> + <el-input v-model="form.username" :disabled="true" /> + </el-form-item> + <el-form-item label="用户昵称"> + <el-input v-model="form.nickname" :disabled="true" /> + </el-form-item> + <el-form-item label="角色"> + <el-select v-model="form.roleIds" multiple placeholder="请选择"> + <el-option + v-for="item in roleOptions" + :key="parseInt(item.id)" + :label="item.name" + :value="parseInt(item.id)" + ></el-option> + </el-select> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitRole">确 定</el-button> + <el-button @click="cancelRole">取 消</el-button> + </div> + </el-dialog> + </div> </template> -<script setup lang="ts" name="User"> -import type { ElTree, UploadRawFile, UploadInstance } from 'element-plus' -import { handleTree, defaultProps } from '@/utils/tree' -import download from '@/utils/download' -import { CommonStatusEnum } from '@/utils/constants' -import { getAccessToken, getTenantId } from '@/utils/auth' -import type { FormExpose } from '@/components/Form' -import { rules, allSchemas } from './user.data' -import * as UserApi from '@/api/system/user' -import { listSimpleDeptApi } from '@/api/system/dept' -import { listSimpleRolesApi } from '@/api/system/role' -import { listSimplePostsApi, PostVO } from '@/api/system/post' + +<script> import { - aassignUserRoleApi, - listUserRolesApi, - PermissionAssignUserRoleReqVO -} from '@/api/system/permission' + createUserApi as addUser, + updateUserStatusApi as changeUserStatus, + deleteUserApi as delUser, + exportUserApi as exportUser, + // TODO: praseStrEmpty(id) + getUserApi as getUser, + importUserTemplateApi as importTemplate, + getUserPageApi as listUser, + resetUserPwdApi as resetUserPwd, + updateUserApi as updateUser +} from '@/api/system/user' +// TODO: change +import Treeselect from '@riophae/vue-treeselect' +// TODO: change??? +import '@riophae/vue-treeselect/dist/vue-treeselect.css' -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 +import { listSimpleDeptApi } from '@/api/system/dept' +import { listSimplePostsApi } from '@/api/system/post' -const queryParams = reactive({ - deptId: null -}) -// ========== 列表相关 ========== -const tableTitle = ref('用户列表') -// 列表相关的变量 -const [registerTable, { reload, deleteData, exportList }] = useXTable({ - allSchemas: allSchemas, - params: queryParams, - getListApi: UserApi.getUserPageApi, - deleteApi: UserApi.deleteUserApi, - exportListApi: UserApi.exportUserApi -}) -// ========== 创建部门树结构 ========== -const filterText = ref('') -const deptOptions = ref<Tree[]>([]) // 树形结构 -const treeRef = ref<InstanceType<typeof ElTree>>() -const getTree = async () => { - const res = await listSimpleDeptApi() - deptOptions.value.push(...handleTree(res)) -} -const filterNode = (value: string, data: Tree) => { - if (!value) return true - return data.name.includes(value) -} -const handleDeptNodeClick = async (row: { [key: string]: any }) => { - queryParams.deptId = row.id - await reload() -} -const { push } = useRouter() -const handleDeptEdit = () => { - push('/system/dept') -} -watch(filterText, (val) => { - treeRef.value!.filter(val) -}) -// ========== CRUD 相关 ========== -const loading = ref(false) // 遮罩层 -const actionType = ref('') // 操作按钮的类型 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const formRef = ref<FormExpose>() // 表单 Ref -const postOptions = ref<PostVO[]>([]) //岗位列表 +import { CommonStatusEnum } from '@/utils/constants' +import { DICT_TYPE, getDictDatas } from '@/utils/dict' +import { assignUserRole, listUserRoles } from '@/api/system/permission' +import { listSimpleRoles } from '@/api/system/role' +import { getBaseHeader } from '@/utils/request' -// 获取岗位列表 -const getPostOptions = async () => { - const res = await listSimplePostsApi() - postOptions.value.push(...res) -} -const dataFormater = (val) => { - return deptFormater(deptOptions.value, val) -} -//部门回显 -const deptFormater = (ary, val: any) => { - var o = '' - if (ary && val) { - for (const v of ary) { - if (v.id == val) { - o = v.name - if (o) return o - } else if (v.children?.length) { - o = deptFormater(v.children, val) - if (o) return o - } +export default { + name: 'User', + components: { Treeselect }, + data() { + return { + // 遮罩层 + loading: true, + // 导出遮罩层 + exportLoading: false, + // 显示搜索条件 + showSearch: true, + // 总条数 + total: 0, + // 用户表格数据 + userList: null, + // 弹出层标题 + title: '', + // 部门树选项 + deptOptions: undefined, + // 是否显示弹出层 + open: false, + // 部门名称 + deptName: undefined, + // 默认密码 + initPassword: undefined, + // 性别状态字典 + sexOptions: [], + // 岗位选项 + postOptions: [], + // 角色选项 + roleOptions: [], + // 表单参数 + form: {}, + defaultProps: { + children: 'children', + label: 'name' + }, + // 用户导入参数 + upload: { + // 是否显示弹出层(用户导入) + open: false, + // 弹出层标题(用户导入) + title: '', + // 是否禁用上传 + isUploading: false, + // 是否更新已经存在的用户数据 + updateSupport: 0, + // 设置上传的请求头部 + headers: getBaseHeader(), + // 上传的地址 + url: process.env.VUE_APP_BASE_API + '/admin-api/system/user/import' + }, + // 查询参数 + queryParams: { + pageNo: 1, + pageSize: 10, + username: undefined, + mobile: undefined, + status: undefined, + deptId: undefined, + createTime: [] + }, + // 列信息 + columns: [ + { key: 0, label: `用户编号`, visible: true }, + { key: 1, label: `用户名称`, visible: true }, + { key: 2, label: `用户昵称`, visible: true }, + { key: 3, label: `部门`, visible: true }, + { key: 4, label: `手机号码`, visible: true }, + { key: 5, label: `状态`, visible: true }, + { key: 6, label: `创建时间`, visible: true } + ], + // 表单校验 + rules: { + username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }], + nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }], + password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }], + email: [ + { + type: 'email', + message: "'请输入正确的邮箱地址", + trigger: ['blur', 'change'] + } + ], + mobile: [ + { + pattern: + /^(?:(?:\+|00)86)?1(?:3[\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\d]|9[189])\d{8}$/, + message: '请输入正确的手机号码', + trigger: 'blur' + } + ] + }, + // 是否显示弹出层(角色权限) + openRole: false, + + // 枚举 + SysCommonStatusEnum: CommonStatusEnum, + // 数据字典 + statusDictDatas: getDictDatas(DICT_TYPE.COMMON_STATUS), + sexDictDatas: getDictDatas(DICT_TYPE.SYSTEM_USER_SEX) } - return o - } else { - return val - } -} - -// 设置标题 -const setDialogTile = async (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true -} - -// 新增操作 -const handleCreate = async () => { - setDialogTile('create') - // 重置表单 - await nextTick() - if (allSchemas.formSchema[0].field !== 'username') { - unref(formRef)?.addSchema( - { - field: 'username', - label: '用户账号', - component: 'Input' - }, - 0 - ) - unref(formRef)?.addSchema( - { - field: 'password', - label: '用户密码', - component: 'InputPassword' - }, - 1 - ) - } -} - -// 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - await nextTick() - unref(formRef)?.delSchema('username') - unref(formRef)?.delSchema('password') - // 设置数据 - const res = await UserApi.getUserApi(rowId) - unref(formRef)?.setValues(res) -} -const detailData = ref() - -// 详情操作 -const handleDetail = async (rowId: number) => { - // 设置数据 - const res = await UserApi.getUserApi(rowId) - detailData.value = res - await setDialogTile('detail') -} - -// 提交按钮 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - // 提交请求 - try { - const data = unref(formRef)?.formModel as UserApi.UserVO - if (actionType.value === 'create') { - loading.value = true - await UserApi.createUserApi(data) - message.success(t('common.createSuccess')) - } else { - loading.value = true - await UserApi.updateUserApi(data) - message.success(t('common.updateSuccess')) + }, + watch: { + // 根据名称筛选部门树 + deptName(val) { + this.$refs.tree.filter(val) + } + }, + created() { + this.getList() + this.getTreeselect() + // this.getConfigKey("sys.user.init-password").then(response => { + // this.initPassword = response.msg; + // }); + }, + methods: { + // 更多操作 + handleCommand(command, index, row) { + switch (command) { + case 'handleUpdate': + this.handleUpdate(row) //修改客户信息 + break + case 'handleDelete': + this.handleDelete(row) //红号变更 + break + case 'handleResetPwd': + this.handleResetPwd(row) + break + case 'handleRole': + this.handleRole(row) + break + default: + break + } + }, + /** 查询用户列表 */ + getList() { + this.loading = true + listUser(this.queryParams).then((response) => { + this.userList = response.data.list + this.total = response.data.total + this.loading = false + }) + }, + /** 查询部门下拉树结构 + 岗位下拉 */ + getTreeselect() { + listSimpleDeptApi().then((response) => { + // 处理 deptOptions 参数 + this.deptOptions = [] + this.deptOptions.push(...this.handleTree(response.data, 'id')) + }) + listSimplePostsApi().then((response) => { + // 处理 postOptions 参数 + this.postOptions = [] + this.postOptions.push(...response.data) + }) + }, + // 筛选节点 + filterNode(value, data) { + if (!value) return true + return data.name.indexOf(value) !== -1 + }, + // 节点单击事件 + handleNodeClick(data) { + this.queryParams.deptId = data.id + this.getList() + }, + // 用户状态修改 + handleStatusChange(row) { + let text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' + this.$modal + .confirm('确认要"' + text + '""' + row.username + '"用户吗?') + .then(function () { + return changeUserStatus(row.id, row.status) + }) + .then(() => { + this.$modal.msgSuccess(text + '成功') + }) + .catch(function () { + row.status = + row.status === CommonStatusEnum.ENABLE + ? CommonStatusEnum.DISABLE + : CommonStatusEnum.ENABLE + }) + }, + // 取消按钮 + cancel() { + this.open = false + this.reset() + }, + // 取消按钮(角色权限) + cancelRole() { + this.openRole = false + this.reset() + }, + // 表单重置 + reset() { + this.form = { + id: undefined, + deptId: undefined, + username: undefined, + nickname: undefined, + password: undefined, + mobile: undefined, + email: undefined, + sex: undefined, + status: '0', + remark: undefined, + postIds: [], + roleIds: [] + } + this.resetForm('form') + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNo = 1 + this.getList() + }, + /** 重置按钮操作 */ + resetQuery() { + this.resetForm('queryForm') + this.handleQuery() + }, + /** 新增按钮操作 */ + handleAdd() { + this.reset() + // 获得下拉数据 + this.getTreeselect() + // 打开表单,并设置初始化 + this.open = true + this.title = '添加用户' + this.form.password = this.initPassword + }, + /** 修改按钮操作 */ + handleUpdate(row) { + this.reset() + this.getTreeselect() + const id = row.id + getUser(id).then((response) => { + this.form = response.data + this.open = true + this.title = '修改用户' + this.form.password = '' + }) + }, + /** 重置密码按钮操作 */ + handleResetPwd(row) { + this.$prompt('请输入"' + row.username + '"的新密码', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消' + }) + .then(({ value }) => { + resetUserPwd(row.id, value).then((response) => { + this.$modal.msgSuccess('修改成功,新密码是:' + value) + }) + }) + .catch(() => {}) + }, + /** 分配用户角色操作 */ + handleRole(row) { + this.reset() + const id = row.id + // 处理了 form 的用户 username 和 nickname 的展示 + this.form.id = id + this.form.username = row.username + this.form.nickname = row.nickname + // 打开弹窗 + this.openRole = true + // 获得角色列表 + listSimpleRoles().then((response) => { + // 处理 roleOptions 参数 + this.roleOptions = [] + this.roleOptions.push(...response.data) + }) + // 获得角色拥有的菜单集合 + listUserRoles(id).then((response) => { + // 设置选中 + this.form.roleIds = response.data + }) + }, + /** 提交按钮 */ + submitForm: function () { + this.$refs['form'].validate((valid) => { + if (valid) { + if (this.form.id !== undefined) { + updateUser(this.form).then((response) => { + this.$modal.msgSuccess('修改成功') + this.open = false + this.getList() + }) + } else { + addUser(this.form).then((response) => { + this.$modal.msgSuccess('新增成功') + this.open = false + this.getList() + }) + } } - dialogVisible.value = false - } finally { - // unref(formRef)?.setSchema(allSchemas.formSchema) - // 刷新列表 - await reload() - loading.value = false + }) + }, + /** 提交按钮(角色权限) */ + submitRole: function () { + if (this.form.id !== undefined) { + assignUserRole({ + userId: this.form.id, + roleIds: this.form.roleIds + }).then((response) => { + this.$modal.msgSuccess('分配角色成功') + this.openRole = false + this.getList() + }) + } + }, + /** 删除按钮操作 */ + handleDelete(row) { + const ids = row.id || this.ids + this.$modal + .confirm('是否确认删除用户编号为"' + ids + '"的数据项?') + .then(function () { + return delUser(ids) + }) + .then(() => { + this.getList() + this.$modal.msgSuccess('删除成功') + }) + .catch(() => {}) + }, + /** 导出按钮操作 */ + handleExport() { + this.$modal + .confirm('是否确认导出所有用户数据项?') + .then(() => { + // 处理查询参数 + let params = { ...this.queryParams } + params.pageNo = undefined + params.pageSize = undefined + this.exportLoading = true + return exportUser(params) + }) + .then((response) => { + this.$download.excel(response, '用户数据.xls') + this.exportLoading = false + }) + .catch(() => {}) + }, + /** 导入按钮操作 */ + handleImport() { + this.upload.title = '用户导入' + this.upload.open = true + }, + /** 下载模板操作 */ + importTemplate() { + importTemplate().then((response) => { + this.$download.excel(response, '用户导入模板.xls') + }) + }, + // 文件上传中处理 + handleFileUploadProgress(event, file, fileList) { + this.upload.isUploading = true + }, + // 文件上传成功处理 + handleFileSuccess(response, file, fileList) { + if (response.code !== 0) { + this.$modal.msgError(response.msg) + return + } + this.upload.open = false + this.upload.isUploading = false + this.$refs.upload.clearFiles() + // 拼接提示语 + let data = response.data + let text = '创建成功数量:' + data.createUsernames.length + for (const username of data.createUsernames) { + text += '<br /> ' + username + } + text += '<br />更新成功数量:' + data.updateUsernames.length + for (const username of data.updateUsernames) { + text += '<br /> ' + username + } + text += '<br />更新失败数量:' + Object.keys(data.failureUsernames).length + for (const username in data.failureUsernames) { + text += '<br /> ' + username + ':' + data.failureUsernames[username] + } + this.$alert(text, '导入结果', { dangerouslyUseHTMLString: true }) + this.getList() + }, + // 提交上传文件 + submitFileForm() { + this.$refs.upload.submit() + }, + // 格式化部门的下拉框 + normalizer(node) { + return { + id: node.id, + label: node.name, + children: node.children } } - }) -} -// 改变用户状态操作 -const handleStatusChange = async (row: UserApi.UserVO) => { - const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' - message - .confirm('确认要"' + text + '""' + row.username + '"用户吗?', t('common.reminder')) - .then(async () => { - row.status = - row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.ENABLE : CommonStatusEnum.DISABLE - await UserApi.updateUserStatusApi(row.id, row.status) - message.success(text + '成功') - // 刷新列表 - await reload() - }) - .catch(() => { - row.status = - row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE - }) -} -// 重置密码 -const handleResetPwd = (row: UserApi.UserVO) => { - message.prompt('请输入"' + row.username + '"的新密码', t('common.reminder')).then(({ value }) => { - UserApi.resetUserPwdApi(row.id, value).then(() => { - message.success('修改成功,新密码是:' + value) - }) - }) -} -// 分配角色 -const roleDialogVisible = ref(false) -const roleOptions = ref() -const userRole = reactive({ - id: 0, - username: '', - nickname: '', - roleIds: [] -}) -const handleRole = async (row: UserApi.UserVO) => { - userRole.id = row.id - userRole.username = row.username - userRole.nickname = row.nickname - // 获得角色拥有的权限集合 - const roles = await listUserRolesApi(row.id) - userRole.roleIds = roles - // 获取角色列表 - const roleOpt = await listSimpleRolesApi() - roleOptions.value = roleOpt - roleDialogVisible.value = true -} -// 提交 -const submitRole = async () => { - const data = ref<PermissionAssignUserRoleReqVO>({ - userId: userRole.id, - roleIds: userRole.roleIds - }) - await aassignUserRoleApi(data.value) - message.success(t('common.updateSuccess')) - roleDialogVisible.value = false -} -// ========== 导入相关 ========== -// TODO @星语:这个要不要把导入用户,封装成一个小组件?可选哈 -const importDialogVisible = ref(false) -const uploadDisabled = ref(false) -const importDialogTitle = ref('用户导入') -const updateSupport = ref(0) -let updateUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import' -const uploadHeaders = ref() -// 下载导入模版 -const handleImportTemp = async () => { - const res = await UserApi.importUserTemplateApi() - download.excel(res, '用户导入模版.xls') -} -// 文件上传之前判断 -const beforeExcelUpload = (file: UploadRawFile) => { - const isExcel = - file.type === 'application/vnd.ms-excel' || - file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' - const isLt5M = file.size / 1024 / 1024 < 5 - if (!isExcel) message.error('上传文件只能是 xls / xlsx 格式!') - if (!isLt5M) message.error('上传文件大小不能超过 5MB!') - return isExcel && isLt5M -} -// 文件上传 -const uploadRef = ref<UploadInstance>() -const submitFileForm = () => { - uploadHeaders.value = { - Authorization: 'Bearer ' + getAccessToken(), - 'tenant-id': getTenantId() } - uploadDisabled.value = true - uploadRef.value!.submit() } -// 文件上传成功 -const handleFileSuccess = async (response: any): Promise<void> => { - if (response.code !== 0) { - message.error(response.msg) - return - } - importDialogVisible.value = false - uploadDisabled.value = false - const data = response.data - let text = '上传成功数量:' + data.createUsernames.length + ';' - for (let username of data.createUsernames) { - text += '< ' + username + ' >' - } - text += '更新成功数量:' + data.updateUsernames.length + ';' - for (const username of data.updateUsernames) { - text += '< ' + username + ' >' - } - text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';' - for (const username in data.failureUsernames) { - text += '< ' + username + ': ' + data.failureUsernames[username] + ' >' - } - message.alert(text) - await reload() -} -// 文件数超出提示 -const handleExceed = (): void => { - message.error('最多只能上传一个文件!') -} -// 上传错误提示 -const excelUploadError = (): void => { - message.error('导入数据失败,请您重新上传!') -} -// ========== 初始化 ========== -onMounted(async () => { - await getPostOptions() - await getTree() -}) </script> - -<style scoped> -.user { - height: 780px; - max-height: 800px; -} -.card-header { - display: flex; - justify-content: space-between; - align-items: center; -} -</style> From bf2b4ced8c9303eabd22c87d505820b5b60e566c Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Wed, 22 Mar 2023 16:02:04 +0800 Subject: [PATCH 054/184] =?UTF-8?q?update:=20=E4=BF=AE=E5=A4=8D=E6=A3=80?= =?UTF-8?q?=E7=B4=A2=E8=A1=A8=E5=8D=95=E7=9F=AD=E4=BF=A1=E4=B8=8B=E6=8B=89?= =?UTF-8?q?=E8=8F=9C=E5=8D=95undefined=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/sms/smsTemplate/index.vue | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/views/system/sms/smsTemplate/index.vue b/src/views/system/sms/smsTemplate/index.vue index a7d6ded1..081f0596 100644 --- a/src/views/system/sms/smsTemplate/index.vue +++ b/src/views/system/sms/smsTemplate/index.vue @@ -51,10 +51,7 @@ :key="channel.id" :value="channel.id" :label=" - channel.signature + - '【' + - getDictObj(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, channel.code) + - '】' + channel.signature + optionLabel(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, channel.code) " /> </el-select> @@ -205,7 +202,7 @@ <SmsTemplateFrom ref="modalRef" :channelOptions="channelOptions" @success="getList" /> </template> <script setup lang="ts" name="SmsTemplate"> -import { DICT_TYPE, getDictOptions, getDictObj } from '@/utils/dict' +import { DICT_TYPE, getDictOptions, getDictLabel } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' import * as templateApi from '@/api/system/sms/smsTemplate' import * as SmsChannelApi from '@/api/system/sms/smsChannel' @@ -282,6 +279,9 @@ onMounted(() => { channelOptions.value = res }) }) +const optionLabel = computed( + () => (type: string, code: string) => `【${getDictLabel(type, code)}】` +) /** 格式化短信渠道 */ const formatChannelSignature = (channelId: number) => { channelOptions.value.forEach((item) => { From 7cda6f554be6d13cfc6320eb593ecc0f8ac7a6e9 Mon Sep 17 00:00:00 2001 From: Chika <wbs_2018@sina.com> Date: Wed, 22 Mar 2023 16:47:45 +0800 Subject: [PATCH 055/184] =?UTF-8?q?[=E7=B3=BB=E7=BB=9F=E7=AE=A1=E7=90=86?= =?UTF-8?q?=20->=20=E5=B2=97=E4=BD=8D=E7=AE=A1=E7=90=86]=20=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=20Element=20Plus=20=E5=8E=9F=E7=94=9F=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/post/form.vue | 135 ++++++++++------- src/views/system/post/index.vue | 231 ++++++++++++++++++++++------- src/views/system/post/post.data.ts | 58 -------- 3 files changed, 258 insertions(+), 166 deletions(-) delete mode 100644 src/views/system/post/post.data.ts diff --git a/src/views/system/post/form.vue b/src/views/system/post/form.vue index 065aecaf..d5d65dd1 100644 --- a/src/views/system/post/form.vue +++ b/src/views/system/post/form.vue @@ -1,81 +1,96 @@ <template> - <!-- 弹窗 --> - <XModal :title="modelTitle" :loading="modelLoading" v-model="modelVisible"> - <!-- 表单:添加/修改 --> - <Form + <Dialog :title="modelTitle" v-model="modelVisible" width="800"> + <el-form ref="formRef" - v-if="['create', 'update'].includes(actionType)" - :schema="allSchemas.formSchema" - :rules="rules" - /> - <!-- 表单:详情 --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - /> + :model="formData" + :rules="formRules" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="岗位标题" prop="name"> + <el-input v-model="formData.name" placeholder="请输入岗位标题" /> + </el-form-item> + <el-form-item label="岗位编码" prop="code"> + <el-input :model-value="formData.code" placeholder="请输入岗位编码" height="150px" /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select v-model="formData.status" placeholder="请选择状态" clearable> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="formData.remark" type="textarea" placeholder="请输备注" /> + </el-form-item> + </el-form> <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="actionLoading" - @click="submitForm()" - /> - <!-- 按钮:关闭 --> - <XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" /> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> </template> - </XModal> + </Dialog> </template> <script setup lang="ts"> -import type { FormExpose } from '@/components/Form' +import { DICT_TYPE, getDictOptions } from '@/utils/dict' import * as PostApi from '@/api/system/post' -import { rules, allSchemas } from './post.data' + const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 -// 弹窗相关的变量 -const modelVisible = ref(false) // 是否显示弹出层 -const modelTitle = ref('') // 弹出层标题 -const modelLoading = ref(false) // 弹出层loading -const actionType = ref('') // 操作按钮的类型 -const actionLoading = ref(false) // 按钮 Loading -const formRef = ref<FormExpose>() // 表单 Ref -const detailData = ref() // 详情 Ref +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: undefined, + name: '', + code: '', + status: undefined, + remark: '' +}) +const formRules = reactive({ + name: [{ required: true, message: '岗位标题不能为空', trigger: 'blur' }], + code: [{ required: true, message: '岗位编码不能为空', trigger: 'change' }], + status: [{ required: true, message: '岗位状态不能为空', trigger: 'change' }], + remark: [{ required: false, message: '岗位内容不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref -// 打开弹窗 +/** 打开弹窗 */ const openModal = async (type: string, id?: number) => { modelVisible.value = true - modelLoading.value = true modelTitle.value = t('action.' + type) - actionType.value = type - // 设置数据 + formType.value = type + resetForm() + // 修改时,设置数据 if (id) { - const res = await PostApi.getPostApi(id) - if (type === 'update') { - unref(formRef)?.setValues(res) - } else if (type === 'detail') { - detailData.value = res + formLoading.value = true + try { + formData.value = await PostApi.getPostApi(id) + } finally { + formLoading.value = false } } - modelLoading.value = false } defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 -// 提交新增/修改的表单 +/** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 const submitForm = async () => { // 校验表单 - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - const valid = await elForm.validate() + if (!formRef) return + const valid = await formRef.value.validate() if (!valid) return // 提交请求 - actionLoading.value = true + formLoading.value = true try { - const data = unref(formRef)?.formModel as PostApi.PostVO - if (actionType.value === 'create') { + const data = formData.value as unknown as PostApi.PostVO + if (formType.value === 'create') { await PostApi.createPostApi(data) message.success(t('common.createSuccess')) } else { @@ -83,9 +98,23 @@ const submitForm = async () => { message.success(t('common.updateSuccess')) } modelVisible.value = false + // 发送操作成功的事件 emit('success') } finally { - actionLoading.value = false + formLoading.value = false } } + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + title: '', + type: undefined, + content: '', + status: undefined, + remark: '' + } + formRef.value?.resetFields() +} </script> diff --git a/src/views/system/post/index.vue b/src/views/system/post/index.vue index c5a13e1e..321b5ae3 100644 --- a/src/views/system/post/index.vue +++ b/src/views/system/post/index.vue @@ -1,71 +1,192 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <!-- 操作:新增 --> - <XButton + <content-wrap> + <!-- 搜索工作栏 --> + <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px"> + <el-form-item label="岗位名称" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入岗位名称" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="岗位编码" prop="code"> + <el-input + v-model="queryParams.code" + placeholder="请输入岗位编码" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="请选择状态" clearable> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['system:post:create']" @click="openModal('create')" - /> - <!-- 操作:导出 --> - <XButton - type="primary" + v-hasPermi="['system:notice:create']" + > + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> + <el-button + type="success" plain - preIcon="ep:download" - :title="t('action.export')" - v-hasPermi="['system:post:export']" - @click="exportList('岗位列表.xls')" - /> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:修改 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['system:post:update']" - @click="openModal('update', row?.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['system:post:query']" - @click="openModal('detail', row?.id)" - /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.delete')" - v-hasPermi="['system:post:delete']" - @click="deleteData(row?.id)" - /> - </template> - </XTable> - </ContentWrap> - <!-- 表单弹窗:添加/修改/详情 --> - <PostForm ref="modalRef" @success="reload()" /> + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['infra:config:export']" + > + <Icon icon="ep:download" class="mr-5px" /> 导出 + </el-button> + </el-form-item> + </el-form> + + <!-- 列表 --> + <el-table v-loading="loading" :data="list" align="center"> + <el-table-column label="岗位编号" align="center" prop="id" /> + <el-table-column label="岗位名称" align="center" prop="name" /> + <el-table-column label="岗位编码" align="center" prop="code" /> + <el-table-column label="岗位顺序" align="center" prop="sort" /> + <el-table-column label="岗位备注" align="center" prop="remark" /> + + <el-table-column label="状态" align="center" prop="status"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> + </template> + </el-table-column> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openModal('update', scope.row.id)" + v-hasPermi="['system:post:update']" + > + 编辑 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['system:post:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + + <!-- 表单弹窗:添加/修改 --> + <post-form ref="modalRef" @success="getList" /> </template> -<script setup lang="ts" name="Post"> +<script setup lang="tsx"> import * as PostApi from '@/api/system/post' -import { allSchemas } from './post.data' import PostForm from './form.vue' +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' +import download from '@/utils/download' +import { DictTag } from '@/components/DictTag' + +const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 -// 列表相关的变量 -const [registerTable, { reload, deleteData, exportList }] = useXTable({ - allSchemas: allSchemas, // 列表配置 - getListApi: PostApi.getPostPageApi, // 加载列表的 API - deleteApi: PostApi.deletePostApi, // 删除数据的 API - exportListApi: PostApi.exportPostApi // 导出数据的 API +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + code: '', + name: '', + status: undefined, + pageNo: 1, + pageSize: 100 }) +const queryFormRef = ref() // 搜索的表单 +const exportLoading = ref(false) // 导出的加载中 +/** 查询岗位列表 */ +const getList = async () => { + loading.value = true + try { + const data = await PostApi.getPostPageApi(queryParams) -// 表单相关的变量 + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await PostApi.exportPostApi(queryParams) + download.excel(data, '岗位列表.xls') + } catch { + } finally { + exportLoading.value = false + } +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ const modalRef = ref() const openModal = (type: string, id?: number) => { modalRef.value.openModal(type, id) } + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await PostApi.deletePostApi(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +/** 初始化 **/ +onMounted(() => { + getList() +}) </script> diff --git a/src/views/system/post/post.data.ts b/src/views/system/post/post.data.ts deleted file mode 100644 index 4926bcc6..00000000 --- a/src/views/system/post/post.data.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -const { t } = useI18n() // 国际化 - -// 表单校验 -export const rules = reactive({ - name: [required], - code: [required], - sort: [required] -}) - -// 增删改查 CrudSchema 配置 -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'id', - primaryTitle: '岗位编号', - action: true, - columns: [ - { - title: '岗位名称', - field: 'name', - isSearch: true - }, - { - title: '岗位编码', - field: 'code', - isSearch: true - }, - { - title: '岗位顺序', - field: 'sort', - form: { - component: 'InputNumber' - } - }, - { - title: t('common.status'), - field: 'status', - dictType: DICT_TYPE.COMMON_STATUS, - dictClass: 'number', - isSearch: true - }, - { - title: '备注', - field: 'remark', - isTable: false - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false, - table: { - width: 180 - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From 25668ac558cebdff720e4b1d75995761a1a8003b Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Wed, 22 Mar 2023 17:47:08 +0800 Subject: [PATCH 056/184] =?UTF-8?q?add:=20=E8=BF=81=E7=A7=BBv2=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E7=9A=84=E6=97=A5=E6=9C=9F=E6=A0=BC=E5=BC=8F=E5=8C=96?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E5=88=B0vue3=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/formatTime.ts | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/utils/formatTime.ts b/src/utils/formatTime.ts index 39671279..f0cf0736 100644 --- a/src/utils/formatTime.ts +++ b/src/utils/formatTime.ts @@ -64,6 +64,51 @@ export function formatDate(date: Date, format: string): string { } return format } +// 日期格式化 +export function parseTime(time: any, pattern?: string) { + if (arguments.length === 0 || !time) { + return null + } + const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'object') { + date = time + } else { + if (typeof time === 'string' && /^[0-9]+$/.test(time)) { + time = parseInt(time) + } else if (typeof time === 'string') { + time = time + .replace(new RegExp(/-/gm), '/') + .replace('T', ' ') + .replace(new RegExp(/\.\d{3}/gm), '') + } + if (typeof time === 'number' && time.toString().length === 10) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => { + let value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { + return ['日', '一', '二', '三', '四', '五', '六'][value] + } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return time_str +} /** * 获取当前日期是第几周 From b4093ab97e257bcaa1139a097b667df1c44e6fd7 Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Wed, 22 Mar 2023 17:49:52 +0800 Subject: [PATCH 057/184] =?UTF-8?q?update:=20=E4=BF=AE=E5=A4=8Dvalue-forma?= =?UTF-8?q?t=E5=B1=9E=E6=80=A7=E5=A4=B1=E6=95=88=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98=EF=BC=8C=E5=BA=94=E5=85=A8=E9=83=A8=E9=83=BD=E7=94=A8?= =?UTF-8?q?=E5=A4=A7=E5=86=99value-format=3D"YYYY-MM-DD"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/sms/smsTemplate/index.vue | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/views/system/sms/smsTemplate/index.vue b/src/views/system/sms/smsTemplate/index.vue index 081f0596..ed934c0f 100644 --- a/src/views/system/sms/smsTemplate/index.vue +++ b/src/views/system/sms/smsTemplate/index.vue @@ -60,21 +60,21 @@ <el-date-picker v-model="queryParams.createTime" style="width: 240px" - value-format="yyyy-MM-dd HH:mm:ss" type="daterange" - range-separator="-" + value-format="YYYY-MM-DD HH:mm:ss" start-placeholder="开始日期" end-placeholder="结束日期" - :default-time="['00:00:00', '23:59:59']" /> </el-form-item> <el-form-item> - <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button type="primary" @click="handleQuery" + ><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button + > <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> </el-form-item> </el-form> <!-- 操作工具栏 --> - <el-row> + <el-row class="mb-10px"> <el-col :span="12"> <el-row :gutter="10"> <el-col :span="1.5"> From 496862e197026c863d3b96e9699c007f40dea44e Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Wed, 22 Mar 2023 17:50:25 +0800 Subject: [PATCH 058/184] delete --- src/views/system/sms/smsLog/sms.log.data.ts | 93 --------------------- 1 file changed, 93 deletions(-) delete mode 100644 src/views/system/sms/smsLog/sms.log.data.ts diff --git a/src/views/system/sms/smsLog/sms.log.data.ts b/src/views/system/sms/smsLog/sms.log.data.ts deleted file mode 100644 index c975bb0f..00000000 --- a/src/views/system/sms/smsLog/sms.log.data.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -import { DICT_TYPE, getStrDictOptions } from '@/utils/dict' - -const { t } = useI18n() // 国际化 - -const authorizedGrantOptions = getStrDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE) -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'id', - primaryTitle: '日志编号', - action: true, - columns: [ - { - title: '手机号', - field: 'mobile', - isSearch: true - }, - { - title: '短信内容', - field: 'templateContent' - }, - { - title: '模板编号', - field: 'templateId', - isSearch: true - }, - { - title: '短信渠道', - field: 'channelId', - // dictType: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, - // dictClass: 'number', - isSearch: true, - // table: { - // component: 'Select', - componentProps: { - options: authorizedGrantOptions - // multiple: false, - // filterable: true - } - // } - }, - { - title: '发送状态', - field: 'sendStatus', - dictType: DICT_TYPE.SYSTEM_SMS_SEND_STATUS, - dictClass: 'number', - isSearch: true - }, - { - title: '发送时间', - field: 'sendTime', - formatter: 'formatDate', - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - }, - { - title: '短信类型', - field: 'templateType', - dictType: DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE, - dictClass: 'number', - isSearch: true - }, - { - title: '接收状态', - field: 'receiveStatus', - dictType: DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS, - dictClass: 'number', - isSearch: true - }, - { - title: '接收时间', - field: 'receiveTime', - formatter: 'formatDate', - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate' - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From 9e2c3476c8829e1eb5488ad93726bed2048a4151 Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Wed, 22 Mar 2023 17:52:06 +0800 Subject: [PATCH 059/184] =?UTF-8?q?add:=20=E7=9F=AD=E4=BF=A1=E6=97=A5?= =?UTF-8?q?=E5=BF=97vue2=E8=BF=81=E7=A7=BB=E5=88=B0vue3=EF=BC=8C=E4=BD=BF?= =?UTF-8?q?=E7=94=A8ts=E6=94=B9=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/sms/smsLog/index.ts | 35 +- src/views/system/sms/smsLog/index.vue | 442 +++++++++++++++++++++++--- 2 files changed, 411 insertions(+), 66 deletions(-) diff --git a/src/api/system/sms/smsLog/index.ts b/src/api/system/sms/smsLog/index.ts index 863eabb6..269b609d 100644 --- a/src/api/system/sms/smsLog/index.ts +++ b/src/api/system/sms/smsLog/index.ts @@ -1,39 +1,40 @@ import request from '@/config/axios' export interface SmsLogVO { - id: number - channelId: number + id: number | null + channelId: number | null channelCode: string - templateId: number + templateId: number | null templateCode: string - templateType: number + templateType: number | null templateContent: string - templateParams: Map<string, object> + templateParams: Map<string, object> | null + apiTemplateId: string mobile: string - userId: number - userType: number - sendStatus: number - sendTime: Date - sendCode: number + userId: number | null + userType: number | null + sendStatus: number | null + sendTime: Date | null + sendCode: number | null sendMsg: string apiSendCode: string apiSendMsg: string apiRequestId: string apiSerialNo: string - receiveStatus: number - receiveTime: Date + receiveStatus: number | null + receiveTime: Date | null apiReceiveCode: string apiReceiveMsg: string - createTime: Date + createTime: Date | null } export interface SmsLogPageReqVO extends PageParam { - channelId?: number - templateId?: number + channelId?: number | null + templateId?: number | null mobile?: string - sendStatus?: number + sendStatus?: number | null sendTime?: Date[] - receiveStatus?: number + receiveStatus?: number | null receiveTime?: Date[] } export interface SmsLogExportReqVO { diff --git a/src/views/system/sms/smsLog/index.vue b/src/views/system/sms/smsLog/index.vue index 334da2ad..78bfd937 100644 --- a/src/views/system/sms/smsLog/index.vue +++ b/src/views/system/sms/smsLog/index.vue @@ -1,57 +1,401 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <!-- 操作:导出 --> - <template #toolbar_buttons> - <XButton - type="warning" - preIcon="ep:download" - :title="t('action.export')" - @click="exportList('短信日志.xls')" + <content-wrap> + <!-- 搜索工作栏 --> + <el-form + :model="queryParams" + ref="queryForm" + :inline="true" + v-show="showSearch" + label-width="100px" + > + <el-form-item label="手机号" prop="mobile"> + <el-input + v-model="queryParams.mobile" + placeholder="请输入手机号" + clearable + @keyup.enter="handleQuery" /> - </template> - <template #actionbtns_default="{ row }"> - <XTextButton preIcon="ep:view" :title="t('action.detail')" @click="handleDetail(row)" /> - </template> - </XTable> - </ContentWrap> - - <XModal id="smsLog" v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(详情) --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" + </el-form-item> + <el-form-item label="短信渠道" prop="channelId"> + <el-select v-model="queryParams.channelId" placeholder="请选择短信渠道" clearable> + <el-option + v-for="channel in channelOptions" + :key="channel.id" + :value="channel.id" + :label=" + channel.signature + optionLabel(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, channel.code) + " + /> + </el-select> + </el-form-item> + <el-form-item label="模板编号" prop="templateId"> + <el-input + v-model="queryParams.templateId" + placeholder="请输入模板编号" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="发送状态" prop="sendStatus"> + <el-select v-model="queryParams.sendStatus" placeholder="请选择发送状态" clearable> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_SMS_SEND_STATUS)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="发送时间" prop="sendTime"> + <el-date-picker + v-model="queryParams.sendTime" + style="width: 240px" + value-format="yyyy-MM-dd HH:mm:ss" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="['00:00:00', '23:59:59']" + /> + </el-form-item> + <el-form-item label="接收状态" prop="receiveStatus"> + <el-select v-model="queryParams.receiveStatus" placeholder="请选择接收状态" clearable> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="接收时间" prop="receiveTime"> + <el-date-picker + v-model="queryParams.receiveTime" + style="width: 240px" + value-format="yyyy-MM-dd HH:mm:ss" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="['00:00:00', '23:59:59']" + /> + </el-form-item> + <el-form-item> + <el-button type="primary" @click="handleQuery" + ><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button + > + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + </el-form-item> + </el-form> + <!-- 操作工具栏 --> + <el-row class="mb-10px"> + <el-col :span="12"> + <el-row :gutter="10"> + <el-col :span="1.5"> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['system:sms-log:export']" + > + <Icon icon="ep:download" class="mr-5px" /> 导出 + </el-button> + </el-col> + </el-row> + </el-col> + <el-col :span="12"> + <right-toolbar v-model:showSearch="showSearch" @query-table="getList" /> + </el-col> + </el-row> + <!-- 列表 --> + <el-table v-loading="loading" :data="smsLoglist"> + <el-table-column label="编号" align="center" prop="id" /> + <el-table-column label="创建时间" align="center" prop="createTime" width="180"> + <template #default="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column label="手机号" align="center" prop="mobile" width="120"> + <template #default="scope"> + <div>{{ scope.row.mobile }}</div> + <div v-if="scope.row.userType && scope.row.userId"> + <dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" />{{ + '(' + scope.row.userId + ')' + }} + </div> + </template> + </el-table-column> + <el-table-column label="短信内容" align="center" prop="templateContent" width="300" /> + <el-table-column label="发送状态" align="center" width="180"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_SEND_STATUS" :value="scope.row.sendStatus" /> + <div>{{ parseTime(scope.row.sendTime) }}</div> + </template> + </el-table-column> + <el-table-column label="接收状态" align="center" width="180"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS" :value="scope.row.receiveStatus" /> + <div>{{ parseTime(scope.row.receiveTime) }}</div> + </template> + </el-table-column> + <el-table-column label="短信渠道" align="center" width="120"> + <template #default="scope"> + <div>{{ formatChannelSignature(scope.row.channelId) }}</div> + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="scope.row.channelCode" /> + </template> + </el-table-column> + <el-table-column label="模板编号" align="center" prop="templateId" /> + <el-table-column label="短信类型" align="center" prop="templateType"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE" :value="scope.row.templateType" /> + </template> + </el-table-column> + <el-table-column + label="操作" + align="center" + fixed="right" + class-name="small-padding fixed-width" + > + <template #default="scope"> + <el-button + link + type="primary" + @click="handleView(scope.row)" + v-hasPermi="['system:sms-log:query']" + ><Icon icon="ep:view" class="mr-3px" />详情</el-button + > + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" /> - <!-- 操作按钮 --> - <template #footer> - <XButton :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> + <!-- 短信日志详细 --> + <el-dialog title="短信日志详细" v-model="open" width="700px" append-to-body> + <el-form ref="form" :model="form" label-width="140px" size="mini"> + <el-row> + <el-col :span="24"> + <el-form-item label="日志主键:">{{ form.id }}</el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="短信渠道:"> + {{ formatChannelSignature(form.channelId) }} + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="form.channelCode" /> + </el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="短信模板:"> + {{ form.templateId }} | {{ form.templateCode }} + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE" :value="form.templateType" /> + </el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="API 的模板编号:">{{ form.apiTemplateId }}</el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="用户信息:" + >{{ form.mobile }} + <span v-if="form.userType && form.userId"> + <dict-tag :type="DICT_TYPE.USER_TYPE" :value="form.userType" />({{ form.userId }}) + </span> + </el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="短信内容:">{{ form.templateContent }}</el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="短信参数:">{{ form.templateParams }}</el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="创建时间:">{{ parseTime(form.createTime) }}</el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="发送状态:"> + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_SEND_STATUS" :value="form.sendStatus" /> + </el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="发送时间:">{{ parseTime(form.sendTime) }}</el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="发送结果:" + >{{ form.sendCode }} | {{ form.sendMsg }} + </el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="API 发送结果:" + >{{ form.apiSendCode }} | {{ form.apiSendMsg }}</el-form-item + > + </el-col> + <el-col :span="24"> + <el-form-item label="API 短信编号:">{{ form.apiSerialNo }}</el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="API 请求编号:">{{ form.apiRequestId }}</el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="接收状态:"> + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS" :value="form.receiveStatus" /> + </el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="接收时间:">{{ parseTime(form.receiveTime) }}</el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="API 接收结果:" + >{{ form.apiReceiveCode }} | {{ form.apiReceiveMsg }} + </el-form-item> + </el-col> + </el-row> + </el-form> + <template #footer> + <el-button @click="open = false">关 闭</el-button> + </template> + </el-dialog> + </content-wrap> </template> -<script setup lang="ts" name="SmsLog"> -import { allSchemas } from './sms.log.data' -import * as SmsLoglApi from '@/api/system/sms/smsLog' -const { t } = useI18n() // 国际化 +<script setup lang="ts" name="smsLog"> +import { DICT_TYPE, getDictOptions, getDictLabel } from '@/utils/dict' +import download from '@/utils/download' +import { parseTime } from '@/utils/formatTime' +import * as SmsChannelApi from '@/api/system/sms/smsChannel' +import * as SmsLogApi from '@/api/system/sms/smsLog' -// 列表相关的变量 -const [registerTable, { exportList }] = useXTable({ - allSchemas: allSchemas, - getListApi: SmsLoglApi.getSmsLogPageApi, - exportListApi: SmsLoglApi.exportSmsLogApi +const message = useMessage() // 消息弹窗 + +// ================= 表单相关================ +const showSearch = ref(true) // 显示搜索条件 +const queryForm = ref() // queryFormRef +// 查询参数 +const queryParams = ref<SmsLogApi.SmsLogPageReqVO>({ + pageNo: 1, + pageSize: 10, + channelId: null, + templateId: null, + mobile: '', + sendStatus: null, + receiveStatus: null, + sendTime: [], + receiveTime: [] }) - -// 弹窗相关的变量 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref(t('action.detail')) // 弹出层标题 -const actionType = ref('') // 操作按钮的类型 -// ========== 详情相关 ========== -const detailData = ref() // 详情 Ref -const handleDetail = (row: SmsLoglApi.SmsLogVO) => { - // 设置数据 - actionType.value = 'detail' - detailData.value = row - dialogVisible.value = true +// 短信渠道列表 +const channelOptions = ref<SmsChannelApi.SmsChannelListVO[]>([]) +onMounted(() => { + SmsChannelApi.getSimpleSmsChannels().then((res) => { + channelOptions.value = res + }) +}) +const optionLabel = computed( + () => (type: string, code: string) => `【${getDictLabel(type, code)}】` +) +/** 格式化短信渠道 */ +const formatChannelSignature = (channelId: number | null) => { + channelOptions.value.forEach((item) => { + if (item.id === channelId) { + return item.signature + } + }) + return '找不到签名:' + channelId } +// ================= 表单相关操作================ +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.value.pageNo = 1 + getList() +} +/** 重置按钮操作 */ +const resetQuery = () => { + resetForm() + handleQuery() +} +/** 重置搜索表单 */ +const resetForm = () => { + queryParams.value = { + pageNo: 1, + pageSize: 10, + channelId: null, + templateId: null, + mobile: '', + sendStatus: null, + receiveStatus: null, + sendTime: [], + receiveTime: [] + } + queryForm.value?.resetFields() +} +// 导出遮罩层 +const exportLoading = ref(false) +/** 导出按钮操作 */ +const handleExport = () => { + // 处理查询参数 + let params = queryParams.value as SmsLogApi.SmsLogExportReqVO + // 执行导出 + message + .confirm('是否确认导出所有短信日志数据项?') + .then(() => { + exportLoading.value = true + return SmsLogApi.exportSmsLogApi(params) + }) + .then((response) => { + download.excel(response, '短信日志.xls') + exportLoading.value = false + }) + .catch(() => {}) +} +// ================== 表格 ==================== +// 总条数 +const total = ref(0) +// 短信日志列表 +const smsLoglist = ref([]) +const loading = ref(false) +/** 查询列表 */ +const getList = () => { + loading.value = true + // 执行查询 + SmsLogApi.getSmsLogPageApi(queryParams.value).then((response) => { + smsLoglist.value = response.list + total.value = response.total + loading.value = false + }) +} +// ================== 详情 ==================== +const open = ref(false) +const form = ref<SmsLogApi.SmsLogVO>({ + id: null, + channelId: null, + channelCode: '', + templateId: null, + templateCode: '', + templateType: null, + templateContent: '', + templateParams: null, + apiTemplateId: '', + mobile: '', + userId: null, + userType: null, + sendStatus: null, + sendTime: null, + sendCode: null, + sendMsg: '', + apiSendCode: '', + apiSendMsg: '', + apiRequestId: '', + apiSerialNo: '', + receiveStatus: null, + receiveTime: null, + apiReceiveCode: '', + apiReceiveMsg: '', + createTime: null +}) +/** 详细按钮操作 */ +const handleView = (row: SmsLogApi.SmsLogVO) => { + open.value = true + form.value = row +} +getList() </script> From 36729f52828619082c1c21481491b30a47351177 Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Wed, 22 Mar 2023 17:53:55 +0800 Subject: [PATCH 060/184] =?UTF-8?q?add:=20=E7=9F=AD=E4=BF=A1=E6=97=A5?= =?UTF-8?q?=E5=BF=97vue2=E8=BF=81=E7=A7=BB=E5=88=B0vue3=EF=BC=8C=E4=BD=BF?= =?UTF-8?q?=E7=94=A8ts=E6=94=B9=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/sms/smsLog/index.vue | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/views/system/sms/smsLog/index.vue b/src/views/system/sms/smsLog/index.vue index 78bfd937..2e690134 100644 --- a/src/views/system/sms/smsLog/index.vue +++ b/src/views/system/sms/smsLog/index.vue @@ -50,12 +50,11 @@ <el-date-picker v-model="queryParams.sendTime" style="width: 240px" - value-format="yyyy-MM-dd HH:mm:ss" + value-format="YYYY-MM-DD HH:mm:ss" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" - :default-time="['00:00:00', '23:59:59']" /> </el-form-item> <el-form-item label="接收状态" prop="receiveStatus"> @@ -72,12 +71,11 @@ <el-date-picker v-model="queryParams.receiveTime" style="width: 240px" - value-format="yyyy-MM-dd HH:mm:ss" + value-format="YYYY-MM-DD HH:mm:ss" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" - :default-time="['00:00:00', '23:59:59']" /> </el-form-item> <el-form-item> From ef3fbc5a04a0e9fb0ce0147a8c8c7f3a838225f6 Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Wed, 22 Mar 2023 18:13:28 +0800 Subject: [PATCH 061/184] =?UTF-8?q?update:=20=E4=BF=AE=E5=A4=8D=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E8=AF=A6=E6=83=85=E6=8A=A5=E9=94=99=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98=EF=BC=8C=E8=A7=A3=E5=86=B3=E8=A1=A8=E5=8D=95ref?= =?UTF-8?q?=E5=91=BD=E5=90=8D=E5=92=8C=E8=A1=A8=E5=8D=95=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=8F=98=E9=87=8F=E5=91=BD=E5=90=8D=E5=86=B2=E7=AA=81=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/sms/smsLog/index.vue | 53 +++++++++++++++------------ 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/views/system/sms/smsLog/index.vue b/src/views/system/sms/smsLog/index.vue index 2e690134..f3d17666 100644 --- a/src/views/system/sms/smsLog/index.vue +++ b/src/views/system/sms/smsLog/index.vue @@ -175,78 +175,83 @@ /> <!-- 短信日志详细 --> <el-dialog title="短信日志详细" v-model="open" width="700px" append-to-body> - <el-form ref="form" :model="form" label-width="140px" size="mini"> + <el-form ref="formRef" :model="formData" label-width="140px"> <el-row> <el-col :span="24"> - <el-form-item label="日志主键:">{{ form.id }}</el-form-item> + <el-form-item label="日志主键:">{{ formData.id }}</el-form-item> </el-col> <el-col :span="24"> <el-form-item label="短信渠道:"> - {{ formatChannelSignature(form.channelId) }} - <dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="form.channelCode" /> + {{ formatChannelSignature(formData.channelId) }} + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="formData.channelCode" /> </el-form-item> </el-col> <el-col :span="24"> <el-form-item label="短信模板:"> - {{ form.templateId }} | {{ form.templateCode }} - <dict-tag :type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE" :value="form.templateType" /> + {{ formData.templateId }} | {{ formData.templateCode }} + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE" :value="formData.templateType" /> </el-form-item> </el-col> <el-col :span="24"> - <el-form-item label="API 的模板编号:">{{ form.apiTemplateId }}</el-form-item> + <el-form-item label="API 的模板编号:">{{ formData.apiTemplateId }}</el-form-item> </el-col> <el-col :span="24"> <el-form-item label="用户信息:" - >{{ form.mobile }} - <span v-if="form.userType && form.userId"> - <dict-tag :type="DICT_TYPE.USER_TYPE" :value="form.userType" />({{ form.userId }}) + >{{ formData.mobile }} + <span v-if="formData.userType && formData.userId"> + <dict-tag :type="DICT_TYPE.USER_TYPE" :value="formData.userType" />({{ + formData.userId + }}) </span> </el-form-item> </el-col> <el-col :span="24"> - <el-form-item label="短信内容:">{{ form.templateContent }}</el-form-item> + <el-form-item label="短信内容:">{{ formData.templateContent }}</el-form-item> </el-col> <el-col :span="24"> - <el-form-item label="短信参数:">{{ form.templateParams }}</el-form-item> + <el-form-item label="短信参数:">{{ formData.templateParams }}</el-form-item> </el-col> <el-col :span="24"> - <el-form-item label="创建时间:">{{ parseTime(form.createTime) }}</el-form-item> + <el-form-item label="创建时间:">{{ parseTime(formData.createTime) }}</el-form-item> </el-col> <el-col :span="24"> <el-form-item label="发送状态:"> - <dict-tag :type="DICT_TYPE.SYSTEM_SMS_SEND_STATUS" :value="form.sendStatus" /> + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_SEND_STATUS" :value="formData.sendStatus" /> </el-form-item> </el-col> <el-col :span="24"> - <el-form-item label="发送时间:">{{ parseTime(form.sendTime) }}</el-form-item> + <el-form-item label="发送时间:">{{ parseTime(formData.sendTime) }}</el-form-item> </el-col> <el-col :span="24"> <el-form-item label="发送结果:" - >{{ form.sendCode }} | {{ form.sendMsg }} + >{{ formData.sendCode }} | {{ formData.sendMsg }} </el-form-item> </el-col> <el-col :span="24"> <el-form-item label="API 发送结果:" - >{{ form.apiSendCode }} | {{ form.apiSendMsg }}</el-form-item + >{{ formData.apiSendCode }} | {{ formData.apiSendMsg }}</el-form-item > </el-col> <el-col :span="24"> - <el-form-item label="API 短信编号:">{{ form.apiSerialNo }}</el-form-item> + <el-form-item label="API 短信编号:">{{ formData.apiSerialNo }}</el-form-item> </el-col> <el-col :span="24"> - <el-form-item label="API 请求编号:">{{ form.apiRequestId }}</el-form-item> + <el-form-item label="API 请求编号:">{{ formData.apiRequestId }}</el-form-item> </el-col> <el-col :span="24"> <el-form-item label="接收状态:"> - <dict-tag :type="DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS" :value="form.receiveStatus" /> + <dict-tag + :type="DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS" + :value="formData.receiveStatus" + /> </el-form-item> </el-col> <el-col :span="24"> - <el-form-item label="接收时间:">{{ parseTime(form.receiveTime) }}</el-form-item> + <el-form-item label="接收时间:">{{ parseTime(formData.receiveTime) }}</el-form-item> </el-col> <el-col :span="24"> <el-form-item label="API 接收结果:" - >{{ form.apiReceiveCode }} | {{ form.apiReceiveMsg }} + >{{ formData.apiReceiveCode }} | {{ formData.apiReceiveMsg }} </el-form-item> </el-col> </el-row> @@ -363,7 +368,7 @@ const getList = () => { } // ================== 详情 ==================== const open = ref(false) -const form = ref<SmsLogApi.SmsLogVO>({ +const formData = ref<SmsLogApi.SmsLogVO>({ id: null, channelId: null, channelCode: '', @@ -392,8 +397,8 @@ const form = ref<SmsLogApi.SmsLogVO>({ }) /** 详细按钮操作 */ const handleView = (row: SmsLogApi.SmsLogVO) => { + formData.value = row open.value = true - form.value = row } getList() </script> From 67de9fc5676f6a980ab5f9eaccfb5ebfde98ceaf Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Wed, 22 Mar 2023 21:50:44 +0800 Subject: [PATCH 062/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E6=B5=81=E5=88=86=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/bpm/userGroup/index.ts | 12 +- src/api/system/user/index.ts | 2 +- src/types/auto-components.d.ts | 1 + src/views/bpm/group/UserGroupForm.vue | 132 +++++++++ src/views/bpm/group/group.data.ts | 64 ----- src/views/bpm/group/index.vue | 314 +++++++++++---------- src/views/bpm/processInstance/detail.vue | 2 +- src/views/bpm/taskAssignRule/index.vue | 8 +- src/views/system/dept/index.vue | 4 +- src/views/system/notify/template/index.vue | 4 +- 10 files changed, 307 insertions(+), 236 deletions(-) create mode 100644 src/views/bpm/group/UserGroupForm.vue delete mode 100644 src/views/bpm/group/group.data.ts diff --git a/src/api/bpm/userGroup/index.ts b/src/api/bpm/userGroup/index.ts index 88ee9619..c3399f27 100644 --- a/src/api/bpm/userGroup/index.ts +++ b/src/api/bpm/userGroup/index.ts @@ -11,7 +11,7 @@ export type UserGroupVO = { } // 创建用户组 -export const createUserGroupApi = async (data: UserGroupVO) => { +export const createUserGroup = async (data: UserGroupVO) => { return await request.post({ url: '/bpm/user-group/create', data: data @@ -19,7 +19,7 @@ export const createUserGroupApi = async (data: UserGroupVO) => { } // 更新用户组 -export const updateUserGroupApi = async (data: UserGroupVO) => { +export const updateUserGroup = async (data: UserGroupVO) => { return await request.put({ url: '/bpm/user-group/update', data: data @@ -27,21 +27,21 @@ export const updateUserGroupApi = async (data: UserGroupVO) => { } // 删除用户组 -export const deleteUserGroupApi = async (id: number) => { +export const deleteUserGroup = async (id: number) => { return await request.delete({ url: '/bpm/user-group/delete?id=' + id }) } // 获得用户组 -export const getUserGroupApi = async (id: number) => { +export const getUserGroup = async (id: number) => { return await request.get({ url: '/bpm/user-group/get?id=' + id }) } // 获得用户组分页 -export const getUserGroupPageApi = async (params) => { +export const getUserGroupPage = async (params) => { return await request.get({ url: '/bpm/user-group/page', params }) } // 获取用户组精简信息列表 -export const listSimpleUserGroupsApi = async () => { +export const listSimpleUserGroup = async () => { return await request.get({ url: '/bpm/user-group/list-all-simple' }) } diff --git a/src/api/system/user/index.ts b/src/api/system/user/index.ts index e505921d..3cc0a84d 100644 --- a/src/api/system/user/index.ts +++ b/src/api/system/user/index.ts @@ -86,6 +86,6 @@ export const updateUserStatusApi = (id: number, status: number) => { } // 获取用户精简信息列表 -export const getListSimpleUsersApi = () => { +export const getSimpleUserList = () => { return request.get({ url: '/system/user/list-all-simple' }) } diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index be71517c..6284d4dc 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -30,6 +30,7 @@ declare module '@vue/runtime-core' { ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] ElCard: typeof import('element-plus/es')['ElCard'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] + ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] ElCol: typeof import('element-plus/es')['ElCol'] ElCollapse: typeof import('element-plus/es')['ElCollapse'] ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] diff --git a/src/views/bpm/group/UserGroupForm.vue b/src/views/bpm/group/UserGroupForm.vue new file mode 100644 index 00000000..05d646d2 --- /dev/null +++ b/src/views/bpm/group/UserGroupForm.vue @@ -0,0 +1,132 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="100px" + v-loading="formLoading" + > + <el-form-item label="组名" prop="name"> + <el-input v-model="formData.name" placeholder="请输入组名" /> + </el-form-item> + <el-form-item label="描述"> + <el-input type="textarea" v-model="formData.name" placeholder="请输入描述" /> + </el-form-item> + <el-form-item label="成员" prop="memberUserIds"> + <el-select v-model="formData.memberUserIds" multiple placeholder="请选择成员"> + <el-option + v-for="user in userList" + :key="user.id" + :label="user.nickname" + :value="user.id" + /> + </el-select> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-radio-group v-model="formData.status"> + <el-radio + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="dict.value" + > + {{ dict.label }} + </el-radio> + </el-radio-group> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { CommonStatusEnum } from '@/utils/constants' +import * as UserGroupApi from '@/api/bpm/userGroup' +import * as UserApi from '@/api/system/user' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: undefined, + name: undefined, + description: undefined, + memberUserIds: undefined, + status: CommonStatusEnum.ENABLE +}) +const formRules = reactive({ + name: [{ required: true, message: '组名不能为空', trigger: 'blur' }], + description: [{ required: true, message: '描述不能为空', trigger: 'blur' }], + memberUserIds: [{ required: true, message: '成员不能为空', trigger: 'blur' }], + status: [{ required: true, message: '状态不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref +const userList = ref([]) // 用户列表 + +/** 打开弹窗 */ +const open = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await UserGroupApi.getUserGroup(id) + } finally { + formLoading.value = false + } + } + // 加载用户列表 + userList.value = await UserApi.getSimpleUserList() +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as UserGroupApi.UserGroupVO + if (formType.value === 'create') { + await UserGroupApi.createUserGroup(data) + message.success(t('common.createSuccess')) + } else { + await UserGroupApi.updateUserGroup(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + name: undefined, + description: undefined, + memberUserIds: undefined, + status: CommonStatusEnum.ENABLE + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/bpm/group/group.data.ts b/src/views/bpm/group/group.data.ts deleted file mode 100644 index 613a7290..00000000 --- a/src/views/bpm/group/group.data.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' - -const { t } = useI18n() // 国际化 - -// 表单校验 -export const rules = reactive({ - name: [required], - description: [required], - memberUserIds: [required], - status: [required] -}) - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'id', - primaryTitle: '编号', - action: true, - searchSpan: 8, - columns: [ - { - title: '组名', - field: 'name', - isSearch: true - }, - { - title: '成员', - field: 'memberUserIds', - table: { - slots: { - default: 'memberUserIds_default' - } - } - }, - { - title: '描述', - field: 'description' - }, - { - title: t('common.status'), - field: 'status', - dictType: DICT_TYPE.COMMON_STATUS, - dictClass: 'number', - isSearch: true - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false, - isSearch: true, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - }, - table: { - width: 180 - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) diff --git a/src/views/bpm/group/index.vue b/src/views/bpm/group/index.vue index b0f08516..5829ebad 100644 --- a/src/views/bpm/group/index.vue +++ b/src/views/bpm/group/index.vue @@ -1,182 +1,184 @@ <template> <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <!-- 操作:新增 --> - <XButton + <!-- 搜索工作栏 --> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="组名" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入组名" + clearable + @keyup.enter="handleQuery" + class="!w-240px" + /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px"> + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + value-format="yyyy-MM-dd HH:mm:ss" + type="daterange" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" + @click="openForm('create')" v-hasPermi="['bpm:user-group:create']" - @click="handleCreate()" - /> - </template> - <template #memberUserIds_default="{ row }"> - <span v-for="userId in row.memberUserIds" :key="userId"> - {{ getUserNickname(userId) }} - </span> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:修改 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['bpm:user-group:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['bpm:user-group:query']" - @click="handleDetail(row.id)" - /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['bpm:user-group:delete']" - @click="deleteData(row.id)" - /> - </template> - </XTable> + > + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> + </el-form-item> + </el-form> </ContentWrap> - <XModal v-model="dialogVisible" :title="dialogTitle" :mask-closable="false"> - <!-- 对话框(添加 / 修改) --> - <Form - v-if="['create', 'update'].includes(actionType)" - :schema="allSchemas.formSchema" - :rules="rules" - ref="formRef" - > - <template #memberUserIds="form"> - <el-select v-model="form.memberUserIds" multiple> - <el-option v-for="item in users" :key="item.id" :label="item.nickname" :value="item.id" /> - </el-select> - </template> - </Form> - <!-- 对话框(详情) --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - > - <template #memberUserIds="{ row }"> - <span v-for="userId in row.memberUserIds" :key="userId"> - {{ getUserNickname(userId) }} - </span> - </template> - </Descriptions> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="actionLoading" - @click="submitForm" + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list"> + <el-table-column label="编号" align="center" prop="id" /> + <el-table-column label="组名" align="center" prop="name" /> + <el-table-column label="描述" align="center" prop="description" /> + <el-table-column label="成员" align="center"> + <template #default="scope"> + <span v-for="userId in scope.row.memberUserIds" :key="userId" class="pr-5px"> + {{ userList.find((user) => user.id === userId)?.nickname }} + </span> + </template> + </el-table-column> + <el-table-column label="状态" align="center" prop="status"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> + </template> + </el-table-column> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + :formatter="dateFormatter" /> - <!-- 按钮:关闭 --> - <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openForm('update', scope.row.id)" + v-hasPermi="['bpm:user-group:update']" + > + 编辑 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['bpm:user-group:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </ContentWrap> + + <!-- 表单弹窗:添加/修改 --> + <UserGroupForm ref="formRef" @success="getList" /> </template> -<script setup lang="ts"> -// 业务相关的 import +<script setup lang="ts" name="UserGroup"> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' import * as UserGroupApi from '@/api/bpm/userGroup' -import { getListSimpleUsersApi, UserVO } from '@/api/system/user' -import { allSchemas, rules } from './group.data' -import { FormExpose } from '@/components/Form' - -const { t } = useI18n() // 国际化 +import * as UserApi from '@/api/system/user' +import UserGroupForm from './UserGroupForm.vue' const message = useMessage() // 消息弹窗 -// 列表相关的变量 -const [registerTable, { reload, deleteData }] = useXTable({ - allSchemas: allSchemas, - getListApi: UserGroupApi.getUserGroupPageApi, - deleteApi: UserGroupApi.deleteUserGroupApi +const { t } = useI18n() // 国际化 + +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + name: null, + status: null, + createTime: [] }) -// 用户列表 -const users = ref<UserVO[]>([]) +const queryFormRef = ref() // 搜索的表单 +const userList = ref([]) // 用户列表 -const getUserNickname = (userId) => { - for (const user of users.value) { - if (user.id === userId) { - return user.nickname - } +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await UserGroupApi.getUserGroupPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false } - return '未知(' + userId + ')' } -// ========== CRUD 相关 ========== -const actionLoading = ref(false) // 遮罩层 -const actionType = ref('') // 操作按钮的类型 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const formRef = ref<FormExpose>() // 表单 Ref -const detailData = ref() // 详情 Ref - -// 设置标题 -const setDialogTile = (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() } -// 新增操作 -const handleCreate = () => { - setDialogTile('create') +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() } -// 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - // 设置数据 - const res = await UserGroupApi.getUserGroupApi(rowId) - unref(formRef)?.setValues(res) +/** 添加/修改操作 */ +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) } -// 详情操作 -const handleDetail = async (rowId: number) => { - setDialogTile('detail') - detailData.value = await UserGroupApi.getUserGroupApi(rowId) +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await UserGroupApi.deleteUserGroup(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} } -// 提交按钮 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - actionLoading.value = true - // 提交请求 - try { - const data = unref(formRef)?.formModel as UserGroupApi.UserGroupVO - if (actionType.value === 'create') { - await UserGroupApi.createUserGroupApi(data) - message.success(t('common.createSuccess')) - } else { - await UserGroupApi.updateUserGroupApi(data) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false - } finally { - actionLoading.value = false - // 刷新列表 - await reload() - } - } - }) -} - -// ========== 初始化 ========== -onMounted(() => { - getListSimpleUsersApi().then((data) => { - users.value = data - }) +/** 初始化 **/ +onMounted(async () => { + await getList() + // 加载用户列表 + userList.value = await UserApi.getSimpleUserList() }) </script> diff --git a/src/views/bpm/processInstance/detail.vue b/src/views/bpm/processInstance/detail.vue index b6d52a4c..7d159673 100644 --- a/src/views/bpm/processInstance/detail.vue +++ b/src/views/bpm/processInstance/detail.vue @@ -378,7 +378,7 @@ onMounted(() => { // 加载详情 getDetail() // 加载用户的列表 - UserApi.getListSimpleUsersApi().then((data) => { + UserApi.getSimpleUserList().then((data) => { userOptions.value.push(...data) }) }) diff --git a/src/views/bpm/taskAssignRule/index.vue b/src/views/bpm/taskAssignRule/index.vue index 012e6f68..6e1432b6 100644 --- a/src/views/bpm/taskAssignRule/index.vue +++ b/src/views/bpm/taskAssignRule/index.vue @@ -140,8 +140,8 @@ import { FormInstance } from 'element-plus' import * as TaskAssignRuleApi from '@/api/bpm/taskAssignRule' import { listSimpleRolesApi } from '@/api/system/role' import { listSimplePostsApi } from '@/api/system/post' -import { getListSimpleUsersApi } from '@/api/system/user' -import { listSimpleUserGroupsApi } from '@/api/bpm/userGroup' +import { getSimpleUserList } from '@/api/system/user' +import { listSimpleUserGroup } from '@/api/bpm/userGroup' import { listSimpleDeptApi } from '@/api/system/dept' import { DICT_TYPE, getDictOptions } from '@/utils/dict' import { handleTree, defaultProps } from '@/utils/tree' @@ -341,12 +341,12 @@ onMounted(() => { }) // 获得用户列表 userOptions.value = [] - getListSimpleUsersApi().then((data) => { + getSimpleUserList().then((data) => { userOptions.value.push(...data) }) // 获得用户组列表 userGroupOptions.value = [] - listSimpleUserGroupsApi().then((data) => { + listSimpleUserGroup().then((data) => { userGroupOptions.value.push(...data) }) if (!isShow) { diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue index 3b182e2a..4e70070f 100644 --- a/src/views/system/dept/index.vue +++ b/src/views/system/dept/index.vue @@ -79,7 +79,7 @@ import { handleTree, defaultProps } from '@/utils/tree' import type { FormExpose } from '@/components/Form' import { allSchemas, rules } from './dept.data' import * as DeptApi from '@/api/system/dept' -import { getListSimpleUsersApi, UserVO } from '@/api/system/user' +import { getSimpleUserList, UserVO } from '@/api/system/user' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -102,7 +102,7 @@ const deptOptions = ref() // 树形结构 const userOption = ref<UserVO[]>([]) const getUserList = async () => { - const res = await getListSimpleUsersApi() + const res = await getSimpleUserList() userOption.value = res } // 获取下拉框[上级]的数据 diff --git a/src/views/system/notify/template/index.vue b/src/views/system/notify/template/index.vue index 4ec16a0a..c4113924 100644 --- a/src/views/system/notify/template/index.vue +++ b/src/views/system/notify/template/index.vue @@ -119,7 +119,7 @@ import { FormExpose } from '@/components/Form' // 业务相关的 import import { rules, allSchemas } from './template.data' import * as NotifyTemplateApi from '@/api/system/notify/template' -import { getListSimpleUsersApi, UserVO } from '@/api/system/user' +import { getSimpleUserList, UserVO } from '@/api/system/user' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -244,7 +244,7 @@ const sendTest = async () => { // ========== 初始化 ========== onMounted(() => { - getListSimpleUsersApi().then((data) => { + getSimpleUserList().then((data) => { userOption.value = data }) }) From c424ee76e4e5145292cb86619899e65fe27ab1ba Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Wed, 22 Mar 2023 22:11:03 +0800 Subject: [PATCH 063/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E5=B2=97=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/post/index.ts | 26 ++----- src/views/bpm/taskAssignRule/index.vue | 4 +- .../system/post/{form.vue => PostForm.vue} | 18 +++-- src/views/system/post/index.vue | 78 ++++++++++--------- src/views/system/user/index.vue | 4 +- 5 files changed, 63 insertions(+), 67 deletions(-) rename src/views/system/post/{form.vue => PostForm.vue} (91%) diff --git a/src/api/system/post/index.ts b/src/api/system/post/index.ts index 9e2540f0..98df227f 100644 --- a/src/api/system/post/index.ts +++ b/src/api/system/post/index.ts @@ -10,49 +10,37 @@ export interface PostVO { createTime?: Date } -export interface PostPageReqVO extends PageParam { - code?: string - name?: string - status?: number -} - -export interface PostExportReqVO { - code?: string - name?: string - status?: number -} - // 查询岗位列表 -export const getPostPageApi = async (params: PostPageReqVO) => { +export const getPostPage = async (params: PageParam) => { return await request.get({ url: '/system/post/page', params }) } // 获取岗位精简信息列表 -export const listSimplePostsApi = async () => { +export const getSimplePostList = async () => { return await request.get({ url: '/system/post/list-all-simple' }) } // 查询岗位详情 -export const getPostApi = async (id: number) => { +export const getPost = async (id: number) => { return await request.get({ url: '/system/post/get?id=' + id }) } // 新增岗位 -export const createPostApi = async (data: PostVO) => { +export const createPost = async (data: PostVO) => { return await request.post({ url: '/system/post/create', data }) } // 修改岗位 -export const updatePostApi = async (data: PostVO) => { +export const updatePost = async (data: PostVO) => { return await request.put({ url: '/system/post/update', data }) } // 删除岗位 -export const deletePostApi = async (id: number) => { +export const deletePost = async (id: number) => { return await request.delete({ url: '/system/post/delete?id=' + id }) } // 导出岗位 -export const exportPostApi = async (params: PostExportReqVO) => { +export const exportPost = async (params) => { return await request.download({ url: '/system/post/export', params }) } diff --git a/src/views/bpm/taskAssignRule/index.vue b/src/views/bpm/taskAssignRule/index.vue index 6e1432b6..8db7b578 100644 --- a/src/views/bpm/taskAssignRule/index.vue +++ b/src/views/bpm/taskAssignRule/index.vue @@ -139,7 +139,7 @@ import { FormInstance } from 'element-plus' // 业务相关的 import import * as TaskAssignRuleApi from '@/api/bpm/taskAssignRule' import { listSimpleRolesApi } from '@/api/system/role' -import { listSimplePostsApi } from '@/api/system/post' +import { getSimplePostList } from '@/api/system/post' import { getSimpleUserList } from '@/api/system/user' import { listSimpleUserGroup } from '@/api/bpm/userGroup' import { listSimpleDeptApi } from '@/api/system/dept' @@ -336,7 +336,7 @@ onMounted(() => { }) // 获得岗位列表 postOptions.value = [] - listSimplePostsApi().then((data) => { + getSimplePostList().then((data) => { postOptions.value.push(...data) }) // 获得用户列表 diff --git a/src/views/system/post/form.vue b/src/views/system/post/PostForm.vue similarity index 91% rename from src/views/system/post/form.vue rename to src/views/system/post/PostForm.vue index d5d65dd1..14aa9651 100644 --- a/src/views/system/post/form.vue +++ b/src/views/system/post/PostForm.vue @@ -37,6 +37,7 @@ </template> <script setup lang="ts"> import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { CommonStatusEnum } from '@/utils/constants' import * as PostApi from '@/api/system/post' const { t } = useI18n() // 国际化 @@ -50,7 +51,8 @@ const formData = ref({ id: undefined, name: '', code: '', - status: undefined, + sort: undefined, + status: CommonStatusEnum.ENABLE, remark: '' }) const formRules = reactive({ @@ -71,7 +73,7 @@ const openModal = async (type: string, id?: number) => { if (id) { formLoading.value = true try { - formData.value = await PostApi.getPostApi(id) + formData.value = await PostApi.getPost(id) } finally { formLoading.value = false } @@ -91,10 +93,10 @@ const submitForm = async () => { try { const data = formData.value as unknown as PostApi.PostVO if (formType.value === 'create') { - await PostApi.createPostApi(data) + await PostApi.createPost(data) message.success(t('common.createSuccess')) } else { - await PostApi.updatePostApi(data) + await PostApi.updatePost(data) message.success(t('common.updateSuccess')) } modelVisible.value = false @@ -109,10 +111,10 @@ const submitForm = async () => { const resetForm = () => { formData.value = { id: undefined, - title: '', - type: undefined, - content: '', - status: undefined, + name: '', + code: '', + sort: undefined, + status: CommonStatusEnum.ENABLE, remark: '' } formRef.value?.resetFields() diff --git a/src/views/system/post/index.vue b/src/views/system/post/index.vue index 321b5ae3..03e491d0 100644 --- a/src/views/system/post/index.vue +++ b/src/views/system/post/index.vue @@ -1,7 +1,13 @@ <template> - <content-wrap> + <ContentWrap> <!-- 搜索工作栏 --> - <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px"> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > <el-form-item label="岗位名称" prop="name"> <el-input v-model="queryParams.name" @@ -21,10 +27,10 @@ <el-form-item label="状态" prop="status"> <el-select v-model="queryParams.status" placeholder="请选择状态" clearable> <el-option - v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" - :key="parseInt(dict.value)" + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" :label="dict.label" - :value="parseInt(dict.value)" + :value="dict.value" /> </el-select> </el-form-item> @@ -49,15 +55,16 @@ </el-button> </el-form-item> </el-form> + </ContentWrap> - <!-- 列表 --> + <!-- 列表 --> + <ContentWrap> <el-table v-loading="loading" :data="list" align="center"> <el-table-column label="岗位编号" align="center" prop="id" /> <el-table-column label="岗位名称" align="center" prop="name" /> <el-table-column label="岗位编码" align="center" prop="code" /> <el-table-column label="岗位顺序" align="center" prop="sort" /> <el-table-column label="岗位备注" align="center" prop="remark" /> - <el-table-column label="状态" align="center" prop="status"> <template #default="scope"> <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> @@ -98,18 +105,17 @@ v-model:limit="queryParams.pageSize" @pagination="getList" /> - </content-wrap> + </ContentWrap> <!-- 表单弹窗:添加/修改 --> - <post-form ref="modalRef" @success="getList" /> + <PostForm ref="formRef" @success="getList" /> </template> <script setup lang="tsx"> -import * as PostApi from '@/api/system/post' -import PostForm from './form.vue' -import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' -import { DictTag } from '@/components/DictTag' +import * as PostApi from '@/api/system/post' +import PostForm from './PostForm.vue' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -118,20 +124,20 @@ const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 const list = ref([]) // 列表的数据 const queryParams = reactive({ + pageNo: 1, + pageSize: 10, code: '', name: '', - status: undefined, - pageNo: 1, - pageSize: 100 + status: undefined }) const queryFormRef = ref() // 搜索的表单 const exportLoading = ref(false) // 导出的加载中 + /** 查询岗位列表 */ const getList = async () => { loading.value = true try { - const data = await PostApi.getPostPageApi(queryParams) - + const data = await PostApi.getPostPage(queryParams) list.value = data.list total.value = data.total } finally { @@ -139,21 +145,6 @@ const getList = async () => { } } -/** 导出按钮操作 */ -const handleExport = async () => { - try { - // 导出的二次确认 - await message.exportConfirm() - // 发起导出 - exportLoading.value = true - const data = await PostApi.exportPostApi(queryParams) - download.excel(data, '岗位列表.xls') - } catch { - } finally { - exportLoading.value = false - } -} - /** 搜索按钮操作 */ const handleQuery = () => { queryParams.pageNo = 1 @@ -167,9 +158,9 @@ const resetQuery = () => { } /** 添加/修改操作 */ -const modalRef = ref() +const formRef = ref() const openModal = (type: string, id?: number) => { - modalRef.value.openModal(type, id) + formRef.value.openModal(type, id) } /** 删除按钮操作 */ @@ -178,13 +169,28 @@ const handleDelete = async (id: number) => { // 删除的二次确认 await message.delConfirm() // 发起删除 - await PostApi.deletePostApi(id) + await PostApi.deletePost(id) message.success(t('common.delSuccess')) // 刷新列表 await getList() } catch {} } +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await PostApi.exportPost(queryParams) + download.excel(data, '岗位列表.xls') + } catch { + } finally { + exportLoading.value = false + } +} + /** 初始化 **/ onMounted(() => { getList() diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index 2f9ba9b0..9bb50930 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -273,7 +273,7 @@ import { rules, allSchemas } from './user.data' import * as UserApi from '@/api/system/user' import { listSimpleDeptApi } from '@/api/system/dept' import { listSimpleRolesApi } from '@/api/system/role' -import { listSimplePostsApi, PostVO } from '@/api/system/post' +import { getSimplePostList, PostVO } from '@/api/system/post' import { aassignUserRoleApi, listUserRolesApi, @@ -329,7 +329,7 @@ const postOptions = ref<PostVO[]>([]) //岗位列表 // 获取岗位列表 const getPostOptions = async () => { - const res = await listSimplePostsApi() + const res = await getSimplePostList() postOptions.value.push(...res) } const dataFormater = (val) => { From 2d4e7e7d084bffed8406faa9e96214ec81e2812c Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Wed, 22 Mar 2023 22:40:56 +0800 Subject: [PATCH 064/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E9=94=99=E8=AF=AF=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/infra/apiErrorLog/index.ts | 24 +---- .../{detail.vue => ApiErrorLogDetail.vue} | 21 +++-- src/views/infra/apiErrorLog/index.vue | 88 ++++++++++--------- 3 files changed, 62 insertions(+), 71 deletions(-) rename src/views/infra/apiErrorLog/{detail.vue => ApiErrorLogDetail.vue} (80%) diff --git a/src/api/infra/apiErrorLog/index.ts b/src/api/infra/apiErrorLog/index.ts index 779cfd61..59ee2143 100644 --- a/src/api/infra/apiErrorLog/index.ts +++ b/src/api/infra/apiErrorLog/index.ts @@ -27,38 +27,20 @@ export interface ApiErrorLogVO { createTime: Date } -export interface ApiErrorLogPageReqVO extends PageParam { - userId?: number - userType?: number - applicationName?: string - requestUrl?: string - exceptionTime?: Date[] - processStatus: number -} - -export interface ApiErrorLogExportReqVO { - userId?: number - userType?: number - applicationName?: string - requestUrl?: string - exceptionTime?: Date[] - processStatus: number -} - // 查询列表API 访问日志 -export const getApiErrorLogPageApi = (params: PageParam) => { +export const getApiErrorLogPage = (params: PageParam) => { return request.get({ url: '/infra/api-error-log/page', params }) } // 更新 API 错误日志的处理状态 -export const updateApiErrorLogPageApi = (id: number, processStatus: number) => { +export const updateApiErrorLogPage = (id: number, processStatus: number) => { return request.put({ url: '/infra/api-error-log/update-status?id=' + id + '&processStatus=' + processStatus }) } // 导出API 访问日志 -export const exportApiErrorLogApi = (params) => { +export const exportApiErrorLog = (params) => { return request.download({ url: '/infra/api-error-log/export-excel', params diff --git a/src/views/infra/apiErrorLog/detail.vue b/src/views/infra/apiErrorLog/ApiErrorLogDetail.vue similarity index 80% rename from src/views/infra/apiErrorLog/detail.vue rename to src/views/infra/apiErrorLog/ApiErrorLogDetail.vue index 9ede0f1b..479c4c4b 100644 --- a/src/views/infra/apiErrorLog/detail.vue +++ b/src/views/infra/apiErrorLog/ApiErrorLogDetail.vue @@ -10,13 +10,18 @@ <el-descriptions-item label="应用名"> {{ detailData.applicationName }} </el-descriptions-item> - <el-descriptions-item label="用户信息"> - {{ detailData.userId }} | + <el-descriptions-item label="用户编号"> + {{ detailData.userId }} <dict-tag :type="DICT_TYPE.USER_TYPE" :value="detailData.userType" /> - | {{ detailData.userIp }} | {{ detailData.userAgent }} + </el-descriptions-item> + <el-descriptions-item label="用户 IP"> + {{ detailData.userIp }} + </el-descriptions-item> + <el-descriptions-item label="用户 UA"> + {{ detailData.userAgent }} </el-descriptions-item> <el-descriptions-item label="请求信息"> - {{ detailData.requestMethod }} | {{ detailData.requestUrl }} + {{ detailData.requestMethod }} {{ detailData.requestUrl }} </el-descriptions-item> <el-descriptions-item label="请求参数"> {{ detailData.requestParams }} @@ -27,7 +32,7 @@ <el-descriptions-item label="异常名"> {{ detailData.exceptionName }} </el-descriptions-item> - <el-descriptions-item label="异常名" v-if="detailData.exceptionStackTrace"> + <el-descriptions-item label="异常堆栈" v-if="detailData.exceptionStackTrace"> <el-input type="textarea" :readonly="true" @@ -50,7 +55,6 @@ </el-descriptions> </Dialog> </template> - <script setup lang="ts"> import { DICT_TYPE } from '@/utils/dict' import { formatDate } from '@/utils/formatTime' @@ -61,7 +65,7 @@ const detailLoading = ref(false) // 表单的加载中 const detailData = ref() // 详情数据 /** 打开弹窗 */ -const openModal = async (data: ApiErrorLog.ApiErrorLogVO) => { +const open = async (data: ApiErrorLog.ApiErrorLogVO) => { modelVisible.value = true // 设置数据 detailLoading.value = true @@ -71,6 +75,5 @@ const openModal = async (data: ApiErrorLog.ApiErrorLogVO) => { detailLoading.value = false } } - -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 </script> diff --git a/src/views/infra/apiErrorLog/index.vue b/src/views/infra/apiErrorLog/index.vue index 664546cb..1108651a 100644 --- a/src/views/infra/apiErrorLog/index.vue +++ b/src/views/infra/apiErrorLog/index.vue @@ -1,5 +1,5 @@ <template> - <content-wrap> + <ContentWrap> <!-- 搜索工作栏 --> <el-form class="-mb-15px" @@ -25,10 +25,10 @@ class="!w-240px" > <el-option - v-for="dict in getDictOptions(DICT_TYPE.USER_TYPE)" - :key="parseInt(dict.value)" + v-for="dict in getIntDictOptions(DICT_TYPE.USER_TYPE)" + :key="dict.value" :label="dict.label" - :value="parseInt(dict.value)" + :value="dict.value" /> </el-select> </el-form-item> @@ -53,12 +53,17 @@ /> </el-form-item> <el-form-item label="处理状态" prop="processStatus"> - <el-select v-model="queryParams.processStatus" placeholder="请选择处理状态" clearable> + <el-select + v-model="queryParams.processStatus" + placeholder="请选择处理状态" + clearable + class="!w-240px" + > <el-option - v-for="dict in getDictOptions(DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS)" - :key="parseInt(dict.value)" + v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS)" + :key="dict.value" :label="dict.label" - :value="parseInt(dict.value)" + :value="dict.value" /> </el-select> </el-form-item> @@ -76,9 +81,10 @@ </el-button> </el-form-item> </el-form> - </content-wrap> + </ContentWrap> + <!-- 列表 --> - <content-wrap> + <ContentWrap> <el-table v-loading="loading" :data="list"> <el-table-column label="日志编号" align="center" prop="id" /> <el-table-column label="用户编号" align="center" prop="userId" /> @@ -87,15 +93,17 @@ <dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" /> </template> </el-table-column> - <el-table-column label="应用名" align="center" prop="applicationName" /> - <el-table-column label="请求方法名" align="center" prop="requestMethod" /> - <el-table-column label="请求地址" align="center" prop="requestUrl" width="250" /> - <el-table-column label="异常发生时间" align="center" prop="exceptionTime" width="180"> - <template #default="scope"> - <span>{{ scope.row.exceptionTime }}</span> - </template> - </el-table-column> - <el-table-column label="异常名" align="center" prop="exceptionName" width="250" /> + <el-table-column label="应用名" align="center" prop="applicationName" width="200" /> + <el-table-column label="请求方法" align="center" prop="requestMethod" width="80" /> + <el-table-column label="请求地址" align="center" prop="requestUrl" width="180" /> + <el-table-column + label="异常发生时间" + align="center" + prop="exceptionTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column label="异常名" align="center" prop="exceptionName" width="180" /> <el-table-column label="处理状态" align="center" prop="processStatus"> <template #default="scope"> <dict-tag @@ -104,12 +112,12 @@ /> </template> </el-table-column> - <el-table-column label="操作" align="center"> + <el-table-column label="操作" align="center" width="200"> <template #default="scope"> <el-button link type="primary" - @click="openModal(scope.row)" + @click="openDetail(scope.row)" v-hasPermi="['infra:api-access-log:query']" > 详细 @@ -118,9 +126,7 @@ link type="primary" v-if="scope.row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT" - @click=" - handleProcessClick(InfraApiErrorLogProcessStatusEnum.DONE, '已处理', scope.row.id) - " + @click="handleProcess(scope.row.id, InfraApiErrorLogProcessStatusEnum.DONE)" v-hasPermi="['infra:api-error-log:update-status']" > 已处理 @@ -128,11 +134,8 @@ <el-button link type="primary" - icon="el-icon-check" v-if="scope.row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT" - @click=" - handleProcessClick(InfraApiErrorLogProcessStatusEnum.IGNORE, '已忽略', scope.row.id) - " + @click="handleProcess(scope.row.id, InfraApiErrorLogProcessStatusEnum.IGNORE)" v-hasPermi="['infra:api-error-log:update-status']" > 已忽略 @@ -140,7 +143,6 @@ </template> </el-table-column> </el-table> - <!-- 分页组件 --> <Pagination :total="total" @@ -148,18 +150,20 @@ v-model:limit="queryParams.pageSize" @pagination="getList" /> - </content-wrap> + </ContentWrap> <!-- 表单弹窗:详情 --> - <api-error-log-detail ref="modalRef" /> + <ApiErrorLogDetail ref="detailRef" /> </template> <script setup lang="ts" name="ApiErrorLog"> -import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' import * as ApiErrorLogApi from '@/api/infra/apiErrorLog' -import ApiErrorLogDetail from './detail.vue' +import ApiErrorLogDetail from './ApiErrorLogDetail.vue' import { InfraApiErrorLogProcessStatusEnum } from '@/utils/constants' + const message = useMessage() // 消息弹窗 const loading = ref(true) // 列表的加载中 @@ -182,13 +186,14 @@ const exportLoading = ref(false) // 导出的加载中 const getList = async () => { loading.value = true try { - const data = await ApiErrorLogApi.getApiErrorLogPageApi(queryParams) + const data = await ApiErrorLogApi.getApiErrorLogPage(queryParams) list.value = data.list total.value = data.total } finally { loading.value = false } } + /** 搜索按钮操作 */ const handleQuery = () => { queryParams.pageNo = 1 @@ -202,18 +207,19 @@ const resetQuery = () => { } /** 详情操作 */ -const modalRef = ref() -const openModal = (data: ApiErrorLogApi.ApiErrorLogVO) => { - modalRef.value.openModal(data) +const detailRef = ref() +const openDetail = (data: ApiErrorLogApi.ApiErrorLogVO) => { + detailRef.value.open(data) } /** 处理已处理 / 已忽略的操作 **/ -const handleProcessClick = async (processStatus: number, type: string, id: number) => { +const handleProcess = async (id: number, processStatus: number) => { try { // 操作的二次确认 + const type = processStatus === InfraApiErrorLogProcessStatusEnum.DONE ? '已处理' : '已忽略' await message.confirm('确认标记为' + type + '?') // 执行操作 - await ApiErrorLogApi.updateApiErrorLogPageApi(id, processStatus) + await ApiErrorLogApi.updateApiErrorLogPage(id, processStatus) await message.success(type) // 刷新列表 await getList() @@ -227,8 +233,8 @@ const handleExport = async () => { await message.exportConfirm() // 发起导出 exportLoading.value = true - const data = await ApiErrorLogApi.exportApiErrorLogApi(queryParams) - download.excel(data, '操作日志.xls') + const data = await ApiErrorLogApi.exportApiErrorLog(queryParams) + download.excel(data, '异常日志.xls') } catch { } finally { exportLoading.value = false From 80d540790e2f58b19480d2e10d9794c9ca027cbd Mon Sep 17 00:00:00 2001 From: fengjingtao <fessor@139.com> Date: Wed, 22 Mar 2023 23:37:29 +0800 Subject: [PATCH 065/184] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E9=87=8D=E5=86=99-=E9=83=A8=E9=97=A8=E6=A0=91?= =?UTF-8?q?=EF=BC=8C=E8=A1=A8=E6=A0=BC=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/user/index.vue | 817 ++++++++------------------------ 1 file changed, 185 insertions(+), 632 deletions(-) diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index c0f922e8..2b4bcc41 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -10,9 +10,12 @@ placeholder="请输入部门名称" clearable size="small" - prefix-icon="el-icon-search" style="margin-bottom: 20px" - /> + > + <template #prefix> + <Icon icon="ep:search" /> + </template> + </el-input> </div> <div class="head-container"> <el-tree @@ -20,10 +23,11 @@ :props="defaultProps" :expand-on-click-node="false" :filter-node-method="filterNode" - ref="tree" + ref="treeRef" + node-key="id" default-expand-all highlight-current - @node-click="handleNodeClick" + @node-click="handleDeptNodeClick" /> </div> </el-col> @@ -43,7 +47,7 @@ placeholder="请输入用户名称" clearable style="width: 240px" - @keyup.enter.native="handleQuery" + @keyup.enter="handleQuery" /> </el-form-item> <el-form-item label="手机号码" prop="mobile"> @@ -52,7 +56,7 @@ placeholder="请输入手机号码" clearable style="width: 240px" - @keyup.enter.native="handleQuery" + @keyup.enter="handleQuery" /> </el-form-item> <el-form-item label="状态" prop="status"> @@ -83,8 +87,8 @@ /> </el-form-item> <el-form-item> - <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button> - <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button> + <el-button type="primary" @click="handleQuery"><Icon icon="ep:search" />搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button> </el-form-item> </el-form> @@ -93,41 +97,37 @@ <el-button type="primary" plain - icon="el-icon-plus" - size="mini" + size="small" @click="handleAdd" v-hasPermi="['system:user:create']" - >新增</el-button + ><Icon icon="ep:plus" />新增</el-button > </el-col> <el-col :span="1.5"> <el-button type="info" - icon="el-icon-upload2" - size="mini" + size="small" @click="handleImport" v-hasPermi="['system:user:import']" - >导入</el-button + ><Icon icon="ep:upload" />导入</el-button > </el-col> <el-col :span="1.5"> <el-button type="warning" - icon="el-icon-download" - size="mini" + size="small" @click="handleExport" :loading="exportLoading" v-hasPermi="['system:user:export']" - >导出</el-button + ><Icon icon="ep:download" />导出</el-button > </el-col> - <right-toolbar + <!-- <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns" - ></right-toolbar> + ></right-toolbar> --> </el-row> - <el-table v-loading="loading" :data="userList"> <el-table-column label="用户编号" @@ -169,7 +169,7 @@ width="120" /> <el-table-column label="状态" key="status" v-if="columns[5].visible" align="center"> - <template v-slot="scope"> + <template #default="scope"> <el-switch v-model="scope.row.status" :active-value="0" @@ -185,7 +185,7 @@ v-if="columns[6].visible" width="160" > - <template v-slot="scope"> + <template #default="scope"> <span>{{ parseTime(scope.row.createTime) }}</span> </template> </el-table-column> @@ -195,14 +195,13 @@ width="160" class-name="small-padding fixed-width" > - <template v-slot="scope"> + <template #default="scope"> <el-button - size="mini" + size="small" type="text" - icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:user:update']" - >修改</el-button + ><Icon icon="ep:edit" />修改</el-button > <el-dropdown @command="(command) => handleCommand(command, scope.$index, scope.row)" @@ -212,630 +211,184 @@ 'system:permission:assign-user-role' ]" > - <el-button size="mini" type="text" icon="el-icon-d-arrow-right">更多</el-button> - <el-dropdown-menu slot="dropdown"> - <el-dropdown-item - command="handleDelete" - v-if="scope.row.id !== 1" - size="mini" - type="text" - icon="el-icon-delete" - v-hasPermi="['system:user:delete']" - >删除</el-dropdown-item - > - <el-dropdown-item - command="handleResetPwd" - size="mini" - type="text" - icon="el-icon-key" - v-hasPermi="['system:user:update-password']" - >重置密码</el-dropdown-item - > - <el-dropdown-item - command="handleRole" - size="mini" - type="text" - icon="el-icon-circle-check" - v-hasPermi="['system:permission:assign-user-role']" - >分配角色</el-dropdown-item - > - </el-dropdown-menu> + <el-button size="small" type="text"><Icon icon="ep:d-arrow-right" />更多</el-button> + <template #dropdown> + <el-dropdown-menu> + <el-dropdown-item + command="handleDelete" + v-if="scope.row.id !== 1" + size="small" + type="text" + v-hasPermi="['system:user:delete']" + ><Icon icon="ep:delete" />删除</el-dropdown-item + > + <el-dropdown-item + command="handleResetPwd" + size="small" + type="text" + v-hasPermi="['system:user:update-password']" + ><Icon icon="ep:key" />重置密码</el-dropdown-item + > + <el-dropdown-item + command="handleRole" + size="small" + type="text" + v-hasPermi="['system:permission:assign-user-role']" + ><Icon icon="ep:circle-check" />分配角色</el-dropdown-item + > + </el-dropdown-menu> + </template> </el-dropdown> </template> </el-table-column> </el-table> - <pagination v-show="total > 0" :total="total" - :page.sync="queryParams.pageNo" - :limit.sync="queryParams.pageSize" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" @pagination="getList" /> </el-col> </el-row> - - <!-- 添加或修改参数配置对话框 --> - <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body> - <el-form ref="form" :model="form" :rules="rules" label-width="80px"> - <el-row> - <el-col :span="12"> - <el-form-item label="用户昵称" prop="nickname"> - <el-input v-model="form.nickname" placeholder="请输入用户昵称" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="归属部门" prop="deptId"> - <treeselect - v-model="form.deptId" - :options="deptOptions" - :show-count="true" - :clearable="false" - placeholder="请选择归属部门" - :normalizer="normalizer" - /> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="12"> - <el-form-item label="手机号码" prop="mobile"> - <el-input v-model="form.mobile" placeholder="请输入手机号码" maxlength="11" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="邮箱" prop="email"> - <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" /> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="12"> - <el-form-item v-if="form.id === undefined" label="用户名称" prop="username"> - <el-input v-model="form.username" placeholder="请输入用户名称" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item v-if="form.id === undefined" label="用户密码" prop="password"> - <el-input - v-model="form.password" - placeholder="请输入用户密码" - type="password" - show-password - /> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="12"> - <el-form-item label="用户性别"> - <el-select v-model="form.sex" placeholder="请选择"> - <el-option - v-for="dict in sexDictDatas" - :key="parseInt(dict.value)" - :label="dict.label" - :value="parseInt(dict.value)" - /> - </el-select> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="岗位"> - <el-select v-model="form.postIds" multiple placeholder="请选择"> - <el-option - v-for="item in postOptions" - :key="item.id" - :label="item.name" - :value="item.id" - ></el-option> - </el-select> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="24"> - <el-form-item label="备注"> - <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input> - </el-form-item> - </el-col> - </el-row> - </el-form> - <div slot="footer" class="dialog-footer"> - <el-button type="primary" @click="submitForm">确 定</el-button> - <el-button @click="cancel">取 消</el-button> - </div> - </el-dialog> - - <!-- 用户导入对话框 --> - <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body> - <el-upload - ref="upload" - :limit="1" - accept=".xlsx, .xls" - :headers="upload.headers" - :action="upload.url + '?updateSupport=' + upload.updateSupport" - :disabled="upload.isUploading" - :on-progress="handleFileUploadProgress" - :on-success="handleFileSuccess" - :auto-upload="false" - drag - > - <i class="el-icon-upload"></i> - <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> - <div class="el-upload__tip text-center" slot="tip"> - <div class="el-upload__tip" slot="tip"> - <el-checkbox v-model="upload.updateSupport" /> 是否更新已经存在的用户数据 - </div> - <span>仅允许导入xls、xlsx格式文件。</span> - <el-link - type="primary" - :underline="false" - style="font-size: 12px; vertical-align: baseline" - @click="importTemplate" - >下载模板</el-link - > - </div> - </el-upload> - <div slot="footer" class="dialog-footer"> - <el-button type="primary" @click="submitFileForm">确 定</el-button> - <el-button @click="upload.open = false">取 消</el-button> - </div> - </el-dialog> - - <!-- 分配角色 --> - <el-dialog title="分配角色" :visible.sync="openRole" width="500px" append-to-body> - <el-form :model="form" label-width="80px"> - <el-form-item label="用户名称"> - <el-input v-model="form.username" :disabled="true" /> - </el-form-item> - <el-form-item label="用户昵称"> - <el-input v-model="form.nickname" :disabled="true" /> - </el-form-item> - <el-form-item label="角色"> - <el-select v-model="form.roleIds" multiple placeholder="请选择"> - <el-option - v-for="item in roleOptions" - :key="parseInt(item.id)" - :label="item.name" - :value="parseInt(item.id)" - ></el-option> - </el-select> - </el-form-item> - </el-form> - <div slot="footer" class="dialog-footer"> - <el-button type="primary" @click="submitRole">确 定</el-button> - <el-button @click="cancelRole">取 消</el-button> - </div> - </el-dialog> </div> </template> -<script> -import { - createUserApi as addUser, - updateUserStatusApi as changeUserStatus, - deleteUserApi as delUser, - exportUserApi as exportUser, - // TODO: praseStrEmpty(id) - getUserApi as getUser, - importUserTemplateApi as importTemplate, - getUserPageApi as listUser, - resetUserPwdApi as resetUserPwd, - updateUserApi as updateUser -} from '@/api/system/user' -// TODO: change -import Treeselect from '@riophae/vue-treeselect' -// TODO: change??? -import '@riophae/vue-treeselect/dist/vue-treeselect.css' - +<script setup lang="ts" name="User"> +import type { ElTree } from 'element-plus' +import { handleTree, defaultProps } from '@/utils/tree' import { listSimpleDeptApi } from '@/api/system/dept' -import { listSimplePostsApi } from '@/api/system/post' +import { listSimplePostsApi, PostVO } from '@/api/system/post' +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { UserVO } from '@/api/system/user' +import { + // createUserApi, + // updateUserStatusApi, + // deleteUserApi, + // exportUserApi, + // getUserApi, + // importUserTemplateApi, + getUserPageApi + // resetUserPwdApid, + // updateUserApi +} from '@/api/system/user' -import { CommonStatusEnum } from '@/utils/constants' -import { DICT_TYPE, getDictDatas } from '@/utils/dict' -import { assignUserRole, listUserRoles } from '@/api/system/permission' -import { listSimpleRoles } from '@/api/system/role' -import { getBaseHeader } from '@/utils/request' +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + username: undefined, + mobile: undefined, + status: undefined, + deptId: undefined, + createTime: [] +}) +const showSearch = ref(true) +// 数据字典 +const statusDictDatas = getDictOptions(DICT_TYPE.COMMON_STATUS) +// const sexDictDatas = getDictOptions(DICT_TYPE.SYSTEM_USER_SEX) -export default { - name: 'User', - components: { Treeselect }, - data() { - return { - // 遮罩层 - loading: true, - // 导出遮罩层 - exportLoading: false, - // 显示搜索条件 - showSearch: true, - // 总条数 - total: 0, - // 用户表格数据 - userList: null, - // 弹出层标题 - title: '', - // 部门树选项 - deptOptions: undefined, - // 是否显示弹出层 - open: false, - // 部门名称 - deptName: undefined, - // 默认密码 - initPassword: undefined, - // 性别状态字典 - sexOptions: [], - // 岗位选项 - postOptions: [], - // 角色选项 - roleOptions: [], - // 表单参数 - form: {}, - defaultProps: { - children: 'children', - label: 'name' - }, - // 用户导入参数 - upload: { - // 是否显示弹出层(用户导入) - open: false, - // 弹出层标题(用户导入) - title: '', - // 是否禁用上传 - isUploading: false, - // 是否更新已经存在的用户数据 - updateSupport: 0, - // 设置上传的请求头部 - headers: getBaseHeader(), - // 上传的地址 - url: process.env.VUE_APP_BASE_API + '/admin-api/system/user/import' - }, - // 查询参数 - queryParams: { - pageNo: 1, - pageSize: 10, - username: undefined, - mobile: undefined, - status: undefined, - deptId: undefined, - createTime: [] - }, - // 列信息 - columns: [ - { key: 0, label: `用户编号`, visible: true }, - { key: 1, label: `用户名称`, visible: true }, - { key: 2, label: `用户昵称`, visible: true }, - { key: 3, label: `部门`, visible: true }, - { key: 4, label: `手机号码`, visible: true }, - { key: 5, label: `状态`, visible: true }, - { key: 6, label: `创建时间`, visible: true } - ], - // 表单校验 - rules: { - username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }], - nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }], - password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }], - email: [ - { - type: 'email', - message: "'请输入正确的邮箱地址", - trigger: ['blur', 'change'] - } - ], - mobile: [ - { - pattern: - /^(?:(?:\+|00)86)?1(?:3[\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\d]|9[189])\d{8}$/, - message: '请输入正确的手机号码', - trigger: 'blur' - } - ] - }, - // 是否显示弹出层(角色权限) - openRole: false, +// ========== 创建部门树结构 ========== +const deptName = ref('') +const deptOptions = ref<Tree[]>([]) // 树形结构 +const treeRef = ref<InstanceType<typeof ElTree>>() +const getTree = async () => { + const res = await listSimpleDeptApi() + deptOptions.value.push(...handleTree(res)) +} +const filterNode = (value: string, data: Tree) => { + if (!value) return true + return data.name.includes(value) +} +const handleDeptNodeClick = async (row: { [key: string]: any }) => { + queryParams.deptId = row.id + // await reload() + getList() +} +// 获取岗位列表 +const postOptions = ref<PostVO[]>([]) //岗位列表 +const getPostOptions = async () => { + const res = await listSimplePostsApi() + postOptions.value.push(...res) +} +// 用户列表 +const userList = ref<UserVO[]>([]) +const loading = ref(false) +const total = ref(0) +const columns = ref([ + { key: 0, label: `用户编号`, visible: true }, + { key: 1, label: `用户名称`, visible: true }, + { key: 2, label: `用户昵称`, visible: true }, + { key: 3, label: `部门`, visible: true }, + { key: 4, label: `手机号码`, visible: true }, + { key: 5, label: `状态`, visible: true }, + { key: 6, label: `创建时间`, visible: true } +]) +const getList = () => { + loading.value = true + getUserPageApi(queryParams).then((response) => { + userList.value = response.list + total.value = response.total + loading.value = false + }) +} +const handleQuery = () => {} +const resetQuery = () => {} +const handleAdd = () => {} +const handleImport = () => {} +const exportLoading = ref(false) +const handleExport = () => {} +const handleStatusChange = () => {} +const handleUpdate = () => {} +const handleCommand = () => {} +// ========== 初始化 ========== +onMounted(async () => { + getList() + await getPostOptions() + await getTree() +}) - // 枚举 - SysCommonStatusEnum: CommonStatusEnum, - // 数据字典 - statusDictDatas: getDictDatas(DICT_TYPE.COMMON_STATUS), - sexDictDatas: getDictDatas(DICT_TYPE.SYSTEM_USER_SEX) - } - }, - watch: { - // 根据名称筛选部门树 - deptName(val) { - this.$refs.tree.filter(val) - } - }, - created() { - this.getList() - this.getTreeselect() - // this.getConfigKey("sys.user.init-password").then(response => { - // this.initPassword = response.msg; - // }); - }, - methods: { - // 更多操作 - handleCommand(command, index, row) { - switch (command) { - case 'handleUpdate': - this.handleUpdate(row) //修改客户信息 - break - case 'handleDelete': - this.handleDelete(row) //红号变更 - break - case 'handleResetPwd': - this.handleResetPwd(row) - break - case 'handleRole': - this.handleRole(row) - break - default: - break - } - }, - /** 查询用户列表 */ - getList() { - this.loading = true - listUser(this.queryParams).then((response) => { - this.userList = response.data.list - this.total = response.data.total - this.loading = false - }) - }, - /** 查询部门下拉树结构 + 岗位下拉 */ - getTreeselect() { - listSimpleDeptApi().then((response) => { - // 处理 deptOptions 参数 - this.deptOptions = [] - this.deptOptions.push(...this.handleTree(response.data, 'id')) - }) - listSimplePostsApi().then((response) => { - // 处理 postOptions 参数 - this.postOptions = [] - this.postOptions.push(...response.data) - }) - }, - // 筛选节点 - filterNode(value, data) { - if (!value) return true - return data.name.indexOf(value) !== -1 - }, - // 节点单击事件 - handleNodeClick(data) { - this.queryParams.deptId = data.id - this.getList() - }, - // 用户状态修改 - handleStatusChange(row) { - let text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' - this.$modal - .confirm('确认要"' + text + '""' + row.username + '"用户吗?') - .then(function () { - return changeUserStatus(row.id, row.status) - }) - .then(() => { - this.$modal.msgSuccess(text + '成功') - }) - .catch(function () { - row.status = - row.status === CommonStatusEnum.ENABLE - ? CommonStatusEnum.DISABLE - : CommonStatusEnum.ENABLE - }) - }, - // 取消按钮 - cancel() { - this.open = false - this.reset() - }, - // 取消按钮(角色权限) - cancelRole() { - this.openRole = false - this.reset() - }, - // 表单重置 - reset() { - this.form = { - id: undefined, - deptId: undefined, - username: undefined, - nickname: undefined, - password: undefined, - mobile: undefined, - email: undefined, - sex: undefined, - status: '0', - remark: undefined, - postIds: [], - roleIds: [] - } - this.resetForm('form') - }, - /** 搜索按钮操作 */ - handleQuery() { - this.queryParams.pageNo = 1 - this.getList() - }, - /** 重置按钮操作 */ - resetQuery() { - this.resetForm('queryForm') - this.handleQuery() - }, - /** 新增按钮操作 */ - handleAdd() { - this.reset() - // 获得下拉数据 - this.getTreeselect() - // 打开表单,并设置初始化 - this.open = true - this.title = '添加用户' - this.form.password = this.initPassword - }, - /** 修改按钮操作 */ - handleUpdate(row) { - this.reset() - this.getTreeselect() - const id = row.id - getUser(id).then((response) => { - this.form = response.data - this.open = true - this.title = '修改用户' - this.form.password = '' - }) - }, - /** 重置密码按钮操作 */ - handleResetPwd(row) { - this.$prompt('请输入"' + row.username + '"的新密码', '提示', { - confirmButtonText: '确定', - cancelButtonText: '取消' - }) - .then(({ value }) => { - resetUserPwd(row.id, value).then((response) => { - this.$modal.msgSuccess('修改成功,新密码是:' + value) - }) - }) - .catch(() => {}) - }, - /** 分配用户角色操作 */ - handleRole(row) { - this.reset() - const id = row.id - // 处理了 form 的用户 username 和 nickname 的展示 - this.form.id = id - this.form.username = row.username - this.form.nickname = row.nickname - // 打开弹窗 - this.openRole = true - // 获得角色列表 - listSimpleRoles().then((response) => { - // 处理 roleOptions 参数 - this.roleOptions = [] - this.roleOptions.push(...response.data) - }) - // 获得角色拥有的菜单集合 - listUserRoles(id).then((response) => { - // 设置选中 - this.form.roleIds = response.data - }) - }, - /** 提交按钮 */ - submitForm: function () { - this.$refs['form'].validate((valid) => { - if (valid) { - if (this.form.id !== undefined) { - updateUser(this.form).then((response) => { - this.$modal.msgSuccess('修改成功') - this.open = false - this.getList() - }) - } else { - addUser(this.form).then((response) => { - this.$modal.msgSuccess('新增成功') - this.open = false - this.getList() - }) - } - } - }) - }, - /** 提交按钮(角色权限) */ - submitRole: function () { - if (this.form.id !== undefined) { - assignUserRole({ - userId: this.form.id, - roleIds: this.form.roleIds - }).then((response) => { - this.$modal.msgSuccess('分配角色成功') - this.openRole = false - this.getList() - }) - } - }, - /** 删除按钮操作 */ - handleDelete(row) { - const ids = row.id || this.ids - this.$modal - .confirm('是否确认删除用户编号为"' + ids + '"的数据项?') - .then(function () { - return delUser(ids) - }) - .then(() => { - this.getList() - this.$modal.msgSuccess('删除成功') - }) - .catch(() => {}) - }, - /** 导出按钮操作 */ - handleExport() { - this.$modal - .confirm('是否确认导出所有用户数据项?') - .then(() => { - // 处理查询参数 - let params = { ...this.queryParams } - params.pageNo = undefined - params.pageSize = undefined - this.exportLoading = true - return exportUser(params) - }) - .then((response) => { - this.$download.excel(response, '用户数据.xls') - this.exportLoading = false - }) - .catch(() => {}) - }, - /** 导入按钮操作 */ - handleImport() { - this.upload.title = '用户导入' - this.upload.open = true - }, - /** 下载模板操作 */ - importTemplate() { - importTemplate().then((response) => { - this.$download.excel(response, '用户导入模板.xls') - }) - }, - // 文件上传中处理 - handleFileUploadProgress(event, file, fileList) { - this.upload.isUploading = true - }, - // 文件上传成功处理 - handleFileSuccess(response, file, fileList) { - if (response.code !== 0) { - this.$modal.msgError(response.msg) - return - } - this.upload.open = false - this.upload.isUploading = false - this.$refs.upload.clearFiles() - // 拼接提示语 - let data = response.data - let text = '创建成功数量:' + data.createUsernames.length - for (const username of data.createUsernames) { - text += '<br /> ' + username - } - text += '<br />更新成功数量:' + data.updateUsernames.length - for (const username of data.updateUsernames) { - text += '<br /> ' + username - } - text += '<br />更新失败数量:' + Object.keys(data.failureUsernames).length - for (const username in data.failureUsernames) { - text += '<br /> ' + username + ':' + data.failureUsernames[username] - } - this.$alert(text, '导入结果', { dangerouslyUseHTMLString: true }) - this.getList() - }, - // 提交上传文件 - submitFileForm() { - this.$refs.upload.submit() - }, - // 格式化部门的下拉框 - normalizer(node) { - return { - id: node.id, - label: node.name, - children: node.children - } - } +const parseTime = (time) => { + if (!time) { + return null } + const format = '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'object') { + date = time + } else { + if (typeof time === 'string' && /^[0-9]+$/.test(time)) { + time = parseInt(time) + } else if (typeof time === 'string') { + time = time + .replace(new RegExp(/-/gm), '/') + .replace('T', ' ') + .replace(new RegExp(/\.[\d]{3}/gm), '') + } + if (typeof time === 'number' && time.toString().length === 10) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { + let value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { + return ['日', '一', '二', '三', '四', '五', '六'][value] + } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return time_str } </script> From 42128709aa88dfb0578cb8a4e405201b04ae75f2 Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Wed, 22 Mar 2023 23:53:39 +0800 Subject: [PATCH 066/184] =?UTF-8?q?update:=20=E4=BF=AE=E6=94=B9=E4=BD=BF?= =?UTF-8?q?=E7=94=A8el-descriptions=E5=B1=95=E7=A4=BA=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/sms/smsLog/index.vue | 144 +++++++++++--------------- 1 file changed, 61 insertions(+), 83 deletions(-) diff --git a/src/views/system/sms/smsLog/index.vue b/src/views/system/sms/smsLog/index.vue index f3d17666..55b1a956 100644 --- a/src/views/system/sms/smsLog/index.vue +++ b/src/views/system/sms/smsLog/index.vue @@ -174,92 +174,70 @@ @pagination="getList" /> <!-- 短信日志详细 --> - <el-dialog title="短信日志详细" v-model="open" width="700px" append-to-body> - <el-form ref="formRef" :model="formData" label-width="140px"> - <el-row> - <el-col :span="24"> - <el-form-item label="日志主键:">{{ formData.id }}</el-form-item> - </el-col> - <el-col :span="24"> - <el-form-item label="短信渠道:"> - {{ formatChannelSignature(formData.channelId) }} - <dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="formData.channelCode" /> - </el-form-item> - </el-col> - <el-col :span="24"> - <el-form-item label="短信模板:"> - {{ formData.templateId }} | {{ formData.templateCode }} - <dict-tag :type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE" :value="formData.templateType" /> - </el-form-item> - </el-col> - <el-col :span="24"> - <el-form-item label="API 的模板编号:">{{ formData.apiTemplateId }}</el-form-item> - </el-col> - <el-col :span="24"> - <el-form-item label="用户信息:" - >{{ formData.mobile }} - <span v-if="formData.userType && formData.userId"> - <dict-tag :type="DICT_TYPE.USER_TYPE" :value="formData.userType" />({{ - formData.userId - }}) - </span> - </el-form-item> - </el-col> - <el-col :span="24"> - <el-form-item label="短信内容:">{{ formData.templateContent }}</el-form-item> - </el-col> - <el-col :span="24"> - <el-form-item label="短信参数:">{{ formData.templateParams }}</el-form-item> - </el-col> - <el-col :span="24"> - <el-form-item label="创建时间:">{{ parseTime(formData.createTime) }}</el-form-item> - </el-col> - <el-col :span="24"> - <el-form-item label="发送状态:"> - <dict-tag :type="DICT_TYPE.SYSTEM_SMS_SEND_STATUS" :value="formData.sendStatus" /> - </el-form-item> - </el-col> - <el-col :span="24"> - <el-form-item label="发送时间:">{{ parseTime(formData.sendTime) }}</el-form-item> - </el-col> - <el-col :span="24"> - <el-form-item label="发送结果:" - >{{ formData.sendCode }} | {{ formData.sendMsg }} - </el-form-item> - </el-col> - <el-col :span="24"> - <el-form-item label="API 发送结果:" - >{{ formData.apiSendCode }} | {{ formData.apiSendMsg }}</el-form-item - > - </el-col> - <el-col :span="24"> - <el-form-item label="API 短信编号:">{{ formData.apiSerialNo }}</el-form-item> - </el-col> - <el-col :span="24"> - <el-form-item label="API 请求编号:">{{ formData.apiRequestId }}</el-form-item> - </el-col> - <el-col :span="24"> - <el-form-item label="接收状态:"> - <dict-tag - :type="DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS" - :value="formData.receiveStatus" - /> - </el-form-item> - </el-col> - <el-col :span="24"> - <el-form-item label="接收时间:">{{ parseTime(formData.receiveTime) }}</el-form-item> - </el-col> - <el-col :span="24"> - <el-form-item label="API 接收结果:" - >{{ formData.apiReceiveCode }} | {{ formData.apiReceiveMsg }} - </el-form-item> - </el-col> - </el-row> - </el-form> + <Dialog title="短信日志详情" v-model="open"> + <el-descriptions border :column="1"> + <el-descriptions-item label-align="right" width="50px" label="日志主键:">{{ + formData.id + }}</el-descriptions-item> + <el-descriptions-item label-align="right" width="50px" label="短信渠道:"> + {{ formatChannelSignature(formData.channelId) }} + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="formData.channelCode" /> + </el-descriptions-item> + <el-descriptions-item label-align="right" width="50px" label="短信模板:"> + {{ formData.templateId }} | {{ formData.templateCode }} + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE" :value="formData.templateType" /> + </el-descriptions-item> + <el-descriptions-item label-align="right" width="50px" label="API 的模板编号:"> + {{ formData.apiTemplateId }} + </el-descriptions-item> + <el-descriptions-item label-align="right" width="50px" label="用户信息:"> + {{ formData.mobile }} + <span v-if="formData.userType && formData.userId"> + <dict-tag :type="DICT_TYPE.USER_TYPE" :value="formData.userType" /> + ({{ formData.userId }}) + </span> + </el-descriptions-item> + <el-descriptions-item label-align="right" width="50px" label="短信内容:"> + {{ formData.templateContent }} + </el-descriptions-item> + <el-descriptions-item label-align="right" width="50px" label="短信参数:"> + {{ formData.templateParams }} + </el-descriptions-item> + <el-descriptions-item label-align="right" width="50px" label="创建时间:"> + {{ parseTime(formData.createTime) }} + </el-descriptions-item> + <el-descriptions-item label-align="right" width="50px" label="发送状态:"> + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_SEND_STATUS" :value="formData.sendStatus" /> + </el-descriptions-item> + <el-descriptions-item label-align="right" width="50px" label="发送时间:"> + {{ parseTime(formData.sendTime) }} + </el-descriptions-item> + <el-descriptions-item label-align="right" width="50px" label="发送结果:"> + {{ formData.sendCode }} | {{ formData.sendMsg }} + </el-descriptions-item> + <el-descriptions-item label-align="right" width="50px" label="API 发送结果:"> + {{ formData.apiSendCode }} | {{ formData.apiSendMsg }} + </el-descriptions-item> + <el-descriptions-item label-align="right" width="50px" label="API 短信编号:"> + {{ formData.apiSerialNo }} + </el-descriptions-item> + <el-descriptions-item label-align="right" width="50px" label="API 请求编号:"> + {{ formData.apiRequestId }} + </el-descriptions-item> + <el-descriptions-item label-align="right" width="50px" label="接收状态:"> + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS" :value="formData.receiveStatus" /> + </el-descriptions-item> + <el-descriptions-item label-align="right" width="50px" label="接收时间:"> + {{ parseTime(formData.receiveTime) }} + </el-descriptions-item> + <el-descriptions-item label-align="right" width="50px" label="API 接收结果:"> + {{ formData.apiReceiveCode }} | {{ formData.apiReceiveMsg }} + </el-descriptions-item> + </el-descriptions> <template #footer> <el-button @click="open = false">关 闭</el-button> </template> - </el-dialog> + </Dialog> </content-wrap> </template> <script setup lang="ts" name="smsLog"> From d44ceda588d614ed82e4ee9466291e8e4cf18e33 Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Thu, 23 Mar 2023 00:08:14 +0800 Subject: [PATCH 067/184] =?UTF-8?q?update:=20=E4=BF=AE=E6=94=B9=E4=BA=86?= =?UTF-8?q?=E4=B8=80=E4=BA=9BTODO=E6=8F=90=E5=88=B0=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/sensitiveWord/index.ts | 16 ++++++++-------- src/views/system/sensitiveWord/form.vue | 8 ++++---- src/views/system/sensitiveWord/index.vue | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/api/system/sensitiveWord/index.ts b/src/api/system/sensitiveWord/index.ts index ffda89c0..7da2c28e 100644 --- a/src/api/system/sensitiveWord/index.ts +++ b/src/api/system/sensitiveWord/index.ts @@ -24,41 +24,41 @@ export interface SensitiveWordExportReqVO { } // 查询敏感词列表 -export const getSensitiveWordPageApi = (params: SensitiveWordPageReqVO) => { +export const getSensitiveWordPage = (params: SensitiveWordPageReqVO) => { return request.get({ url: '/system/sensitive-word/page', params }) } // 查询敏感词详情 -export const getSensitiveWordApi = (id: number) => { +export const getSensitiveWord = (id: number) => { return request.get({ url: '/system/sensitive-word/get?id=' + id }) } // 新增敏感词 -export const createSensitiveWordApi = (data: SensitiveWordVO) => { +export const createSensitiveWord = (data: SensitiveWordVO) => { return request.post({ url: '/system/sensitive-word/create', data }) } // 修改敏感词 -export const updateSensitiveWordApi = (data: SensitiveWordVO) => { +export const updateSensitiveWord = (data: SensitiveWordVO) => { return request.put({ url: '/system/sensitive-word/update', data }) } // 删除敏感词 -export const deleteSensitiveWordApi = (id: number) => { +export const deleteSensitiveWord = (id: number) => { return request.delete({ url: '/system/sensitive-word/delete?id=' + id }) } // 导出敏感词 -export const exportSensitiveWordApi = (params: SensitiveWordExportReqVO) => { +export const exportSensitiveWord = (params: SensitiveWordExportReqVO) => { return request.download({ url: '/system/sensitive-word/export-excel', params }) } // 获取所有敏感词的标签数组 -export const getSensitiveWordTagsApi = () => { +export const getSensitiveWordTags = () => { return request.get({ url: '/system/sensitive-word/get-tags' }) } // 获得文本所包含的不合法的敏感词数组 -export const validateTextApi = (id: number) => { +export const validateText = (id: number) => { return request.get({ url: '/system/sensitive-word/validate-text?' + id }) } diff --git a/src/views/system/sensitiveWord/form.vue b/src/views/system/sensitiveWord/form.vue index 1dccd656..24bcdaaa 100644 --- a/src/views/system/sensitiveWord/form.vue +++ b/src/views/system/sensitiveWord/form.vue @@ -45,7 +45,7 @@ </template> </Dialog> </template> -<script setup lang="ts"> +<script setup lang="ts" name="SensitiveWordForm"> import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import * as SensitiveWordApi from '@/api/system/sensitiveWord' import { CommonStatusEnum } from '@/utils/constants' @@ -81,7 +81,7 @@ const openModal = async (type: string, id?: number) => { if (id) { formLoading.value = true try { - formData.value = await SensitiveWordApi.getSensitiveWordApi(id) + formData.value = await SensitiveWordApi.getSensitiveWord(id) console.log(formData.value) } finally { formLoading.value = false @@ -102,10 +102,10 @@ const submitForm = async () => { try { const data = formData.value as unknown as SensitiveWordApi.SensitiveWordVO if (formType.value === 'create') { - await SensitiveWordApi.createSensitiveWordApi(data) // TODO @blue-syd:去掉 API 后缀 + await SensitiveWordApi.createSensitiveWord(data) // TODO @blue-syd:去掉 API 后缀 message.success(t('common.createSuccess')) } else { - await SensitiveWordApi.updateSensitiveWordApi(data) // TODO @blue-syd:去掉 API 后缀 + await SensitiveWordApi.updateSensitiveWord(data) // TODO @blue-syd:去掉 API 后缀 message.success(t('common.updateSuccess')) } modelVisible.value = false diff --git a/src/views/system/sensitiveWord/index.vue b/src/views/system/sensitiveWord/index.vue index 93ea29f1..17da6ca3 100644 --- a/src/views/system/sensitiveWord/index.vue +++ b/src/views/system/sensitiveWord/index.vue @@ -126,14 +126,14 @@ </content-wrap> <!-- 表单弹窗:添加/修改 --> - <config-form ref="modalRef" @success="getList" /> + <SensitiveWordForm ref="modalRef" @success="getList" /> </template> <script setup lang="ts" name="SensitiveWord"> import { DICT_TYPE, getDictOptions } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' import * as SensitiveWordApi from '@/api/system/sensitiveWord' -import ConfigForm from './form.vue' // TODO @blue-syd:组件名不对 +import SensitiveWordForm from './form.vue' // TODO @blue-syd:组件名不对 const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -156,7 +156,7 @@ const tags = ref([]) const getList = async () => { loading.value = true try { - const data = await SensitiveWordApi.getSensitiveWordPageApi(queryParams) // TODO @blue-syd:去掉 API 后缀哈 + const data = await SensitiveWordApi.getSensitiveWordPage(queryParams) // TODO @blue-syd:去掉 API 后缀哈 list.value = data.list total.value = data.total } finally { @@ -190,7 +190,7 @@ const handleDelete = async (id: number) => { // 删除的二次确认 await message.delConfirm() // 发起删除 - await SensitiveWordApi.deleteSensitiveWordApi(id) + await SensitiveWordApi.deleteSensitiveWord(id) message.success(t('common.delSuccess')) // 刷新列表 await getList() @@ -204,7 +204,7 @@ const handleExport = async () => { await message.exportConfirm() // 发起导出 exportLoading.value = true - const data = await SensitiveWordApi.exportSensitiveWordApi(queryParams) // TODO @blue-syd:去掉 API 后缀哈 + const data = await SensitiveWordApi.exportSensitiveWord(queryParams) // TODO @blue-syd:去掉 API 后缀哈 download.excel(data, '敏感词.xls') } catch { } finally { @@ -214,7 +214,7 @@ const handleExport = async () => { /** 获得 Tag 标签列表 */ const getTags = async () => { - tags.value = await SensitiveWordApi.getSensitiveWordTagsApi() // TODO @blue-syd:去掉 API 后缀哈 + tags.value = await SensitiveWordApi.getSensitiveWordTags() // TODO @blue-syd:去掉 API 后缀哈 } /** 初始化 **/ From 5fedc6674b3694dbac6f4ca6e60007a51fda53ee Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Thu, 23 Mar 2023 00:33:40 +0800 Subject: [PATCH 068/184] =?UTF-8?q?update:=20=E4=BF=AE=E6=94=B9=E4=BA=86?= =?UTF-8?q?=E4=B8=80=E4=BA=9BTODO=E6=8F=90=E5=88=B0=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98=EF=BC=8C=E7=AE=80=E5=8C=96=E6=97=B6=E9=97=B4=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=8C=96=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layout/components/Message/src/Message.vue | 4 ++-- src/views/Profile/components/ProfileUser.vue | 4 ++-- src/views/bpm/processInstance/detail.vue | 6 +++--- src/views/infra/job/JobLog.vue | 8 ++------ src/views/infra/webSocket/index.vue | 4 ++-- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/layout/components/Message/src/Message.vue b/src/layout/components/Message/src/Message.vue index 94347812..4ac85860 100644 --- a/src/layout/components/Message/src/Message.vue +++ b/src/layout/components/Message/src/Message.vue @@ -1,5 +1,5 @@ <script setup lang="ts"> -import dayjs from 'dayjs' +import { parseTime } from '@/utils/formatTime' import * as NotifyMessageApi from '@/api/system/notify/message' const { push } = useRouter() @@ -57,7 +57,7 @@ onMounted(() => { {{ item.templateNickname }}:{{ item.templateContent }} </span> <span class="message-date"> - {{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }} + {{ parseTime(item.createTime) }} </span> </div> </div> diff --git a/src/views/Profile/components/ProfileUser.vue b/src/views/Profile/components/ProfileUser.vue index 2f5a77b2..015440fc 100644 --- a/src/views/Profile/components/ProfileUser.vue +++ b/src/views/Profile/components/ProfileUser.vue @@ -34,13 +34,13 @@ </li> <li class="list-group-item"> <Icon icon="ep:calendar" class="mr-5px" />{{ t('profile.user.createTime') }} - <div class="pull-right">{{ dayjs(userInfo?.createTime).format('YYYY-MM-DD') }}</div> + <div class="pull-right">{{ parseTime(userInfo?.createTime) }}</div> </li> </ul> </div> </template> <script setup lang="ts"> -import dayjs from 'dayjs' +import { parseTime } from '@/utils/formatTime' import UserAvatar from './UserAvatar.vue' import { getUserProfileApi, ProfileVO } from '@/api/system/user/profile' diff --git a/src/views/bpm/processInstance/detail.vue b/src/views/bpm/processInstance/detail.vue index 7d159673..91fbfe19 100644 --- a/src/views/bpm/processInstance/detail.vue +++ b/src/views/bpm/processInstance/detail.vue @@ -112,13 +112,13 @@ </label> <label style="font-weight: normal" v-if="item.createTime">创建时间:</label> <label style="color: #8a909c; font-weight: normal"> - {{ dayjs(item?.createTime).format('YYYY-MM-DD HH:mm:ss') }} + {{ parseTime(item?.createTime) }} </label> <label v-if="item.endTime" style="margin-left: 30px; font-weight: normal"> 审批时间: </label> <label v-if="item.endTime" style="color: #8a909c; font-weight: normal"> - {{ dayjs(item?.endTime).format('YYYY-MM-DD HH:mm:ss') }} + {{ parseTime(item?.endTime) }} </label> <label v-if="item.durationInMillis" style="margin-left: 30px; font-weight: normal"> 耗时: @@ -192,7 +192,7 @@ </ContentWrap> </template> <script setup lang="ts"> -import dayjs from 'dayjs' +import { parseTime } from '@/utils/formatTime' import * as UserApi from '@/api/system/user' import * as ProcessInstanceApi from '@/api/bpm/processInstance' import * as DefinitionApi from '@/api/bpm/definition' diff --git a/src/views/infra/job/JobLog.vue b/src/views/infra/job/JobLog.vue index 1bf9d745..ba397d51 100644 --- a/src/views/infra/job/JobLog.vue +++ b/src/views/infra/job/JobLog.vue @@ -12,11 +12,7 @@ /> </template> <template #beginTime_default="{ row }"> - <span>{{ - dayjs(row.beginTime).format('YYYY-MM-DD HH:mm:ss') + - ' ~ ' + - dayjs(row.endTime).format('YYYY-MM-DD HH:mm:ss') - }}</span> + <span>{{ parseTime(row.beginTime) + ' ~ ' + parseTime(row.endTime) }}</span> </template> <template #duration_default="{ row }"> <span>{{ row.duration + ' 毫秒' }}</span> @@ -48,7 +44,7 @@ </XModal> </template> <script setup lang="ts" name="JobLog"> -import dayjs from 'dayjs' +import { parseTime } from '@/utils/formatTime' import * as JobLogApi from '@/api/infra/jobLog' import { allSchemas } from './jobLog.data' diff --git a/src/views/infra/webSocket/index.vue b/src/views/infra/webSocket/index.vue index 655045c0..f090ba9b 100644 --- a/src/views/infra/webSocket/index.vue +++ b/src/views/infra/webSocket/index.vue @@ -44,7 +44,7 @@ <li v-for="item in getList" class="mt-2" :key="item.time"> <div class="flex items-center"> <span class="mr-2 text-primary font-medium">收到消息:</span> - <span>{{ dayjs(item.time).format('YYYY-MM-DD HH:mm:ss') }}</span> + <span>{{ parseTime(item.time) }}</span> </div> <div> {{ item.res }} @@ -56,7 +56,7 @@ </div> </template> <script setup lang="ts"> -import dayjs from 'dayjs' +import { parseTime } from '@/utils/formatTime' import { useUserStore } from '@/store/modules/user' import { useWebSocket } from '@vueuse/core' From 993a96757f51d55b336c76b15ada9563fb656b02 Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Thu, 23 Mar 2023 00:34:55 +0800 Subject: [PATCH 069/184] =?UTF-8?q?update:=20!!!=E8=AF=B7=E4=BF=9D?= =?UTF-8?q?=E7=95=99=E8=BF=99=E4=B8=AA=E5=B1=9E=E6=80=A7=EF=BC=8C=E9=98=B2?= =?UTF-8?q?=E6=AD=A2tsconfig.json=E6=96=87=E4=BB=B6=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 6b412cd8..b97c7079 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -32,6 +32,7 @@ "vite-plugin-svg-icons/client", "@form-create/element-ui/types" ], + "outDir": "target", // 请保留这个属性,防止tsconfig.json文件报错 "typeRoots": ["./node_modules/@types/", "./types"] }, "include": [ @@ -40,5 +41,5 @@ "src/types/auto-imports.d.ts", "src/types/auto-components.d.ts" ], - "exclude": ["dist", "node_modules"] + "exclude": ["dist", "target", "node_modules"] } From 94a6305fce1b428e035679d27c6453e9db53b42b Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Thu, 23 Mar 2023 00:36:25 +0800 Subject: [PATCH 070/184] update --- .gitignore | 3 ++- src/types/auto-components.d.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ba12c396..2500fdef 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ dist-ssr *.local /dist* *-lock.* -pnpm-debug \ No newline at end of file +pnpm-debug +.idea diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index c2a7f830..5c679fa9 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -21,6 +21,7 @@ declare module '@vue/runtime-core' { DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default'] Echart: typeof import('./../components/Echart/src/Echart.vue')['default'] Editor: typeof import('./../components/Editor/src/Editor.vue')['default'] + ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElBadge: typeof import('element-plus/es')['ElBadge'] ElButton: typeof import('element-plus/es')['ElButton'] ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] @@ -65,6 +66,7 @@ declare module '@vue/runtime-core' { ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElSelect: typeof import('element-plus/es')['ElSelect'] ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] + ElSpace: typeof import('element-plus/es')['ElSpace'] ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElTable: typeof import('element-plus/es')['ElTable'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] From c735cd72ffa1aca48879f9ca3137eddb75d5935f Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Thu, 23 Mar 2023 00:39:20 +0800 Subject: [PATCH 071/184] update --- src/views/system/sms/smsLog/index.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/views/system/sms/smsLog/index.vue b/src/views/system/sms/smsLog/index.vue index 55b1a956..f4a0ca9f 100644 --- a/src/views/system/sms/smsLog/index.vue +++ b/src/views/system/sms/smsLog/index.vue @@ -176,9 +176,9 @@ <!-- 短信日志详细 --> <Dialog title="短信日志详情" v-model="open"> <el-descriptions border :column="1"> - <el-descriptions-item label-align="right" width="50px" label="日志主键:">{{ - formData.id - }}</el-descriptions-item> + <el-descriptions-item label-align="right" width="50px" label="日志主键:"> + {{ formData.id }} + </el-descriptions-item> <el-descriptions-item label-align="right" width="50px" label="短信渠道:"> {{ formatChannelSignature(formData.channelId) }} <dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="formData.channelCode" /> From b56a5e06c9d8869a4b2c3ee930d5bac81dcb4966 Mon Sep 17 00:00:00 2001 From: xingyuv <xingyu4j@vip.qq.com> Date: Thu, 23 Mar 2023 09:23:48 +0800 Subject: [PATCH 072/184] =?UTF-8?q?fix:Descriptions=E5=8E=BB=E6=8E=89mb-10?= =?UTF-8?q?px?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Descriptions/src/Descriptions.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Descriptions/src/Descriptions.vue b/src/components/Descriptions/src/Descriptions.vue index fca37000..f1e77ddf 100644 --- a/src/components/Descriptions/src/Descriptions.vue +++ b/src/components/Descriptions/src/Descriptions.vue @@ -76,7 +76,7 @@ const toggleClick = () => { v-if="title" :class="[ `${prefixCls}-header`, - 'h-50px flex justify-between items-center mb-10px border-bottom-1 border-solid border-[var(--tags-view-border-color)] px-10px cursor-pointer dark:border-[var(--el-border-color)]' + 'h-50px flex justify-between items-center border-bottom-1 border-solid border-[var(--tags-view-border-color)] px-10px cursor-pointer dark:border-[var(--el-border-color)]' ]" @click="toggleClick" > From 55dafabdf36efeea194cd72665507e2195a5e1d0 Mon Sep 17 00:00:00 2001 From: xingyuv <xingyu4j@vip.qq.com> Date: Thu, 23 Mar 2023 09:24:52 +0800 Subject: [PATCH 073/184] =?UTF-8?q?fix:=20=E9=9D=A2=E5=8C=85=E5=B1=91:1.?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BD=BF=E7=94=A8=E5=8A=A8=E6=80=81=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E7=9A=84=E6=97=B6=E5=80=99=EF=BC=8C=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layout/components/Breadcrumb/src/Breadcrumb.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/layout/components/Breadcrumb/src/Breadcrumb.vue b/src/layout/components/Breadcrumb/src/Breadcrumb.vue index 19be7400..de036654 100644 --- a/src/layout/components/Breadcrumb/src/Breadcrumb.vue +++ b/src/layout/components/Breadcrumb/src/Breadcrumb.vue @@ -37,7 +37,7 @@ export default defineComponent({ }) const getBreadcrumb = () => { - const currentPath = currentRoute.value.path + const currentPath = currentRoute.value.matched.slice(-1)[0].path levelList.value = filter<AppRouteRecordRaw>(unref(menuRouters), (node: AppRouteRecordRaw) => { return node.path === currentPath @@ -47,7 +47,7 @@ export default defineComponent({ const renderBreadcrumb = () => { const breadcrumbList = treeToList<AppRouteRecordRaw[]>(unref(levelList)) return breadcrumbList.map((v) => { - const disabled = v.redirect === 'noredirect' + const disabled = !v.redirect || v.redirect === 'noredirect' const meta = v.meta as RouteMeta return ( <ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}> From 9edd3c5f805abbd16ec3c45dfc891e32dfdcf1a7 Mon Sep 17 00:00:00 2001 From: xingyuv <xingyu4j@vip.qq.com> Date: Thu, 23 Mar 2023 09:25:43 +0800 Subject: [PATCH 074/184] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=20Editor=20?= =?UTF-8?q?=E7=9A=84=20z-index=20=E4=BD=BF=E5=85=B6=E4=B8=8D=E4=BC=9A?= =?UTF-8?q?=E9=81=AE=E6=8C=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Editor/src/Editor.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Editor/src/Editor.vue b/src/components/Editor/src/Editor.vue index 85b849fb..4d8e7dde 100644 --- a/src/components/Editor/src/Editor.vue +++ b/src/components/Editor/src/Editor.vue @@ -178,7 +178,7 @@ defineExpose({ </script> <template> - <div class="border-1 border-solid border-[var(--tags-view-border-color)] z-3000"> + <div class="border-1 border-solid border-[var(--tags-view-border-color)] z-99"> <!-- 工具栏 --> <Toolbar :editor="editorRef" From 8bd47446cf9c0e88ef757889e639d62d8d7c74cb Mon Sep 17 00:00:00 2001 From: xingyuv <xingyu4j@vip.qq.com> Date: Thu, 23 Mar 2023 09:40:32 +0800 Subject: [PATCH 075/184] fix: api error --- src/views/infra/codegen/components/ImportTable.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/views/infra/codegen/components/ImportTable.vue b/src/views/infra/codegen/components/ImportTable.vue index 38a81541..9dc843cf 100644 --- a/src/views/infra/codegen/components/ImportTable.vue +++ b/src/views/infra/codegen/components/ImportTable.vue @@ -52,7 +52,7 @@ import { VxeTableInstance } from 'vxe-table' import type { DatabaseTableVO } from '@/api/infra/codegen/types' import { getSchemaTableListApi, createCodegenListApi } from '@/api/infra/codegen' -import { getDataSourceConfigListApi, DataSourceConfigVO } from '@/api/infra/dataSourceConfig' +import { getDataSourceConfigList, DataSourceConfigVO } from '@/api/infra/dataSourceConfig' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -67,9 +67,9 @@ const queryParams = reactive({ }) const dataSourceConfigs = ref<DataSourceConfigVO[]>([]) const show = async () => { - const res = await getDataSourceConfigListApi() + const res = await getDataSourceConfigList() dataSourceConfigs.value = res - queryParams.dataSourceConfigId = dataSourceConfigs.value[0].id + queryParams.dataSourceConfigId = dataSourceConfigs.value[0].id as number visible.value = true await getList() } From 69acb5bb339c057638549ccb2adc4194e89bbd93 Mon Sep 17 00:00:00 2001 From: xingyuv <xingyu4j@vip.qq.com> Date: Thu, 23 Mar 2023 09:41:16 +0800 Subject: [PATCH 076/184] chore: update deps --- package.json | 60 ++++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index d067560f..a33d0dc7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yudao-ui-admin-vue3", - "version": "1.7.1-snapshot.1941", + "version": "1.7.1-snapshot.1961", "description": "基于vue3、vite4、element-plus、typesScript", "author": "xingyu", "private": false, @@ -43,7 +43,7 @@ "diagram-js": "^11.6.0", "echarts": "^5.4.1", "echarts-wordcloud": "^2.1.0", - "element-plus": "2.2.34", + "element-plus": "2.3.1", "fast-xml-parser": "^4.1.3", "highlight.js": "^11.7.0", "intro.js": "^6.0.0", @@ -62,57 +62,57 @@ "vue-router": "^4.1.6", "vue-types": "^5.0.2", "vuedraggable": "^4.1.0", - "vxe-table": "^4.3.10", + "vxe-table": "^4.3.11", "web-storage-cache": "^1.1.1", "xe-utils": "^3.5.7", "xml-js": "^1.6.11" }, "devDependencies": { - "@commitlint/cli": "^17.4.4", + "@commitlint/cli": "^17.5.0", "@commitlint/config-conventional": "^17.4.4", - "@iconify/json": "^2.2.31", - "@intlify/unplugin-vue-i18n": "^0.8.2", + "@iconify/json": "^2.2.38", + "@intlify/unplugin-vue-i18n": "^0.10.0", "@purge-icons/generated": "^0.9.0", "@types/intro.js": "^5.1.1", - "@types/lodash-es": "^4.17.6", - "@types/node": "^18.14.6", + "@types/lodash-es": "^4.17.7", + "@types/node": "^18.15.5", "@types/nprogress": "^0.2.0", "@types/qrcode": "^1.5.0", "@types/qs": "^6.9.7", - "@typescript-eslint/eslint-plugin": "^5.54.1", - "@typescript-eslint/parser": "^5.54.1", - "@vitejs/plugin-legacy": "^4.0.1", - "@vitejs/plugin-vue": "^4.0.0", - "@vitejs/plugin-vue-jsx": "^3.0.0", - "autoprefixer": "^10.4.13", + "@typescript-eslint/eslint-plugin": "^5.56.0", + "@typescript-eslint/parser": "^5.56.0", + "@vitejs/plugin-legacy": "^4.0.2", + "@vitejs/plugin-vue": "^4.1.0", + "@vitejs/plugin-vue-jsx": "^3.0.1", + "autoprefixer": "^10.4.14", "bpmn-js": "^8.9.0", "bpmn-js-properties-panel": "^0.46.0", "consola": "^2.15.3", - "eslint": "^8.35.0", - "eslint-config-prettier": "^8.7.0", - "eslint-define-config": "^1.15.0", + "eslint": "^8.36.0", + "eslint-config-prettier": "^8.8.0", + "eslint-define-config": "^1.17.0", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-vue": "^9.9.0", - "lint-staged": "^13.1.2", + "lint-staged": "^13.2.0", "postcss": "^8.4.21", "postcss-html": "^1.5.0", "postcss-scss": "^4.0.6", - "prettier": "^2.8.4", - "rimraf": "^4.3.1", - "rollup": "^3.18.0", - "sass": "^1.58.3", - "stylelint": "^15.2.0", + "prettier": "^2.8.6", + "rimraf": "^4.4.1", + "rollup": "^3.20.0", + "sass": "^1.59.3", + "stylelint": "^15.3.0", "stylelint-config-html": "^1.1.0", "stylelint-config-prettier": "^9.0.5", - "stylelint-config-recommended": "^10.0.1", - "stylelint-config-standard": "^30.0.1", - "stylelint-order": "^6.0.2", - "terser": "^5.16.5", - "typescript": "4.9.5", + "stylelint-config-recommended": "^11.0.0", + "stylelint-config-standard": "^31.0.0", + "stylelint-order": "^6.0.3", + "terser": "^5.16.6", + "typescript": "5.0.2", "unplugin-auto-import": "^0.15.1", "unplugin-element-plus": "^0.7.0", "unplugin-vue-components": "^0.24.1", - "vite": "4.1.4", + "vite": "4.2.1", "vite-plugin-compression": "^0.5.1", "vite-plugin-ejs": "^1.6.4", "vite-plugin-eslint": "^1.8.1", @@ -125,7 +125,7 @@ "windicss": "^3.5.6" }, "engines": { - "node": ">=16.0.0" + "node": ">=16.18.0" }, "license": "MIT", "repository": { From e6ccfbddb63cb4989f32ac627e6bed13485df3a9 Mon Sep 17 00:00:00 2001 From: xingyuv <xingyu4j@vip.qq.com> Date: Thu, 23 Mar 2023 09:42:01 +0800 Subject: [PATCH 077/184] chore: update vite 4.2 --- build/vite/index.ts | 6 ++---- index.html | 4 ++-- vite.config.ts | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/build/vite/index.ts b/build/vite/index.ts index 53f66ec0..dcec1aed 100644 --- a/build/vite/index.ts +++ b/build/vite/index.ts @@ -15,7 +15,7 @@ import vueSetupExtend from 'vite-plugin-vue-setup-extend' import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite' import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' -export function createVitePlugins(VITE_APP_TITLE: string) { +export function createVitePlugins() { const root = process.cwd() // 路径查找 function pathResolve(dir: string) { @@ -95,8 +95,6 @@ export function createVitePlugins(VITE_APP_TITLE: string) { ext: '.gz', // 生成的压缩包后缀 deleteOriginFile: false //压缩后是否删除源文件 }), - ViteEjsPlugin({ - title: VITE_APP_TITLE - }) + ViteEjsPlugin() ] } diff --git a/index.html b/index.html index cce65bdc..8cfcbefa 100644 --- a/index.html +++ b/index.html @@ -13,7 +13,7 @@ name="description" content="芋道管理系统 基于 vue3 + CompositionAPI + typescript + vite3 + element plus 的后台开源免费管理系统!" /> - <title><%= title %></title> + <title>%VITE_APP_TITLE%</title> </head> <body> <div id="app"> @@ -137,7 +137,7 @@ <div class="app-loading-wrap"> <div class="app-loading-title"> <img src="/logo.gif" class="app-loading-logo" alt="Logo" /> - <div class="app-loading-title"><%= title %></div> + <div class="app-loading-title">%VITE_APP_TITLE%</div> </div> <div class="app-loading-item"> <div class="app-loading-outter"></div> diff --git a/vite.config.ts b/vite.config.ts index 6b54e183..fe2d7131 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -42,7 +42,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => { // }, }, // 项目使用的vite插件。 单独提取到build/vite/plugin中管理 - plugins: createVitePlugins(env.VITE_APP_TITLE), + plugins: createVitePlugins(), css: { preprocessorOptions: { scss: { From 7c92735aff994ce44b5862d76a1f8b2af62e0d3e Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Thu, 23 Mar 2023 09:42:53 +0800 Subject: [PATCH 078/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E7=A7=9F=E6=88=B7=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/tenant/index.ts | 12 +- src/views/system/tenant/form.vue | 166 +++++++++------------- src/views/system/tenant/index.vue | 76 +++++----- src/views/system/tenant/tenant.data.ts | 186 ------------------------- 4 files changed, 114 insertions(+), 326 deletions(-) delete mode 100644 src/views/system/tenant/tenant.data.ts diff --git a/src/api/system/tenant/index.ts b/src/api/system/tenant/index.ts index d79fb7b2..176c3757 100644 --- a/src/api/system/tenant/index.ts +++ b/src/api/system/tenant/index.ts @@ -32,31 +32,31 @@ export interface TenantExportReqVO { } // 查询租户列表 -export const getTenantPageApi = (params: TenantPageReqVO) => { +export const getTenantPage = (params: TenantPageReqVO) => { return request.get({ url: '/system/tenant/page', params }) } // 查询租户详情 -export const getTenantApi = (id: number) => { +export const getTenant = (id: number) => { return request.get({ url: '/system/tenant/get?id=' + id }) } // 新增租户 -export const createTenantApi = (data: TenantVO) => { +export const createTenant = (data: TenantVO) => { return request.post({ url: '/system/tenant/create', data }) } // 修改租户 -export const updateTenantApi = (data: TenantVO) => { +export const updateTenant = (data: TenantVO) => { return request.put({ url: '/system/tenant/update', data }) } // 删除租户 -export const deleteTenantApi = (id: number) => { +export const deleteTenant = (id: number) => { return request.delete({ url: '/system/tenant/delete?id=' + id }) } // 导出租户 -export const exportTenantApi = (params: TenantExportReqVO) => { +export const exportTenant = (params: TenantExportReqVO) => { return request.download({ url: '/system/tenant/export-excel', params }) } diff --git a/src/views/system/tenant/form.vue b/src/views/system/tenant/form.vue index c50f6ffc..368327d0 100644 --- a/src/views/system/tenant/form.vue +++ b/src/views/system/tenant/form.vue @@ -7,96 +7,67 @@ label-width="80px" v-loading="formLoading" > - <el-row> - <el-col :span="10"> - <el-form-item label="租户名" prop="name"> - <el-input v-model="formData.name" placeholder="请输入租户名" /> - </el-form-item> - </el-col> - <el-col :span="10" :offset="2"> - <el-form-item label="租户套餐" prop="packageId"> - <el-select v-model="formData.packageId" placeholder="请选择租户套餐" clearable> - <el-option - v-for="item in packageList" - :key="item.id" - :label="item.name" - :value="item.id" - /> - </el-select> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="10"> - <el-form-item label="联系人" prop="contactName"> - <el-input v-model="formData.contactName" placeholder="请输入联系人" /> - </el-form-item> - </el-col> - <el-col :span="10" :offset="2"> - <el-form-item label="联系手机" prop="contactMobile"> - <el-input v-model="formData.contactMobile" placeholder="请输入联系手机" /> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="10"> - <el-form-item v-if="formData.id === undefined" label="用户名称" prop="username"> - <el-input v-model="formData.username" placeholder="请输入用户名称" /> - </el-form-item> - </el-col> - <el-col :span="10" :offset="2"> - <el-form-item v-if="formData.id === undefined" label="用户密码" prop="password"> - <el-input - v-model="formData.password" - placeholder="请输入用户密码" - type="password" - show-password - /> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="10"> - <el-form-item label="账号额度" prop="accountCount"> - <el-input-number - v-model="formData.accountCount" - placeholder="请输入账号额度" - controls-position="right" - :min="0" - /> - </el-form-item> - </el-col> - <el-col :span="10" :offset="2"> - <el-form-item label="过期时间" prop="expireTime"> - <el-date-picker - clearable - v-model="formData.expireTime" - type="date" - value-format="x" - placeholder="请选择过期时间" - /> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="10"> - <el-form-item label="绑定域名" prop="domain"> - <el-input v-model="formData.domain" placeholder="请输入绑定域名" /> - </el-form-item> - </el-col> - <el-col :span="10" :offset="2"> - <el-form-item label="租户状态" prop="status"> - <el-radio-group v-model="formData.status"> - <el-radio - v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" - :key="dict.value" - :label="dict.value" - >{{ dict.label }} - </el-radio> - </el-radio-group> - </el-form-item> - </el-col> - </el-row> + <el-form-item label="租户名" prop="name"> + <el-input v-model="formData.name" placeholder="请输入租户名" /> + </el-form-item> + <el-form-item label="租户套餐" prop="packageId"> + <el-select v-model="formData.packageId" placeholder="请选择租户套餐" clearable> + <el-option + v-for="item in packageList" + :key="item.id" + :label="item.name" + :value="item.id" + /> + </el-select> + </el-form-item> + <el-form-item label="联系人" prop="contactName"> + <el-input v-model="formData.contactName" placeholder="请输入联系人" /> + </el-form-item> + <el-form-item label="联系手机" prop="contactMobile"> + <el-input v-model="formData.contactMobile" placeholder="请输入联系手机" /> + </el-form-item> + <el-form-item v-if="formData.id === undefined" label="用户名称" prop="username"> + <el-input v-model="formData.username" placeholder="请输入用户名称" /> + </el-form-item> + <el-form-item v-if="formData.id === undefined" label="用户密码" prop="password"> + <el-input + v-model="formData.password" + placeholder="请输入用户密码" + type="password" + show-password + /> + </el-form-item> + <el-form-item label="账号额度" prop="accountCount"> + <el-input-number + v-model="formData.accountCount" + placeholder="请输入账号额度" + controls-position="right" + :min="0" + /> + </el-form-item> + <el-form-item label="过期时间" prop="expireTime"> + <el-date-picker + clearable + v-model="formData.expireTime" + type="date" + value-format="x" + placeholder="请选择过期时间" + /> + </el-form-item> + <el-form-item label="绑定域名" prop="domain"> + <el-input v-model="formData.domain" placeholder="请输入绑定域名" /> + </el-form-item> + <el-form-item label="租户状态" prop="status"> + <el-radio-group v-model="formData.status"> + <el-radio + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="dict.value" + > + {{ dict.label }} + </el-radio> + </el-radio-group> + </el-form-item> </el-form> <template #footer> <div class="dialog-footer"> @@ -110,7 +81,7 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import * as TenantApi from '@/api/system/tenant' import { CommonStatusEnum } from '@/utils/constants' -import { getTenantPackageList as getTenantPackageListApi } from '@/api/system/tenantPackage' +import * as TenantPackageApi from '@/api/system/tenantPackage' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -142,7 +113,7 @@ const formRef = ref() // 表单 Ref const packageList = ref([]) // 租户套餐 /** 打开弹窗 */ -const openModal = async (type: string, id?: number) => { +const open = async (type: string, id?: number) => { modelVisible.value = true modelTitle.value = t('action.' + type) formType.value = type @@ -151,14 +122,15 @@ const openModal = async (type: string, id?: number) => { if (id) { formLoading.value = true try { - formData.value = await TenantApi.getTenantApi(id) + formData.value = await TenantApi.getTenant(id) } finally { formLoading.value = false } } - packageList.value = await getTenantPackageListApi() + // 加载套餐列表 + packageList.value = await TenantPackageApi.getTenantPackageList() } -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 /** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 @@ -172,10 +144,10 @@ const submitForm = async () => { try { const data = formData.value as unknown as TenantApi.TenantVO if (formType.value === 'create') { - await TenantApi.createTenantApi(data) + await TenantApi.createTenant(data) message.success(t('common.createSuccess')) } else { - await TenantApi.updateTenantApi(data) + await TenantApi.updateTenant(data) message.success(t('common.updateSuccess')) } modelVisible.value = false diff --git a/src/views/system/tenant/index.vue b/src/views/system/tenant/index.vue index 08ff5228..e316992d 100644 --- a/src/views/system/tenant/index.vue +++ b/src/views/system/tenant/index.vue @@ -1,13 +1,20 @@ <template> <!-- 搜索 --> - <content-wrap> - <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true"> + <ContentWrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > <el-form-item label="租户名" prop="name"> <el-input v-model="queryParams.name" placeholder="请输入租户名" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="联系人" prop="contactName"> @@ -16,6 +23,7 @@ placeholder="请输入联系人" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="联系手机" prop="contactMobile"> @@ -24,12 +32,18 @@ placeholder="请输入联系手机" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="租户状态" prop="status"> - <el-select v-model="queryParams.status" placeholder="请选择租户状态" clearable> + <el-select + v-model="queryParams.status" + placeholder="请选择租户状态" + clearable + class="!w-240px" + > <el-option - v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" :label="dict.label" :value="dict.value" @@ -41,10 +55,10 @@ v-model="queryParams.createTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange" - range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="!w-240px" /> </el-form-item> @@ -57,11 +71,7 @@ <Icon icon="ep:refresh" class="mr-5px" /> 重置 </el-button> - <el-button - type="primary" - @click="openModal('create')" - v-hasPermi="['system:tenant:create']" - > + <el-button type="primary" @click="openForm('create')" v-hasPermi="['system:tenant:create']"> <Icon icon="ep:plus" class="mr-5px" /> 新增 </el-button> @@ -77,10 +87,10 @@ </el-button> </el-form-item> </el-form> - </content-wrap> + </ContentWrap> <!-- 列表 --> - <content-wrap> + <ContentWrap> <el-table v-loading="loading" :data="list" align="center"> <el-table-column label="租户编号" align="center" prop="id" /> <el-table-column label="租户名" align="center" prop="name" /> @@ -126,7 +136,7 @@ <el-button link type="primary" - @click="openModal('update', scope.row.id)" + @click="openForm('update', scope.row.id)" v-hasPermi="['system:tenant:update']" > 编辑 @@ -149,20 +159,18 @@ v-model:limit="queryParams.pageSize" @pagination="getList" /> - </content-wrap> + </ContentWrap> <!-- 表单弹窗:添加/修改 --> - <tenant-form ref="modalRef" @success="getList" /> + <TenantForm ref="formRef" @success="getList" /> </template> <script setup lang="ts" name="Tenant"> -import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' import * as TenantApi from '@/api/system/tenant' -import { getTenantPackageList as getTenantPackageListApi } from '@/api/system/tenantPackage' +import * as TenantPackageApi from '@/api/system/tenantPackage' import TenantForm from './form.vue' -import ContentWrap from '@/components/ContentWrap/src/ContentWrap.vue' -import DictTag from '@/components/DictTag/src/DictTag.vue' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -170,7 +178,6 @@ const { t } = useI18n() // 国际化 const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 const list = ref([]) // 列表的数据 -const packageList = ref([]) //租户套餐列表 const queryParams = reactive({ pageNo: 1, pageSize: 10, @@ -179,15 +186,16 @@ const queryParams = reactive({ contactMobile: undefined, status: undefined, createTime: [] -}) //查询参数对象 +}) const queryFormRef = ref() // 搜索的表单 const exportLoading = ref(false) // 导出的加载中 +const packageList = ref([]) //租户套餐列表 /** 查询参数列表 */ const getList = async () => { loading.value = true try { - const data = await TenantApi.getTenantPageApi(queryParams) + const data = await TenantApi.getTenantPage(queryParams) list.value = data.list total.value = data.total } finally { @@ -208,9 +216,9 @@ const resetQuery = () => { } /** 添加/修改操作 */ -const modalRef = ref() -const openModal = (type: string, id?: number) => { - modalRef.value.openModal(type, id) +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) } /** 删除按钮操作 */ @@ -219,7 +227,7 @@ const handleDelete = async (id: number) => { // 删除的二次确认 await message.delConfirm() // 发起删除 - await TenantApi.deleteTenantApi(id) + await TenantApi.deleteTenant(id) message.success(t('common.delSuccess')) // 刷新列表 await getList() @@ -233,23 +241,17 @@ const handleExport = async () => { await message.exportConfirm() // 发起导出 exportLoading.value = true - const data = await TenantApi.exportTenantApi(queryParams) - download.excel(data, '参数配置.xls') + const data = await TenantApi.exportTenant(queryParams) + download.excel(data, '租户列表.xls') } catch { } finally { exportLoading.value = false } } -/**获取租户套餐**/ -const getTenantPackageList = async () => { - const data = await getTenantPackageListApi() - packageList.value = data -} - /** 初始化 **/ -onMounted(() => { - getList() - getTenantPackageList() +onMounted(async () => { + await getList() + packageList.value = await TenantPackageApi.getTenantPackageList() }) </script> diff --git a/src/views/system/tenant/tenant.data.ts b/src/views/system/tenant/tenant.data.ts deleted file mode 100644 index 1137b44a..00000000 --- a/src/views/system/tenant/tenant.data.ts +++ /dev/null @@ -1,186 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -import { getTenantPackageList, TenantPackageVO } from '@/api/system/tenantPackage' -import { ComponentOptions } from '@/types/components' - -const { t } = useI18n() // 国际化 - -export const tenantPackageOption: ComponentOptions[] = [] -const getTenantPackageOptions = async () => { - const res = await getTenantPackageList() - res.forEach((tenantPackage: TenantPackageVO) => { - tenantPackageOption.push({ - key: tenantPackage.id, - value: tenantPackage.id, - label: tenantPackage.name - }) - }) - - return tenantPackageOption -} -getTenantPackageOptions() - -const validateName = (rule: any, value: any, callback: any) => { - const reg = /^[a-zA-Z0-9]{4,30}$/ - if (value === '') { - callback(new Error('请输入用户名称')) - } else { - console.log(reg.test(rule), 'reg.test(rule)') - if (!reg.test(value)) { - callback(new Error('用户名称由 数字、字母 组成')) - } else { - callback() - } - } -} -const validateMobile = (rule: any, value: any, callback: any) => { - const reg = /^1(3[0-9]|4[01456879]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\d{8}$/ - if (value === '') { - callback(new Error('请输入联系手机')) - } else { - if (!reg.test(value)) { - callback(new Error('请输入正确的手机号')) - } else { - callback() - } - } -} - -// 表单校验 -export const rules = reactive({ - name: [required], - packageId: [required], - contactName: [required], - contactMobile: [ - required, - { - validator: validateMobile, - trigger: 'blur' - } - ], - accountCount: [required], - expireTime: [required], - username: [ - required, - { - min: 4, - max: 30, - trigger: 'blur', - message: '用户名称长度为 4-30 个字符' - }, - { validator: validateName, trigger: 'blur' } - ], - password: [ - required, - { - min: 4, - max: 16, - trigger: 'blur', - message: '密码长度为 4-16 位' - } - ], - domain: [required], - status: [required] -}) - -// CrudSchema. -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryTitle: '租户编号', - primaryType: 'id', - action: true, - columns: [ - { - title: '租户名称', - field: 'name', - isSearch: true - }, - { - title: '租户套餐', - field: 'packageId', - table: { - slots: { - default: 'packageId_default' - } - }, - form: { - component: 'Select', - componentProps: { - options: tenantPackageOption - } - } - }, - { - title: '联系人', - field: 'contactName', - isSearch: true - }, - { - title: '联系手机', - field: 'contactMobile', - isSearch: true - }, - { - title: '用户名称', - field: 'username', - isTable: false, - isDetail: false - }, - { - title: '用户密码', - field: 'password', - isTable: false, - isDetail: false, - form: { - component: 'InputPassword' - } - }, - { - title: '账号额度', - field: 'accountCount', - table: { - slots: { - default: 'accountCount_default' - } - }, - form: { - component: 'InputNumber' - } - }, - { - title: '过期时间', - field: 'expireTime', - formatter: 'formatDate', - form: { - component: 'DatePicker', - componentProps: { - type: 'datetime', - valueFormat: 'x' - } - } - }, - { - title: '绑定域名', - field: 'domain' - }, - { - title: '租户状态', - field: 'status', - dictType: DICT_TYPE.COMMON_STATUS, - dictClass: 'number', - isSearch: true - }, - { - title: t('table.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From 98b9bdd4fa695a291b8a01def5626fbb48cb5fe2 Mon Sep 17 00:00:00 2001 From: dtsz <314942997@qq.com> Date: Thu, 23 Mar 2023 13:21:01 +0800 Subject: [PATCH 079/184] =?UTF-8?q?fix=EF=BC=9A=E6=9D=83=E9=99=90=E6=A0=87?= =?UTF-8?q?=E8=AF=86=E5=90=8D=E7=A7=B0=E7=9A=84=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/infra/apiErrorLog/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/infra/apiErrorLog/index.vue b/src/views/infra/apiErrorLog/index.vue index 1108651a..cfe4adb1 100644 --- a/src/views/infra/apiErrorLog/index.vue +++ b/src/views/infra/apiErrorLog/index.vue @@ -118,7 +118,7 @@ link type="primary" @click="openDetail(scope.row)" - v-hasPermi="['infra:api-access-log:query']" + v-hasPermi="['infra:api-error-log:query']" > 详细 </el-button> From aad57e2840b02f5c5a096e3085a28a0ea8fde8fc Mon Sep 17 00:00:00 2001 From: dtsz <314942997@qq.com> Date: Thu, 23 Mar 2023 13:21:38 +0800 Subject: [PATCH 080/184] =?UTF-8?q?=E8=AE=BF=E9=97=AE=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/infra/apiAccessLog/index.ts | 25 +- .../infra/apiAccessLog/ApiAccessLogDetail.vue | 66 +++++ .../infra/apiAccessLog/apiAccessLog.data.ts | 74 ----- src/views/infra/apiAccessLog/index.vue | 261 ++++++++++++++---- 4 files changed, 278 insertions(+), 148 deletions(-) create mode 100644 src/views/infra/apiAccessLog/ApiAccessLogDetail.vue delete mode 100644 src/views/infra/apiAccessLog/apiAccessLog.data.ts diff --git a/src/api/infra/apiAccessLog/index.ts b/src/api/infra/apiAccessLog/index.ts index b46199e4..25997c67 100644 --- a/src/api/infra/apiAccessLog/index.ts +++ b/src/api/infra/apiAccessLog/index.ts @@ -18,33 +18,12 @@ export interface ApiAccessLogVO { resultMsg: string createTime: Date } - -export interface ApiAccessLogPageReqVO extends PageParam { - userId?: number - userType?: number - applicationName?: string - requestUrl?: string - beginTime?: Date[] - duration?: number - resultCode?: number -} - -export interface ApiAccessLogExportReqVO { - userId?: number - userType?: number - applicationName?: string - requestUrl?: string - beginTime?: Date[] - duration?: number - resultCode?: number -} - // 查询列表API 访问日志 -export const getApiAccessLogPageApi = (params: ApiAccessLogPageReqVO) => { +export const getApiAccessLogPage = (params: PageParam) => { return request.get({ url: '/infra/api-access-log/page', params }) } // 导出API 访问日志 -export const exportApiAccessLogApi = (params: ApiAccessLogExportReqVO) => { +export const exportApiAccessLog = (params) => { return request.download({ url: '/infra/api-access-log/export-excel', params }) } diff --git a/src/views/infra/apiAccessLog/ApiAccessLogDetail.vue b/src/views/infra/apiAccessLog/ApiAccessLogDetail.vue new file mode 100644 index 00000000..d70c7ec7 --- /dev/null +++ b/src/views/infra/apiAccessLog/ApiAccessLogDetail.vue @@ -0,0 +1,66 @@ +<template> + <Dialog + title="API 访问日志详细" + v-model="modelVisible" + :scroll="true" + :max-height="500" + width="800" + > + <el-descriptions border :column="1"> + <el-descriptions-item label="日志主键" min-width="120"> + {{ detailData.id }} + </el-descriptions-item> + <el-descriptions-item label="链路追踪"> + {{ detailData.traceId }} + </el-descriptions-item> + <el-descriptions-item label="应用名"> + {{ detailData.applicationName }} + </el-descriptions-item> + <el-descriptions-item label="用户信息"> + {{ detailData.userId }} | + <dict-tag :type="DICT_TYPE.USER_TYPE" :value="detailData.userType" /> + | {{ detailData.userIp }} | {{ detailData.userAgent }} + </el-descriptions-item> + <el-descriptions-item label="请求信息"> + {{ detailData.requestMethod }} | {{ detailData.requestUrl }} + </el-descriptions-item> + <el-descriptions-item label="请求参数"> + {{ detailData.requestParams }} + </el-descriptions-item> + <el-descriptions-item label="开始时间"> + {{ formatDate(detailData.beginTime, 'YYYY-MM-DD HH:mm:ss') }} ~ + {{ formatDate(detailData.endTime, 'YYYY-MM-DD HH:mm:ss') }} | {{ detailData.duration }} ms + </el-descriptions-item> + <el-descriptions-item label="操作结果"> + <div v-if="detailData.resultCode === 0">正常</div> + <div v-else-if="detailData.resultCode > 0" + >失败 | {{ detailData.resultCode }} | {{ detailData.resultMsg }}</div + > + </el-descriptions-item> + </el-descriptions> + </Dialog> +</template> + +<script setup lang="ts"> +import { DICT_TYPE } from '@/utils/dict' +import { formatDate } from '@/utils/formatTime' +import * as ApiAccessLog from '@/api/infra/apiAccessLog' + +const modelVisible = ref(false) // 弹窗的是否展示 +const detailLoading = ref(false) // 表单地加载中 +const detailData = ref() // 详情数据 + +/** 打开弹窗 */ +const openModal = async (data: ApiAccessLog.ApiAccessLogVO) => { + modelVisible.value = true + // 设置数据 + detailLoading.value = true + try { + detailData.value = data + } finally { + detailLoading.value = false + } +} + +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +</script> diff --git a/src/views/infra/apiAccessLog/apiAccessLog.data.ts b/src/views/infra/apiAccessLog/apiAccessLog.data.ts deleted file mode 100644 index cb9e97a7..00000000 --- a/src/views/infra/apiAccessLog/apiAccessLog.data.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'id', - primaryTitle: '日志编号', - action: true, - actionWidth: '80px', - columns: [ - { - title: '链路追踪', - field: 'traceId', - isTable: false - }, - { - title: '用户编号', - field: 'userId', - isSearch: true - }, - { - title: '用户类型', - field: 'userType', - dictType: DICT_TYPE.USER_TYPE, - dictClass: 'number', - isSearch: true - }, - { - title: '应用名', - field: 'applicationName', - isSearch: true - }, - { - title: '请求方法名', - field: 'requestMethod' - }, - { - title: '请求地址', - field: 'requestUrl', - isSearch: true - }, - { - title: '请求时间', - field: 'beginTime', - formatter: 'formatDate', - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - }, - { - title: '执行时长', - field: 'duration', - table: { - slots: { - default: 'duration_default' - } - } - }, - { - title: '操作结果', - field: 'resultCode', - isSearch: true, - table: { - slots: { - default: 'resultCode_default' - } - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) diff --git a/src/views/infra/apiAccessLog/index.vue b/src/views/infra/apiAccessLog/index.vue index 6a09927d..1b433eb6 100644 --- a/src/views/infra/apiAccessLog/index.vue +++ b/src/views/infra/apiAccessLog/index.vue @@ -1,62 +1,221 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #duration_default="{ row }"> - <span>{{ row.duration + 'ms' }}</span> - </template> - <template #resultCode_default="{ row }"> - <span>{{ row.resultCode === 0 ? '成功' : '失败(' + row.resultMsg + ')' }}</span> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['infra:api-access-log:query']" - @click="handleDetail(row)" + <content-wrap> + <!-- 搜索工作栏 --> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="用户编号" prop="userId"> + <el-input + v-model="queryParams.userId" + placeholder="请输入用户编号" + clearable + @keyup.enter="handleQuery" + class="!w-240px" /> - </template> - </XTable> - </ContentWrap> - <XModal v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(详情) --> - <Descriptions :schema="allSchemas.detailSchema" :data="detailData"> - <template #duration="{ row }"> - <span>{{ row.duration + 'ms' }}</span> - </template> - <template #resultCode="{ row }"> - <span>{{ row.resultCode === 0 ? '成功' : '失败(' + row.resultMsg + ')' }}</span> - </template> - </Descriptions> - <!-- 操作按钮 --> - <template #footer> - <XButton :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> + </el-form-item> + <el-form-item label="用户类型" prop="userType"> + <el-select + v-model="queryParams.userType" + placeholder="请选择用户类型" + clearable + class="!w-240px" + > + <el-option + v-for="dict in getDictOptions(DICT_TYPE.USER_TYPE)" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + <el-form-item label="应用名" prop="applicationName"> + <el-input + v-model="queryParams.applicationName" + placeholder="请输入应用名" + clearable + @keyup.enter="handleQuery" + class="!w-240px" + /> + </el-form-item> + <el-form-item label="请求时间" prop="beginTime"> + <el-date-picker + v-model="queryParams.beginTime" + value-format="yyyy-MM-dd HH:mm:ss" + type="daterange" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="!w-240px" + /> + </el-form-item> + <el-form-item label="执行时长" prop="duration"> + <el-input + v-model="queryParams.duration" + placeholder="请输入执行时长" + clearable + @keyup.enter="handleQuery" + class="!w-240px" + /> + </el-form-item> + <el-form-item label="结果码" prop="resultCode"> + <el-input + v-model="queryParams.resultCode" + placeholder="请输入结果码" + clearable + @keyup.enter="handleQuery" + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['infra:api-error-log:export']" + > + <Icon icon="ep:download" class="mr-5px" /> 导出 + </el-button> + </el-form-item> + </el-form> + </content-wrap> + <!-- 列表 --> + <content-wrap> + <el-table v-loading="loading" :data="list"> + <el-table-column label="日志编号" align="center" prop="id" /> + <el-table-column label="用户编号" align="center" prop="userId" /> + <el-table-column label="用户类型" align="center" prop="userType"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" /> + </template> + </el-table-column> + <el-table-column label="应用名" align="center" prop="applicationName" /> + <el-table-column label="请求方法名" align="center" prop="requestMethod" /> + <el-table-column label="请求地址" align="center" prop="requestUrl" width="250" /> + <el-table-column label="请求时间" align="center" prop="beginTime" width="180"> + <template #default="scope"> + <span>{{ formatDate(scope.row.beginTime, 'YYYY-MM-DD HH:mm:ss') }}</span> + </template> + </el-table-column> + <el-table-column label="执行时长" align="center" prop="duration" width="180"> + <template #default="scope"> + <span>{{ scope.row.duration }} ms</span> + </template> + </el-table-column> + <el-table-column label="操作结果" align="center" prop="status"> + <template #default="scope"> + <span>{{ + scope.row.resultCode === 0 ? '成功' : '失败(' + scope.row.resultMsg + ')' + }}</span> + </template> + </el-table-column> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openDetail(scope.row)" + v-hasPermi="['infra:api-access-log:query']" + > + 详细 + </el-button> + </template> + </el-table-column> + </el-table> + + <!-- 分页组件 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + + <!-- 表单弹窗:详情 --> + <ApiAccessLogDetail ref="detailRef" /> </template> + <script setup lang="ts" name="ApiAccessLog"> -import { allSchemas } from './apiAccessLog.data' +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import download from '@/utils/download' +import { formatDate } from '@/utils/formatTime' import * as ApiAccessLogApi from '@/api/infra/apiAccessLog' +import ApiAccessLogDetail from './ApiAccessLogDetail.vue' -const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 -// 列表相关的变量 -const [registerTable] = useXTable({ - allSchemas: allSchemas, - topActionSlots: false, - getListApi: ApiAccessLogApi.getApiAccessLogPageApi +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 + +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + userId: null, + userType: null, + applicationName: null, + requestUrl: null, + duration: null, + resultCode: null, + beginTime: [] }) -// ========== 详情相关 ========== -const detailData = ref() // 详情 Ref -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('') // 弹出层标题 +const queryFormRef = ref() // 搜索的表单 +const exportLoading = ref(false) // 导出的加载中 -// 详情操作 -const handleDetail = (row: ApiAccessLogApi.ApiAccessLogVO) => { - // 设置数据 - detailData.value = row - dialogTitle.value = t('action.detail') - dialogVisible.value = true +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await ApiAccessLogApi.getApiAccessLogPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } } + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 详情操作 */ +const detailRef = ref() +const openDetail = (data: ApiAccessLogApi.ApiAccessLogVO) => { + detailRef.value.open(data) +} + +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await ApiAccessLogApi.exportApiAccessLog(queryParams) + download.excel(data, 'API 访问日志.xls') + } catch { + } finally { + exportLoading.value = false + } +} + +/** 初始化 **/ +onMounted(() => { + getList() +}) </script> From c640b337283f344cf08c4b6bee9c91a032196d21 Mon Sep 17 00:00:00 2001 From: xiaobai <2511883673@qq.com> Date: Thu, 23 Mar 2023 16:50:25 +0800 Subject: [PATCH 081/184] =?UTF-8?q?=E7=BF=BB=E5=86=99=20=E5=85=AC=E4=BC=97?= =?UTF-8?q?=E5=8F=B7-=E6=A0=87=E7=AD=BE=E7=AE=A1=E7=90=86=20mp/tag/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/mp/tag/form.vue | 99 +++++++++++++ src/views/mp/tag/index.vue | 211 +++++++++++++++++++++++++++ src/views/mp/tag/tmp.vue | 286 +++++++++++++++++++++++++++++++++++++ 3 files changed, 596 insertions(+) create mode 100644 src/views/mp/tag/form.vue create mode 100644 src/views/mp/tag/index.vue create mode 100644 src/views/mp/tag/tmp.vue diff --git a/src/views/mp/tag/form.vue b/src/views/mp/tag/form.vue new file mode 100644 index 00000000..004a8589 --- /dev/null +++ b/src/views/mp/tag/form.vue @@ -0,0 +1,99 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="编号" prop="accountId"> + <el-input v-model="formData.accountId" /> + </el-form-item> + <el-form-item label="标签名称" prop="name"> + <el-input v-model="formData.name" placeholder="请输入标签名称" /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import * as MpTagApi from '@/api/mp/tag' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + accountId: '', + name: undefined +}) +const formRules = reactive({ + name: [{ required: true, message: '请输入标签名称', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + + if (id) { + formLoading.value = true + try { + console.log(id) + const res = await MpTagApi.getTag(id) + formData.value = res.data + } finally { + formLoading.value = false + } + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value + if (formType.value === 'create') { + await MpTagApi.createTag(data) + message.success(t('common.createSuccess')) + } else { + await MpTagApi.updateTag(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + accountId: '', + name: undefined + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/mp/tag/index.vue b/src/views/mp/tag/index.vue new file mode 100644 index 00000000..9ceb9a61 --- /dev/null +++ b/src/views/mp/tag/index.vue @@ -0,0 +1,211 @@ +<template> + <!-- 搜索 --> + <content-wrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="公众号" prop="accountId"> + <el-select v-model="queryParams.accountId" placeholder="请选择公众号"> + <el-option + v-for="item in accounts" + :key="parseInt(item.id)" + :label="item.name" + :value="parseInt(item.id)" + /> + </el-select> + </el-form-item> + <el-form-item label="标签名称" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入标签名称" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button type="primary" @click="openModal('create')" v-hasPermi="['mp:tag:create']"> + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> + <el-button type="info" @click="handleSync" v-hasPermi="['mp:tag:sync']"> + <Icon icon="ep:refresh" class="mr-5px" /> 同步 + </el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['mp:tag:export']" + > + <Icon icon="ep:download" class="mr-5px" /> 导出 + </el-button> + </el-form-item> + </el-form> + </content-wrap> + + <!-- 列表 --> + <content-wrap> + <el-table v-loading="loading" :data="list"> + <el-table-column label="编号" align="center" prop="id" /> + <el-table-column label="标签名称" align="center" prop="name" /> + <el-table-column label="粉丝数" align="center" prop="count" /> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + size="small" + @click="openModal('update', scope.row.id)" + v-hasPermi="['mp:tag:update']" + ><Icon icon="ep:edit" class="mr-5px" />修改 + </el-button> + <el-button + link + size="small" + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['mp:tag:delete']" + > + <Icon icon="ep:delete" class="mr-5px" />删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + + <!-- 表单弹窗:添加/修改 --> + <mpTagForm ref="modalRef" @success="getList" /> +</template> +<script setup lang="ts" name="MpTag"> +import { dateFormatter } from '@/utils/formatTime' +// import download from '@/utils/download' +import * as MpTagApi from '@/api/mp/tag' +import * as MpAccountAPI from '@/api/mp/account' +import mpTagForm from './form.vue' +const message = useMessage() // 消息弹窗 +const { t } = useI18n() // 国际化 + +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + accountId: '', + name: null +}) + +interface accountsType { + id?: string | number | any + name?: string | any +} +let accounts = [] as accountsType // 公众号账号列表 +const queryFormRef = ref() // 搜索的表单 +const exportLoading = ref(false) // 导出的加载中 + +/** 查询参数列表 */ +const getList = async () => { + // 如果没有选中公众号账号,则进行提示。 + try { + if (!queryParams.accountId) { + await message.error('未选中公众号,无法查询标签') + return false + } + loading.value = true + + const data = await MpTagApi.getTagPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} +/**同步 */ +const handleSync = async () => { + try { + await message.confirm('是否确认同步标签?') //未做国际化处理 + await MpTagApi.syncTag(queryParams.accountId) + message.success('同步标签成功') //未做国际化处理 + getList() + } catch {} +} +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + // 默认选中第一个 + if (accounts.length > 0) { + queryParams.accountId = accounts[0].id + } + handleQuery() +} + +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, id) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm('是否确认删除公众号标签编号为"' + id + '"的数据项?') //未做国际化处理 + // 发起删除 + await MpTagApi.deleteTag(id) + + // 刷新列表 + await getList() + message.success(t('common.delSuccess')) + } catch {} +} + +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + message.info('功能不支持') + // const data = await MpTagApi.exportConfigApi(queryParams) + // download.excel(data, '参数配置.xls') + } catch { + } finally { + exportLoading.value = false + } +} + +/** 初始化 **/ +onMounted(async () => { + accounts = await MpAccountAPI.getSimpleAccounts() + if (accounts.length > 0) { + queryParams.accountId = accounts[0].id + } + await getList() +}) +</script> diff --git a/src/views/mp/tag/tmp.vue b/src/views/mp/tag/tmp.vue new file mode 100644 index 00000000..7ae3ad96 --- /dev/null +++ b/src/views/mp/tag/tmp.vue @@ -0,0 +1,286 @@ +<template> + <div class="app-container"> + <doc-alert title="公众号标签" url="https://doc.iocoder.cn/mp/tag/" /> + + <!-- 搜索工作栏 --> + <el-form + :model="queryParams" + ref="queryForm" + size="small" + :inline="true" + v-show="showSearch" + label-width="68px" + > + <el-form-item label="公众号" prop="accountId"> + <el-select v-model="queryParams.accountId" placeholder="请选择公众号"> + <el-option + v-for="item in accounts" + :key="parseInt(item.id)" + :label="item.name" + :value="parseInt(item.id)" + /> + </el-select> + </el-form-item> + <el-form-item label="标签名称" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入标签名称" + clearable + @keyup.enter.native="handleQuery" + /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <!-- 操作工具栏 --> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + icon="el-icon-plus" + size="mini" + @click="handleAdd" + v-hasPermi="['mp:tag:create']" + >新增 + </el-button> + </el-col> + <el-col :span="1.5"> + <el-button + type="info" + plain + icon="el-icon-refresh" + size="mini" + @click="handleSync" + v-hasPermi="['mp:tag:sync']" + >同步 + </el-button> + </el-col> + <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> + </el-row> + + <!-- 列表 --> + <el-table v-loading="loading" :data="list"> + <el-table-column label="编号" align="center" prop="id" /> + <el-table-column label="标签名称" align="center" prop="name" /> + <el-table-column label="粉丝数" align="center" prop="count" /> + <el-table-column label="创建时间" align="center" prop="createTime" width="180"> + <template v-slot="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template v-slot="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-edit" + @click="handleUpdate(scope.row)" + v-hasPermi="['mp:tag:update']" + >修改 + </el-button> + <el-button + size="mini" + type="text" + icon="el-icon-delete" + @click="handleDelete(scope.row)" + v-hasPermi="['mp:tag:delete']" + >删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页组件 --> + <pagination + v-show="total > 0" + :total="total" + :page.sync="queryParams.pageNo" + :limit.sync="queryParams.pageSize" + @pagination="getList" + /> + + <!-- 对话框(添加 / 修改) --> + <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> + <el-form ref="form" :model="form" :rules="rules" label-width="80px"> + <el-form-item label="标签名称" prop="name"> + <el-input v-model="form.name" placeholder="请输入标签名称" /> + </el-form-item> + </el-form> + <div slot="footer" class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </el-dialog> + </div> +</template> + +<script> +import { createTag, updateTag, deleteTag, getTag, getTagPage, syncTag } from '@/api/mp/tag' +import { getSimpleAccounts } from '@/api/mp/account' + +export default { + name: 'MpTag', + components: {}, + data() { + return { + // 遮罩层 + loading: true, + // 显示搜索条件 + showSearch: true, + // 总条数 + total: 0, + // 公众号标签列表 + list: [], + // 弹出层标题 + title: '', + // 是否显示弹出层 + open: false, + // 查询参数 + queryParams: { + accountId: null, + name: null + }, + // 表单参数 + form: { + accountId: undefined, + name: undefined + }, + // 表单校验 + rules: { + name: [{ required: true, message: '请输入标签名称', trigger: 'blur' }] + }, + + // 公众号账号列表 + accounts: [] + } + }, + created() { + getSimpleAccounts().then((response) => { + this.accounts = response.data + // 默认选中第一个 + if (this.accounts.length > 0) { + this.queryParams.accountId = this.accounts[0].id + } + // 加载数据 + this.getList() + }) + }, + methods: { + /** 查询列表 */ + getList() { + // 如果没有选中公众号账号,则进行提示。 + if (!this.queryParams.accountId) { + this.$message.error('未选中公众号,无法查询标签') + return false + } + + this.loading = false + // 处理查询参数 + let params = { ...this.queryParams } + // 执行查询 + getTagPage(params).then((response) => { + this.list = response.data.list + this.total = response.data.total + this.loading = false + }) + }, + /** 取消按钮 */ + cancel() { + this.open = false + this.reset() + }, + /** 表单重置 */ + reset() { + this.form = { + accountId: undefined, + name: undefined + } + this.resetForm('form') + }, + /** 搜索按钮操作 */ + handleQuery() { + this.queryParams.pageNo = 1 + this.getList() + }, + /** 重置按钮操作 */ + resetQuery() { + this.resetForm('queryForm') + // 默认选中第一个 + if (this.accounts.length > 0) { + this.queryParams.accountId = this.accounts[0].id + } + this.handleQuery() + }, + /** 新增按钮操作 */ + handleAdd() { + this.reset() + this.open = true + this.title = '添加公众号标签' + }, + /** 修改按钮操作 */ + handleUpdate(row) { + this.reset() + const id = row.id + getTag(id).then((response) => { + this.form = response.data + this.open = true + this.title = '修改公众号标签' + }) + }, + /** 提交按钮 */ + submitForm() { + this.$refs['form'].validate((valid) => { + if (!valid) { + return + } + this.form.accountId = this.queryParams.accountId + // 修改的提交 + if (this.form.id != null) { + updateTag(this.form).then((response) => { + this.$modal.msgSuccess('修改成功') + this.open = false + this.getList() + }) + return + } + // 添加的提交 + createTag(this.form).then((response) => { + this.$modal.msgSuccess('新增成功') + this.open = false + this.getList() + }) + }) + }, + /** 删除按钮操作 */ + handleDelete(row) { + const id = row.id + this.$modal + .confirm('是否确认删除公众号标签编号为"' + id + '"的数据项?') + .then(function () { + return deleteTag(id) + }) + .then(() => { + this.getList() + this.$modal.msgSuccess('删除成功') + }) + .catch(() => {}) + }, + /** 同步标签 */ + handleSync() { + const accountId = this.queryParams.accountId + this.$modal + .confirm('是否确认同步标签?') + .then(function () { + return syncTag(accountId) + }) + .then(() => { + this.$modal.msgSuccess('同步标签成功') + }) + .catch(() => {}) + } + } +} +</script> From 62b7c7bf92e480c8e07c67be4039c4a62adea5a1 Mon Sep 17 00:00:00 2001 From: zhangjinlong <zhangjinlong@sdcreditech.com> Date: Thu, 23 Mar 2023 18:15:02 +0800 Subject: [PATCH 082/184] =?UTF-8?q?vue=E9=87=8D=E6=9E=84=EF=BC=9A=E5=85=AC?= =?UTF-8?q?=E4=BC=97=E5=8F=B7=E8=B4=A6=E5=8F=B7=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/mp/account/form.vue | 158 ++++++++++++++++++++++++++++ src/views/mp/account/index.vue | 186 ++++++++++++++++++++++++++++++++- 2 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 src/views/mp/account/form.vue diff --git a/src/views/mp/account/form.vue b/src/views/mp/account/form.vue new file mode 100644 index 00000000..37ffb888 --- /dev/null +++ b/src/views/mp/account/form.vue @@ -0,0 +1,158 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form ref="formRef" :model="formData" :rules="rules" label-width="120px"> + <el-form-item label="名称" prop="name"> + <el-input v-model="formData.name" placeholder="请输入名称" /> + </el-form-item> + <el-form-item label="微信号" prop="account"> + <template #label> + <span> + <el-tooltip + content="在微信公众平台(mp.weixin.qq.com)的菜单 [设置与开发 - 公众号设置 - 账号详情] 中能找到「微信号」" + placement="top" + > + <Icon icon="ep:question-filled" style="vertical-align: middle" /> + </el-tooltip> + 微信号 + </span> + </template> + <el-input v-model="formData.account" placeholder="请输入微信号" /> + </el-form-item> + <el-form-item label="appId" prop="appId"> + <template #label> + <span> + <el-tooltip + content="在微信公众平台(mp.weixin.qq.com)的菜单 [设置与开发 - 公众号设置 - 基本设置] 中能找到「开发者ID(AppID)」" + placement="top" + > + <Icon icon="ep:question-filled" style="vertical-align: middle" /> + </el-tooltip> + appId + </span> + </template> + <el-input v-model="formData.appId" placeholder="请输入公众号 appId" /> + </el-form-item> + <el-form-item label="appSecret" prop="appSecret"> + <template #label> + <span> + <el-tooltip + content="在微信公众平台(mp.weixin.qq.com)的菜单 [设置与开发 - 公众号设置 - 基本设置] 中能找到「开发者密码(AppSecret)」" + placement="top" + > + <Icon icon="ep:question-filled" style="vertical-align: middle" /> + </el-tooltip> + appSecret + </span> + </template> + <el-input v-model="formData.appSecret" placeholder="请输入公众号 appSecret" /> + </el-form-item> + <el-form-item label="token" prop="token"> + <el-input v-model="formData.token" placeholder="请输入公众号token" /> + </el-form-item> + <el-form-item label="消息加解密密钥" prop="aesKey"> + <el-input v-model="formData.aesKey" placeholder="请输入消息加解密密钥" /> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import * as AccountApi from '@/api/mp/account' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +// 表单参数 +const formData = ref({ + id: undefined, + name: '', + account: '', + appId: '', + appSecret: '', + token: '', + aesKey: '', + remark: '' +}) +// 表单校验 +const rules = reactive({ + name: [{ required: true, message: '名称不能为空', trigger: 'blur' }], + account: [{ required: true, message: '公众号账号不能为空', trigger: 'blur' }], + appId: [{ required: true, message: '公众号 appId 不能为空', trigger: 'blur' }], + appSecret: [{ required: true, message: '公众号密钥不能为空', trigger: 'blur' }], + token: [{ required: true, message: '公众号 token 不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await AccountApi.getAccount(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 + +/** 提交按钮 */ +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value + if (formType.value === 'create') { + await AccountApi.createAccount(data) + message.success(t('common.createSuccess')) + } else { + await AccountApi.updateAccount(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 表单重置 */ +const resetForm = () => { + formData.value = { + id: undefined, + name: '', + account: '', + appId: '', + appSecret: '', + token: '', + aesKey: '', + remark: '' + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/mp/account/index.vue b/src/views/mp/account/index.vue index 497f72ec..04a7efe5 100644 --- a/src/views/mp/account/index.vue +++ b/src/views/mp/account/index.vue @@ -1,3 +1,187 @@ <template> - <span>开发中</span> + <!-- 搜索工作栏 --> + <content-wrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="90px" + > + <el-form-item label="名称" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入名称" + clearable + @keyup.enter="handleQuery" + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" />搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button> + <el-button type="primary" @click="openModal('create')" v-hasPermi="['mp:account:create']" + ><Icon icon="ep:plus" class="mr-5px" />新增 + </el-button> + </el-form-item> + </el-form> + </content-wrap> + + <!-- 列表 --> + <content-wrap> + <el-table v-loading="loading" :data="list"> + <el-table-column label="名称" align="center" prop="name" /> + <el-table-column label="微信号" align="center" prop="account" width="180" /> + <el-table-column label="appId" align="center" prop="appId" width="180" /> + <el-table-column label="服务器地址(URL)" align="center" prop="appId" width="360"> + <template #default="scope"> + {{ 'http://服务端地址/mp/open/' + scope.row.appId }} + </template> + </el-table-column> + <el-table-column label="二维码" align="center" prop="qrCodeUrl"> + <template #default="scope"> + <img + v-if="scope.row.qrCodeUrl" + :src="scope.row.qrCodeUrl" + alt="二维码" + style="height: 100px; display: inline-block" + /> + </template> + </el-table-column> + <el-table-column label="备注" align="center" prop="remark" /> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openModal('update', scope.row.id)" + v-hasPermi="['mp:account:update']" + >编辑 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['mp:account:delete']" + >删除 + </el-button> + <el-button + link + type="primary" + @click="handleGenerateQrCode(scope.row)" + v-hasPermi="['mp:account:qr-code']" + >生成二维码 + </el-button> + <el-button + link + type="danger" + @click="handleCleanQuota(scope.row)" + v-hasPermi="['mp:account:clear-quota']" + >清空 API 配额 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页组件 --> + <pagination + v-show="total > 0" + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + <!-- 对话框(添加 / 修改) --> + <account-form ref="modalRef" @success="getList" /> </template> + +<script setup lang="ts" name="MpAccount"> +import * as AccountApi from '@/api/mp/account' +import AccountForm from './form.vue' + +const message = useMessage() // 消息弹窗 +const { t } = useI18n() // 国际化 +// 遮罩层 +const loading = ref(true) +// 总条数 +const total = ref(0) +// 公众号账号列表 +const list = ref([]) +// 查询参数 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + name: null, + account: null, + appId: null +}) +const queryFormRef = ref() // 搜索的表单 + +/** 查询列表 */ +const getList = async () => { + loading.value = true + // 处理查询参数 + let params = { ...queryParams } + // 执行查询 + const data = await AccountApi.getAccountPage(params) + list.value = data.list + total.value = data.total + loading.value = false +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, id) +} + +/** 删除按钮操作 */ +const handleDelete = async (id) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await AccountApi.deleteAccount(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} +/** 生成二维码的按钮操作 */ +const handleGenerateQrCode = async (row) => { + try { + // 生成二维码的二次确认 + await message.confirm('是否确认生成公众号账号编号为"' + row.name + '"的二维码?') + // 发起生成二维码 + await AccountApi.generateAccountQrCode(row.id) + message.success('生成二维码成功') + // 刷新列表 + await getList() + } catch {} +} +/** 清空二维码 API 配额的按钮操作 */ +const handleCleanQuota = async (row) => { + try { + // 清空 API 配额的二次确认 + await message.confirm('是否确认清空生成公众号账号编号为"' + row.name + '"的 API 配额?') + // 发起清空 API 配额 + await AccountApi.clearAccountQuota(row.id) + message.success('清空 API 配额成功') + } catch {} +} + +onMounted(() => { + getList() +}) +</script> From abbaeabc3af652a1a3b5de7909a6733a56696696 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Thu, 23 Mar 2023 22:50:48 +0800 Subject: [PATCH 083/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=B7=A5=E4=BD=9C=E6=B5=81=E7=9A=84=E8=A1=A8?= =?UTF-8?q?=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/bpm/form/index.ts | 12 +- src/router/modules/remaining.ts | 4 +- src/views/bpm/form/FormEditor.vue | 108 +++++++++++++++ src/views/bpm/form/form.data.ts | 43 ------ src/views/bpm/form/formEditor.vue | 157 ---------------------- src/views/bpm/form/index.vue | 210 ++++++++++++++++++++---------- src/views/bpm/model/index.vue | 4 +- 7 files changed, 262 insertions(+), 276 deletions(-) create mode 100644 src/views/bpm/form/FormEditor.vue delete mode 100644 src/views/bpm/form/form.data.ts delete mode 100644 src/views/bpm/form/formEditor.vue diff --git a/src/api/bpm/form/index.ts b/src/api/bpm/form/index.ts index c745201f..142ed24c 100644 --- a/src/api/bpm/form/index.ts +++ b/src/api/bpm/form/index.ts @@ -11,7 +11,7 @@ export type FormVO = { } // 创建工作流的表单定义 -export const createFormApi = async (data: FormVO) => { +export const createForm = async (data: FormVO) => { return await request.post({ url: '/bpm/form/create', data: data @@ -19,7 +19,7 @@ export const createFormApi = async (data: FormVO) => { } // 更新工作流的表单定义 -export const updateFormApi = async (data: FormVO) => { +export const updateForm = async (data: FormVO) => { return await request.put({ url: '/bpm/form/update', data: data @@ -27,21 +27,21 @@ export const updateFormApi = async (data: FormVO) => { } // 删除工作流的表单定义 -export const deleteFormApi = async (id: number) => { +export const deleteForm = async (id: number) => { return await request.delete({ url: '/bpm/form/delete?id=' + id }) } // 获得工作流的表单定义 -export const getFormApi = async (id: number) => { +export const getForm = async (id: number) => { return await request.get({ url: '/bpm/form/get?id=' + id }) } // 获得工作流的表单定义分页 -export const getFormPageApi = async (params) => { +export const getFormPage = async (params) => { return await request.get({ url: '/bpm/form/page', params @@ -49,7 +49,7 @@ export const getFormPageApi = async (params) => { } // 获得动态表单的精简列表 -export const getSimpleFormsApi = async () => { +export const getSimpleFormList = async () => { return await request.get({ url: '/bpm/form/list-all-simple' }) diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index d5970267..04f0d3d0 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -225,14 +225,14 @@ const remainingRouter: AppRouteRecordRaw[] = [ children: [ { path: '/manager/form/edit', - component: () => import('@/views/bpm/form/formEditor.vue'), + component: () => import('@/views/bpm/form/FormEditor.vue'), name: 'bpmFormEditor', meta: { noCache: true, hidden: true, canTo: true, title: '设计流程表单', - activeMenu: 'bpm/manager/form/formEditor' + activeMenu: '/bpm/manager/form' } }, { diff --git a/src/views/bpm/form/FormEditor.vue b/src/views/bpm/form/FormEditor.vue new file mode 100644 index 00000000..25ff66fe --- /dev/null +++ b/src/views/bpm/form/FormEditor.vue @@ -0,0 +1,108 @@ +<template> + <ContentWrap> + <!-- 表单设计器 --> + <fc-designer ref="designer" height="780px"> + <template #handle> + <el-button round size="small" type="primary" @click="handleSave"> + <Icon icon="ep:plus" class="mr-5px" /> 保存 + </el-button> + </template> + </fc-designer> + </ContentWrap> + + <!-- 表单保存的弹窗 --> + <Dialog title="保存表单" v-model="modelVisible" width="600"> + <el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px"> + <el-form-item label="表单名" prop="name"> + <el-input v-model="formData.name" placeholder="请输入表单名" /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-radio-group v-model="formData.status"> + <el-radio + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="dict.value" + > + {{ dict.label }} + </el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { CommonStatusEnum } from '@/utils/constants' +import * as FormApi from '@/api/bpm/form' +import { encodeConf, encodeFields, setConfAndFields } from '@/utils/formCreate' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息 +const { query } = useRoute() // 路由 + +const designer = ref() // 表单设计器 +const modelVisible = ref(false) // 弹窗是否展示 +const formLoading = ref(false) // 表单的加载中:提交的按钮禁用 +const formData = ref({ + name: '', + status: CommonStatusEnum.ENABLE, + remark: '' +}) +const formRules = reactive({ + name: [{ required: true, message: '表单名不能为空', trigger: 'blur' }], + status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 处理保存按钮 */ +const handleSave = () => { + modelVisible.value = true +} + +/** 提交表单 */ +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as FormApi.FormVO + data.conf = encodeConf(designer) // 表单配置 + data.fields = encodeFields(designer) // 表单字段 + if (!data.id) { + await FormApi.createForm(data) + message.success(t('common.createSuccess')) + } else { + await FormApi.updateForm(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + } finally { + formLoading.value = false + } +} + +/** 初始化 **/ +onMounted(async () => { + // 场景一:新增表单 + const id = query.id as unknown as number + if (!id) { + return + } + // 场景二:修改表单 + const data = await FormApi.getForm(id) + formData.value = data + setConfAndFields(designer, data.conf, data.fields) +}) +</script> diff --git a/src/views/bpm/form/form.data.ts b/src/views/bpm/form/form.data.ts deleted file mode 100644 index 43c93dd7..00000000 --- a/src/views/bpm/form/form.data.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' - -const { t } = useI18n() // 国际化 - -// 表单校验 -export const rules = reactive({ - name: [required] -}) - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'id', - primaryTitle: '表单编号', - action: true, - columns: [ - { - title: '表单名', - field: 'name', - isSearch: true - }, - { - title: t('common.status'), - field: 'status', - dictType: DICT_TYPE.COMMON_STATUS, - dictClass: 'number' - }, - { - title: '备注', - field: 'remark' - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false, - table: { - width: 180 - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) diff --git a/src/views/bpm/form/formEditor.vue b/src/views/bpm/form/formEditor.vue deleted file mode 100644 index 1070739e..00000000 --- a/src/views/bpm/form/formEditor.vue +++ /dev/null @@ -1,157 +0,0 @@ -<template> - <ContentWrap> - <!-- 表单设计器 --> - <fc-designer ref="designer" height="780px"> - <template #handle> - <XButton type="primary" title="生成JSON" @click="showJson" /> - <XButton type="primary" title="生成Options" @click="showOption" /> - <XButton type="primary" :title="t('action.save')" @click="handleSave" /> - </template> - </fc-designer> - <Dialog :title="dialogTitle" v-model="dialogVisible1" maxHeight="600"> - <div ref="editor" v-if="dialogVisible1"> - <XTextButton style="float: right" :title="t('common.copy')" @click="copy(formValue)" /> - <el-scrollbar height="580"> - <pre> - {{ formValue }} - </pre> - </el-scrollbar> - </div> - </Dialog> - <!-- 表单保存的弹窗 --> - <XModal v-model="dialogVisible" title="保存表单"> - <el-form ref="formRef" :model="formValues" :rules="formRules" label-width="80px"> - <el-form-item label="表单名" prop="name"> - <el-input v-model="formValues.name" placeholder="请输入表单名" /> - </el-form-item> - <el-form-item label="开启状态" prop="status"> - <el-radio-group v-model="formValues.status"> - <el-radio - v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" - :key="dict.value" - :label="dict.value" - > - {{ dict.label }} - </el-radio> - </el-radio-group> - </el-form-item> - <el-form-item label="备注" prop="remark"> - <el-input v-model="formValues.remark" type="textarea" placeholder="请输入备注" /> - </el-form-item> - </el-form> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton - type="primary" - :title="t('action.save')" - :loading="dialogLoading" - @click="submitForm" - /> - <!-- 按钮:关闭 --> - <XButton :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> - </ContentWrap> -</template> -<script setup lang="ts" name="BpmFormEditor"> -import { FormInstance } from 'element-plus' -import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' -import { CommonStatusEnum } from '@/utils/constants' -import * as FormApi from '@/api/bpm/form' -import { encodeConf, encodeFields, setConfAndFields } from '@/utils/formCreate' -import { useClipboard } from '@vueuse/core' - -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息 -const { query } = useRoute() // 路由 - -const designer = ref() // 表单设计器 -const type = ref(-1) -const formValue = ref('') -const dialogTitle = ref('') -const dialogVisible = ref(false) // 弹窗是否展示 -const dialogVisible1 = ref(false) // 弹窗是否展示 -const dialogLoading = ref(false) // 弹窗的加载中 -const formRef = ref<FormInstance>() -const formRules = reactive({ - name: [{ required: true, message: '表单名不能为空', trigger: 'blur' }], - status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }] -}) -const formValues = ref({ - name: '', - status: CommonStatusEnum.ENABLE, - remark: '' -}) - -// 处理保存按钮 -const handleSave = () => { - dialogVisible.value = true -} - -// 提交保存表单 -const submitForm = async () => { - // 参数校验 - const elForm = unref(formRef) - if (!elForm) return - const valid = await elForm.validate() - if (!valid) return - - // 提交请求 - dialogLoading.value = true - try { - const data = formValues.value as FormApi.FormVO - data.conf = encodeConf(designer) // 表单配置 - data.fields = encodeFields(designer) // 表单字段 - if (!data.id) { - await FormApi.createFormApi(data) - message.success(t('common.createSuccess')) - } else { - await FormApi.updateFormApi(data) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false - } finally { - dialogLoading.value = false - } -} -const showJson = () => { - openModel('生成JSON') - type.value = 0 - formValue.value = designer.value.getRule() -} -const showOption = () => { - openModel('生成Options') - type.value = 1 - formValue.value = designer.value.getOption() -} -const openModel = (title: string) => { - dialogVisible1.value = true - dialogTitle.value = title -} -/** 复制 **/ -const copy = async (text: string) => { - const { copy, copied, isSupported } = useClipboard({ source: text }) - if (!isSupported) { - message.error(t('common.copyError')) - } else { - await copy() - if (unref(copied)) { - message.success(t('common.copySuccess')) - } - } -} -// ========== 初始化 ========== -onMounted(() => { - // 场景一:新增表单 - const id = query.id as unknown as number - if (!id) { - return - } - // 场景二:修改表单 - FormApi.getFormApi(id).then((data) => { - formValues.value = data - setConfAndFields(designer, data.conf, data.fields) - }) -}) -</script> diff --git a/src/views/bpm/form/index.vue b/src/views/bpm/form/index.vue index b4b208a9..7e14b3e3 100644 --- a/src/views/bpm/form/index.vue +++ b/src/views/bpm/form/index.vue @@ -1,93 +1,171 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <!-- 操作:新增 --> - <template #toolbar_buttons> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['system:post:create']" - @click="handleCreate()" + <content-wrap> + <!-- 搜索工作栏 --> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="表单名" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入表单名" + clearable + @keyup.enter="handleQuery" + class="!w-240px" /> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:修改 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['bpm:form:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['bpm:form:query']" - @click="handleDetail(row.id)" - /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['bpm:form:delete']" - @click="deleteData(row.id)" - /> - </template> - </XTable> - <!-- 表单详情的弹窗 --> - <XModal v-model="detailOpen" width="800" title="表单详情"> - <form-create :rule="detailPreview.rule" :option="detailPreview.option" v-if="detailOpen" /> - </XModal> - </ContentWrap> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button type="primary" @click="openForm()" v-hasPermi="['bpm:form:create']"> + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> + </el-form-item> + </el-form> + </content-wrap> + + <!-- 列表 --> + <content-wrap> + <el-table v-loading="loading" :data="list"> + <el-table-column label="编号" align="center" prop="id" /> + <el-table-column label="表单名" align="center" prop="name" /> + <el-table-column label="状态" align="center" prop="status"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> + </template> + </el-table-column> + <el-table-column label="备注" align="center" prop="remark" /> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + :formatter="dateFormatter" + /> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openForm(scope.row.id)" + v-hasPermi="['bpm:form:update']" + > + 编辑 + </el-button> + <el-button link @click="openDetail(scope.row.id)" v-hasPermi="['bpm:form:query']"> + 详情 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['bpm:form:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + + <!-- 表单详情的弹窗 --> + <Dialog title="表单详情" v-model="detailVisible" width="800"> + <form-create :rule="detailData.rule" :option="detailData.option" /> + </Dialog> </template> -<script setup lang="ts" name="BpmForm"> -// 业务相关的 import +<script setup lang="ts" name="Form"> +import { DICT_TYPE } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' import * as FormApi from '@/api/bpm/form' -import { allSchemas } from './form.data' -// 表单详情相关的变量和 import import { setConfAndFields2 } from '@/utils/formCreate' +const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 const { push } = useRouter() // 路由 -// 列表相关的变量 -const [registerTable, { deleteData }] = useXTable({ - allSchemas: allSchemas, - getListApi: FormApi.getFormPageApi, - deleteApi: FormApi.deleteFormApi +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + name: null }) +const queryFormRef = ref() // 搜索的表单 -// 新增操作 -const handleCreate = () => { - push({ - name: 'bpmFormEditor' - }) +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await FormApi.getFormPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } } -// 修改操作 -const handleUpdate = async (rowId: number) => { - await push({ +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const openForm = (id?: number) => { + push({ name: 'bpmFormEditor', query: { - id: rowId + id } }) } -// 详情操作 -const detailOpen = ref(false) -const detailPreview = ref({ +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await FormApi.deleteForm(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +/** 详情操作 */ +const detailVisible = ref(false) +const detailData = ref({ rule: [], option: {} }) -const handleDetail = async (rowId: number) => { +const openDetail = async (rowId: number) => { // 设置表单 - const data = await FormApi.getFormApi(rowId) - setConfAndFields2(detailPreview, data.conf, data.fields) + const data = await FormApi.getForm(rowId) + setConfAndFields2(detailData, data.conf, data.fields) // 弹窗打开 - detailOpen.value = true + detailVisible.value = true } + +/** 初始化 **/ +onMounted(() => { + getList() +}) </script> diff --git a/src/views/bpm/model/index.vue b/src/views/bpm/model/index.vue index 01a97d3f..e88ee89e 100644 --- a/src/views/bpm/model/index.vue +++ b/src/views/bpm/model/index.vue @@ -374,7 +374,7 @@ const formDetailPreview = ref({ const handleFormDetail = async (row) => { if (row.formType == 10) { // 设置表单 - const data = await FormApi.getFormApi(row.formId) + const data = await FormApi.getForm(row.formId) setConfAndFields2(formDetailPreview, data.conf, data.fields) // 弹窗打开 formDetailVisible.value = true @@ -588,7 +588,7 @@ const uploadClose = () => { // ========== 初始化 ========== onMounted(() => { // 获得流程表单的下拉框的数据 - FormApi.getSimpleFormsApi().then((data) => { + FormApi.getSimpleFormList().then((data) => { forms.value = data }) }) From 8a8154e124095a5612cc9e25df59a4f306f11c14 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Thu, 23 Mar 2023 23:16:53 +0800 Subject: [PATCH 084/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E5=85=AC=E4=BC=97=E5=8F=B7=E8=B4=A6=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mp/account/{form.vue => AccountForm.vue} | 23 ++++--- src/views/mp/account/index.vue | 61 ++++++++++--------- src/views/system/dept/index.vue | 13 +++- 3 files changed, 54 insertions(+), 43 deletions(-) rename src/views/mp/account/{form.vue => AccountForm.vue} (91%) diff --git a/src/views/mp/account/form.vue b/src/views/mp/account/AccountForm.vue similarity index 91% rename from src/views/mp/account/form.vue rename to src/views/mp/account/AccountForm.vue index 37ffb888..406db8fe 100644 --- a/src/views/mp/account/form.vue +++ b/src/views/mp/account/AccountForm.vue @@ -1,6 +1,12 @@ <template> <Dialog :title="modelTitle" v-model="modelVisible"> - <el-form ref="formRef" :model="formData" :rules="rules" label-width="120px"> + <el-form + ref="formRef" + :model="formData" + :rules="rules" + label-width="120px" + v-loading="formLoading" + > <el-form-item label="名称" prop="name"> <el-input v-model="formData.name" placeholder="请输入名称" /> </el-form-item> @@ -57,16 +63,13 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button type="primary" @click="submitForm">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> <script setup lang="ts"> import * as AccountApi from '@/api/mp/account' - const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -74,7 +77,6 @@ const modelVisible = ref(false) // 弹窗的是否展示 const modelTitle = ref('') // 弹窗的标题 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formType = ref('') // 表单的类型:create - 新增;update - 修改 -// 表单参数 const formData = ref({ id: undefined, name: '', @@ -85,7 +87,6 @@ const formData = ref({ aesKey: '', remark: '' }) -// 表单校验 const rules = reactive({ name: [{ required: true, message: '名称不能为空', trigger: 'blur' }], account: [{ required: true, message: '公众号账号不能为空', trigger: 'blur' }], @@ -96,7 +97,7 @@ const rules = reactive({ const formRef = ref() // 表单 Ref /** 打开弹窗 */ -const openModal = async (type: string, id?: number) => { +const open = async (type: string, id?: number) => { modelVisible.value = true modelTitle.value = t('action.' + type) formType.value = type @@ -111,12 +112,10 @@ const openModal = async (type: string, id?: number) => { } } } -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 /** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 - -/** 提交按钮 */ const submitForm = async () => { // 校验表单 if (!formRef) return diff --git a/src/views/mp/account/index.vue b/src/views/mp/account/index.vue index 04a7efe5..3489998b 100644 --- a/src/views/mp/account/index.vue +++ b/src/views/mp/account/index.vue @@ -6,7 +6,7 @@ :model="queryParams" ref="queryFormRef" :inline="true" - label-width="90px" + label-width="68px" > <el-form-item label="名称" prop="name"> <el-input @@ -20,8 +20,8 @@ <el-form-item> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" />搜索</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button> - <el-button type="primary" @click="openModal('create')" v-hasPermi="['mp:account:create']" - ><Icon icon="ep:plus" class="mr-5px" />新增 + <el-button type="primary" @click="openForm('create')" v-hasPermi="['mp:account:create']"> + <Icon icon="ep:plus" class="mr-5px" /> 新增 </el-button> </el-form-item> </el-form> @@ -46,6 +46,14 @@ alt="二维码" style="height: 100px; display: inline-block" /> + <el-button + link + type="primary" + @click="handleGenerateQrCode(scope.row)" + v-hasPermi="['mp:account:qr-code']" + > + 生成二维码 + </el-button> </template> </el-table-column> <el-table-column label="备注" align="center" prop="remark" /> @@ -54,30 +62,26 @@ <el-button link type="primary" - @click="openModal('update', scope.row.id)" + @click="openForm('update', scope.row.id)" v-hasPermi="['mp:account:update']" - >编辑 + > + 编辑 </el-button> <el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['mp:account:delete']" - >删除 - </el-button> - <el-button - link - type="primary" - @click="handleGenerateQrCode(scope.row)" - v-hasPermi="['mp:account:qr-code']" - >生成二维码 + > + 删除 </el-button> <el-button link type="danger" @click="handleCleanQuota(scope.row)" v-hasPermi="['mp:account:clear-quota']" - >清空 API 配额 + > + 清空 API 配额 </el-button> </template> </el-table-column> @@ -91,23 +95,20 @@ @pagination="getList" /> </content-wrap> - <!-- 对话框(添加 / 修改) --> - <account-form ref="modalRef" @success="getList" /> -</template> + <!-- 对话框(添加 / 修改) --> + <AccountForm ref="formRef" @success="getList" /> +</template> <script setup lang="ts" name="MpAccount"> import * as AccountApi from '@/api/mp/account' -import AccountForm from './form.vue' +import AccountForm from './AccountForm.vue' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 -// 遮罩层 -const loading = ref(true) -// 总条数 -const total = ref(0) -// 公众号账号列表 -const list = ref([]) -// 查询参数 + +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 const queryParams = reactive({ pageNo: 1, pageSize: 10, @@ -134,6 +135,7 @@ const handleQuery = () => { queryParams.pageNo = 1 getList() } + /** 重置按钮操作 */ const resetQuery = () => { queryFormRef.value.resetFields() @@ -141,9 +143,9 @@ const resetQuery = () => { } /** 添加/修改操作 */ -const modalRef = ref() -const openModal = (type: string, id?: number) => { - modalRef.value.openModal(type, id) +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) } /** 删除按钮操作 */ @@ -158,6 +160,7 @@ const handleDelete = async (id) => { await getList() } catch {} } + /** 生成二维码的按钮操作 */ const handleGenerateQrCode = async (row) => { try { @@ -170,6 +173,7 @@ const handleGenerateQrCode = async (row) => { await getList() } catch {} } + /** 清空二维码 API 配额的按钮操作 */ const handleCleanQuota = async (row) => { try { @@ -181,6 +185,7 @@ const handleCleanQuota = async (row) => { } catch {} } +/** 初始化 **/ onMounted(() => { getList() }) diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue index de561a1f..977840e2 100644 --- a/src/views/system/dept/index.vue +++ b/src/views/system/dept/index.vue @@ -1,7 +1,13 @@ <template> + <!-- 搜索工作栏 --> <ContentWrap> - <!-- 搜索工作栏 --> - <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px"> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > <el-form-item label="部门名称" prop="title"> <el-input v-model="queryParams.name" placeholder="请输入部门名称" clearable /> </el-form-item> @@ -113,7 +119,7 @@ const isExpandAll = ref(true) // 是否展开,默认全部展开 const refreshTable = ref(true) // 重新渲染表格状态 const loading = ref(true) // 列表的加载中 -//获取用户列表 +// 获取用户列表 const getUserList = async () => { const res = await getSimpleUserList() userOption.value = res @@ -157,6 +163,7 @@ const getList = async () => { loading.value = false } } + /** 重置按钮操作 */ const resetQuery = () => { queryParams.pageNo = 1 From 12b60da942d978f97524a4b9f6784e8dac8e9a6f Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Thu, 23 Mar 2023 23:37:15 +0800 Subject: [PATCH 085/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20API=20=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/infra/apiAccessLog/index.ts | 1 + .../infra/apiAccessLog/ApiAccessLogDetail.vue | 28 +++++++++---------- src/views/infra/apiAccessLog/index.vue | 7 ++--- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/api/infra/apiAccessLog/index.ts b/src/api/infra/apiAccessLog/index.ts index 25997c67..c6b4b45f 100644 --- a/src/api/infra/apiAccessLog/index.ts +++ b/src/api/infra/apiAccessLog/index.ts @@ -18,6 +18,7 @@ export interface ApiAccessLogVO { resultMsg: string createTime: Date } + // 查询列表API 访问日志 export const getApiAccessLogPage = (params: PageParam) => { return request.get({ url: '/infra/api-access-log/page', params }) diff --git a/src/views/infra/apiAccessLog/ApiAccessLogDetail.vue b/src/views/infra/apiAccessLog/ApiAccessLogDetail.vue index d70c7ec7..1db9820c 100644 --- a/src/views/infra/apiAccessLog/ApiAccessLogDetail.vue +++ b/src/views/infra/apiAccessLog/ApiAccessLogDetail.vue @@ -1,11 +1,5 @@ <template> - <Dialog - title="API 访问日志详细" - v-model="modelVisible" - :scroll="true" - :max-height="500" - width="800" - > + <Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="500" width="800"> <el-descriptions border :column="1"> <el-descriptions-item label="日志主键" min-width="120"> {{ detailData.id }} @@ -17,20 +11,26 @@ {{ detailData.applicationName }} </el-descriptions-item> <el-descriptions-item label="用户信息"> - {{ detailData.userId }} | + {{ detailData.userId }} <dict-tag :type="DICT_TYPE.USER_TYPE" :value="detailData.userType" /> - | {{ detailData.userIp }} | {{ detailData.userAgent }} + </el-descriptions-item> + <el-descriptions-item label="用户 IP"> + {{ detailData.userIp }} + </el-descriptions-item> + <el-descriptions-item label="用户 UA"> + {{ detailData.userAgent }} </el-descriptions-item> <el-descriptions-item label="请求信息"> - {{ detailData.requestMethod }} | {{ detailData.requestUrl }} + {{ detailData.requestMethod }} {{ detailData.requestUrl }} </el-descriptions-item> <el-descriptions-item label="请求参数"> {{ detailData.requestParams }} </el-descriptions-item> - <el-descriptions-item label="开始时间"> + <el-descriptions-item label="请求时间"> {{ formatDate(detailData.beginTime, 'YYYY-MM-DD HH:mm:ss') }} ~ - {{ formatDate(detailData.endTime, 'YYYY-MM-DD HH:mm:ss') }} | {{ detailData.duration }} ms + {{ formatDate(detailData.endTime, 'YYYY-MM-DD HH:mm:ss') }} </el-descriptions-item> + <el-descriptions-item label="请求耗时">{{ detailData.duration }} ms</el-descriptions-item> <el-descriptions-item label="操作结果"> <div v-if="detailData.resultCode === 0">正常</div> <div v-else-if="detailData.resultCode > 0" @@ -51,7 +51,7 @@ const detailLoading = ref(false) // 表单地加载中 const detailData = ref() // 详情数据 /** 打开弹窗 */ -const openModal = async (data: ApiAccessLog.ApiAccessLogVO) => { +const open = async (data: ApiAccessLog.ApiAccessLogVO) => { modelVisible.value = true // 设置数据 detailLoading.value = true @@ -62,5 +62,5 @@ const openModal = async (data: ApiAccessLog.ApiAccessLogVO) => { } } -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 </script> diff --git a/src/views/infra/apiAccessLog/index.vue b/src/views/infra/apiAccessLog/index.vue index 1b433eb6..ecae3821 100644 --- a/src/views/infra/apiAccessLog/index.vue +++ b/src/views/infra/apiAccessLog/index.vue @@ -85,6 +85,7 @@ </el-form-item> </el-form> </content-wrap> + <!-- 列表 --> <content-wrap> <el-table v-loading="loading" :data="list"> @@ -96,7 +97,7 @@ </template> </el-table-column> <el-table-column label="应用名" align="center" prop="applicationName" /> - <el-table-column label="请求方法名" align="center" prop="requestMethod" /> + <el-table-column label="请求方法" align="center" prop="requestMethod" width="80" /> <el-table-column label="请求地址" align="center" prop="requestUrl" width="250" /> <el-table-column label="请求时间" align="center" prop="beginTime" width="180"> <template #default="scope"> @@ -128,7 +129,6 @@ </template> </el-table-column> </el-table> - <!-- 分页组件 --> <Pagination :total="total" @@ -154,7 +154,6 @@ const message = useMessage() // 消息弹窗 const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 const list = ref([]) // 列表的数据 - const queryParams = reactive({ pageNo: 1, pageSize: 10, @@ -169,7 +168,7 @@ const queryParams = reactive({ const queryFormRef = ref() // 搜索的表单 const exportLoading = ref(false) // 导出的加载中 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { From 4481964182f8f47a1bf5387212d595e9472992a2 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Thu, 23 Mar 2023 23:50:14 +0800 Subject: [PATCH 086/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E7=99=BB=E5=BD=95=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/loginLog/index.ts | 12 +++--------- src/styles/index.scss | 6 ------ .../loginlog/{detail.vue => LoginLogDetail.vue} | 6 +++--- src/views/system/loginlog/index.vue | 15 +++++++-------- 4 files changed, 13 insertions(+), 26 deletions(-) rename src/views/system/loginlog/{detail.vue => LoginLogDetail.vue} (87%) diff --git a/src/api/system/loginLog/index.ts b/src/api/system/loginLog/index.ts index 5b06e753..f275c3e2 100644 --- a/src/api/system/loginLog/index.ts +++ b/src/api/system/loginLog/index.ts @@ -13,18 +13,12 @@ export interface LoginLogVO { createTime: Date } -export interface LoginLogReqVO extends PageParam { - userIp?: string - username?: string - status?: boolean - createTime?: Date[] -} - // 查询登录日志列表 -export const getLoginLogPage = (params: LoginLogReqVO) => { +export const getLoginLogPage = (params: PageParam) => { return request.get({ url: '/system/login-log/page', params }) } + // 导出登录日志 -export const exportLoginLog = (params: LoginLogReqVO) => { +export const exportLoginLog = (params) => { return request.download({ url: '/system/login-log/export', params }) } diff --git a/src/styles/index.scss b/src/styles/index.scss index 02c187a5..39c4c4da 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -26,9 +26,3 @@ border-left-color: var(--el-color-primary); } } - -// 添加表头样式 -.el-table.yudao-table { - --el-table-header-bg-color: #f8f8f9; - --el-table-header-text-color: #606266; -} diff --git a/src/views/system/loginlog/detail.vue b/src/views/system/loginlog/LoginLogDetail.vue similarity index 87% rename from src/views/system/loginlog/detail.vue rename to src/views/system/loginlog/LoginLogDetail.vue index 8372d2e6..c224f147 100644 --- a/src/views/system/loginlog/detail.vue +++ b/src/views/system/loginlog/LoginLogDetail.vue @@ -1,5 +1,5 @@ <template> - <Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="500" width="800"> + <Dialog title="详情" v-model="modelVisible" width="800"> <el-descriptions border :column="1"> <el-descriptions-item label="日志编号" min-width="120"> {{ detailData.id }} @@ -35,7 +35,7 @@ const detailLoading = ref(false) // 表单的加载中 const detailData = ref() // 详情数据 /** 打开弹窗 */ -const openModal = async (data: LoginLogApi.LoginLogVO) => { +const open = async (data: LoginLogApi.LoginLogVO) => { modelVisible.value = true // 设置数据 detailLoading.value = true @@ -45,5 +45,5 @@ const openModal = async (data: LoginLogApi.LoginLogVO) => { detailLoading.value = false } } -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 </script> diff --git a/src/views/system/loginlog/index.vue b/src/views/system/loginlog/index.vue index f5a695fc..7b1aca5f 100644 --- a/src/views/system/loginlog/index.vue +++ b/src/views/system/loginlog/index.vue @@ -55,7 +55,7 @@ <!-- 列表 --> <content-wrap> - <el-table class="yudao-table" v-loading="loading" :data="list"> + <el-table v-loading="loading" :data="list"> <el-table-column label="日志编号" align="center" prop="id" /> <el-table-column label="操作类型" align="center" prop="logType"> <template #default="scope"> @@ -64,7 +64,6 @@ </el-table-column> <el-table-column label="用户名称" align="center" prop="username" width="180" /> <el-table-column label="登录地址" align="center" prop="userIp" width="180" /> - <el-table-column label="浏览器" align="center" prop="userAgent" /> <el-table-column label="登陆结果" align="center" prop="result"> <template #default="scope"> @@ -83,7 +82,7 @@ <el-button link type="primary" - @click="openModal(scope.row)" + @click="openDetail(scope.row)" v-hasPermi="['infra:config:query']" > 详情 @@ -101,14 +100,14 @@ </content-wrap> <!-- 表单弹窗:详情 --> - <login-log-detail ref="modalRef" /> + <LoginLogDetail ref="detailRef" /> </template> <script setup lang="ts" name="LoginLog"> import { DICT_TYPE } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' import * as LoginLogApi from '@/api/system/loginLog' -import LoginLogDetail from './detail.vue' +import LoginLogDetail from './LoginLogDetail.vue' const message = useMessage() // 消息弹窗 const loading = ref(true) // 列表的加载中 @@ -149,9 +148,9 @@ const resetQuery = () => { } /** 详情操作 */ -const modalRef = ref() -const openModal = (data: LoginLogApi.LoginLogVO) => { - modalRef.value.openModal(data) +const detailRef = ref() +const openDetail = (data: LoginLogApi.LoginLogVO) => { + detailRef.value.open(data) } /** 导出按钮操作 */ From 8b56388de0f927fa757f1dbe1b7a4464a82e8e2d Mon Sep 17 00:00:00 2001 From: xiaobai <2511883673@qq.com> Date: Fri, 24 Mar 2023 11:37:44 +0800 Subject: [PATCH 087/184] =?UTF-8?q?=E7=BF=BB=E5=86=99=E5=85=AC=E4=BC=97?= =?UTF-8?q?=E5=8F=B7=E7=AE=A1=E7=90=86-=E6=A0=87=E7=AD=BE=E7=AE=A1?= =?UTF-8?q?=E7=90=8620230324?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/mp/tag/index.ts | 3 +- src/views/mp/tag/form.vue | 7 +- src/views/mp/tag/tmp.vue | 286 -------------------------------------- 3 files changed, 3 insertions(+), 293 deletions(-) delete mode 100644 src/views/mp/tag/tmp.vue diff --git a/src/api/mp/tag/index.ts b/src/api/mp/tag/index.ts index e681e2e1..7fa61c75 100644 --- a/src/api/mp/tag/index.ts +++ b/src/api/mp/tag/index.ts @@ -25,9 +25,10 @@ export const deleteTag = (id) => { // 获得公众号标签 export const getTag = (id) => { - return request.get({ + const res = request.get({ url: '/mp/tag/get?id=' + id }) + return res } // 获得公众号标签分页 diff --git a/src/views/mp/tag/form.vue b/src/views/mp/tag/form.vue index 004a8589..47dcce38 100644 --- a/src/views/mp/tag/form.vue +++ b/src/views/mp/tag/form.vue @@ -7,9 +7,6 @@ label-width="80px" v-loading="formLoading" > - <el-form-item label="编号" prop="accountId"> - <el-input v-model="formData.accountId" /> - </el-form-item> <el-form-item label="标签名称" prop="name"> <el-input v-model="formData.name" placeholder="请输入标签名称" /> </el-form-item> @@ -52,9 +49,7 @@ const openModal = async (type: string, id?: number) => { if (id) { formLoading.value = true try { - console.log(id) - const res = await MpTagApi.getTag(id) - formData.value = res.data + formData.value = await MpTagApi.getTag(id) //调用该接口无数据返回,导致提交修改时无法上送id编号 } finally { formLoading.value = false } diff --git a/src/views/mp/tag/tmp.vue b/src/views/mp/tag/tmp.vue deleted file mode 100644 index 7ae3ad96..00000000 --- a/src/views/mp/tag/tmp.vue +++ /dev/null @@ -1,286 +0,0 @@ -<template> - <div class="app-container"> - <doc-alert title="公众号标签" url="https://doc.iocoder.cn/mp/tag/" /> - - <!-- 搜索工作栏 --> - <el-form - :model="queryParams" - ref="queryForm" - size="small" - :inline="true" - v-show="showSearch" - label-width="68px" - > - <el-form-item label="公众号" prop="accountId"> - <el-select v-model="queryParams.accountId" placeholder="请选择公众号"> - <el-option - v-for="item in accounts" - :key="parseInt(item.id)" - :label="item.name" - :value="parseInt(item.id)" - /> - </el-select> - </el-form-item> - <el-form-item label="标签名称" prop="name"> - <el-input - v-model="queryParams.name" - placeholder="请输入标签名称" - clearable - @keyup.enter.native="handleQuery" - /> - </el-form-item> - <el-form-item> - <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button> - <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button> - </el-form-item> - </el-form> - - <!-- 操作工具栏 --> - <el-row :gutter="10" class="mb8"> - <el-col :span="1.5"> - <el-button - type="primary" - plain - icon="el-icon-plus" - size="mini" - @click="handleAdd" - v-hasPermi="['mp:tag:create']" - >新增 - </el-button> - </el-col> - <el-col :span="1.5"> - <el-button - type="info" - plain - icon="el-icon-refresh" - size="mini" - @click="handleSync" - v-hasPermi="['mp:tag:sync']" - >同步 - </el-button> - </el-col> - <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> - </el-row> - - <!-- 列表 --> - <el-table v-loading="loading" :data="list"> - <el-table-column label="编号" align="center" prop="id" /> - <el-table-column label="标签名称" align="center" prop="name" /> - <el-table-column label="粉丝数" align="center" prop="count" /> - <el-table-column label="创建时间" align="center" prop="createTime" width="180"> - <template v-slot="scope"> - <span>{{ parseTime(scope.row.createTime) }}</span> - </template> - </el-table-column> - <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> - <template v-slot="scope"> - <el-button - size="mini" - type="text" - icon="el-icon-edit" - @click="handleUpdate(scope.row)" - v-hasPermi="['mp:tag:update']" - >修改 - </el-button> - <el-button - size="mini" - type="text" - icon="el-icon-delete" - @click="handleDelete(scope.row)" - v-hasPermi="['mp:tag:delete']" - >删除 - </el-button> - </template> - </el-table-column> - </el-table> - <!-- 分页组件 --> - <pagination - v-show="total > 0" - :total="total" - :page.sync="queryParams.pageNo" - :limit.sync="queryParams.pageSize" - @pagination="getList" - /> - - <!-- 对话框(添加 / 修改) --> - <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> - <el-form ref="form" :model="form" :rules="rules" label-width="80px"> - <el-form-item label="标签名称" prop="name"> - <el-input v-model="form.name" placeholder="请输入标签名称" /> - </el-form-item> - </el-form> - <div slot="footer" class="dialog-footer"> - <el-button type="primary" @click="submitForm">确 定</el-button> - <el-button @click="cancel">取 消</el-button> - </div> - </el-dialog> - </div> -</template> - -<script> -import { createTag, updateTag, deleteTag, getTag, getTagPage, syncTag } from '@/api/mp/tag' -import { getSimpleAccounts } from '@/api/mp/account' - -export default { - name: 'MpTag', - components: {}, - data() { - return { - // 遮罩层 - loading: true, - // 显示搜索条件 - showSearch: true, - // 总条数 - total: 0, - // 公众号标签列表 - list: [], - // 弹出层标题 - title: '', - // 是否显示弹出层 - open: false, - // 查询参数 - queryParams: { - accountId: null, - name: null - }, - // 表单参数 - form: { - accountId: undefined, - name: undefined - }, - // 表单校验 - rules: { - name: [{ required: true, message: '请输入标签名称', trigger: 'blur' }] - }, - - // 公众号账号列表 - accounts: [] - } - }, - created() { - getSimpleAccounts().then((response) => { - this.accounts = response.data - // 默认选中第一个 - if (this.accounts.length > 0) { - this.queryParams.accountId = this.accounts[0].id - } - // 加载数据 - this.getList() - }) - }, - methods: { - /** 查询列表 */ - getList() { - // 如果没有选中公众号账号,则进行提示。 - if (!this.queryParams.accountId) { - this.$message.error('未选中公众号,无法查询标签') - return false - } - - this.loading = false - // 处理查询参数 - let params = { ...this.queryParams } - // 执行查询 - getTagPage(params).then((response) => { - this.list = response.data.list - this.total = response.data.total - this.loading = false - }) - }, - /** 取消按钮 */ - cancel() { - this.open = false - this.reset() - }, - /** 表单重置 */ - reset() { - this.form = { - accountId: undefined, - name: undefined - } - this.resetForm('form') - }, - /** 搜索按钮操作 */ - handleQuery() { - this.queryParams.pageNo = 1 - this.getList() - }, - /** 重置按钮操作 */ - resetQuery() { - this.resetForm('queryForm') - // 默认选中第一个 - if (this.accounts.length > 0) { - this.queryParams.accountId = this.accounts[0].id - } - this.handleQuery() - }, - /** 新增按钮操作 */ - handleAdd() { - this.reset() - this.open = true - this.title = '添加公众号标签' - }, - /** 修改按钮操作 */ - handleUpdate(row) { - this.reset() - const id = row.id - getTag(id).then((response) => { - this.form = response.data - this.open = true - this.title = '修改公众号标签' - }) - }, - /** 提交按钮 */ - submitForm() { - this.$refs['form'].validate((valid) => { - if (!valid) { - return - } - this.form.accountId = this.queryParams.accountId - // 修改的提交 - if (this.form.id != null) { - updateTag(this.form).then((response) => { - this.$modal.msgSuccess('修改成功') - this.open = false - this.getList() - }) - return - } - // 添加的提交 - createTag(this.form).then((response) => { - this.$modal.msgSuccess('新增成功') - this.open = false - this.getList() - }) - }) - }, - /** 删除按钮操作 */ - handleDelete(row) { - const id = row.id - this.$modal - .confirm('是否确认删除公众号标签编号为"' + id + '"的数据项?') - .then(function () { - return deleteTag(id) - }) - .then(() => { - this.getList() - this.$modal.msgSuccess('删除成功') - }) - .catch(() => {}) - }, - /** 同步标签 */ - handleSync() { - const accountId = this.queryParams.accountId - this.$modal - .confirm('是否确认同步标签?') - .then(function () { - return syncTag(accountId) - }) - .then(() => { - this.$modal.msgSuccess('同步标签成功') - }) - .catch(() => {}) - } - } -} -</script> From e8dfe53053ccbd7990e326af1e93032886511d61 Mon Sep 17 00:00:00 2001 From: xiaobai <2511883673@qq.com> Date: Fri, 24 Mar 2023 11:43:37 +0800 Subject: [PATCH 088/184] =?UTF-8?q?=E7=BF=BB=E5=86=99=E5=85=AC=E4=BC=97?= =?UTF-8?q?=E5=8F=B7=E7=AE=A1=E7=90=86-=E6=A0=87=E7=AD=BE=E7=AE=A1?= =?UTF-8?q?=E7=90=8620230324?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/mp/tag/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/api/mp/tag/index.ts b/src/api/mp/tag/index.ts index 7fa61c75..e681e2e1 100644 --- a/src/api/mp/tag/index.ts +++ b/src/api/mp/tag/index.ts @@ -25,10 +25,9 @@ export const deleteTag = (id) => { // 获得公众号标签 export const getTag = (id) => { - const res = request.get({ + return request.get({ url: '/mp/tag/get?id=' + id }) - return res } // 获得公众号标签分页 From 6b194bb95f48043fd53050b222b7f311adc9c7d7 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Fri, 24 Mar 2023 23:07:51 +0800 Subject: [PATCH 089/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E7=9F=AD=E4=BF=A1=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/sms/smsChannel/index.ts | 2 +- src/api/system/sms/smsLog/index.ts | 23 +- src/components/RightToolbar/index.ts | 9 - src/components/RightToolbar/src/index.vue | 104 ----- src/components/index.ts | 2 - src/locales/zh-CN.ts | 9 +- src/types/auto-components.d.ts | 6 +- src/types/auto-imports.d.ts | 2 +- src/utils/dict.ts | 8 + src/utils/formatTime.ts | 12 +- .../sms/{smsChannel => channel}/form.vue | 274 ++++++------- .../sms/{smsChannel => channel}/index.vue | 0 src/views/system/sms/log/SmsLogDetail.vue | 87 ++++ src/views/system/sms/log/index.vue | 263 ++++++++++++ src/views/system/sms/smsLog/index.vue | 382 ------------------ .../sms/{smsTemplate => template}/form.vue | 0 .../sms/{smsTemplate => template}/index.vue | 2 +- 17 files changed, 516 insertions(+), 669 deletions(-) delete mode 100644 src/components/RightToolbar/index.ts delete mode 100644 src/components/RightToolbar/src/index.vue rename src/views/system/sms/{smsChannel => channel}/form.vue (97%) rename src/views/system/sms/{smsChannel => channel}/index.vue (100%) create mode 100644 src/views/system/sms/log/SmsLogDetail.vue create mode 100644 src/views/system/sms/log/index.vue delete mode 100644 src/views/system/sms/smsLog/index.vue rename src/views/system/sms/{smsTemplate => template}/form.vue (100%) rename src/views/system/sms/{smsTemplate => template}/index.vue (99%) diff --git a/src/api/system/sms/smsChannel/index.ts b/src/api/system/sms/smsChannel/index.ts index 7c8ccea9..ee0e6167 100644 --- a/src/api/system/sms/smsChannel/index.ts +++ b/src/api/system/sms/smsChannel/index.ts @@ -31,7 +31,7 @@ export const getSmsChannelPageApi = (params: SmsChannelPageReqVO) => { } // 获得短信渠道精简列表 -export function getSimpleSmsChannels() { +export function getSimpleSmsChannelList() { return request.get({ url: '/system/sms-channel/list-all-simple' }) } diff --git a/src/api/system/sms/smsLog/index.ts b/src/api/system/sms/smsLog/index.ts index 269b609d..3d54fac1 100644 --- a/src/api/system/sms/smsLog/index.ts +++ b/src/api/system/sms/smsLog/index.ts @@ -28,31 +28,12 @@ export interface SmsLogVO { createTime: Date | null } -export interface SmsLogPageReqVO extends PageParam { - channelId?: number | null - templateId?: number | null - mobile?: string - sendStatus?: number | null - sendTime?: Date[] - receiveStatus?: number | null - receiveTime?: Date[] -} -export interface SmsLogExportReqVO { - channelId?: number - templateId?: number - mobile?: string - sendStatus?: number - sendTime?: Date[] - receiveStatus?: number - receiveTime?: Date[] -} - // 查询短信日志列表 -export const getSmsLogPageApi = (params: SmsLogPageReqVO) => { +export const getSmsLogPage = (params: PageParam) => { return request.get({ url: '/system/sms-log/page', params }) } // 导出短信日志 -export const exportSmsLogApi = (params: SmsLogExportReqVO) => { +export const exportSmsLog = (params) => { return request.download({ url: '/system/sms-log/export-excel', params }) } diff --git a/src/components/RightToolbar/index.ts b/src/components/RightToolbar/index.ts deleted file mode 100644 index eb9d1112..00000000 --- a/src/components/RightToolbar/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import RightToolbar from './src/index.vue' - -export interface columnsType { - key?: number - label?: string - visible?: boolean -} - -export { RightToolbar } diff --git a/src/components/RightToolbar/src/index.vue b/src/components/RightToolbar/src/index.vue deleted file mode 100644 index 11e021dc..00000000 --- a/src/components/RightToolbar/src/index.vue +++ /dev/null @@ -1,104 +0,0 @@ -<template> - <div :style="style"> - <el-row justify="end"> - <el-tooltip - class="item" - effect="dark" - :content="showSearch ? '隐藏搜索' : '显示搜索'" - placement="top" - v-if="search" - > - <el-button circle @click="toggleSearch()"> - <Icon icon="ep:search" /> - </el-button> - </el-tooltip> - <el-tooltip class="item" effect="dark" content="刷新" placement="top"> - <el-button circle @click="refresh()"> - <Icon icon="ep:refresh" /> - </el-button> - </el-tooltip> - <el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="isColumns"> - <el-button circle @click="showColumn()"> - <Icon icon="ep:menu" /> - </el-button> - </el-tooltip> - </el-row> - <el-dialog :title="title" v-model="open" append-to-body> - <el-transfer - :titles="['显示', '隐藏']" - v-model="value" - :data="columns" - @change="dataChange" - /> - </el-dialog> - </div> -</template> -<script lang="ts" setup name="RightToolbar"> -import type { CSSProperties } from 'vue' -import type { columnsType } from '@/components/RightToolbar' -import { propTypes } from '@/utils/propTypes' -// 显隐数据 -const value = ref<number[]>([]) -// 弹出层标题 -const title = ref('显示/隐藏') -// 是否显示弹出层 -const open = ref(false) - -const props = defineProps({ - showSearch: propTypes.bool.def(true), - columns: { - type: Array as PropType<columnsType[]>, - default: () => [] - }, - search: propTypes.bool.def(true), - gutter: propTypes.number.def(10) -}) -const isColumns = computed(() => props.columns?.length > 0) -const style = computed((): CSSProperties => { - const ret: CSSProperties = {} - if (props.gutter) { - ret.marginRight = `${props.gutter / 2}px` - } - return ret -}) -const emit = defineEmits(['update:showSearch', 'queryTable']) -// 搜索 -const toggleSearch = () => { - emit('update:showSearch', !props.showSearch) -} -// 刷新 -const refresh = () => { - emit('queryTable') -} -// 右侧列表元素变化 -const dataChange = (data: number[]) => { - props.columns.forEach((item) => { - const key: number = item.key! - item.visible = !data.includes(key) - }) -} -// 打开显隐列dialog -const showColumn = () => { - open.value = true -} -// 显隐列初始默认隐藏列 -const init = () => { - props.columns.forEach((item, index) => { - if (item.visible === false) { - value.value.push(index) - } - }) -} -init() -</script> -<style lang="scss" scoped> -:deep(.el-transfer__button) { - border-radius: 50%; - padding: 12px; - display: block; - margin-left: 0px; -} -:deep(.el-transfer__button:first-child) { - margin-bottom: 10px; -} -</style> diff --git a/src/components/index.ts b/src/components/index.ts index 97c2b4b0..19b2aac6 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -9,7 +9,6 @@ import { XButton, XTextButton } from '@/components/XButton' import { DictTag } from '@/components/DictTag' import { ContentWrap } from '@/components/ContentWrap' import { Descriptions } from '@/components/Descriptions' -import { RightToolbar } from '@/components/RightToolbar' export const setupGlobCom = (app: App<Element>): void => { app.component('Icon', Icon) @@ -23,5 +22,4 @@ export const setupGlobCom = (app: App<Element>): void => { app.component('DictTag', DictTag) app.component('ContentWrap', ContentWrap) app.component('Descriptions', Descriptions) - app.component('RightToolbar', RightToolbar) } diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 0a1e9e19..6f46f1ab 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -303,14 +303,7 @@ export default { dialog: { dialog: '弹窗', open: '打开', - close: '关闭', - sms: { - template: { - addTitle: '添加短信模板', - updtaeTitle: '修改短信模板', - sendSms: '发送短信' - } - } + close: '关闭' }, sys: { api: { diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index 5c679fa9..2b199f1a 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -1,5 +1,7 @@ -// generated by unplugin-vue-components -// We suggest you to commit this file into source control +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-components // Read more: https://github.com/vuejs/core/pull/3399 import '@vue/runtime-core' diff --git a/src/types/auto-imports.d.ts b/src/types/auto-imports.d.ts index 2c68c6ce..75cf16d9 100644 --- a/src/types/auto-imports.d.ts +++ b/src/types/auto-imports.d.ts @@ -72,5 +72,5 @@ declare global { // for type re-export declare global { // @ts-ignore - export type { Component,ComponentPublicInstance,ComputedRef,InjectionKey,PropType,Ref,VNode } from 'vue' + export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue' } diff --git a/src/utils/dict.ts b/src/utils/dict.ts index 4f5d63fb..395f7157 100644 --- a/src/utils/dict.ts +++ b/src/utils/dict.ts @@ -69,6 +69,13 @@ export const getDictObj = (dictType: string, value: any) => { } }) } + +/** + * 获得字典数据的文本展示 + * + * @param dictType 字典类型 + * @param value 字典数据的值 + */ export const getDictLabel = (dictType: string, value: any) => { const dictOptions: DictDataType[] = getDictOptions(dictType) const dictLabel = ref('') @@ -79,6 +86,7 @@ export const getDictLabel = (dictType: string, value: any) => { }) return dictLabel.value } + export enum DICT_TYPE { USER_TYPE = 'user_type', COMMON_STATUS = 'common_status', diff --git a/src/utils/formatTime.ts b/src/utils/formatTime.ts index 35c783a6..ec7f3744 100644 --- a/src/utils/formatTime.ts +++ b/src/utils/formatTime.ts @@ -11,9 +11,19 @@ import dayjs from 'dayjs' * @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ" * @returns 返回拼接后的时间字符串 */ -export function formatDate(date: Date, format: string): string { +export function formatDate(date: Date, format?: string): string { + // 日期不存在,则返回空 + if (!date) { + return '' + } + // 日期存在,则进行格式化 + if (format === undefined) { + format = 'YYYY-MM-DD HH:mm:ss' + } return dayjs(date).format(format) } + +// TODO 芋艿:稍后去掉 // 日期格式化 export function parseTime(time: any, pattern?: string) { if (arguments.length === 0 || !time) { diff --git a/src/views/system/sms/smsChannel/form.vue b/src/views/system/sms/channel/form.vue similarity index 97% rename from src/views/system/sms/smsChannel/form.vue rename to src/views/system/sms/channel/form.vue index 7c20a90d..9c3881d8 100644 --- a/src/views/system/sms/smsChannel/form.vue +++ b/src/views/system/sms/channel/form.vue @@ -1,137 +1,137 @@ -<template> - <Dialog :title="modelTitle" v-model="modelVisible"> - <el-form ref="formRef" :model="form" :rules="rules" label-width="130px" v-loading="formLoading"> - <el-form-item label="短信签名" prop="signature"> - <el-input v-model="form.signature" placeholder="请输入短信签名" /> - </el-form-item> - <el-form-item label="渠道编码" prop="code"> - <el-select v-model="form.code" placeholder="请选择渠道编码" clearable> - <el-option - v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE)" - :key="dict.value" - :label="dict.label" - :value="dict.value" - /> - </el-select> - </el-form-item> - <el-form-item label="启用状态"> - <el-radio-group v-model="form.status"> - <el-radio - v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" - :key="dict.value" - :label="parseInt(dict.value)" - >{{ dict.label }}</el-radio - > - </el-radio-group> - </el-form-item> - <el-form-item label="备注" prop="remark"> - <el-input v-model="form.remark" placeholder="请输入备注" /> - </el-form-item> - <el-form-item label="短信 API 的账号" prop="apiKey"> - <el-input v-model="form.apiKey" placeholder="请输入短信 API 的账号" /> - </el-form-item> - <el-form-item label="短信 API 的密钥" prop="apiSecret"> - <el-input v-model="form.apiSecret" placeholder="请输入短信 API 的密钥" /> - </el-form-item> - <el-form-item label="短信发送回调 URL" prop="callbackUrl"> - <el-input v-model="form.callbackUrl" placeholder="请输入短信发送回调 URL" /> - </el-form-item> - </el-form> - <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> - </template> - </Dialog> -</template> -<script setup lang="ts"> -import { DICT_TYPE, getDictOptions } from '@/utils/dict' -import * as SmsChannelApi from '@/api/system/sms/smsChannel' - -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 - -const modelVisible = ref(false) // 弹窗的是否展示 -const modelTitle = ref('') // 弹窗的标题 -const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 -const formType = ref('') // 表单的类型:create - 新增;update - 修改 -const form = ref({ - id: undefined, - signature: '', - code: '', - status: '', - remark: '', - apiKey: '', - apiSecret: '', - callbackUrl: '' -}) -const rules = reactive({ - signature: [{ required: true, message: '短信签名不能为空', trigger: 'blur' }], - code: [{ required: true, message: '渠道编码不能为空', trigger: 'blur' }], - status: [{ required: true, message: '启用状态不能为空', trigger: 'blur' }], - apiKey: [{ required: true, message: '短信 API 的账号不能为空', trigger: 'blur' }] -}) -const formRef = ref() // 表单 Ref - -/** 打开弹窗 */ -const openModal = async (type: string, id?: number) => { - modelVisible.value = true - modelTitle.value = t('action.' + type) - formType.value = type - resetForm() - // 修改时,设置数据 - if (id) { - formLoading.value = true - try { - form.value = await SmsChannelApi.getSmsChannelApi(id) - console.log(form) - } finally { - formLoading.value = false - } - } -} - -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 - -/** 提交表单 */ -const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 -const submitForm = async () => { - // 校验表单 - if (!formRef) return - const valid = await formRef.value.validate() - if (!valid) return - // 提交请求 - formLoading.value = true - try { - const data = unref(formRef)?.formModel as SmsChannelApi.SmsChannelVO - if (formType.value === 'create') { - await SmsChannelApi.createSmsChannelApi(data) - message.success(t('common.createSuccess')) - } else { - await SmsChannelApi.updateSmsChannelApi(data) - message.success(t('common.updateSuccess')) - } - modelVisible.value = false - // 发送操作成功的事件 - emit('success') - } finally { - formLoading.value = false - } -} - -/** 重置表单 */ -const resetForm = () => { - form.value = { - id: undefined, - signature: '', - code: '', - status: '', - remark: '', - apiKey: '', - apiSecret: '', - callbackUrl: '' - } - formRef.value?.resetFields() -} -</script> +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form ref="formRef" :model="form" :rules="rules" label-width="130px" v-loading="formLoading"> + <el-form-item label="短信签名" prop="signature"> + <el-input v-model="form.signature" placeholder="请输入短信签名" /> + </el-form-item> + <el-form-item label="渠道编码" prop="code"> + <el-select v-model="form.code" placeholder="请选择渠道编码" clearable> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="启用状态"> + <el-radio-group v-model="form.status"> + <el-radio + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="parseInt(dict.value)" + >{{ dict.label }}</el-radio + > + </el-radio-group> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="form.remark" placeholder="请输入备注" /> + </el-form-item> + <el-form-item label="短信 API 的账号" prop="apiKey"> + <el-input v-model="form.apiKey" placeholder="请输入短信 API 的账号" /> + </el-form-item> + <el-form-item label="短信 API 的密钥" prop="apiSecret"> + <el-input v-model="form.apiSecret" placeholder="请输入短信 API 的密钥" /> + </el-form-item> + <el-form-item label="短信发送回调 URL" prop="callbackUrl"> + <el-input v-model="form.callbackUrl" placeholder="请输入短信发送回调 URL" /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import * as SmsChannelApi from '@/api/system/sms/smsChannel' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const form = ref({ + id: undefined, + signature: '', + code: '', + status: '', + remark: '', + apiKey: '', + apiSecret: '', + callbackUrl: '' +}) +const rules = reactive({ + signature: [{ required: true, message: '短信签名不能为空', trigger: 'blur' }], + code: [{ required: true, message: '渠道编码不能为空', trigger: 'blur' }], + status: [{ required: true, message: '启用状态不能为空', trigger: 'blur' }], + apiKey: [{ required: true, message: '短信 API 的账号不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + form.value = await SmsChannelApi.getSmsChannelApi(id) + console.log(form) + } finally { + formLoading.value = false + } + } +} + +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = unref(formRef)?.formModel as SmsChannelApi.SmsChannelVO + if (formType.value === 'create') { + await SmsChannelApi.createSmsChannelApi(data) + message.success(t('common.createSuccess')) + } else { + await SmsChannelApi.updateSmsChannelApi(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + form.value = { + id: undefined, + signature: '', + code: '', + status: '', + remark: '', + apiKey: '', + apiSecret: '', + callbackUrl: '' + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/system/sms/smsChannel/index.vue b/src/views/system/sms/channel/index.vue similarity index 100% rename from src/views/system/sms/smsChannel/index.vue rename to src/views/system/sms/channel/index.vue diff --git a/src/views/system/sms/log/SmsLogDetail.vue b/src/views/system/sms/log/SmsLogDetail.vue new file mode 100644 index 00000000..736d0b8e --- /dev/null +++ b/src/views/system/sms/log/SmsLogDetail.vue @@ -0,0 +1,87 @@ +<template> + <Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="500" width="800"> + <el-descriptions border :column="1"> + <el-descriptions-item label="日志主键" min-width="120"> + {{ detailData.id }} + </el-descriptions-item> + <el-descriptions-item label="短信渠道"> + {{ channelList.find((channel) => channel.id === detailData.channelId)?.signature }} + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="detailData.channelCode" /> + </el-descriptions-item> + <el-descriptions-item label="短信模板"> + {{ detailData.templateId }} | {{ detailData.templateCode }} + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE" :value="detailData.templateType" /> + </el-descriptions-item> + <el-descriptions-item label="API 的模板编号"> + {{ detailData.apiTemplateId }} + </el-descriptions-item> + <el-descriptions-item label="用户信息"> + {{ detailData.mobile }} + <span v-if="detailData.userType && detailData.userId"> + <dict-tag :type="DICT_TYPE.USER_TYPE" :value="detailData.userType" /> + ({{ detailData.userId }}) + </span> + </el-descriptions-item> + <el-descriptions-item label="短信内容"> + {{ detailData.templateContent }} + </el-descriptions-item> + <el-descriptions-item label="短信参数"> + {{ detailData.templateParams }} + </el-descriptions-item> + <el-descriptions-item label="创建时间"> + {{ formatDate(detailData.createTime) }} + </el-descriptions-item> + <el-descriptions-item label="发送状态"> + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_SEND_STATUS" :value="detailData.sendStatus" /> + </el-descriptions-item> + <el-descriptions-item label="发送时间"> + {{ formatDate(detailData.sendTime) }} + </el-descriptions-item> + <el-descriptions-item label="发送结果"> + {{ detailData.sendCode }} | {{ detailData.sendMsg }} + </el-descriptions-item> + <el-descriptions-item label="API 发送结果"> + {{ detailData.apiSendCode }} | {{ detailData.apiSendMsg }} + </el-descriptions-item> + <el-descriptions-item label="API 短信编号"> + {{ detailData.apiSerialNo }} + </el-descriptions-item> + <el-descriptions-item label="API 请求编号"> + {{ detailData.apiRequestId }} + </el-descriptions-item> + <el-descriptions-item label="API 接收状态"> + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS" :value="detailData.receiveStatus" /> + {{ formatDate(detailData.receiveTime) }} + </el-descriptions-item> + <el-descriptions-item label="API 接收结果"> + {{ detailData.apiReceiveCode }} | {{ detailData.apiReceiveMsg }} + </el-descriptions-item> + </el-descriptions> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE } from '@/utils/dict' +import { formatDate } from '@/utils/formatTime' +import * as SmsLogApi from '@/api/system/sms/smsLog' +import * as SmsChannelApi from '@/api/system/sms/smsChannel' + +const modelVisible = ref(false) // 弹窗的是否展示 +const detailLoading = ref(false) // 表单的加载中 +const detailData = ref() // 详情数据 +const channelList = ref([]) // 短信渠道列表 + +/** 打开弹窗 */ +const open = async (data: SmsLogApi.SmsLogVO) => { + modelVisible.value = true + // 设置数据 + detailLoading.value = true + try { + detailData.value = data + } finally { + detailLoading.value = false + } + // 加载渠道列表 + channelList.value = await SmsChannelApi.getSimpleSmsChannelList() +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 +</script> diff --git a/src/views/system/sms/log/index.vue b/src/views/system/sms/log/index.vue new file mode 100644 index 00000000..a0acdfaa --- /dev/null +++ b/src/views/system/sms/log/index.vue @@ -0,0 +1,263 @@ +<template> + <ContentWrap> + <!-- 搜索工作栏 --> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="100px" + > + <el-form-item label="手机号" prop="mobile"> + <el-input + v-model="queryParams.mobile" + placeholder="请输入手机号" + clearable + @keyup.enter="handleQuery" + class="!w-240px" + /> + </el-form-item> + <el-form-item label="短信渠道" prop="channelId"> + <el-select + v-model="queryParams.channelId" + placeholder="请选择短信渠道" + clearable + class="!w-240px" + > + <el-option + v-for="channel in channelList" + :key="channel.id" + :value="channel.id" + :label=" + channel.signature + + `【 ${getDictLabel(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, channel.code)}】` + " + /> + </el-select> + </el-form-item> + <el-form-item label="模板编号" prop="templateId"> + <el-input + v-model="queryParams.templateId" + placeholder="请输入模板编号" + clearable + @keyup.enter="handleQuery" + class="!w-240px" + /> + </el-form-item> + <el-form-item label="发送状态" prop="sendStatus"> + <el-select + v-model="queryParams.sendStatus" + placeholder="请选择发送状态" + clearable + class="!w-240px" + > + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_SMS_SEND_STATUS)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="发送时间" prop="sendTime"> + <el-date-picker + v-model="queryParams.sendTime" + value-format="YYYY-MM-DD HH:mm:ss" + type="daterange" + start-placeholder="开始日期" + end-placeholder="结束日期" + class="!w-240px" + /> + </el-form-item> + <el-form-item label="接收状态" prop="receiveStatus"> + <el-select + v-model="queryParams.receiveStatus" + placeholder="请选择接收状态" + clearable + class="!w-240px" + > + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="接收时间" prop="receiveTime"> + <el-date-picker + v-model="queryParams.receiveTime" + value-format="YYYY-MM-DD HH:mm:ss" + type="daterange" + start-placeholder="开始日期" + end-placeholder="结束日期" + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['infra:config:export']" + > + <Icon icon="ep:download" class="mr-5px" /> 导出 + </el-button> + </el-form-item> + </el-form> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list"> + <el-table-column label="编号" align="center" prop="id" /> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column label="手机号" align="center" prop="mobile" width="120"> + <template #default="scope"> + <div>{{ scope.row.mobile }}</div> + <div v-if="scope.row.userType && scope.row.userId"> + <dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" /> + {{ '(' + scope.row.userId + ')' }} + </div> + </template> + </el-table-column> + <el-table-column label="短信内容" align="center" prop="templateContent" width="300" /> + <el-table-column label="发送状态" align="center" width="180"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_SEND_STATUS" :value="scope.row.sendStatus" /> + <div>{{ formatDate(scope.row.sendTime) }}</div> + </template> + </el-table-column> + <el-table-column label="接收状态" align="center" width="180"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS" :value="scope.row.receiveStatus" /> + <div>{{ formatDate(scope.row.receiveTime) }}</div> + </template> + </el-table-column> + <el-table-column label="短信渠道" align="center" width="120"> + <template #default="scope"> + <div> + {{ channelList.find((channel) => channel.id === scope.row.channelId)?.signature }} + </div> + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="scope.row.channelCode" /> + </template> + </el-table-column> + <el-table-column label="模板编号" align="center" prop="templateId" /> + <el-table-column label="短信类型" align="center" prop="templateType"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE" :value="scope.row.templateType" /> + </template> + </el-table-column> + <el-table-column label="操作" align="center" fixed="right" class-name="fixed-width"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openDetail(scope.row)" + v-hasPermi="['system:sms-log:query']" + > + 详情 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </ContentWrap> + + <!-- 表单弹窗:详情 --> + <SmsLogDetail ref="detailRef" /> +</template> +<script setup lang="ts" name="smsLog"> +import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict' +import { dateFormatter, formatDate } from '@/utils/formatTime' +import download from '@/utils/download' +import * as SmsChannelApi from '@/api/system/sms/smsChannel' +import * as SmsLogApi from '@/api/system/sms/smsLog' +import SmsLogDetail from './SmsLogDetail.vue' +const message = useMessage() // 消息弹窗 + +const loading = ref(false) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryFormRef = ref() // 搜索的表单 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + channelId: null, + templateId: null, + mobile: '', + sendStatus: null, + receiveStatus: null, + sendTime: [], + receiveTime: [] +}) +const exportLoading = ref(false) // 导出的加载中 +const channelList = ref([]) // 短信渠道列表 + +/** 查询列表 */ +const getList = async () => { + loading.value = true + try { + const data = await SmsLogApi.getSmsLogPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await SmsLogApi.exportSmsLog(queryParams) + download.excel(data, '短信日志.xls') + } catch { + } finally { + exportLoading.value = false + } +} + +/** 详情操作 */ +const detailRef = ref() +const openDetail = (data: SmsLogApi.SmsLogVO) => { + detailRef.value.open(data) +} + +/** 初始化 **/ +onMounted(async () => { + await getList() + // 加载渠道列表 + channelList.value = await SmsChannelApi.getSimpleSmsChannelList() +}) +</script> diff --git a/src/views/system/sms/smsLog/index.vue b/src/views/system/sms/smsLog/index.vue deleted file mode 100644 index f4a0ca9f..00000000 --- a/src/views/system/sms/smsLog/index.vue +++ /dev/null @@ -1,382 +0,0 @@ -<template> - <content-wrap> - <!-- 搜索工作栏 --> - <el-form - :model="queryParams" - ref="queryForm" - :inline="true" - v-show="showSearch" - label-width="100px" - > - <el-form-item label="手机号" prop="mobile"> - <el-input - v-model="queryParams.mobile" - placeholder="请输入手机号" - clearable - @keyup.enter="handleQuery" - /> - </el-form-item> - <el-form-item label="短信渠道" prop="channelId"> - <el-select v-model="queryParams.channelId" placeholder="请选择短信渠道" clearable> - <el-option - v-for="channel in channelOptions" - :key="channel.id" - :value="channel.id" - :label=" - channel.signature + optionLabel(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, channel.code) - " - /> - </el-select> - </el-form-item> - <el-form-item label="模板编号" prop="templateId"> - <el-input - v-model="queryParams.templateId" - placeholder="请输入模板编号" - clearable - @keyup.enter="handleQuery" - /> - </el-form-item> - <el-form-item label="发送状态" prop="sendStatus"> - <el-select v-model="queryParams.sendStatus" placeholder="请选择发送状态" clearable> - <el-option - v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_SMS_SEND_STATUS)" - :key="dict.value" - :label="dict.label" - :value="dict.value" - /> - </el-select> - </el-form-item> - <el-form-item label="发送时间" prop="sendTime"> - <el-date-picker - v-model="queryParams.sendTime" - style="width: 240px" - value-format="YYYY-MM-DD HH:mm:ss" - type="daterange" - range-separator="-" - start-placeholder="开始日期" - end-placeholder="结束日期" - /> - </el-form-item> - <el-form-item label="接收状态" prop="receiveStatus"> - <el-select v-model="queryParams.receiveStatus" placeholder="请选择接收状态" clearable> - <el-option - v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS)" - :key="dict.value" - :label="dict.label" - :value="dict.value" - /> - </el-select> - </el-form-item> - <el-form-item label="接收时间" prop="receiveTime"> - <el-date-picker - v-model="queryParams.receiveTime" - style="width: 240px" - value-format="YYYY-MM-DD HH:mm:ss" - type="daterange" - range-separator="-" - start-placeholder="开始日期" - end-placeholder="结束日期" - /> - </el-form-item> - <el-form-item> - <el-button type="primary" @click="handleQuery" - ><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button - > - <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> - </el-form-item> - </el-form> - <!-- 操作工具栏 --> - <el-row class="mb-10px"> - <el-col :span="12"> - <el-row :gutter="10"> - <el-col :span="1.5"> - <el-button - type="success" - plain - @click="handleExport" - :loading="exportLoading" - v-hasPermi="['system:sms-log:export']" - > - <Icon icon="ep:download" class="mr-5px" /> 导出 - </el-button> - </el-col> - </el-row> - </el-col> - <el-col :span="12"> - <right-toolbar v-model:showSearch="showSearch" @query-table="getList" /> - </el-col> - </el-row> - <!-- 列表 --> - <el-table v-loading="loading" :data="smsLoglist"> - <el-table-column label="编号" align="center" prop="id" /> - <el-table-column label="创建时间" align="center" prop="createTime" width="180"> - <template #default="scope"> - <span>{{ parseTime(scope.row.createTime) }}</span> - </template> - </el-table-column> - <el-table-column label="手机号" align="center" prop="mobile" width="120"> - <template #default="scope"> - <div>{{ scope.row.mobile }}</div> - <div v-if="scope.row.userType && scope.row.userId"> - <dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" />{{ - '(' + scope.row.userId + ')' - }} - </div> - </template> - </el-table-column> - <el-table-column label="短信内容" align="center" prop="templateContent" width="300" /> - <el-table-column label="发送状态" align="center" width="180"> - <template #default="scope"> - <dict-tag :type="DICT_TYPE.SYSTEM_SMS_SEND_STATUS" :value="scope.row.sendStatus" /> - <div>{{ parseTime(scope.row.sendTime) }}</div> - </template> - </el-table-column> - <el-table-column label="接收状态" align="center" width="180"> - <template #default="scope"> - <dict-tag :type="DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS" :value="scope.row.receiveStatus" /> - <div>{{ parseTime(scope.row.receiveTime) }}</div> - </template> - </el-table-column> - <el-table-column label="短信渠道" align="center" width="120"> - <template #default="scope"> - <div>{{ formatChannelSignature(scope.row.channelId) }}</div> - <dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="scope.row.channelCode" /> - </template> - </el-table-column> - <el-table-column label="模板编号" align="center" prop="templateId" /> - <el-table-column label="短信类型" align="center" prop="templateType"> - <template #default="scope"> - <dict-tag :type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE" :value="scope.row.templateType" /> - </template> - </el-table-column> - <el-table-column - label="操作" - align="center" - fixed="right" - class-name="small-padding fixed-width" - > - <template #default="scope"> - <el-button - link - type="primary" - @click="handleView(scope.row)" - v-hasPermi="['system:sms-log:query']" - ><Icon icon="ep:view" class="mr-3px" />详情</el-button - > - </template> - </el-table-column> - </el-table> - <!-- 分页 --> - <Pagination - :total="total" - v-model:page="queryParams.pageNo" - v-model:limit="queryParams.pageSize" - @pagination="getList" - /> - <!-- 短信日志详细 --> - <Dialog title="短信日志详情" v-model="open"> - <el-descriptions border :column="1"> - <el-descriptions-item label-align="right" width="50px" label="日志主键:"> - {{ formData.id }} - </el-descriptions-item> - <el-descriptions-item label-align="right" width="50px" label="短信渠道:"> - {{ formatChannelSignature(formData.channelId) }} - <dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="formData.channelCode" /> - </el-descriptions-item> - <el-descriptions-item label-align="right" width="50px" label="短信模板:"> - {{ formData.templateId }} | {{ formData.templateCode }} - <dict-tag :type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE" :value="formData.templateType" /> - </el-descriptions-item> - <el-descriptions-item label-align="right" width="50px" label="API 的模板编号:"> - {{ formData.apiTemplateId }} - </el-descriptions-item> - <el-descriptions-item label-align="right" width="50px" label="用户信息:"> - {{ formData.mobile }} - <span v-if="formData.userType && formData.userId"> - <dict-tag :type="DICT_TYPE.USER_TYPE" :value="formData.userType" /> - ({{ formData.userId }}) - </span> - </el-descriptions-item> - <el-descriptions-item label-align="right" width="50px" label="短信内容:"> - {{ formData.templateContent }} - </el-descriptions-item> - <el-descriptions-item label-align="right" width="50px" label="短信参数:"> - {{ formData.templateParams }} - </el-descriptions-item> - <el-descriptions-item label-align="right" width="50px" label="创建时间:"> - {{ parseTime(formData.createTime) }} - </el-descriptions-item> - <el-descriptions-item label-align="right" width="50px" label="发送状态:"> - <dict-tag :type="DICT_TYPE.SYSTEM_SMS_SEND_STATUS" :value="formData.sendStatus" /> - </el-descriptions-item> - <el-descriptions-item label-align="right" width="50px" label="发送时间:"> - {{ parseTime(formData.sendTime) }} - </el-descriptions-item> - <el-descriptions-item label-align="right" width="50px" label="发送结果:"> - {{ formData.sendCode }} | {{ formData.sendMsg }} - </el-descriptions-item> - <el-descriptions-item label-align="right" width="50px" label="API 发送结果:"> - {{ formData.apiSendCode }} | {{ formData.apiSendMsg }} - </el-descriptions-item> - <el-descriptions-item label-align="right" width="50px" label="API 短信编号:"> - {{ formData.apiSerialNo }} - </el-descriptions-item> - <el-descriptions-item label-align="right" width="50px" label="API 请求编号:"> - {{ formData.apiRequestId }} - </el-descriptions-item> - <el-descriptions-item label-align="right" width="50px" label="接收状态:"> - <dict-tag :type="DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS" :value="formData.receiveStatus" /> - </el-descriptions-item> - <el-descriptions-item label-align="right" width="50px" label="接收时间:"> - {{ parseTime(formData.receiveTime) }} - </el-descriptions-item> - <el-descriptions-item label-align="right" width="50px" label="API 接收结果:"> - {{ formData.apiReceiveCode }} | {{ formData.apiReceiveMsg }} - </el-descriptions-item> - </el-descriptions> - <template #footer> - <el-button @click="open = false">关 闭</el-button> - </template> - </Dialog> - </content-wrap> -</template> -<script setup lang="ts" name="smsLog"> -import { DICT_TYPE, getDictOptions, getDictLabel } from '@/utils/dict' -import download from '@/utils/download' -import { parseTime } from '@/utils/formatTime' -import * as SmsChannelApi from '@/api/system/sms/smsChannel' -import * as SmsLogApi from '@/api/system/sms/smsLog' - -const message = useMessage() // 消息弹窗 - -// ================= 表单相关================ -const showSearch = ref(true) // 显示搜索条件 -const queryForm = ref() // queryFormRef -// 查询参数 -const queryParams = ref<SmsLogApi.SmsLogPageReqVO>({ - pageNo: 1, - pageSize: 10, - channelId: null, - templateId: null, - mobile: '', - sendStatus: null, - receiveStatus: null, - sendTime: [], - receiveTime: [] -}) -// 短信渠道列表 -const channelOptions = ref<SmsChannelApi.SmsChannelListVO[]>([]) -onMounted(() => { - SmsChannelApi.getSimpleSmsChannels().then((res) => { - channelOptions.value = res - }) -}) -const optionLabel = computed( - () => (type: string, code: string) => `【${getDictLabel(type, code)}】` -) -/** 格式化短信渠道 */ -const formatChannelSignature = (channelId: number | null) => { - channelOptions.value.forEach((item) => { - if (item.id === channelId) { - return item.signature - } - }) - return '找不到签名:' + channelId -} -// ================= 表单相关操作================ -/** 搜索按钮操作 */ -const handleQuery = () => { - queryParams.value.pageNo = 1 - getList() -} -/** 重置按钮操作 */ -const resetQuery = () => { - resetForm() - handleQuery() -} -/** 重置搜索表单 */ -const resetForm = () => { - queryParams.value = { - pageNo: 1, - pageSize: 10, - channelId: null, - templateId: null, - mobile: '', - sendStatus: null, - receiveStatus: null, - sendTime: [], - receiveTime: [] - } - queryForm.value?.resetFields() -} -// 导出遮罩层 -const exportLoading = ref(false) -/** 导出按钮操作 */ -const handleExport = () => { - // 处理查询参数 - let params = queryParams.value as SmsLogApi.SmsLogExportReqVO - // 执行导出 - message - .confirm('是否确认导出所有短信日志数据项?') - .then(() => { - exportLoading.value = true - return SmsLogApi.exportSmsLogApi(params) - }) - .then((response) => { - download.excel(response, '短信日志.xls') - exportLoading.value = false - }) - .catch(() => {}) -} -// ================== 表格 ==================== -// 总条数 -const total = ref(0) -// 短信日志列表 -const smsLoglist = ref([]) -const loading = ref(false) -/** 查询列表 */ -const getList = () => { - loading.value = true - // 执行查询 - SmsLogApi.getSmsLogPageApi(queryParams.value).then((response) => { - smsLoglist.value = response.list - total.value = response.total - loading.value = false - }) -} -// ================== 详情 ==================== -const open = ref(false) -const formData = ref<SmsLogApi.SmsLogVO>({ - id: null, - channelId: null, - channelCode: '', - templateId: null, - templateCode: '', - templateType: null, - templateContent: '', - templateParams: null, - apiTemplateId: '', - mobile: '', - userId: null, - userType: null, - sendStatus: null, - sendTime: null, - sendCode: null, - sendMsg: '', - apiSendCode: '', - apiSendMsg: '', - apiRequestId: '', - apiSerialNo: '', - receiveStatus: null, - receiveTime: null, - apiReceiveCode: '', - apiReceiveMsg: '', - createTime: null -}) -/** 详细按钮操作 */ -const handleView = (row: SmsLogApi.SmsLogVO) => { - formData.value = row - open.value = true -} -getList() -</script> diff --git a/src/views/system/sms/smsTemplate/form.vue b/src/views/system/sms/template/form.vue similarity index 100% rename from src/views/system/sms/smsTemplate/form.vue rename to src/views/system/sms/template/form.vue diff --git a/src/views/system/sms/smsTemplate/index.vue b/src/views/system/sms/template/index.vue similarity index 99% rename from src/views/system/sms/smsTemplate/index.vue rename to src/views/system/sms/template/index.vue index ed934c0f..ffdd4240 100644 --- a/src/views/system/sms/smsTemplate/index.vue +++ b/src/views/system/sms/template/index.vue @@ -275,7 +275,7 @@ const resetForm = () => { // 短信渠道 const channelOptions = ref<SmsChannelApi.SmsChannelListVO[]>([]) onMounted(() => { - SmsChannelApi.getSimpleSmsChannels().then((res) => { + SmsChannelApi.getSimpleSmsChannelList().then((res) => { channelOptions.value = res }) }) From 21cce9812debdf51d7ca82b0346eddd2453660ae Mon Sep 17 00:00:00 2001 From: syd <syidong@aliyun.com> Date: Fri, 24 Mar 2023 23:23:14 +0800 Subject: [PATCH 090/184] =?UTF-8?q?update:=20=E6=95=8F=E6=84=9F=E8=AF=8D?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E6=8F=90=E4=BA=A4TODO=20=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/sensitiveWord/index.ts | 12 ++- src/types/auto-components.d.ts | 14 +--- src/types/auto-imports.d.ts | 2 +- src/views/system/sensitiveWord/form.vue | 5 +- src/views/system/sensitiveWord/index.vue | 18 +++- src/views/system/sensitiveWord/testForm.vue | 92 +++++++++++++++++++++ 6 files changed, 126 insertions(+), 17 deletions(-) create mode 100644 src/views/system/sensitiveWord/testForm.vue diff --git a/src/api/system/sensitiveWord/index.ts b/src/api/system/sensitiveWord/index.ts index 7da2c28e..08078ba6 100644 --- a/src/api/system/sensitiveWord/index.ts +++ b/src/api/system/sensitiveWord/index.ts @@ -1,4 +1,5 @@ import request from '@/config/axios' +import qs from 'qs' export interface SensitiveWordVO { id: number @@ -23,6 +24,11 @@ export interface SensitiveWordExportReqVO { createTime?: Date[] } +export interface SensitiveWordTestReqVO { + text: string + tag: string[] +} + // 查询敏感词列表 export const getSensitiveWordPage = (params: SensitiveWordPageReqVO) => { return request.get({ url: '/system/sensitive-word/page', params }) @@ -59,6 +65,8 @@ export const getSensitiveWordTags = () => { } // 获得文本所包含的不合法的敏感词数组 -export const validateText = (id: number) => { - return request.get({ url: '/system/sensitive-word/validate-text?' + id }) +export const validateText = (query: SensitiveWordTestReqVO) => { + return request.get({ + url: '/system/sensitive-word/validate-text?' + qs.stringify(query, { arrayFormat: 'repeat' }) + }) } diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index 5c679fa9..374893bb 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -1,5 +1,7 @@ -// generated by unplugin-vue-components -// We suggest you to commit this file into source control +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-components // Read more: https://github.com/vuejs/core/pull/3399 import '@vue/runtime-core' @@ -21,13 +23,11 @@ declare module '@vue/runtime-core' { DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default'] Echart: typeof import('./../components/Echart/src/Echart.vue')['default'] Editor: typeof import('./../components/Editor/src/Editor.vue')['default'] - ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElBadge: typeof import('element-plus/es')['ElBadge'] ElButton: typeof import('element-plus/es')['ElButton'] ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] ElCard: typeof import('element-plus/es')['ElCard'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] - ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] ElCol: typeof import('element-plus/es')['ElCol'] ElCollapse: typeof import('element-plus/es')['ElCollapse'] ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] @@ -54,30 +54,24 @@ declare module '@vue/runtime-core' { ElIcon: typeof import('element-plus/es')['ElIcon'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] - ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElLink: typeof import('element-plus/es')['ElLink'] ElOption: typeof import('element-plus/es')['ElOption'] ElPagination: typeof import('element-plus/es')['ElPagination'] ElPopover: typeof import('element-plus/es')['ElPopover'] ElRadio: typeof import('element-plus/es')['ElRadio'] - ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRow: typeof import('element-plus/es')['ElRow'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElSelect: typeof import('element-plus/es')['ElSelect'] ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] - ElSpace: typeof import('element-plus/es')['ElSpace'] ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElTable: typeof import('element-plus/es')['ElTable'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] - ElTableV2: typeof import('element-plus/es')['ElTableV2'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] ElTag: typeof import('element-plus/es')['ElTag'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTransfer: typeof import('element-plus/es')['ElTransfer'] - ElTree: typeof import('element-plus/es')['ElTree'] - ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElUpload: typeof import('element-plus/es')['ElUpload'] Error: typeof import('./../components/Error/src/Error.vue')['default'] FlowCondition: typeof import('./../components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue')['default'] diff --git a/src/types/auto-imports.d.ts b/src/types/auto-imports.d.ts index 2c68c6ce..75cf16d9 100644 --- a/src/types/auto-imports.d.ts +++ b/src/types/auto-imports.d.ts @@ -72,5 +72,5 @@ declare global { // for type re-export declare global { // @ts-ignore - export type { Component,ComponentPublicInstance,ComputedRef,InjectionKey,PropType,Ref,VNode } from 'vue' + export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue' } diff --git a/src/views/system/sensitiveWord/form.vue b/src/views/system/sensitiveWord/form.vue index 24bcdaaa..85f751c3 100644 --- a/src/views/system/sensitiveWord/form.vue +++ b/src/views/system/sensitiveWord/form.vue @@ -69,10 +69,11 @@ const formRules = reactive({ tags: [{ required: true, message: '标签不能为空', trigger: 'blur' }] }) const formRef = ref() // 表单 Ref -const tags = ref([]) // todo @blue-syd:在 openModal 里加载下 +const tags: Ref<string[]> = ref([]) // todo @blue-syd:在 openModal 里加载下 /** 打开弹窗 */ -const openModal = async (type: string, id?: number) => { +const openModal = async (type: string, paramTags: string[], id?: number) => { + tags.value = paramTags modelVisible.value = true modelTitle.value = t('action.' + type) formType.value = type diff --git a/src/views/system/sensitiveWord/index.vue b/src/views/system/sensitiveWord/index.vue index 17da6ca3..93ea3c71 100644 --- a/src/views/system/sensitiveWord/index.vue +++ b/src/views/system/sensitiveWord/index.vue @@ -45,13 +45,14 @@ <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> <el-button type="primary" + plain @click="openModal('create')" v-hasPermi="['system:sensitive-word:create']" > <Icon icon="ep:plus" class="mr-5px" /> 新增 </el-button> <el-button - type="success" + type="warning" plain @click="handleExport" :loading="exportLoading" @@ -59,6 +60,9 @@ > <Icon icon="ep:download" class="mr-5px" /> 导出 </el-button> + <el-button type="success" plain @click="handleTest"> + <Icon icon="ep:document-checked" class="mr-5px" /> 测试 + </el-button> </el-form-item> </el-form> </content-wrap> @@ -127,6 +131,9 @@ <!-- 表单弹窗:添加/修改 --> <SensitiveWordForm ref="modalRef" @success="getList" /> + + <!-- 表单弹窗:测试敏感词 --> + <SensitiveWordTestForm ref="modalTestRef" /> </template> <script setup lang="ts" name="SensitiveWord"> import { DICT_TYPE, getDictOptions } from '@/utils/dict' @@ -134,6 +141,8 @@ import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' import * as SensitiveWordApi from '@/api/system/sensitiveWord' import SensitiveWordForm from './form.vue' // TODO @blue-syd:组件名不对 +import SensitiveWordTestForm from './testForm.vue' + const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -179,10 +188,15 @@ const resetQuery = () => { /** 添加/修改操作 */ const modalRef = ref() const openModal = (type: string, id?: number) => { - modalRef.value.openModal(type, id) + modalRef.value.openModal(type, tags.value, id) } // TODO @blue-syd:还少一个【测试】按钮的功能,参见 http://dashboard.yudao.iocoder.cn/system/sensitive-word +/* 测试敏感词按钮操作 */ +const modalTestRef = ref() +const handleTest = () => { + modalTestRef.value.openModal(tags.value) +} /** 删除按钮操作 */ const handleDelete = async (id: number) => { diff --git a/src/views/system/sensitiveWord/testForm.vue b/src/views/system/sensitiveWord/testForm.vue new file mode 100644 index 00000000..766d771f --- /dev/null +++ b/src/views/system/sensitiveWord/testForm.vue @@ -0,0 +1,92 @@ +<template> + <!-- 对话框(测试敏感词) --> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="文本" prop="text"> + <el-input type="textarea" v-model="formData.text" placeholder="请输入测试文本" /> + </el-form-item> + <el-form-item label="标签" prop="tags"> + <el-select + v-model="formData.tags" + multiple + filterable + allow-create + placeholder="请选择文章标签" + style="width: 380px" + > + <el-option v-for="tag in tags" :key="tag" :label="tag" :value="tag" /> + </el-select> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">检 测</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import * as SensitiveWordApi from '@/api/system/sensitiveWord' + +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('检测敏感词') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const tags: Ref<string[]> = ref([]) +const formData = ref({ + text: '', + tags: [] +}) +const formRules = reactive({ + text: [{ required: true, message: '测试文本不能为空', trigger: 'blur' }], + tags: [{ required: true, message: '标签不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async (paramTags: string[]) => { + tags.value = paramTags + modelVisible.value = true + resetForm() +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const form = formData.value as unknown as SensitiveWordApi.SensitiveWordTestReqVO + const data = await SensitiveWordApi.validateText(form) + if (data.length === 0) { + message.success('不包含敏感词!') + return + } + message.warning('包含敏感词:' + data.join(', ')) + modelVisible.value = false + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + text: '', + tags: [] + } + formRef.value?.resetFields() +} +</script> From 74aeffec123c0267b4664afd757b098384ea344b Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 25 Mar 2023 00:29:09 +0800 Subject: [PATCH 091/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E7=9F=AD=E4=BF=A1=E6=A8=A1=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/sms/smsTemplate/index.ts | 44 +-- src/views/system/sms/log/index.vue | 2 +- .../system/sms/template/SmsTemplateForm.vue | 160 +++++++++ .../sms/template/SmsTemplateSendForm.vue | 117 +++++++ src/views/system/sms/template/form.vue | 267 -------------- src/views/system/sms/template/index.vue | 325 ++++++++---------- 6 files changed, 441 insertions(+), 474 deletions(-) create mode 100644 src/views/system/sms/template/SmsTemplateForm.vue create mode 100644 src/views/system/sms/template/SmsTemplateSendForm.vue delete mode 100644 src/views/system/sms/template/form.vue diff --git a/src/api/system/sms/smsTemplate/index.ts b/src/api/system/sms/smsTemplate/index.ts index 55a61762..35cb489d 100644 --- a/src/api/system/sms/smsTemplate/index.ts +++ b/src/api/system/sms/smsTemplate/index.ts @@ -3,7 +3,7 @@ import request from '@/config/axios' export interface SmsTemplateVO { id: number | null type: number | null - status: number | null + status: number code: string name: string content: string @@ -21,60 +21,40 @@ export interface SendSmsReqVO { templateParams: Map<String, Object> } -export interface SmsTemplatePageReqVO extends PageParam { - type?: number | null - status?: number | null - code?: string - content?: string - apiTemplateId?: string - channelId?: number | null - createTime?: Date[] -} - -export interface SmsTemplateExportReqVO { - type?: number - status?: number - code?: string - content?: string - apiTemplateId?: string - channelId?: number - createTime?: Date[] -} - // 查询短信模板列表 -export const getSmsTemplatePageApi = (params: SmsTemplatePageReqVO) => { +export const getSmsTemplatePage = (params: PageParam) => { return request.get({ url: '/system/sms-template/page', params }) } // 查询短信模板详情 -export const getSmsTemplateApi = (id: number) => { +export const getSmsTemplate = (id: number) => { return request.get({ url: '/system/sms-template/get?id=' + id }) } // 新增短信模板 -export const createSmsTemplateApi = (data: SmsTemplateVO) => { +export const createSmsTemplate = (data: SmsTemplateVO) => { return request.post({ url: '/system/sms-template/create', data }) } // 修改短信模板 -export const updateSmsTemplateApi = (data: SmsTemplateVO) => { +export const updateSmsTemplate = (data: SmsTemplateVO) => { return request.put({ url: '/system/sms-template/update', data }) } // 删除短信模板 -export const deleteSmsTemplateApi = (id: number) => { +export const deleteSmsTemplate = (id: number) => { return request.delete({ url: '/system/sms-template/delete?id=' + id }) } -// 发送短信 -export const sendSmsApi = (data: SendSmsReqVO) => { - return request.post({ url: '/system/sms-template/send-sms', data }) -} - // 导出短信模板 -export const exportPostApi = (params: SmsTemplateExportReqVO) => { +export const exportSmsTemplate = (params) => { return request.download({ url: '/system/sms-template/export-excel', params }) } + +// 发送短信 +export const sendSms = (data: SendSmsReqVO) => { + return request.post({ url: '/system/sms-template/send-sms', data }) +} diff --git a/src/views/system/sms/log/index.vue b/src/views/system/sms/log/index.vue index a0acdfaa..ec8a4659 100644 --- a/src/views/system/sms/log/index.vue +++ b/src/views/system/sms/log/index.vue @@ -102,7 +102,7 @@ plain @click="handleExport" :loading="exportLoading" - v-hasPermi="['infra:config:export']" + v-hasPermi="['system:sms-log:export']" > <Icon icon="ep:download" class="mr-5px" /> 导出 </el-button> diff --git a/src/views/system/sms/template/SmsTemplateForm.vue b/src/views/system/sms/template/SmsTemplateForm.vue new file mode 100644 index 00000000..e6bdce6c --- /dev/null +++ b/src/views/system/sms/template/SmsTemplateForm.vue @@ -0,0 +1,160 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="140px" + v-loading="formLoading" + > + <el-form-item label="短信渠道编号" prop="channelId"> + <el-select v-model="formData.channelId" placeholder="请选择短信渠道编号"> + <el-option + v-for="channel in channelList" + :key="channel.id" + :value="channel.id" + :label=" + channel.signature + + `【 ${getDictLabel(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, channel.code)}】` + " + /> + </el-select> + </el-form-item> + <el-form-item label="短信类型" prop="type"> + <el-select v-model="formData.type" placeholder="请选择短信类型"> + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="模板编号" prop="code"> + <el-input v-model="formData.code" placeholder="请输入模板编号" /> + </el-form-item> + <el-form-item label="模板名称" prop="name"> + <el-input v-model="formData.name" placeholder="请输入模板名称" /> + </el-form-item> + <el-form-item label="模板内容" prop="content"> + <el-input type="textarea" v-model="formData.content" placeholder="请输入模板内容" /> + </el-form-item> + <el-form-item label="开启状态" prop="status"> + <el-radio-group v-model="formData.status"> + <el-radio + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="parseInt(dict.value)" + > + {{ dict.label }} + </el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="短信 API 模板编号" prop="apiTemplateId"> + <el-input v-model="formData.apiTemplateId" placeholder="请输入短信 API 的模板编号" /> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="formData.remark" placeholder="请输入备注" /> + </el-form-item> + </el-form> + <template #footer> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict' +import * as SmsTemplateApi from '@/api/system/sms/smsTemplate' +import * as SmsChannelApi from '@/api/system/sms/smsChannel' +import { CommonStatusEnum } from '@/utils/constants' +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型 +const formData = ref<SmsTemplateApi.SmsTemplateVO>({ + id: null, + type: null, + status: CommonStatusEnum.ENABLE, + code: '', + name: '', + content: '', + remark: '', + apiTemplateId: '', + channelId: null +}) +const formRules = reactive({ + type: [{ required: true, message: '短信类型不能为空', trigger: 'change' }], + status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }], + code: [{ required: true, message: '模板编码不能为空', trigger: 'blur' }], + name: [{ required: true, message: '模板名称不能为空', trigger: 'blur' }], + content: [{ required: true, message: '模板内容不能为空', trigger: 'blur' }], + apiTemplateId: [{ required: true, message: '短信 API 的模板编号不能为空', trigger: 'blur' }], + channelId: [{ required: true, message: '短信渠道编号不能为空', trigger: 'change' }] +}) +const formRef = ref() // 表单 Ref +const channelList = ref([]) // 短信渠道列表 + +const open = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await SmsTemplateApi.getSmsTemplate(id) + } finally { + formLoading.value = false + } + } + // 加载渠道列表 + channelList.value = await SmsChannelApi.getSimpleSmsChannelList() +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + formLoading.value = true + try { + const data = formData.value as SmsTemplateApi.SmsTemplateVO + if (formType.value === 'create') { + await SmsTemplateApi.createSmsTemplate(data) + message.success(t('common.createSuccess')) + } else { + await SmsTemplateApi.updateSmsTemplate(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: null, + type: null, + status: CommonStatusEnum.ENABLE, + code: '', + name: '', + content: '', + remark: '', + apiTemplateId: '', + channelId: null + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/system/sms/template/SmsTemplateSendForm.vue b/src/views/system/sms/template/SmsTemplateSendForm.vue new file mode 100644 index 00000000..f2ecbe9f --- /dev/null +++ b/src/views/system/sms/template/SmsTemplateSendForm.vue @@ -0,0 +1,117 @@ +<template> + <Dialog title="测试" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="140px" + v-loading="formLoading" + > + <el-form-item label="模板内容" prop="content"> + <el-input + v-model="formData.content" + type="textarea" + placeholder="请输入模板内容" + readonly + /> + </el-form-item> + <el-form-item label="手机号" prop="mobile"> + <el-input v-model="formData.mobile" placeholder="请输入手机号" /> + </el-form-item> + <el-form-item + v-for="param in formData.params" + :key="param" + :label="'参数 {' + param + '}'" + :prop="'templateParams.' + param" + > + <el-input + v-model="formData.templateParams[param]" + :placeholder="'请输入 ' + param + ' 参数'" + /> + </el-form-item> + </el-form> + <template #footer> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import * as SmsTemplateApi from '@/api/system/sms/smsTemplate' +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 + +// 发送短信表单相关 +const formData = ref({ + content: '', + params: {}, + mobile: '', + templateCode: '', + templateParams: new Map() +}) +const formRules = reactive({ + mobile: [{ required: true, message: '手机不能为空', trigger: 'blur' }], + templateCode: [{ required: true, message: '模版编码不能为空', trigger: 'blur' }], + templateParams: {} +}) +const formRef = ref() // 表单 Ref + +const open = async (id: number) => { + modelVisible.value = true + resetForm() + // 设置数据 + formLoading.value = true + try { + const data = await SmsTemplateApi.getSmsTemplate(id) + // 设置动态表单 + formData.value.content = data.content + formData.value.params = data.params + formData.value.templateCode = data.code + formData.value.templateParams = data.params.reduce((obj, item) => { + obj[item] = '' // 给每个动态属性赋值,避免无法读取 + return obj + }, {}) + formRules.templateParams = data.params.reduce((obj, item) => { + obj[item] = { required: true, message: '参数 ' + item + ' 不能为空', trigger: 'blur' } + return obj + }, {}) + } finally { + formLoading.value = false + } +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as SmsTemplateApi.SendSmsReqVO + const logId = await SmsTemplateApi.sendSms(data) + if (logId) { + message.success('提交发送成功!发送结果,见发送日志编号:' + logId) + } + modelVisible.value = false + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + content: '', + params: {}, + mobile: '', + templateCode: '', + templateParams: new Map() + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/system/sms/template/form.vue b/src/views/system/sms/template/form.vue deleted file mode 100644 index 4c9825f9..00000000 --- a/src/views/system/sms/template/form.vue +++ /dev/null @@ -1,267 +0,0 @@ -<template> - <Dialog :title="modelTitle" v-model="modelVisible"> - <!-- 修改/新增 --> - <el-form - v-if="['template.addTitle', 'template.updtaeTitle'].includes(formType)" - ref="formRef" - :model="formData" - :rules="formRules" - label-width="140px" - > - <el-form-item label="短信渠道编号" prop="channelId"> - <el-select v-model="formData.channelId" placeholder="请选择短信渠道编号"> - <el-option - v-for="channel in channelOptions" - :key="channel.id" - :value="channel.id" - :label=" - channel.signature + optionLabel(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, channel.code) - " - /> - </el-select> - </el-form-item> - <el-form-item label="短信类型" prop="type"> - <el-select v-model="formData.type" placeholder="请选择短信类型"> - <el-option - v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE)" - :key="dict.value" - :label="dict.label" - :value="parseInt(dict.value)" - /> - </el-select> - </el-form-item> - <el-form-item label="模板编号" prop="code"> - <el-input v-model="formData.code" placeholder="请输入模板编号" /> - </el-form-item> - <el-form-item label="模板名称" prop="name"> - <el-input v-model="formData.name" placeholder="请输入模板名称" /> - </el-form-item> - <el-form-item label="模板内容" prop="content"> - <el-input type="textarea" v-model="formData.content" placeholder="请输入模板内容" /> - </el-form-item> - <el-form-item label="开启状态" prop="status"> - <el-radio-group v-model="formData.status"> - <el-radio - v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" - :key="dict.value" - :label="parseInt(dict.value)" - >{{ dict.label }}</el-radio - > - </el-radio-group> - </el-form-item> - <el-form-item label="短信 API 模板编号" prop="apiTemplateId"> - <el-input v-model="formData.apiTemplateId" placeholder="请输入短信 API 的模板编号" /> - </el-form-item> - <el-form-item label="备注" prop="remark"> - <el-input v-model="formData.remark" placeholder="请输入备注" /> - </el-form-item> - </el-form> - <!-- 短信测试 --> - <el-form - v-if="formType === 'template.sendSms'" - ref="sendSmsFormRef" - :model="sendSmsForm" - :rules="sendSmsRules" - > - <el-form-item label="模板内容" prop="content"> - <el-input - v-model="sendSmsForm.content" - type="textarea" - placeholder="请输入模板内容" - readonly - /> - </el-form-item> - <el-form-item label="手机号" prop="mobile"> - <el-input v-model="sendSmsForm.mobile" placeholder="请输入手机号" /> - </el-form-item> - <el-form-item - v-for="param in sendSmsForm.params" - :key="param" - :label="'参数 {' + param + '}'" - :prop="'templateParams.' + param" - > - <el-input - v-model="sendSmsForm.templateParams[param]" - :placeholder="'请输入 ' + param + ' 参数'" - /> - </el-form-item> - </el-form> - <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> - </template> - </Dialog> -</template> -<script setup lang="ts" name="SmsTemplateFrom"> -import { DICT_TYPE, getDictOptions, getDictLabel } from '@/utils/dict' -import * as templateApi from '@/api/system/sms/smsTemplate' -import * as SmsChannelApi from '@/api/system/sms/smsChannel' -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 - -defineProps({ - channelOptions: { - type: Array as PropType<SmsChannelApi.SmsChannelListVO[]>, - define: () => {} - } -}) - -const modelVisible = ref(false) // 弹窗的是否展示 -const modelTitle = ref('') // 弹窗的标题 -const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 -const formType = ref('') // 表单的类型 -const formData = ref<templateApi.SmsTemplateVO>({ - id: null, - type: null, - status: null, - code: '', - name: '', - content: '', - remark: '', - apiTemplateId: '', - channelId: null -}) -const formRules = reactive({ - type: [{ required: true, message: '短信类型不能为空', trigger: 'change' }], - status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }], - code: [{ required: true, message: '模板编码不能为空', trigger: 'blur' }], - name: [{ required: true, message: '模板名称不能为空', trigger: 'blur' }], - content: [{ required: true, message: '模板内容不能为空', trigger: 'blur' }], - apiTemplateId: [{ required: true, message: '短信 API 的模板编号不能为空', trigger: 'blur' }], - channelId: [{ required: true, message: '短信渠道编号不能为空', trigger: 'change' }] -}) - -const formRef = ref() // 表单 Ref -const optionLabel = computed( - () => (type: string, code: string) => `【${getDictLabel(type, code)}】` -) -// 发送短信表单相关 -const sendSmsForm = ref({ - content: '', - params: {}, - mobile: '', - templateCode: '', - templateParams: {} -}) -const sendSmsRules = reactive({ - mobile: [{ required: true, message: '手机不能为空', trigger: 'blur' }], - templateCode: [{ required: true, message: '模版编码不能为空', trigger: 'blur' }], - templateParams: {} -}) -const sendSmsFormRef = ref() -/** 打开弹窗 */ -interface openModalOption { - type: string - // 编辑传id - id?: '' - // 短信测试传row - row?: any -} -const openModal = async (option: openModalOption) => { - modelVisible.value = true - modelTitle.value = t('dialog.sms.' + option.type) - formType.value = option.type - resetForm() - resetSendSms() - // 短信测试 - if (option.row) { - sendSmsForm.value.content = option.row.content - sendSmsForm.value.params = option.row.params - sendSmsForm.value.templateCode = option.row.code - sendSmsForm.value.templateParams = option.row.params.reduce(function (obj, item) { - obj[item] = undefined - return obj - }, {}) - sendSmsRules.templateParams = option.row.params.reduce(function (obj, item) { - obj[item] = { required: true, message: '参数 ' + item + ' 不能为空', trigger: 'change' } - return obj - }, {}) - } - - // 修改时,设置数据 - if (option.id) { - formLoading.value = true - try { - formData.value = await templateApi.getSmsTemplateApi(option.id) - } finally { - formLoading.value = false - } - } -} -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 - -const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 -/** 提交表单 */ -const submitForm = async () => { - formLoading.value = true - // 提交请求 - if (['template.addTitle', 'template.updtaeTitle'].includes(formType.value)) { - // 校验表单 - if (!formRef) return - const valid = await formRef.value.validate() - if (!valid) return - try { - const data = formData.value as templateApi.SmsTemplateVO - if (formType.value === 'template.addTitle') { - await templateApi.createSmsTemplateApi(data) - message.success(t('common.createSuccess')) - } else { - await templateApi.updateSmsTemplateApi(data) - message.success(t('common.updateSuccess')) - } - modelVisible.value = false - // 发送操作成功的事件 - emit('success') - } finally { - formLoading.value = false - } - } - if (formType.value === 'template.sendSms') { - sendSmsTest() - } -} - -/** 重置表单 */ -const resetForm = () => { - formData.value = { - id: null, - type: null, - status: null, - code: '', - name: '', - content: '', - remark: '', - apiTemplateId: '', - channelId: null - } - formRef.value?.resetFields() -} -/** 重置发送短信的表单 */ -const resetSendSms = () => { - // 根据 row 重置表单 - sendSmsForm.value = { - content: '', - params: {}, - mobile: '', - templateCode: '', - templateParams: {} - } - sendSmsFormRef.value?.resetFields() -} -/** 发送短信 */ -const sendSmsTest = async () => { - const data: templateApi.SendSmsReqVO = { - mobile: sendSmsForm.value.mobile, - templateCode: sendSmsForm.value.templateCode, - templateParams: sendSmsForm.value.templateParams as unknown as Map<string, Object> - } - const res = await templateApi.sendSmsApi(data) - if (res) { - message.success('提交发送成功!发送结果,见发送日志编号:' + res) - } - formLoading.value = false - modelVisible.value = false -} -</script> diff --git a/src/views/system/sms/template/index.vue b/src/views/system/sms/template/index.vue index ffdd4240..85d84e88 100644 --- a/src/views/system/sms/template/index.vue +++ b/src/views/system/sms/template/index.vue @@ -1,17 +1,22 @@ <template> - <content-wrap> + <ContentWrap> <!-- 搜索工作栏 --> <el-form + class="-mb-15px" :model="queryParams" - ref="queryForm" + ref="queryFormRef" :inline="true" - v-show="showSearch" label-width="150px" > <el-form-item label="短信类型" prop="type"> - <el-select v-model="queryParams.type" placeholder="请选择短信类型" clearable> + <el-select + v-model="queryParams.type" + placeholder="请选择短信类型" + clearable + class="!w-240px" + > <el-option - v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE)" + v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value" @@ -19,9 +24,14 @@ </el-select> </el-form-item> <el-form-item label="开启状态" prop="status"> - <el-select v-model="queryParams.status" placeholder="请选择开启状态" clearable> + <el-select + v-model="queryParams.status" + placeholder="请选择开启状态" + clearable + class="!w-240px" + > <el-option - v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" :label="dict.label" :value="dict.value" @@ -34,6 +44,7 @@ placeholder="请输入模板编码" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="短信 API 的模板编号" prop="apiTemplateId"> @@ -42,16 +53,23 @@ placeholder="请输入短信 API 的模板编号" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="短信渠道" prop="channelId"> - <el-select v-model="queryParams.channelId" placeholder="请选择短信渠道" clearable> + <el-select + v-model="queryParams.channelId" + placeholder="请选择短信渠道" + clearable + class="!w-240px" + > <el-option - v-for="channel in channelOptions" + v-for="channel in channelList" :key="channel.id" :value="channel.id" :label=" - channel.signature + optionLabel(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, channel.code) + channel.signature + + `【 ${getDictLabel(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, channel.code)}】` " /> </el-select> @@ -64,47 +82,36 @@ value-format="YYYY-MM-DD HH:mm:ss" start-placeholder="开始日期" end-placeholder="结束日期" + class="!w-240px" /> </el-form-item> <el-form-item> - <el-button type="primary" @click="handleQuery" - ><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button - > + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button + type="primary" + plain + @click="openForm('create')" + v-hasPermi="['system:sms-template:create']" + > + <Icon icon="ep:plus" class="mr-5px" />新增 + </el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['system:sms-template:export']" + > + <Icon icon="ep:download" class="mr-5px" /> 导出 + </el-button> </el-form-item> </el-form> - <!-- 操作工具栏 --> - <el-row class="mb-10px"> - <el-col :span="12"> - <el-row :gutter="10"> - <el-col :span="1.5"> - <el-button - type="primary" - plain - @click="handleAdd('template.addTitle')" - v-hasPermi="['system:sms-template:create']" - ><Icon icon="ep:plus" class="mr-5px" />新增</el-button - > - </el-col> - <el-col :span="1.5"> - <el-button - type="success" - plain - @click="handleExport" - :loading="exportLoading" - v-hasPermi="['system:sms-template:export']" - > - <Icon icon="ep:download" class="mr-5px" /> 导出 - </el-button> - </el-col> - </el-row> - </el-col> - <el-col :span="12"> - <right-toolbar v-model:showSearch="showSearch" @query-table="getList" /> - </el-col> - </el-row> - <!-- 列表 --> - <el-table v-loading="loading" :data="templateList" align="center"> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list" align="center"> <el-table-column label="模板编码" align="center" @@ -146,7 +153,9 @@ /> <el-table-column label="短信渠道" align="center" width="120"> <template #default="scope"> - <div>{{ formatChannelSignature(scope.row.channelId) }}</div> + <div> + {{ channelList.find((channel) => channel.id === scope.row.channelId)?.signature }} + </div> <dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="scope.row.channelCode" /> </template> </el-table-column> @@ -168,24 +177,27 @@ <el-button link type="primary" - @click="handleSendSms('template.sendSms', scope.row)" - v-hasPermi="['system:sms-template:send-sms']" - ><Icon icon="ep:share" class="mr-3px" />测试</el-button - > - <el-button - link - type="primary" - @click="handleUpdate('template.updtaeTitle', scope.row)" + @click="openForm('update', scope.row.id)" v-hasPermi="['system:sms-template:update']" - ><Icon icon="ep:edit" class="mr-3px" />修改</el-button > + 修改 + </el-button> <el-button link type="primary" - @click="handleDelete(scope.row)" - v-hasPermi="['system:sms-template:delete']" - ><Icon icon="ep:delete" class="mr-3px" />删除</el-button + @click="openSendForm(scope.row.id)" + v-hasPermi="['system:sms-template:send-sms']" > + 测试 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['system:sms-template:delete']" + > + 删除 + </el-button> </template> </el-table-column> </el-table> @@ -196,33 +208,29 @@ v-model:limit="queryParams.pageSize" @pagination="getList" /> - </content-wrap> + </ContentWrap> <!-- 表单弹窗:添加/修改 --> - <SmsTemplateFrom ref="modalRef" :channelOptions="channelOptions" @success="getList" /> + <SmsTemplateForm ref="formRef" @success="getList" /> + <!-- 表单弹窗:测试发送 --> + <SmsTemplateSendForm ref="sendFormRef" /> </template> <script setup lang="ts" name="SmsTemplate"> -import { DICT_TYPE, getDictOptions, getDictLabel } from '@/utils/dict' +import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' -import * as templateApi from '@/api/system/sms/smsTemplate' +import * as SmsTemplateApi from '@/api/system/sms/smsTemplate' import * as SmsChannelApi from '@/api/system/sms/smsChannel' import download from '@/utils/download' -import SmsTemplateFrom from './form.vue' +import SmsTemplateForm from './SmsTemplateForm.vue' +import SmsTemplateSendForm from './SmsTemplateSendForm.vue' const message = useMessage() // 消息弹窗 -// const { t } = useI18n() // 国际化 +const { t } = useI18n() // 国际化 -// 弹出层ref -const modalRef = ref() -// 遮罩层 -const loading = ref(true) -// 导出遮罩层 -// const exportLoading = ref(false) -// 显示搜索条件 -const showSearch = ref(true) -// 表单ref -const queryForm = ref() -// 查询参数 -const queryParams = ref<templateApi.SmsTemplatePageReqVO>({ +const loading = ref(false) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryFormRef = ref() // 搜索的表单 +const queryParams = reactive({ pageNo: 1, pageSize: 10, type: null, @@ -233,108 +241,77 @@ const queryParams = ref<templateApi.SmsTemplatePageReqVO>({ channelId: null, createTime: [] }) -// 总条数 -const total = ref(0) -// 短信模板列表 -const templateList = ref([]) +const exportLoading = ref(false) // 导出的加载中 +const channelList = ref([]) // 短信渠道列表 + /** 查询列表 */ -const getList = () => { +const getList = async () => { loading.value = true - // 执行查询 - templateApi.getSmsTemplatePageApi(queryParams.value).then((response) => { - templateList.value = response.list - total.value = response.total + try { + const data = await SmsTemplateApi.getSmsTemplatePage(queryParams) + list.value = data.list + total.value = data.total + } finally { loading.value = false - }) -} -/** 搜索按钮操作 */ -const handleQuery = () => { - queryParams.value.pageNo = 1 - getList() -} -/** 重置按钮操作 */ -const resetQuery = () => { - resetForm() - handleQuery() -} -/** 重置搜索表单 */ -const resetForm = () => { - queryParams.value = { - pageNo: 1, - pageSize: 10, - type: null, - status: null, - code: '', - content: '', - apiTemplateId: '', - channelId: null, - createTime: [] } - queryForm.value?.resetFields() -} -// 短信渠道 -const channelOptions = ref<SmsChannelApi.SmsChannelListVO[]>([]) -onMounted(() => { - SmsChannelApi.getSimpleSmsChannelList().then((res) => { - channelOptions.value = res - }) -}) -const optionLabel = computed( - () => (type: string, code: string) => `【${getDictLabel(type, code)}】` -) -/** 格式化短信渠道 */ -const formatChannelSignature = (channelId: number) => { - channelOptions.value.forEach((item) => { - if (item.id === channelId) { - return item.signature - } - }) - return '找不到签名:' + channelId -} -/** 新增按钮操作 */ -const handleAdd = (type: string) => { - modalRef.value.openModal({ type }) -} -/** 修改按钮操作 */ -// const handleUpdate = (row) => {} -const exportLoading = ref(false) -/** 导出按钮操作 */ -const handleExport = () => { - // 处理查询参数 - let params = { ...queryParams.value } as templateApi.SmsTemplateExportReqVO - // 执行导出 - message - .confirm('是否确认导出所有短信模板数据项?', '警告') - .then(() => { - exportLoading.value = true - return templateApi.exportPostApi(params) - }) - .then((response) => { - download.excel(response, '短信模板.xls') - exportLoading.value = false - }) - .catch(() => {}) -} -/** 发送短信按钮 */ -const handleSendSms = (type, row: any) => { - modalRef.value.openModal({ type, row }) -} -const handleUpdate = (type: string, { id }: { id: number }) => { - modalRef.value.openModal({ type, id }) -} -/** 删除按钮操作 */ -const handleDelete = ({ id }: { id: number }) => { - message - .confirm('是否确认删除短信模板编号为"' + id + '"的数据项?') - .then(function () { - return templateApi.deleteSmsTemplateApi(id) - }) - .then(() => { - getList() - message.success('删除成功') - }) - .catch(() => {}) } -getList() +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) +} + +/** 发送短信按钮 */ +const sendFormRef = ref() +const openSendForm = (id: number) => { + sendFormRef.value.open(id) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await SmsTemplateApi.deleteSmsTemplate(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await SmsTemplateApi.exportSmsTemplate(queryParams) + download.excel(data, '短信模板.xls') + } catch { + } finally { + exportLoading.value = false + } +} + +/** 初始化 **/ +onMounted(async () => { + await getList() + // 加载渠道列表 + channelList.value = await SmsChannelApi.getSimpleSmsChannelList() +}) </script> From fa32194772b8135a138b9c4dce2669e886277a10 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 25 Mar 2023 00:41:06 +0800 Subject: [PATCH 092/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E7=9F=AD=E4=BF=A1=E6=B8=A0=E9=81=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/sms/smsChannel/index.ts | 23 +---- .../channel/{form.vue => SmsChannelForm.vue} | 64 +++++++------ src/views/system/sms/channel/index.vue | 93 +++++++------------ 3 files changed, 71 insertions(+), 109 deletions(-) rename src/views/system/sms/channel/{form.vue => SmsChannelForm.vue} (64%) diff --git a/src/api/system/sms/smsChannel/index.ts b/src/api/system/sms/smsChannel/index.ts index ee0e6167..f335628f 100644 --- a/src/api/system/sms/smsChannel/index.ts +++ b/src/api/system/sms/smsChannel/index.ts @@ -12,21 +12,8 @@ export interface SmsChannelVO { createTime: Date } -export interface SmsChannelListVO { - id: number - code: string - signature: string -} - -export interface SmsChannelPageReqVO extends PageParam { - signature?: string - code?: string - status?: number - createTime?: Date[] -} - // 查询短信渠道列表 -export const getSmsChannelPageApi = (params: SmsChannelPageReqVO) => { +export const getSmsChannelPage = (params: PageParam) => { return request.get({ url: '/system/sms-channel/page', params }) } @@ -36,21 +23,21 @@ export function getSimpleSmsChannelList() { } // 查询短信渠道详情 -export const getSmsChannelApi = (id: number) => { +export const getSmsChannel = (id: number) => { return request.get({ url: '/system/sms-channel/get?id=' + id }) } // 新增短信渠道 -export const createSmsChannelApi = (data: SmsChannelVO) => { +export const createSmsChannel = (data: SmsChannelVO) => { return request.post({ url: '/system/sms-channel/create', data }) } // 修改短信渠道 -export const updateSmsChannelApi = (data: SmsChannelVO) => { +export const updateSmsChannel = (data: SmsChannelVO) => { return request.put({ url: '/system/sms-channel/update', data }) } // 删除短信渠道 -export const deleteSmsChannelApi = (id: number) => { +export const deleteSmsChannel = (id: number) => { return request.delete({ url: '/system/sms-channel/delete?id=' + id }) } diff --git a/src/views/system/sms/channel/form.vue b/src/views/system/sms/channel/SmsChannelForm.vue similarity index 64% rename from src/views/system/sms/channel/form.vue rename to src/views/system/sms/channel/SmsChannelForm.vue index 9c3881d8..3145af91 100644 --- a/src/views/system/sms/channel/form.vue +++ b/src/views/system/sms/channel/SmsChannelForm.vue @@ -1,13 +1,19 @@ <template> <Dialog :title="modelTitle" v-model="modelVisible"> - <el-form ref="formRef" :model="form" :rules="rules" label-width="130px" v-loading="formLoading"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="130px" + v-loading="formLoading" + > <el-form-item label="短信签名" prop="signature"> - <el-input v-model="form.signature" placeholder="请输入短信签名" /> + <el-input v-model="formData.signature" placeholder="请输入短信签名" /> </el-form-item> <el-form-item label="渠道编码" prop="code"> - <el-select v-model="form.code" placeholder="请选择渠道编码" clearable> + <el-select v-model="formData.code" placeholder="请选择渠道编码" clearable> <el-option - v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE)" + v-for="dict in getStrDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE)" :key="dict.value" :label="dict.label" :value="dict.value" @@ -15,40 +21,39 @@ </el-select> </el-form-item> <el-form-item label="启用状态"> - <el-radio-group v-model="form.status"> + <el-radio-group v-model="formData.status"> <el-radio - v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" - :label="parseInt(dict.value)" - >{{ dict.label }}</el-radio + :label="dict.value" > + {{ dict.label }} + </el-radio> </el-radio-group> </el-form-item> <el-form-item label="备注" prop="remark"> - <el-input v-model="form.remark" placeholder="请输入备注" /> + <el-input v-model="formData.remark" placeholder="请输入备注" /> </el-form-item> <el-form-item label="短信 API 的账号" prop="apiKey"> - <el-input v-model="form.apiKey" placeholder="请输入短信 API 的账号" /> + <el-input v-model="formData.apiKey" placeholder="请输入短信 API 的账号" /> </el-form-item> <el-form-item label="短信 API 的密钥" prop="apiSecret"> - <el-input v-model="form.apiSecret" placeholder="请输入短信 API 的密钥" /> + <el-input v-model="formData.apiSecret" placeholder="请输入短信 API 的密钥" /> </el-form-item> <el-form-item label="短信发送回调 URL" prop="callbackUrl"> - <el-input v-model="form.callbackUrl" placeholder="请输入短信发送回调 URL" /> + <el-input v-model="formData.callbackUrl" placeholder="请输入短信发送回调 URL" /> </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> <script setup lang="ts"> -import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict' import * as SmsChannelApi from '@/api/system/sms/smsChannel' - +import { CommonStatusEnum } from '@/utils/constants' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -56,17 +61,17 @@ const modelVisible = ref(false) // 弹窗的是否展示 const modelTitle = ref('') // 弹窗的标题 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formType = ref('') // 表单的类型:create - 新增;update - 修改 -const form = ref({ +const formData = ref({ id: undefined, signature: '', code: '', - status: '', + status: CommonStatusEnum.ENABLE, remark: '', apiKey: '', apiSecret: '', callbackUrl: '' }) -const rules = reactive({ +const formRules = reactive({ signature: [{ required: true, message: '短信签名不能为空', trigger: 'blur' }], code: [{ required: true, message: '渠道编码不能为空', trigger: 'blur' }], status: [{ required: true, message: '启用状态不能为空', trigger: 'blur' }], @@ -75,7 +80,7 @@ const rules = reactive({ const formRef = ref() // 表单 Ref /** 打开弹窗 */ -const openModal = async (type: string, id?: number) => { +const open = async (type: string, id?: number) => { modelVisible.value = true modelTitle.value = t('action.' + type) formType.value = type @@ -84,15 +89,14 @@ const openModal = async (type: string, id?: number) => { if (id) { formLoading.value = true try { - form.value = await SmsChannelApi.getSmsChannelApi(id) - console.log(form) + formData.value = await SmsChannelApi.getSmsChannel(id) + console.log(formData) } finally { formLoading.value = false } } } - -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 /** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 @@ -106,10 +110,10 @@ const submitForm = async () => { try { const data = unref(formRef)?.formModel as SmsChannelApi.SmsChannelVO if (formType.value === 'create') { - await SmsChannelApi.createSmsChannelApi(data) + await SmsChannelApi.createSmsChannel(data) message.success(t('common.createSuccess')) } else { - await SmsChannelApi.updateSmsChannelApi(data) + await SmsChannelApi.updateSmsChannel(data) message.success(t('common.updateSuccess')) } modelVisible.value = false @@ -122,11 +126,11 @@ const submitForm = async () => { /** 重置表单 */ const resetForm = () => { - form.value = { + formData.value = { id: undefined, signature: '', code: '', - status: '', + status: CommonStatusEnum.ENABLE, remark: '', apiKey: '', apiSecret: '', diff --git a/src/views/system/sms/channel/index.vue b/src/views/system/sms/channel/index.vue index bac94f25..65d18029 100644 --- a/src/views/system/sms/channel/index.vue +++ b/src/views/system/sms/channel/index.vue @@ -1,6 +1,12 @@ <template> <ContentWrap> - <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px"> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > <el-form-item label="短信签名" prop="signature"> <el-input v-model="queryParams.signature" @@ -12,10 +18,10 @@ <el-form-item label="启用状态" prop="status"> <el-select v-model="queryParams.status" placeholder="请选择启用状态" clearable> <el-option - v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" - :key="parseInt(dict.value)" + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" :label="dict.label" - :value="parseInt(dict.value)" + :value="dict.value" /> </el-select> </el-form-item> @@ -34,24 +40,17 @@ <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> <el-button type="primary" - @click="openModal('create')" + @click="openForm('create')" v-hasPermi="['system:sms-channel:create']" > <Icon icon="ep:plus" class="mr-5px" /> 新增</el-button > - <el-button - type="success" - plain - @click="handleExport" - :loading="exportLoading" - v-hasPermi="['system:sms-channel:export']" - > - <Icon icon="ep:download" class="mr-5px" /> 导出</el-button - > </el-form-item> </el-form> + </ContentWrap> - <!-- 列表 --> + <!-- 列表 --> + <ContentWrap> <el-table v-loading="loading" :data="list" align="center"> <el-table-column label="编号" align="center" prop="id" /> <el-table-column label="短信签名" align="center" prop="signature" /> @@ -71,18 +70,21 @@ align="center" prop="apiKey" :show-overflow-tooltip="true" + width="180" /> <el-table-column label="短信 API 的密钥" align="center" prop="apiSecret" :show-overflow-tooltip="true" + width="180" /> <el-table-column label="短信发送回调 URL" align="center" prop="callbackUrl" :show-overflow-tooltip="true" + width="180" /> <el-table-column label="创建时间" @@ -96,7 +98,7 @@ <el-button link type="primary" - @click="openModal('update', scope.row.id)" + @click="openForm('update', scope.row.id)" v-hasPermi="['system:sms-channel:update']" > 编辑 @@ -120,35 +122,22 @@ @pagination="getList" /> </ContentWrap> + <!-- 表单弹窗:添加/修改 --> - <SmsChannelForm ref="modalRef" @success="getList" /> + <SmsChannelForm ref="formRef" @success="getList" /> </template> <script setup lang="ts" name="SmsChannel"> -// 业务相关的 import -import * as SmsChannelApi from '@/api/system/sms/smsChannel' -//格式化时间 +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' -//字典 -import { DICT_TYPE, getDictOptions } from '@/utils/dict' -//表单弹窗:添加/修改 -import SmsChannelForm from './form.vue' -//下载 -// import download from '@/utils/download' - +import * as SmsChannelApi from '@/api/system/sms/smsChannel' +import SmsChannelForm from './SmsChannelForm.vue' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 -// 列表的加载中 -const loading = ref(true) -//搜索的表单 -const queryFormRef = ref() -// 列表的总页数 -const total = ref(0) -// 列表的数据 -const list = ref([]) -//导出的加载中 -const exportLoading = ref(false) -//查询参数 +const loading = ref(false) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryFormRef = ref() // 搜索的表单 const queryParams = reactive({ pageNo: 1, pageSize: 10, @@ -160,9 +149,8 @@ const queryParams = reactive({ /** 查询参数列表 */ const getList = async () => { loading.value = true - // 执行查询 try { - const data = await SmsChannelApi.getSmsChannelPageApi(queryParams) + const data = await SmsChannelApi.getSmsChannelPage(queryParams) list.value = data.list total.value = data.total } finally { @@ -183,26 +171,9 @@ const resetQuery = () => { } /** 添加/修改操作 */ -const modalRef = ref() -const openModal = (type: string, id?: number) => { - modalRef.value.openModal(type, id) -} - -/** 导出按钮操作 */ -const handleExport = async () => { - try { - // 导出的二次确认 - await message.exportConfirm() - // 发起导出 - exportLoading.value = true - await message.info('该功能目前不支持') - //导出功能先不考虑 - // const data = await SmsChannelApi.exportSmsChanelApi(queryParams) - // download.excel(data, '短信渠道.xls') - } catch { - } finally { - exportLoading.value = false - } +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) } /** 删除按钮操作 */ @@ -211,7 +182,7 @@ const handleDelete = async (id: number) => { // 删除的二次确认 await message.delConfirm() // 发起删除 - await SmsChannelApi.deleteSmsChannelApi(id) + await SmsChannelApi.deleteSmsChannel(id) message.success(t('common.delSuccess')) // 刷新列表 await getList() From eb29f8e2c57c964408dc401b5aab68ef4e80c532 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 25 Mar 2023 00:59:14 +0800 Subject: [PATCH 093/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=A4=9A=E4=BD=99=E7=9A=84=20dialog-footer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/bpm/form/FormEditor.vue | 6 ++---- src/views/bpm/group/UserGroupForm.vue | 6 ++---- src/views/infra/codegen/components/ImportTable.vue | 6 ++---- src/views/infra/config/form.vue | 6 ++---- src/views/infra/dataSourceConfig/form.vue | 6 ++---- src/views/infra/file/form.vue | 6 ++---- src/views/infra/fileConfig/form.vue | 6 ++---- src/views/mp/tag/form.vue | 6 ++---- src/views/system/area/form.vue | 6 ++---- src/views/system/dept/form.vue | 3 --- src/views/system/dict/data.form.vue | 6 ++---- src/views/system/dict/form.vue | 6 ++---- src/views/system/errorCode/form.vue | 6 ++---- src/views/system/mail/template/send.vue | 6 ++---- src/views/system/notice/form.vue | 6 ++---- src/views/system/post/PostForm.vue | 6 ++---- src/views/system/sensitiveWord/form.vue | 6 ++---- src/views/system/tenant/form.vue | 6 ++---- 18 files changed, 34 insertions(+), 71 deletions(-) diff --git a/src/views/bpm/form/FormEditor.vue b/src/views/bpm/form/FormEditor.vue index 25ff66fe..cb7d023f 100644 --- a/src/views/bpm/form/FormEditor.vue +++ b/src/views/bpm/form/FormEditor.vue @@ -32,10 +32,8 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> diff --git a/src/views/bpm/group/UserGroupForm.vue b/src/views/bpm/group/UserGroupForm.vue index 05d646d2..9496ad84 100644 --- a/src/views/bpm/group/UserGroupForm.vue +++ b/src/views/bpm/group/UserGroupForm.vue @@ -36,10 +36,8 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> diff --git a/src/views/infra/codegen/components/ImportTable.vue b/src/views/infra/codegen/components/ImportTable.vue index 55e6d143..aebe7a8f 100644 --- a/src/views/infra/codegen/components/ImportTable.vue +++ b/src/views/infra/codegen/components/ImportTable.vue @@ -41,10 +41,8 @@ <vxe-column field="comment" title="表描述" /> </vxe-table> <template #footer> - <div class="dialog-footer"> - <XButton type="primary" :title="t('action.import')" @click="handleImportTable()" /> - <XButton :title="t('dialog.close')" @click="handleClose()" /> - </div> + <XButton type="primary" :title="t('action.import')" @click="handleImportTable()" /> + <XButton :title="t('dialog.close')" @click="handleClose()" /> </template> </XModal> </template> diff --git a/src/views/infra/config/form.vue b/src/views/infra/config/form.vue index 30e2f4d9..8d96b629 100644 --- a/src/views/infra/config/form.vue +++ b/src/views/infra/config/form.vue @@ -35,10 +35,8 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> diff --git a/src/views/infra/dataSourceConfig/form.vue b/src/views/infra/dataSourceConfig/form.vue index ea699b57..cd79e24b 100644 --- a/src/views/infra/dataSourceConfig/form.vue +++ b/src/views/infra/dataSourceConfig/form.vue @@ -21,10 +21,8 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> diff --git a/src/views/infra/file/form.vue b/src/views/infra/file/form.vue index 15ef02fd..b0a76e0e 100644 --- a/src/views/infra/file/form.vue +++ b/src/views/infra/file/form.vue @@ -23,10 +23,8 @@ </template> </el-upload> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitFileForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitFileForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> diff --git a/src/views/infra/fileConfig/form.vue b/src/views/infra/fileConfig/form.vue index a23aac9f..f08ba4c4 100644 --- a/src/views/infra/fileConfig/form.vue +++ b/src/views/infra/fileConfig/form.vue @@ -93,10 +93,8 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> diff --git a/src/views/mp/tag/form.vue b/src/views/mp/tag/form.vue index 47dcce38..331d2c33 100644 --- a/src/views/mp/tag/form.vue +++ b/src/views/mp/tag/form.vue @@ -12,10 +12,8 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> diff --git a/src/views/system/area/form.vue b/src/views/system/area/form.vue index 4e61fe32..f0cff434 100644 --- a/src/views/system/area/form.vue +++ b/src/views/system/area/form.vue @@ -15,10 +15,8 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> diff --git a/src/views/system/dept/form.vue b/src/views/system/dept/form.vue index 8d9405f3..c5c600fb 100644 --- a/src/views/system/dept/form.vue +++ b/src/views/system/dept/form.vue @@ -65,12 +65,9 @@ </el-col> </el-row> </el-form> - <template #footer> - <div class="dialog-footer"> <el-button type="primary" @click="submitForm">确 定</el-button> <el-button @click="modelVisible = false">取 消</el-button> - </div> </template> </Dialog> </template> diff --git a/src/views/system/dict/data.form.vue b/src/views/system/dict/data.form.vue index c0b70b8e..9271e8a9 100644 --- a/src/views/system/dict/data.form.vue +++ b/src/views/system/dict/data.form.vue @@ -51,10 +51,8 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> diff --git a/src/views/system/dict/form.vue b/src/views/system/dict/form.vue index af02c597..179656de 100644 --- a/src/views/system/dict/form.vue +++ b/src/views/system/dict/form.vue @@ -33,10 +33,8 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> diff --git a/src/views/system/errorCode/form.vue b/src/views/system/errorCode/form.vue index 9544c6ab..f261ced1 100644 --- a/src/views/system/errorCode/form.vue +++ b/src/views/system/errorCode/form.vue @@ -21,10 +21,8 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> diff --git a/src/views/system/mail/template/send.vue b/src/views/system/mail/template/send.vue index 94aaf004..b4b411b9 100644 --- a/src/views/system/mail/template/send.vue +++ b/src/views/system/mail/template/send.vue @@ -26,10 +26,8 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> diff --git a/src/views/system/notice/form.vue b/src/views/system/notice/form.vue index b8a49586..141dd131 100644 --- a/src/views/system/notice/form.vue +++ b/src/views/system/notice/form.vue @@ -38,10 +38,8 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> diff --git a/src/views/system/post/PostForm.vue b/src/views/system/post/PostForm.vue index 14aa9651..a9dec8b0 100644 --- a/src/views/system/post/PostForm.vue +++ b/src/views/system/post/PostForm.vue @@ -28,10 +28,8 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> diff --git a/src/views/system/sensitiveWord/form.vue b/src/views/system/sensitiveWord/form.vue index 24bcdaaa..0736542e 100644 --- a/src/views/system/sensitiveWord/form.vue +++ b/src/views/system/sensitiveWord/form.vue @@ -38,10 +38,8 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> diff --git a/src/views/system/tenant/form.vue b/src/views/system/tenant/form.vue index 368327d0..4a6eaae4 100644 --- a/src/views/system/tenant/form.vue +++ b/src/views/system/tenant/form.vue @@ -70,10 +70,8 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> From 44dc02a02e31593c1e29cadc1ff0c5d7c7047484 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 25 Mar 2023 01:01:50 +0800 Subject: [PATCH 094/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E7=AE=80=E5=8C=96=20formatDate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/formatTime.ts | 2 +- src/views/infra/apiAccessLog/ApiAccessLogDetail.vue | 3 +-- src/views/infra/apiAccessLog/index.vue | 2 +- src/views/infra/apiErrorLog/ApiErrorLogDetail.vue | 4 ++-- src/views/system/loginlog/LoginLogDetail.vue | 2 +- src/views/system/operatelog/detail.vue | 2 +- 6 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/utils/formatTime.ts b/src/utils/formatTime.ts index ec7f3744..008bc929 100644 --- a/src/utils/formatTime.ts +++ b/src/utils/formatTime.ts @@ -194,5 +194,5 @@ export const dateFormatter = (row, column, cellValue) => { if (!cellValue) { return } - return dayjs(cellValue).format('YYYY-MM-DD HH:mm:ss') + return formatDate(cellValue) } diff --git a/src/views/infra/apiAccessLog/ApiAccessLogDetail.vue b/src/views/infra/apiAccessLog/ApiAccessLogDetail.vue index 1db9820c..d046a521 100644 --- a/src/views/infra/apiAccessLog/ApiAccessLogDetail.vue +++ b/src/views/infra/apiAccessLog/ApiAccessLogDetail.vue @@ -27,8 +27,7 @@ {{ detailData.requestParams }} </el-descriptions-item> <el-descriptions-item label="请求时间"> - {{ formatDate(detailData.beginTime, 'YYYY-MM-DD HH:mm:ss') }} ~ - {{ formatDate(detailData.endTime, 'YYYY-MM-DD HH:mm:ss') }} + {{ formatDate(detailData.beginTime) }} ~ {{ formatDate(detailData.endTime) }} </el-descriptions-item> <el-descriptions-item label="请求耗时">{{ detailData.duration }} ms</el-descriptions-item> <el-descriptions-item label="操作结果"> diff --git a/src/views/infra/apiAccessLog/index.vue b/src/views/infra/apiAccessLog/index.vue index ecae3821..3102d39d 100644 --- a/src/views/infra/apiAccessLog/index.vue +++ b/src/views/infra/apiAccessLog/index.vue @@ -101,7 +101,7 @@ <el-table-column label="请求地址" align="center" prop="requestUrl" width="250" /> <el-table-column label="请求时间" align="center" prop="beginTime" width="180"> <template #default="scope"> - <span>{{ formatDate(scope.row.beginTime, 'YYYY-MM-DD HH:mm:ss') }}</span> + <span>{{ formatDate(scope.row.beginTime) }}</span> </template> </el-table-column> <el-table-column label="执行时长" align="center" prop="duration" width="180"> diff --git a/src/views/infra/apiErrorLog/ApiErrorLogDetail.vue b/src/views/infra/apiErrorLog/ApiErrorLogDetail.vue index 479c4c4b..5076fe00 100644 --- a/src/views/infra/apiErrorLog/ApiErrorLogDetail.vue +++ b/src/views/infra/apiErrorLog/ApiErrorLogDetail.vue @@ -27,7 +27,7 @@ {{ detailData.requestParams }} </el-descriptions-item> <el-descriptions-item label="异常时间"> - {{ formatDate(detailData.exceptionTime, 'YYYY-MM-DD HH:mm:ss') }} + {{ formatDate(detailData.exceptionTime) }} </el-descriptions-item> <el-descriptions-item label="异常名"> {{ detailData.exceptionName }} @@ -50,7 +50,7 @@ {{ detailData.processUserId }} </el-descriptions-item> <el-descriptions-item label="处理时间" v-if="detailData.processTime"> - {{ formatDate(detailData.processTime, 'YYYY-MM-DD HH:mm:ss') }} + {{ formatDate(detailData.processTime) }} </el-descriptions-item> </el-descriptions> </Dialog> diff --git a/src/views/system/loginlog/LoginLogDetail.vue b/src/views/system/loginlog/LoginLogDetail.vue index c224f147..f0890eca 100644 --- a/src/views/system/loginlog/LoginLogDetail.vue +++ b/src/views/system/loginlog/LoginLogDetail.vue @@ -20,7 +20,7 @@ <dict-tag :type="DICT_TYPE.SYSTEM_LOGIN_RESULT" :value="detailData.result" /> </el-descriptions-item> <el-descriptions-item label="登录日期"> - {{ formatDate(detailData.createTime, 'YYYY-MM-DD HH:mm:ss') }} + {{ formatDate(detailData.createTime) }} </el-descriptions-item> </el-descriptions> </Dialog> diff --git a/src/views/system/operatelog/detail.vue b/src/views/system/operatelog/detail.vue index 6c856e95..b3603e2e 100644 --- a/src/views/system/operatelog/detail.vue +++ b/src/views/system/operatelog/detail.vue @@ -41,7 +41,7 @@ {{ detailData.javaMethodArgs }} </el-descriptions-item> <el-descriptions-item label="操作时间"> - {{ formatDate(detailData.startTime, 'YYYY-MM-DD HH:mm:ss') }} + {{ formatDate(detailData.startTime) }} </el-descriptions-item> <el-descriptions-item label="执行时长">{{ detailData.duration }} ms</el-descriptions-item> <el-descriptions-item label="操作结果"> From c0d5a5663c9b7a44b8b432382498240c149358d3 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 25 Mar 2023 09:11:46 +0800 Subject: [PATCH 095/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E9=83=A8=E9=97=A8=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/dept/DeptForm.vue | 174 ++++++++++++++++++++++ src/views/system/dept/form.vue | 187 ------------------------ src/views/system/dept/index.vue | 161 ++++++++++---------- src/views/system/errorCode/index.vue | 2 +- src/views/system/sms/template/index.vue | 2 +- 5 files changed, 251 insertions(+), 275 deletions(-) create mode 100644 src/views/system/dept/DeptForm.vue delete mode 100644 src/views/system/dept/form.vue diff --git a/src/views/system/dept/DeptForm.vue b/src/views/system/dept/DeptForm.vue new file mode 100644 index 00000000..188ecb79 --- /dev/null +++ b/src/views/system/dept/DeptForm.vue @@ -0,0 +1,174 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="上级部门" prop="parentId"> + <el-tree-select + v-model="formData.parentId" + :data="deptTree" + :props="{ value: 'id', label: 'name', children: 'children' }" + value-key="deptId" + placeholder="请选择上级部门" + check-strictly + default-expand-all + /> + </el-form-item> + <el-form-item label="部门名称" prop="name"> + <el-input v-model="formData.name" placeholder="请输入部门名称" /> + </el-form-item> + <el-form-item label="显示排序" prop="sort"> + <el-input-number v-model="formData.sort" controls-position="right" :min="0" /> + </el-form-item> + <el-form-item label="负责人" prop="leaderUserId"> + <el-select + v-model="formData.leaderUserId" + placeholder="请输入负责人" + clearable + style="width: 100%" + > + <el-option + v-for="item in userList" + :key="item.id" + :label="item.nickname" + :value="item.id" + /> + </el-select> + </el-form-item> + <el-form-item label="联系电话" prop="phone"> + <el-input v-model="formData.phone" placeholder="请输入联系电话" maxlength="11" /> + </el-form-item> + <el-form-item label="邮箱" prop="email"> + <el-input v-model="formData.email" placeholder="请输入邮箱" maxlength="50" /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select v-model="formData.status" placeholder="请选择状态" clearable> + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-form> + <template #footer> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { handleTree } from '@/utils/tree' +import * as DeptApi from '@/api/system/dept' +import * as UserApi from '@/api/system/user' +import { CommonStatusEnum } from '@/utils/constants' +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: undefined, + title: '', + parentId: undefined, + name: undefined, + sort: undefined, + leaderUserId: undefined, + phone: undefined, + email: undefined, + status: CommonStatusEnum.ENABLE +}) +const formRules = reactive({ + parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }], + name: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }], + sort: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }], + email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }], + phone: [ + { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' } + ] +}) +const formRef = ref() // 表单 Ref +const deptTree = ref() // 树形结构 +const userList = ref<UserApi.UserVO[]>([]) // 用户列表 + +/** 打开弹窗 */ +const open = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await DeptApi.getDeptApi(id) + } finally { + formLoading.value = false + } + } + // 获得用户列表 + userList.value = await UserApi.getSimpleUserList() + // 获得部门树 + await getTree() +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as DeptApi.DeptVO + if (formType.value === 'create') { + await DeptApi.createDeptApi(data) + message.success(t('common.createSuccess')) + } else { + await DeptApi.updateDeptApi(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + title: '', + parentId: undefined, + name: undefined, + sort: undefined, + leaderUserId: undefined, + phone: undefined, + email: undefined, + status: CommonStatusEnum.ENABLE + } + formRef.value?.resetFields() +} + +/** 获得部门树 */ +const getTree = async () => { + deptTree.value = [] + const data = await DeptApi.listSimpleDeptApi() + let dept: Tree = { id: 0, name: '顶级部门', children: [] } + dept.children = handleTree(data) + deptTree.value.push(dept) +} +</script> diff --git a/src/views/system/dept/form.vue b/src/views/system/dept/form.vue deleted file mode 100644 index c5c600fb..00000000 --- a/src/views/system/dept/form.vue +++ /dev/null @@ -1,187 +0,0 @@ -<template> - <Dialog :title="modelTitle" v-model="modelVisible" width="800"> - <el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px"> - <el-row> - <el-col :span="24" v-if="formData.parentId !== 0"> - <el-form-item label="上级部门" prop="parentId"> - <el-tree-select - v-model="formData.parentId" - :data="deptOptions" - :props="{ value: 'id', label: 'name', children: 'children' }" - value-key="deptId" - placeholder="选择上级部门" - check-strictly - /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="部门名称" prop="name"> - <el-input v-model="formData.name" placeholder="请输入部门名称" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="显示排序" prop="sort"> - <el-input-number v-model="formData.sort" controls-position="right" :min="0" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="负责人" prop="leaderUserId"> - <el-select - v-model="formData.leaderUserId" - placeholder="请输入负责人" - clearable - style="width: 100%" - > - <el-option - v-for="item in userList" - :key="item.id" - :label="item.nickname" - :value="item.id" - /> - </el-select> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="联系电话" prop="phone"> - <el-input v-model="formData.phone" placeholder="请输入联系电话" maxlength="11" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="邮箱" prop="email"> - <el-input v-model="formData.email" placeholder="请输入邮箱" maxlength="50" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="状态" prop="status"> - <el-select v-model="formData.status" placeholder="请选择状态" clearable> - <el-option - v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" - :key="parseInt(dict.value)" - :label="dict.label" - :value="parseInt(dict.value)" - /> - </el-select> - </el-form-item> - </el-col> - </el-row> - </el-form> - <template #footer> - <el-button type="primary" @click="submitForm">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </template> - </Dialog> -</template> - -<script setup lang="ts"> -import { DICT_TYPE, getDictOptions } from '@/utils/dict' -import * as DeptApi from '@/api/system/dept' -import { UserVO } from '@/api/system/user' -import { handleTree } from '@/utils/tree' -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 -const modelVisible = ref(false) // 弹窗的是否展示 -const modelTitle = ref('') // 弹窗的标题 -const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 -const formType = ref('') // 表单的类型:create - 新增;update - 修改 -const formRef = ref() // 表单 Ref -const deptOptions = ref() // 树形结构 -const userList = ref() // 负责人列表选项结构 - -const formData = ref({ - id: undefined, - title: '', - parentId: undefined, - name: undefined, - sort: undefined, - leaderUserId: undefined, - phone: undefined, - email: undefined, - status: undefined -}) - -const formRules = reactive({ - parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }], - name: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }], - order: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }], - email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }], - phone: [ - { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' } - ] -}) - -/** 打开弹窗 */ -const openModal = async (type: string, id?: number, userOption?: UserVO[]) => { - userList.value = userOption - modelVisible.value = true - modelTitle.value = t('action.' + type) - formType.value = type - resetForm() - // 修改时,设置数据 - if (id) { - formLoading.value = true - try { - formData.value = await DeptApi.getDeptApi(id) - } finally { - formLoading.value = false - } - } -} - -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 - -/** 提交表单 */ -const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 -const submitForm = async () => { - // 校验表单 - if (!formRef) return - const valid = await formRef.value.validate() - if (!valid) return - // 提交请求 - formLoading.value = true - try { - const data = formData.value as unknown as DeptApi.DeptVO - if (formType.value === 'create') { - await DeptApi.createDeptApi(data) - message.success(t('common.createSuccess')) - } else { - await DeptApi.updateDeptApi(data) - message.success(t('common.updateSuccess')) - } - modelVisible.value = false - // 发送操作成功的事件 - emit('success') - } finally { - formLoading.value = false - } -} - -/** 重置表单 */ -const resetForm = () => { - formData.value = { - id: undefined, - title: '', - parentId: undefined, - name: undefined, - sort: undefined, - leaderUserId: undefined, - phone: undefined, - email: undefined, - status: undefined - } - formRef.value?.resetFields() -} - -// 获取下拉框[上级]的数据 -const getTree = async () => { - deptOptions.value = [] - const res = await DeptApi.listSimpleDeptApi() - let dept: Tree = { id: 0, name: '顶级部门', children: [] } - dept.children = handleTree(res) - deptOptions.value.push(dept) -} - -// ========== 初始化 ========== -onMounted(async () => { - await getTree() -}) -</script> diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue index 977840e2..ead319ce 100644 --- a/src/views/system/dept/index.vue +++ b/src/views/system/dept/index.vue @@ -9,50 +9,61 @@ label-width="68px" > <el-form-item label="部门名称" prop="title"> - <el-input v-model="queryParams.name" placeholder="请输入部门名称" clearable /> + <el-input + v-model="queryParams.name" + placeholder="请输入部门名称" + clearable + class="!w-240px" + /> </el-form-item> <el-form-item label="部门状态" prop="status"> - <el-select v-model="queryParams.status" placeholder="请选择" clearable> + <el-select + v-model="queryParams.status" + placeholder="请选择不么你状态" + clearable + class="!w-240px" + > <el-option - v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" - :key="parseInt(dict.value)" + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" :label="dict.label" - :value="parseInt(dict.value)" + :value="dict.value" /> </el-select> </el-form-item> <el-form-item> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> - </el-form-item> - </el-form> - <el-row :gutter="10" class="mb8"> - <el-col :span="1.5"> <el-button type="primary" plain - @click="openModal('create')" + @click="openForm('create')" v-hasPermi="['system:dept:create']" - ><Icon icon="ep:plus" class="mr-5px" /> 新增</el-button > - </el-col> - <el-col :span="1.5"> - <el-button type="danger" plain @click="toggleExpandAll" - ><Icon icon="ep:sort" class="mr-5px" /> 展开/折叠</el-button - > - </el-col> - </el-row> - <!-- 列表 --> + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> + <el-button type="danger" plain @click="toggleExpandAll"> + <Icon icon="ep:sort" class="mr-5px" /> 展开/折叠 + </el-button> + </el-form-item> + </el-form> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> <el-table - v-if="refreshTable" v-loading="loading" - :data="deptDatas" + :data="list" row-key="id" + v-if="refreshTable" :default-expand-all="isExpandAll" - :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" > <el-table-column prop="name" label="部门名称" width="260" /> - <el-table-column prop="leader" label="负责人" :formatter="userNicknameFormat" width="120" /> + <el-table-column prop="leader" label="负责人" width="120"> + <template #default="scope"> + {{ userList.find((user) => user.id === scope.row.leaderUserId)?.nickname }} + </template> + </el-table-column> <el-table-column prop="sort" label="排序" width="200" /> <el-table-column prop="status" label="状态" width="100"> <template #default="scope"> @@ -66,43 +77,44 @@ width="180" :formatter="dateFormatter" /> - <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <el-table-column label="操作" align="center" class-name="fixed-width"> <template #default="scope"> <el-button link type="primary" - icon="el-icon-edit" - @click="openModal('update', scope.row.id)" + @click="openForm('update', scope.row.id)" v-hasPermi="['system:dept:update']" - >修改</el-button > + 修改 + </el-button> <el-button - v-if="scope.row.parentId !== 0" link type="danger" - icon="el-icon-delete" @click="handleDelete(scope.row.id)" v-hasPermi="['system:dept:delete']" - >删除</el-button > + 删除 + </el-button> </template> </el-table-column> </el-table> </ContentWrap> - <!-- 添加或修改部门对话框 --> - <dept-form ref="modalRef" @success="getList" /> + <!-- 表单弹窗:添加/修改 --> + <DeptForm ref="formRef" @success="getList" /> </template> <script setup lang="ts" name="Dept"> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' import { handleTree } from '@/utils/tree' import * as DeptApi from '@/api/system/dept' -import { DICT_TYPE, getDictOptions } from '@/utils/dict' -import DeptForm from './form.vue' -import { dateFormatter } from '@/utils/formatTime' -import { getSimpleUserList, UserVO } from '@/api/system/user' +import DeptForm from './DeptForm.vue' +import * as UserApi from '@/api/system/user' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 -// 搜索变量 + +const loading = ref(true) // 列表的加载中 +const list = ref() // 列表的数据 const queryParams = reactive({ title: '', name: undefined, @@ -110,19 +122,20 @@ const queryParams = reactive({ pageNo: 1, pageSize: 100 }) - const queryFormRef = ref() // 搜索的表单 -const deptDatas = ref() // 数据变量 -const userOption = ref<UserVO[]>([]) - const isExpandAll = ref(true) // 是否展开,默认全部展开 const refreshTable = ref(true) // 重新渲染表格状态 -const loading = ref(true) // 列表的加载中 +const userList = ref<UserApi.UserVO[]>([]) // 用户列表 -// 获取用户列表 -const getUserList = async () => { - const res = await getSimpleUserList() - userOption.value = res +/** 查询部门列表 */ +const getList = async () => { + loading.value = true + try { + const data = await DeptApi.getDeptPageApi(queryParams) + list.value = handleTree(data) + } finally { + loading.value = false + } } /** 展开/折叠操作 */ @@ -140,6 +153,19 @@ const handleQuery = () => { getList() } +/** 重置按钮操作 */ +const resetQuery = () => { + queryParams.pageNo = 1 + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) +} + /** 删除按钮操作 */ const handleDelete = async (id: number) => { try { @@ -153,47 +179,10 @@ const handleDelete = async (id: number) => { } catch {} } -/** 查询部门列表 */ -const getList = async () => { - loading.value = true - try { - const res = await DeptApi.getDeptPageApi(queryParams) - deptDatas.value = handleTree(res) - } finally { - loading.value = false - } -} - -/** 重置按钮操作 */ -const resetQuery = () => { - queryParams.pageNo = 1 - queryParams.name = undefined - queryParams.status = undefined - queryFormRef.value.resetFields() - handleQuery() -} - -/** 添加/修改操作 */ -const modalRef = ref() -const openModal = (type: string, id?: number) => { - modalRef.value.openModal(type, id, userOption.value) -} - -const userNicknameFormat = (row) => { - if (!row || !row.leaderUserId) { - return '未设置' - } - for (const user of userOption.value) { - if (row.leaderUserId === user.id) { - return user.nickname - } - } - return '未知【' + row.leaderUserId + '】' -} - -// ========== 初始化 ========== +/** 初始化 **/ onMounted(async () => { - await getUserList() await getList() + // 获取用户列表 + userList.value = await UserApi.getSimpleUserList() }) </script> diff --git a/src/views/system/errorCode/index.vue b/src/views/system/errorCode/index.vue index fc152903..c95d652c 100644 --- a/src/views/system/errorCode/index.vue +++ b/src/views/system/errorCode/index.vue @@ -99,7 +99,7 @@ width="180" :formatter="dateFormatter" /> - <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <el-table-column label="操作" align="center" class-name="small-paddingfixed-width"> <template #default="scope"> <el-button link diff --git a/src/views/system/sms/template/index.vue b/src/views/system/sms/template/index.vue index 85d84e88..33cbeb90 100644 --- a/src/views/system/sms/template/index.vue +++ b/src/views/system/sms/template/index.vue @@ -169,7 +169,7 @@ <el-table-column label="操作" align="center" - class-name="small-padding fixed-width" + class-name="fixed-width" width="210" fixed="right" > From 2abc7eca67a2ea386df655cba8cd9b1e19a60f78 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 25 Mar 2023 11:20:35 +0800 Subject: [PATCH 096/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E5=85=AC=E4=BC=97=E5=8F=B7=E6=A0=87=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/mp/account/index.ts | 7 +- src/api/mp/tag/index.ts | 21 ++-- src/views/mp/tag/{form.vue => TagForm.vue} | 19 ++-- src/views/mp/tag/index.vue | 118 ++++++++------------- src/views/system/area/area.data.ts | 23 ---- 5 files changed, 74 insertions(+), 114 deletions(-) rename src/views/mp/tag/{form.vue => TagForm.vue} (86%) delete mode 100644 src/views/system/area/area.data.ts diff --git a/src/api/mp/account/index.ts b/src/api/mp/account/index.ts index cbdb1422..d641ef3c 100644 --- a/src/api/mp/account/index.ts +++ b/src/api/mp/account/index.ts @@ -1,5 +1,10 @@ import request from '@/config/axios' +export interface AccountVO { + id?: number + name: string +} + // 创建公众号账号 export const createAccount = async (data) => { return await request.post({ url: '/mp/account/create', data }) @@ -26,7 +31,7 @@ export const getAccountPage = async (query) => { } // 获取公众号账号精简信息列表 -export const getSimpleAccounts = async () => { +export const getSimpleAccountList = async () => { return request.get({ url: '/mp/account/list-all-simple' }) } diff --git a/src/api/mp/tag/index.ts b/src/api/mp/tag/index.ts index e681e2e1..50183a51 100644 --- a/src/api/mp/tag/index.ts +++ b/src/api/mp/tag/index.ts @@ -1,7 +1,14 @@ import request from '@/config/axios' +export interface TagVO { + id?: number + name: string + accountId: number + createTime: Date +} + // 创建公众号标签 -export const createTag = (data) => { +export const createTag = (data: TagVO) => { return request.post({ url: '/mp/tag/create', data: data @@ -9,7 +16,7 @@ export const createTag = (data) => { } // 更新公众号标签 -export const updateTag = (data) => { +export const updateTag = (data: TagVO) => { return request.put({ url: '/mp/tag/update', data: data @@ -17,21 +24,21 @@ export const updateTag = (data) => { } // 删除公众号标签 -export const deleteTag = (id) => { +export const deleteTag = (id: number) => { return request.delete({ url: '/mp/tag/delete?id=' + id }) } // 获得公众号标签 -export const getTag = (id) => { +export const getTag = (id: number) => { return request.get({ url: '/mp/tag/get?id=' + id }) } // 获得公众号标签分页 -export const getTagPage = (query) => { +export const getTagPage = (query: PageParam) => { return request.get({ url: '/mp/tag/page', params: query @@ -39,14 +46,14 @@ export const getTagPage = (query) => { } // 获取公众号标签精简信息列表 -export const getSimpleTags = () => { +export const getSimpleTagList = () => { return request.get({ url: '/mp/tag/list-all-simple' }) } // 同步公众号标签 -export const syncTag = (accountId) => { +export const syncTag = (accountId: number) => { return request.post({ url: '/mp/tag/sync?accountId=' + accountId }) diff --git a/src/views/mp/tag/form.vue b/src/views/mp/tag/TagForm.vue similarity index 86% rename from src/views/mp/tag/form.vue rename to src/views/mp/tag/TagForm.vue index 331d2c33..db251cdf 100644 --- a/src/views/mp/tag/form.vue +++ b/src/views/mp/tag/TagForm.vue @@ -19,7 +19,6 @@ </template> <script setup lang="ts"> import * as MpTagApi from '@/api/mp/tag' - const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -28,8 +27,8 @@ const modelTitle = ref('') // 弹窗的标题 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formType = ref('') // 表单的类型:create - 新增;update - 修改 const formData = ref({ - accountId: '', - name: undefined + accountId: -1, + name: '' }) const formRules = reactive({ name: [{ required: true, message: '请输入标签名称', trigger: 'blur' }] @@ -37,23 +36,23 @@ const formRules = reactive({ const formRef = ref() // 表单 Ref /** 打开弹窗 */ -const openModal = async (type: string, id?: number) => { +const open = async (type: string, accountId: number, id?: number) => { modelVisible.value = true modelTitle.value = t('action.' + type) formType.value = type resetForm() + formData.value.accountId = accountId // 修改时,设置数据 - if (id) { formLoading.value = true try { - formData.value = await MpTagApi.getTag(id) //调用该接口无数据返回,导致提交修改时无法上送id编号 + formData.value = await MpTagApi.getTag(id) } finally { formLoading.value = false } } } -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 /** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 @@ -65,7 +64,7 @@ const submitForm = async () => { // 提交请求 formLoading.value = true try { - const data = formData.value + const data = formData.value as MpTagApi.TagVO if (formType.value === 'create') { await MpTagApi.createTag(data) message.success(t('common.createSuccess')) @@ -84,8 +83,8 @@ const submitForm = async () => { /** 重置表单 */ const resetForm = () => { formData.value = { - accountId: '', - name: undefined + accountId: -1, + name: '' } formRef.value?.resetFields() } diff --git a/src/views/mp/tag/index.vue b/src/views/mp/tag/index.vue index 9ceb9a61..84e6fc17 100644 --- a/src/views/mp/tag/index.vue +++ b/src/views/mp/tag/index.vue @@ -1,6 +1,6 @@ <template> - <!-- 搜索 --> - <content-wrap> + <!-- 搜索工作栏 --> + <ContentWrap> <el-form class="-mb-15px" :model="queryParams" @@ -9,9 +9,9 @@ label-width="68px" > <el-form-item label="公众号" prop="accountId"> - <el-select v-model="queryParams.accountId" placeholder="请选择公众号"> + <el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px"> <el-option - v-for="item in accounts" + v-for="item in accountList" :key="parseInt(item.id)" :label="item.name" :value="parseInt(item.id)" @@ -24,32 +24,24 @@ placeholder="请输入标签名称" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> - <el-button type="primary" @click="openModal('create')" v-hasPermi="['mp:tag:create']"> + <el-button type="primary" plain @click="openForm('create')" v-hasPermi="['mp:tag:create']"> <Icon icon="ep:plus" class="mr-5px" /> 新增 </el-button> - <el-button type="info" @click="handleSync" v-hasPermi="['mp:tag:sync']"> + <el-button type="success" plain @click="handleSync" v-hasPermi="['mp:tag:sync']"> <Icon icon="ep:refresh" class="mr-5px" /> 同步 </el-button> - <el-button - type="success" - plain - @click="handleExport" - :loading="exportLoading" - v-hasPermi="['mp:tag:export']" - > - <Icon icon="ep:download" class="mr-5px" /> 导出 - </el-button> </el-form-item> </el-form> - </content-wrap> + </ContentWrap> <!-- 列表 --> - <content-wrap> + <ContentWrap> <el-table v-loading="loading" :data="list"> <el-table-column label="编号" align="center" prop="id" /> <el-table-column label="标签名称" align="center" prop="name" /> @@ -66,19 +58,18 @@ <el-button link type="primary" - size="small" - @click="openModal('update', scope.row.id)" + @click="openForm('update', scope.row.id)" v-hasPermi="['mp:tag:update']" - ><Icon icon="ep:edit" class="mr-5px" />修改 + > + 修改 </el-button> <el-button link - size="small" type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['mp:tag:delete']" > - <Icon icon="ep:delete" class="mr-5px" />删除 + 删除 </el-button> </template> </el-table-column> @@ -90,17 +81,16 @@ v-model:limit="queryParams.pageSize" @pagination="getList" /> - </content-wrap> + </ContentWrap> <!-- 表单弹窗:添加/修改 --> - <mpTagForm ref="modalRef" @success="getList" /> + <TagForm ref="formRef" @success="getList" /> </template> <script setup lang="ts" name="MpTag"> import { dateFormatter } from '@/utils/formatTime' -// import download from '@/utils/download' import * as MpTagApi from '@/api/mp/tag' -import * as MpAccountAPI from '@/api/mp/account' -import mpTagForm from './form.vue' +import * as MpAccountApi from '@/api/mp/account' +import TagForm from './TagForm.vue' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -110,28 +100,21 @@ const list = ref([]) // 列表的数据 const queryParams = reactive({ pageNo: 1, pageSize: 10, - accountId: '', + accountId: undefined, name: null }) - -interface accountsType { - id?: string | number | any - name?: string | any -} -let accounts = [] as accountsType // 公众号账号列表 const queryFormRef = ref() // 搜索的表单 -const exportLoading = ref(false) // 导出的加载中 +const accountList = ref<MpAccountApi.AccountVO[]>([]) // 公众号账号列表 /** 查询参数列表 */ const getList = async () => { // 如果没有选中公众号账号,则进行提示。 + if (!queryParams.accountId) { + await message.error('未选中公众号,无法查询标签') + return + } try { - if (!queryParams.accountId) { - await message.error('未选中公众号,无法查询标签') - return false - } loading.value = true - const data = await MpTagApi.getTagPage(queryParams) list.value = data.list total.value = data.total @@ -139,15 +122,7 @@ const getList = async () => { loading.value = false } } -/**同步 */ -const handleSync = async () => { - try { - await message.confirm('是否确认同步标签?') //未做国际化处理 - await MpTagApi.syncTag(queryParams.accountId) - message.success('同步标签成功') //未做国际化处理 - getList() - } catch {} -} + /** 搜索按钮操作 */ const handleQuery = () => { queryParams.pageNo = 1 @@ -158,53 +133,50 @@ const handleQuery = () => { const resetQuery = () => { queryFormRef.value.resetFields() // 默认选中第一个 - if (accounts.length > 0) { - queryParams.accountId = accounts[0].id + if (accountList.value.length > 0) { + // @ts-ignore + queryParams.accountId = accountList.value[0].id } handleQuery() } /** 添加/修改操作 */ -const modalRef = ref() -const openModal = (type: string, id?: number) => { - modalRef.value.openModal(type, id) +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, queryParams.accountId, id) } /** 删除按钮操作 */ const handleDelete = async (id: number) => { try { // 删除的二次确认 - await message.delConfirm('是否确认删除公众号标签编号为"' + id + '"的数据项?') //未做国际化处理 + await message.delConfirm() // 发起删除 await MpTagApi.deleteTag(id) - + message.success(t('common.delSuccess')) // 刷新列表 await getList() - message.success(t('common.delSuccess')) } catch {} } -/** 导出按钮操作 */ -const handleExport = async () => { +/** 同步操作 */ +const handleSync = async () => { try { - // 导出的二次确认 - await message.exportConfirm() - // 发起导出 - exportLoading.value = true - message.info('功能不支持') - // const data = await MpTagApi.exportConfigApi(queryParams) - // download.excel(data, '参数配置.xls') - } catch { - } finally { - exportLoading.value = false - } + await message.confirm('是否确认同步标签?') + // @ts-ignore + await MpTagApi.syncTag(queryParams.accountId) + message.success('同步标签成功') + await getList() + } catch {} } /** 初始化 **/ onMounted(async () => { - accounts = await MpAccountAPI.getSimpleAccounts() - if (accounts.length > 0) { - queryParams.accountId = accounts[0].id + accountList.value = await MpAccountApi.getSimpleAccountList() + // 选中第一个 + if (accountList.value.length > 0) { + // @ts-ignore + queryParams.accountId = accountList.value[0].id } await getList() }) diff --git a/src/views/system/area/area.data.ts b/src/views/system/area/area.data.ts deleted file mode 100644 index 008e8a41..00000000 --- a/src/views/system/area/area.data.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: null, - action: false, - columns: [ - { - title: '编号', - field: 'id', - table: { - treeNode: true, - align: 'left' - } - }, - { - title: '名字', - field: 'name' - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From ca4bdb4639eccc7b1b01fc6c44cc42659bc01047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=92=B1=E5=93=A5=E4=B8=B6?= <385454831@qq.com> Date: Sat, 25 Mar 2023 11:33:40 +0800 Subject: [PATCH 097/184] =?UTF-8?q?=E9=87=8D=E6=9E=84VUE3=E3=80=90?= =?UTF-8?q?=E7=AB=99=E5=86=85=E4=BF=A1=E6=B6=88=E6=81=AF=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E3=80=91=EF=BC=9A1=E3=80=81=E8=A1=A8=E6=A0=BC=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E8=87=AA=E9=80=82=E5=BA=94=E9=AB=98=E5=BA=A6=EF=BC=88?= =?UTF-8?q?=E7=AE=80=E5=8D=95=E5=AE=9E=E7=8E=B0=EF=BC=892=E3=80=81?= =?UTF-8?q?=E6=88=91=E7=9A=84=E7=AB=99=E5=86=85=E4=BF=A1=E6=AF=8F=E6=9D=A1?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E5=88=86=E4=B8=BA=E6=9F=A5=E9=98=85=EF=BC=88?= =?UTF-8?q?=E6=9C=AA=E8=AF=BB=E7=8A=B6=E6=80=81=EF=BC=89=E5=92=8C=E8=AF=A6?= =?UTF-8?q?=E6=83=85=EF=BC=88=E5=B7=B2=E8=AF=BB=E7=8A=B6=E6=80=81=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/notify/message/detail.vue | 64 +++++ src/views/system/notify/message/index.vue | 258 +++++++++++++---- .../system/notify/message/message.data.ts | 101 ------- src/views/system/notify/my/detail.vue | 46 ++++ src/views/system/notify/my/index.vue | 260 +++++++++++++++--- src/views/system/notify/my/my.data.ts | 58 ---- 6 files changed, 540 insertions(+), 247 deletions(-) create mode 100644 src/views/system/notify/message/detail.vue delete mode 100644 src/views/system/notify/message/message.data.ts create mode 100644 src/views/system/notify/my/detail.vue delete mode 100644 src/views/system/notify/my/my.data.ts diff --git a/src/views/system/notify/message/detail.vue b/src/views/system/notify/message/detail.vue new file mode 100644 index 00000000..0b146113 --- /dev/null +++ b/src/views/system/notify/message/detail.vue @@ -0,0 +1,64 @@ +<template> + <Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="500" width="800"> + <el-descriptions border :column="1"> + <el-descriptions-item label="编号" min-width="120"> + {{ detailData.id }} + </el-descriptions-item> + <el-descriptions-item label="用户类型"> + <dict-tag :type="DICT_TYPE.USER_TYPE" :value="detailData.userType" /> + </el-descriptions-item> + <el-descriptions-item label="用户编号"> + {{ detailData.userId }} + </el-descriptions-item> + <el-descriptions-item label="模版编号"> + {{ detailData.templateId }} + </el-descriptions-item> + <el-descriptions-item label="模板编码"> + {{ detailData.templateCode }} + </el-descriptions-item> + <el-descriptions-item label="发送人名称"> + {{ detailData.templateNickname }} + </el-descriptions-item> + <el-descriptions-item label="模版内容"> + {{ detailData.templateContent }} + </el-descriptions-item> + <el-descriptions-item label="模版参数"> + {{ detailData.templateParams }} + </el-descriptions-item> + <el-descriptions-item label="模版类型"> + <dict-tag :type="DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE" :value="detailData.templateType" /> + </el-descriptions-item> + <el-descriptions-item label="是否已读"> + <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="detailData.readStatus" /> + </el-descriptions-item> + <el-descriptions-item label="阅读时间"> + {{ formatDate(detailData.readTime, 'YYYY-MM-DD HH:mm:ss') }} + </el-descriptions-item> + <el-descriptions-item label="创建时间"> + {{ formatDate(detailData.createTime, 'YYYY-MM-DD HH:mm:ss') }} + </el-descriptions-item> + </el-descriptions> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE } from '@/utils/dict' +import { formatDate } from '@/utils/formatTime' +import * as NotifyMessageApi from '@/api/system/notify/message' + +const modelVisible = ref(false) // 弹窗的是否展示 +const detailLoading = ref(false) // 表单的加载中 +const detailData = ref() // 详情数据 + +/** 打开弹窗 */ +const openModal = async (data: NotifyMessageApi.NotifyMessageVO) => { + modelVisible.value = true + // 设置数据 + detailLoading.value = true + try { + detailData.value = data + } finally { + detailLoading.value = false + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +</script> diff --git a/src/views/system/notify/message/index.vue b/src/views/system/notify/message/index.vue index 93a8ed68..06f98c62 100644 --- a/src/views/system/notify/message/index.vue +++ b/src/views/system/notify/message/index.vue @@ -1,67 +1,217 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #actionbtns_default="{ row }"> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['system:notify-message:query']" - @click="handleDetail(row.id)" + <content-wrap> + <!-- 搜索工作栏 --> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="用户编号" prop="userId"> + <el-input + v-model="queryParams.userId" + placeholder="请输入用户编号" + clearable + @keyup.enter="handleQuery" + class="!w-240px" /> - </template> - </XTable> - </ContentWrap> - <!-- 弹窗 --> - <XModal id="messageModel" :loading="modelLoading" v-model="modelVisible" :title="modelTitle"> - <!-- 表单:详情 --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" + </el-form-item> + <el-form-item label="用户类型" prop="userType"> + <el-select + v-model="queryParams.userType" + placeholder="请选择用户类型" + clearable + class="!w-240px" + > + <el-option + v-for="dict in getDictOptions(DICT_TYPE.USER_TYPE)" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + <el-form-item label="模板编码" prop="templateCode"> + <el-input + v-model="queryParams.templateCode" + placeholder="请输入模板编码" + clearable + @keyup.enter="handleQuery" + class="!w-240px" + /> + </el-form-item> + <el-form-item label="模版类型" prop="templateType"> + <el-select + v-model="queryParams.templateType" + placeholder="请选择模版类型" + clearable + class="!w-240px" + > + <el-option + v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE)" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + value-format="YYYY-MM-DD HH:mm:ss" + type="daterange" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + </el-form-item> + </el-form> + </content-wrap> + + <!-- 列表 --> + <content-wrap> + <el-table ref="tableRef" v-loading="loading" :data="list" :height="tableHeight"> + <el-table-column label="编号" align="center" prop="id" /> + <el-table-column label="用户类型" align="center" prop="userType"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" /> + </template> + </el-table-column> + <el-table-column label="用户编号" align="center" prop="userId" width="80" /> + <el-table-column label="模版编号" align="center" prop="templateId" width="80" /> + <el-table-column label="模板编码" align="center" prop="templateCode" width="80" /> + <el-table-column label="发送人名称" align="center" prop="templateNickname" width="180" /> + <el-table-column + label="模版内容" + align="center" + prop="templateContent" + width="200" + show-overflow-tooltip + /> + <el-table-column + label="模版参数" + align="center" + prop="templateParams" + width="180" + show-overflow-tooltip + > + <template #default="scope"> {{ scope.row.templateParams }}</template> + </el-table-column> + <el-table-column label="模版类型" align="center" prop="templateType" width="120"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE" :value="scope.row.templateType" /> + </template> + </el-table-column> + <el-table-column label="是否已读" align="center" prop="readStatus" width="100"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.readStatus" /> + </template> + </el-table-column> + <el-table-column + label="阅读时间" + align="center" + prop="readTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openModal(scope.row)" + v-hasPermi="['system:notify-message:query']" + > + 详情 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" /> - <template #footer> - <!-- 按钮:关闭 --> - <XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" /> - </template> - </XModal> + </content-wrap> + + <!-- 表单弹窗:详情 --> + <notify-message-detail ref="modalRef" /> </template> <script setup lang="ts" name="NotifyMessage"> -// 业务相关的 import -import { allSchemas } from './message.data' +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' import * as NotifyMessageApi from '@/api/system/notify/message' +import NotifyMessageDetail from './detail.vue' -const { t } = useI18n() // 国际化 - -// 列表相关的变量 -const [registerTable] = useXTable({ - allSchemas: allSchemas, - topActionSlots: false, - getListApi: NotifyMessageApi.getNotifyMessagePageApi +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + userType: undefined, + userId: undefined, + templateCode: undefined, + templateType: undefined, + createTime: [] }) +const queryFormRef = ref() // 搜索的表单 +const tableRef = ref() +const tableHeight = ref() // table高度 -// 弹窗相关的变量 -const modelVisible = ref(false) // 是否显示弹出层 -const modelTitle = ref('edit') // 弹出层标题 -const modelLoading = ref(false) // 弹出层loading -const actionType = ref('') // 操作按钮的类型 -const actionLoading = ref(false) // 按钮 Loading -const detailData = ref() // 详情 Ref - -// 设置标题 -const setDialogTile = (type: string) => { - modelLoading.value = true - modelTitle.value = t('action.' + type) - actionType.value = type - modelVisible.value = true +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await NotifyMessageApi.getNotifyMessagePageApi(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } } -// 详情操作 -const handleDetail = async (rowId: number) => { - setDialogTile('detail') - const res = await NotifyMessageApi.getNotifyMessageApi(rowId) - detailData.value = res - modelLoading.value = false +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() } + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 详情操作 */ +const modalRef = ref() +const openModal = (data: NotifyMessageApi.NotifyMessageVO) => { + modalRef.value.openModal(data) +} + +/** 初始化 **/ +onMounted(() => { + getList() + // TODO 感觉表格自适应高度体验很好的,目前简单实现 需要进一步优化,如根据筛选条件展开或收缩改变盒子高度时 + tableHeight.value = window.innerHeight - tableRef.value.$el.offsetTop - 85 - 155 + // 监听浏览器高度变化 + window.onresize = () => { + tableHeight.value = window.innerHeight - tableRef.value.$el.offsetTop - 85 - 155 + } +}) </script> diff --git a/src/views/system/notify/message/message.data.ts b/src/views/system/notify/message/message.data.ts deleted file mode 100644 index 665311d2..00000000 --- a/src/views/system/notify/message/message.data.ts +++ /dev/null @@ -1,101 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', // 默认的主键ID - primaryTitle: '编号', // 默认显示的值 - primaryType: 'id', // 默认为seq,序号模式 - action: true, - actionWidth: '200', // 3个按钮默认200,如有删减对应增减即可 - columns: [ - { - title: '用户编号', - field: 'userId', - isSearch: true - }, - { - title: '用户类型', - field: 'userType', - dictType: DICT_TYPE.USER_TYPE, - dictClass: 'string', - isSearch: true, - table: { - width: 80 - } - }, - { - title: '模版编号', - field: 'templateId' - }, - { - title: '模板编码', - field: 'templateCode', - isSearch: true, - table: { - width: 80 - } - }, - { - title: '发送人名称', - field: 'templateNickname', - table: { - width: 120 - } - }, - { - title: '模版内容', - field: 'templateContent', - table: { - width: 200 - } - }, - { - title: '模版类型', - field: 'templateType', - dictType: DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE, - dictClass: 'number', - isSearch: true, - table: { - width: 80 - } - }, - { - title: '模版参数', - field: 'templateParams', - isTable: false - }, - { - title: '是否已读', - field: 'readStatus', - dictType: DICT_TYPE.INFRA_BOOLEAN_STRING, - dictClass: 'boolean', - table: { - width: 80 - } - }, - { - title: '阅读时间', - field: 'readTime', - formatter: 'formatDate', - table: { - width: 180 - } - }, - { - title: '创建时间', - field: 'createTime', - isForm: false, - formatter: 'formatDate', - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - }, - table: { - width: 180 - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) diff --git a/src/views/system/notify/my/detail.vue b/src/views/system/notify/my/detail.vue new file mode 100644 index 00000000..ea9ae961 --- /dev/null +++ b/src/views/system/notify/my/detail.vue @@ -0,0 +1,46 @@ +<template> + <Dialog title="消息详情" v-model="modelVisible" :scroll="true" :max-height="500" width="800"> + <el-descriptions border :column="1"> + <el-descriptions-item label="发送人"> + {{ detailData.templateNickname }} + </el-descriptions-item> + <el-descriptions-item label="发送时间"> + {{ formatDate(detailData.createTime, 'YYYY-MM-DD HH:mm:ss') }} + </el-descriptions-item> + <el-descriptions-item label="消息类型"> + <dict-tag :type="DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE" :value="detailData.templateType" /> + </el-descriptions-item> + <el-descriptions-item label="是否已读"> + <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="detailData.readStatus" /> + </el-descriptions-item> + <el-descriptions-item label="阅读时间" v-if="detailData.readStatus"> + {{ formatDate(detailData.readTime, 'YYYY-MM-DD HH:mm:ss') }} + </el-descriptions-item> + <el-descriptions-item label="内容"> + {{ detailData.templateContent }} + </el-descriptions-item> + </el-descriptions> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE } from '@/utils/dict' +import { formatDate } from '@/utils/formatTime' +import * as NotifyMessageApi from '@/api/system/notify/message' + +const modelVisible = ref(false) // 弹窗的是否展示 +const detailLoading = ref(false) // 表单的加载中 +const detailData = ref() // 详情数据 + +/** 打开弹窗 */ +const openModal = async (data: NotifyMessageApi.NotifyMessageVO) => { + modelVisible.value = true + // 设置数据 + detailLoading.value = true + try { + detailData.value = data + } finally { + detailLoading.value = false + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +</script> diff --git a/src/views/system/notify/my/index.vue b/src/views/system/notify/my/index.vue index 9f3e9b10..d889e184 100644 --- a/src/views/system/notify/my/index.vue +++ b/src/views/system/notify/my/index.vue @@ -1,58 +1,250 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <!-- 操作:标记已读 --> - <XButton type="primary" preIcon="ep:zoom-in" title="标记已读" @click="handleUpdateList" /> - <!-- 操作:全部已读 --> - <XButton type="primary" preIcon="ep:zoom-in" title="全部已读" @click="handleUpdateAll" /> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:已读 --> - <XTextButton - preIcon="ep:view" - title="已读" - v-hasPermi="['system:notify-message:query']" - v-if="!row.readStatus" - @click="handleUpdate([row.id])" + <content-wrap> + <!-- 搜索工作栏 --> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="是否已读" prop="readStatus"> + <el-select + v-model="queryParams.readStatus" + placeholder="请选择状态" + clearable + class="!w-240px" + > + <el-option + v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="发送时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + value-format="YYYY-MM-DD HH:mm:ss" + type="daterange" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="!w-240px" /> - </template> - </XTable> - </ContentWrap> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button @click="handleUpdateList" + ><Icon icon="ep:reading" class="mr-5px" /> 标记已读</el-button + > + <el-button @click="handleUpdateAll" + ><Icon icon="ep:reading" class="mr-5px" /> 全部已读</el-button + > + </el-form-item> + </el-form> + <!-- <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button @click="handleUpdateList" + ><Icon icon="ep:refresh" class="mr-5px" /> 标记已读</el-button + > + </el-col> + <el-col :span="1.5"> + <el-button @click="handleUpdateAll" + ><Icon icon="ep:refresh" class="mr-5px" /> 全部已读</el-button + > + </el-col> + </el-row> --> + </content-wrap> + + <content-wrap> + <!-- 列表 --> + <el-table + ref="tableRef" + v-loading="loading" + :data="list" + :height="tableHeight" + :row-key="getRowKeys" + @selection-change="handleSelectionChange" + > + <el-table-column type="selection" :selectable="selectable" :reserve-selection="true" /> + <!-- <el-table-column label="编号" align="center" prop="id" /> --> + <el-table-column label="发送人" align="center" prop="templateNickname" width="180" /> + <el-table-column + label="发送时间" + align="center" + prop="createTime" + width="200" + :formatter="dateFormatter" + /> + <el-table-column label="类型" align="center" prop="templateType" width="180"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE" :value="scope.row.templateType" /> + </template> + </el-table-column> + <el-table-column + label="消息内容" + align="center" + prop="templateContent" + show-overflow-tooltip + /> + <el-table-column label="是否已读" align="center" prop="readStatus" width="160"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.readStatus" /> + </template> + </el-table-column> + <el-table-column + label="阅读时间" + align="center" + prop="readTime" + width="200" + :formatter="dateFormatter" + /> + <el-table-column label="操作" align="center" width="160"> + <template #default="scope"> + <el-button + link + :type="scope.row.readStatus ? 'primary' : 'warning'" + @click="openModal(scope.row)" + > + {{ scope.row.readStatus ? '详情' : '查阅' }} + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + + <!-- 表单弹窗:详情 --> + <my-notify-message-detail ref="modalRef" /> </template> + <script setup lang="ts" name="MyNotifyMessage"> -// 业务相关的 import -import { allSchemas } from './my.data' +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' import * as NotifyMessageApi from '@/api/system/notify/message' +import MyNotifyMessageDetail from './detail.vue' const message = useMessage() // 消息 -// 列表相关的变量 -const [registerTable, { reload, getCheckboxRecords }] = useXTable({ - allSchemas: allSchemas, - getListApi: NotifyMessageApi.getMyNotifyMessagePage +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + readStatus: undefined, + createTime: [] }) +const queryFormRef = ref() // 搜索的表单 +const tableRef = ref() +const tableHeight = ref() // table高度 +const selectedNum = ref(0) //表格select选中的条数 +const selectedIds = ref([] as any[]) //表格select复选框选中的id +const multipleSelection = ref([]) -const handleUpdateList = async () => { - const list = getCheckboxRecords() as any as any[] - if (list.length === 0) { - return +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await NotifyMessageApi.getMyNotifyMessagePage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false } - await handleUpdate(list.map((v) => v.id)) +} + +/** 详情操作 */ +const modalRef = ref() +const openModal = (data: NotifyMessageApi.NotifyMessageVO) => { + if (!data.readStatus) { + handleUpdate(data.id) + } + modalRef.value.openModal(data) } // 标记指定 id 已读 const handleUpdate = async (ids) => { await NotifyMessageApi.updateNotifyMessageRead(ids) - message.success('标记已读成功!') - reload() + //message.success('标记已读成功!') + handleQuery() } // 标记全部已读 const handleUpdateAll = async () => { await NotifyMessageApi.updateAllNotifyMessageRead() message.success('全部已读成功!') - reload() + handleQuery() +} + +// 标记勾选 ids 已读 +const handleUpdateList = async () => { + if (selectedIds.value.length === 0) { + return + } + await NotifyMessageApi.updateNotifyMessageRead(selectedIds.value) + message.success('批量已读成功!') + tableRef.value.clearSelection() + handleQuery() +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + tableRef.value.clearSelection() + handleQuery() +} + +/** 初始化 **/ +onMounted(() => { + getList() + // TODO 感觉表格自适应高度体验好些,目前简单实现 需要进一步优化 + tableHeight.value = window.innerHeight - tableRef.value.$el.offsetTop - 85 - 155 + // 监听浏览器高度变化 + window.onresize = () => { + tableHeight.value = window.innerHeight - tableRef.value.$el.offsetTop - 85 - 155 + } +}) + +// 禁用某行复选框 +const selectable = (row) => { + return !row.readStatus +} + +//选中的list +const getRowKeys = (row) => { + return row.id +} +//当表格选择项发生变化时会触发该事件 +const handleSelectionChange = (val) => { + // 解决来回切换页面,也无法清除上次选中情况 + multipleSelection.value = val + selectedNum.value = multipleSelection.value.length + selectedIds.value = [] + if (val) { + undefined + val.forEach((row) => { + undefined + if (row) { + undefined + selectedIds.value.push(row.id) + } + }) + } } </script> diff --git a/src/views/system/notify/my/my.data.ts b/src/views/system/notify/my/my.data.ts deleted file mode 100644 index 103ed8ef..00000000 --- a/src/views/system/notify/my/my.data.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryTitle: ' ', - primaryType: 'checkbox', - action: true, - actionWidth: '200', // 3个按钮默认200,如有删减对应增减即可 - columns: [ - { - title: '发送人名称', - field: 'templateNickname', - table: { - width: 120 - } - }, - { - title: '发送时间', - field: 'createTime', - isForm: false, - formatter: 'formatDate', - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - }, - table: { - width: 180 - } - }, - { - title: '类型', - field: 'templateType', - dictType: DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE, - dictClass: 'number', - table: { - width: 80 - } - }, - { - title: '内容', - field: 'templateContent' - }, - { - title: '是否已读', - field: 'readStatus', - dictType: DICT_TYPE.INFRA_BOOLEAN_STRING, - dictClass: 'boolean', - table: { - width: 80 - }, - isSearch: true - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From 7463eea243bdfaf379dc48816ef6268717de3fa4 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 25 Mar 2023 16:22:43 +0800 Subject: [PATCH 098/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E8=A1=A8=E5=8D=95=E6=9E=84=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/infra/build/index.vue | 139 +++++++++++++++----------------- 1 file changed, 67 insertions(+), 72 deletions(-) diff --git a/src/views/infra/build/index.vue b/src/views/infra/build/index.vue index 6f577e95..00b56fea 100644 --- a/src/views/infra/build/index.vue +++ b/src/views/infra/build/index.vue @@ -3,77 +3,99 @@ <el-row> <el-col> <div class="mb-2 float-right"> - <el-button size="small" @click="setJson"> 导入JSON</el-button> - <el-button size="small" @click="setOption"> 导入Options</el-button> - <el-button size="small" type="primary" @click="showJson">生成JSON</el-button> - <el-button size="small" type="success" @click="showOption">生成Options</el-button> + <el-button size="small" type="primary" @click="showJson">生成 JSON</el-button> + <el-button size="small" type="success" @click="showOption">生成O ptions</el-button> <el-button size="small" type="danger" @click="showTemplate">生成组件</el-button> - <!-- <el-button size="small" @click="changeLocale">中英切换</el-button> --> </div> </el-col> + <!-- 表单设计器 --> <el-col> <fc-designer ref="designer" height="780px" /> </el-col> </el-row> - <Dialog :title="dialogTitle" v-model="dialogVisible" maxHeight="600"> - <div ref="editor" v-if="dialogVisible"> - <XTextButton style="float: right" :title="t('common.copy')" @click="copy(formValue)" /> - <el-scrollbar height="580"> - <div v-highlight> - <code class="hljs"> - {{ formValue }} - </code> - </div> - </el-scrollbar> - </div> - <span style="color: red" v-if="err">输入内容格式有误!</span> - </Dialog> </ContentWrap> + + <!-- 弹窗:表单预览 --> + <Dialog :title="dialogTitle" v-model="dialogVisible" max-height="600"> + <div ref="editor" v-if="dialogVisible"> + <el-button style="float: right" @click="copy(formData)"> + {{ t('common.copy') }} + </el-button> + <el-scrollbar height="580"> + <div v-highlight> + <code class="hljs"> + {{ formData }} + </code> + </div> + </el-scrollbar> + </div> + </Dialog> </template> <script setup lang="ts" name="Build"> import formCreate from '@form-create/element-ui' import { useClipboard } from '@vueuse/core' +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息 -const { t } = useI18n() -const message = useMessage() - -const designer = ref() - -const dialogVisible = ref(false) -const dialogTitle = ref('') -const err = ref(false) -const type = ref(-1) -const formValue = ref('') +const designer = ref() // 表单设计器 +const dialogVisible = ref(false) // 弹窗的是否展示 +const dialogTitle = ref('') // 弹窗的标题 +const formType = ref(-1) // 表单的类型:0 - 生成 JSON;1 - 生成 Options;2 - 生成组件 +const formData = ref('') // 表单数据 +/** 打开弹窗 */ const openModel = (title: string) => { dialogVisible.value = true dialogTitle.value = title } -const setJson = () => { - openModel('导入JSON--未实现') -} -const setOption = () => { - openModel('导入Options--未实现') -} +/** 生成 JSON */ const showJson = () => { - openModel('生成JSON') - type.value = 0 - formValue.value = designer.value.getRule() + openModel('生成 JSON') + formType.value = 0 + formData.value = designer.value.getRule() } + +/** 生成 Options */ const showOption = () => { - openModel('生成Options') - type.value = 1 - formValue.value = designer.value.getOption() + openModel('生成 Options') + formType.value = 1 + formData.value = designer.value.getOption() } + +/** 生成组件 */ const showTemplate = () => { openModel('生成组件') - type.value = 2 - formValue.value = makeTemplate() + formType.value = 2 + formData.value = makeTemplate() +} + +const makeTemplate = () => { + const rule = designer.value.getRule() + const opt = designer.value.getOption() + return `<template> + <form-create + v-model="fapi" + :rule="rule" + :option="option" + @submit="onSubmit" + ></form-create> + </template> + <script setup lang=ts> + import formCreate from "@form-create/element-ui"; + const faps = ref(null) + const rule = ref('') + const option = ref('') + const init = () => { + rule.value = formCreate.parseJson('${formCreate.toJson(rule).replaceAll('\\', '\\\\')}') + option.value = formCreate.parseJson('${JSON.stringify(opt)}') + } + const onSubmit = (formData) => { + //todo 提交表单 + } + init() + <\/script>` } -// const changeLocale = () => { -// console.info('changeLocale') -// } /** 复制 **/ const copy = async (text: string) => { @@ -87,31 +109,4 @@ const copy = async (text: string) => { } } } - -const makeTemplate = () => { - const rule = designer.value.getRule() - const opt = designer.value.getOption() - return `<template> - <form-create - v-model="fapi" - :rule="rule" - :option="option" - @submit="onSubmit" - ></form-create> -</template> -<script setup lang=ts> - import formCreate from "@form-create/element-ui"; - const faps = ref(null) - const rule = ref('') - const option = ref('') - const init = () => { - rule.value = formCreate.parseJson('${formCreate.toJson(rule).replaceAll('\\', '\\\\')}') - option.value = formCreate.parseJson('${JSON.stringify(opt)}') - } - const onSubmit = (formData) => { - //todo 提交表单 - } - init() -<\/script>` -} </script> From 8535409d021b34b618ee31c7eecff2926e1430bd Mon Sep 17 00:00:00 2001 From: Theo <koutianyu@163.com> Date: Sat, 25 Mar 2023 16:47:20 +0800 Subject: [PATCH 099/184] =?UTF-8?q?=E5=AE=8C=E6=88=90=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/menu/form.vue | 297 ++++++++++++++++++ src/views/system/menu/index.vue | 473 +++++++++-------------------- src/views/system/menu/menu.data.ts | 76 ----- 3 files changed, 442 insertions(+), 404 deletions(-) create mode 100644 src/views/system/menu/form.vue delete mode 100644 src/views/system/menu/menu.data.ts diff --git a/src/views/system/menu/form.vue b/src/views/system/menu/form.vue new file mode 100644 index 00000000..cf1583ec --- /dev/null +++ b/src/views/system/menu/form.vue @@ -0,0 +1,297 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="上级菜单"> + <el-tree-select + node-key="id" + v-model="formData.parentId" + :props="defaultProps" + :data="menuOptions" + :default-expanded-keys="[0]" + check-strictly + /> + </el-form-item> + <el-col :span="16"> + <el-form-item label="菜单名称" prop="name"> + <el-input v-model="formData.name" placeholder="请输入菜单名称" clearable /> + </el-form-item> + </el-col> + <el-form-item label="菜单类型" prop="type"> + <el-radio-group v-model="formData.type"> + <el-radio-button + v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_MENU_TYPE)" + :key="dict.label" + :label="dict.value" + > + {{ dict.label }} + </el-radio-button> + </el-radio-group> + </el-form-item> + <template v-if="formData.type !== 3"> + <el-form-item label="菜单图标"> + <IconSelect v-model="formData.icon" clearable /> + </el-form-item> + <el-col :span="16"> + <el-form-item label="路由地址" prop="path"> + <template #label> + <Tooltip + titel="路由地址" + message="访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头" + /> + </template> + <el-input v-model="formData.path" placeholder="请输入路由地址" clearable /> + </el-form-item> + </el-col> + </template> + <template v-if="formData.type === 2"> + <el-col :span="16"> + <el-form-item label="组件地址" prop="component"> + <el-input + v-model="formData.component" + placeholder="例如说:system/user/index" + clearable + /> + </el-form-item> + </el-col> + <el-col :span="16"> + <el-form-item label="组件名字" prop="componentName"> + <el-input v-model="formData.componentName" placeholder="例如说:SystemUser" clearable /> + </el-form-item> + </el-col> + </template> + <template v-if="formData.type !== 1"> + <el-col :span="16"> + <el-form-item label="权限标识" prop="permission"> + <template #label> + <Tooltip + titel="权限标识" + message="Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission('system:user:list')`)" + /> + </template> + <el-input v-model="formData.permission" placeholder="请输入权限标识" clearable /> + </el-form-item> + </el-col> + </template> + <el-col :span="16"> + <el-form-item label="显示排序" prop="sort"> + <el-input-number v-model="formData.sort" controls-position="right" :min="0" clearable /> + </el-form-item> + </el-col> + <el-col :span="16"> + <el-form-item label="菜单状态" prop="status"> + <el-radio-group v-model="formData.status"> + <el-radio + border + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.label" + :label="dict.value" + > + {{ dict.label }} + </el-radio> + </el-radio-group> + </el-form-item> + </el-col> + <template v-if="formData.type !== 3"> + <el-col :span="16"> + <el-form-item label="显示状态" prop="visible"> + <template #label> + <Tooltip + titel="显示状态" + message="选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问" + /> + </template> + <el-radio-group v-model="formData.visible"> + <el-radio border key="true" :label="true">显示</el-radio> + <el-radio border key="false" :label="false">隐藏</el-radio> + </el-radio-group> + </el-form-item> + </el-col> + </template> + <template v-if="formData.type !== 3"> + <el-col :span="16"> + <el-form-item label="总是显示" prop="alwaysShow"> + <template #label> + <Tooltip + titel="总是显示" + message="选择不是时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单" + /> + </template> + <el-radio-group v-model="formData.alwaysShow"> + <el-radio border key="true" :label="true">总是</el-radio> + <el-radio border key="false" :label="false">不是</el-radio> + </el-radio-group> + </el-form-item> + </el-col> + </template> + <template v-if="formData.type === 2"> + <el-col :span="16"> + <el-form-item label="缓存状态" prop="keepAlive"> + <template #label> + <Tooltip + titel="缓存状态" + message="选择缓存时,则会被 `keep-alive` 缓存,必须填写「组件名称」字段" + /> + </template> + <el-radio-group v-model="formData.keepAlive"> + <el-radio border key="true" :label="true">缓存</el-radio> + <el-radio border key="false" :label="false">不缓存</el-radio> + </el-radio-group> + </el-form-item> + </el-col> + </template> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import * as MenuApi from '@/api/system/menu' +import { CACHE_KEY, useCache } from '@/hooks/web/useCache' +import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants' +import { handleTree, defaultProps } from '@/utils/tree' +const { wsCache } = useCache() +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: 0, + name: '', + permission: '', + type: SystemMenuTypeEnum.DIR, + sort: 1, + parentId: 0, + path: '', + icon: '', + component: '', + componentName: '', + status: CommonStatusEnum.ENABLE, + visible: true, + keepAlive: true, + alwaysShow: true, + createTime: new Date() +}) + +const formRules = reactive({ + name: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }], + sort: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }], + path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }], + status: [{ required: true, message: '状态不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + await getTree() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await MenuApi.getMenuApi(id) + // TODO 芋艿:这块要优化下,部分字段未重置,无法修改 + // formData.value.componentName = res.componentName || '' + // formData.value.alwaysShow = res.alwaysShow !== undefined ? res.alwaysShow : true + } finally { + formLoading.value = false + } + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + if ( + formData.value.type === SystemMenuTypeEnum.DIR || + formData.value.type === SystemMenuTypeEnum.MENU + ) { + if (!isExternal(formData.value.path)) { + if (formData.value.parentId === 0 && formData.value.path.charAt(0) !== '/') { + message.error('路径必须以 / 开头') + return + } else if (formData.value.parentId !== 0 && formData.value.path.charAt(0) === '/') { + message.error('路径不能以 / 开头') + return + } + } + } + const data = formData.value + if (formType.value === 'create') { + await MenuApi.createMenuApi(data) + message.success(t('common.createSuccess')) + } else { + await MenuApi.updateMenuApi(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + wsCache.delete(CACHE_KEY.ROLE_ROUTERS) + } +} + +// ========== 下拉框[上级菜单] ========== +const menuOptions = ref<any[]>([]) // 树形结构 +// 获取下拉框[上级菜单]的数据 +const getTree = async () => { + menuOptions.value = [] + const res = await MenuApi.listSimpleMenusApi() + let menu: Tree = { id: 0, name: '主类目', children: [] } + menu.children = handleTree(res) + menuOptions.value.push(menu) +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: 0, + name: '', + permission: '', + type: SystemMenuTypeEnum.DIR, + sort: 1, + parentId: 0, + path: '', + icon: '', + component: '', + componentName: '', + status: CommonStatusEnum.ENABLE, + visible: true, + keepAlive: true, + alwaysShow: true, + createTime: new Date() + } + formRef.value?.resetFields() +} + +// 判断 path 是不是外部的 HTTP 等链接 +const isExternal = (path: string) => { + return /^(https?:|mailto:|tel:)/.test(path) +} +</script> diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue index 0604aa93..41d1bd67 100644 --- a/src/views/system/menu/index.vue +++ b/src/views/system/menu/index.vue @@ -1,351 +1,168 @@ <template> <ContentWrap> - <!-- 列表 --> - <XTable ref="xGrid" @register="registerTable" show-overflow> - <template #toolbar_buttons> - <!-- 操作:新增 --> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['system:menu:create']" - @click="handleCreate()" - /> - <XButton title="展开所有" @click="xGrid?.Ref.setAllTreeExpand(true)" /> - <XButton title="关闭所有" @click="xGrid?.Ref.clearTreeExpand()" /> - </template> - <template #name_default="{ row }"> - <Icon :icon="row.icon" /> - <span class="ml-3">{{ row.name }}</span> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:修改 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['system:menu:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['system:menu:delete']" - @click="deleteData(row.id)" - /> - </template> - </XTable> - </ContentWrap> - <!-- 添加或修改菜单对话框 --> - <XModal id="menuModel" v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(添加 / 修改) --> - <el-form - ref="formRef" - :model="menuForm" - :rules="rules" - label-width="100px" - label-position="right" - > - <el-form-item label="上级菜单"> - <el-tree-select - node-key="id" - v-model="menuForm.parentId" - :props="defaultProps" - :data="menuOptions" - :default-expanded-keys="[0]" - check-strictly + <!-- 搜索工作栏 --> + <el-form :model="queryParams" ref="queryFormRef" :inline="true"> + <el-form-item label="菜单名称" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入菜单名称" + clearable + @keyup.enter="handleQuery" /> </el-form-item> - <el-col :span="16"> - <el-form-item label="菜单名称" prop="name"> - <el-input v-model="menuForm.name" placeholder="请输入菜单名称" clearable /> - </el-form-item> - </el-col> - <el-form-item label="菜单类型" prop="type"> - <el-radio-group v-model="menuForm.type"> - <el-radio-button - v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_MENU_TYPE)" - :key="dict.label" - :label="dict.value" - > - {{ dict.label }} - </el-radio-button> - </el-radio-group> + <el-form-item label="状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="菜单状态" clearable> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button type="primary" @click="openModal('create')" v-hasPermi="['system:menu:create']"> + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> </el-form-item> - <template v-if="menuForm.type !== 3"> - <el-form-item label="菜单图标"> - <IconSelect v-model="menuForm.icon" clearable /> - </el-form-item> - <el-col :span="16"> - <el-form-item label="路由地址" prop="path"> - <template #label> - <Tooltip - titel="路由地址" - message="访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头" - /> - </template> - <el-input v-model="menuForm.path" placeholder="请输入路由地址" clearable /> - </el-form-item> - </el-col> - </template> - <template v-if="menuForm.type === 2"> - <el-col :span="16"> - <el-form-item label="组件地址" prop="component"> - <el-input - v-model="menuForm.component" - placeholder="例如说:system/user/index" - clearable - /> - </el-form-item> - </el-col> - <el-col :span="16"> - <el-form-item label="组件名字" prop="componentName"> - <el-input v-model="menuForm.componentName" placeholder="例如说:SystemUser" clearable /> - </el-form-item> - </el-col> - </template> - <template v-if="menuForm.type !== 1"> - <el-col :span="16"> - <el-form-item label="权限标识" prop="permission"> - <template #label> - <Tooltip - titel="权限标识" - message="Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission('system:user:list')`)" - /> - </template> - <el-input v-model="menuForm.permission" placeholder="请输入权限标识" clearable /> - </el-form-item> - </el-col> - </template> - <el-col :span="16"> - <el-form-item label="显示排序" prop="sort"> - <el-input-number v-model="menuForm.sort" controls-position="right" :min="0" clearable /> - </el-form-item> - </el-col> - <el-col :span="16"> - <el-form-item label="菜单状态" prop="status"> - <el-radio-group v-model="menuForm.status"> - <el-radio - border - v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" - :key="dict.label" - :label="dict.value" - > - {{ dict.label }} - </el-radio> - </el-radio-group> - </el-form-item> - </el-col> - <template v-if="menuForm.type !== 3"> - <el-col :span="16"> - <el-form-item label="显示状态" prop="visible"> - <template #label> - <Tooltip - titel="显示状态" - message="选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问" - /> - </template> - <el-radio-group v-model="menuForm.visible"> - <el-radio border key="true" :label="true">显示</el-radio> - <el-radio border key="false" :label="false">隐藏</el-radio> - </el-radio-group> - </el-form-item> - </el-col> - </template> - <template v-if="menuForm.type !== 3"> - <el-col :span="16"> - <el-form-item label="总是显示" prop="alwaysShow"> - <template #label> - <Tooltip - titel="总是显示" - message="选择不是时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单" - /> - </template> - <el-radio-group v-model="menuForm.alwaysShow"> - <el-radio border key="true" :label="true">总是</el-radio> - <el-radio border key="false" :label="false">不是</el-radio> - </el-radio-group> - </el-form-item> - </el-col> - </template> - <template v-if="menuForm.type === 2"> - <el-col :span="16"> - <el-form-item label="缓存状态" prop="keepAlive"> - <template #label> - <Tooltip - titel="缓存状态" - message="选择缓存时,则会被 `keep-alive` 缓存,必须填写「组件名称」字段" - /> - </template> - <el-radio-group v-model="menuForm.keepAlive"> - <el-radio border key="true" :label="true">缓存</el-radio> - <el-radio border key="false" :label="false">不缓存</el-radio> - </el-radio-group> - </el-form-item> - </el-col> - </template> </el-form> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :loading="actionLoading" - @click="submitForm()" - :title="t('action.save')" - /> - <!-- 按钮:关闭 --> - <XButton :loading="actionLoading" @click="dialogVisible = false" :title="t('dialog.close')" /> - </template> - </XModal> + + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button type="info" plain icon="el-icon-sort" @click="toggleExpandAll" + >展开/折叠</el-button + > + </el-col> + </el-row> + + <el-table + v-loading="loading" + :data="list" + v-if="refreshTable" + row-key="id" + :default-expand-all="isExpandAll" + :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" + > + <el-table-column prop="name" label="菜单名称" :show-overflow-tooltip="true" width="250" /> + <el-table-column prop="icon" label="图标" align="center" width="100"> + <template #default="scope"> + <Icon :icon="scope.row.icon" /> + </template> + </el-table-column> + <el-table-column prop="sort" label="排序" width="60" /> + <el-table-column prop="permission" label="权限标识" :show-overflow-tooltip="true" /> + <el-table-column prop="component" label="组件路径" :show-overflow-tooltip="true" /> + <el-table-column prop="componentName" label="组件名称" :show-overflow-tooltip="true" /> + <el-table-column prop="status" label="状态" width="80"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> + </template> + </el-table-column> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openModal('update', scope.row.id)" + v-hasPermi="['system:menu:update']" + >修改</el-button + > + <el-button + link + type="primary" + @click="openModal('create', scope.row.id)" + v-hasPermi="['system:menu:create']" + >新增</el-button + > + <el-button + link + type="primary" + @click="handleDelete(scope.row.id)" + v-hasPermi="['system:menu:delete']" + >删除</el-button + > + </template> + </el-table-column> + </el-table> + </ContentWrap> + <!-- 表单弹窗:添加/修改 --> + <menu-form ref="modalRef" @success="getList" /> </template> <script setup lang="ts" name="Menu"> -import { CACHE_KEY, useCache } from '@/hooks/web/useCache' -import { FormInstance } from 'element-plus' // 业务相关的 import -import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' -import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants' -import { handleTree, defaultProps } from '@/utils/tree' -import * as MenuApi from '@/api/system/menu' -import { allSchemas, rules } from './menu.data' +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { handleTree } from '@/utils/tree' +import * as MenuApi from '@/api/system/menu' +import MenuForm from './form.vue' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 -const { wsCache } = useCache() -const xGrid = ref<any>(null) +const loading = ref(true) // 列表的加载中 -// 列表相关的变量 -const treeConfig = { - transform: true, - rowField: 'id', - parentField: 'parentId', - expandAll: false -} -const [registerTable, { reload, deleteData }] = useXTable({ - allSchemas: allSchemas, - treeConfig: treeConfig, - getListApi: MenuApi.getMenuListApi, - deleteApi: MenuApi.deleteMenuApi -}) -// 弹窗相关的变量 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const actionType = ref('') // 操作按钮的类型 -const actionLoading = ref(false) // 遮罩层 -// 新增和修改的表单值 -const formRef = ref<FormInstance>() -const menuForm = ref<MenuApi.MenuVO>({ - id: 0, - name: '', - permission: '', - type: SystemMenuTypeEnum.DIR, - sort: 1, - parentId: 0, - path: '', - icon: '', - component: '', - componentName: '', - status: CommonStatusEnum.ENABLE, - visible: true, - keepAlive: true, - alwaysShow: true, - createTime: new Date() +const list = ref<any>([]) // 列表的数据 +const isExpandAll = ref(false) // 是否展开,默认全部折叠 +const refreshTable = ref(true) // 重新渲染表格状态 +const queryParams = reactive({ + name: undefined, + status: undefined }) +const queryFormRef = ref() // 搜索的表单 -// ========== 下拉框[上级菜单] ========== -const menuOptions = ref<any[]>([]) // 树形结构 -// 获取下拉框[上级菜单]的数据 -const getTree = async () => { - menuOptions.value = [] - const res = await MenuApi.listSimpleMenusApi() - let menu: Tree = { id: 0, name: '主类目', children: [] } - menu.children = handleTree(res) - menuOptions.value.push(menu) -} - -// ========== 新增/修改 ========== - -// 设置标题 -const setDialogTile = async (type: string) => { - await getTree() - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true -} - -// 新增操作 -const handleCreate = () => { - setDialogTile('create') - // 重置表单 - formRef.value?.resetFields() - menuForm.value = { - id: 0, - name: '', - permission: '', - type: SystemMenuTypeEnum.DIR, - sort: 1, - parentId: 0, - path: '', - icon: '', - component: '', - componentName: '', - status: CommonStatusEnum.ENABLE, - visible: true, - keepAlive: true, - alwaysShow: true, - createTime: new Date() - } -} - -// 修改操作 -const handleUpdate = async (rowId: number) => { - await setDialogTile('update') - // 设置数据 - const res = await MenuApi.getMenuApi(rowId) - menuForm.value = res - // TODO 芋艿:这块要优化下,部分字段未重置,无法修改 - menuForm.value.componentName = res.componentName || '' - menuForm.value.alwaysShow = res.alwaysShow !== undefined ? res.alwaysShow : true -} - -// 提交新增/修改的表单 -const submitForm = async () => { - actionLoading.value = true - // 提交请求 +/** 查询参数列表 */ +const getList = async () => { + loading.value = true try { - if ( - menuForm.value.type === SystemMenuTypeEnum.DIR || - menuForm.value.type === SystemMenuTypeEnum.MENU - ) { - if (!isExternal(menuForm.value.path)) { - if (menuForm.value.parentId === 0 && menuForm.value.path.charAt(0) !== '/') { - message.error('路径必须以 / 开头') - return - } else if (menuForm.value.parentId !== 0 && menuForm.value.path.charAt(0) === '/') { - message.error('路径不能以 / 开头') - return - } - } - } - if (actionType.value === 'create') { - await MenuApi.createMenuApi(menuForm.value) - message.success(t('common.createSuccess')) - } else { - await MenuApi.updateMenuApi(menuForm.value) - message.success(t('common.updateSuccess')) - } + const data = await MenuApi.getMenuListApi(queryParams) + list.value = handleTree(data) } finally { - dialogVisible.value = false - actionLoading.value = false - wsCache.delete(CACHE_KEY.ROLE_ROUTERS) - // 操作成功,重新加载列表 - await reload() + loading.value = false } } -// 判断 path 是不是外部的 HTTP 等链接 -const isExternal = (path: string) => { - return /^(https?:|mailto:|tel:)/.test(path) +/** 搜索按钮操作 */ +const handleQuery = () => { + getList() } + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = async (type: string, id?: number) => { + modalRef.value.openModal(type, id) +} + +/** 展开/折叠操作 */ +const toggleExpandAll = () => { + refreshTable.value = false + isExpandAll.value = !isExpandAll.value + nextTick(() => { + refreshTable.value = true + }) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await MenuApi.deleteMenuApi(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +/** 初始化 **/ +onMounted(() => { + getList() +}) </script> diff --git a/src/views/system/menu/menu.data.ts b/src/views/system/menu/menu.data.ts deleted file mode 100644 index 753c1211..00000000 --- a/src/views/system/menu/menu.data.ts +++ /dev/null @@ -1,76 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -const { t } = useI18n() // 国际化 - -// 新增和修改的表单校验 -export const rules = reactive({ - name: [required], - sort: [required], - path: [required], - status: [required] -}) - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: null, - action: true, - columns: [ - { - title: '上级菜单', - field: 'parentId', - isTable: false - }, - { - title: '菜单名称', - field: 'name', - isSearch: true, - table: { - treeNode: true, - align: 'left', - width: '200px', - slots: { - default: 'name_default' - } - } - }, - { - title: '菜单类型', - field: 'type', - dictType: DICT_TYPE.SYSTEM_MENU_TYPE - }, - { - title: '路由地址', - field: 'path' - }, - { - title: '组件路径', - field: 'component' - }, - { - title: '组件名字', - field: 'componentName' - }, - { - title: '权限标识', - field: 'permission' - }, - { - title: '排序', - field: 'sort' - }, - { - title: t('common.status'), - field: 'status', - dictType: DICT_TYPE.COMMON_STATUS, - dictClass: 'number', - isSearch: true - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isTable: false - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From 9e798fb7f866084d4e05978075ce1963eeb46de4 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 25 Mar 2023 18:51:09 +0800 Subject: [PATCH 100/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E5=8E=BB=E9=99=A4=20modelEditor.vue=20=E5=86=97=E4=BD=99?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/bpm/model/index.ts | 2 +- src/views/bpm/model/index.vue | 4 +- src/views/bpm/model/modelEditor.vue | 200 ++++++++-------------------- 3 files changed, 60 insertions(+), 146 deletions(-) diff --git a/src/api/bpm/model/index.ts b/src/api/bpm/model/index.ts index 0335a3db..feedd20b 100644 --- a/src/api/bpm/model/index.ts +++ b/src/api/bpm/model/index.ts @@ -29,7 +29,7 @@ export const getModelPageApi = async (params) => { return await request.get({ url: '/bpm/model/page', params }) } -export const getModelApi = async (id: number) => { +export const getModel = async (id: number) => { return await request.get({ url: '/bpm/model/get?id=' + id }) } diff --git a/src/views/bpm/model/index.vue b/src/views/bpm/model/index.vue index e88ee89e..9a4df2e8 100644 --- a/src/views/bpm/model/index.vue +++ b/src/views/bpm/model/index.vue @@ -389,7 +389,7 @@ const handleFormDetail = async (row) => { const handleBpmnDetail = (row) => { // TODO 芋艿:流程组件开发中 console.log(row) - ModelApi.getModelApi(row).then((response) => { + ModelApi.getModel(row).then((response) => { console.log(response, 'response') bpmnXML.value = response.bpmnXml // 弹窗打开 @@ -432,7 +432,7 @@ const handleUpdate = async (rowId: number) => { resetForm() await setDialogTile('edit') // 设置数据 - saveForm.value = await ModelApi.getModelApi(rowId) + saveForm.value = await ModelApi.getModel(rowId) if (saveForm.value.category == null) { saveForm.value.category = 1 } else { diff --git a/src/views/bpm/model/modelEditor.vue b/src/views/bpm/model/modelEditor.vue index 43836976..f4206f03 100644 --- a/src/views/bpm/model/modelEditor.vue +++ b/src/views/bpm/model/modelEditor.vue @@ -1,53 +1,40 @@ <template> - <div class="app-container"> - <!-- 流程设计器,负责绘制流程等 --> - <!-- <myProcessDesigner --> - <my-process-designer - :key="`designer-${reloadIndex}`" - v-if="xmlString !== undefined" - v-model="xmlString" - :value="xmlString" - v-bind="controlForm" - keyboard - ref="processDesigner" - @init-finished="initModeler" - :additionalModel="controlForm.additionalModel" - @save="save" - /> - <!-- 流程属性器,负责编辑每个流程节点的属性 --> - <!-- <MyProcessPalette --> - <my-properties-panel - :key="`penal-${reloadIndex}`" - :bpmnModeler="modeler" - :prefix="controlForm.prefix" - class="process-panel" - :model="model" - /> - </div> + <!-- 流程设计器,负责绘制流程等 --> + <my-process-designer + key="designer" + v-if="xmlString !== undefined" + v-model="xmlString" + :value="xmlString" + v-bind="controlForm" + keyboard + ref="processDesigner" + @init-finished="initModeler" + :additionalModel="controlForm.additionalModel" + @save="save" + /> + <!-- 流程属性器,负责编辑每个流程节点的属性 --> + <my-properties-panel + key="penal" + :bpmnModeler="modeler" + :prefix="controlForm.prefix" + class="process-panel" + :model="model" + /> </template> <script setup lang="ts"> -// import { translations } from '@/components/bpmnProcessDesigner/src/translations' // 自定义元素选中时的弹出菜单(修改 默认任务 为 用户任务) import CustomContentPadProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/content-pad' // 自定义左侧菜单(修改 默认任务 为 用户任务) import CustomPaletteProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/palette' -// import xmlObj2json from "./utils/xml2json"; -// import myProcessDesigner from '@/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue' -// import MyProcessPalette from '@/components/bpmnProcessDesigner/package/palette/ProcessPalette.vue' -import { createModelApi, getModelApi, updateModelApi, ModelVO } from '@/api/bpm/model' +import * as ModelApi from '@/api/bpm/model' -const router = useRouter() -const message = useMessage() - -// 自定义侧边栏 -// import MyProcessPanel from "../package/process-panel/ProcessPanel"; +const router = useRouter() // 路由 +const { query } = useRoute() // 路由的查询 +const message = useMessage() // 国际化 const xmlString = ref(undefined) // BPMN XML const modeler = ref(null) -const reloadIndex = ref(0) -// const controlDrawerVisible = ref(false) -// const translationsSelf = translations const controlForm = ref({ simulation: true, labelEditing: false, @@ -56,128 +43,55 @@ const controlForm = ref({ headerButtonSize: 'mini', additionalModel: [CustomContentPadProvider, CustomPaletteProvider] }) -// const addis = ref({ -// CustomContentPadProvider, -// CustomPaletteProvider -// }) -// 流程模型的信息 -const model = ref<ModelVO>() -onMounted(() => { - // 如果 modelId 非空,说明是修改流程模型 - const modelId = router.currentRoute.value.query && router.currentRoute.value.query.modelId - console.log(modelId, 'modelId') - if (modelId) { - // let data = '4b4909d8-97e7-11ec-8e20-862bc1a4a054' - getModelApi(modelId as unknown as number).then((data) => { - console.log(data, 'response') - xmlString.value = data.bpmnXml - model.value = { - ...data, - bpmnXml: undefined // 清空 bpmnXml 属性 - } - // this.controlForm.processId = data.key +const model = ref<ModelApi.ModelVO>() // 流程模型的信息 - // xmlString.value = - // '<?xml version="1.0" encoding="UTF-8"?>\n<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="diagram_Process_1645980650311" targetNamespace="http://activiti.org/bpmn"><bpmn2:process id="flowable_01" name="flowable测试" isExecutable="true"><bpmn2:startEvent id="Event_1iruxim"><bpmn2:outgoing>Flow_0804gmo</bpmn2:outgoing></bpmn2:startEvent><bpmn2:userTask id="task01" name="task01"><bpmn2:incoming>Flow_0804gmo</bpmn2:incoming><bpmn2:outgoing>Flow_0cx479x</bpmn2:outgoing></bpmn2:userTask><bpmn2:sequenceFlow id="Flow_0804gmo" sourceRef="Event_1iruxim" targetRef="task01" /><bpmn2:endEvent id="Event_1mdsccz"><bpmn2:incoming>Flow_0cx479x</bpmn2:incoming></bpmn2:endEvent><bpmn2:sequenceFlow id="Flow_0cx479x" sourceRef="task01" targetRef="Event_1mdsccz" /></bpmn2:process><bpmndi:BPMNDiagram id="BPMNDiagram_1"><bpmndi:BPMNPlane id="flowable_01_di" bpmnElement="flowable_01"><bpmndi:BPMNEdge id="Flow_0cx479x_di" bpmnElement="Flow_0cx479x"><di:waypoint x="440" y="350" /><di:waypoint x="492" y="350" /></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="Flow_0804gmo_di" bpmnElement="Flow_0804gmo"><di:waypoint x="288" y="350" /><di:waypoint x="340" y="350" /></bpmndi:BPMNEdge><bpmndi:BPMNShape id="Event_1iruxim_di" bpmnElement="Event_1iruxim"><dc:Bounds x="252" y="332" width="36" height="36" /></bpmndi:BPMNShape><bpmndi:BPMNShape id="task01_di" bpmnElement="task01"><dc:Bounds x="340" y="310" width="100" height="80" /></bpmndi:BPMNShape><bpmndi:BPMNShape id="Event_1mdsccz_di" bpmnElement="Event_1mdsccz"><dc:Bounds x="492" y="332" width="36" height="36" /></bpmndi:BPMNShape></bpmndi:BPMNPlane></bpmndi:BPMNDiagram></bpmn2:definitions>' - - // model.value = { - // key: 'flowable_01', - // name: 'flowable测试', - // description: 'ooxx', - // category: '1', - // formType: 10, - // formId: 11, - // formCustomCreatePath: null, - // formCustomViewPath: null, - // id: '4b4909d8-97e7-11ec-8e20-862bc1a4a054', - // createTime: 1645978019795, - // bpmnXml: undefined // 清空 bpmnXml 属性 - // } - // console.log(modeler.value, 'modeler11111111') - }) - } -}) +/** 初始化 modeler */ const initModeler = (item) => { setTimeout(() => { modeler.value = item - console.log(item, 'initModeler方法modeler') - console.log(modeler.value, 'initModeler方法modeler') - // controlForm.value.prefix = '2222' }, 10) } -const save = (bpmnXml) => { - const data: ModelVO = { - ...(model.value ?? ({} as ModelVO)), +/** 添加/修改模型 */ +const save = async (bpmnXml) => { + const data = { + ...model.value, bpmnXml: bpmnXml // bpmnXml 只是初始化流程图,后续修改无法通过它获得 - } - console.log(data, 'data') - - // 修改的提交 + } as unknown as ModelApi.ModelVO + // 提交 if (data.id) { - updateModelApi(data).then((response) => { - console.log(response, 'response') - message.success('修改成功') - // 跳转回去 - close() - }) - return + await ModelApi.updateModelApi(data) + message.success('修改成功') + } else { + await ModelApi.createModelApi(data) + message.success('新增成功') } - // 添加的提交 - createModelApi(data).then((response) => { - console.log(response, 'response1') - message.success('保存成功') - // 跳转回去 - close() - }) + // 跳转回去 + close() } + /** 关闭按钮 */ const close = () => { router.push({ path: '/bpm/manager/model' }) } -</script> -<style lang="scss"> -//body { -// overflow: hidden; -// margin: 0; -// box-sizing: border-box; -//} -//.app { -// width: 100%; -// height: 100%; -// box-sizing: border-box; -// display: inline-grid; -// grid-template-columns: 100px auto max-content; -//} -.demo-control-bar { - position: fixed; - right: 8px; - bottom: 8px; - z-index: 1; - .open-control-dialog { - width: 48px; - height: 48px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 4px; - font-size: 32px; - background: rgba(64, 158, 255, 1); - color: #ffffff; - cursor: pointer; +/** 初始化 */ +onMounted(async () => { + const modelId = query.modelId as unknown as number + if (!modelId) { + message.error('缺少模型 modelId 编号') + return } -} - -// TODO 芋艿:去掉多余的 faq -//.info-tip { -// position: fixed; -// top: 40px; -// right: 500px; -// z-index: 10; -// color: #999999; -//} - + // 查询模型 + const data = await ModelApi.getModel(modelId) + xmlString.value = data.bpmnXml + model.value = { + ...data, + bpmnXml: undefined // 清空 bpmnXml 属性 + } +}) +</script> +<style lang="scss"> .control-form { .el-radio { width: 100%; From d0ed5edb6e538cfa83c352f14b597fd7c4a3b97a Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 25 Mar 2023 19:45:56 +0800 Subject: [PATCH 101/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9Aform?= =?UTF-8?q?=20=E5=92=8C=20model=20=E7=BC=96=E8=BE=91=E5=99=A8=EF=BC=8C?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/router/modules/remaining.ts | 6 +- .../form/{FormEditor.vue => editor/index.vue} | 0 .../{modelEditor.vue => editor/index.vue} | 68 +++++++------------ 3 files changed, 29 insertions(+), 45 deletions(-) rename src/views/bpm/form/{FormEditor.vue => editor/index.vue} (100%) rename src/views/bpm/model/{modelEditor.vue => editor/index.vue} (68%) diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index 04f0d3d0..b1bdfafe 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -225,7 +225,7 @@ const remainingRouter: AppRouteRecordRaw[] = [ children: [ { path: '/manager/form/edit', - component: () => import('@/views/bpm/form/FormEditor.vue'), + component: () => import('@/views/bpm/form/editor/index.vue'), name: 'bpmFormEditor', meta: { noCache: true, @@ -237,14 +237,14 @@ const remainingRouter: AppRouteRecordRaw[] = [ }, { path: '/manager/model/edit', - component: () => import('@/views/bpm/model/modelEditor.vue'), + component: () => import('@/views/bpm/model/editor/index.vue'), name: 'modelEditor', meta: { noCache: true, hidden: true, canTo: true, title: '设计流程', - activeMenu: 'bpm/manager/model/design' + activeMenu: '/bpm/manager/model' } }, { diff --git a/src/views/bpm/form/FormEditor.vue b/src/views/bpm/form/editor/index.vue similarity index 100% rename from src/views/bpm/form/FormEditor.vue rename to src/views/bpm/form/editor/index.vue diff --git a/src/views/bpm/model/modelEditor.vue b/src/views/bpm/model/editor/index.vue similarity index 68% rename from src/views/bpm/model/modelEditor.vue rename to src/views/bpm/model/editor/index.vue index f4206f03..a59844dc 100644 --- a/src/views/bpm/model/modelEditor.vue +++ b/src/views/bpm/model/editor/index.vue @@ -1,25 +1,27 @@ <template> - <!-- 流程设计器,负责绘制流程等 --> - <my-process-designer - key="designer" - v-if="xmlString !== undefined" - v-model="xmlString" - :value="xmlString" - v-bind="controlForm" - keyboard - ref="processDesigner" - @init-finished="initModeler" - :additionalModel="controlForm.additionalModel" - @save="save" - /> - <!-- 流程属性器,负责编辑每个流程节点的属性 --> - <my-properties-panel - key="penal" - :bpmnModeler="modeler" - :prefix="controlForm.prefix" - class="process-panel" - :model="model" - /> + <ContentWrap> + <!-- 流程设计器,负责绘制流程等 --> + <my-process-designer + key="designer" + v-if="xmlString !== undefined" + v-model="xmlString" + :value="xmlString" + v-bind="controlForm" + keyboard + ref="processDesigner" + @init-finished="initModeler" + :additionalModel="controlForm.additionalModel" + @save="save" + /> + <!-- 流程属性器,负责编辑每个流程节点的属性 --> + <my-properties-panel + key="penal" + :bpmnModeler="modeler" + :prefix="controlForm.prefix" + class="process-panel" + :model="model" + /> + </ContentWrap> </template> <script setup lang="ts"> @@ -34,7 +36,7 @@ const { query } = useRoute() // 路由的查询 const message = useMessage() // 国际化 const xmlString = ref(undefined) // BPMN XML -const modeler = ref(null) +const modeler = ref(null) // BPMN Modeler const controlForm = ref({ simulation: true, labelEditing: false, @@ -92,27 +94,9 @@ onMounted(async () => { }) </script> <style lang="scss"> -.control-form { - .el-radio { - width: 100%; - line-height: 32px; - } -} -.element-overlays { - box-sizing: border-box; - padding: 8px; - background: rgba(0, 0, 0, 0.6); - border-radius: 4px; - color: #fafafa; -} - -.my-process-designer { - height: calc(100vh - 84px); -} .process-panel__container { position: absolute; - right: 0; - top: 55px; - height: calc(100vh - 84px); + right: 60px; + top: 90px; } </style> From 1854b85b40e55841e9611222d3cb449305435781 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 26 Mar 2023 00:16:28 +0800 Subject: [PATCH 102/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E6=A8=A1=E5=9E=8B=E7=9A=84=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E5=92=8C=E6=96=B0=E5=A2=9E=E3=80=81=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/bpm/model/index.ts | 6 +- src/components/DictTag/src/DictTag.vue | 2 +- src/views/bpm/model/ModelForm.vue | 219 +++++++ src/views/bpm/model/editor/index.vue | 4 +- src/views/bpm/model/index.vue | 790 +++++++------------------ src/views/bpm/model/model.data.ts | 106 ---- 6 files changed, 447 insertions(+), 680 deletions(-) create mode 100644 src/views/bpm/model/ModelForm.vue delete mode 100644 src/views/bpm/model/model.data.ts diff --git a/src/api/bpm/model/index.ts b/src/api/bpm/model/index.ts index feedd20b..68a8d0e8 100644 --- a/src/api/bpm/model/index.ts +++ b/src/api/bpm/model/index.ts @@ -25,7 +25,7 @@ export type ModelVO = { bpmnXml: string } -export const getModelPageApi = async (params) => { +export const getModelPage = async (params) => { return await request.get({ url: '/bpm/model/page', params }) } @@ -33,7 +33,7 @@ export const getModel = async (id: number) => { return await request.get({ url: '/bpm/model/get?id=' + id }) } -export const updateModelApi = async (data: ModelVO) => { +export const updateModel = async (data: ModelVO) => { return await request.put({ url: '/bpm/model/update', data: data }) } @@ -46,7 +46,7 @@ export const updateModelStateApi = async (id: number, state: number) => { return await request.put({ url: '/bpm/model/update-state', data: data }) } -export const createModelApi = async (data: ModelVO) => { +export const createModel = async (data: ModelVO) => { return await request.post({ url: '/bpm/model/create', data: data }) } diff --git a/src/components/DictTag/src/DictTag.vue b/src/components/DictTag/src/DictTag.vue index ecbfedb4..e3ba78d2 100644 --- a/src/components/DictTag/src/DictTag.vue +++ b/src/components/DictTag/src/DictTag.vue @@ -34,7 +34,7 @@ export default defineComponent({ return null } // 解决自定义字典标签值为零时标签不渲染的问题 - if (props.value === undefined) { + if (props.value === undefined || props.value === null) { return null } getDictObj(props.type, props.value.toString()) diff --git a/src/views/bpm/model/ModelForm.vue b/src/views/bpm/model/ModelForm.vue new file mode 100644 index 00000000..192c5b2f --- /dev/null +++ b/src/views/bpm/model/ModelForm.vue @@ -0,0 +1,219 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible" width="600"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="110px" + v-loading="formLoading" + > + <el-form-item label="流程标识" prop="key"> + <el-input + v-model="formData.key" + placeholder="请输入流标标识" + style="width: 330px" + :disabled="!!formData.id" + /> + <el-tooltip + v-if="!formData.id" + class="item" + effect="light" + content="新建后,流程标识不可修改!" + placement="top" + > + <i style="padding-left: 5px" class="el-icon-question"></i> + </el-tooltip> + <el-tooltip v-else class="item" effect="light" content="流程标识不可修改!" placement="top"> + <i style="padding-left: 5px" class="el-icon-question"></i> + </el-tooltip> + </el-form-item> + <el-form-item label="流程名称" prop="name"> + <el-input + v-model="formData.name" + placeholder="请输入流程名称" + :disabled="!!formData.id" + clearable + /> + </el-form-item> + <el-form-item v-if="formData.id" label="流程分类" prop="category"> + <el-select + v-model="formData.category" + placeholder="请选择流程分类" + clearable + style="width: 100%" + > + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="流程描述" prop="description"> + <el-input type="textarea" v-model="formData.description" clearable /> + </el-form-item> + <div v-if="formData.id"> + <el-form-item label="表单类型" prop="formType"> + <el-radio-group v-model="formData.formType"> + <el-radio + v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)" + :key="dict.value" + :label="dict.value" + > + {{ dict.label }} + </el-radio> + </el-radio-group> + </el-form-item> + <el-form-item v-if="formData.formType === 10" label="流程表单" prop="formId"> + <el-select v-model="formData.formId" clearable style="width: 100%"> + <el-option v-for="form in forms" :key="form.id" :label="form.name" :value="form.id" /> + </el-select> + </el-form-item> + <el-form-item + v-if="formData.formType === 20" + label="表单提交路由" + prop="formCustomCreatePath" + > + <el-input + v-model="formData.formCustomCreatePath" + placeholder="请输入表单提交路由" + style="width: 330px" + /> + <el-tooltip + class="item" + effect="light" + content="自定义表单的提交路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/create" + placement="top" + > + <i style="padding-left: 5px" class="el-icon-question"></i> + </el-tooltip> + </el-form-item> + <el-form-item + v-if="formData.formType === 20" + label="表单查看路由" + prop="formCustomViewPath" + > + <el-input + v-model="formData.formCustomViewPath" + placeholder="请输入表单查看路由" + style="width: 330px" + /> + <el-tooltip + class="item" + effect="light" + content="自定义表单的查看路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/view" + placement="top" + > + <i style="padding-left: 5px" class="el-icon-question"></i> + </el-tooltip> + </el-form-item> + </div> + </el-form> + <template #footer> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { ElMessageBox } from 'element-plus' +import * as ModelApi from '@/api/bpm/model' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + formType: 10, + name: '', + category: undefined, + description: '', + formId: '', + formCustomCreatePath: '', + formCustomViewPath: '' +}) +const formRules = reactive({ + category: [{ required: true, message: '参数分类不能为空', trigger: 'blur' }], + name: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }], + key: [{ required: true, message: '参数键名不能为空', trigger: 'blur' }], + value: [{ required: true, message: '参数键值不能为空', trigger: 'blur' }], + visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const open = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await ModelApi.getModel(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as ModelApi.ModelVO + if (formType.value === 'create') { + await ModelApi.createModel(data) + // 提示,引导用户做后续的操作 + await ElMessageBox.alert( + '<strong>新建模型成功!</strong>后续需要执行如下 4 个步骤:' + + '<div>1. 点击【修改流程】按钮,配置流程的分类、表单信息</div>' + + '<div>2. 点击【设计流程】按钮,绘制流程图</div>' + + '<div>3. 点击【分配规则】按钮,设置每个用户任务的审批人</div>' + + '<div>4. 点击【发布流程】按钮,完成流程的最终发布</div>' + + '另外,每次流程修改后,都需要点击【发布流程】按钮,才能正式生效!!!', + '重要提示', + { + dangerouslyUseHTMLString: true, + type: 'success' + } + ) + } else { + await ModelApi.updateModel(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + formType: 10, + name: '', + category: undefined, + description: '', + formId: '', + formCustomCreatePath: '', + formCustomViewPath: '' + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/bpm/model/editor/index.vue b/src/views/bpm/model/editor/index.vue index a59844dc..7e3d8413 100644 --- a/src/views/bpm/model/editor/index.vue +++ b/src/views/bpm/model/editor/index.vue @@ -62,10 +62,10 @@ const save = async (bpmnXml) => { } as unknown as ModelApi.ModelVO // 提交 if (data.id) { - await ModelApi.updateModelApi(data) + await ModelApi.updateModel(data) message.success('修改成功') } else { - await ModelApi.createModelApi(data) + await ModelApi.createModel(data) message.success('新增成功') } // 跳转回去 diff --git a/src/views/bpm/model/index.vue b/src/views/bpm/model/index.vue index 9a4df2e8..01e38a92 100644 --- a/src/views/bpm/model/index.vue +++ b/src/views/bpm/model/index.vue @@ -1,595 +1,249 @@ <template> <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <!-- 操作:新增 --> - <XButton - type="primary" - preIcon="ep:zoom-in" - title="新建流程" - v-hasPermi="['bpm:model:create']" - @click="handleCreate" + <!-- 搜索工作栏 --> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="流程标识" prop="key"> + <el-input + v-model="queryParams.key" + placeholder="请输入流程标识" + clearable + @keyup.enter="handleQuery" + class="!w-240px" /> - <!-- 操作:导入 --> - <XButton - type="warning" - preIcon="ep:upload" - :title="'导入流程'" - @click="handleImport" - style="margin-left: 10px" + </el-form-item> + <el-form-item label="流程名称" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入流程名称" + clearable + @keyup.enter="handleQuery" + class="!w-240px" /> - </template> - <!-- 流程名称 --> - <template #name_default="{ row }"> - <XTextButton :title="row.name" @click="handleBpmnDetail(row.id)" /> - </template> - <!-- 流程分类 --> - <template #category_default="{ row }"> - <DictTag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="Number(row?.category)" /> - </template> - <!-- 表单信息 --> - <template #formId_default="{ row }"> - <XTextButton - v-if="row.formType === 10" - :title="forms.find((form) => form.id === row.formId)?.name || row.formId" - @click="handleFormDetail(row)" - /> - <XTextButton v-else :title="row.formCustomCreatePath" @click="handleFormDetail(row)" /> - </template> - <!-- 流程版本 --> - <template #version_default="{ row }"> - <el-tag v-if="row.processDefinition">v{{ row.processDefinition.version }}</el-tag> - <el-tag type="warning" v-else>未部署</el-tag> - </template> - <!-- 激活状态 --> - <template #status_default="{ row }"> - <el-switch - v-if="row.processDefinition" - v-model="row.processDefinition.suspensionState" - :active-value="1" - :inactive-value="2" - @change="handleChangeState(row)" - /> - </template> - <!-- 操作 --> - <template #actionbtns_default="{ row }"> - <XTextButton - preIcon="ep:edit" - title="修改流程" - v-hasPermi="['bpm:model:update']" - @click="handleUpdate(row.id)" - /> - <XTextButton - preIcon="ep:setting" - title="设计流程" - v-hasPermi="['bpm:model:update']" - @click="handleDesign(row)" - /> - <XTextButton - preIcon="ep:user" - title="分配规则" - v-hasPermi="['bpm:task-assign-rule:query']" - @click="handleAssignRule(row)" - /> - <XTextButton - preIcon="ep:position" - title="发布流程" - v-hasPermi="['bpm:model:deploy']" - @click="handleDeploy(row)" - /> - <XTextButton - preIcon="ep:aim" - title="流程定义" - v-hasPermi="['bpm:process-definition:query']" - @click="handleDefinitionList(row)" - /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['bpm:model:delete']" - @click="handleDelete(row.id)" - /> - </template> - </XTable> - - <!-- 对话框(添加 / 修改流程) --> - <XModal v-model="dialogVisible" :title="dialogTitle" width="600"> - <el-form - :loading="dialogLoading" - el-form - ref="saveFormRef" - :model="saveForm" - :rules="rules" - label-width="110px" - > - <el-form-item label="流程标识" prop="key"> - <el-input - v-model="saveForm.key" - placeholder="请输入流标标识" - style="width: 330px" - :disabled="!!saveForm.id" - /> - <el-tooltip - v-if="!saveForm.id" - class="item" - effect="light" - content="新建后,流程标识不可修改!" - placement="top" - > - <i style="padding-left: 5px" class="el-icon-question"></i> - </el-tooltip> - <el-tooltip - v-else - class="item" - effect="light" - content="流程标识不可修改!" - placement="top" - > - <i style="padding-left: 5px" class="el-icon-question"></i> - </el-tooltip> - </el-form-item> - <el-form-item label="流程名称" prop="name"> - <el-input - v-model="saveForm.name" - placeholder="请输入流程名称" - :disabled="!!saveForm.id" - clearable - /> - </el-form-item> - <el-form-item v-if="saveForm.id" label="流程分类" prop="category"> - <el-select - v-model="saveForm.category" - placeholder="请选择流程分类" - clearable - style="width: 100%" - > - <el-option - v-for="dict in getDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY)" - :key="dict.value" - :label="dict.label" - :value="dict.value" - /> - </el-select> - </el-form-item> - <el-form-item label="流程描述" prop="description"> - <el-input type="textarea" v-model="saveForm.description" clearable /> - </el-form-item> - <div v-if="saveForm.id"> - <el-form-item label="表单类型" prop="formType"> - <el-radio-group v-model="saveForm.formType"> - <el-radio - v-for="dict in getDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)" - :key="parseInt(dict.value)" - :label="parseInt(dict.value)" - > - {{ dict.label }} - </el-radio> - </el-radio-group> - </el-form-item> - <el-form-item v-if="saveForm.formType === 10" label="流程表单" prop="formId"> - <el-select v-model="saveForm.formId" clearable style="width: 100%"> - <el-option v-for="form in forms" :key="form.id" :label="form.name" :value="form.id" /> - </el-select> - </el-form-item> - <el-form-item - v-if="saveForm.formType === 20" - label="表单提交路由" - prop="formCustomCreatePath" - > - <el-input - v-model="saveForm.formCustomCreatePath" - placeholder="请输入表单提交路由" - style="width: 330px" - /> - <el-tooltip - class="item" - effect="light" - content="自定义表单的提交路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/create" - placement="top" - > - <i style="padding-left: 5px" class="el-icon-question"></i> - </el-tooltip> - </el-form-item> - <el-form-item - v-if="saveForm.formType === 20" - label="表单查看路由" - prop="formCustomViewPath" - > - <el-input - v-model="saveForm.formCustomViewPath" - placeholder="请输入表单查看路由" - style="width: 330px" - /> - <el-tooltip - class="item" - effect="light" - content="自定义表单的查看路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/view" - placement="top" - > - <i style="padding-left: 5px" class="el-icon-question"></i> - </el-tooltip> - </el-form-item> - </div> - </el-form> - <template #footer> - <!-- 按钮:保存 --> - <XButton - type="primary" - :loading="dialogLoading" - @click="submitForm" - :title="t('action.save')" - /> - <!-- 按钮:关闭 --> - <XButton - :loading="dialogLoading" - @click="dialogVisible = false" - :title="t('dialog.close')" - /> - </template> - </XModal> - - <!-- 导入流程 --> - <XModal v-model="importDialogVisible" width="400" title="导入流程"> - <div> - <el-upload - ref="uploadRef" - :action="importUrl" - :headers="uploadHeaders" - :drag="true" - :limit="1" - :multiple="true" - :show-file-list="true" - :disabled="uploadDisabled" - :on-exceed="handleExceed" - :on-success="handleFileSuccess" - :on-error="excelUploadError" - :auto-upload="false" - accept=".bpmn, .xml" - name="bpmnFile" - :data="importForm" + </el-form-item> + <el-form-item label="流程分类" prop="category"> + <el-select + v-model="queryParams.category" + placeholder="请选择流程分类" + clearable + class="!w-240px" > - <Icon class="el-icon--upload" icon="ep:upload-filled" /> - <div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em> </div> - <template #tip> - <div class="el-upload__tip" style="color: red"> - 提示:仅允许导入“bpm”或“xml”格式文件! - </div> - <div> - <el-form - ref="importFormRef" - :model="importForm" - :rules="rules" - label-width="120px" - status-icon - > - <el-form-item label="流程标识" prop="key"> - <el-input - v-model="importForm.key" - placeholder="请输入流标标识" - style="width: 250px" - /> - </el-form-item> - <el-form-item label="流程名称" prop="name"> - <el-input v-model="importForm.name" placeholder="请输入流程名称" clearable /> - </el-form-item> - <el-form-item label="流程描述" prop="description"> - <el-input type="textarea" v-model="importForm.description" clearable /> - </el-form-item> - </el-form> - </div> - </template> - </el-upload> - </div> - <template #footer> - <!-- 按钮:保存 --> - <XButton - type="warning" - preIcon="ep:upload-filled" - :title="t('action.save')" - @click="submitFileForm" - /> - <XButton title="取 消" @click="uploadClose" /> - </template> - </XModal> - - <!-- 表单详情的弹窗 --> - <XModal v-model="formDetailVisible" width="800" title="表单详情" :show-footer="false"> - <form-create - :rule="formDetailPreview.rule" - :option="formDetailPreview.option" - v-if="formDetailVisible" - /> - </XModal> - - <!-- 流程模型图的预览 --> - <XModal title="流程图" v-model="showBpmnOpen" width="80%" height="90%"> - <my-process-viewer - key="designer" - v-model="bpmnXML" - :value="bpmnXML" - v-bind="bpmnControlForm" - :prefix="bpmnControlForm.prefix" - /> - </XModal> + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button + type="primary" + plain + @click="openForm('create')" + v-hasPermi="['bpm:model:create']" + > + <Icon icon="ep:plus" class="mr-5px" /> 新建流程 + </el-button> + <el-button type="success" plain @click="handleImport()" v-hasPermi="['bpm:model:import']"> + <Icon icon="ep:upload" class="mr-5px" /> 导入流程 + </el-button> + </el-form-item> + </el-form> </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list"> + <el-table-column label="流程标识" align="center" prop="key" /> + <el-table-column label="流程名称" align="center" prop="name" width="200"> + <template #default="scope"> + <el-button type="text" @click="handleBpmnDetail(scope.row)"> + <span>{{ scope.row.name }}</span> + </el-button> + </template> + </el-table-column> + <el-table-column label="流程分类" align="center" prop="category" width="100"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" /> + </template> + </el-table-column> + <el-table-column label="表单信息" align="center" prop="formType" width="200"> + <template #default="scope"> + <el-button v-if="scope.row.formId" type="text" @click="handleFormDetail(scope.row)"> + <span>{{ scope.row.formName }}</span> + </el-button> + <el-button + v-else-if="scope.row.formCustomCreatePath" + type="text" + @click="handleFormDetail(scope.row)" + > + <span>{{ scope.row.formCustomCreatePath }}</span> + </el-button> + <label v-else>暂无表单</label> + </template> + </el-table-column> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + :formatter="dateFormatter" + /> + <el-table-column label="最新部署的流程定义" align="center"> + <el-table-column + label="流程版本" + align="center" + prop="processDefinition.version" + width="100" + > + <template #default="scope"> + <el-tag v-if="scope.row.processDefinition"> + v{{ scope.row.processDefinition.version }} + </el-tag> + <el-tag v-else type="warning">未部署</el-tag> + </template> + </el-table-column> + <el-table-column + label="激活状态" + align="center" + prop="processDefinition.version" + width="80" + > + <template #default="scope"> + <el-switch + v-if="scope.row.processDefinition" + v-model="scope.row.processDefinition.suspensionState" + :active-value="1" + :inactive-value="2" + @change="handleChangeState(scope.row)" + /> + </template> + </el-table-column> + <el-table-column label="部署时间" align="center" prop="deploymentTime" width="180"> + <template #default="scope"> + <span v-if="scope.row.processDefinition"> + {{ formatDate(scope.row.processDefinition.deploymentTime) }} + </span> + </template> + </el-table-column> + </el-table-column> + <el-table-column label="操作" align="center" fixed="right" class-name="fixed-width"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openForm('update', scope.row.id)" + v-hasPermi="['bpm:model:update']" + > + 修改流程 + </el-button> + <!-- TODO tailow 了--> + <el-button link @click="openDetail(scope.row.id)" v-hasPermi="['bpm:form:query']"> + 详情 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['bpm:form:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </ContentWrap> + + <!-- 表单弹窗:添加/修改 --> + <ModelForm ref="formRef" @success="getList" /> + + <!-- 表单详情的弹窗 --> + <Dialog title="表单详情" v-model="detailVisible" width="800"> + <form-create :rule="detailData.rule" :option="detailData.option" /> + </Dialog> </template> -<script setup lang="ts"> -// 全局相关的 import -import { DICT_TYPE, getDictOptions } from '@/utils/dict' -import { FormInstance, UploadInstance } from 'element-plus' - -// 业务相关的 import -import { getAccessToken, getTenantId } from '@/utils/auth' -import * as FormApi from '@/api/bpm/form' +<script setup lang="ts" name="Form"> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { dateFormatter, formatDate } from '@/utils/formatTime' import * as ModelApi from '@/api/bpm/model' -import { allSchemas, rules } from './model.data' -import { setConfAndFields2 } from '@/utils/formCreate' +import ModelForm from './ModelForm.vue' +// import { setConfAndFields2 } from '@/utils/formCreate' +// const message = useMessage() // 消息弹窗 +// const { t } = useI18n() // 国际化 +// const { push } = useRouter() // 路由 -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 -const router = useRouter() // 路由 - -const showBpmnOpen = ref(false) -const bpmnXML = ref(null) -const bpmnControlForm = ref({ - prefix: 'flowable' +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + key: undefined, + name: undefined, + category: undefined }) -// ========== 列表相关 ========== -const [registerTable, { reload }] = useXTable({ - allSchemas: allSchemas, - getListApi: ModelApi.getModelPageApi -}) -const forms = ref() // 流程表单的下拉框的数据 +const queryFormRef = ref() // 搜索的表单 -// 设计流程 -const handleDesign = (row) => { - console.log(row, '设计流程') - router.push({ - name: 'modelEditor', - query: { - modelId: row.id - } - }) -} - -// 跳转到指定流程定义列表 -const handleDefinitionList = (row) => { - router.push({ - name: 'BpmProcessDefinitionList', - query: { - key: row.key - } - }) -} - -// 流程表单的详情按钮操作 -const formDetailVisible = ref(false) -const formDetailPreview = ref({ - rule: [], - option: {} -}) -const handleFormDetail = async (row) => { - if (row.formType == 10) { - // 设置表单 - const data = await FormApi.getForm(row.formId) - setConfAndFields2(formDetailPreview, data.conf, data.fields) - // 弹窗打开 - formDetailVisible.value = true - } else { - await router.push({ - path: row.formCustomCreatePath - }) - } -} - -// 流程图的详情按钮操作 -const handleBpmnDetail = (row) => { - // TODO 芋艿:流程组件开发中 - console.log(row) - ModelApi.getModel(row).then((response) => { - console.log(response, 'response') - bpmnXML.value = response.bpmnXml - // 弹窗打开 - showBpmnOpen.value = true - }) - // message.success('流程组件开发中,预计 2 月底完成') -} - -// 点击任务分配按钮 -const handleAssignRule = (row) => { - router.push({ - name: 'BpmTaskAssignRuleList', - query: { - modelId: row.id - } - }) -} - -// ========== 新建/修改流程 ========== -const dialogVisible = ref(false) -const dialogTitle = ref('新建模型') -const dialogLoading = ref(false) -const saveForm = ref() -const saveFormRef = ref<FormInstance>() - -// 设置标题 -const setDialogTile = async (type: string) => { - dialogTitle.value = t('action.' + type) - dialogVisible.value = true -} - -// 新增操作 -const handleCreate = async () => { - resetForm() - await setDialogTile('create') -} - -// 修改操作 -const handleUpdate = async (rowId: number) => { - resetForm() - await setDialogTile('edit') - // 设置数据 - saveForm.value = await ModelApi.getModel(rowId) - if (saveForm.value.category == null) { - saveForm.value.category = 1 - } else { - saveForm.value.category = Number(saveForm.value.category) - } -} - -// 提交按钮 -const submitForm = async () => { - // 参数校验 - const elForm = unref(saveFormRef) - if (!elForm) return - const valid = await elForm.validate() - if (!valid) return - - // 提交请求 - dialogLoading.value = true +/** 查询参数列表 */ +const getList = async () => { + loading.value = true try { - const data = saveForm.value as ModelApi.ModelVO - if (!data.id) { - await ModelApi.createModelApi(data) - message.success(t('common.createSuccess')) - } else { - await ModelApi.updateModelApi(data) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false + const data = await ModelApi.getModelPage(queryParams) + list.value = data.list + total.value = data.total } finally { - // 刷新列表 - await reload() - dialogLoading.value = false + loading.value = false } } -// 重置表单 -const resetForm = () => { - saveForm.value = { - formType: 10, - name: '', - courseSort: '', - description: '', - formId: '', - formCustomCreatePath: '', - formCustomViewPath: '' - } - saveFormRef.value?.resetFields() +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() } -// ========== 删除 / 更新状态 / 发布流程 ========== -// 删除流程 -const handleDelete = (rowId) => { - message.delConfirm('是否删除该流程!!').then(async () => { - await ModelApi.deleteModelApi(rowId) - message.success(t('common.delSuccess')) - // 刷新列表 - reload() - }) +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() } -// 更新状态操作 -const handleChangeState = (row) => { - const id = row.id - const state = row.processDefinition.suspensionState - const statusState = state === 1 ? '激活' : '挂起' - const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?' - message - .confirm(content) - .then(async () => { - await ModelApi.updateModelStateApi(id, state) - message.success(t('部署成功')) - // 刷新列表 - reload() - }) - .catch(() => { - // 取消后,进行恢复按钮 - row.processDefinition.suspensionState = state === 1 ? 2 : 1 - }) +/** 添加/修改操作 */ +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) } -// 发布流程 -const handleDeploy = (row) => { - message.confirm('是否部署该流程!!').then(async () => { - await ModelApi.deployModelApi(row.id) - message.success(t('部署成功')) - // 刷新列表 - reload() - }) -} +/** 删除按钮操作 */ +// const handleDelete = async (id: number) => { +// try { +// // 删除的二次确认 +// await message.delConfirm() +// // 发起删除 +// await FormApi.deleteForm(id) +// message.success(t('common.delSuccess')) +// // 刷新列表 +// await getList() +// } catch {} +// } -// ========== 导入流程 ========== -const uploadRef = ref<UploadInstance>() -let importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/bpm/model/import' -const uploadHeaders = ref() -const importDialogVisible = ref(false) -const uploadDisabled = ref(false) -const importFormRef = ref<FormInstance>() -const importForm = ref({ - key: '', - name: '', - description: '' -}) - -// 导入流程弹窗显示 -const handleImport = () => { - importDialogVisible.value = true -} -// 文件数超出提示 -const handleExceed = (): void => { - message.error('最多只能上传一个文件!') -} -// 上传错误提示 -const excelUploadError = (): void => { - message.error('导入流程失败,请您重新上传!') -} - -// 提交文件上传 -const submitFileForm = () => { - uploadHeaders.value = { - Authorization: 'Bearer ' + getAccessToken(), - 'tenant-id': getTenantId() - } - uploadDisabled.value = true - uploadRef.value!.submit() -} -// 文件上传成功 -const handleFileSuccess = async (response: any): Promise<void> => { - if (response.code !== 0) { - message.error(response.msg) - return - } - // 重置表单 - uploadClose() - // 提示,并刷新 - message.success('导入流程成功!请点击【设计流程】按钮,进行编辑保存后,才可以进行【发布流程】') - await reload() -} -// 关闭文件上传 -const uploadClose = () => { - // 关闭弹窗 - importDialogVisible.value = false - // 重置上传状态和文件 - uploadDisabled.value = false - uploadRef.value!.clearFiles() - // 重置表单 - importForm.value = { - key: '', - name: '', - description: '' - } - importFormRef.value?.resetFields() -} - -// ========== 初始化 ========== +/** 初始化 **/ onMounted(() => { - // 获得流程表单的下拉框的数据 - FormApi.getSimpleFormList().then((data) => { - forms.value = data - }) + getList() }) </script> diff --git a/src/views/bpm/model/model.data.ts b/src/views/bpm/model/model.data.ts deleted file mode 100644 index 89e886cc..00000000 --- a/src/views/bpm/model/model.data.ts +++ /dev/null @@ -1,106 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' - -const { t } = useI18n() // 国际化 - -// 表单校验 -export const rules = reactive({ - key: [required], - name: [required], - category: [required], - formType: [required], - formId: [required], - formCustomCreatePath: [required], - formCustomViewPath: [required] -}) - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'key', - primaryType: null, - action: true, - actionWidth: '540px', - columns: [ - { - title: '流程标识', - field: 'key', - isSearch: true, - table: { - width: 120 - } - }, - { - title: '流程名称', - field: 'name', - isSearch: true, - table: { - width: 120, - slots: { - default: 'name_default' - } - } - }, - { - title: '流程分类', - field: 'category', - dictType: DICT_TYPE.BPM_MODEL_CATEGORY, - dictClass: 'number', - isSearch: true, - table: { - slots: { - default: 'category_default' - } - } - }, - { - title: '表单信息', - field: 'formId', - table: { - width: 180, - slots: { - default: 'formId_default' - } - } - }, - { - title: '最新部署的流程定义', - field: 'processDefinition', - isForm: false, - table: { - children: [ - { - title: '流程版本', - field: 'version', - slots: { - default: 'version_default' - }, - width: 80 - }, - { - title: '激活状态', - field: 'status', - slots: { - default: 'status_default' - }, - width: 80 - }, - { - title: '部署时间', - field: 'processDefinition.deploymentTime', - formatter: 'formatDate', - width: 180 - } - ] - } - }, - { - title: t('common.createTime'), - field: 'createTime', - isForm: false, - formatter: 'formatDate', - table: { - width: 180 - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From e83d5914f03392d03e52917e01befb8aaf4a1df9 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 26 Mar 2023 00:46:32 +0800 Subject: [PATCH 103/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E6=A8=A1=E5=9E=8B=E7=9A=84=E5=90=84=E7=A7=8D?= =?UTF-8?q?=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/bpm/model/index.ts | 6 +- src/views/bpm/model/ModelForm.vue | 12 +- src/views/bpm/model/ModelImportForm.vue | 153 ++++++++++++++++++++ src/views/bpm/model/index.vue | 178 +++++++++++++++++++++--- 4 files changed, 322 insertions(+), 27 deletions(-) create mode 100644 src/views/bpm/model/ModelImportForm.vue diff --git a/src/api/bpm/model/index.ts b/src/api/bpm/model/index.ts index 68a8d0e8..2e1d4e64 100644 --- a/src/api/bpm/model/index.ts +++ b/src/api/bpm/model/index.ts @@ -38,7 +38,7 @@ export const updateModel = async (data: ModelVO) => { } // 任务状态修改 -export const updateModelStateApi = async (id: number, state: number) => { +export const updateModelState = async (id: number, state: number) => { const data = { id: id, state: state @@ -50,10 +50,10 @@ export const createModel = async (data: ModelVO) => { return await request.post({ url: '/bpm/model/create', data: data }) } -export const deleteModelApi = async (id: number) => { +export const deleteModel = async (id: number) => { return await request.delete({ url: '/bpm/model/delete?id=' + id }) } -export const deployModelApi = async (id: number) => { +export const deployModel = async (id: number) => { return await request.post({ url: '/bpm/model/deploy?id=' + id }) } diff --git a/src/views/bpm/model/ModelForm.vue b/src/views/bpm/model/ModelForm.vue index 192c5b2f..ac536958 100644 --- a/src/views/bpm/model/ModelForm.vue +++ b/src/views/bpm/model/ModelForm.vue @@ -67,7 +67,12 @@ </el-form-item> <el-form-item v-if="formData.formType === 10" label="流程表单" prop="formId"> <el-select v-model="formData.formId" clearable style="width: 100%"> - <el-option v-for="form in forms" :key="form.id" :label="form.name" :value="form.id" /> + <el-option + v-for="form in formList" + :key="form.id" + :label="form.name" + :value="form.id" + /> </el-select> </el-form-item> <el-form-item @@ -120,7 +125,7 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { ElMessageBox } from 'element-plus' import * as ModelApi from '@/api/bpm/model' - +import * as FormApi from '@/api/bpm/form' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -145,6 +150,7 @@ const formRules = reactive({ visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }] }) const formRef = ref() // 表单 Ref +const formList = ref([]) // 流程表单的下拉框的数据 /** 打开弹窗 */ const open = async (type: string, id?: number) => { @@ -161,6 +167,8 @@ const open = async (type: string, id?: number) => { formLoading.value = false } } + // 获得流程表单的下拉框的数据 + formList.value = await FormApi.getSimpleFormList() } defineExpose({ open }) // 提供 open 方法,用于打开弹窗 diff --git a/src/views/bpm/model/ModelImportForm.vue b/src/views/bpm/model/ModelImportForm.vue new file mode 100644 index 00000000..6b53d322 --- /dev/null +++ b/src/views/bpm/model/ModelImportForm.vue @@ -0,0 +1,153 @@ +<template> + <ContentWrap> + <!-- 列表 --> + <XTable @register="registerTable"> + <template #toolbar_buttons> + <!-- 操作:导入 --> + <XButton + type="warning" + preIcon="ep:upload" + :title="'导入流程'" + @click="handleImport" + style="margin-left: 10px" + /> + </template> + </XTable> + + <!-- 导入流程 --> + <XModal v-model="importDialogVisible" width="400" title="导入流程"> + <div> + <el-upload + ref="uploadRef" + :action="importUrl" + :headers="uploadHeaders" + :drag="true" + :limit="1" + :multiple="true" + :show-file-list="true" + :disabled="uploadDisabled" + :on-exceed="handleExceed" + :on-success="handleFileSuccess" + :on-error="excelUploadError" + :auto-upload="false" + accept=".bpmn, .xml" + name="bpmnFile" + :data="importForm" + > + <Icon class="el-icon--upload" icon="ep:upload-filled" /> + <div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em> </div> + <template #tip> + <div class="el-upload__tip" style="color: red"> + 提示:仅允许导入“bpm”或“xml”格式文件! + </div> + <div> + <el-form + ref="importFormRef" + :model="importForm" + :rules="rules" + label-width="120px" + status-icon + > + <el-form-item label="流程标识" prop="key"> + <el-input + v-model="importForm.key" + placeholder="请输入流标标识" + style="width: 250px" + /> + </el-form-item> + <el-form-item label="流程名称" prop="name"> + <el-input v-model="importForm.name" placeholder="请输入流程名称" clearable /> + </el-form-item> + <el-form-item label="流程描述" prop="description"> + <el-input type="textarea" v-model="importForm.description" clearable /> + </el-form-item> + </el-form> + </div> + </template> + </el-upload> + </div> + <template #footer> + <!-- 按钮:保存 --> + <XButton + type="warning" + preIcon="ep:upload-filled" + :title="t('action.save')" + @click="submitFileForm" + /> + <XButton title="取 消" @click="uploadClose" /> + </template> + </XModal> + + </ContentWrap> +</template> + +<script setup lang="ts"> +import { getAccessToken, getTenantId } from '@/utils/auth' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 +const router = useRouter() // 路由 + +// ========== 导入流程 ========== +const uploadRef = ref<UploadInstance>() +let importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/bpm/model/import' +const uploadHeaders = ref() +const importDialogVisible = ref(false) +const uploadDisabled = ref(false) +const importFormRef = ref<FormInstance>() +const importForm = ref({ + key: '', + name: '', + description: '' +}) + +// 导入流程弹窗显示 +const handleImport = () => { + importDialogVisible.value = true +} +// 文件数超出提示 +const handleExceed = (): void => { + message.error('最多只能上传一个文件!') +} +// 上传错误提示 +const excelUploadError = (): void => { + message.error('导入流程失败,请您重新上传!') +} + +// 提交文件上传 +const submitFileForm = () => { + uploadHeaders.value = { + Authorization: 'Bearer ' + getAccessToken(), + 'tenant-id': getTenantId() + } + uploadDisabled.value = true + uploadRef.value!.submit() +} +// 文件上传成功 +const handleFileSuccess = async (response: any): Promise<void> => { + if (response.code !== 0) { + message.error(response.msg) + return + } + // 重置表单 + uploadClose() + // 提示,并刷新 + message.success('导入流程成功!请点击【设计流程】按钮,进行编辑保存后,才可以进行【发布流程】') + await reload() +} +// 关闭文件上传 +const uploadClose = () => { + // 关闭弹窗 + importDialogVisible.value = false + // 重置上传状态和文件 + uploadDisabled.value = false + uploadRef.value!.clearFiles() + // 重置表单 + importForm.value = { + key: '', + name: '', + description: '' + } + importFormRef.value?.resetFields() +} +</script> diff --git a/src/views/bpm/model/index.vue b/src/views/bpm/model/index.vue index 01e38a92..471a00fe 100644 --- a/src/views/bpm/model/index.vue +++ b/src/views/bpm/model/index.vue @@ -144,15 +144,43 @@ > 修改流程 </el-button> - <!-- TODO tailow 了--> - <el-button link @click="openDetail(scope.row.id)" v-hasPermi="['bpm:form:query']"> - 详情 + <el-button + link + type="primary" + @click="handleDesign(scope.row)" + v-hasPermi="['bpm:model:update']" + > + 设计流程 + </el-button> + <el-button + link + type="primary" + @click="handleAssignRule(scope.row)" + v-hasPermi="['bpm:task-assign-rule:query']" + > + 分配规则 + </el-button> + <el-button + link + type="primary" + @click="handleDeploy(scope.row)" + v-hasPermi="['bpm:model:deploy']" + > + 发布流程 + </el-button> + <el-button + link + type="primary" + v-hasPermi="['bpm:process-definition:query']" + @click="handleDefinitionList(scope.row)" + > + 流程定义 </el-button> <el-button link type="danger" @click="handleDelete(scope.row.id)" - v-hasPermi="['bpm:form:delete']" + v-hasPermi="['bpm:model:delete']" > 删除 </el-button> @@ -171,9 +199,20 @@ <!-- 表单弹窗:添加/修改 --> <ModelForm ref="formRef" @success="getList" /> - <!-- 表单详情的弹窗 --> - <Dialog title="表单详情" v-model="detailVisible" width="800"> - <form-create :rule="detailData.rule" :option="detailData.option" /> + <!-- 弹窗:表单详情 --> + <Dialog title="表单详情" v-model="formDetailVisible" width="800"> + <form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" /> + </Dialog> + + <!-- 弹窗:流程模型图的预览 --> + <Dialog title="流程图" v-model="bpmnDetailVisible" width="800"> + <my-process-viewer + key="designer" + v-model="bpmnXML" + :value="bpmnXML" + v-bind="bpmnControlForm" + :prefix="bpmnControlForm.prefix" + /> </Dialog> </template> @@ -181,11 +220,12 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { dateFormatter, formatDate } from '@/utils/formatTime' import * as ModelApi from '@/api/bpm/model' +import * as FormApi from '@/api/bpm/form' import ModelForm from './ModelForm.vue' -// import { setConfAndFields2 } from '@/utils/formCreate' -// const message = useMessage() // 消息弹窗 -// const { t } = useI18n() // 国际化 -// const { push } = useRouter() // 路由 +import { setConfAndFields2 } from '@/utils/formCreate' +const message = useMessage() // 消息弹窗 +const { t } = useI18n() // 国际化 +const { push } = useRouter() // 路由 const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 @@ -230,17 +270,111 @@ const openForm = (type: string, id?: number) => { } /** 删除按钮操作 */ -// const handleDelete = async (id: number) => { -// try { -// // 删除的二次确认 -// await message.delConfirm() -// // 发起删除 -// await FormApi.deleteForm(id) -// message.success(t('common.delSuccess')) -// // 刷新列表 -// await getList() -// } catch {} -// } +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await ModelApi.deleteModel(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +/** 更新状态操作 */ +const handleChangeState = async (row) => { + const state = row.processDefinition.suspensionState + try { + // 修改状态的二次确认 + const id = row.id + const statusState = state === 1 ? '激活' : '挂起' + const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?' + await message.confirm(content) + // 发起修改状态 + await ModelApi.updateModelState(id, state) + // 刷新列表 + await getList() + } catch { + // 取消后,进行恢复按钮 + row.processDefinition.suspensionState = state === 1 ? 2 : 1 + } +} + +/** 设计流程 */ +const handleDesign = (row) => { + push({ + name: 'modelEditor', + query: { + modelId: row.id + } + }) +} + +/** 发布流程 */ +const handleDeploy = async (row) => { + try { + // 删除的二次确认 + await message.confirm('是否部署该流程!!') + // 发起部署 + await ModelApi.deployModel(row.id) + message.success(t('部署成功')) + // 刷新列表 + await getList() + } catch {} +} + +/** 点击任务分配按钮 */ +const handleAssignRule = (row) => { + push({ + name: 'BpmTaskAssignRuleList', + query: { + modelId: row.id + } + }) +} + +/** 跳转到指定流程定义列表 */ +const handleDefinitionList = (row) => { + push({ + name: 'BpmProcessDefinitionList', + query: { + key: row.key + } + }) +} + +/** 流程表单的详情按钮操作 */ +const formDetailVisible = ref(false) +const formDetailPreview = ref({ + rule: [], + option: {} +}) +const handleFormDetail = async (row) => { + if (row.formType == 10) { + // 设置表单 + const data = await FormApi.getForm(row.formId) + setConfAndFields2(formDetailPreview, data.conf, data.fields) + // 弹窗打开 + formDetailVisible.value = true + } else { + await push({ + path: row.formCustomCreatePath + }) + } +} + +/** 流程图的详情按钮操作 */ +const bpmnDetailVisible = ref(false) +const bpmnXML = ref(null) +const bpmnControlForm = ref({ + prefix: 'flowable' +}) +const handleBpmnDetail = async (row) => { + const data = await ModelApi.getModel(row.id) + bpmnXML.value = data.bpmnXml || '' + bpmnDetailVisible.value = true +} /** 初始化 **/ onMounted(() => { From b587d7634cd6b57b13a6491a44ca626b1978c80d Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 26 Mar 2023 01:36:27 +0800 Subject: [PATCH 104/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E6=A8=A1=E5=9E=8B=E7=9A=84=E5=90=84=E7=A7=8D?= =?UTF-8?q?=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/bpm/model/ModelImportForm.vue | 224 +++++++++++------------- src/views/bpm/model/index.vue | 14 +- 2 files changed, 116 insertions(+), 122 deletions(-) diff --git a/src/views/bpm/model/ModelImportForm.vue b/src/views/bpm/model/ModelImportForm.vue index 6b53d322..ac26ac08 100644 --- a/src/views/bpm/model/ModelImportForm.vue +++ b/src/views/bpm/model/ModelImportForm.vue @@ -1,153 +1,137 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <!-- 操作:导入 --> - <XButton - type="warning" - preIcon="ep:upload" - :title="'导入流程'" - @click="handleImport" - style="margin-left: 10px" - /> - </template> - </XTable> - - <!-- 导入流程 --> - <XModal v-model="importDialogVisible" width="400" title="导入流程"> - <div> - <el-upload - ref="uploadRef" - :action="importUrl" - :headers="uploadHeaders" - :drag="true" - :limit="1" - :multiple="true" - :show-file-list="true" - :disabled="uploadDisabled" - :on-exceed="handleExceed" - :on-success="handleFileSuccess" - :on-error="excelUploadError" - :auto-upload="false" - accept=".bpmn, .xml" - name="bpmnFile" - :data="importForm" - > - <Icon class="el-icon--upload" icon="ep:upload-filled" /> - <div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em> </div> - <template #tip> - <div class="el-upload__tip" style="color: red"> - 提示:仅允许导入“bpm”或“xml”格式文件! - </div> - <div> - <el-form - ref="importFormRef" - :model="importForm" - :rules="rules" - label-width="120px" - status-icon - > - <el-form-item label="流程标识" prop="key"> - <el-input - v-model="importForm.key" - placeholder="请输入流标标识" - style="width: 250px" - /> - </el-form-item> - <el-form-item label="流程名称" prop="name"> - <el-input v-model="importForm.name" placeholder="请输入流程名称" clearable /> - </el-form-item> - <el-form-item label="流程描述" prop="description"> - <el-input type="textarea" v-model="importForm.description" clearable /> - </el-form-item> - </el-form> - </div> - </template> - </el-upload> - </div> - <template #footer> - <!-- 按钮:保存 --> - <XButton - type="warning" - preIcon="ep:upload-filled" - :title="t('action.save')" - @click="submitFileForm" - /> - <XButton title="取 消" @click="uploadClose" /> - </template> - </XModal> - - </ContentWrap> + <Dialog title="导入流程" v-model="modelVisible" width="400"> + <div> + <el-upload + ref="uploadRef" + :action="importUrl" + :headers="uploadHeaders" + :data="formData" + name="bpmnFile" + v-model:file-list="fileList" + :drag="true" + :auto-upload="false" + accept=".bpmn, .xml" + :limit="1" + :on-exceed="handleExceed" + :on-success="submitFormSuccess" + :on-error="submitFormError" + :disabled="formLoading" + > + <Icon class="el-icon--upload" icon="ep:upload-filled" /> + <div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em> </div> + <template #tip> + <div class="el-upload__tip" style="color: red"> + 提示:仅允许导入“bpm”或“xml”格式文件! + </div> + <div> + <el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px"> + <el-form-item label="流程标识" prop="key"> + <el-input + v-model="formData.key" + placeholder="请输入流标标识" + style="width: 250px" + /> + </el-form-item> + <el-form-item label="流程名称" prop="name"> + <el-input v-model="formData.name" placeholder="请输入流程名称" clearable /> + </el-form-item> + <el-form-item label="流程描述" prop="description"> + <el-input type="textarea" v-model="formData.description" clearable /> + </el-form-item> + </el-form> + </div> + </template> + </el-upload> + </div> + <template #footer> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </template> + </Dialog> </template> - <script setup lang="ts"> import { getAccessToken, getTenantId } from '@/utils/auth' - -const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 -const router = useRouter() // 路由 -// ========== 导入流程 ========== -const uploadRef = ref<UploadInstance>() -let importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/bpm/model/import' -const uploadHeaders = ref() -const importDialogVisible = ref(false) -const uploadDisabled = ref(false) -const importFormRef = ref<FormInstance>() -const importForm = ref({ +const modelVisible = ref(false) // 弹窗的是否展示 +const formLoading = ref(false) // 表单的加载中 +const formData = ref({ key: '', name: '', description: '' }) +const formRules = reactive({ + key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }], + name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref +const uploadRef = ref() // 上传 Ref +const importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/bpm/model/import' +const uploadHeaders = ref() // 上传 Header 头 +const fileList = ref([]) // 文件列表 -// 导入流程弹窗显示 -const handleImport = () => { - importDialogVisible.value = true -} -// 文件数超出提示 -const handleExceed = (): void => { - message.error('最多只能上传一个文件!') -} -// 上传错误提示 -const excelUploadError = (): void => { - message.error('导入流程失败,请您重新上传!') +/** 打开弹窗 */ +const open = async () => { + modelVisible.value = true + resetForm() } +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 -// 提交文件上传 -const submitFileForm = () => { +/** 重置表单 */ +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + if (fileList.value.length == 0) { + message.error('请上传文件') + return + } + // 提交请求 uploadHeaders.value = { Authorization: 'Bearer ' + getAccessToken(), 'tenant-id': getTenantId() } - uploadDisabled.value = true + formLoading.value = true uploadRef.value!.submit() } -// 文件上传成功 -const handleFileSuccess = async (response: any): Promise<void> => { + +/** 文件上传成功 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitFormSuccess = async (response: any): Promise<void> => { if (response.code !== 0) { message.error(response.msg) + formLoading.value = false return } - // 重置表单 - uploadClose() - // 提示,并刷新 + // 提示成功 message.success('导入流程成功!请点击【设计流程】按钮,进行编辑保存后,才可以进行【发布流程】') - await reload() + // 发送操作成功的事件 + emit('success') } -// 关闭文件上传 -const uploadClose = () => { - // 关闭弹窗 - importDialogVisible.value = false + +/** 上传错误提示 */ +const submitFormError = (): void => { + message.error('导入流程失败,请您重新上传!') + formLoading.value = false +} + +/** 重置表单 */ +const resetForm = () => { // 重置上传状态和文件 - uploadDisabled.value = false - uploadRef.value!.clearFiles() + formLoading.value = false + uploadRef.value?.clearFiles() // 重置表单 - importForm.value = { + formData.value = { key: '', name: '', description: '' } - importFormRef.value?.resetFields() + formRef.value?.resetFields() +} + +/** 文件数超出提示 */ +const handleExceed = (): void => { + message.error('最多只能上传一个文件!') } </script> diff --git a/src/views/bpm/model/index.vue b/src/views/bpm/model/index.vue index 471a00fe..cd744986 100644 --- a/src/views/bpm/model/index.vue +++ b/src/views/bpm/model/index.vue @@ -52,7 +52,7 @@ > <Icon icon="ep:plus" class="mr-5px" /> 新建流程 </el-button> - <el-button type="success" plain @click="handleImport()" v-hasPermi="['bpm:model:import']"> + <el-button type="success" plain @click="openImportForm" v-hasPermi="['bpm:model:import']"> <Icon icon="ep:upload" class="mr-5px" /> 导入流程 </el-button> </el-form-item> @@ -196,9 +196,12 @@ /> </ContentWrap> - <!-- 表单弹窗:添加/修改 --> + <!-- 表单弹窗:添加/修改流程 --> <ModelForm ref="formRef" @success="getList" /> + <!-- 表单弹窗:导入流程 --> + <ModelImportForm ref="importFormRef" @success="getList" /> + <!-- 弹窗:表单详情 --> <Dialog title="表单详情" v-model="formDetailVisible" width="800"> <form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" /> @@ -222,6 +225,7 @@ import { dateFormatter, formatDate } from '@/utils/formatTime' import * as ModelApi from '@/api/bpm/model' import * as FormApi from '@/api/bpm/form' import ModelForm from './ModelForm.vue' +import ModelImportForm from '@/views/bpm/model/ModelImportForm.vue' import { setConfAndFields2 } from '@/utils/formCreate' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -269,6 +273,12 @@ const openForm = (type: string, id?: number) => { formRef.value.open(type, id) } +/** 添加/修改操作 */ +const importFormRef = ref() +const openImportForm = () => { + importFormRef.value.open() +} + /** 删除按钮操作 */ const handleDelete = async (id: number) => { try { From 1134921a0ab6364dec2366999f54d67d729cfc04 Mon Sep 17 00:00:00 2001 From: wuxiran <wuxiran@outlook.com> Date: Sun, 26 Mar 2023 04:25:34 +0800 Subject: [PATCH 105/184] =?UTF-8?q?1=E3=80=81=E5=BE=AE=E4=BF=A1=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E6=9B=B4=E6=96=B0vue3=EF=BC=8C=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=AF=E8=83=BD=E8=BF=98=E6=9C=89=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/dict.ts | 23 +- src/utils/formatTime.ts | 57 +- src/views/mp/components/img.png | Bin 0 -> 15404 bytes src/views/mp/components/wx-location/main.vue | 72 ++ src/views/mp/components/wx-msg/card.scss | 101 +++ src/views/mp/components/wx-msg/comment.scss | 88 +++ src/views/mp/components/wx-msg/main.vue | 338 ++++++++++ src/views/mp/components/wx-music/main.vue | 60 ++ src/views/mp/components/wx-news/main.vue | 107 +++ src/views/mp/components/wx-reply/main.vue | 634 ++++++++++++++++++ .../mp/components/wx-video-play/main.vue | 117 ++++ .../mp/components/wx-voice-play/main.vue | 100 +++ src/views/mp/freePublish/index.vue | 394 ++++++++++- src/views/mp/message/index.vue | 261 ++++++- 14 files changed, 2348 insertions(+), 4 deletions(-) create mode 100644 src/views/mp/components/img.png create mode 100644 src/views/mp/components/wx-location/main.vue create mode 100644 src/views/mp/components/wx-msg/card.scss create mode 100644 src/views/mp/components/wx-msg/comment.scss create mode 100644 src/views/mp/components/wx-msg/main.vue create mode 100644 src/views/mp/components/wx-music/main.vue create mode 100644 src/views/mp/components/wx-news/main.vue create mode 100644 src/views/mp/components/wx-reply/main.vue create mode 100644 src/views/mp/components/wx-video-play/main.vue create mode 100644 src/views/mp/components/wx-voice-play/main.vue diff --git a/src/utils/dict.ts b/src/utils/dict.ts index 15e57ff2..05c70dad 100644 --- a/src/utils/dict.ts +++ b/src/utils/dict.ts @@ -70,6 +70,23 @@ export const getDictObj = (dictType: string, value: any) => { }) } +/** + * 获得字典数据的文本展示 + * + * @param dictType 字典类型 + * @param value 字典数据的值 + */ +export const getDictLabel = (dictType: string, value: any) => { + const dictOptions: DictDataType[] = getDictOptions(dictType) + const dictLabel = ref('') + dictOptions.forEach((dict: DictDataType) => { + if (dict.value === value) { + dictLabel.value = dict.label + } + }) + return dictLabel.value +} + export enum DICT_TYPE { USER_TYPE = 'user_type', COMMON_STATUS = 'common_status', @@ -123,5 +140,9 @@ export enum DICT_TYPE { PAY_ORDER_STATUS = 'pay_order_status', // 商户支付订单状态 PAY_ORDER_REFUND_STATUS = 'pay_order_refund_status', // 商户支付订单退款状态 PAY_REFUND_ORDER_STATUS = 'pay_refund_order_status', // 退款订单状态 - PAY_REFUND_ORDER_TYPE = 'pay_refund_order_type' // 退款订单类别 + PAY_REFUND_ORDER_TYPE = 'pay_refund_order_type', // 退款订单类别 + + // ========== MP 模块 ========== + MP_AUTO_REPLY_REQUEST_MATCH = 'mp_auto_reply_request_match', // 自动回复请求匹配类型 + MP_MESSAGE_TYPE = 'mp_message_type' // 消息类型 } diff --git a/src/utils/formatTime.ts b/src/utils/formatTime.ts index 2582beee..ec7f3744 100644 --- a/src/utils/formatTime.ts +++ b/src/utils/formatTime.ts @@ -11,10 +11,65 @@ import dayjs from 'dayjs' * @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ" * @returns 返回拼接后的时间字符串 */ -export function formatDate(date: Date, format: string): string { +export function formatDate(date: Date, format?: string): string { + // 日期不存在,则返回空 + if (!date) { + return '' + } + // 日期存在,则进行格式化 + if (format === undefined) { + format = 'YYYY-MM-DD HH:mm:ss' + } return dayjs(date).format(format) } +// TODO 芋艿:稍后去掉 +// 日期格式化 +export function parseTime(time: any, pattern?: string) { + if (arguments.length === 0 || !time) { + return null + } + const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'object') { + date = time + } else { + if (typeof time === 'string' && /^[0-9]+$/.test(time)) { + time = parseInt(time) + } else if (typeof time === 'string') { + time = time + .replace(new RegExp(/-/gm), '/') + .replace('T', ' ') + .replace(new RegExp(/\.\d{3}/gm), '') + } + if (typeof time === 'number' && time.toString().length === 10) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => { + let value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { + return ['日', '一', '二', '三', '四', '五', '六'][value] + } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return time_str +} + /** * 获取当前日期是第几周 * @param dateTime 当前传入的日期值 diff --git a/src/views/mp/components/img.png b/src/views/mp/components/img.png new file mode 100644 index 0000000000000000000000000000000000000000..c25a6e762f3a84c5a7f05c6383de843937725fcf GIT binary patch literal 15404 zcmcJ0Wn5HU_^k>ef`9@70*a!55(CmDf*>lXv@i%F-7s{gG$I`{AnnjKz(@(wF_iSs zG2|dEeGkU#i|_y5`@0|R2S0c?bM}e7*R!6r)(&_s_k@Umn&8ZtGepmxN+_H;bG94! zM~Qz9IO2MFLGR2Np^aw}VoDCLR$$JMk$%MdhAav_g4%70lJy~Z!TIz(xzUTXy9zG; z;UB~3jfUTA1-QB_Uc>A~Jz1LD<Thiwo|%`hnN=EZ1XVNRL<OVWyc6Tb;*R>pwoAun z%(ZbQ8eg43TomPC$m6M53mbGc^RQd*ox*F_iWiQc6^~!R7PIfzB`$+lLA5W0^2VKx z_epLLoqsk-0Y?vg(v%Ln(7U#`GJ1G8S`p=-Bj~nJdvw$*Wcy}#E}AzDr6frQR=S~> z!ERpVvfIPYsadff`79rOYsuU!3puRNt{5&I{dPdqAY5-BqZ~6j{^_fh-z&CauTS{y zj{Z2W+z)N<QQe64;Ev0(iO=>m<VX{djS;NsVRaRTX)g3_Sl?7pGtcX6OEz??LKJb_ zyO%2R&E0)#+TCsQ%5H>EqD(qzfJ6tg9F+h4b8q4AbnqQo4grCSBm`6PSd(w#M}yNc z<wZ|WdM{?v2QBYn>RO71if7iO8D)x87)%6H{FTY>8W@-lRYUq72NQC<@_p~i{}~QZ z3|e``!{S#L!3>f9jBK2FZ;ic}Ad0!cM@y)>jY5_{(<bYY#=9+jQKl;`LE&uHePGFE z^{&3bh&sKY7qjFlM~`Wz8<8}l5Y|tSOwK^abmI)%C4!%CJqwl~mwAET-p-B!=Imm5 z8$3jaQE;XvwNb+mPlH#2_l`XD@;u8SA&aqW5m)&5ZLzxC(f$oY1buEW8o9`d4t3r% zDc+lp4NMFYW`j4SU^D@8%GrIUyloXKAy4lpWcT^o$$x;nJ6kAWy+3)osV?e)Rh}c$ z*Ra&m{H%l*H<rOS4ejn>?>~UQXi=Y|{#<o_o3lM8EvihimQYpvZCxBhVDJ#^8<*ZE zm#N;)5D_P1mD@dZ_YOVBQdofv=vGW8Sz)4xYdjgYzFwkGjg_204ReF82ePr-Kq?IK zXF{zky&Fp|8pi$5%a18aNfRn{B}}k@uc3Ix?@mKn^f>HWA~&cX7dpPoBT#X3TQ+~d zu}XY9RNxW@FYPI}GHbp$H}9tqMAU&P>%8iFmLVZNszjMPiq9(+Y(6P5!vt*un<-&I zcgyYb+R+mV2MuV~6#cJnOF?3_-M6+;S0ALb^VL*fpxH9K?&mzKS{f656Kx`{>AALQ zg*ztUS_LqnuHRdRN(V57=h)qT{}iFl;7(2Owv<#1?RweWx*I7T#u6RWxA#1Zyd2eh zZU=JV%#QeFzTgB1gJgw`s^?eaTgz@_vl<5!vWAVz3{HkHXjbgHR4Xxevz@mAaY|mA z1?zEAAjj?}q8K9E^ioC_7K~KN=fS$Hmu*0!#1wG>{#>OfhC016t8OH|*Y{d}p_qYO z>Mj*}{|b^Rd5|EbnB+Md5as{p1NQhT@uS;wdft4WFr71$liavgX9Qyi4Oy<#k6u_~ zqJ#<IeiJgU-idgZP$kPxCieC`tnGYB6#f*}`+GzX>iW5PaBJgkkR_+rrSpIN&JK0` z%mL^LZecwRrco*z*8d2-nLFe8Tv&0`(9S7W>TQyH^9KmqHA+l$%+k9X?m9!aQiibR z4_zV?6a2(!=h3fgqd!lT+nC>^YRlWCj*^KzR9SBssxvMd0>g8XAta4SXe%o$4NX6+ zE11}5cdGXAb{F!C7wT$LI^@fOQ*l*vK>V=iooE!VO5oa4{+D8y8^I5e(^KG~tg{Tl zoK*QxX<6J6ZcACm-5W$2F)|Mrrvi=?GhTXck7~Cop!q<<u!ir%)@^!7i42P{Gm4Rr zsr0c)FV~fM9SS}ak&i-ye*UZ2$vtVgOwNef=b{A3kjLMc?1P~!2@FCxmU{1?($0&U z8p~g?V;L*m?Hx@YCxVSi6yq}3MqQOMzy?~mnQ@a65Y@!92KVD}3axd?LWo68y%9J? zcy^uWO7C&Jp{7Czj)EG9Tccx9ruoyrH9=KiayGRgi}FU9Dr#=1lowH=5K9I)6*8@J zrTS>Tks=}%<O_3uO@nVQ%T=N*{n@1VtNK@pBaNVt3wBO)5U3Ded^VzDwmot^c4=Rl z=>wzzxaDOi3rS77_Jk+IEbWaWl#LCUaS7^mwWAQl<DO7nh?*$=oOHnz6JK^a_@*>b zvjPQX37mVkaaG8VA7U;VFQY8j(f5e^qXPwu1uNnYyyJOrol`bfj^%8jJ*d)@VN^u= zI-=aKf1E~agOlIZ+_-QwTYvVsgMyy<MHnIR2}Z{X4qUszKd?AjHEQK%A%;fIRh@sc zK2dzBAXATCPc&N4coNLkU0EDlZiSWHp(O-PKblE__<gQ|=qW$TG4Xerf8zjk6^w*L zk@n1G40#k^DrHJ~YOHKKH^C~`e`m>aEfzj?We5pjN-dONHGlq5=jX^EL3?~*tGt|q zP&^ZsUaR7FZ>>R&l`SrELGC6Nkm%a&H2M92y#?ZSPPr!?((ZRGGbMF1QL_`<kS~Qp zad&RPv=$JHkGHJoQ?LhnX9NdGZLpVT0%vs)BII|@=RrPxJ_OG^wWLDHvchIdudBll zn|A~Us81lKiO2?frGX3+N<I&k?)v%P)646DIe_4I=fR*4xe}M{>FSffHXyHH-2X2J z^64r#plu|v-uy8S)A0T*@yB8BJycQMBsO^r^I)x)Ms_0!F+^kYi4P;Re0sy~`~3bJ zI{6;^KmyHm2Qcn;Hi7Z|mZpv|Mrc4VO{I0H8?esBFvL$--@9ynk1)`}@{DNmJKgL; zMuu!qPR?BM*{8UF0OKQ>jDl>6qmh~NNptT~AXmJ78`5D+6{r#u4En5^8_i6%mn~@7 z=WV&?$)Hx4@TYf3(N=%3Y6-V#_<<biym%?wt;<*Vr)j<UyO6comTqp<3UJ-xm+MU3 z6hZ`M3|{5?H%}(d^(mQ(d2l_k3SZN0JRv?>Vs;QHT8cqujEqU{@@{xj^7H1{Gk;Ep ziI+$_m*$sASil{pz~+>FQw#kJ)ls3$64I*1WU;7fw4En1yFs(<5M1h8FI~leTed6> zX(A|1I&c2G`4vB(LgkSqrsk-M{C?=u9^KNd)gAY}HKwGagGu+X3UJ~@Dbfh5^825W z-G-4=yh)u&K#@Dbt8a;53Tn^+#c<7uY6RT;vA*WP82<SoYyf0PPA6VwUp`7OGOOeA zl*iOpW3gTkn`v9dT~dBk*_kCUC}wY<>wXz|9aYP-r?u_rbSuKu1F`x!f>qZ07@>z1 z?ztmsX0Ix@dbO}FHupx@?~n*BLurB<n4?<bxC|H>83(n5Bklyy%4hc4Q2&_}w*EMi zt*-=P3TgUgdo?@svIb-X8Jm!JdfJHU48-j{`hw9_k4`pAlrNHVS=ZEE`HlaOwD^<C z!&-LtT{?_{>!eiA=Fq-SO?j0dTwRR-RkhU%^BW*Igo+bOV@Ub>j1d5n8SckdI1cYt zV%`!Iq<n3~2p+nPY2Cx~l3~lh-l1Au8`@bYDd9F&oAqaw-IGhYff+lZ5nF!dCnHE< z4{BB><~+~ih6?t++FNkzvB~k0fsn=k#Uq*bEYH?jku0NAB}d$Y<z>YXgO0M5^U|2` zn^C6p?<Z`tFI<DcV6N9;K~(${dCM?}ZJ9Ac7Bhdk{vCR*sPxas%hCeFL_qx^k&~Ia z^(@&po5aq|&O|`)X@-L3<oJ;ZrdDI);B&Gs3=+{Ya|RPm4-l2j0EX}p5;_60lM5os zjwyNi7WZ-Lom`$}udoe{bXb#pHutZf<ZDLWRDJ7AxUI_b)>4P`o%SM{#C>;Rr-Cb= z<w%nOv0Nn?mLML8o*7zo-&-I-F<x*S8WF{)d!eq!Z=zk?3US{BO~gUqx@D!84&geZ zuB~b^2REpXZ(X3b>CXyoBB}Iil#14(K!=KBihuzwJ&QLVbFSQ&W*G|mWMndpZaJ8J zCvv#?1!82kF<J1Y0J4z($kG$_Sh$e|)7oV?p?EgK4E(9ZGR)4%k~u9geQhFTWz5T( z;!siVurshQiHpm^>H|c8|Nh0dca+C>Z7^RtHX|u=cRV28TqZjrW4qtC)zMETtKGsN zyL&1mHlQg6qz+Oom*o62&!wA9z$^6<gYD0#*66&3u{@?2JL=9%I%-Pa_0O*ZzK99V z4_XAAbn97foQ@G(*K$`p0~O^JKylCNcnLNeJAUdkl-Ecntm0eJu`_1c*`hyNGO|a} zZH40p8qqgD{T$Hp(h{Xvy?d{0<y*Gj0Ac<x5(zdpGJ=12lB>dXBjTp`CZ9+0DeQ&D zhKdae>})Rdt~t1<lT3l9?_cDUsF9-_mcq5#5j$vkjQMfp1u7pH1o<&UBgy|D3WM)e zY3(ped-&s1okCRHYZw3Tf7pt`!m0@Ag!%Z7u^X+nZp564m3arlYTx<SGLJWU-H+C8 zuQSo=Bx{-B;QYJ~^h}^PFem1OoGr#iyqg}E?@m{d&?S7Y?c5o2d6)Dy_*PRV^7!!G zh2sOvlNovbdN0&t;t5K5x1Hr()r3{SZjcQ~Z^P9sBMCe@G=4m_)IZBU8HV}pn3wvC z=b;^XW%Kyw(aA5X3U_Asv;=zdV2}%<3KN23QwbL!G4A`dlp?UDW>3`YIpCi7#%v;3 zhy@2Oh9ZQ_Q5~8`i<3u3U-rlHT@I)WFp~6F#sG5c^Cix%8|i<2dKpYw#F`3uk8^X_ zbq`ro=6p0}G5L`pkv~Q;t)Ul|uRbzf33QoZ*KO$pP;7e0N7%W6r28-rfj2$rCH*8$ z6r<c-`(l;lSvS%KwCfE<TF1jDkR_V<hrX4%(GT(fx~@g?xM%|@mCnh>rwWQ;c=iZG z?|t(s=n4pK7vS<^C_ek+hNB}sI&@l*qTaB3*2lp3wt|F7c83S#{ocA(5&KF^3YyfZ zhnajeN20KyVYk{>XQp+kQCEdyZdn~Jd`P<1<ZT5pze~!-3RIPJZH}jx5<2vQ8m0h1 z2Og4#*e7A96&c+8(f#56Bh{!BwusOcD+Oj^5a~4`lY7e_-@ARNxVOk)lu0uIPKMmS zy5^Zb^>Nx0@&;(hOm5w{A1bl21T>a-xu~HAUH?u&ZajWILEBFyC6S%%P@r^BM-N8x z$HW5Qqc$N^?)VOxFu-?t{j0<xjb#?o^jVlB%ac?BmC#t;vnA86Bv~7~9k^9FrL;UZ zgSzyFTs_242=!cd(1g7WR{LD7nqB(9N8LJw@vPc;8R7Q|akA`YCA`#eaMe|?fQTYo z2?Z74x_|#QRkY8YXS3ErhPTcFg_FkOQ_Iy0Fd^<x--I0$@{<MvhTEqV_|Ct3u=NT+ zm0pmjhcJ_s91@Kdd)emo8P9{8e0sO^o!Q6`D#nKT@UgMGjH#CM-wJHZ?+60j?VwY1 zh#|L><n-G2fHyPffLVK~bo-D@U=r>Qp;n<6`BnjNec-bnZ@gs&rsuCt>)WP?ouZ^a za2wa*?(`2&$iGBn35;_vc1}<=SoE&4>Gq=UwSarp`w|$ITyq|p;u{aRSz*#oGkbCN zk~>hSo)>r7ul$(l3}kJ5s}@?jZFC?riw_XZ=hdZU*4_{A`Wv9q!$%!ot3A8A#Fup6 z+}4gHx50Iy)+K>3lv$aV>q&l&EK09|*#YFxF%K3x_Ya$Y1V3xUtoU$b*pR%a_(k?b zKR=*ez`vfsFn;B?s)uEdY}X};RJo<PVn&|XU|H%|FmuTT?Q|T^zc%()D*5sb>pl1= zqcWPg+P=O|n(?CX`|y<_;^MN;sIsG+*bZbDatb#Esa~OhlDYRT-3H#v(y=`%1x--< z7|oo$rDEZVxV!&xbj=29Z!>bVwzg%`QITVI4`6uk@j(-`iwL>xlLV^f_g~^5I6b;q zJw|yHDPoT^x{$6Z8&TUsi_jE(cdBLZUiM`$Ee<Aer25-C1J#<5@*Os94T2vaor#iV zD|PP6p(T*dYG)hbj~NuGLYqV?Sk8w5Kg_G-nffTi=f08q@Yro5zXrKC88yPBNHP2a zJ|`NH%YK#@zaq~XNhQLP;`&AMXu{~>#`(y|w?VX|(+l9bn&>g)*6oH$_x*zvx@ub% zZC>_Y!@`0aNej!xlCuVoXP=?+wpD_j?OFMHa2I#Y=d#vM()-m93dZQf?yPp^X~HKS zaN}OP4;6^<1}X(qL(`%xdVNU3*u1*O)O^s*V~2wZf>BV~s6Ko}1`E1*yYV$Y-_6Hw zHu{F0GKa7^{U#7D#j|t|QviYr7O5#IDS3YPxiLaLF+c$>;r`~_p7c-LYp%ItQN`GK z=L)Xltiu%geH{wfYt70V5i4>%^)lvdTx@)NidH6}@2?dz<6e49bTVA+cm5b}H;GGl z<k9%%l?#ljKJL$|E7qoi=D|SS=t6=;4FV<_6U?m}#7?S?$0?}&BOsKoWh8*Dx{%dH zJ<Fwh>@=0#58N4R@H20{!T;H_|AXbfDZI~8+1T-s_Y>BC@%W!zhvl>^A9Csg#P4?W zQxT_w7C<tLUjflRxco~9SpQytMAXzcmyybUPEMZlpI$Lev^;?B`yKCmt2y4ivP(u> z_yGWRH8t?CW6_dK#feM+)P7o~jOneBiVHMd8q!V`@l3Q2F>ae>$A{eyAJV(m-J-Ao zS#yLUoWU@m>ouF&?#Hza;!3e)(Kg88*Ojk+>LkA(lh`Ul(<bi?SUvp9tg+vkQ#bIQ zH2!%{f_{cOE!g}3y8(@qvaiBcAPiwprbZ02y`5IIi_ue0K`&ZNwAVeU8a!LXQSV`{ ztgF>0lF!%q^m`nfGV^5fMqNMU%i~d2!u7kSm!%7Yec$UDJli{GM3G%rJD@F$NNCJ$ zKkVq@c$E+Ibf*VaDwilv8)jJdO^pU-!9q|Vhxx$AWTm+6B<8x5p8F*!X0gNgF(_{I z>p?#hMpqMA+>q#hg+Xg4j=hIFE-|#~M!$z$Ce;Mk$?l75g8gPTwFA*%P+5}XTea-) z8tBNPiBJB(gMWXgO1$HhD_3aoRkvG_?Z@A~Qp(%&On+?TUWlb-vc0Ck@radK&-0w( z0|ajf<Z`y#3O?jDde1AfAR(0<Me}XKMBDA=OF5cv#RM>xbAkim0~0k5tZ$-xSe$3T zam?bjo>Pjp8lN3T+5!Vwo9|9V-p;Vc278!h$*qv<-=?w1HiNZQDjRsSd|p8rIufA5 z_YAc{TNS?zhu8*BjN=Z<7b_u4`2|(aob;CHWGhzikPv3^##dwRKH|Uxf2qNh$3SO{ z3(H=8GoN_<Ic9i-Ag}YrHA{lnhVJpJuXUX!crL~A8sfuPe)ip#^`tE;e(#_cPqf+L z{qZ264)8Tg;Qk=lmr9~OLgHNISIS|K4w@X=u0&Uy;iPl=FCW=!M<t2t3~*=<dy3!2 zpw)~Jz3ToZL|qbo#6&;u>{(|AhEn=lIewx*jFg>85VGQNd-;B3A?c1j+FGmVp<+8y z$-uhCi&r3~kE{x5k>#?bDC%5<LI|C2=e)^9M^%QTG9i5R#lz^JtATB0`16wOJ82A_ zSK+c${)-kLF(&`B;(i1t+Cg5v!z}pPq9~8j@z`OsBTdX+n`DI-D*fGpU8c2<<8(gs zQQxfe9H{30%LEcPDz(IJEICJzp5pj9eJ!4f;R%z11V*lW>1#Si(GyFy0n(VaKs_D# zG&<ywEPfs)9jE4&sid=)T<h*U^R4-6TZ6Id-KPZL3j%KCR^~S&nR%VJ>MohBvGCgr zet<AVTa1lYug5$D507<0_t{(Q0i}|Yl;6%=l%UO5Kl>%@;+o2Jju{+)u8o#b9&Ijc z(X0rK%Prbs*MFUAZ_cf1V&4{bZ}$Gc&VXC<g~ndwc(>VpQ`0~`&+4{WcG364wjW>y z72vxmo52j5GrmQwq}v*3`#J1ka3-YFOzV47arU?wdOv!oG;++F@aNfre277;a<TPY zVK`^mSNpt`A7^jyjqh!&@*VFbWYfJux^gy{#86#30a8yIjJZu`VF3z3yY9DU1De#5 zUPbS{*BPsEJ=!K2|8!NmJ;)Z2RtV%^Lf1kVbgaA|MGa{b(5(9v+JYK5R>zYxD-^m* zg7$$Iz|0Q;HF?O60H3TrRwyPVerIJT{yXwKP)AM{y!%h2IbYieK=Q|l0LT-9AJWM$ z!2Sb7d#zH%BV(1O-z0Yq;tuGc04M+V(gh$kqY`Xk?$0Ya?J;h(QI+I^UN)<XYc4;{ zsO|Z60%lU1k}i#42LS1|$m)n58XSD}uS+$)jIj*____9uW@z($oS!1@kCH-blSf8t z0<grv)7Krig(?X`Jzf^YSpSR4Ezzc&D36T8nic_L3LEuE@H?EKOk<{r7*ooN!y{JO zN%)H1cnIHu?MO4c<UH|r#iO4G^$Fxx!PIC1{6gH*14C%J&urHtP21S3UgWJQE#a~j z%QoWfCByTSoZ2@lFj*TfUqWO*^RBAxkfml5@n6@uc&cE5{rE5EEk{A(GK#;}$oCyp zw<->k#C8|<@5I2BZ&5^Xa@DMwXOB%6&`f&<K4BF*{V2EwfGJ{~m$Qo3Tq2hCiA~!S zzhL{T24<aTaDmao3ayq~Q;vLiTZ5e?V1hiu70)IQ(3k&>=)@s!CR@h@RChj<MtlgA zhsy+jWEq$&^BBBPdIQ|;L8vW))0OGV73n)=bRyP(k|BgKcE&$Z0Y=Ja%tW+$jGYPR zjwWfv1!5jq#NYc=HmE^mQ$t*M`b%NE(lB!yK@5P#)(Mqns6gm_l;xSmDw=Dke7_Oz z4+)s3n>W{6ordq%xDHG|;5PUGL3)&u)`swnp7euTSM#AP-tvT9JKqmUuEsw2XvR6{ z@VpCY<3B>kt1xf16;AwISSR2DDkMrpDA(7ao}V;PW7cVdT=DxMvb6k!$}h3}Y1e}} zFGTnGGk{<;_>Dy@Rgw&3(R(T(x&!J<2pjsV195?sPME~FHj1Ez${W6qY8_;}F6nVz z0~AdEbE{dm=mBM)U(=#=fb&I!0uyFHJ*44YW1X8025lo5P#_zYJTx>k{fpzf(MSPG zL3Arj^NqZCg(n^sdF#SdBhvGE*5Gm<Ay@dz{D_SZmDI~ok36;gY1YpQVHg$R67;ne z4D;`wPuCY(V(ZsC_D58Y*8QYMXyPG-d?Zsu%WfbnGyj)=DeV4b#@qvjxyo}vAqctr zA!D{#(pz*LLMT+9ne(K4(yN;8>W!5<+aDytu6#j4Qh&4Sju6IF<d#aNhE{7wdpn9k zg&MZ4lj82TQgBTk<MyO%g^o_EroVEeESF$g{Ylu1+JAHEhGHm0yl{8fMV&6bHD{B$ zfgC&o5M*?`@?X5V*z8`_1<F@#T8^PNn^~Gpdz0HleFjpaDhxE6CeE)5AjtK8|5Pmc zprh>QV85g0Xl~I@)jUaNZJ0q)@`NY?{2J|NX$GtKYNlLVJt|WSF&Bj0m_P%s{PxO! zx%KB*$|ZnsUWE$Ub5cv!PvQAyy#j2yu1%M!B7SMY5f5&_qRnMv1h}<NI^=dY+!ztx zq3<}5btOQ;7%{x|Jty|{Sk1!<yYv(z)uB$`<88q3ddG#aw%kY70uG~HMI}elQ$5Zg zlMp*D%4tPn5<mmA-W9&Gw9uO=FexV^Lmt%dDB1Gs5}2Z|(xPTSSETF=Kx}Z=lD=4) ziv~&<NY5g+_aqH$5sA{R)n=?uw^2lInaEnmzXHz%ijvBdgVC=);6=?o7fB*KUU~l| z)!AUR?m{OKs%F2^8kXc8%#_G)pH=_?F(f`M8V-@5?=y}%FuF@c@Qc6`3)&mTuP#?P zT9DkJoSb{NBjRE@b>ZQ-^Tq==YR<QR*@91#w1js@i!oYe)k_i;fXdymvm4jsoh`J# zujMlO>41Uu5r1PBGCXh8MMR4^YCig$Am!wLWRxdf=-F|%Z|4P7BY}#Q3<i;{$jQqW z*8v?8Bdv*j4m9Xe1JKFf$BZ@thx++@0nf1T4HVY4q(TH};=edO7;||=<PbbG^1ywy z%<?O#JhaU$TwQo{Hw$st(H6r_H#R^#2Xf!C)B=Q*^NNw|c#5h^4@cdtlr^EC^&z|C zhN8@jRPF|Tc6+mBS~O(h?|PsxNje#B9Meo0sy-C}f$V54uexv4c2qCKX0}o=D<@70 znQ8S&mfJACzZqq8BGYu2Rxi4!^y5I=h%`t0U5vxm4=Ed8&IgEe^##v#BX0s;$L}WN z`jlw7V?H#SAT%&)Z>N}@dmR3gyI#$Hk*%)t;vUz=O1&|)f~;9&83h^fx`@~B4rmjw zE|&m+?{J<$S60)A-nc8GTy{Q~=xndEqq48$)g0OvqnQ@ehD{-TdqG@5R3^mMdDX0C zZ!AAVUcotKyV@Jl5}xP^wj{<v7qhw}TO;{HZRvrMj)h4lZSOE_de5KqRws4gmpK1F zsu5EXKh%i!Sth>%$nN1;b4Fk6h}Q$Xu>t%Gr;StYSrca#zVF^%4s&4UAsFS`v&vj< z8`s{5u2m_CVT{Eao2Zn^sP&e`r{}03LGLsJkMA}dQmSni{RI2p26pvS6lCN1>&1kf zBc==F0-QYTqq+s%dF2q<ky;_GLazKQIyddIfJp_VUP(dwYj228-Nh2i7L9XALZ@E} z){qQG!20yH0T%e50}d0vnDfTyR~!Xy%_ZI#%oh>2*GL}};_MF&_J{F`7UCI1_Y2Yx zT8YdS6hg3ZNYg^K_N5nh$%C8jG(TsI0NPo6>+(Mp;#K)sBPa9Ooa4uNx~snnMCzco zf-pc_e$YF-M{u2DRrdDJb_TV&h&ARwaLX97Maft+f6IrODYOtW8+XU+W<G5bis<QU zWS1#VHrK6J<*jeOEN0)cX0XX4`pGN*ZI2=nKXMDgq~PirtK9KRvyb#S=aXEzk<lL@ zS9I@K&Y^#2RX#E+_9I)LZ<(6Bsp5!<PYi9sv~Gor1@;|%xpjXK$&)P#Jk1oD94tZI zbSLYj0C}QPoW`P%y`5F6YG)H%7e~Q?dE6AyTG(v~QXQiP)b`8uBxrv7ROisg_U4jX z>|E}p{fn$jhr_Q5{sHQjKta65(qZnrQB+Gax?!*$WC;Kf|EZWrTke($C7C3QMgwig z6WE(yf(nty%<41dO_<fpmb^m>?=x;XDAxHle&d>qBXSHq|9P4oT(fnwLno}WJ0>q; zzRf!+ql>nj0~2F!sNd1lvM6Km-PPrc0OjN$D*AFR6%$|lACcO_7SzM^KkEaWoGP?$ z0aII-xmf9?Nq$*HNTcy<(3SP<=BQ;D%_Vup@OvLeZ3H!$qvT+Mk7ob|y2r{2S`i+y z&CnFW5PPJ3whf#1`@f|(<ScVEZ9qCr<pQcYCDQg+pTbP=ftm8{7f4=$u`CRCU^dbf z0?h9LpHsu%E@}BF-q5~^!AZJ<3}bG-vbs8!TTfOhUj|afE?2wkuGc<ZnXozN@phf% zFN+jZs;L^0e?jxW)C`zEWXASMSulpETZa-9Oy>u*p)qsRl-_Ebe?>p)&z;7~Om^nC z6`G3V87(EpN>}b85+=pIWPNiACZDR$v|l_tztu7afiHFOoX_*Tk=e#P0Lf(6!{z)Z zG;*RKvpCO!`}CA@zPbX9Jp#@Hf95;Ge@`3P(^siJp0??KKZ&QD_obHB47H(W@NDi) zpi_HQX8Z@Lz)bvrBlRAlH`h@y^~6@gL#o$7H1oyMwW}YkCGw5?9^e|wR})}`VuqBp zQ?;IuRN_s8ml<x^onHOo(`w~K_Ncz9jG7?TnU8AW4qXoH>>2`;0Bo!f^d;w=?Qp*N zvmyEAVq^bm)pvs8=Qd@}th*(#8zHLBJC74t=Q)Z;%afPCJ(>y9kn{{z`vvX4JQiYE zCv%Y+HLs?}-+<oRQbXe{i*g}W1hL0@wuBkn+JoG@za-UA+_-}9&hwOU`>-Ehg?ane zHTD8kQBiDW&2CS%+bTn(`L5-Z2n`}P;e`=xeQn<PVk+$7(h`$5aQ_0vKLi-?t4@`Q zKZ@J`Q-+5GO2d+Cr%t$r?@Aq(QOx2plBkjy2ism?vH1sW7SN&`l9xsR34X6EToIF} zY`rEC$XQ&_Z=8>kOM&E`M0HMp@Mpsh2<R*Ok%IQNuO;27p8;X9;mTXHU+`ci7k_A^ zf5I!tD~t$l1%6IgY~t54>bkh}km6Yz@(eoCABlt?%|6H~8*Eq_-xt^Q;T$g%Ud9`( zI4K6HJpcP*7*xod@rt<sSGq9o&WGjB_ZggRKqT#w20k6{KUev8N~HN~KQe-_4TjZG z7%}*E;YV})-&{fcX@aW)jMA@>D+S&v0@U}DFoEm|VqG@#XgpgpJ?H#zw|r>ChwHu7 zmk<b&WYQWy`89BAnTpIFvhZdy#7y*`^ug<qLuyfs0xwF-C*mDFm`h56HWT$*GbUI9 z&)PE#?@}c>0|Lm&Fd@aDC2|0GK4U9rpO68vBA^xHU;*}PSvhE)OPP7d_N#g@@%Nt) zu;~IQjSO3<)0PXZoQP@HNwxSLGmwW0Dg3zTe<esBpogY>rFh{2qBu@33IC0+EKh@4 zpxItqzI<X)YI{**gXnJEZG6JhQ<3%pk;elo(1?aigwZj)-&d6a>nCYoy~v&PNOr&$ zF3X!e)Vz}P6@4b;71Q<y+S9?VrOOxUqN=Px)9hKLna1u~v-egS+UC`k_S-i3yzi#y zG8RnrhjO%8N9aM0x#cz`Ex5M?FKFVNUWS6TaVI$^mL(c|&Mnxd2vON4gz`Y(^DYnA z5@Ac#l7aoz!HHIbX`R9F=<7V7w;SxgJNV!6GQb*QamIi(4b;W5)D__L0qbfAF^sy- z0=UBUj1`8u@4g1(?Z5EmbQ?`k@e!^vPE@{mCbDFBtnPDyY9(Y#_9DMxg6SB8cCRez zYV{cjNsJfJ2><3r;t`1no$S#QR#$`}ps3~kXsZpWtzoR9q#3$@al+XUO1As%!Q}=n z`<7RneCVg|+fXxGvQiVHxfkX}U)PhKTJ!^|mDYKt@W&ba%Gob6=6KvcdEf}D_77y% zhW4~<dh2nkC9R|SL{vQOJBjg5h3B@Sz=hzPem^guroFlq?NvJcnUv=KFMj<iqVj%{ z3Wapok)b*@CgruzWFXquNUwV7uj9mGn4T5w_i^~AgG7MkeCE{He;wFYKWc^GioFsz zh0-TkqZ|CH3$=hAx|6Vvm)5NG=~vN4METmV&c7+c|E+C-1^F%q>!~`0NHMmn@j!EU z1!<z{T<GKEC{<`+a`y!;LP#$D>s)9a--t=v-I0rI;aQ2A?48|qH8Vy<J!UE;nIax2 z1nys~u0Vw7>7?%L@?K947X7VVp)8$c@WPM9<(mGVRxT7Kt;<XqpQ~@nSl`ASdpF-K zhaDv+t-v3ymSthe+P-P;OhR*VFX&{W>f)Wbss|2WpM-~F``-!!=nh;~J9fSsHB(KY zk=@|jQ8t5&$fmjK8i}R|xOLU2Cc~l`A7P8pcp&)VNgTo6&~0tTzBADsv71M>@(93{ zag0IeIaT}Zotp2TY<H#kBP$L@NU8_N$9D;cuQTI-7Cuh51HKlg@&dOiVfW2ZQ4S$K z?g&kDK9#JEp}HyK%tCOO7@Ld3Px$Fr45?zO*=>!pTY(Cl2|qWmcD5sn*GnAZ6sm~H zpctt#Rw4FfQl5*p0U)Va243~5?QzK=SfCiK$&&7}UrzNyR0agT9ee?Mj(KpW)igQ; zD?l-dgx5)07?sv|(m*xM%_85-gK18(3xf404m=1@%5c+K3|X%P(U4~y3i7=)+*;g? zT|@f9eYaEyVJ1Njkw6CNKb@sx)6@b=HFw406~LIjRoijc?`He$_Xqk=aD`fmBlp?& zV7Kk6=b#A{yITfMW!mOqcJmAn<!D1DJ=Hg;x>uxI)w~AB4a!>+9x*tbJPLr)dZ8jI z+a>XJa-V5gG{qbleACp;5#d@|t-U8al<ajfQ<i8?)2e`d;N9;|Fm8o1_Z}%e7FRNU zYl1hBwrt$0sBDKOu%XVD=Byv?a(R{!q78bVcRW<OB7e_J@3tn)0_tCt*1t{YAbB(k zxbog?59uAS;H?EEVb$DR`^fP_O)Zu=1sjV2x?i~dqdRSL@L`?<deb2ITh0?-g(v?7 z-lLBNmo64XTr)9|wtA`Ln=_AiamcAnZ%^QFt+4TNmci=f;<z`bpR+)tmH81q&^P}Q zi2*uhK6V>3w?*V5y!HMBPj!Vq^p?<fEA14nHX)`ZDVTDqA#qbNnol5kj!U2MIq4Xg zH?|bMuIqeI9_oprNq<~e?KP)T5yJ&|bSK*1KadA-G{;qFw#j1_4rRo>##_Xk%Bi1` z(Ny}nZ8Ha9D1^(gxj=o@A?C78UNt<$F*?^^U%7O5HkkhuBw)J^OfmK-Ai97;#>-+& zviinH$%xh%a!bp!x7?t<6sQto-s>f!8uR0k-l=Hr7-zy9-~AX}3ciZD=j<*vr_s$X z!^O;E7SH|p<sWk9M&0wY#TW|_K(*B$*BKcV@1^*<<2w`|83RM&s*Ity3^Efg4ja!r z@fr==ji&a|xHeY71vQ}^XxpuZN5yP31o_SV=fQvU!CiV;_A(lUrXA9`y7`)b)354_ z9&s@irzTn5xRE&^j$=-|=|}8TEHCdA^|Axo<Y6T_>%aTzTG@u>ozu7Mn(p={-)dv` z`Pb`aI<%Vn-9qANC3HNe1OTvZ$+t%2lSS;czff3`P#&7iYMmB{78az${S&8qEJ+xH zN>8XXS8-52{n`GfPCK|5a{E2L#Yq<Qre7<N8~0&7@leDL-duw1JKYV3bt*GQA(2RH zB<nL$fbd;TqT<86rSGnr(t#@x;Ca^<44Iayu_g*Tk^(U6PxnvfzLT(q3t+rHyvH!# z!e*d<ao483Oy96a6eF1_7ZV-5zU!-)Dfi+F=?E!?bOQ)z;;!Muponna3`M<44Zp~x z0&D*|FhUdc2o%?vMwo4~g6hfX2_K~c;>Aq{|0D`8eF+b{0|$ecNH3oBu%klGlp+r1 zmPezQsHFp1U16&QwB@HU(|o-Y*GaszL6_7rb(zJ(F8abLNt4{mTiIU`|4oAcSJLTn zF<9+zCa%Fb>(<mk;#R#g=X}7edkFx%pFxlFiuuK&9I4xy2Kp#anINK`#TIl6CP&l2 z8ay=gQt)57P`^1!`N--Bal}542P)mPy0eqk6?{}TSn%CN@Yre@oIaqNJY=k5@Q5)E zFKuW_(n5feD^voqlpC_xo^5B&8AUyT21JK0q`&9C*rOKPFs1$OT``-y>z;;r{n%)( zR>@rC>Z)!L+Q=+tHhk@=t}*k=9P`coY{)G@kT^-G!Y0%zni`Qu-YA5RKhLj{v6X{d zwa_gs=rOW&%9XQsL>7)Murlr?8S|Us3B?>S<5B{lRtf_kN+AeP{%4|6e{vXb=kjG( z$x{bG5uU5k1D^up51Fmgy`gQuwvS(Juv^~pN@9&cH$gif6%vl{eUtA)>6`kUFLfQ7 z=qtZnQd{rIMiZ>KNyA6g6n7O*T2UhmX3+<geLy+FF<43@B+S|RE1|svCYnvHfa&zq z(5pVe411t#L%s1n0}(E>nvZ<R_Y;j_9vi_{czm)G@d8CKC*&(#iWT=u7qhrRZgFnl zxSq!j<30VxyyBzZUu8A4yr4e9J`sOF_~KYU)b(MB2lp?Vt`CgKf~2H1o8`_hxw8J^ z;(#q=KE{IK<h}3oL}9$}@JeuEh4=n4wWjrg;+u6(HfItW!F?Q2A<hC<@1hU8f^70l zde=1jk(70IS`SH2s!mx_b}~6~r7k%Cao!IB>yMb|8J4M%@}NhV&T6z_>utFUj@_Xq z(hl9MgJ__r#4}|Zvd`<2d2`+g#M>;>*5;vt5Wapr<tHq+QCchU-vKH$d~<zB{{iJ% z*@^0r(1rmP7X`t2sa%ydt`~qHY$5ZfML+Sy(Um}VJ)3ehJZqGE^)gvuiYE$DAC>`^ zDdmKx<FXkH=W?6@O7;WSA*cTGquTRZhCfP*5Js-8a8O2P&nEa0lty32*f`J)2Koa# z*W;5`4p4EH8-RxeX?2xF>{U3>-~ORf=8Q!Zch$wIbU1zbKIYXRQ*jmV%npSb;f}n) zSeYKX(Z?8J<KTA?f%zx(+W)@Sds-tJ$XYt`dM=DY3$~Jj9Iif>(AmCPJ;XO`S<_eY zp_;utI-L#sg=9{{IOp=D%wFt6IX9(2Exb|Cy!7~-&9YTh*lL2h<1MX92Sc}8PfqWF zIU(ETV+sanKZ3#dm`^QZ_QN!G&uJ8D7e+EN?3d6g9!881dMDVhRe=bh<~L@pq%B6J z%F+)%q1@;H1m%w>-12ZF0{6Oqd6d6)xIC6TZvl12T}^!P7sM-lt4D6?-xL<vJB`?Y z=i7s{i5Tc0$Ol3z|J(gLjH<N%%JTmdyeof2gnnM|w`=!@s{it9PiQ$H<m{b4@xlSr zhr*;EMeChZgQNRzQLYcjTTmDPZP92j5*&T!A}lCY?R0AYa{bxqIrQ^8>S|!tD5+ZQ zFe=`J4#1|1V3JRPyt_I(>jX)~c}Hk{*uOvS{>^{(cXxe7SB+MPPq?ggOPnQRv${s% zPCRq8sz!+2iSZ)e68>%+ECQt0;l1hU=`L=QU*U>h3+hXO*EG@!>~`=`qu-rhW9qC% zo0L{qDgnC~e(W47y7_BYVXn00yl93AAf<&+C62+ryqGbZ97MifRyV1NmP?xwRJ<aa zQ~SNgI8%{rE*vhCF=%MROEGn1J6{ONqfLS<F&m(uaj~%+m!uzsr&%LSOIAF8Tc2E# zD287uV$N?B5qK2)AA1-@hPMYN29XgPCb^AM<af1k6!>3Yc}<4G1j)lviV?*f=_MBF zc|UwD_1wn(VpECK7o61Zn&fG3W8_$C6q&0`gs-z>TCpWU>L}$v^ymn}XIn}6n|IK# z;!N{o9r7P%a?42c#N%7sEw|DI6yMw#SCe4<1@I%ld+YMHwzJ#5yZxa=S7&$baD~Nv z_e=Up$bGxK<wq5%v*;j={dx2opEnpuNJziBUiF~b(q?>azXB+kvb6VQ1Jc@E|72g} zghrFC5avc(bq(z&Gxmdp-X*?a`)U}i1*SFYx5UX)&&C66eX=nsc{DiwsZG#c@yfzz zhfqap7j{%iSS&q9c5Hk&m_>!5caLRx^=RTktIKW6FOzqt>X0)vQ|7Ms__(-)G}Mf2 zhJ!BZ^_H%J9Sf_w+gs+*IF=D6->#3Ay1Td>s&9ZyG+JW?WBz2G>>Q@R%;40U>D#)n zHW3CTtZBE7O18i9m0sHd)B`ynR&tj&2`2x<yQEZkn~b_4a!(Cn{(INwdu!04GCnBO z^WTXOiUKx0TG$e>v6B+z%U4}qbv_-{7xdD9@ue9#%(>li85|m?P^R31mwp+`b}1+U z?ESYR`qB{C`?Lh8mrN+-hocSg-E~MO1{3YEXTTm$d0_|^_;rU=AcSo-nXPi&*|2E@ z>Eq5;1zCC$=y<%nJMz$FN#(QY*qU`7&17qS9rA0MekZVkm{3rfut&WcTdh;3VMvS8 z(pV)r4Of*Ms3RijYDD8_B>9(0cXzuLsr3BOV-gAh+}Q1o=3-zM@R?_i<s|YRX?y)I D*J2vT literal 0 HcmV?d00001 diff --git a/src/views/mp/components/wx-location/main.vue b/src/views/mp/components/wx-location/main.vue new file mode 100644 index 00000000..c0d67e29 --- /dev/null +++ b/src/views/mp/components/wx-location/main.vue @@ -0,0 +1,72 @@ +<!-- + 【微信消息 - 定位】 +--> +<template> + <div> + <el-link + type="primary" + target="_blank" + :href=" + 'https://map.qq.com/?type=marker&isopeninfowin=1&markertype=1&pointx=' + + locationY + + '&pointy=' + + locationX + + '&name=' + + label + + '&ref=yudao' + " + > + <el-col> + <el-row> + <img + :src=" + 'https://apis.map.qq.com/ws/staticmap/v2/?zoom=10&markers=color:blue|label:A|' + + locationX + + ',' + + locationY + + '&key=' + + qqMapKey + + '&size=250*180' + " + /> + </el-row> + <el-row> + <el-icon><Location /></el-icon>{{ label }} + </el-row> + </el-col> + </el-link> + </div> +</template> + +<script setup lang="ts" name="WxLocation"> +import { Location } from '@element-plus/icons-vue' + +const props = defineProps({ + locationX: { + required: true, + type: Number + }, + locationY: { + required: true, + type: Number + }, + label: { + // 地名 + required: true, + type: String + }, + qqMapKey: { + // QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc + required: false, + type: String, + default: 'TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E' // 需要自定义 + } +}) + +defineExpose({ + locationX: props.locationX, + locationY: props.locationY, + label: props.label, + qqMapKey: props.qqMapKey +}) +</script> diff --git a/src/views/mp/components/wx-msg/card.scss b/src/views/mp/components/wx-msg/card.scss new file mode 100644 index 00000000..67ac9219 --- /dev/null +++ b/src/views/mp/components/wx-msg/card.scss @@ -0,0 +1,101 @@ +.avue-card{ + &__item{ + margin-bottom: 16px; + border: 1px solid #e8e8e8; + background-color: #fff; + box-sizing: border-box; + color: rgba(0,0,0,.65); + font-size: 14px; + font-variant: tabular-nums; + line-height: 1.5; + list-style: none; + font-feature-settings: "tnum"; + cursor: pointer; + height:200px; + &:hover{ + border-color: rgba(0,0,0,.09); + box-shadow: 0 2px 8px rgba(0,0,0,.09); + } + &--add{ + border:1px dashed #000; + width: 100%; + color: rgba(0,0,0,.45); + background-color: #fff; + border-color: #d9d9d9; + border-radius: 2px; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + i{ + margin-right: 10px; + } + &:hover{ + color: #40a9ff; + background-color: #fff; + border-color: #40a9ff; + } + } + } + &__body{ + display: flex; + padding: 24px; + } + &__detail{ + flex:1 + } + &__avatar{ + width: 48px; + height: 48px; + border-radius: 48px; + overflow: hidden; + margin-right: 12px; + img{ + width: 100%; + height: 100%; + } + } + &__title{ + color: rgba(0,0,0,.85); + margin-bottom: 12px; + font-size: 16px; + &:hover{ + color:#1890ff; + } + } + &__info{ + color: rgba(0,0,0,.45); + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; + overflow: hidden; + height: 64px; + } + &__menu{ + display: flex; + justify-content:space-around; + height: 50px; + background: #f7f9fa; + color: rgba(0,0,0,.45); + text-align: center; + line-height: 50px; + &:hover{ + color:#1890ff; + } + } +} + +/** joolun 额外加的 */ +.avue-comment__main { + flex: unset!important; + border-radius: 5px!important; + margin: 0 8px!important; +} +.avue-comment__header { + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} +.avue-comment__body { + border-bottom-right-radius: 5px; + border-bottom-left-radius: 5px; +} diff --git a/src/views/mp/components/wx-msg/comment.scss b/src/views/mp/components/wx-msg/comment.scss new file mode 100644 index 00000000..3f1341b2 --- /dev/null +++ b/src/views/mp/components/wx-msg/comment.scss @@ -0,0 +1,88 @@ +/* 来自 https://github.com/nmxiaowei/avue/blob/master/styles/src/element-ui/comment.scss */ +.avue-comment{ + margin-bottom: 30px; + display: flex; + align-items: flex-start; + &--reverse{ + flex-direction:row-reverse; + .avue-comment__main{ + &:before,&:after{ + left: auto; + right: -8px; + border-width: 8px 0 8px 8px; + } + &:before{ + border-left-color: #dedede; + } + &:after{ + border-left-color: #f8f8f8; + margin-right: 1px; + margin-left: auto; + } + } + } + &__avatar{ + width: 48px; + height: 48px; + border-radius: 50%; + border: 1px solid transparent; + box-sizing: border-box; + vertical-align: middle; + } + &__header{ + padding: 5px 15px; + background: #f8f8f8; + border-bottom: 1px solid #eee; + display: flex; + align-items: center; + justify-content: space-between; + } + &__author{ + font-weight: 700; + font-size: 14px; + color: #999; + } + &__main{ + flex:1; + margin: 0 20px; + position: relative; + border: 1px solid #dedede; + border-radius: 2px; + &:before,&:after{ + position: absolute; + top: 10px; + left: -8px; + right: 100%; + width: 0; + height: 0; + display: block; + content: " "; + border-color: transparent; + border-style: solid solid outset; + border-width: 8px 8px 8px 0; + pointer-events: none; + } + &:before { + border-right-color: #dedede; + z-index: 1; + } + &:after{ + border-right-color: #f8f8f8; + margin-left: 1px; + z-index: 2; + } + } + &__body{ + padding: 15px; + overflow: hidden; + background: #fff; + font-family: Segoe UI,Lucida Grande,Helvetica,Arial,Microsoft YaHei,FreeSans,Arimo,Droid Sans,wenquanyi micro hei,Hiragino Sans GB,Hiragino Sans GB W3,FontAwesome,sans-serif;color: #333; + font-size: 14px; + } + blockquote{ + margin:0; + font-family: Georgia,Times New Roman,Times,Kai,Kaiti SC,KaiTi,BiauKai,FontAwesome,serif; + padding: 1px 0 1px 15px; + border-left: 4px solid #ddd; + } +} diff --git a/src/views/mp/components/wx-msg/main.vue b/src/views/mp/components/wx-msg/main.vue new file mode 100644 index 00000000..b514a73e --- /dev/null +++ b/src/views/mp/components/wx-msg/main.vue @@ -0,0 +1,338 @@ +<!-- + - Copyright (C) 2018-2019 + - All rights reserved, Designed By www.joolun.com + 芋道源码: + ① 移除暂时用不到的 websocket + ② 代码优化,补充注释,提升阅读性 +--> +<template> + <div class="msg-main"> + <div class="msg-div" :id="'msg-div' + nowStr"> + <!-- 加载更多 --> + <div v-loading="loading"></div> + <div v-if="!loading"> + <div class="el-table__empty-block" v-if="loadMore" @click="loadingMore" + ><span class="el-table__empty-text">点击加载更多</span></div + > + <div class="el-table__empty-block" v-if="!loadMore" + ><span class="el-table__empty-text">没有更多了</span></div + > + </div> + <!-- 消息列表 --> + <div class="execution" v-for="item in list" :key="item.id"> + <div class="avue-comment" :class="item.sendFrom === 2 ? 'avue-comment--reverse' : ''"> + <div class="avatar-div"> + <img + :src="item.sendFrom === 1 ? user.avatar : mp.avatar" + class="avue-comment__avatar" + /> + <div class="avue-comment__author">{{ + item.sendFrom === 1 ? user.nickname : mp.nickname + }}</div> + </div> + <div class="avue-comment__main"> + <div class="avue-comment__header"> + <div class="avue-comment__create_time">{{ parseTime(item.createTime) }}</div> + </div> + <div + class="avue-comment__body" + :style="item.sendFrom === 2 ? 'background: #6BED72;' : ''" + > + <!-- 【事件】区域 --> + <div v-if="item.type === 'event' && item.event === 'subscribe'"> + <el-tag type="success" size="mini">关注</el-tag> + </div> + <div v-else-if="item.type === 'event' && item.event === 'unsubscribe'"> + <el-tag type="danger" size="mini">取消关注</el-tag> + </div> + <div v-else-if="item.type === 'event' && item.event === 'CLICK'"> + <el-tag size="mini">点击菜单</el-tag>【{{ item.eventKey }}】 + </div> + <div v-else-if="item.type === 'event' && item.event === 'VIEW'"> + <el-tag size="mini">点击菜单链接</el-tag>【{{ item.eventKey }}】 + </div> + <div v-else-if="item.type === 'event' && item.event === 'scancode_waitmsg'"> + <el-tag size="mini">扫码结果</el-tag>【{{ item.eventKey }}】 + </div> + <div v-else-if="item.type === 'event' && item.event === 'scancode_push'"> + <el-tag size="mini">扫码结果</el-tag>【{{ item.eventKey }}】 + </div> + <div v-else-if="item.type === 'event' && item.event === 'pic_sysphoto'"> + <el-tag size="mini">系统拍照发图</el-tag> + </div> + <div v-else-if="item.type === 'event' && item.event === 'pic_photo_or_album'"> + <el-tag size="mini">拍照或者相册</el-tag> + </div> + <div v-else-if="item.type === 'event' && item.event === 'pic_weixin'"> + <el-tag size="mini">微信相册</el-tag> + </div> + <div v-else-if="item.type === 'event' && item.event === 'location_select'"> + <el-tag size="mini">选择地理位置</el-tag> + </div> + <div v-else-if="item.type === 'event'"> + <el-tag type="danger" size="mini">未知事件类型</el-tag> + </div> + <!-- 【消息】区域 --> + <div v-else-if="item.type === 'text'">{{ item.content }}</div> + <div v-else-if="item.type === 'voice'"> + <wx-voice-player :url="item.mediaUrl" :content="item.recognition" /> + </div> + <div v-else-if="item.type === 'image'"> + <a target="_blank" :href="item.mediaUrl"> + <img :src="item.mediaUrl" style="width: 100px" /> + </a> + </div> + <div + v-else-if="item.type === 'video' || item.type === 'shortvideo'" + style="text-align: center" + > + <wx-video-player :url="item.mediaUrl" /> + </div> + <div v-else-if="item.type === 'link'" class="avue-card__detail"> + <el-link type="success" :underline="false" target="_blank" :href="item.url"> + <div class="avue-card__title"><i class="el-icon-link"></i>{{ item.title }}</div> + </el-link> + <div class="avue-card__info" style="height: unset">{{ item.description }}</div> + </div> + <!-- TODO 芋艿:待完善 --> + <div v-else-if="item.type === 'location'"> + <wx-location + :label="item.label" + :location-y="item.locationY" + :location-x="item.locationX" + /> + </div> + <div v-else-if="item.type === 'news'" style="width: 300px"> + <!-- TODO 芋艿:待测试;详情页也存在类似的情况 --> + <wx-news :articles="item.articles" /> + </div> + <div v-else-if="item.type === 'music'"> + <wx-music + :title="item.title" + :description="item.description" + :thumb-media-url="item.thumbMediaUrl" + :music-url="item.musicUrl" + :hq-music-url="item.hqMusicUrl" + /> + </div> + </div> + </div> + </div> + </div> + </div> + <div class="msg-send" v-loading="sendLoading"> + <wx-reply-select ref="replySelect" :objData="objData" /> + <el-button type="success" size="small" class="send-but" @click="sendMsg">发送(S)</el-button> + </div> + </div> +</template> + +<script> +import { getMessagePage, sendMessage } from '@/api/mp/message' +import WxReplySelect from '@/views/mp/components/wx-reply/main.vue' +import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' +import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' +import WxNews from '@/views/mp/components/wx-news/main.vue' +import WxLocation from '@/views/mp/components/wx-location/main.vue' +import WxMusic from '@/views/mp/components/wx-music/main.vue' +import { getUser } from '@/api/mp/mpuser' + +export default { + name: 'WxMsg', + components: { + WxReplySelect, + WxVideoPlayer, + WxVoicePlayer, + WxNews, + WxLocation, + WxMusic + }, + props: { + userId: { + type: Number, + required: true + } + }, + data() { + return { + nowStr: new Date().getTime(), // 当前的时间戳,用于每次消息加载后,回到原位置;具体见 :id="'msg-div' + nowStr" 处 + loading: false, // 消息列表是否正在加载中 + loadMore: true, // 是否可以加载更多 + list: [], // 消息列表 + queryParams: { + pageNo: 1, // 当前页数 + pageSize: 14, // 每页显示多少条 + accountId: undefined + }, + user: { + // 由于微信不再提供昵称,直接使用“用户”展示 + nickname: '用户', + avatar: require('@/assets/images/profile.jpg'), + accountId: 0 // 公众号账号编号 + }, + mp: { + nickname: '公众号', + avatar: require('@/assets/images/wechat.png') + }, + + // ========= 消息发送 ========= + sendLoading: false, // 发送消息是否加载中 + objData: { + // 微信发送消息 + type: 'text' + } + } + }, + created() { + // 获得用户信息 + getUser(this.userId).then((response) => { + this.user.nickname = + response.data.nickname && response.data.nickname.length > 0 + ? response.data.nickname + : this.user.nickname + this.user.avatar = + response.data.avatar && this.user.avatar.length > 0 + ? response.data.avatar + : this.user.avatar + this.user.accountId = response.data.accountId + // 设置公众号账号编号 + this.queryParams.accountId = response.data.accountId + this.objData.accountId = response.data.accountId + + // 加载消息 + console.log(this.queryParams) + this.refreshChange() + }) + }, + methods: { + sendMsg() { + if (!this.objData) { + return + } + // 公众号限制:客服消息,公众号只允许发送一条 + if (this.objData.type === 'news' && this.objData.articles.length > 1) { + this.objData.articles = [this.objData.articles[0]] + this.$message({ + showClose: true, + message: '图文消息条数限制在 1 条以内,已默认发送第一条', + type: 'success' + }) + } + + // 执行发送 + this.sendLoading = true + sendMessage( + Object.assign( + { + userId: this.userId + }, + { + ...this.objData + } + ) + ) + .then((response) => { + this.sendLoading = false + // 添加到消息列表,并滚动 + this.list = [...this.list, ...[response.data]] + this.scrollToBottom() + // 重置 objData 状态 + this.$refs['replySelect'].deleteObj() // 重置,避免 tab 的数据未清理 + }) + .catch(() => { + this.sendLoading = false + }) + }, + loadingMore() { + this.queryParams.pageNo++ + this.getPage(this.queryParams) + }, + getPage(page, params) { + this.loading = true + getMessagePage( + Object.assign( + { + pageNo: page.pageNo, + pageSize: page.pageSize, + userId: this.userId, + accountId: page.accountId + }, + params + ) + ).then((response) => { + // 计算当前的滚动高度 + const msgDiv = document.getElementById('msg-div' + this.nowStr) + let scrollHeight = 0 + if (msgDiv) { + scrollHeight = msgDiv.scrollHeight + } + + // 处理数据 + const data = response.data.list.reverse() + this.list = [...data, ...this.list] + this.loading = false + if (data.length < this.queryParams.pageSize || data.length === 0) { + this.loadMore = false + } + this.queryParams.pageNo = page.pageNo + this.queryParams.pageSize = page.pageSize + + // 滚动到原来的位置 + if (this.queryParams.pageNo === 1) { + // 定位到消息底部 + this.scrollToBottom() + } else if (data.length !== 0) { + // 定位滚动条 + this.$nextTick(() => { + if (scrollHeight !== 0) { + msgDiv.scrollTop = + document.getElementById('msg-div' + this.nowStr).scrollHeight - scrollHeight - 100 + } + }) + } + }) + }, + /** + * 刷新回调 + */ + refreshChange() { + this.getPage(this.queryParams) + }, + /** 定位到消息底部 */ + scrollToBottom: function () { + this.$nextTick(() => { + let div = document.getElementById('msg-div' + this.nowStr) + div.scrollTop = div.scrollHeight + }) + } + } +} +</script> +<style lang="scss" scoped> +/* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc */ +@import './comment.scss'; +@import './card.scss'; + +.msg-main { + margin-top: -30px; + padding: 10px; +} +.msg-div { + height: 50vh; + overflow: auto; + background-color: #eaeaea; + margin-left: 10px; + margin-right: 10px; +} +.msg-send { + padding: 10px; +} +.avatar-div { + text-align: center; + width: 80px; +} +.send-but { + float: right; + margin-top: 8px !important; +} +</style> diff --git a/src/views/mp/components/wx-music/main.vue b/src/views/mp/components/wx-music/main.vue new file mode 100644 index 00000000..52555f15 --- /dev/null +++ b/src/views/mp/components/wx-music/main.vue @@ -0,0 +1,60 @@ +<!-- + 【微信消息 - 音乐】 +--> +<template> + <div> + <el-link + type="success" + :underline="false" + target="_blank" + :href="hqMusicUrl ? hqMusicUrl : musicUrl" + > + <div + class="avue-card__body" + style="padding: 10px; background-color: #fff; border-radius: 5px" + > + <div class="avue-card__avatar"> + <img :src="thumbMediaUrl" alt="" /> + </div> + <div class="avue-card__detail"> + <div class="avue-card__title" style="margin-bottom: unset">{{ title }}</div> + <div class="avue-card__info" style="height: unset">{{ description }}</div> + </div> + </div> + </el-link> + </div> +</template> + +<script setup lang="ts" name="WxMusic"> +const props = defineProps({ + title: { + required: false, + type: String + }, + description: { + required: false, + type: String + }, + musicUrl: { + required: false, + type: String + }, + hqMusicUrl: { + required: false, + type: String + }, + thumbMediaUrl: { + required: true, + type: String + } +}) + +defineExpose({ + musicUrl: props.musicUrl +}) +</script> + +<style lang="scss" scoped> +/* 因为 joolun 实现依赖 avue 组件,该页面使用了 card.scc */ +@import '../wx-msg/card.scss'; +</style> diff --git a/src/views/mp/components/wx-news/main.vue b/src/views/mp/components/wx-news/main.vue new file mode 100644 index 00000000..d08e2813 --- /dev/null +++ b/src/views/mp/components/wx-news/main.vue @@ -0,0 +1,107 @@ +<!-- + - Copyright (C) 2018-2019 + - All rights reserved, Designed By www.joolun.com + 【微信消息 - 图文】 + 芋道源码: + ① 代码优化,补充注释,提升阅读性 +--> +<template> + <div class="news-home"> + <div v-for="(article, index) in articles" :key="index" class="news-div"> + <!-- 头条 --> + <a target="_blank" :href="article.url" v-if="index === 0"> + <div class="news-main"> + <div class="news-content"> + <el-image + class="material-img" + style="width: 100%; height: 120px" + :src="article.picUrl" + /> + <div class="news-content-title"> + <span>{{ article.title }}</span> + </div> + </div> + </div> + </a> + <!-- 二条/三条等等 --> + <a target="_blank" :href="article.url" v-else> + <div class="news-main-item"> + <div class="news-content-item"> + <div class="news-content-item-title">{{ article.title }}</div> + <div class="news-content-item-img"> + <img class="material-img" :src="article.picUrl" height="100%" /> + </div> + </div> + </div> + </a> + </div> + </div> +</template> + +<script setup lang="ts"> +const props = defineProps({ + articles: { + type: Array, + default: () => null + } +}) + +defineExpose({ + articles: props.articles +}) +</script> + +<style lang="scss" scoped> +.news-home { + background-color: #ffffff; + width: 100%; + margin: auto; +} +.news-main { + width: 100%; + margin: auto; +} +.news-content { + background-color: #acadae; + width: 100%; + position: relative; +} +.news-content-title { + display: inline-block; + font-size: 12px; + color: #ffffff; + position: absolute; + left: 0; + bottom: 0; + background-color: black; + width: 98%; + padding: 1%; + opacity: 0.65; + white-space: normal; + box-sizing: unset !important; +} +.news-main-item { + background-color: #ffffff; + padding: 5px 0; + border-top: 1px solid #eaeaea; +} +.news-content-item { + position: relative; +} +.news-content-item-title { + display: inline-block; + font-size: 10px; + width: 70%; + margin-left: 1%; + white-space: normal; +} +.news-content-item-img { + display: inline-block; + width: 25%; + background-color: #acadae; + margin-right: 1%; +} +.material-img { + width: 100%; +} +</style> diff --git a/src/views/mp/components/wx-reply/main.vue b/src/views/mp/components/wx-reply/main.vue new file mode 100644 index 00000000..57a3cd84 --- /dev/null +++ b/src/views/mp/components/wx-reply/main.vue @@ -0,0 +1,634 @@ +<!--<!–--> +<!-- - Copyright (C) 2018-2019--> +<!-- - All rights reserved, Designed By www.joolun.com--> +<!-- 芋道源码:--> +<!-- ① 移除多余的 rep 为前缀的变量,让 message 消息更简单--> +<!-- ② 代码优化,补充注释,提升阅读性--> +<!-- ③ 优化消息的临时缓存策略,发送消息时,只清理被发送消息的 tab,不会强制切回到 text 输入--> +<!-- ④ 支持发送【视频】消息时,支持新建视频--> +<!--–>--> +<!--<template>--> +<!-- <el-tabs type="border-card" v-model="objData.type" @tab-click="handleClick">--> +<!-- <!– 类型 1:文本 –>--> +<!-- <el-tab-pane name="text">--> +<!-- <span slot="label"><i class="el-icon-document"></i> 文本</span>--> +<!-- <el-input--> +<!-- type="textarea"--> +<!-- :rows="5"--> +<!-- placeholder="请输入内容"--> +<!-- v-model="objData.content"--> +<!-- @input="inputContent"--> +<!-- />--> +<!-- </el-tab-pane>--> +<!-- <!– 类型 2:图片 –>--> +<!-- <el-tab-pane name="image">--> +<!-- <span slot="label"><i class="el-icon-picture"></i> 图片</span>--> +<!-- <el-row>--> +<!-- <!– 情况一:已经选择好素材、或者上传好图片 –>--> +<!-- <div class="select-item" v-if="objData.url">--> +<!-- <img class="material-img" :src="objData.url" />--> +<!-- <p class="item-name" v-if="objData.name">{{ objData.name }}</p>--> +<!-- <el-row class="ope-row">--> +<!-- <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" />--> +<!-- </el-row>--> +<!-- </div>--> +<!-- <!– 情况二:未做完上述操作 –>--> +<!-- <div v-else>--> +<!-- <el-row style="text-align: center">--> +<!-- <!– 选择素材 –>--> +<!-- <el-col :span="12" class="col-select">--> +<!-- <el-button type="success" @click="openMaterial">--> +<!-- 素材库选择<i class="el-icon-circle-check el-icon--right"></i>--> +<!-- </el-button>--> +<!-- <el-dialog--> +<!-- title="选择图片"--> +<!-- v-model:visible="dialogImageVisible"--> +<!-- width="90%"--> +<!-- append-to-body--> +<!-- >--> +<!-- <wx-material-select :obj-data="objData" @selectMaterial="selectMaterial" />--> +<!-- </el-dialog>--> +<!-- </el-col>--> +<!-- <!– 文件上传 –>--> +<!-- <el-col :span="12" class="col-add">--> +<!-- <el-upload--> +<!-- :action="actionUrl"--> +<!-- :headers="headers"--> +<!-- multiple--> +<!-- :limit="1"--> +<!-- :file-list="fileList"--> +<!-- :data="uploadData"--> +<!-- :before-upload="beforeImageUpload"--> +<!-- :on-success="handleUploadSuccess"--> +<!-- >--> +<!-- <el-button type="primary">上传图片</el-button>--> +<!-- <div slot="tip" class="el-upload__tip"--> +<!-- >支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div--> +<!-- >--> +<!-- </el-upload>--> +<!-- </el-col>--> +<!-- </el-row>--> +<!-- </div>--> +<!-- </el-row>--> +<!-- </el-tab-pane>--> +<!-- <!– 类型 3:语音 –>--> +<!-- <el-tab-pane name="voice">--> +<!-- <span slot="label"><i class="el-icon-phone"></i> 语音</span>--> +<!-- <el-row>--> +<!-- <div class="select-item2" v-if="objData.url">--> +<!-- <p class="item-name">{{ objData.name }}</p>--> +<!-- <div class="item-infos">--> +<!-- <wx-voice-player :url="objData.url" />--> +<!-- </div>--> +<!-- <el-row class="ope-row">--> +<!-- <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" />--> +<!-- </el-row>--> +<!-- </div>--> +<!-- <div v-else>--> +<!-- <el-row style="text-align: center">--> +<!-- <!– 选择素材 –>--> +<!-- <el-col :span="12" class="col-select">--> +<!-- <el-button type="success" @click="openMaterial">--> +<!-- 素材库选择<i class="el-icon-circle-check el-icon--right"></i>--> +<!-- </el-button>--> +<!-- <el-dialog--> +<!-- title="选择语音"--> +<!-- v-model:visible="dialogVoiceVisible"--> +<!-- width="90%"--> +<!-- append-to-body--> +<!-- >--> +<!-- <WxMaterialSelect :objData="objData" @selectMaterial="selectMaterial" />--> +<!-- </el-dialog>--> +<!-- </el-col>--> +<!-- <!– 文件上传 –>--> +<!-- <el-col :span="12" class="col-add">--> +<!-- <el-upload--> +<!-- :action="actionUrl"--> +<!-- :headers="headers"--> +<!-- multiple--> +<!-- :limit="1"--> +<!-- :file-list="fileList"--> +<!-- :data="uploadData"--> +<!-- :before-upload="beforeVoiceUpload"--> +<!-- :on-success="handleUploadSuccess"--> +<!-- >--> +<!-- <el-button type="primary">点击上传</el-button>--> +<!-- <div slot="tip" class="el-upload__tip"--> +<!-- >格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s</div--> +<!-- >--> +<!-- </el-upload>--> +<!-- </el-col>--> +<!-- </el-row>--> +<!-- </div>--> +<!-- </el-row>--> +<!-- </el-tab-pane>--> +<!-- <!– 类型 4:视频 –>--> +<!-- <el-tab-pane name="video">--> +<!-- <span slot="label"><i class="el-icon-share"></i> 视频</span>--> +<!-- <el-row>--> +<!-- <el-input v-model="objData.title" placeholder="请输入标题" @input="inputContent" />--> +<!-- <div style="margin: 20px 0"></div>--> +<!-- <el-input v-model="objData.description" placeholder="请输入描述" @input="inputContent" />--> +<!-- <div style="margin: 20px 0"></div>--> +<!-- <div style="text-align: center">--> +<!-- <wx-video-player v-if="objData.url" :url="objData.url" />--> +<!-- </div>--> +<!-- <div style="margin: 20px 0"></div>--> +<!-- <el-row style="text-align: center">--> +<!-- <!– 选择素材 –>--> +<!-- <el-col :span="12">--> +<!-- <el-button type="success" @click="openMaterial">--> +<!-- 素材库选择<i class="el-icon-circle-check el-icon--right"></i>--> +<!-- </el-button>--> +<!-- <el-dialog--> +<!-- title="选择视频"--> +<!-- v-model:visible="dialogVideoVisible"--> +<!-- width="90%"--> +<!-- append-to-body--> +<!-- >--> +<!-- <wx-material-select :objData="objData" @selectMaterial="selectMaterial" />--> +<!-- </el-dialog>--> +<!-- </el-col>--> +<!-- <!– 文件上传 –>--> +<!-- <el-col :span="12">--> +<!-- <el-upload--> +<!-- :action="actionUrl"--> +<!-- :headers="headers"--> +<!-- multiple--> +<!-- :limit="1"--> +<!-- :file-list="fileList"--> +<!-- :data="uploadData"--> +<!-- :before-upload="beforeVideoUpload"--> +<!-- :on-success="handleUploadSuccess"--> +<!-- >--> +<!-- <el-button type="primary"--> +<!-- >新建视频<i class="el-icon-upload el-icon--right"></i--> +<!-- ></el-button>--> +<!-- </el-upload>--> +<!-- </el-col>--> +<!-- </el-row>--> +<!-- </el-row>--> +<!-- </el-tab-pane>--> +<!-- <!– 类型 5:图文 –>--> +<!-- <el-tab-pane name="news">--> +<!-- <span slot="label"><i class="el-icon-news"></i> 图文</span>--> +<!-- <el-row>--> +<!-- <div class="select-item" v-if="objData.articles">--> +<!-- <wx-news :articles="objData.articles" />--> +<!-- <el-row class="ope-row">--> +<!-- <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" />--> +<!-- </el-row>--> +<!-- </div>--> +<!-- <!– 选择素材 –>--> +<!-- <div v-if="!objData.content">--> +<!-- <el-row style="text-align: center">--> +<!-- <el-col :span="24">--> +<!-- <el-button type="success" @click="openMaterial"--> +<!-- >{{ newsType === '1' ? '选择已发布图文' : '选择草稿箱图文'--> +<!-- }}<i class="el-icon-circle-check el-icon--right"></i--> +<!-- ></el-button>--> +<!-- </el-col>--> +<!-- </el-row>--> +<!-- </div>--> +<!-- <el-dialog title="选择图文" v-model:visible="dialogNewsVisible" width="90%" append-to-body>--> +<!-- <wx-material-select--> +<!-- :objData="objData"--> +<!-- @selectMaterial="selectMaterial"--> +<!-- :newsType="newsType"--> +<!-- />--> +<!-- </el-dialog>--> +<!-- </el-row>--> +<!-- </el-tab-pane>--> +<!-- <!– 类型 6:音乐 –>--> +<!-- <el-tab-pane name="music">--> +<!-- <span slot="label"><i class="el-icon-service"></i> 音乐</span>--> +<!-- <el-row>--> +<!-- <el-col :span="6">--> +<!-- <div class="thumb-div">--> +<!-- <img style="width: 100px" v-if="objData.thumbMediaUrl" :src="objData.thumbMediaUrl" />--> +<!-- <i v-else class="el-icon-plus avatar-uploader-icon"></i>--> +<!-- <div class="thumb-but">--> +<!-- <el-upload--> +<!-- :action="actionUrl"--> +<!-- :headers="headers"--> +<!-- multiple--> +<!-- :limit="1"--> +<!-- :file-list="fileList"--> +<!-- :data="uploadData"--> +<!-- :before-upload="beforeThumbImageUpload"--> +<!-- :on-success="handleUploadSuccess"--> +<!-- >--> +<!-- <el-button slot="trigger" size="mini" type="text">本地上传</el-button>--> +<!-- <el-button size="mini" type="text" @click="openMaterial" style="margin-left: 5px"--> +<!-- >素材库选择</el-button--> +<!-- >--> +<!-- </el-upload>--> +<!-- </div>--> +<!-- </div>--> +<!-- <el-dialog--> +<!-- title="选择图片"--> +<!-- v-model:visible="dialogThumbVisible"--> +<!-- width="80%"--> +<!-- append-to-body--> +<!-- >--> +<!-- <wx-material-select--> +<!-- :objData="{ type: 'image', accountId: objData.accountId }"--> +<!-- @selectMaterial="selectMaterial"--> +<!-- />--> +<!-- </el-dialog>--> +<!-- </el-col>--> +<!-- <el-col :span="18">--> +<!-- <el-input v-model="objData.title" placeholder="请输入标题" @input="inputContent" />--> +<!-- <div style="margin: 20px 0"></div>--> +<!-- <el-input v-model="objData.description" placeholder="请输入描述" @input="inputContent" />--> +<!-- </el-col>--> +<!-- </el-row>--> +<!-- <div style="margin: 20px 0"></div>--> +<!-- <el-input v-model="objData.musicUrl" placeholder="请输入音乐链接" @input="inputContent" />--> +<!-- <div style="margin: 20px 0"></div>--> +<!-- <el-input--> +<!-- v-model="objData.hqMusicUrl"--> +<!-- placeholder="请输入高质量音乐链接"--> +<!-- @input="inputContent"--> +<!-- />--> +<!-- </el-tab-pane>--> +<!-- </el-tabs>--> +<!--</template>--> + +<!--<script>--> +<!--import WxNews from '@/views/mp/components/wx-news/main.vue'--> +<!--import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'--> +<!--import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'--> +<!--import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'--> + +<!--import { getAccessToken } from '@/utils/auth'--> + +<!--export default {--> +<!-- name: 'WxReplySelect',--> +<!-- components: {--> +<!-- WxNews,--> +<!-- WxMaterialSelect,--> +<!-- WxVoicePlayer,--> +<!-- WxVideoPlayer--> +<!-- },--> +<!-- props: {--> +<!-- objData: {--> +<!-- // 消息对象。--> +<!-- type: Object, // 设置为 Object 的原因,方便属性的传递--> +<!-- required: true--> +<!-- },--> +<!-- newsType: {--> +<!-- // 图文类型:1、已发布图文;2、草稿箱图文--> +<!-- type: String,--> +<!-- default: '1'--> +<!-- }--> +<!-- },--> +<!-- data() {--> +<!-- return {--> +<!-- tempPlayerObj: {--> +<!-- type: '2'--> +<!-- },--> + +<!-- tempObj: new Map().set(--> +<!-- // 临时缓存,切换消息类型的 tab 的时候,可以保存对应的数据;--> +<!-- this.objData.type, // 消息类型--> +<!-- Object.assign({}, this.objData)--> +<!-- ), // 消息内容--> + +<!-- // ========== 素材选择的弹窗,是否可见 ==========--> +<!-- dialogNewsVisible: false, // 图文--> +<!-- dialogImageVisible: false, // 图片--> +<!-- dialogVoiceVisible: false, // 语音--> +<!-- dialogVideoVisible: false, // 视频--> +<!-- dialogThumbVisible: false, // 缩略图--> + +<!-- // ========== 文件上传(图片、语音、视频) ==========--> +<!-- fileList: [], // 文件列表--> +<!-- uploadData: {--> +<!-- accountId: undefined,--> +<!-- type: this.objData.type,--> +<!-- title: '',--> +<!-- introduction: ''--> +<!-- },--> +<!-- actionUrl: process.env.VUE_APP_BASE_API + '/admin-api/mp/material/upload-temporary',--> +<!-- headers: { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部--> +<!-- }--> +<!-- },--> +<!-- methods: {--> +<!-- beforeThumbImageUpload(file) {--> +<!-- const isType =--> +<!-- file.type === 'image/jpeg' ||--> +<!-- file.type === 'image/png' ||--> +<!-- file.type === 'image/gif' ||--> +<!-- file.type === 'image/bmp' ||--> +<!-- file.type === 'image/jpg'--> +<!-- if (!isType) {--> +<!-- this.$message.error('上传图片格式不对!')--> +<!-- return false--> +<!-- }--> +<!-- const isLt = file.size / 1024 / 1024 < 2--> +<!-- if (!isLt) {--> +<!-- this.$message.error('上传图片大小不能超过 2M!')--> +<!-- return false--> +<!-- }--> +<!-- this.uploadData.accountId = this.objData.accountId--> +<!-- return true--> +<!-- },--> +<!-- beforeVoiceUpload(file) {--> +<!-- // 校验格式--> +<!-- const isType =--> +<!-- file.type === 'audio/mp3' ||--> +<!-- file.type === 'audio/mpeg' ||--> +<!-- file.type === 'audio/wma' ||--> +<!-- file.type === 'audio/wav' ||--> +<!-- file.type === 'audio/amr'--> +<!-- if (!isType) {--> +<!-- this.$message.error('上传语音格式不对!' + file.type)--> +<!-- return false--> +<!-- }--> +<!-- // 校验大小--> +<!-- const isLt = file.size / 1024 / 1024 < 2--> +<!-- if (!isLt) {--> +<!-- this.$message.error('上传语音大小不能超过 2M!')--> +<!-- return false--> +<!-- }--> +<!-- this.uploadData.accountId = this.objData.accountId--> +<!-- return true--> +<!-- },--> +<!-- beforeImageUpload(file) {--> +<!-- // 校验格式--> +<!-- const isType =--> +<!-- file.type === 'image/jpeg' ||--> +<!-- file.type === 'image/png' ||--> +<!-- file.type === 'image/gif' ||--> +<!-- file.type === 'image/bmp' ||--> +<!-- file.type === 'image/jpg'--> +<!-- if (!isType) {--> +<!-- this.$message.error('上传图片格式不对!')--> +<!-- return false--> +<!-- }--> +<!-- // 校验大小--> +<!-- const isLt = file.size / 1024 / 1024 < 2--> +<!-- if (!isLt) {--> +<!-- this.$message.error('上传图片大小不能超过 2M!')--> +<!-- return false--> +<!-- }--> +<!-- this.uploadData.accountId = this.objData.accountId--> +<!-- return true--> +<!-- },--> +<!-- beforeVideoUpload(file) {--> +<!-- // 校验格式--> +<!-- const isType = file.type === 'video/mp4'--> +<!-- if (!isType) {--> +<!-- this.$message.error('上传视频格式不对!')--> +<!-- return false--> +<!-- }--> +<!-- // 校验大小--> +<!-- const isLt = file.size / 1024 / 1024 < 10--> +<!-- if (!isLt) {--> +<!-- this.$message.error('上传视频大小不能超过 10M!')--> +<!-- return false--> +<!-- }--> +<!-- this.uploadData.accountId = this.objData.accountId--> +<!-- return true--> +<!-- },--> +<!-- handleUploadSuccess(response, file, fileList) {--> +<!-- if (response.code !== 0) {--> +<!-- this.$message.error('上传出错:' + response.msg)--> +<!-- return false--> +<!-- }--> + +<!-- // 清空上传时的各种数据--> +<!-- this.fileList = []--> +<!-- this.uploadData.title = ''--> +<!-- this.uploadData.introduction = ''--> + +<!-- // 上传好的文件,本质是个素材,所以可以进行选中--> +<!-- let item = response.data--> +<!-- this.selectMaterial(item)--> +<!-- },--> +<!-- /**--> +<!-- * 切换消息类型的 tab--> +<!-- *--> +<!-- * @param tab tab--> +<!-- */--> +<!-- handleClick(tab) {--> +<!-- // 设置后续文件上传的文件类型--> +<!-- this.uploadData.type = this.objData.type--> +<!-- if (this.uploadData.type === 'music') {--> +<!-- // 【音乐】上传的是缩略图--> +<!-- this.uploadData.type = 'thumb'--> +<!-- }--> + +<!-- // 从 tempObj 临时缓存中,获取对应的数据,并设置回 objData--> +<!-- let tempObjItem = this.tempObj.get(this.objData.type)--> +<!-- if (tempObjItem) {--> +<!-- this.objData.content = tempObjItem.content ? tempObjItem.content : null--> +<!-- this.objData.mediaId = tempObjItem.mediaId ? tempObjItem.mediaId : null--> +<!-- this.objData.url = tempObjItem.url ? tempObjItem.url : null--> +<!-- this.objData.name = tempObjItem.url ? tempObjItem.name : null--> +<!-- this.objData.title = tempObjItem.title ? tempObjItem.title : null--> +<!-- this.objData.description = tempObjItem.description ? tempObjItem.description : null--> +<!-- return--> +<!-- }--> +<!-- // 如果获取不到,需要把 objData 复原--> +<!-- // 必须使用 $set 赋值,不然 input 无法输入内容--> +<!-- this.$set(this.objData, 'content', '')--> +<!-- this.$delete(this.objData, 'mediaId')--> +<!-- this.$delete(this.objData, 'url')--> +<!-- this.$set(this.objData, 'title', '')--> +<!-- this.$set(this.objData, 'description', '')--> +<!-- },--> +<!-- /**--> +<!-- * 选择素材,将设置设置到 objData 变量--> +<!-- *--> +<!-- * @param item 素材--> +<!-- */--> +<!-- selectMaterial(item) {--> +<!-- // 选择好素材,所以隐藏弹窗--> +<!-- this.closeMaterial()--> + +<!-- // 创建 tempObjItem 对象,并设置对应的值--> +<!-- let tempObjItem = {}--> +<!-- tempObjItem.type = this.objData.type--> +<!-- if (this.objData.type === 'news') {--> +<!-- tempObjItem.articles = item.content.newsItem--> +<!-- this.objData.articles = item.content.newsItem--> +<!-- } else if (this.objData.type === 'music') {--> +<!-- // 音乐需要特殊处理,因为选择的是图片的缩略图--> +<!-- tempObjItem.thumbMediaId = item.mediaId--> +<!-- this.objData.thumbMediaId = item.mediaId--> +<!-- tempObjItem.thumbMediaUrl = item.url--> +<!-- this.objData.thumbMediaUrl = item.url--> +<!-- // title、introduction、musicUrl、hqMusicUrl:从 objData 到 tempObjItem,避免上传素材后,被覆盖掉--> +<!-- tempObjItem.title = this.objData.title || ''--> +<!-- tempObjItem.introduction = this.objData.introduction || ''--> +<!-- tempObjItem.musicUrl = this.objData.musicUrl || ''--> +<!-- tempObjItem.hqMusicUrl = this.objData.hqMusicUrl || ''--> +<!-- } else if (this.objData.type === 'image' || this.objData.type === 'voice') {--> +<!-- tempObjItem.mediaId = item.mediaId--> +<!-- this.objData.mediaId = item.mediaId--> +<!-- tempObjItem.url = item.url--> +<!-- this.objData.url = item.url--> +<!-- tempObjItem.name = item.name--> +<!-- this.objData.name = item.name--> +<!-- } else if (this.objData.type === 'video') {--> +<!-- tempObjItem.mediaId = item.mediaId--> +<!-- this.objData.mediaId = item.mediaId--> +<!-- tempObjItem.url = item.url--> +<!-- this.objData.url = item.url--> +<!-- tempObjItem.name = item.name--> +<!-- this.objData.name = item.name--> +<!-- // title、introduction:从 item 到 tempObjItem,因为素材里有 title、introduction--> +<!-- if (item.title) {--> +<!-- this.objData.title = item.title || ''--> +<!-- tempObjItem.title = item.title || ''--> +<!-- }--> +<!-- if (item.introduction) {--> +<!-- this.objData.description = item.introduction || '' // 消息使用的是 description,素材使用的是 introduction,所以转换下--> +<!-- tempObjItem.description = item.introduction || ''--> +<!-- }--> +<!-- } else if (this.objData.type === 'text') {--> +<!-- this.objData.content = item.content || ''--> +<!-- }--> +<!-- // 最终设置到临时缓存--> +<!-- this.tempObj.set(this.objData.type, tempObjItem)--> +<!-- },--> +<!-- openMaterial() {--> +<!-- if (this.objData.type === 'news') {--> +<!-- this.dialogNewsVisible = true--> +<!-- } else if (this.objData.type === 'image') {--> +<!-- this.dialogImageVisible = true--> +<!-- } else if (this.objData.type === 'voice') {--> +<!-- this.dialogVoiceVisible = true--> +<!-- } else if (this.objData.type === 'video') {--> +<!-- this.dialogVideoVisible = true--> +<!-- } else if (this.objData.type === 'music') {--> +<!-- this.dialogThumbVisible = true--> +<!-- }--> +<!-- },--> +<!-- closeMaterial() {--> +<!-- this.dialogNewsVisible = false--> +<!-- this.dialogImageVisible = false--> +<!-- this.dialogVoiceVisible = false--> +<!-- this.dialogVideoVisible = false--> +<!-- this.dialogThumbVisible = false--> +<!-- },--> +<!-- deleteObj() {--> +<!-- if (this.objData.type === 'news') {--> +<!-- this.$delete(this.objData, 'articles')--> +<!-- } else if (this.objData.type === 'image') {--> +<!-- this.objData.mediaId = null--> +<!-- this.$delete(this.objData, 'url')--> +<!-- this.objData.name = null--> +<!-- } else if (this.objData.type === 'voice') {--> +<!-- this.objData.mediaId = null--> +<!-- this.$delete(this.objData, 'url')--> +<!-- this.objData.name = null--> +<!-- } else if (this.objData.type === 'video') {--> +<!-- this.objData.mediaId = null--> +<!-- this.$delete(this.objData, 'url')--> +<!-- this.objData.name = null--> +<!-- this.objData.title = null--> +<!-- this.objData.description = null--> +<!-- } else if (this.objData.type === 'music') {--> +<!-- this.objData.thumbMediaId = null--> +<!-- this.objData.thumbMediaUrl = null--> +<!-- this.objData.title = null--> +<!-- this.objData.description = null--> +<!-- this.objData.musicUrl = null--> +<!-- this.objData.hqMusicUrl = null--> +<!-- } else if (this.objData.type === 'text') {--> +<!-- this.objData.content = null--> +<!-- }--> +<!-- // 覆盖缓存--> +<!-- this.tempObj.set(this.objData.type, Object.assign({}, this.objData))--> +<!-- },--> +<!-- /**--> +<!-- * 输入时,缓存每次 objData 到 tempObj 中--> +<!-- *--> +<!-- * why?不确定为什么 v-model="objData.content" 不能自动缓存,所以通过这样的方式--> +<!-- */--> +<!-- inputContent(str) {--> +<!-- // 覆盖缓存--> +<!-- this.tempObj.set(this.objData.type, Object.assign({}, this.objData))--> +<!-- }--> +<!-- }--> +<!--}--> +<!--</script>--> + +<!--<style lang="scss" scoped>--> +<!--.public-account-management {--> +<!-- .el-input {--> +<!-- width: 70%;--> +<!-- margin-right: 2%;--> +<!-- }--> +<!--}--> +<!--.pagination {--> +<!-- text-align: right;--> +<!-- margin-right: 25px;--> +<!--}--> +<!--.select-item {--> +<!-- width: 280px;--> +<!-- padding: 10px;--> +<!-- margin: 0 auto 10px auto;--> +<!-- border: 1px solid #eaeaea;--> +<!--}--> +<!--.select-item2 {--> +<!-- padding: 10px;--> +<!-- margin: 0 auto 10px auto;--> +<!-- border: 1px solid #eaeaea;--> +<!--}--> +<!--.ope-row {--> +<!-- padding-top: 10px;--> +<!-- text-align: center;--> +<!--}--> +<!--.item-name {--> +<!-- font-size: 12px;--> +<!-- overflow: hidden;--> +<!-- text-overflow: ellipsis;--> +<!-- white-space: nowrap;--> +<!-- text-align: center;--> +<!--}--> +<!--.el-form-item__content {--> +<!-- line-height: unset !important;--> +<!--}--> +<!--.col-select {--> +<!-- border: 1px solid rgb(234, 234, 234);--> +<!-- padding: 50px 0px;--> +<!-- height: 160px;--> +<!-- width: 49.5%;--> +<!--}--> +<!--.col-select2 {--> +<!-- border: 1px solid rgb(234, 234, 234);--> +<!-- padding: 50px 0px;--> +<!-- height: 160px;--> +<!--}--> +<!--.col-add {--> +<!-- border: 1px solid rgb(234, 234, 234);--> +<!-- padding: 50px 0px;--> +<!-- height: 160px;--> +<!-- width: 49.5%;--> +<!-- float: right;--> +<!--}--> +<!--.avatar-uploader-icon {--> +<!-- border: 1px solid #d9d9d9;--> +<!-- font-size: 28px;--> +<!-- color: #8c939d;--> +<!-- width: 100px !important;--> +<!-- height: 100px !important;--> +<!-- line-height: 100px !important;--> +<!-- text-align: center;--> +<!--}--> +<!--.material-img {--> +<!-- width: 100%;--> +<!--}--> +<!--.thumb-div {--> +<!-- display: inline-block;--> +<!-- text-align: center;--> +<!--}--> +<!--.item-infos {--> +<!-- width: 30%;--> +<!-- margin: auto;--> +<!--}--> +<!--</style>--> diff --git a/src/views/mp/components/wx-video-play/main.vue b/src/views/mp/components/wx-video-play/main.vue new file mode 100644 index 00000000..880d10f8 --- /dev/null +++ b/src/views/mp/components/wx-video-play/main.vue @@ -0,0 +1,117 @@ +<!-- + - Copyright (C) 2018-2019 + - All rights reserved, Designed By www.joolun.com + 【微信消息 - 视频】 + 芋道源码: + ① bug 修复: + 1)joolun 的做法:使用 mediaId 从微信公众号,下载对应的 mp4 素材,从而播放内容; + 存在的问题:mediaId 有效期是 3 天,超过时间后无法播放 + 2)重构后的做法:后端接收到微信公众号的视频消息后,将视频消息的 media_id 的文件内容保存到文件服务器中,这样前端可以直接使用 URL 播放。 + ② 体验优化:弹窗关闭后,自动暂停视频的播放 +--> +<template> + <div> + <!-- 提示 --> + <div @click="playVideo()"> + <el-icon> + <VideoPlay /> + </el-icon> + <p>点击播放视频</p> + </div> + + <!-- 弹窗播放 --> + <el-dialog + title="视频播放" + v-model:visible="dialogVideo" + width="40%" + append-to-body + @close="closeDialog" + > + <video-player + v-if="playerOptions.sources[0].src" + class="video-player vjs-custom-skin" + ref="videoPlayerRef" + :playsinline="true" + :options="playerOptions" + @play="onPlayerPlay($event)" + @pause="onPlayerPause($event)" + /> + </el-dialog> + </div> +</template> + +<script setup lang="ts" name="WxVideoPlayer"> +// 引入 videoPlayer 相关组件。教程:https://juejin.cn/post/6923056942281654285 +import { videoPlayer } from 'vue-video-player' +import 'video.js/dist/video-js.css' +import 'vue-video-player/src/custom-theme.css' +import { VideoPlay } from '@element-plus/icons-vue' + +const props = defineProps({ + url: { + // 视频地址,例如说:https://www.iocoder.cn/xxx.mp4 + type: String, + required: true + } +}) +const videoPlayerRef = ref() +const dialogVideo = ref(false) +const playerOptions = reactive({ + playbackRates: [0.5, 1.0, 1.5, 2.0], // 播放速度 + autoplay: false, // 如果 true,浏览器准备好时开始回放。 + muted: false, // 默认情况下将会消除任何音频。 + loop: false, // 导致视频一结束就重新开始。 + preload: 'auto', // 建议浏览器在 <video> 加载元素后是否应该开始下载视频数据。auto 浏览器选择最佳行为,立即开始加载视频(如果浏览器支持) + language: 'zh-CN', + aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3") + fluid: true, // 当true时,Video.js player 将拥有流体大小。换句话说,它将按比例缩放以适应其容器。 + sources: [ + { + type: 'video/mp4', + src: '' // 你的视频地址(必填)【重要】 + } + ], + poster: '', // 你的封面地址 + width: document.documentElement.clientWidth, + notSupportedMessage: '此视频暂无法播放,请稍后再试', //允许覆盖 Video.js 无法播放媒体源时显示的默认信息。 + controlBar: { + timeDivider: true, + durationDisplay: true, + remainingTimeDisplay: false, + fullscreenToggle: true //全屏按钮 + } +}) + +const playVideo = () => { + dialogVideo.value = true + playerOptions.sources[0].src = props.url +} +const closeDialog = () => { + // 暂停播放 + // videoPlayerRef.player.pause() +} +// onPlayerPlay(player) {}, +// // // eslint-disable-next-line @typescript-eslint/no-unused-vars +// // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars +// onPlayerPause(player) {} + +// methods: { +// playVideo() { +// this.dialogVideo = true +// // 设置地址 +// this.playerOptions.sources[0]['src'] = this.url +// }, +// closeDialog() { +// // 暂停播放 +// this.$refs.videoPlayer.player.pause() +// }, +// +// //todo player组件引入可能有问题 +// +// // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars +// onPlayerPlay(player) {}, +// // // eslint-disable-next-line @typescript-eslint/no-unused-vars +// // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars +// onPlayerPause(player) {} +// } +</script> diff --git a/src/views/mp/components/wx-voice-play/main.vue b/src/views/mp/components/wx-voice-play/main.vue new file mode 100644 index 00000000..f98ac681 --- /dev/null +++ b/src/views/mp/components/wx-voice-play/main.vue @@ -0,0 +1,100 @@ +<!-- + - Copyright (C) 2018-2019 + - All rights reserved, Designed By www.joolun.com + 【微信消息 - 语音】 + 芋道源码: + ① bug 修复: + 1)joolun 的做法:使用 mediaId 从微信公众号,下载对应的 mp4 素材,从而播放内容; + 存在的问题:mediaId 有效期是 3 天,超过时间后无法播放 + 2)重构后的做法:后端接收到微信公众号的视频消息后,将视频消息的 media_id 的文件内容保存到文件服务器中,这样前端可以直接使用 URL 播放。 + ② 代码优化:将 props 中的 objData 调成为 data 中对应的属性,并补充相关注释 +--> +<template> + <div class="wx-voice-div" @click="playVoice"> + <el-icon + ><VideoPlay v-if="playing !== true" /> + <VideoPause v-if="playing === true" /> + <span class="amr-duration" v-if="duration">{{ duration }} 秒</span> + </el-icon> + <div v-if="content"> + <el-tag type="success" size="mini">语音识别</el-tag> + {{ content }} + </div> + </div> +</template> + +<script setup lang="ts" name="WxVoicePlayer"> +// 因为微信语音是 amr 格式,所以需要用到 amr 解码器:https://www.npmjs.com/package/benz-amr-recorder + +import BenzAMRRecorder from 'benz-amr-recorder' +import { VideoPause, VideoPlay } from '@element-plus/icons-vue' + +const props = defineProps({ + url: { + // 语音地址,例如说:https://www.iocoder.cn/xxx.amr + type: String, + required: true + }, + content: { + // 语音文本 + type: String, + required: false + } +}) + +const amr = ref() +const playing = ref(false) +const duration = ref() + +const playVoice = () => { + // 情况一:未初始化,则创建 BenzAMRRecorder + debugger + console.log('进入' + amr.value) + if (amr.value === undefined) { + console.log('开始初始化') + amrInit() + return + } + + if (amr.value.isPlaying()) { + amrStop() + } else { + amrPlay() + } +} + +const amrInit = () => { + amr.value = new BenzAMRRecorder() + console.log(amr.value) + console.log(props.url) + // 设置播放 + amr.value.initWithUrl(props.url).then(function () { + amrPlay() + duration.value = amr.value.getDuration() + }) + // 监听暂停 + amr.value.onEnded(function () { + playing.value = false + }) +} +const amrPlay = () => { + playing.value = true + amr.value.play() +} +const amrStop = () => { + playing.value = false + amr.value.stop() +} +</script> + +<style lang="scss" scoped> +.wx-voice-div { + padding: 5px; + background-color: #eaeaea; + border-radius: 10px; +} +.amr-duration { + font-size: 11px; + margin-left: 5px; +} +</style> diff --git a/src/views/mp/freePublish/index.vue b/src/views/mp/freePublish/index.vue index 497f72ec..1d9b331e 100644 --- a/src/views/mp/freePublish/index.vue +++ b/src/views/mp/freePublish/index.vue @@ -1,3 +1,395 @@ <template> - <span>开发中</span> + <content-wrap> + <doc-alert title="公众号图文" url="https://doc.iocoder.cn/mp/article/" /> + + <!-- 搜索工作栏 --> + <el-form + :model="queryParams" + ref="queryFormRef" + size="small" + :inline="true" + v-show="showSearch" + label-width="68px" + > + <el-form-item label="公众号" prop="accountId"> + <el-select v-model="queryParams.accountId" placeholder="请选择公众号"> + <el-option + v-for="item in accounts" + :key="parseInt(item.id)" + :label="item.name" + :value="parseInt(item.id)" + /> + </el-select> + </el-form-item> + <el-form-item> + <el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button> + <el-button :icon="Refresh" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <!-- 列表 --> + <div class="waterfall" v-loading="loading"> + <div + v-show="item.content && item.content.newsItem" + class="waterfall-item" + v-for="item in list" + :key="item.articleId" + > + <wx-news :articles="item.content.newsItem" /> + <!-- 操作 --> + <el-row justify="center" class="ope-row"> + <el-button + type="danger" + :icon="Delete" + circle + @click="handleDelete(item)" + v-hasPermi="['mp:free-publish:delete']" + /> + </el-row> + </div> + </div> + <!-- 分页组件 --> + <pagination + v-show="total > 0" + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> </template> + +<script setup lang="ts" name="freePublish"> +import { getFreePublishPage, deleteFreePublish } from '@/api/mp/freePublish' +import { getSimpleAccounts } from '@/api/mp/account' +import WxNews from '@/views/mp/components/wx-news/main.vue' +import { Delete, Search, Refresh } from '@element-plus/icons-vue' + +const message = useMessage() // 消息弹窗 + +const queryParams = reactive({ + total: 0, // 总页数 + currentPage: 1, // 当前页数 + pageNo: 1, // 当前页数 + accountId: undefined, // 当前页数 + queryParamsSize: 10 // 每页显示多少条 +}) +const loading = ref(false) // 列表的加载中 +const showSearch = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const accounts = ref([]) // 列表的数据 +const queryFormRef = ref() // 搜索的表单 +/** 查询列表 */ +const getList = async () => { + // 如果没有选中公众号账号,则进行提示。 + if (!queryParams.accountId) { + message.error('未选中公众号,无法查询已发表图文') + return false + } + loading.value = true + getFreePublishPage(queryParams) + .then((data) => { + console.log(data) + // 将 thumbUrl 转成 picUrl,保证 wx-news 组件可以预览封面 + data.list.forEach((item) => { + console.log(item) + const newsItem = item.content.newsItem + newsItem.forEach((article) => { + article.picUrl = article.thumbUrl + }) + }) + list.value = data.list + total.value = data.total + }) + .finally(() => { + loading.value = false + }) +} +/** 搜索按钮操作 */ +const handleQuery = async () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = async () => { + queryFormRef.value.resetFields() + // 默认选中第一个 + if (accounts.value.length > 0) { + queryParams.accountId = accounts[0].id + } + handleQuery() +} + +/** 删除按钮操作 */ +const handleDelete = async (item) => { + { + const articleId = item.articleId + const accountId = queryParams.accountId + message + .confirm('删除后用户将无法访问此页面,确定删除?') + .then(function () { + return deleteFreePublish(accountId, articleId) + }) + .then(() => { + getList() + message.success('删除成功') + }) + .catch(() => {}) + } +} + +onMounted(() => { + getSimpleAccounts().then((response) => { + accounts.value = response + // 默认选中第一个 + if (accounts.value.length > 0) { + queryParams.accountId = accounts.value[0]['id'] + } + // 加载数据 + getList() + }) +}) +</script> + +<style lang="scss" scoped> +.pagination { + float: right; + margin-right: 25px; +} + +.add_but { + padding: 10px; +} + +.ope-row { + margin-top: 5px; + text-align: center; + border-top: 1px solid #eaeaea; + padding-top: 5px; +} + +.item-name { + font-size: 12px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: center; +} + +.el-upload__tip { + margin-left: 5px; +} + +/*新增图文*/ +.left { + display: inline-block; + width: 35%; + vertical-align: top; + margin-top: 200px; +} + +.right { + display: inline-block; + width: 60%; + margin-top: -40px; +} + +.avatar-uploader { + width: 20%; + display: inline-block; +} + +.avatar-uploader .el-upload { + border-radius: 6px; + cursor: pointer; + position: relative; + overflow: hidden; + text-align: unset !important; +} + +.avatar-uploader .el-upload:hover { + border-color: #165dff; +} + +.avatar-uploader-icon { + border: 1px solid #d9d9d9; + font-size: 28px; + color: #8c939d; + width: 120px; + height: 120px; + line-height: 120px; + text-align: center; +} + +.avatar { + width: 230px; + height: 120px; +} + +.avatar1 { + width: 120px; + height: 120px; +} + +.digest { + width: 60%; + display: inline-block; + vertical-align: top; +} + +/*新增图文*/ +/*瀑布流样式*/ +.waterfall { + width: 100%; + column-gap: 10px; + column-count: 5; + margin: 0 auto; +} + +.waterfall-item { + padding: 10px; + margin-bottom: 10px; + break-inside: avoid; + border: 1px solid #eaeaea; +} + +p { + line-height: 30px; +} + +@media (min-width: 992px) and (max-width: 1300px) { + .waterfall { + column-count: 3; + } + p { + color: red; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .waterfall { + column-count: 2; + } + p { + color: orange; + } +} + +@media (max-width: 767px) { + .waterfall { + column-count: 1; + } +} + +/*瀑布流样式*/ +.news-main { + background-color: #ffffff; + width: 100%; + margin: auto; + height: 120px; +} + +.news-content { + background-color: #acadae; + width: 100%; + height: 120px; + position: relative; +} + +.news-content-title { + display: inline-block; + font-size: 15px; + color: #ffffff; + position: absolute; + left: 0px; + bottom: 0px; + background-color: black; + width: 98%; + padding: 1%; + opacity: 0.65; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + height: 25px; +} + +.news-main-item { + background-color: #ffffff; + padding: 5px 0px; + border-top: 1px solid #eaeaea; + width: 100%; + margin: auto; +} + +.news-content-item { + position: relative; + margin-left: -3px; +} + +.news-content-item-title { + display: inline-block; + font-size: 12px; + width: 70%; +} + +.news-content-item-img { + display: inline-block; + width: 25%; + background-color: #acadae; +} + +.input-tt { + padding: 5px; +} + +.activeAddNews { + border: 5px solid #2bb673; +} + +.news-main-plus { + width: 280px; + text-align: center; + margin: auto; + height: 50px; +} + +.icon-plus { + margin: 10px; + font-size: 25px; +} + +.select-item { + width: 60%; + padding: 10px; + margin: 0 auto 10px auto; + border: 1px solid #eaeaea; +} + +.father .child { + display: none; + text-align: center; + position: relative; + bottom: 25px; +} + +.father:hover .child { + display: block; +} + +.thumb-div { + display: inline-block; + width: 30%; + text-align: center; +} + +.thumb-but { + margin: 5px; +} + +.material-img { + width: 100%; + height: 100%; +} +</style> diff --git a/src/views/mp/message/index.vue b/src/views/mp/message/index.vue index 497f72ec..34e64ebf 100644 --- a/src/views/mp/message/index.vue +++ b/src/views/mp/message/index.vue @@ -1,3 +1,262 @@ <template> - <span>开发中</span> + <ContentWrap> + <doc-alert title="公众号消息" url="https://doc.iocoder.cn/mp/message/" /> + + <!-- 搜索工作栏 --> + <el-form + :model="queryParams" + ref="queryFormRef" + size="small" + :inline="true" + v-show="showSearch" + label-width="68px" + > + <el-form-item label="公众号" prop="accountId"> + <el-select v-model="queryParams.accountId" placeholder="请选择公众号"> + <el-option + v-for="item in accounts" + :key="parseInt(item.id)" + :label="item.name" + :value="parseInt(item.id)" + /> + </el-select> + </el-form-item> + <el-form-item label="消息类型" prop="type"> + <el-select v-model="queryParams.type" placeholder="请选择消息类型" clearable size="small"> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.MP_MESSAGE_TYPE)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="用户标识" prop="openid"> + <el-input + v-model="queryParams.openid" + placeholder="请输入用户标识" + clearable + :v-on="handleQuery" + /> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + style="width: 240px" + value-format="yyyy-MM-dd HH:mm:ss" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="['00:00:00', '23:59:59']" + /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <!--todo 操作工具栏 --> + <!-- <el-row :gutter="10" class="mb8">--> + <!-- <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />--> + <!-- </el-row>--> + + <!-- 列表 --> + <el-table v-loading="loading" :data="list"> + <el-table-column label="发送时间" align="center" prop="createTime" width="180"> + <template #default="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column label="消息类型" align="center" prop="type" width="80" /> + <el-table-column label="发送方" align="center" prop="sendFrom" width="80"> + <template #default="scope"> + <el-tag v-if="scope.row.sendFrom === 1" type="success">粉丝</el-tag> + <el-tag v-else type="info">公众号</el-tag> + </template> + </el-table-column> + <el-table-column label="用户标识" align="center" prop="openid" width="300" /> + <el-table-column label="内容" prop="content"> + <template #default="scope"> + <!-- 【事件】区域 --> + <div v-if="scope.row.type === 'event' && scope.row.event === 'subscribe'"> + <el-tag type="success" size="mini">关注</el-tag> + </div> + <div v-else-if="scope.row.type === 'event' && scope.row.event === 'unsubscribe'"> + <el-tag type="danger" size="mini">取消关注</el-tag> + </div> + <div v-else-if="scope.row.type === 'event' && scope.row.event === 'CLICK'"> + <el-tag size="mini">点击菜单</el-tag>【{{ scope.row.eventKey }}】 + </div> + <div v-else-if="scope.row.type === 'event' && scope.row.event === 'VIEW'"> + <el-tag size="mini">点击菜单链接</el-tag>【{{ scope.row.eventKey }}】 + </div> + <div v-else-if="scope.row.type === 'event' && scope.row.event === 'scancode_waitmsg'"> + <el-tag size="mini">扫码结果</el-tag>【{{ scope.row.eventKey }}】 + </div> + <div v-else-if="scope.row.type === 'event' && scope.row.event === 'scancode_push'"> + <el-tag size="mini">扫码结果</el-tag>【{{ scope.row.eventKey }}】 + </div> + <div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_sysphoto'"> + <el-tag size="mini">系统拍照发图</el-tag> + </div> + <div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_photo_or_album'"> + <el-tag size="mini">拍照或者相册</el-tag> + </div> + <div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_weixin'"> + <el-tag size="mini">微信相册</el-tag> + </div> + <div v-else-if="scope.row.type === 'event' && scope.row.event === 'location_select'"> + <el-tag size="mini">选择地理位置</el-tag> + </div> + <div v-else-if="scope.row.type === 'event'"> + <el-tag type="danger" size="mini">未知事件类型</el-tag> + </div> + <!-- 【消息】区域 --> + <div v-else-if="scope.row.type === 'text'">{{ scope.row.content }}</div> + <div v-else-if="scope.row.type === 'voice'"> + <wx-voice-player :url="scope.row.mediaUrl" :content="scope.row.recognition" /> + </div> + <div v-else-if="scope.row.type === 'image'"> + <a target="_blank" :href="scope.row.mediaUrl"> + <img :src="scope.row.mediaUrl" style="width: 100px" /> + </a> + </div> + <div v-else-if="scope.row.type === 'video' || scope.row.type === 'shortvideo'"> + <wx-video-player :url="scope.row.mediaUrl" style="margin-top: 10px" /> + </div> + <div v-else-if="scope.row.type === 'link'"> + <el-tag size="mini">链接</el-tag>: + <a :href="scope.row.url" target="_blank">{{ scope.row.title }}</a> + </div> + <div v-else-if="scope.row.type === 'location'"> + <wx-location + :label="scope.row.label" + :location-y="scope.row.locationY" + :location-x="scope.row.locationX" + /> + </div> + <div v-else-if="scope.row.type === 'music'"> + <wx-music + :title="scope.row.title" + :description="scope.row.description" + :thumb-media-url="scope.row.thumbMediaUrl" + :music-url="scope.row.musicUrl" + :hq-music-url="scope.row.hqMusicUrl" + /> + </div> + <div v-else-if="scope.row.type === 'news'"> + <wx-news :articles="scope.row.articles" /> + </div> + <div v-else> + <el-tag type="danger" size="mini">未知消息类型</el-tag> + </div> + </template> + </el-table-column> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-edit" + @click="handleSend(scope.row)" + v-hasPermi="['mp:message:send']" + >消息 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页组件 --> + <pagination + v-show="total > 0" + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + + <!-- 发送消息的弹窗 --> + <el-dialog title="粉丝消息列表" v-model:visible="open" width="50%"> + <wx-msg :user-id="userId" v-if="open" /> + </el-dialog> + </ContentWrap> </template> + +<script setup lang="ts" name="MpMessage"> +import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' +import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' +// import WxMsg from '@/views/mp/components/wx-msg/main.vue' +import WxLocation from '@/views/mp/components/wx-location/main.vue' +import WxMusic from '@/views/mp/components/wx-music/main.vue' +import WxNews from '@/views/mp/components/wx-news/main.vue' +import { getMessagePage } from '@/api/mp/message' +import { getSimpleAccounts } from '@/api/mp/account' +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { parseTime } from '@/utils/formatTime' + +// ========== CRUD 相关 ========== +const loading = ref(false) // 遮罩层 +const showSearch = ref(true) // 显示搜索条件 +const total = ref(0) // 总条数 +const list = ref([]) // 粉丝消息列表 +const accounts = ref([]) // 公众号账号列表 +const open = ref(false) // 是否显示弹出层 +const userId = ref(0) // 操作的用户编号 +const message = useMessage() // 消息弹窗 +const queryFormRef = ref() // 搜索的表单 + +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + openid: null, + accountId: null, + type: null, + createTime: [] +}) // 是否显示弹出层 + +const getList = async () => { + // 如果没有选中公众号账号,则进行提示。 + if (!queryParams.accountId) { + message.error('未选中公众号,无法查询消息') + return false + } + + loading.value = true + // 执行查询 + getMessagePage(queryParams).then((data) => { + console.log(data) + list.value = data.list + total.value = data.total + loading.value = false + }) +} + +const handleQuery = async () => { + queryParams.pageNo = 1 + getList() +} +const resetQuery = async () => { + queryFormRef.value.resetFields() + // 默认选中第一个 + if (accounts.value.length > 0) { + queryParams.accountId = accounts[0].id + } + handleQuery() +} +const handleSend = async (row) => { + userId.value = row.userId + open.value = true +} +onMounted(() => { + getSimpleAccounts().then((response) => { + accounts.value = response + // 默认选中第一个 + if (accounts.value.length > 0) { + queryParams.accountId = accounts.value[0]['id'] + } + // 加载数据 + getList() + }) +}) +</script> From 3c6bf378792e385147737f3a2583dd951f03c5be Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 26 Mar 2023 08:46:04 +0800 Subject: [PATCH 106/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E5=AE=9A=E4=B9=89=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/bpm/definition/index.ts | 6 +- src/views/bpm/definition/definition.data.ts | 79 ------- src/views/bpm/definition/index.vue | 235 ++++++++++++-------- src/views/bpm/model/index.vue | 15 +- src/views/bpm/processInstance/create.vue | 4 +- src/views/bpm/processInstance/detail.vue | 2 +- src/views/system/sms/template/index.vue | 1 - 7 files changed, 153 insertions(+), 189 deletions(-) delete mode 100644 src/views/bpm/definition/definition.data.ts diff --git a/src/api/bpm/definition/index.ts b/src/api/bpm/definition/index.ts index 477d6729..c0e51fab 100644 --- a/src/api/bpm/definition/index.ts +++ b/src/api/bpm/definition/index.ts @@ -1,19 +1,19 @@ import request from '@/config/axios' -export const getProcessDefinitionBpmnXMLApi = async (id: number) => { +export const getProcessDefinitionBpmnXML = async (id: number) => { return await request.get({ url: '/bpm/process-definition/get-bpmn-xml?id=' + id }) } -export const getProcessDefinitionPageApi = async (params) => { +export const getProcessDefinitionPage = async (params) => { return await request.get({ url: '/bpm/process-definition/page', params }) } -export const getProcessDefinitionListApi = async (params) => { +export const getProcessDefinitionList = async (params) => { return await request.get({ url: '/bpm/process-definition/list', params diff --git a/src/views/bpm/definition/definition.data.ts b/src/views/bpm/definition/definition.data.ts deleted file mode 100644 index 14a0c319..00000000 --- a/src/views/bpm/definition/definition.data.ts +++ /dev/null @@ -1,79 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: null, - action: true, - columns: [ - { - title: '定义编号', - field: 'id', - table: { - width: 360 - } - }, - { - title: '定义名称', - field: 'name', - table: { - // width: 120, - slots: { - default: 'name_default' - } - } - }, - { - title: '定义分类', - field: 'category', - // dictType: DICT_TYPE.BPM_MODEL_CATEGORY, - // dictClass: 'number', - table: { - // width: 120, - slots: { - default: 'category_default' - } - } - }, - { - title: '表单信息', - field: 'formId', - table: { - // width: 200, - slots: { - default: 'formId_default' - } - } - }, - { - title: '流程版本', - field: 'version', - table: { - // width: 80, - slots: { - default: 'version_default' - } - } - }, - { - title: '激活状态', - field: 'suspensionState', - table: { - // width: 80, - slots: { - default: 'suspensionState_default' - } - } - }, - { - title: '部署时间', - field: 'deploymentTime', - isForm: false, - formatter: 'formatDate' - // table: { - // width: 180 - // } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) diff --git a/src/views/bpm/definition/index.vue b/src/views/bpm/definition/index.vue index f2ef640c..ce643ff6 100644 --- a/src/views/bpm/definition/index.vue +++ b/src/views/bpm/definition/index.vue @@ -1,92 +1,138 @@ <template> <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <!-- 流程名称 --> - <template #name_default="{ row }"> - <XTextButton :title="row.name" @click="handleBpmnDetail(row.id)" /> - </template> - <!-- 流程分类 --> - <template #category_default="{ row }"> - <DictTag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="Number(row?.category)" /> - </template> - <!-- 表单信息 --> - <template #formId_default="{ row }"> - <XTextButton - v-if="row.formType === 10" - :title="row.formName" - @click="handleFormDetail(row)" - /> - <XTextButton v-else :title="row.formCustomCreatePath" @click="handleFormDetail(row)" /> - </template> - <!-- 流程版本 --> - <template #version_default="{ row }"> - <el-tag>v{{ row.version }}</el-tag> - </template> - <!-- 激活状态 --> - <template #suspensionState_default="{ row }"> - <el-tag type="success" v-if="row.suspensionState === 1">激活</el-tag> - <el-tag type="warning" v-if="row.suspensionState === 2">挂起</el-tag> - </template> - <!-- 操作 --> - <template #actionbtns_default="{ row }"> - <XTextButton - preIcon="ep:user" - title="分配规则" - v-hasPermi="['bpm:task-assign-rule:query']" - @click="handleAssignRule(row)" - /> - </template> - </XTable> - - <!-- 表单详情的弹窗 --> - <XModal v-model="formDetailVisible" width="800" title="表单详情" :show-footer="false"> - <form-create - :rule="formDetailPreview.rule" - :option="formDetailPreview.option" - v-if="formDetailVisible" + <el-table v-loading="loading" :data="list"> + <el-table-column label="定义编号" align="center" prop="id" width="400" /> + <el-table-column label="流程名称" align="center" prop="name" width="200"> + <template #default="scope"> + <el-button type="text" @click="handleBpmnDetail(scope.row)"> + <span>{{ scope.row.name }}</span> + </el-button> + </template> + </el-table-column> + <el-table-column label="定义分类" align="center" prop="category" width="100"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" /> + </template> + </el-table-column> + <el-table-column label="表单信息" align="center" prop="formType" width="200"> + <template #default="scope"> + <el-button + v-if="scope.row.formType === 10" + type="text" + @click="handleFormDetail(scope.row)" + > + <span>{{ scope.row.formName }}</span> + </el-button> + <el-button v-else type="text" @click="handleFormDetail(scope.row)"> + <span>{{ scope.row.formCustomCreatePath }}</span> + </el-button> + </template> + </el-table-column> + <el-table-column label="流程版本" align="center" prop="processDefinition.version" width="80"> + <template #default="scope"> + <el-tag v-if="scope.row">v{{ scope.row.version }}</el-tag> + <el-tag type="warning" v-else>未部署</el-tag> + </template> + </el-table-column> + <el-table-column label="状态" align="center" prop="version" width="80"> + <template #default="scope"> + <el-tag type="success" v-if="scope.row.suspensionState === 1">激活</el-tag> + <el-tag type="warning" v-if="scope.row.suspensionState === 2">挂起</el-tag> + </template> + </el-table-column> + <el-table-column + label="部署时间" + align="center" + prop="deploymentTime" + width="180" + :formatter="dateFormatter" /> - </XModal> - <!-- 流程模型图的预览 --> - <XModal title="流程图" v-model="showBpmnOpen" width="80%" height="90%"> - <my-process-viewer - key="designer" - v-model="bpmnXML" - :value="bpmnXML" - v-bind="bpmnControlForm" - :prefix="bpmnControlForm.prefix" + <el-table-column + label="定义描述" + align="center" + prop="description" + width="300" + show-overflow-tooltip /> - </XModal> + <el-table-column label="操作" align="center" width="150" fixed="right"> + <template #default="scope"> + <el-button + link + type="primary" + @click="handleAssignRule(scope.row)" + v-hasPermi="['bpm:task-assign-rule:query']" + > + 分配规则 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> </ContentWrap> -</template> -<script setup lang="ts"> -// 业务相关的 import -import * as DefinitionApi from '@/api/bpm/definition' -// import * as ModelApi from '@/api/bpm/model' -import { allSchemas } from './definition.data' -import { setConfAndFields2 } from '@/utils/formCreate' -import { DICT_TYPE } from '@/utils/dict' -const bpmnXML = ref(null) -const showBpmnOpen = ref(false) -const bpmnControlForm = ref({ - prefix: 'flowable' -}) -// const message = useMessage() // 消息弹窗 -const router = useRouter() // 路由 + <!-- 弹窗:表单详情 --> + <Dialog title="表单详情" v-model="formDetailVisible" width="800"> + <form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" /> + </Dialog> + + <!-- 弹窗:流程模型图的预览 --> + <Dialog title="流程图" v-model="bpmnDetailVisible" width="800"> + <my-process-viewer + key="designer" + v-model="bpmnXML" + :value="bpmnXML" + v-bind="bpmnControlForm" + :prefix="bpmnControlForm.prefix" + /> + </Dialog> +</template> + +<script setup lang="ts" name="Form"> +import { DICT_TYPE } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' +import * as DefinitionApi from '@/api/bpm/definition' +import { setConfAndFields2 } from '@/utils/formCreate' +const { push } = useRouter() // 路由 const { query } = useRoute() // 查询参数 -// ========== 列表相关 ========== +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 const queryParams = reactive({ + pageNo: 1, + pageSize: 10, key: query.key }) -const [registerTable] = useXTable({ - allSchemas: allSchemas, - getListApi: DefinitionApi.getProcessDefinitionPageApi, - params: queryParams -}) -// 流程表单的详情按钮操作 +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await DefinitionApi.getProcessDefinitionPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 点击任务分配按钮 */ +const handleAssignRule = (row) => { + push({ + name: 'BpmTaskAssignRuleList', + query: { + modelId: row.id + } + }) +} + +/** 流程表单的详情按钮操作 */ const formDetailVisible = ref(false) const formDetailPreview = ref({ rule: [], @@ -99,32 +145,25 @@ const handleFormDetail = async (row) => { // 弹窗打开 formDetailVisible.value = true } else { - await router.push({ + await push({ path: row.formCustomCreatePath }) } } -// 流程图的详情按钮操作 -const handleBpmnDetail = (row) => { - // TODO 芋艿:流程组件开发中 - console.log(row) - DefinitionApi.getProcessDefinitionBpmnXMLApi(row).then((response) => { - console.log(response, 'response') - bpmnXML.value = response - // 弹窗打开 - showBpmnOpen.value = true - }) - // message.success('流程组件开发中,预计 2 月底完成') +/** 流程图的详情按钮操作 */ +const bpmnDetailVisible = ref(false) +const bpmnXML = ref(null) +const bpmnControlForm = ref({ + prefix: 'flowable' +}) +const handleBpmnDetail = async (row) => { + bpmnXML.value = await DefinitionApi.getProcessDefinitionBpmnXML(row.id) + bpmnDetailVisible.value = true } -// 点击任务分配按钮 -const handleAssignRule = (row) => { - router.push({ - name: 'BpmTaskAssignRuleList', - query: { - processDefinitionId: row.id - } - }) -} +/** 初始化 **/ +onMounted(() => { + getList() +}) </script> diff --git a/src/views/bpm/model/index.vue b/src/views/bpm/model/index.vue index cd744986..b19ed956 100644 --- a/src/views/bpm/model/index.vue +++ b/src/views/bpm/model/index.vue @@ -62,7 +62,7 @@ <!-- 列表 --> <ContentWrap> <el-table v-loading="loading" :data="list"> - <el-table-column label="流程标识" align="center" prop="key" /> + <el-table-column label="流程标识" align="center" prop="key" width="200" /> <el-table-column label="流程名称" align="center" prop="name" width="200"> <template #default="scope"> <el-button type="text" @click="handleBpmnDetail(scope.row)"> @@ -77,11 +77,15 @@ </el-table-column> <el-table-column label="表单信息" align="center" prop="formType" width="200"> <template #default="scope"> - <el-button v-if="scope.row.formId" type="text" @click="handleFormDetail(scope.row)"> + <el-button + v-if="scope.row.formType === 10" + type="text" + @click="handleFormDetail(scope.row)" + > <span>{{ scope.row.formName }}</span> </el-button> <el-button - v-else-if="scope.row.formCustomCreatePath" + v-else-if="scope.row.formType === 20" type="text" @click="handleFormDetail(scope.row)" > @@ -94,6 +98,7 @@ label="创建时间" align="center" prop="createTime" + width="180" :formatter="dateFormatter" /> <el-table-column label="最新部署的流程定义" align="center"> @@ -114,7 +119,7 @@ label="激活状态" align="center" prop="processDefinition.version" - width="80" + width="85" > <template #default="scope"> <el-switch @@ -134,7 +139,7 @@ </template> </el-table-column> </el-table-column> - <el-table-column label="操作" align="center" fixed="right" class-name="fixed-width"> + <el-table-column label="操作" align="center" width="240" fixed="right"> <template #default="scope"> <el-button link diff --git a/src/views/bpm/processInstance/create.vue b/src/views/bpm/processInstance/create.vue index 1b59ec7c..b6fc0f49 100644 --- a/src/views/bpm/processInstance/create.vue +++ b/src/views/bpm/processInstance/create.vue @@ -72,7 +72,7 @@ const [registerTable] = useXTable({ params: { suspensionState: 1 }, - getListApi: DefinitionApi.getProcessDefinitionListApi, + getListApi: DefinitionApi.getProcessDefinitionList, isList: true }) @@ -99,7 +99,7 @@ const handleSelect = async (row) => { setConfAndFields2(detailForm, row.formConf, row.formFields) // 加载流程图 - DefinitionApi.getProcessDefinitionBpmnXMLApi(row.id).then((response) => { + DefinitionApi.getProcessDefinitionBpmnXML(row.id).then((response) => { bpmnXML.value = response }) // 情况二:业务表单 diff --git a/src/views/bpm/processInstance/detail.vue b/src/views/bpm/processInstance/detail.vue index 91fbfe19..a34d8a1a 100644 --- a/src/views/bpm/processInstance/detail.vue +++ b/src/views/bpm/processInstance/detail.vue @@ -411,7 +411,7 @@ const getDetail = () => { } // 加载流程图 - DefinitionApi.getProcessDefinitionBpmnXMLApi(processDefinition.id).then((data) => { + DefinitionApi.getProcessDefinitionBpmnXML(processDefinition.id).then((data) => { bpmnXML.value = data }) diff --git a/src/views/system/sms/template/index.vue b/src/views/system/sms/template/index.vue index 33cbeb90..f43b8f62 100644 --- a/src/views/system/sms/template/index.vue +++ b/src/views/system/sms/template/index.vue @@ -169,7 +169,6 @@ <el-table-column label="操作" align="center" - class-name="fixed-width" width="210" fixed="right" > From eead9efc5efd5e75eea1d0c1ea6f1322454e3ae9 Mon Sep 17 00:00:00 2001 From: yj441106 <yj441106@163.com> Date: Sun, 26 Mar 2023 15:22:51 +0800 Subject: [PATCH 107/184] =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=E9=87=8D?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/oauth2/client/client.data.ts | 197 ------------- src/views/system/oauth2/client/form.vue | 259 ++++++++++++++++++ src/views/system/oauth2/token/form.vue | 131 +++++++++ 3 files changed, 390 insertions(+), 197 deletions(-) delete mode 100644 src/views/system/oauth2/client/client.data.ts create mode 100644 src/views/system/oauth2/client/form.vue create mode 100644 src/views/system/oauth2/token/form.vue diff --git a/src/views/system/oauth2/client/client.data.ts b/src/views/system/oauth2/client/client.data.ts deleted file mode 100644 index 52ee8895..00000000 --- a/src/views/system/oauth2/client/client.data.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { DICT_TYPE, getStrDictOptions } from '@/utils/dict' -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -const { t } = useI18n() // 国际化 - -const authorizedGrantOptions = getStrDictOptions(DICT_TYPE.SYSTEM_OAUTH2_GRANT_TYPE) - -// 表单校验 -export const rules = reactive({ - clientId: [required], - secret: [required], - name: [required], - status: [required], - accessTokenValiditySeconds: [required], - refreshTokenValiditySeconds: [required], - redirectUris: [required], - authorizedGrantTypes: [required] -}) - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'clientId', - primaryType: null, - action: true, - columns: [ - { - title: '客户端端号', - field: 'clientId' - }, - { - title: '客户端密钥', - field: 'secret' - }, - { - title: '应用名', - field: 'name', - isSearch: true - }, - { - title: '应用图标', - field: 'logo', - table: { - cellRender: { - name: 'XImg' - } - }, - form: { - component: 'UploadImg' - } - }, - { - title: t('common.status'), - field: 'status', - dictType: DICT_TYPE.COMMON_STATUS, - dictClass: 'number', - isSearch: true - }, - { - title: '访问令牌的有效期', - field: 'accessTokenValiditySeconds', - form: { - component: 'InputNumber' - }, - table: { - slots: { - default: 'accessTokenValiditySeconds_default' - } - } - }, - { - title: '刷新令牌的有效期', - field: 'refreshTokenValiditySeconds', - form: { - component: 'InputNumber' - }, - table: { - slots: { - default: 'refreshTokenValiditySeconds_default' - } - } - }, - { - title: '授权类型', - field: 'authorizedGrantTypes', - table: { - width: 400, - slots: { - default: 'authorizedGrantTypes_default' - } - }, - form: { - component: 'Select', - componentProps: { - options: authorizedGrantOptions, - multiple: true, - filterable: true - } - } - }, - { - title: '授权范围', - field: 'scopes', - isTable: false, - form: { - component: 'Select', - componentProps: { - options: [], - multiple: true, - filterable: true, - allowCreate: true, - defaultFirstOption: true - } - } - }, - { - title: '自动授权范围', - field: 'autoApproveScopes', - isTable: false, - form: { - component: 'Select', - componentProps: { - options: [], - multiple: true, - filterable: true, - allowCreate: true, - defaultFirstOption: true - } - } - }, - { - title: '可重定向的 URI 地址', - field: 'redirectUris', - isTable: false, - form: { - component: 'Select', - componentProps: { - options: [], - multiple: true, - filterable: true, - allowCreate: true, - defaultFirstOption: true - } - } - }, - { - title: '权限', - field: 'authorities', - isTable: false, - form: { - component: 'Select', - componentProps: { - options: [], - multiple: true, - filterable: true, - allowCreate: true, - defaultFirstOption: true - } - } - }, - { - title: '资源', - field: 'resourceIds', - isTable: false, - form: { - component: 'Select', - componentProps: { - options: [], - multiple: true, - filterable: true, - allowCreate: true, - defaultFirstOption: true - } - } - }, - { - title: '附加信息', - field: 'additionalInformation', - isTable: false, - form: { - component: 'Input', - componentProps: { - type: 'textarea', - rows: 4 - }, - colProps: { - span: 24 - } - } - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) diff --git a/src/views/system/oauth2/client/form.vue b/src/views/system/oauth2/client/form.vue new file mode 100644 index 00000000..0822e59f --- /dev/null +++ b/src/views/system/oauth2/client/form.vue @@ -0,0 +1,259 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible" width="800"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="120px" + v-loading="formLoading" + > + <el-form-item label="客户端编号" prop="secret"> + <el-input v-model="formData.clientId" placeholder="请输入客户端编号" /> + </el-form-item> + <el-form-item label="客户端密钥" prop="secret"> + <el-input v-model="formData.secret" placeholder="请输入客户端密钥" /> + </el-form-item> + <el-form-item label="应用名" prop="name"> + <el-input v-model="formData.name" placeholder="请输入应用名" /> + </el-form-item> + <el-form-item label="应用图标"> + <UploadImg v-model="formData.logo" :limit="1" /> + </el-form-item> + <el-form-item label="应用描述"> + <el-input type="textarea" v-model="formData.description" placeholder="请输入应用名" /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-radio-group v-model="formData.status"> + <el-radio + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="parseInt(dict.value)" + >{{ dict.label }}</el-radio + > + </el-radio-group> + </el-form-item> + <el-form-item label="访问令牌的有效期" prop="accessTokenValiditySeconds"> + <el-input-number v-model="formData.accessTokenValiditySeconds" placeholder="单位:秒" /> + </el-form-item> + <el-form-item label="刷新令牌的有效期" prop="refreshTokenValiditySeconds"> + <el-input-number v-model="formData.refreshTokenValiditySeconds" placeholder="单位:秒" /> + </el-form-item> + <el-form-item label="授权类型" prop="authorizedGrantTypes"> + <el-select + v-model="formData.authorizedGrantTypes" + multiple + filterable + placeholder="请输入授权类型" + style="width: 500px" + > + <el-option + v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_OAUTH2_GRANT_TYPE)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="授权范围" prop="scopes"> + <el-select + v-model="formData.scopes" + multiple + filterable + allow-create + placeholder="请输入授权范围" + style="width: 500px" + > + <el-option v-for="scope in formData.scopes" :key="scope" :label="scope" :value="scope" /> + </el-select> + </el-form-item> + <el-form-item label="自动授权范围" prop="autoApproveScopes"> + <el-select + v-model="formData.autoApproveScopes" + multiple + filterable + placeholder="请输入授权范围" + style="width: 500px" + > + <el-option v-for="scope in formData.scopes" :key="scope" :label="scope" :value="scope" /> + </el-select> + </el-form-item> + <el-form-item label="可重定向的 URI 地址" prop="redirectUris"> + <el-select + v-model="formData.redirectUris" + multiple + filterable + allow-create + placeholder="请输入可重定向的 URI 地址" + style="width: 500px" + > + <el-option + v-for="redirectUri in formData.redirectUris" + :key="redirectUri" + :label="redirectUri" + :value="redirectUri" + /> + </el-select> + </el-form-item> + <el-form-item label="权限" prop="authorities"> + <el-select + v-model="formData.authorities" + multiple + filterable + allow-create + placeholder="请输入权限" + style="width: 500px" + > + <el-option + v-for="authority in formData.authorities" + :key="authority" + :label="authority" + :value="authority" + /> + </el-select> + </el-form-item> + <el-form-item label="资源" prop="resourceIds"> + <el-select + v-model="formData.resourceIds" + multiple + filterable + allow-create + placeholder="请输入资源" + style="width: 500px" + > + <el-option + v-for="resourceId in formData.resourceIds" + :key="resourceId" + :label="resourceId" + :value="resourceId" + /> + </el-select> + </el-form-item> + <el-form-item label="附加信息" prop="additionalInformation"> + <el-input + type="textarea" + v-model="formData.additionalInformation" + placeholder="请输入附加信息,JSON 格式数据" + /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import * as ClientApi from '@/api/system/oauth2/client' +import UploadImg from '@/components/UploadFile' +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: undefined, + clientId: undefined, + secret: undefined, + name: undefined, + logo: undefined, + description: undefined, + status: DICT_TYPE.COMMON_STATUS, + accessTokenValiditySeconds: 30 * 60, + refreshTokenValiditySeconds: 30 * 24 * 60, + redirectUris: [], + authorizedGrantTypes: [], + scopes: [], + autoApproveScopes: [], + authorities: [], + resourceIds: [], + additionalInformation: undefined +}) +const formRules = reactive({ + clientId: [{ required: true, message: '客户端编号不能为空', trigger: 'blur' }], + secret: [{ required: true, message: '客户端密钥不能为空', trigger: 'blur' }], + name: [{ required: true, message: '应用名不能为空', trigger: 'blur' }], + logo: [{ required: true, message: '应用图标不能为空', trigger: 'blur' }], + status: [{ required: true, message: '状态不能为空', trigger: 'blur' }], + accessTokenValiditySeconds: [ + { required: true, message: '访问令牌的有效期不能为空', trigger: 'blur' } + ], + refreshTokenValiditySeconds: [ + { required: true, message: '刷新令牌的有效期不能为空', trigger: 'blur' } + ], + redirectUris: [{ required: true, message: '可重定向的 URI 地址不能为空', trigger: 'blur' }], + authorizedGrantTypes: [{ required: true, message: '授权类型不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await ClientApi.getOAuth2ClientApi(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as ClientApi.OAuth2ClientVO + if (formType.value === 'create') { + await ClientApi.createOAuth2ClientApi(data) + message.success(t('common.createSuccess')) + } else { + await ClientApi.updateOAuth2ClientApi(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + clientId: undefined, + secret: undefined, + name: undefined, + logo: undefined, + description: undefined, + status: DICT_TYPE.COMMON_STATUS, + accessTokenValiditySeconds: 30 * 60, + refreshTokenValiditySeconds: 30 * 24 * 60, + redirectUris: [], + authorizedGrantTypes: [], + scopes: [], + autoApproveScopes: [], + authorities: [], + resourceIds: [], + additionalInformation: undefined + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/system/oauth2/token/form.vue b/src/views/system/oauth2/token/form.vue new file mode 100644 index 00000000..5372ca7e --- /dev/null +++ b/src/views/system/oauth2/token/form.vue @@ -0,0 +1,131 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="参数分类" prop="category"> + <el-input v-model="formData.category" placeholder="请输入参数分类" /> + </el-form-item> + <el-form-item label="参数名称" prop="name"> + <el-input v-model="formData.name" placeholder="请输入参数名称" /> + </el-form-item> + <el-form-item label="参数键名" prop="key"> + <el-input v-model="formData.key" placeholder="请输入参数键名" /> + </el-form-item> + <el-form-item label="参数键值" prop="value"> + <el-input v-model="formData.value" placeholder="请输入参数键值" /> + </el-form-item> + <el-form-item label="是否可见" prop="visible"> + <el-radio-group v-model="formData.visible"> + <el-radio + v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" + :key="dict.value" + :label="dict.value" + > + {{ dict.label }} + </el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="formData.remark" type="textarea" placeholder="请输入内容" /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import * as ConfigApi from '@/api/infra/config' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: undefined, + category: '', + name: '', + key: '', + value: '', + visible: true, + remark: '' +}) +const formRules = reactive({ + category: [{ required: true, message: '参数分类不能为空', trigger: 'blur' }], + name: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }], + key: [{ required: true, message: '参数键名不能为空', trigger: 'blur' }], + value: [{ required: true, message: '参数键值不能为空', trigger: 'blur' }], + visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await ConfigApi.getConfig(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as ConfigApi.ConfigVO + if (formType.value === 'create') { + await ConfigApi.createConfig(data) + message.success(t('common.createSuccess')) + } else { + await ConfigApi.updateConfig(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + category: '', + name: '', + key: '', + value: '', + visible: true, + remark: '' + } + formRef.value?.resetFields() +} +</script> From f5d900db29abcae8330cc1265481e4acae116800 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 26 Mar 2023 19:32:19 +0800 Subject: [PATCH 108/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E5=88=86=E9=85=8D=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/bpm/userGroup/index.ts | 2 +- src/api/system/dept/index.ts | 2 +- src/api/system/post/index.ts | 2 +- src/api/system/role/index.ts | 2 +- src/api/system/user/index.ts | 2 +- src/router/modules/remaining.ts | 2 +- .../bpm/taskAssignRule/TaskAssignRuleForm.vue | 247 ++++++++++++ src/views/bpm/taskAssignRule/index.vue | 367 ++++-------------- .../bpm/taskAssignRule/taskAssignRule.data.ts | 54 --- src/views/system/dept/DeptForm.vue | 2 +- src/views/system/role/index.vue | 4 +- src/views/system/user/index.vue | 8 +- 12 files changed, 331 insertions(+), 363 deletions(-) create mode 100644 src/views/bpm/taskAssignRule/TaskAssignRuleForm.vue delete mode 100644 src/views/bpm/taskAssignRule/taskAssignRule.data.ts diff --git a/src/api/bpm/userGroup/index.ts b/src/api/bpm/userGroup/index.ts index c3399f27..035762bf 100644 --- a/src/api/bpm/userGroup/index.ts +++ b/src/api/bpm/userGroup/index.ts @@ -42,6 +42,6 @@ export const getUserGroupPage = async (params) => { } // 获取用户组精简信息列表 -export const listSimpleUserGroup = async () => { +export const getSimpleUserGroupList = async (): Promise<UserGroupVO[]> => { return await request.get({ url: '/bpm/user-group/list-all-simple' }) } diff --git a/src/api/system/dept/index.ts b/src/api/system/dept/index.ts index d66de3f1..e9c31fd7 100644 --- a/src/api/system/dept/index.ts +++ b/src/api/system/dept/index.ts @@ -18,7 +18,7 @@ export interface DeptPageReqVO { } // 查询部门(精简)列表 -export const listSimpleDeptApi = async () => { +export const getSimpleDeptList = async (): Promise<DeptVO[]> => { return await request.get({ url: '/system/dept/list-all-simple' }) } diff --git a/src/api/system/post/index.ts b/src/api/system/post/index.ts index 98df227f..405db387 100644 --- a/src/api/system/post/index.ts +++ b/src/api/system/post/index.ts @@ -16,7 +16,7 @@ export const getPostPage = async (params: PageParam) => { } // 获取岗位精简信息列表 -export const getSimplePostList = async () => { +export const getSimplePostList = async (): Promise<PostVO[]> => { return await request.get({ url: '/system/post/list-all-simple' }) } diff --git a/src/api/system/role/index.ts b/src/api/system/role/index.ts index 0d477555..9692548a 100644 --- a/src/api/system/role/index.ts +++ b/src/api/system/role/index.ts @@ -28,7 +28,7 @@ export const getRolePageApi = async (params: RolePageReqVO) => { } // 查询角色(精简)列表 -export const listSimpleRolesApi = async () => { +export const getSimpleRoleList = async (): Promise<RoleVO[]> => { return await request.get({ url: '/system/role/list-all-simple' }) } diff --git a/src/api/system/user/index.ts b/src/api/system/user/index.ts index 3cc0a84d..058b320a 100644 --- a/src/api/system/user/index.ts +++ b/src/api/system/user/index.ts @@ -86,6 +86,6 @@ export const updateUserStatusApi = (id: number, status: number) => { } // 获取用户精简信息列表 -export const getSimpleUserList = () => { +export const getSimpleUserList = (): Promise<UserVO[]> => { return request.get({ url: '/system/user/list-all-simple' }) } diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index b1bdfafe..58d5601b 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -256,7 +256,7 @@ const remainingRouter: AppRouteRecordRaw[] = [ hidden: true, canTo: true, title: '流程定义', - activeMenu: 'bpm/definition/index' + activeMenu: '/bpm/manager/model' } }, { diff --git a/src/views/bpm/taskAssignRule/TaskAssignRuleForm.vue b/src/views/bpm/taskAssignRule/TaskAssignRuleForm.vue new file mode 100644 index 00000000..a452cab9 --- /dev/null +++ b/src/views/bpm/taskAssignRule/TaskAssignRuleForm.vue @@ -0,0 +1,247 @@ +<template> + <Dialog title="修改任务规则" v-model="modelVisible" width="600"> + <el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px"> + <el-form-item label="任务名称" prop="taskDefinitionName"> + <el-input v-model="formData.taskDefinitionName" placeholder="请输入流标标识" disabled /> + </el-form-item> + <el-form-item label="任务标识" prop="taskDefinitionKey"> + <el-input v-model="formData.taskDefinitionKey" placeholder="请输入任务标识" disabled /> + </el-form-item> + <el-form-item label="规则类型" prop="type"> + <el-select v-model="formData.type" clearable style="width: 100%"> + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_RULE_TYPE)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item v-if="formData.type === 10" label="指定角色" prop="roleIds"> + <el-select v-model="formData.roleIds" multiple clearable style="width: 100%"> + <el-option + v-for="item in roleOptions" + :key="item.id" + :label="item.name" + :value="item.id" + /> + </el-select> + </el-form-item> + <el-form-item + label="指定部门" + prop="deptIds" + span="24" + v-if="formData.type === 20 || formData.type === 21" + > + <el-tree-select + ref="treeRef" + v-model="formData.deptIds" + node-key="id" + show-checkbox + :props="defaultProps" + :data="deptTreeOptions" + empty-text="加载中,请稍后" + multiple + /> + </el-form-item> + <el-form-item label="指定岗位" prop="postIds" span="24" v-if="formData.type === 22"> + <el-select v-model="formData.postIds" multiple clearable style="width: 100%"> + <el-option + v-for="item in postOptions" + :key="parseInt(item.id)" + :label="item.name" + :value="parseInt(item.id)" + /> + </el-select> + </el-form-item> + <el-form-item + label="指定用户" + prop="userIds" + span="24" + v-if="formData.type === 30 || formData.type === 31 || formData.type === 32" + > + <el-select v-model="formData.userIds" multiple clearable style="width: 100%"> + <el-option + v-for="item in userOptions" + :key="parseInt(item.id)" + :label="item.nickname" + :value="parseInt(item.id)" + /> + </el-select> + </el-form-item> + <el-form-item label="指定用户组" prop="userGroupIds" v-if="formData.type === 40"> + <el-select v-model="formData.userGroupIds" multiple clearable style="width: 100%"> + <el-option + v-for="item in userGroupOptions" + :key="parseInt(item.id)" + :label="item.name" + :value="parseInt(item.id)" + /> + </el-select> + </el-form-item> + <el-form-item label="指定脚本" prop="scripts" v-if="formData.type === 50"> + <el-select v-model="formData.scripts" multiple clearable style="width: 100%"> + <el-option + v-for="dict in taskAssignScriptDictDatas" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-form> + <!-- 操作按钮 --> + <template #footer> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { handleTree, defaultProps } from '@/utils/tree' +import * as TaskAssignRuleApi from '@/api/bpm/taskAssignRule' +import * as RoleApi from '@/api/system/role' +import * as DeptApi from '@/api/system/dept' +import * as PostApi from '@/api/system/post' +import * as UserApi from '@/api/system/user' +import * as UserGroupApi from '@/api/bpm/userGroup' +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formData = ref({ + type: Number(undefined), + modelId: '', + options: [], + roleIds: [], + deptIds: [], + postIds: [], + userIds: [], + userGroupIds: [], + scripts: [] +}) +const formRules = reactive({ + type: [{ required: true, message: '规则类型不能为空', trigger: 'change' }], + roleIds: [{ required: true, message: '指定角色不能为空', trigger: 'change' }], + deptIds: [{ required: true, message: '指定部门不能为空', trigger: 'change' }], + postIds: [{ required: true, message: '指定岗位不能为空', trigger: 'change' }], + userIds: [{ required: true, message: '指定用户不能为空', trigger: 'change' }], + userGroupIds: [{ required: true, message: '指定用户组不能为空', trigger: 'change' }], + scripts: [{ required: true, message: '指定脚本不能为空', trigger: 'change' }] +}) +const formRef = ref() // 表单 Ref +const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表 +const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表 +const deptTreeOptions = ref() // 部门树 +const postOptions = ref<PostApi.PostVO[]>([]) // 岗位列表 +const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表 +const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表 +const taskAssignScriptDictDatas = getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_SCRIPT) + +/** 打开弹窗 */ +const open = async (modelId: string, row: TaskAssignRuleApi.TaskAssignVO) => { + // 1. 先重置表单 + resetForm() + // 2. 再设置表单 + formData.value = { + ...row, + modelId: modelId, + options: [], + roleIds: [], + deptIds: [], + postIds: [], + userIds: [], + userGroupIds: [], + scripts: [] + } + // 将 options 赋值到对应的 roleIds 等选项 + if (row.type === 10) { + formData.value.roleIds.push(...row.options) + } else if (row.type === 20 || row.type === 21) { + formData.value.deptIds.push(...row.options) + } else if (row.type === 22) { + formData.value.postIds.push(...row.options) + } else if (row.type === 30 || row.type === 31 || row.type === 32) { + formData.value.userIds.push(...row.options) + } else if (row.type === 40) { + formData.value.userGroupIds.push(...row.options) + } else if (row.type === 50) { + formData.value.scripts.push(...row.options) + } + // 打开弹窗 + modelVisible.value = true + + // 获得角色列表 + roleOptions.value = await RoleApi.getSimpleRoleList() + // 获得部门列表 + deptOptions.value = await DeptApi.getSimpleDeptList() + deptTreeOptions.value = handleTree(deptOptions.value, 'id') + // 获得岗位列表 + postOptions.value = await PostApi.getSimplePostList() + // 获得用户列表 + userOptions.value = await UserApi.getSimpleUserList() + // 获得用户组列表 + userGroupOptions.value = await UserGroupApi.getSimpleUserGroupList() +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + + // 构建表单 + const form = { + ...formData.value, + taskDefinitionName: undefined + } + // 将 roleIds 等选项赋值到 options 中 + if (form.type === 10) { + form.options = form.roleIds + } else if (form.type === 20 || form.type === 21) { + form.options = form.deptIds + } else if (form.type === 22) { + form.options = form.postIds + } else if (form.type === 30 || form.type === 31 || form.type === 32) { + form.options = form.userIds + } else if (form.type === 40) { + form.options = form.userGroupIds + } else if (form.type === 50) { + form.options = form.scripts + } + form.roleIds = undefined + form.deptIds = undefined + form.postIds = undefined + form.userIds = undefined + form.userGroupIds = undefined + form.scripts = undefined + + // 提交请求 + formLoading.value = true + try { + const data = form as unknown as TaskAssignRuleApi.TaskAssignVO + if (!data.id) { + await TaskAssignRuleApi.createTaskAssignRule(data) + message.success(t('common.createSuccess')) + } else { + await TaskAssignRuleApi.updateTaskAssignRule(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formRef.value?.resetFields() +} +</script> diff --git a/src/views/bpm/taskAssignRule/index.vue b/src/views/bpm/taskAssignRule/index.vue index 8db7b578..feea80a2 100644 --- a/src/views/bpm/taskAssignRule/index.vue +++ b/src/views/bpm/taskAssignRule/index.vue @@ -1,186 +1,73 @@ <template> <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable" ref="xGrid"> - <template #options_default="{ row }"> - <span :key="option" v-for="option in row.options"> - <el-tag> - {{ getAssignRuleOptionName(row.type, option) }} + <el-table v-loading="loading" :data="list"> + <el-table-column label="任务名" align="center" prop="taskDefinitionName" /> + <el-table-column label="任务标识" align="center" prop="taskDefinitionKey" /> + <el-table-column label="规则类型" align="center" prop="type"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.BPM_TASK_ASSIGN_RULE_TYPE" :value="scope.row.type" /> + </template> + </el-table-column> + <el-table-column label="规则范围" align="center" prop="options"> + <template #default="scope"> + <el-tag class="mr-5px" :key="option" v-for="option in scope.row.options"> + {{ getAssignRuleOptionName(scope.row.type, option) }} </el-tag> - - </span> - </template> - <!-- 操作 --> - <template #actionbtns_default="{ row }" v-if="modelId"> - <!-- 操作:修改 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['bpm:task-assign-rule:update']" - @click="handleUpdate(row)" - /> - </template> - </XTable> - - <!-- 添加/修改弹窗 --> - <XModal v-model="dialogVisible" title="修改任务规则" width="800" height="35%"> - <el-form ref="formRef" :model="formData" :rules="rules" label-width="80px"> - <el-form-item label="任务名称" prop="taskDefinitionName"> - <el-input v-model="formData.taskDefinitionName" placeholder="请输入流标标识" disabled /> - </el-form-item> - <el-form-item label="任务标识" prop="taskDefinitionKey"> - <el-input v-model="formData.taskDefinitionKey" placeholder="请输入任务标识" disabled /> - </el-form-item> - <el-form-item label="规则类型" prop="type"> - <el-select v-model="formData.type" clearable style="width: 100%"> - <el-option - v-for="dict in getDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_RULE_TYPE)" - :key="parseInt(dict.value)" - :label="dict.label" - :value="parseInt(dict.value)" - /> - </el-select> - </el-form-item> - <el-form-item v-if="formData.type === 10" label="指定角色" prop="roleIds"> - <el-select v-model="formData.roleIds" multiple clearable style="width: 100%"> - <el-option - v-for="item in roleOptions" - :key="item.id" - :label="item.name" - :value="item.id" - /> - </el-select> - </el-form-item> - <el-form-item - label="指定部门" - prop="deptIds" - span="24" - v-if="formData.type === 20 || formData.type === 21" - > - <el-tree-select - ref="treeRef" - v-model="formData.deptIds" - node-key="id" - show-checkbox - :props="defaultProps" - :data="deptTreeOptions" - empty-text="加载中,请稍后" - multiple - /> - </el-form-item> - <el-form-item label="指定岗位" prop="postIds" span="24" v-if="formData.type === 22"> - <el-select v-model="formData.postIds" multiple clearable style="width: 100%"> - <el-option - v-for="item in postOptions" - :key="parseInt(item.id)" - :label="item.name" - :value="parseInt(item.id)" - /> - </el-select> - </el-form-item> - <el-form-item - label="指定用户" - prop="userIds" - span="24" - v-if="formData.type === 30 || formData.type === 31 || formData.type === 32" - > - <el-select v-model="formData.userIds" multiple clearable style="width: 100%"> - <el-option - v-for="item in userOptions" - :key="parseInt(item.id)" - :label="item.nickname" - :value="parseInt(item.id)" - /> - </el-select> - </el-form-item> - <el-form-item label="指定用户组" prop="userGroupIds" v-if="formData.type === 40"> - <el-select v-model="formData.userGroupIds" multiple clearable style="width: 100%"> - <el-option - v-for="item in userGroupOptions" - :key="parseInt(item.id)" - :label="item.name" - :value="parseInt(item.id)" - /> - </el-select> - </el-form-item> - <el-form-item label="指定脚本" prop="scripts" v-if="formData.type === 50"> - <el-select v-model="formData.scripts" multiple clearable style="width: 100%"> - <el-option - v-for="dict in taskAssignScriptDictDatas" - :key="parseInt(dict.value)" - :label="dict.label" - :value="parseInt(dict.value)" - /> - </el-select> - </el-form-item> - </el-form> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton - type="primary" - :title="t('action.save')" - :loading="actionLoading" - @click="submitForm" - /> - <!-- 按钮:关闭 --> - <XButton - :loading="actionLoading" - :title="t('dialog.close')" - @click="dialogVisible = false" - /> - </template> - </XModal> + </template> + </el-table-column> + <el-table-column v-if="queryParams.modelId" label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openForm(scope.row)" + v-hasPermi="['bpm:task-assign-rule:update']" + > + 修改 + </el-button> + </template> + </el-table-column> + </el-table> </ContentWrap> + <!-- 添加/修改弹窗 --> + <TaskAssignRuleForm ref="formRef" @success="getList" /> </template> <script setup lang="ts" name="TaskAssignRule"> -// 全局相关的 import -import { FormInstance } from 'element-plus' -// 业务相关的 import +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import * as TaskAssignRuleApi from '@/api/bpm/taskAssignRule' -import { listSimpleRolesApi } from '@/api/system/role' -import { getSimplePostList } from '@/api/system/post' -import { getSimpleUserList } from '@/api/system/user' -import { listSimpleUserGroup } from '@/api/bpm/userGroup' -import { listSimpleDeptApi } from '@/api/system/dept' -import { DICT_TYPE, getDictOptions } from '@/utils/dict' -import { handleTree, defaultProps } from '@/utils/tree' -import { allSchemas, rules, idShowActionClick } from './taskAssignRule.data' +import * as RoleApi from '@/api/system/role' +import * as DeptApi from '@/api/system/dept' +import * as PostApi from '@/api/system/post' +import * as UserApi from '@/api/system/user' +import * as UserGroupApi from '@/api/bpm/userGroup' +import TaskAssignRuleForm from './TaskAssignRuleForm.vue' +const { query } = useRoute() // 查询参数 -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 -const { query } = useRoute() -const xGrid = ref() - -// ========== 列表相关 ========== - -const roleOptions = ref() // 角色列表 -const deptOptions = ref() // 部门列表 -const deptTreeOptions = ref() -const postOptions = ref() // 岗位列表 -const userOptions = ref() // 用户列表 -const userGroupOptions = ref() // 用户组列表 -const taskAssignScriptDictDatas = getDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_SCRIPT) - -// 流程模型的编号。如果 modelId 非空,则用于流程模型的查看与配置 -const modelId = query.modelId -// 流程定义的编号。如果 processDefinitionId 非空,则用于流程定义的查看,不支持配置 -const processDefinitionId = query.processDefinitionId -let isShow = idShowActionClick(modelId) - -// 查询参数 +const loading = ref(true) // 列表的加载中 +const list = ref([]) // 列表的数据 const queryParams = reactive({ - modelId: modelId, - processDefinitionId: processDefinitionId -}) -const [registerTable, { reload }] = useXTable({ - allSchemas: allSchemas, - params: queryParams, - getListApi: TaskAssignRuleApi.getTaskAssignRuleList, - isList: true + modelId: query.modelId, // 流程模型的编号。如果 modelId 非空,则用于流程模型的查看与配置 + processDefinitionId: query.processDefinitionId // 流程定义的编号。如果 processDefinitionId 非空,则用于流程定义的查看,不支持配置 }) +const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表 +const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表 +const postOptions = ref<PostApi.PostVO[]>([]) // 岗位列表 +const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表 +const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表 +const taskAssignScriptDictDatas = getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_SCRIPT) -// 翻译规则范围 +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + list.value = await TaskAssignRuleApi.getTaskAssignRuleList(queryParams) + } finally { + loading.value = false + } +} + +/** 翻译规则范围 */ +// TODO 芋艿:各种 ts 报错 const getAssignRuleOptionName = (type, option) => { if (type === 10) { for (const roleOption of roleOptions.value) { @@ -223,136 +110,24 @@ const getAssignRuleOptionName = (type, option) => { return '未知(' + option + ')' } -// ========== 修改相关 ========== - -// 修改任务责任表单 -const actionLoading = ref(false) // 遮罩层 -const dialogVisible = ref(false) // 是否显示弹出层 -const formRef = ref<FormInstance>() -const formData = ref() // 表单数据 - -// 提交按钮 -const submitForm = async () => { - // 参数校验 - const elForm = unref(formRef) - if (!elForm) return - const valid = await elForm.validate() - if (!valid) return - // 构建表单 - let form = { - ...formData.value, - taskDefinitionName: undefined - } - // 将 roleIds 等选项赋值到 options 中 - if (form.type === 10) { - form.options = form.roleIds - } else if (form.type === 20 || form.type === 21) { - form.options = form.deptIds - } else if (form.type === 22) { - form.options = form.postIds - } else if (form.type === 30 || form.type === 31 || form.type === 32) { - form.options = form.userIds - } else if (form.type === 40) { - form.options = form.userGroupIds - } else if (form.type === 50) { - form.options = form.scripts - } - form.roleIds = undefined - form.deptIds = undefined - form.postIds = undefined - form.userIds = undefined - form.userGroupIds = undefined - form.scripts = undefined - // 设置提交中 - actionLoading.value = true - // 提交请求 - try { - const data = form as TaskAssignRuleApi.TaskAssignVO - // 新增 - if (!data.id) { - await TaskAssignRuleApi.createTaskAssignRule(data) - message.success(t('common.createSuccess')) - // 修改 - } else { - await TaskAssignRuleApi.updateTaskAssignRule(data) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false - } finally { - actionLoading.value = false - // 刷新列表 - await reload() - } +/** 添加/修改操作 */ +const formRef = ref() +const openForm = (row: TaskAssignRuleApi.TaskAssignVO) => { + formRef.value.open(queryParams.modelId, row) } -// 修改任务分配规则 -const handleUpdate = (row) => { - // 1. 先重置表单 - formData.value = {} - // 2. 再设置表单 - formData.value = { - ...row, - modelId: modelId, - options: [], - roleIds: [], - deptIds: [], - postIds: [], - userIds: [], - userGroupIds: [], - scripts: [] - } - // 将 options 赋值到对应的 roleIds 等选项 - if (row.type === 10) { - formData.value.roleIds.push(...row.options) - } else if (row.type === 20 || row.type === 21) { - formData.value.deptIds.push(...row.options) - } else if (row.type === 22) { - formData.value.postIds.push(...row.options) - } else if (row.type === 30 || row.type === 31 || row.type === 32) { - formData.value.userIds.push(...row.options) - } else if (row.type === 40) { - formData.value.userGroupIds.push(...row.options) - } else if (row.type === 50) { - formData.value.scripts.push(...row.options) - } - // 打开弹窗 - dialogVisible.value = true - actionLoading.value = false -} - -// ========== 初始化 ========== -onMounted(() => { +/** 初始化 */ +onMounted(async () => { + await getList() // 获得角色列表 - roleOptions.value = [] - listSimpleRolesApi().then((data) => { - roleOptions.value.push(...data) - }) + roleOptions.value = await RoleApi.getSimpleRoleList() // 获得部门列表 - deptOptions.value = [] - deptTreeOptions.value = [] - listSimpleDeptApi().then((data) => { - deptOptions.value.push(...data) - deptTreeOptions.value.push(...handleTree(data, 'id')) - }) + deptOptions.value = await DeptApi.getSimpleDeptList() // 获得岗位列表 - postOptions.value = [] - getSimplePostList().then((data) => { - postOptions.value.push(...data) - }) + postOptions.value = await PostApi.getSimplePostList() // 获得用户列表 - userOptions.value = [] - getSimpleUserList().then((data) => { - userOptions.value.push(...data) - }) + userOptions.value = await UserApi.getSimpleUserList() // 获得用户组列表 - userGroupOptions.value = [] - listSimpleUserGroup().then((data) => { - userGroupOptions.value.push(...data) - }) - if (!isShow) { - setTimeout(() => { - xGrid.value.Ref.hideColumn('actionbtns') - }, 100) - } + userGroupOptions.value = await UserGroupApi.getSimpleUserGroupList() }) </script> diff --git a/src/views/bpm/taskAssignRule/taskAssignRule.data.ts b/src/views/bpm/taskAssignRule/taskAssignRule.data.ts deleted file mode 100644 index cad74325..00000000 --- a/src/views/bpm/taskAssignRule/taskAssignRule.data.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' - -// 表单校验 -export const rules = reactive({ - type: [{ required: true, message: '规则类型不能为空', trigger: 'change' }], - roleIds: [{ required: true, message: '指定角色不能为空', trigger: 'change' }], - deptIds: [{ required: true, message: '指定部门不能为空', trigger: 'change' }], - postIds: [{ required: true, message: '指定岗位不能为空', trigger: 'change' }], - userIds: [{ required: true, message: '指定用户不能为空', trigger: 'change' }], - userGroupIds: [{ required: true, message: '指定用户组不能为空', trigger: 'change' }], - scripts: [{ required: true, message: '指定脚本不能为空', trigger: 'change' }] -}) - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: null, - action: true, - actionWidth: '200px', - columns: [ - { - title: '任务名', - field: 'taskDefinitionName' - }, - { - title: '任务标识', - field: 'taskDefinitionKey' - }, - { - title: '规则类型', - field: 'type', - dictType: DICT_TYPE.BPM_TASK_ASSIGN_RULE_TYPE, - dictClass: 'number' - }, - { - title: '规则范围', - field: 'options', - table: { - slots: { - default: 'options_default' - } - } - } - ] -}) - -export const idShowActionClick = (modelId?: any) => { - if (modelId) { - return true - } else { - return false - } -} -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) diff --git a/src/views/system/dept/DeptForm.vue b/src/views/system/dept/DeptForm.vue index 188ecb79..f2c3bc02 100644 --- a/src/views/system/dept/DeptForm.vue +++ b/src/views/system/dept/DeptForm.vue @@ -166,7 +166,7 @@ const resetForm = () => { /** 获得部门树 */ const getTree = async () => { deptTree.value = [] - const data = await DeptApi.listSimpleDeptApi() + const data = await DeptApi.getSimpleDeptList() let dept: Tree = { id: 0, name: '顶级部门', children: [] } dept.children = handleTree(data) deptTree.value.push(dept) diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue index da4b8389..d2661abb 100644 --- a/src/views/system/role/index.vue +++ b/src/views/system/role/index.vue @@ -165,7 +165,7 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { rules, allSchemas } from './role.data' import * as RoleApi from '@/api/system/role' import { listSimpleMenusApi } from '@/api/system/menu' -import { listSimpleDeptApi } from '@/api/system/dept' +import { getSimpleDeptList } from '@/api/system/dept' import * as PermissionApi from '@/api/system/permission' const { t } = useI18n() // 国际化 @@ -278,7 +278,7 @@ const handleScope = async (type: string, row: RoleApi.RoleVO) => { }) } } else if (type === 'data') { - const deptRes = await listSimpleDeptApi() + const deptRes = await getSimpleDeptList() treeOptions.value = handleTree(deptRes) const role = await RoleApi.getRoleApi(row.id) dataScopeForm.dataScope = role.dataScope diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index 9bb50930..542ae2f0 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -271,8 +271,8 @@ import { getAccessToken, getTenantId } from '@/utils/auth' import type { FormExpose } from '@/components/Form' import { rules, allSchemas } from './user.data' import * as UserApi from '@/api/system/user' -import { listSimpleDeptApi } from '@/api/system/dept' -import { listSimpleRolesApi } from '@/api/system/role' +import { getSimpleDeptList } from '@/api/system/dept' +import { getSimpleRoleList } from '@/api/system/role' import { getSimplePostList, PostVO } from '@/api/system/post' import { aassignUserRoleApi, @@ -301,7 +301,7 @@ const filterText = ref('') const deptOptions = ref<Tree[]>([]) // 树形结构 const treeRef = ref<InstanceType<typeof ElTree>>() const getTree = async () => { - const res = await listSimpleDeptApi() + const res = await getSimpleDeptList() deptOptions.value.push(...handleTree(res)) } const filterNode = (value: string, data: Tree) => { @@ -477,7 +477,7 @@ const handleRole = async (row: UserApi.UserVO) => { const roles = await listUserRolesApi(row.id) userRole.roleIds = roles // 获取角色列表 - const roleOpt = await listSimpleRolesApi() + const roleOpt = await getSimpleRoleList() roleOptions.value = roleOpt roleDialogVisible.value = true } From e0b57b805590939b750e2a250069fad596019fff Mon Sep 17 00:00:00 2001 From: yj441106 <yj441106@163.com> Date: Sun, 26 Mar 2023 20:21:31 +0800 Subject: [PATCH 109/184] =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=E9=87=8D?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/oauth2/client/form.vue | 3 +- src/views/system/oauth2/client/index.vue | 350 +++++++++++------------ 2 files changed, 162 insertions(+), 191 deletions(-) diff --git a/src/views/system/oauth2/client/form.vue b/src/views/system/oauth2/client/form.vue index 0822e59f..2800bd1f 100644 --- a/src/views/system/oauth2/client/form.vue +++ b/src/views/system/oauth2/client/form.vue @@ -17,7 +17,7 @@ <el-input v-model="formData.name" placeholder="请输入应用名" /> </el-form-item> <el-form-item label="应用图标"> - <UploadImg v-model="formData.logo" :limit="1" /> + <imageUpload v-model="formData.logo" :limit="1" /> </el-form-item> <el-form-item label="应用描述"> <el-input type="textarea" v-model="formData.description" placeholder="请输入应用名" /> @@ -147,7 +147,6 @@ <script setup lang="ts"> import { DICT_TYPE, getDictOptions } from '@/utils/dict' import * as ClientApi from '@/api/system/oauth2/client' -import UploadImg from '@/components/UploadFile' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 diff --git a/src/views/system/oauth2/client/index.vue b/src/views/system/oauth2/client/index.vue index 9ff44692..ff196c6f 100644 --- a/src/views/system/oauth2/client/index.vue +++ b/src/views/system/oauth2/client/index.vue @@ -1,207 +1,179 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <!-- 操作:新增 --> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['system:oauth2-client:create']" - @click="handleCreate()" - /> - </template> - <template #accessTokenValiditySeconds_default="{ row }"> - {{ row.accessTokenValiditySeconds + '秒' }} - </template> - <template #refreshTokenValiditySeconds_default="{ row }"> - {{ row.refreshTokenValiditySeconds + '秒' }} - </template> - <template #authorizedGrantTypes_default="{ row }"> - <el-tag - :disable-transitions="true" - :key="index" - v-for="(authorizedGrantType, index) in row.authorizedGrantTypes" - :index="index" - > - {{ authorizedGrantType }} - </el-tag> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:修改 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['system:oauth2-client:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['system:oauth2-client:query']" - @click="handleDetail(row.id)" - /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['system:oauth2-client:delete']" - @click="deleteData(row.id)" - /> - </template> - </XTable> - </ContentWrap> - <!-- 弹窗 --> - <XModal id="postModel" v-model="dialogVisible" :title="dialogTitle"> - <!-- 表单:添加/修改 --> - <Form - ref="formRef" - v-if="['create', 'update'].includes(actionType)" - :schema="allSchemas.formSchema" - :rules="rules" - /> - <!-- 表单:详情 --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" + <!-- 搜索 --> + <content-wrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" > - <template #accessTokenValiditySeconds="{ row }"> - {{ row.accessTokenValiditySeconds + '秒' }} - </template> - <template #refreshTokenValiditySeconds="{ row }"> - {{ row.refreshTokenValiditySeconds + '秒' }} - </template> - <template #authorizedGrantTypes="{ row }"> - <el-tag - :disable-transitions="true" - :key="index" - v-for="(authorizedGrantType, index) in row.authorizedGrantTypes" - :index="index" - > - {{ authorizedGrantType }} - </el-tag> - </template> - <template #scopes="{ row }"> - <el-tag - :disable-transitions="true" - :key="index" - v-for="(scopes, index) in row.scopes" - :index="index" - > - {{ scopes }} - </el-tag> - </template> - <template #autoApproveScopes="{ row }"> - <el-tag - :disable-transitions="true" - :key="index" - v-for="(autoApproveScopes, index) in row.autoApproveScopes" - :index="index" - > - {{ autoApproveScopes }} - </el-tag> - </template> - <template #redirectUris="{ row }"> - <el-tag - :disable-transitions="true" - :key="index" - v-for="(redirectUris, index) in row.redirectUris" - :index="index" - > - {{ redirectUris }} - </el-tag> - </template> - </Descriptions> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="actionLoading" - @click="submitForm()" + <el-form-item label="应用名" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入应用名" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small"> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button type="primary" @click="openModal('create')" v-hasPermi="['infra:config:create']"> + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> + </el-form-item> + </el-form> + </content-wrap> + + <!-- 列表 --> + <content-wrap> + <el-table v-loading="loading" :data="list"> + <el-table-column label="客户端编号" align="center" prop="clientId" /> + <el-table-column label="客户端密钥" align="center" prop="secret" /> + <el-table-column label="应用名" align="center" prop="name" /> + <el-table-column label="应用图标" align="center" prop="logo"> + <template #default="scope"> + <img width="40px" height="40px" :src="scope.row.logo" /> + </template> + </el-table-column> + <el-table-column label="状态" align="center" prop="status"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> + </template> + </el-table-column> + <el-table-column label="访问令牌的有效期" align="center" prop="accessTokenValiditySeconds"> + <template #default="scope">{{ scope.row.accessTokenValiditySeconds }} 秒</template> + </el-table-column> + <el-table-column label="刷新令牌的有效期" align="center" prop="refreshTokenValiditySeconds"> + <template #default="scope">{{ scope.row.refreshTokenValiditySeconds }} 秒</template> + </el-table-column> + <el-table-column label="授权类型" align="center" prop="authorizedGrantTypes"> + <template #default="scope"> + <el-tag + :disable-transitions="true" + :key="index" + v-for="(authorizedGrantType, index) in scope.row.authorizedGrantTypes" + :index="index" + > + {{ authorizedGrantType }} + </el-tag> + </template> + </el-table-column> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" /> - <!-- 按钮:关闭 --> - <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openModal('update', scope.row.id)" + v-hasPermi="['infra:config:update']" + > + 编辑 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['infra:config:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + + <!-- 表单弹窗:添加/修改 --> + <ClientForm ref="modalRef" @success="getList" /> </template> -<script setup lang="ts" name="Client"> -import type { FormExpose } from '@/components/Form' -// 业务相关的 import +<script setup lang="ts"> +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' import * as ClientApi from '@/api/system/oauth2/client' -import { rules, allSchemas } from './client.data' - -const { t } = useI18n() // 国际化 +import ClientForm from './form.vue' const message = useMessage() // 消息弹窗 +const { t } = useI18n() // 国际化 -// 列表相关的变量 -const [registerTable, { reload, deleteData }] = useXTable({ - allSchemas: allSchemas, - getListApi: ClientApi.getOAuth2ClientPageApi, - deleteApi: ClientApi.deleteOAuth2ClientApi +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + name: null, + status: null }) -// 弹窗相关的变量 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const actionType = ref('') // 操作按钮的类型 -const actionLoading = ref(false) // 按钮 Loading -const formRef = ref<FormExpose>() // 表单 Ref -const detailData = ref() // 详情 Ref -// 设置标题 -const setDialogTile = (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true +const queryFormRef = ref() // 搜索的表单 + +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await ClientApi.getOAuth2ClientPageApi(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } } -// 新增操作 -const handleCreate = () => { - setDialogTile('create') +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() } -// 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - // 设置数据 - const res = await ClientApi.getOAuth2ClientApi(rowId) - unref(formRef)?.setValues(res) +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() } -// 详情操作 -const handleDetail = async (rowId: number) => { - setDialogTile('detail') - const res = await ClientApi.getOAuth2ClientApi(rowId) - detailData.value = res +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, id) } -// 提交新增/修改的表单 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - actionLoading.value = true - // 提交请求 - try { - const data = unref(formRef)?.formModel as ClientApi.OAuth2ClientVO - if (actionType.value === 'create') { - await ClientApi.createOAuth2ClientApi(data) - message.success(t('common.createSuccess')) - } else { - await ClientApi.updateOAuth2ClientApi(data) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false - } finally { - actionLoading.value = false - // 刷新列表 - await reload() - } - } - }) +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await ClientApi.deleteOAuth2ClientApi(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} } + +/** 初始化 **/ +onMounted(() => { + getList() +}) </script> From 9c869dee5f2e582bab8ed23287865fd183e6e69b Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 26 Mar 2023 21:16:16 +0800 Subject: [PATCH 110/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E6=95=8F=E6=84=9F=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/sensitiveWord/index.ts | 20 +-- src/types/auto-components.d.ts | 4 + src/views/system/oauth2/client/index.vue | 1 - src/views/system/oauth2/token/form.vue | 131 ------------------ .../{form.vue => SensitiveWordForm.vue} | 17 ++- ...testForm.vue => SensitiveWordTestForm.vue} | 18 ++- src/views/system/sensitiveWord/index.vue | 90 ++++++------ 7 files changed, 69 insertions(+), 212 deletions(-) delete mode 100644 src/views/system/oauth2/token/form.vue rename src/views/system/sensitiveWord/{form.vue => SensitiveWordForm.vue} (86%) rename src/views/system/sensitiveWord/{testForm.vue => SensitiveWordTestForm.vue} (82%) diff --git a/src/api/system/sensitiveWord/index.ts b/src/api/system/sensitiveWord/index.ts index 08078ba6..1116226f 100644 --- a/src/api/system/sensitiveWord/index.ts +++ b/src/api/system/sensitiveWord/index.ts @@ -10,27 +10,13 @@ export interface SensitiveWordVO { createTime: Date } -export interface SensitiveWordPageReqVO extends PageParam { - name?: string - tag?: string - status?: number - createTime?: Date[] -} - -export interface SensitiveWordExportReqVO { - name?: string - tag?: string - status?: number - createTime?: Date[] -} - export interface SensitiveWordTestReqVO { text: string tag: string[] } // 查询敏感词列表 -export const getSensitiveWordPage = (params: SensitiveWordPageReqVO) => { +export const getSensitiveWordPage = (params: PageParam) => { return request.get({ url: '/system/sensitive-word/page', params }) } @@ -55,12 +41,12 @@ export const deleteSensitiveWord = (id: number) => { } // 导出敏感词 -export const exportSensitiveWord = (params: SensitiveWordExportReqVO) => { +export const exportSensitiveWord = (params) => { return request.download({ url: '/system/sensitive-word/export-excel', params }) } // 获取所有敏感词的标签数组 -export const getSensitiveWordTags = () => { +export const getSensitiveWordTagList = () => { return request.get({ url: '/system/sensitive-word/get-tags' }) } diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index 374893bb..4edfc6e7 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -54,11 +54,13 @@ declare module '@vue/runtime-core' { ElIcon: typeof import('element-plus/es')['ElIcon'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] + ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElLink: typeof import('element-plus/es')['ElLink'] ElOption: typeof import('element-plus/es')['ElOption'] ElPagination: typeof import('element-plus/es')['ElPagination'] ElPopover: typeof import('element-plus/es')['ElPopover'] ElRadio: typeof import('element-plus/es')['ElRadio'] + ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRow: typeof import('element-plus/es')['ElRow'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] @@ -72,6 +74,8 @@ declare module '@vue/runtime-core' { ElTag: typeof import('element-plus/es')['ElTag'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTransfer: typeof import('element-plus/es')['ElTransfer'] + ElTree: typeof import('element-plus/es')['ElTree'] + ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElUpload: typeof import('element-plus/es')['ElUpload'] Error: typeof import('./../components/Error/src/Error.vue')['default'] FlowCondition: typeof import('./../components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue')['default'] diff --git a/src/views/system/oauth2/client/index.vue b/src/views/system/oauth2/client/index.vue index 9ff44692..c88af726 100644 --- a/src/views/system/oauth2/client/index.vue +++ b/src/views/system/oauth2/client/index.vue @@ -133,7 +133,6 @@ import type { FormExpose } from '@/components/Form' // 业务相关的 import import * as ClientApi from '@/api/system/oauth2/client' -import { rules, allSchemas } from './client.data' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 diff --git a/src/views/system/oauth2/token/form.vue b/src/views/system/oauth2/token/form.vue deleted file mode 100644 index 5372ca7e..00000000 --- a/src/views/system/oauth2/token/form.vue +++ /dev/null @@ -1,131 +0,0 @@ -<template> - <Dialog :title="modelTitle" v-model="modelVisible"> - <el-form - ref="formRef" - :model="formData" - :rules="formRules" - label-width="80px" - v-loading="formLoading" - > - <el-form-item label="参数分类" prop="category"> - <el-input v-model="formData.category" placeholder="请输入参数分类" /> - </el-form-item> - <el-form-item label="参数名称" prop="name"> - <el-input v-model="formData.name" placeholder="请输入参数名称" /> - </el-form-item> - <el-form-item label="参数键名" prop="key"> - <el-input v-model="formData.key" placeholder="请输入参数键名" /> - </el-form-item> - <el-form-item label="参数键值" prop="value"> - <el-input v-model="formData.value" placeholder="请输入参数键值" /> - </el-form-item> - <el-form-item label="是否可见" prop="visible"> - <el-radio-group v-model="formData.visible"> - <el-radio - v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" - :key="dict.value" - :label="dict.value" - > - {{ dict.label }} - </el-radio> - </el-radio-group> - </el-form-item> - <el-form-item label="备注" prop="remark"> - <el-input v-model="formData.remark" type="textarea" placeholder="请输入内容" /> - </el-form-item> - </el-form> - <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> - </template> - </Dialog> -</template> -<script setup lang="ts"> -import { DICT_TYPE, getDictOptions } from '@/utils/dict' -import * as ConfigApi from '@/api/infra/config' - -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 - -const modelVisible = ref(false) // 弹窗的是否展示 -const modelTitle = ref('') // 弹窗的标题 -const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 -const formType = ref('') // 表单的类型:create - 新增;update - 修改 -const formData = ref({ - id: undefined, - category: '', - name: '', - key: '', - value: '', - visible: true, - remark: '' -}) -const formRules = reactive({ - category: [{ required: true, message: '参数分类不能为空', trigger: 'blur' }], - name: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }], - key: [{ required: true, message: '参数键名不能为空', trigger: 'blur' }], - value: [{ required: true, message: '参数键值不能为空', trigger: 'blur' }], - visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }] -}) -const formRef = ref() // 表单 Ref - -/** 打开弹窗 */ -const openModal = async (type: string, id?: number) => { - modelVisible.value = true - modelTitle.value = t('action.' + type) - formType.value = type - resetForm() - // 修改时,设置数据 - if (id) { - formLoading.value = true - try { - formData.value = await ConfigApi.getConfig(id) - } finally { - formLoading.value = false - } - } -} -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 - -/** 提交表单 */ -const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 -const submitForm = async () => { - // 校验表单 - if (!formRef) return - const valid = await formRef.value.validate() - if (!valid) return - // 提交请求 - formLoading.value = true - try { - const data = formData.value as ConfigApi.ConfigVO - if (formType.value === 'create') { - await ConfigApi.createConfig(data) - message.success(t('common.createSuccess')) - } else { - await ConfigApi.updateConfig(data) - message.success(t('common.updateSuccess')) - } - modelVisible.value = false - // 发送操作成功的事件 - emit('success') - } finally { - formLoading.value = false - } -} - -/** 重置表单 */ -const resetForm = () => { - formData.value = { - id: undefined, - category: '', - name: '', - key: '', - value: '', - visible: true, - remark: '' - } - formRef.value?.resetFields() -} -</script> diff --git a/src/views/system/sensitiveWord/form.vue b/src/views/system/sensitiveWord/SensitiveWordForm.vue similarity index 86% rename from src/views/system/sensitiveWord/form.vue rename to src/views/system/sensitiveWord/SensitiveWordForm.vue index ce5de578..c069756b 100644 --- a/src/views/system/sensitiveWord/form.vue +++ b/src/views/system/sensitiveWord/SensitiveWordForm.vue @@ -33,7 +33,7 @@ placeholder="请选择文章标签" style="width: 380px" > - <el-option v-for="tag in tags" :key="tag" :label="tag" :value="tag" /> + <el-option v-for="tag in tagList" :key="tag" :label="tag" :value="tag" /> </el-select> </el-form-item> </el-form> @@ -47,7 +47,6 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import * as SensitiveWordApi from '@/api/system/sensitiveWord' import { CommonStatusEnum } from '@/utils/constants' - const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -67,11 +66,10 @@ const formRules = reactive({ tags: [{ required: true, message: '标签不能为空', trigger: 'blur' }] }) const formRef = ref() // 表单 Ref -const tags: Ref<string[]> = ref([]) // todo @blue-syd:在 openModal 里加载下 +const tagList = ref([]) // 标签数组 /** 打开弹窗 */ -const openModal = async (type: string, paramTags: string[], id?: number) => { - tags.value = paramTags +const open = async (type: string, id?: number) => { modelVisible.value = true modelTitle.value = t('action.' + type) formType.value = type @@ -81,13 +79,14 @@ const openModal = async (type: string, paramTags: string[], id?: number) => { formLoading.value = true try { formData.value = await SensitiveWordApi.getSensitiveWord(id) - console.log(formData.value) } finally { formLoading.value = false } } + // 获得 Tag 标签列表 + tagList.value = await SensitiveWordApi.getSensitiveWordTagList() } -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 /** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 @@ -101,10 +100,10 @@ const submitForm = async () => { try { const data = formData.value as unknown as SensitiveWordApi.SensitiveWordVO if (formType.value === 'create') { - await SensitiveWordApi.createSensitiveWord(data) // TODO @blue-syd:去掉 API 后缀 + await SensitiveWordApi.createSensitiveWord(data) message.success(t('common.createSuccess')) } else { - await SensitiveWordApi.updateSensitiveWord(data) // TODO @blue-syd:去掉 API 后缀 + await SensitiveWordApi.updateSensitiveWord(data) message.success(t('common.updateSuccess')) } modelVisible.value = false diff --git a/src/views/system/sensitiveWord/testForm.vue b/src/views/system/sensitiveWord/SensitiveWordTestForm.vue similarity index 82% rename from src/views/system/sensitiveWord/testForm.vue rename to src/views/system/sensitiveWord/SensitiveWordTestForm.vue index 766d771f..881309c8 100644 --- a/src/views/system/sensitiveWord/testForm.vue +++ b/src/views/system/sensitiveWord/SensitiveWordTestForm.vue @@ -1,6 +1,5 @@ <template> - <!-- 对话框(测试敏感词) --> - <Dialog :title="modelTitle" v-model="modelVisible"> + <Dialog title="检测敏感词" v-model="modelVisible"> <el-form ref="formRef" :model="formData" @@ -17,10 +16,10 @@ multiple filterable allow-create - placeholder="请选择文章标签" + placeholder="请选择标签" style="width: 380px" > - <el-option v-for="tag in tags" :key="tag" :label="tag" :value="tag" /> + <el-option v-for="tag in tagList" :key="tag" :label="tag" :value="tag" /> </el-select> </el-form-item> </el-form> @@ -34,13 +33,10 @@ </template> <script setup lang="ts"> import * as SensitiveWordApi from '@/api/system/sensitiveWord' - const message = useMessage() // 消息弹窗 const modelVisible = ref(false) // 弹窗的是否展示 -const modelTitle = ref('检测敏感词') // 弹窗的标题 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 -const tags: Ref<string[]> = ref([]) const formData = ref({ text: '', tags: [] @@ -50,14 +46,16 @@ const formRules = reactive({ tags: [{ required: true, message: '标签不能为空', trigger: 'blur' }] }) const formRef = ref() // 表单 Ref +const tagList = ref([]) // 标签数组 /** 打开弹窗 */ -const openModal = async (paramTags: string[]) => { - tags.value = paramTags +const open = async () => { modelVisible.value = true resetForm() + // 获得 Tag 标签列表 + tagList.value = await SensitiveWordApi.getSensitiveWordTagList() } -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 /** 提交表单 */ const submitForm = async () => { diff --git a/src/views/system/sensitiveWord/index.vue b/src/views/system/sensitiveWord/index.vue index 93ea3c71..cf1fdb82 100644 --- a/src/views/system/sensitiveWord/index.vue +++ b/src/views/system/sensitiveWord/index.vue @@ -1,13 +1,20 @@ <template> - <!-- 搜索 --> - <content-wrap> - <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true"> + <!-- 搜索工作栏 --> + <ContentWrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > <el-form-item label="敏感词" prop="name"> <el-input v-model="queryParams.name" placeholder="请输入敏感词" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="标签" prop="tag"> @@ -16,17 +23,19 @@ placeholder="请选择标签" clearable @keyup.enter="handleQuery" + class="!w-240px" > - <el-option v-for="tag in tags" :key="tag" :label="tag" :value="tag" /> + <el-option v-for="tag in tagList" :key="tag" :label="tag" :value="tag" /> </el-select> </el-form-item> <el-form-item label="状态" prop="status"> <el-select v-model="queryParams.status" placeholder="请选择启用状态" clearable> <el-option - v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" - :key="parseInt(dict.value)" + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" :label="dict.label" - :value="parseInt(dict.value)" + :value="dict.value" + class="!w-240px" /> </el-select> </el-form-item> @@ -38,6 +47,7 @@ start-placeholder="开始日期" end-placeholder="结束日期" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="!w-240px" /> </el-form-item> <el-form-item> @@ -46,13 +56,13 @@ <el-button type="primary" plain - @click="openModal('create')" + @click="openForm('create')" v-hasPermi="['system:sensitive-word:create']" > <Icon icon="ep:plus" class="mr-5px" /> 新增 </el-button> <el-button - type="warning" + type="success" plain @click="handleExport" :loading="exportLoading" @@ -60,15 +70,15 @@ > <Icon icon="ep:download" class="mr-5px" /> 导出 </el-button> - <el-button type="success" plain @click="handleTest"> + <el-button type="warning" plain @click="openTestForm"> <Icon icon="ep:document-checked" class="mr-5px" /> 测试 </el-button> </el-form-item> </el-form> - </content-wrap> + </ContentWrap> <!-- 列表 --> - <content-wrap> + <ContentWrap> <el-table v-loading="loading" :data="list"> <el-table-column label="编号" align="center" prop="id" /> <el-table-column label="敏感词" align="center" prop="name" /> @@ -81,15 +91,13 @@ <el-table-column label="标签" align="center" prop="tags"> <template #default="scope"> <el-tag - :disable-transitions="true" - :key="index" - v-for="(tag, index) in scope.row.tags" - :index="index" class="mr-5px" + v-for="tag in scope.row.tags" + :key="tag" + :disable-transitions="true" > {{ tag }} </el-tag> - </template> </el-table-column> <el-table-column @@ -104,7 +112,7 @@ <el-button link type="primary" - @click="openModal('update', scope.row.id)" + @click="openForm('update', scope.row.id)" v-hasPermi="['infra:config:update']" > 编辑 @@ -127,22 +135,21 @@ v-model:limit="queryParams.pageSize" @pagination="getList" /> - </content-wrap> + </ContentWrap> <!-- 表单弹窗:添加/修改 --> - <SensitiveWordForm ref="modalRef" @success="getList" /> + <SensitiveWordForm ref="formRef" @success="getList" /> <!-- 表单弹窗:测试敏感词 --> - <SensitiveWordTestForm ref="modalTestRef" /> + <SensitiveWordTestForm ref="testFormRef" /> </template> <script setup lang="ts" name="SensitiveWord"> -import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' import * as SensitiveWordApi from '@/api/system/sensitiveWord' -import SensitiveWordForm from './form.vue' // TODO @blue-syd:组件名不对 -import SensitiveWordTestForm from './testForm.vue' - +import SensitiveWordForm from './SensitiveWordForm.vue' +import SensitiveWordTestForm from './SensitiveWordTestForm.vue' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -159,13 +166,13 @@ const queryParams = reactive({ }) const queryFormRef = ref() // 搜索的表单 const exportLoading = ref(false) // 导出的加载中 -const tags = ref([]) +const tagList = ref([]) // 标签数组 /** 查询参数列表 */ const getList = async () => { loading.value = true try { - const data = await SensitiveWordApi.getSensitiveWordPage(queryParams) // TODO @blue-syd:去掉 API 后缀哈 + const data = await SensitiveWordApi.getSensitiveWordPage(queryParams) list.value = data.list total.value = data.total } finally { @@ -186,16 +193,15 @@ const resetQuery = () => { } /** 添加/修改操作 */ -const modalRef = ref() -const openModal = (type: string, id?: number) => { - modalRef.value.openModal(type, tags.value, id) +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) } -// TODO @blue-syd:还少一个【测试】按钮的功能,参见 http://dashboard.yudao.iocoder.cn/system/sensitive-word -/* 测试敏感词按钮操作 */ -const modalTestRef = ref() -const handleTest = () => { - modalTestRef.value.openModal(tags.value) +/** 测试敏感词按钮操作 */ +const testFormRef = ref() +const openTestForm = () => { + testFormRef.value.open() } /** 删除按钮操作 */ @@ -218,7 +224,7 @@ const handleExport = async () => { await message.exportConfirm() // 发起导出 exportLoading.value = true - const data = await SensitiveWordApi.exportSensitiveWord(queryParams) // TODO @blue-syd:去掉 API 后缀哈 + const data = await SensitiveWordApi.exportSensitiveWord(queryParams) download.excel(data, '敏感词.xls') } catch { } finally { @@ -226,14 +232,10 @@ const handleExport = async () => { } } -/** 获得 Tag 标签列表 */ -const getTags = async () => { - tags.value = await SensitiveWordApi.getSensitiveWordTags() // TODO @blue-syd:去掉 API 后缀哈 -} - /** 初始化 **/ -onMounted(() => { - getTags() - getList() +onMounted(async () => { + await getList() + // 获得 Tag 标签列表 + tagList.value = await SensitiveWordApi.getSensitiveWordTagList() }) </script> From 56e2f21d1a8f7eef87f94268d1335d723ccaaa20 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 26 Mar 2023 22:15:45 +0800 Subject: [PATCH 111/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/menu/index.ts | 15 +- .../codegen/components/BasicInfoForm.vue | 4 +- src/views/system/menu/MenuForm.vue | 253 +++++++++++++++ src/views/system/menu/form.vue | 297 ------------------ src/views/system/menu/index.vue | 89 +++--- src/views/system/role/index.vue | 4 +- src/views/system/tenantPackage/index.vue | 4 +- 7 files changed, 316 insertions(+), 350 deletions(-) create mode 100644 src/views/system/menu/MenuForm.vue delete mode 100644 src/views/system/menu/form.vue diff --git a/src/api/system/menu/index.ts b/src/api/system/menu/index.ts index 5913972b..13736215 100644 --- a/src/api/system/menu/index.ts +++ b/src/api/system/menu/index.ts @@ -18,18 +18,13 @@ export interface MenuVO { createTime: Date } -export interface MenuPageReqVO { - name?: string - status?: number -} - // 查询菜单(精简)列表 -export const listSimpleMenusApi = () => { +export const getSimpleMenusList = () => { return request.get({ url: '/system/menu/list-all-simple' }) } // 查询菜单列表 -export const getMenuListApi = (params: MenuPageReqVO) => { +export const getMenuList = (params) => { return request.get({ url: '/system/menu/list', params }) } @@ -39,16 +34,16 @@ export const getMenuApi = (id: number) => { } // 新增菜单 -export const createMenuApi = (data: MenuVO) => { +export const createMenu = (data: MenuVO) => { return request.post({ url: '/system/menu/create', data }) } // 修改菜单 -export const updateMenuApi = (data: MenuVO) => { +export const updateMenu = (data: MenuVO) => { return request.put({ url: '/system/menu/update', data }) } // 删除菜单 -export const deleteMenuApi = (id: number) => { +export const deleteMenu = (id: number) => { return request.delete({ url: '/system/menu/delete?id=' + id }) } diff --git a/src/views/infra/codegen/components/BasicInfoForm.vue b/src/views/infra/codegen/components/BasicInfoForm.vue index 2009553f..5ab820a2 100644 --- a/src/views/infra/codegen/components/BasicInfoForm.vue +++ b/src/views/infra/codegen/components/BasicInfoForm.vue @@ -6,7 +6,7 @@ import { useForm } from '@/hooks/web/useForm' import { FormSchema } from '@/types/form' import { CodegenTableVO } from '@/api/infra/codegen/types' import { getIntDictOptions } from '@/utils/dict' -import { listSimpleMenusApi } from '@/api/system/menu' +import { getSimpleMenusList } from '@/api/system/menu' import { handleTree, defaultProps } from '@/utils/tree' import { PropType } from 'vue' @@ -21,7 +21,7 @@ const templateTypeOptions = getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_T const sceneOptions = getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE) const menuOptions = ref<any>([]) // 树形结构 const getTree = async () => { - const res = await listSimpleMenusApi() + const res = await getSimpleMenusList() menuOptions.value = handleTree(res) } diff --git a/src/views/system/menu/MenuForm.vue b/src/views/system/menu/MenuForm.vue new file mode 100644 index 00000000..45bcedfb --- /dev/null +++ b/src/views/system/menu/MenuForm.vue @@ -0,0 +1,253 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="100px" + v-loading="formLoading" + > + <el-form-item label="上级菜单"> + <el-tree-select + node-key="id" + v-model="formData.parentId" + :props="defaultProps" + :data="menuTree" + :default-expanded-keys="[0]" + check-strictly + /> + </el-form-item> + <el-form-item label="菜单名称" prop="name"> + <el-input v-model="formData.name" placeholder="请输入菜单名称" clearable /> + </el-form-item> + <el-form-item label="菜单类型" prop="type"> + <el-radio-group v-model="formData.type"> + <el-radio-button + v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_MENU_TYPE)" + :key="dict.label" + :label="dict.value" + > + {{ dict.label }} + </el-radio-button> + </el-radio-group> + </el-form-item> + <el-form-item label="菜单图标" v-if="formData.type !== 3"> + <IconSelect v-model="formData.icon" clearable /> + </el-form-item> + <el-form-item label="路由地址" prop="path" v-if="formData.type !== 3"> + <template #label> + <Tooltip + titel="路由地址" + message="访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头" + /> + </template> + <el-input v-model="formData.path" placeholder="请输入路由地址" clearable /> + </el-form-item> + <el-form-item label="组件地址" prop="component" v-if="formData.type === 2"> + <el-input v-model="formData.component" placeholder="例如说:system/user/index" clearable /> + </el-form-item> + <el-form-item label="组件名字" prop="componentName" v-if="formData.type === 2"> + <el-input v-model="formData.componentName" placeholder="例如说:SystemUser" clearable /> + </el-form-item> + <el-form-item label="权限标识" prop="permission" v-if="formData.type !== 1"> + <template #label> + <Tooltip + titel="权限标识" + message="Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission('system:user:list')`)" + /> + </template> + <el-input v-model="formData.permission" placeholder="请输入权限标识" clearable /> + </el-form-item> + <el-form-item label="显示排序" prop="sort"> + <el-input-number v-model="formData.sort" controls-position="right" :min="0" clearable /> + </el-form-item> + <el-form-item label="菜单状态" prop="status"> + <el-radio-group v-model="formData.status"> + <el-radio + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.label" + :label="dict.value" + > + {{ dict.label }} + </el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="显示状态" prop="visible" v-if="formData.type !== 3"> + <template #label> + <Tooltip titel="显示状态" message="选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问" /> + </template> + <el-radio-group v-model="formData.visible"> + <el-radio border key="true" :label="true">显示</el-radio> + <el-radio border key="false" :label="false">隐藏</el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="总是显示" prop="alwaysShow" v-if="formData.type !== 3"> + <template #label> + <Tooltip + titel="总是显示" + message="选择不是时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单" + /> + </template> + <el-radio-group v-model="formData.alwaysShow"> + <el-radio border key="true" :label="true">总是</el-radio> + <el-radio border key="false" :label="false">不是</el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="缓存状态" prop="keepAlive" v-if="formData.type === 2"> + <template #label> + <Tooltip + titel="缓存状态" + message="选择缓存时,则会被 `keep-alive` 缓存,必须填写「组件名称」字段" + /> + </template> + <el-radio-group v-model="formData.keepAlive"> + <el-radio border key="true" :label="true">缓存</el-radio> + <el-radio border key="false" :label="false">不缓存</el-radio> + </el-radio-group> + </el-form-item> + </el-form> + <template #footer> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import * as MenuApi from '@/api/system/menu' +import { CACHE_KEY, useCache } from '@/hooks/web/useCache' +import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants' +import { handleTree, defaultProps } from '@/utils/tree' +const { wsCache } = useCache() +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: 0, + name: '', + permission: '', + type: SystemMenuTypeEnum.DIR, + sort: Number(undefined), + parentId: 0, + path: '', + icon: '', + component: '', + componentName: '', + status: CommonStatusEnum.ENABLE, + visible: true, + keepAlive: true, + alwaysShow: true +}) +const formRules = reactive({ + name: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }], + sort: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }], + path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }], + status: [{ required: true, message: '状态不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const open = async (type: string, id?: number, parentId?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + if (parentId) { + formData.value.parentId = parentId + } + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await MenuApi.getMenuApi(id) + } finally { + formLoading.value = false + } + } + // 获得菜单列表 + await getTree() +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + if ( + formData.value.type === SystemMenuTypeEnum.DIR || + formData.value.type === SystemMenuTypeEnum.MENU + ) { + if (!isExternal(formData.value.path)) { + if (formData.value.parentId === 0 && formData.value.path.charAt(0) !== '/') { + message.error('路径必须以 / 开头') + return + } else if (formData.value.parentId !== 0 && formData.value.path.charAt(0) === '/') { + message.error('路径不能以 / 开头') + return + } + } + } + const data = formData.value as unknown as MenuApi.MenuVO + if (formType.value === 'create') { + await MenuApi.createMenu(data) + message.success(t('common.createSuccess')) + } else { + await MenuApi.updateMenu(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + // 清空,从而触发刷新 + wsCache.delete(CACHE_KEY.ROLE_ROUTERS) + } +} + +/** 获取下拉框[上级菜单]的数据 */ +const menuTree = ref<Tree[]>([]) // 树形结构 +const getTree = async () => { + menuTree.value = [] + const res = await MenuApi.getSimpleMenusList() + let menu: Tree = { id: 0, name: '主类目', children: [] } + menu.children = handleTree(res) + menuTree.value.push(menu) +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: 0, + name: '', + permission: '', + type: SystemMenuTypeEnum.DIR, + sort: Number(undefined), + parentId: 0, + path: '', + icon: '', + component: '', + componentName: '', + status: CommonStatusEnum.ENABLE, + visible: true, + keepAlive: true, + alwaysShow: true + } + formRef.value?.resetFields() +} + +/** 判断 path 是不是外部的 HTTP 等链接 */ +const isExternal = (path: string) => { + return /^(https?:|mailto:|tel:)/.test(path) +} +</script> diff --git a/src/views/system/menu/form.vue b/src/views/system/menu/form.vue deleted file mode 100644 index cf1583ec..00000000 --- a/src/views/system/menu/form.vue +++ /dev/null @@ -1,297 +0,0 @@ -<template> - <Dialog :title="modelTitle" v-model="modelVisible"> - <el-form - ref="formRef" - :model="formData" - :rules="formRules" - label-width="80px" - v-loading="formLoading" - > - <el-form-item label="上级菜单"> - <el-tree-select - node-key="id" - v-model="formData.parentId" - :props="defaultProps" - :data="menuOptions" - :default-expanded-keys="[0]" - check-strictly - /> - </el-form-item> - <el-col :span="16"> - <el-form-item label="菜单名称" prop="name"> - <el-input v-model="formData.name" placeholder="请输入菜单名称" clearable /> - </el-form-item> - </el-col> - <el-form-item label="菜单类型" prop="type"> - <el-radio-group v-model="formData.type"> - <el-radio-button - v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_MENU_TYPE)" - :key="dict.label" - :label="dict.value" - > - {{ dict.label }} - </el-radio-button> - </el-radio-group> - </el-form-item> - <template v-if="formData.type !== 3"> - <el-form-item label="菜单图标"> - <IconSelect v-model="formData.icon" clearable /> - </el-form-item> - <el-col :span="16"> - <el-form-item label="路由地址" prop="path"> - <template #label> - <Tooltip - titel="路由地址" - message="访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头" - /> - </template> - <el-input v-model="formData.path" placeholder="请输入路由地址" clearable /> - </el-form-item> - </el-col> - </template> - <template v-if="formData.type === 2"> - <el-col :span="16"> - <el-form-item label="组件地址" prop="component"> - <el-input - v-model="formData.component" - placeholder="例如说:system/user/index" - clearable - /> - </el-form-item> - </el-col> - <el-col :span="16"> - <el-form-item label="组件名字" prop="componentName"> - <el-input v-model="formData.componentName" placeholder="例如说:SystemUser" clearable /> - </el-form-item> - </el-col> - </template> - <template v-if="formData.type !== 1"> - <el-col :span="16"> - <el-form-item label="权限标识" prop="permission"> - <template #label> - <Tooltip - titel="权限标识" - message="Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission('system:user:list')`)" - /> - </template> - <el-input v-model="formData.permission" placeholder="请输入权限标识" clearable /> - </el-form-item> - </el-col> - </template> - <el-col :span="16"> - <el-form-item label="显示排序" prop="sort"> - <el-input-number v-model="formData.sort" controls-position="right" :min="0" clearable /> - </el-form-item> - </el-col> - <el-col :span="16"> - <el-form-item label="菜单状态" prop="status"> - <el-radio-group v-model="formData.status"> - <el-radio - border - v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" - :key="dict.label" - :label="dict.value" - > - {{ dict.label }} - </el-radio> - </el-radio-group> - </el-form-item> - </el-col> - <template v-if="formData.type !== 3"> - <el-col :span="16"> - <el-form-item label="显示状态" prop="visible"> - <template #label> - <Tooltip - titel="显示状态" - message="选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问" - /> - </template> - <el-radio-group v-model="formData.visible"> - <el-radio border key="true" :label="true">显示</el-radio> - <el-radio border key="false" :label="false">隐藏</el-radio> - </el-radio-group> - </el-form-item> - </el-col> - </template> - <template v-if="formData.type !== 3"> - <el-col :span="16"> - <el-form-item label="总是显示" prop="alwaysShow"> - <template #label> - <Tooltip - titel="总是显示" - message="选择不是时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单" - /> - </template> - <el-radio-group v-model="formData.alwaysShow"> - <el-radio border key="true" :label="true">总是</el-radio> - <el-radio border key="false" :label="false">不是</el-radio> - </el-radio-group> - </el-form-item> - </el-col> - </template> - <template v-if="formData.type === 2"> - <el-col :span="16"> - <el-form-item label="缓存状态" prop="keepAlive"> - <template #label> - <Tooltip - titel="缓存状态" - message="选择缓存时,则会被 `keep-alive` 缓存,必须填写「组件名称」字段" - /> - </template> - <el-radio-group v-model="formData.keepAlive"> - <el-radio border key="true" :label="true">缓存</el-radio> - <el-radio border key="false" :label="false">不缓存</el-radio> - </el-radio-group> - </el-form-item> - </el-col> - </template> - </el-form> - <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> - </template> - </Dialog> -</template> -<script setup lang="ts"> -import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' -import * as MenuApi from '@/api/system/menu' -import { CACHE_KEY, useCache } from '@/hooks/web/useCache' -import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants' -import { handleTree, defaultProps } from '@/utils/tree' -const { wsCache } = useCache() -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 - -const modelVisible = ref(false) // 弹窗的是否展示 -const modelTitle = ref('') // 弹窗的标题 -const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 -const formType = ref('') // 表单的类型:create - 新增;update - 修改 -const formData = ref({ - id: 0, - name: '', - permission: '', - type: SystemMenuTypeEnum.DIR, - sort: 1, - parentId: 0, - path: '', - icon: '', - component: '', - componentName: '', - status: CommonStatusEnum.ENABLE, - visible: true, - keepAlive: true, - alwaysShow: true, - createTime: new Date() -}) - -const formRules = reactive({ - name: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }], - sort: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }], - path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }], - status: [{ required: true, message: '状态不能为空', trigger: 'blur' }] -}) -const formRef = ref() // 表单 Ref - -/** 打开弹窗 */ -const openModal = async (type: string, id?: number) => { - modelVisible.value = true - modelTitle.value = t('action.' + type) - formType.value = type - resetForm() - await getTree() - // 修改时,设置数据 - if (id) { - formLoading.value = true - try { - formData.value = await MenuApi.getMenuApi(id) - // TODO 芋艿:这块要优化下,部分字段未重置,无法修改 - // formData.value.componentName = res.componentName || '' - // formData.value.alwaysShow = res.alwaysShow !== undefined ? res.alwaysShow : true - } finally { - formLoading.value = false - } - } -} -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 - -/** 提交表单 */ -const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 -const submitForm = async () => { - // 校验表单 - if (!formRef) return - const valid = await formRef.value.validate() - if (!valid) return - // 提交请求 - formLoading.value = true - try { - if ( - formData.value.type === SystemMenuTypeEnum.DIR || - formData.value.type === SystemMenuTypeEnum.MENU - ) { - if (!isExternal(formData.value.path)) { - if (formData.value.parentId === 0 && formData.value.path.charAt(0) !== '/') { - message.error('路径必须以 / 开头') - return - } else if (formData.value.parentId !== 0 && formData.value.path.charAt(0) === '/') { - message.error('路径不能以 / 开头') - return - } - } - } - const data = formData.value - if (formType.value === 'create') { - await MenuApi.createMenuApi(data) - message.success(t('common.createSuccess')) - } else { - await MenuApi.updateMenuApi(data) - message.success(t('common.updateSuccess')) - } - modelVisible.value = false - // 发送操作成功的事件 - emit('success') - } finally { - formLoading.value = false - wsCache.delete(CACHE_KEY.ROLE_ROUTERS) - } -} - -// ========== 下拉框[上级菜单] ========== -const menuOptions = ref<any[]>([]) // 树形结构 -// 获取下拉框[上级菜单]的数据 -const getTree = async () => { - menuOptions.value = [] - const res = await MenuApi.listSimpleMenusApi() - let menu: Tree = { id: 0, name: '主类目', children: [] } - menu.children = handleTree(res) - menuOptions.value.push(menu) -} - -/** 重置表单 */ -const resetForm = () => { - formData.value = { - id: 0, - name: '', - permission: '', - type: SystemMenuTypeEnum.DIR, - sort: 1, - parentId: 0, - path: '', - icon: '', - component: '', - componentName: '', - status: CommonStatusEnum.ENABLE, - visible: true, - keepAlive: true, - alwaysShow: true, - createTime: new Date() - } - formRef.value?.resetFields() -} - -// 判断 path 是不是外部的 HTTP 等链接 -const isExternal = (path: string) => { - return /^(https?:|mailto:|tel:)/.test(path) -} -</script> diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue index 41d1bd67..3baf3148 100644 --- a/src/views/system/menu/index.vue +++ b/src/views/system/menu/index.vue @@ -1,49 +1,63 @@ <template> + <!-- 搜索工作栏 --> <ContentWrap> - <!-- 搜索工作栏 --> - <el-form :model="queryParams" ref="queryFormRef" :inline="true"> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > <el-form-item label="菜单名称" prop="name"> <el-input v-model="queryParams.name" placeholder="请输入菜单名称" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="状态" prop="status"> - <el-select v-model="queryParams.status" placeholder="菜单状态" clearable> + <el-select + v-model="queryParams.status" + placeholder="请选择菜单状态" + clearable + class="!w-240px" + > <el-option - v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" - :key="parseInt(dict.value)" + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" :label="dict.label" - :value="parseInt(dict.value)" + :value="dict.value" /> </el-select> </el-form-item> <el-form-item> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> - <el-button type="primary" @click="openModal('create')" v-hasPermi="['system:menu:create']"> + <el-button + type="primary" + plain + @click="openForm('create')" + v-hasPermi="['system:menu:create']" + > <Icon icon="ep:plus" class="mr-5px" /> 新增 </el-button> + <el-button type="danger" plain @click="toggleExpandAll"> + <Icon icon="ep:sort" class="mr-5px" /> 展开/折叠 + </el-button> </el-form-item> </el-form> + </ContentWrap> - <el-row :gutter="10" class="mb8"> - <el-col :span="1.5"> - <el-button type="info" plain icon="el-icon-sort" @click="toggleExpandAll" - >展开/折叠</el-button - > - </el-col> - </el-row> - + <!-- 列表 --> + <ContentWrap> <el-table v-loading="loading" :data="list" - v-if="refreshTable" row-key="id" + v-if="refreshTable" :default-expand-all="isExpandAll" - :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" > <el-table-column prop="name" label="菜单名称" :show-overflow-tooltip="true" width="250" /> <el-table-column prop="icon" label="图标" align="center" width="100"> @@ -60,62 +74,63 @@ <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> </template> </el-table-column> - <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <el-table-column label="操作" align="center"> <template #default="scope"> <el-button link type="primary" - @click="openModal('update', scope.row.id)" + @click="openForm('update', scope.row.id)" v-hasPermi="['system:menu:update']" - >修改</el-button > + 修改 + </el-button> <el-button link type="primary" - @click="openModal('create', scope.row.id)" + @click="openForm('create', undefined, scope.row.id)" v-hasPermi="['system:menu:create']" - >新增</el-button > + 新增 + </el-button> <el-button link - type="primary" + type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['system:menu:delete']" - >删除</el-button > + 删除 + </el-button> </template> </el-table-column> </el-table> </ContentWrap> + <!-- 表单弹窗:添加/修改 --> - <menu-form ref="modalRef" @success="getList" /> + <MenuForm ref="formRef" @success="getList" /> </template> <script setup lang="ts" name="Menu"> -// 业务相关的 import -import { DICT_TYPE, getDictOptions } from '@/utils/dict' - +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { handleTree } from '@/utils/tree' import * as MenuApi from '@/api/system/menu' -import MenuForm from './form.vue' +import MenuForm from './MenuForm.vue' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 const loading = ref(true) // 列表的加载中 - const list = ref<any>([]) // 列表的数据 -const isExpandAll = ref(false) // 是否展开,默认全部折叠 -const refreshTable = ref(true) // 重新渲染表格状态 const queryParams = reactive({ name: undefined, status: undefined }) const queryFormRef = ref() // 搜索的表单 +const isExpandAll = ref(false) // 是否展开,默认全部折叠 +const refreshTable = ref(true) // 重新渲染表格状态 /** 查询参数列表 */ const getList = async () => { loading.value = true try { - const data = await MenuApi.getMenuListApi(queryParams) + const data = await MenuApi.getMenuList(queryParams) list.value = handleTree(data) } finally { loading.value = false @@ -134,9 +149,9 @@ const resetQuery = () => { } /** 添加/修改操作 */ -const modalRef = ref() -const openModal = async (type: string, id?: number) => { - modalRef.value.openModal(type, id) +const formRef = ref() +const openForm = (type: string, id?: number, parentId?: number) => { + formRef.value.open(type, id, parentId) } /** 展开/折叠操作 */ @@ -154,7 +169,7 @@ const handleDelete = async (id: number) => { // 删除的二次确认 await message.delConfirm() // 发起删除 - await MenuApi.deleteMenuApi(id) + await MenuApi.deleteMenu(id) message.success(t('common.delSuccess')) // 刷新列表 await getList() diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue index d2661abb..cf10e09e 100644 --- a/src/views/system/role/index.vue +++ b/src/views/system/role/index.vue @@ -164,7 +164,7 @@ import { SystemDataScopeEnum } from '@/utils/constants' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { rules, allSchemas } from './role.data' import * as RoleApi from '@/api/system/role' -import { listSimpleMenusApi } from '@/api/system/menu' +import { getSimpleMenusList } from '@/api/system/menu' import { getSimpleDeptList } from '@/api/system/dept' import * as PermissionApi from '@/api/system/permission' @@ -269,7 +269,7 @@ const handleScope = async (type: string, row: RoleApi.RoleVO) => { actionScopeType.value = type dialogScopeVisible.value = true if (type === 'menu') { - const menuRes = await listSimpleMenusApi() + const menuRes = await getSimpleMenusList() treeOptions.value = handleTree(menuRes) const role = await PermissionApi.listRoleMenusApi(row.id) if (role) { diff --git a/src/views/system/tenantPackage/index.vue b/src/views/system/tenantPackage/index.vue index c8f5756a..07ea39c6 100644 --- a/src/views/system/tenantPackage/index.vue +++ b/src/views/system/tenantPackage/index.vue @@ -71,7 +71,7 @@ import type { ElTree } from 'element-plus' // 业务相关的 import import { rules, allSchemas } from './tenantPackage.data' import * as TenantPackageApi from '@/api/system/tenantPackage' -import { listSimpleMenusApi } from '@/api/system/menu' +import { getSimpleMenusList } from '@/api/system/menu' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -102,7 +102,7 @@ const validateCategory = (rule: any, value: any, callback: any) => { rules.menuIds = [{ required: true, validator: validateCategory, trigger: 'blur' }] const getTree = async () => { - const res = await listSimpleMenusApi() + const res = await getSimpleMenusList() menuOptions.value = handleTree(res) } From 9ab791867ace0c1e42780faf70d3e90a4f3d8832 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 26 Mar 2023 22:47:53 +0800 Subject: [PATCH 112/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E5=85=AC=E4=BC=97=E5=8F=B7=E6=B6=88=E6=81=AF=EF=BC=8C?= =?UTF-8?q?=E5=85=88=E4=B8=8D=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/mp/message/index.ts | 2 +- src/types/auto-components.d.ts | 1 + src/views/mp/message/index.vue | 145 ++++++++++++++++----------------- 3 files changed, 73 insertions(+), 75 deletions(-) diff --git a/src/api/mp/message/index.ts b/src/api/mp/message/index.ts index 8b7d3cbd..ad9b95dd 100644 --- a/src/api/mp/message/index.ts +++ b/src/api/mp/message/index.ts @@ -1,7 +1,7 @@ import request from '@/config/axios' // 获得公众号消息分页 -export const getMessagePage = (query) => { +export const getMessagePage = (query: PageParam) => { return request.get({ url: '/mp/message/page', params: query diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index 4edfc6e7..04eb4d9e 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -52,6 +52,7 @@ declare module '@vue/runtime-core' { ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] + ElImage: typeof import('element-plus/es')['ElImage'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] diff --git a/src/views/mp/message/index.vue b/src/views/mp/message/index.vue index 34e64ebf..a95de2e4 100644 --- a/src/views/mp/message/index.vue +++ b/src/views/mp/message/index.vue @@ -1,20 +1,17 @@ <template> <ContentWrap> - <doc-alert title="公众号消息" url="https://doc.iocoder.cn/mp/message/" /> - <!-- 搜索工作栏 --> <el-form + class="-mb-15px" :model="queryParams" ref="queryFormRef" - size="small" :inline="true" - v-show="showSearch" label-width="68px" > <el-form-item label="公众号" prop="accountId"> - <el-select v-model="queryParams.accountId" placeholder="请选择公众号"> + <el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px"> <el-option - v-for="item in accounts" + v-for="item in accountList" :key="parseInt(item.id)" :label="item.name" :value="parseInt(item.id)" @@ -22,9 +19,9 @@ </el-select> </el-form-item> <el-form-item label="消息类型" prop="type"> - <el-select v-model="queryParams.type" placeholder="请选择消息类型" clearable size="small"> + <el-select v-model="queryParams.type" placeholder="请选择消息类型" class="!w-240px"> <el-option - v-for="dict in getDictOptions(DICT_TYPE.MP_MESSAGE_TYPE)" + v-for="dict in getStrDictOptions(DICT_TYPE.MP_MESSAGE_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value" @@ -37,6 +34,7 @@ placeholder="请输入用户标识" clearable :v-on="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="创建时间" prop="createTime"> @@ -49,20 +47,18 @@ start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" + class="!w-240px" /> </el-form-item> <el-form-item> - <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button> - <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> </el-form-item> </el-form> + </ContentWrap> - <!--todo 操作工具栏 --> - <!-- <el-row :gutter="10" class="mb8">--> - <!-- <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />--> - <!-- </el-row>--> - - <!-- 列表 --> + <!-- 列表 --> + <ContentWrap> <el-table v-loading="loading" :data="list"> <el-table-column label="发送时间" align="center" prop="createTime" width="180"> <template #default="scope"> @@ -81,37 +77,37 @@ <template #default="scope"> <!-- 【事件】区域 --> <div v-if="scope.row.type === 'event' && scope.row.event === 'subscribe'"> - <el-tag type="success" size="mini">关注</el-tag> + <el-tag type="success">关注</el-tag> </div> <div v-else-if="scope.row.type === 'event' && scope.row.event === 'unsubscribe'"> - <el-tag type="danger" size="mini">取消关注</el-tag> + <el-tag type="danger">取消关注</el-tag> </div> <div v-else-if="scope.row.type === 'event' && scope.row.event === 'CLICK'"> - <el-tag size="mini">点击菜单</el-tag>【{{ scope.row.eventKey }}】 + <el-tag>点击菜单</el-tag>【{{ scope.row.eventKey }}】 </div> <div v-else-if="scope.row.type === 'event' && scope.row.event === 'VIEW'"> - <el-tag size="mini">点击菜单链接</el-tag>【{{ scope.row.eventKey }}】 + <el-tag>点击菜单链接</el-tag>【{{ scope.row.eventKey }}】 </div> <div v-else-if="scope.row.type === 'event' && scope.row.event === 'scancode_waitmsg'"> - <el-tag size="mini">扫码结果</el-tag>【{{ scope.row.eventKey }}】 + <el-tag>扫码结果</el-tag>【{{ scope.row.eventKey }}】 </div> <div v-else-if="scope.row.type === 'event' && scope.row.event === 'scancode_push'"> - <el-tag size="mini">扫码结果</el-tag>【{{ scope.row.eventKey }}】 + <el-tag>扫码结果</el-tag>【{{ scope.row.eventKey }}】 </div> <div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_sysphoto'"> - <el-tag size="mini">系统拍照发图</el-tag> + <el-tag>系统拍照发图</el-tag> </div> <div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_photo_or_album'"> - <el-tag size="mini">拍照或者相册</el-tag> + <el-tag>拍照或者相册</el-tag> </div> <div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_weixin'"> - <el-tag size="mini">微信相册</el-tag> + <el-tag>微信相册</el-tag> </div> <div v-else-if="scope.row.type === 'event' && scope.row.event === 'location_select'"> - <el-tag size="mini">选择地理位置</el-tag> + <el-tag>选择地理位置</el-tag> </div> <div v-else-if="scope.row.type === 'event'"> - <el-tag type="danger" size="mini">未知事件类型</el-tag> + <el-tag type="danger">未知事件类型</el-tag> </div> <!-- 【消息】区域 --> <div v-else-if="scope.row.type === 'text'">{{ scope.row.content }}</div> @@ -127,7 +123,7 @@ <wx-video-player :url="scope.row.mediaUrl" style="margin-top: 10px" /> </div> <div v-else-if="scope.row.type === 'link'"> - <el-tag size="mini">链接</el-tag>: + <el-tag>链接</el-tag>: <a :href="scope.row.url" target="_blank">{{ scope.row.title }}</a> </div> <div v-else-if="scope.row.type === 'location'"> @@ -150,19 +146,19 @@ <wx-news :articles="scope.row.articles" /> </div> <div v-else> - <el-tag type="danger" size="mini">未知消息类型</el-tag> + <el-tag type="danger">未知消息类型</el-tag> </div> </template> </el-table-column> <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <template #default="scope"> <el-button - size="mini" - type="text" - icon="el-icon-edit" + link + type="primary" @click="handleSend(scope.row)" v-hasPermi="['mp:message:send']" - >消息 + > + 消息 </el-button> </template> </el-table-column> @@ -182,30 +178,22 @@ </el-dialog> </ContentWrap> </template> - <script setup lang="ts" name="MpMessage"> -import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' -import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' +import { DICT_TYPE, getStrDictOptions } from '@/utils/dict' +// import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' +// import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' // import WxMsg from '@/views/mp/components/wx-msg/main.vue' -import WxLocation from '@/views/mp/components/wx-location/main.vue' -import WxMusic from '@/views/mp/components/wx-music/main.vue' -import WxNews from '@/views/mp/components/wx-news/main.vue' -import { getMessagePage } from '@/api/mp/message' -import { getSimpleAccounts } from '@/api/mp/account' -import { DICT_TYPE, getDictOptions } from '@/utils/dict' +// import WxLocation from '@/views/mp/components/wx-location/main.vue' +// import WxMusic from '@/views/mp/components/wx-music/main.vue' +// import WxNews from '@/views/mp/components/wx-news/main.vue' import { parseTime } from '@/utils/formatTime' - -// ========== CRUD 相关 ========== -const loading = ref(false) // 遮罩层 -const showSearch = ref(true) // 显示搜索条件 -const total = ref(0) // 总条数 -const list = ref([]) // 粉丝消息列表 -const accounts = ref([]) // 公众号账号列表 -const open = ref(false) // 是否显示弹出层 -const userId = ref(0) // 操作的用户编号 +import * as MpAccountApi from '@/api/mp/account' +import * as MpMessageApi from '@/api/mp/message' const message = useMessage() // 消息弹窗 -const queryFormRef = ref() // 搜索的表单 +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 const queryParams = reactive({ pageNo: 1, pageSize: 10, @@ -213,34 +201,43 @@ const queryParams = reactive({ accountId: null, type: null, createTime: [] -}) // 是否显示弹出层 +}) +const queryFormRef = ref() // 搜索的表单 +// TODO 芋艿:下面应该移除 +const open = ref(false) // 是否显示弹出层 +const userId = ref(0) // 操作的用户编号 +const accountList = ref<MpAccountApi.AccountVO[]>([]) // 公众号账号列表 +/** 查询参数列表 */ const getList = async () => { // 如果没有选中公众号账号,则进行提示。 if (!queryParams.accountId) { - message.error('未选中公众号,无法查询消息') - return false + await message.error('未选中公众号,无法查询消息') + return } - - loading.value = true - // 执行查询 - getMessagePage(queryParams).then((data) => { - console.log(data) + try { + loading.value = true + const data = await MpMessageApi.getMessagePage(queryParams) list.value = data.list total.value = data.total + } finally { loading.value = false - }) + } } -const handleQuery = async () => { +/** 搜索按钮操作 */ +const handleQuery = () => { queryParams.pageNo = 1 getList() } + +/** 重置按钮操作 */ const resetQuery = async () => { queryFormRef.value.resetFields() // 默认选中第一个 - if (accounts.value.length > 0) { - queryParams.accountId = accounts[0].id + if (accountList.value.length > 0) { + // @ts-ignore + queryParams.accountId = accountList.value[0].id } handleQuery() } @@ -248,15 +245,15 @@ const handleSend = async (row) => { userId.value = row.userId open.value = true } -onMounted(() => { - getSimpleAccounts().then((response) => { - accounts.value = response - // 默认选中第一个 - if (accounts.value.length > 0) { - queryParams.accountId = accounts.value[0]['id'] - } - // 加载数据 - getList() - }) + +/** 初始化 **/ +onMounted(async () => { + accountList.value = await MpAccountApi.getSimpleAccountList() + // 选中第一个 + if (accountList.value.length > 0) { + // @ts-ignore + queryParams.accountId = accountList.value[0].id + } + await getList() }) </script> From 7380165bf07c9c310ee2dd9c531c2dccf8d2845d Mon Sep 17 00:00:00 2001 From: Chika <wbs_2018@sina.com> Date: Sun, 26 Mar 2023 22:57:18 +0800 Subject: [PATCH 113/184] =?UTF-8?q?=E8=A7=92=E8=89=B2=E7=AE=A1=E7=90=86ep?= =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/role/MenuPermissionForm.vue | 202 ++++++++ src/views/system/role/RoleForm.vue | 148 ++++++ src/views/system/role/index.vue | 504 ++++++++----------- src/views/system/role/role.data.ts | 82 --- 4 files changed, 561 insertions(+), 375 deletions(-) create mode 100644 src/views/system/role/MenuPermissionForm.vue create mode 100644 src/views/system/role/RoleForm.vue delete mode 100644 src/views/system/role/role.data.ts diff --git a/src/views/system/role/MenuPermissionForm.vue b/src/views/system/role/MenuPermissionForm.vue new file mode 100644 index 00000000..4628ef24 --- /dev/null +++ b/src/views/system/role/MenuPermissionForm.vue @@ -0,0 +1,202 @@ +<template> + <Dialog :title="dialogScopeTitle" v-model="dialogScopeVisible" width="800"> + <el-form + ref="menuPermissionFormRef" + :model="dataScopeForm" + :inline="true" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="角色名称"> + <el-tag>{{ dataScopeForm.name }}</el-tag> + </el-form-item> + <el-form-item label="角色标识"> + <el-tag>{{ dataScopeForm.code }}</el-tag> + </el-form-item> + <!-- 分配角色的数据权限对话框 --> + <el-form-item label="权限范围" v-if="actionScopeType === 'data'"> + <el-select v-model="dataScopeForm.dataScope"> + <el-option + v-for="item in dataScopeDictDatas" + :key="item.value" + :label="item.label" + :value="item.value" + /> + </el-select> + </el-form-item> + <!-- 分配角色的菜单权限对话框 --> + <el-row> + <el-col :span="24"> + <el-form-item + label="权限范围" + v-if=" + actionScopeType === 'menu' || + dataScopeForm.dataScope === SystemDataScopeEnum.DEPT_CUSTOM + " + style="display: flex" + > + <el-card class="card" shadow="never"> + <template #header> + 父子联动(选中父节点,自动选择子节点): + <el-switch + v-model="checkStrictly" + inline-prompt + active-text="是" + inactive-text="否" + /> + 全选/全不选: + <el-switch + v-model="treeNodeAll" + inline-prompt + active-text="是" + inactive-text="否" + @change="handleCheckedTreeNodeAll()" + /> + </template> + <el-tree + ref="treeRef" + node-key="id" + show-checkbox + :check-strictly="!checkStrictly" + :props="defaultProps" + :data="dataScopeForm" + empty-text="加载中,请稍后" + /> + </el-card> + </el-form-item> </el-col + ></el-row> + </el-form> + <!-- 操作按钮 --> + <template #footer> + <div class="dialog-footer"> + <el-button + :title="t('action.save')" + :loading="actionLoading" + @click="submitScope()" + type="primary" + :disabled="formLoading" + > + 保存 + </el-button> + <el-button + :loading="actionLoading" + :title="t('dialog.close')" + @click="dialogScopeVisible = false" + >取 消</el-button + > + </div> + </template> + </Dialog> +</template> + +<script setup lang="ts"> +import * as RoleApi from '@/api/system/role' +import type { ElTree } from 'element-plus' +import type { FormExpose } from '@/components/Form' +import { handleTree, defaultProps } from '@/utils/tree' +import { SystemDataScopeEnum } from '@/utils/constants' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { listSimpleMenusApi } from '@/api/system/menu' +import { listSimpleDeptApi } from '@/api/system/dept' +import * as PermissionApi from '@/api/system/permission' +// ========== CRUD 相关 ========== +const actionLoading = ref(false) // 遮罩层 +const menuPermissionFormRef = ref<FormExpose>() // 表单 Ref +const { t } = useI18n() // 国际化 +const dialogScopeTitle = ref('菜单权限') +const dataScopeDictDatas = ref() +const message = useMessage() // 消息弹窗 +const actionScopeType = ref('') +// 选项 +const checkStrictly = ref(true) +const treeNodeAll = ref(false) +const dialogScopeVisible = ref(false) // 弹窗的是否展示 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const treeOptions = ref<any[]>([]) // 菜单树形结构 +const treeRef = ref<InstanceType<typeof ElTree>>() +// ========== 数据权限 ========== +const dataScopeForm = reactive({ + id: 0, + name: '', + code: '', + dataScope: 0, + checkList: [] +}) + +/** 打开弹窗 */ +const openModal = async (type: string, row: RoleApi.RoleVO) => { + dataScopeForm.id = row.id + dataScopeForm.name = row.name + dataScopeForm.code = row.code + actionScopeType.value = type + dialogScopeVisible.value = true + if (type === 'menu') { + const menuRes = await listSimpleMenusApi() + treeOptions.value = handleTree(menuRes) + const role = await PermissionApi.listRoleMenusApi(row.id) + if (role) { + role?.forEach((item: any) => { + unref(treeRef)?.setChecked(item, true, false) + }) + } + } else if (type === 'data') { + const deptRes = await listSimpleDeptApi() + treeOptions.value = handleTree(deptRes) + const role = await RoleApi.getRole(row.id) + dataScopeForm.dataScope = role.dataScope + if (role.dataScopeDeptIds) { + role.dataScopeDeptIds?.forEach((item: any) => { + unref(treeRef)?.setChecked(item, true, false) + }) + } + } +} + +// 保存权限 +const submitScope = async () => { + if ('data' === actionScopeType.value) { + const data = ref<PermissionApi.PermissionAssignRoleDataScopeReqVO>({ + roleId: dataScopeForm.id, + dataScope: dataScopeForm.dataScope, + dataScopeDeptIds: + dataScopeForm.dataScope !== SystemDataScopeEnum.DEPT_CUSTOM + ? [] + : (treeRef.value!.getCheckedKeys(false) as unknown as Array<number>) + }) + await PermissionApi.assignRoleDataScopeApi(data.value) + } else if ('menu' === actionScopeType.value) { + const data = ref<PermissionApi.PermissionAssignRoleMenuReqVO>({ + roleId: dataScopeForm.id, + menuIds: [ + ...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>), + ...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>) + ] + }) + await PermissionApi.assignRoleMenuApi(data.value) + } + message.success(t('common.updateSuccess')) + dialogScopeVisible.value = false +} + +// 全选/全不选 +const handleCheckedTreeNodeAll = () => { + treeRef.value!.setCheckedNodes(treeNodeAll.value ? treeOptions.value : []) +} + +const init = () => { + dataScopeDictDatas.value = getIntDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE) +} + +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +// ========== 初始化 ========== +onMounted(() => { + init() +}) +</script> +<style scoped> +.card { + width: 100%; + max-height: 400px; + overflow-y: scroll; +} +</style> diff --git a/src/views/system/role/RoleForm.vue b/src/views/system/role/RoleForm.vue new file mode 100644 index 00000000..0fdb130b --- /dev/null +++ b/src/views/system/role/RoleForm.vue @@ -0,0 +1,148 @@ +<template> + <Dialog :title="dialogTitle" v-model="modelVisible" width="800"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="角色名称" prop="name"> + <el-input v-model="formData.name" placeholder="请输入角色名称" /> + </el-form-item> + <el-form-item label="角色类型" prop="type"> + <el-input :model-value="formData.type" placeholder="请输入角色类型" height="150px" /> + </el-form-item> + <el-form-item label="角色标识" prop="code"> + <el-input :model-value="formData.code" placeholder="请输入角色标识" height="150px" /> + </el-form-item> + <el-form-item label="显示顺序" prop="sort"> + <el-input :model-value="formData.sort" placeholder="请输入显示顺序" height="150px" /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select v-model="formData.status" placeholder="请选择状态" clearable> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="formData.remark" type="textarea" placeholder="请输备注" /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { getDictOptions } from '@/utils/dict' +import { CommonStatusEnum } from '@/utils/constants' +import type { FormExpose } from '@/components/Form' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import * as RoleApi from '@/api/system/role' +// ========== CRUD 相关 ========== +const dialogTitle = ref('edit') // 弹出层标题 +const formRef = ref<FormExpose>() // 表单 Ref +const { t } = useI18n() // 国际化 +const dataScopeDictDatas = ref() +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 + +const formData = ref({ + id: undefined, + name: '', + type: '', + code: '', + sort: undefined, + status: CommonStatusEnum.ENABLE, + remark: '' +}) +const formRules = reactive({ + name: [{ required: true, message: '岗位标题不能为空', trigger: 'blur' }], + code: [{ required: true, message: '岗位编码不能为空', trigger: 'change' }], + sort: [{ required: true, message: '岗位顺序不能为空', trigger: 'change' }], + status: [{ required: true, message: '岗位状态不能为空', trigger: 'change' }], + remark: [{ required: false, message: '岗位内容不能为空', trigger: 'blur' }] +}) + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await RoleApi.getRole(id) + } finally { + formLoading.value = false + } + } +} +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + name: '', + code: '', + sort: undefined, + status: CommonStatusEnum.ENABLE, + remark: '' + } + formRef.value?.resetFields() +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as RoleApi.RoleVO + if (formType.value === 'create') { + await RoleApi.createRole(data) + message.success(t('common.createSuccess')) + } else { + await RoleApi.updateRole(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +const init = () => { + dataScopeDictDatas.value = getIntDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE) +} +// ========== 初始化 ========== +onMounted(() => { + init() +}) +</script> +<style scoped> +.card { + width: 100%; + max-height: 400px; + overflow-y: scroll; +} +</style> diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue index cf10e09e..12ef7845 100644 --- a/src/views/system/role/index.vue +++ b/src/views/system/role/index.vue @@ -1,331 +1,249 @@ <template> <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <!-- 操作:新增 --> - <template #toolbar_buttons> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['system:role:create']" - @click="handleCreate()" + <!-- 搜索工作栏 --> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="角色名称" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入角色名称" + clearable + @keyup.enter="handleQuery" /> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:编辑 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['system:role:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['system:role:query']" - @click="handleDetail(row.id)" - /> - <!-- 操作:菜单权限 --> - <XTextButton - preIcon="ep:basketball" - title="菜单权限" - v-hasPermi="['system:permission:assign-role-menu']" - @click="handleScope('menu', row)" - /> - <!-- 操作:数据权限 --> - <XTextButton - preIcon="ep:coin" - title="数据权限" - v-hasPermi="['system:permission:assign-role-data-scope']" - @click="handleScope('data', row)" - /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['system:role:delete']" - @click="deleteData(row.id)" - /> - </template> - </XTable> - </ContentWrap> - - <XModal v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(添加 / 修改) --> - <Form - v-if="['create', 'update'].includes(actionType)" - :schema="allSchemas.formSchema" - :rules="rules" - ref="formRef" - /> - <!-- 对话框(详情) --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - /> - <!-- 操作按钮 --> - <template #footer> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="actionLoading" - @click="submitForm()" - /> - <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> - - <XModal v-model="dialogScopeVisible" :title="dialogScopeTitle"> - <el-form :model="dataScopeForm" label-width="140px" :inline="true"> - <el-form-item label="角色名称"> - <el-tag>{{ dataScopeForm.name }}</el-tag> </el-form-item> - <el-form-item label="角色标识"> - <el-tag>{{ dataScopeForm.code }}</el-tag> + <el-form-item label="角色标识" prop="code"> + <el-input + v-model="queryParams.code" + placeholder="请输入角色标识" + clearable + @keyup.enter="handleQuery" + /> </el-form-item> - <!-- 分配角色的数据权限对话框 --> - <el-form-item label="权限范围" v-if="actionScopeType === 'data'"> - <el-select v-model="dataScopeForm.dataScope"> + <el-form-item label="状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="请选择状态" clearable> <el-option - v-for="item in dataScopeDictDatas" - :key="item.value" - :label="item.label" - :value="item.value" + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="dict.label" + :value="dict.value" /> </el-select> </el-form-item> - <!-- 分配角色的菜单权限对话框 --> - <el-row> - <el-col :span="24"> - <el-form-item - label="权限范围" - v-if=" - actionScopeType === 'menu' || - dataScopeForm.dataScope === SystemDataScopeEnum.DEPT_CUSTOM - " - style="display: flex" - > - <el-card class="card" shadow="never"> - <template #header> - 父子联动(选中父节点,自动选择子节点): - <el-switch - v-model="checkStrictly" - inline-prompt - active-text="是" - inactive-text="否" - /> - 全选/全不选: - <el-switch - v-model="treeNodeAll" - inline-prompt - active-text="是" - inactive-text="否" - @change="handleCheckedTreeNodeAll()" - /> - </template> - <el-tree - ref="treeRef" - node-key="id" - show-checkbox - :check-strictly="!checkStrictly" - :props="defaultProps" - :data="treeOptions" - empty-text="加载中,请稍后" - /> - </el-card> - </el-form-item> </el-col - ></el-row> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + value-format="YYYY-MM-DD HH:mm:ss" + type="daterange" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button type="primary" @click="openModal('create')" v-hasPermi="['system:role:create']"> + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['system:role:export']" + > + <Icon icon="ep:download" class="mr-5px" /> 导出 + </el-button> + </el-form-item> </el-form> - <!-- 操作按钮 --> - <template #footer> - <XButton - type="primary" - :title="t('action.save')" - :loading="actionLoading" - @click="submitScope()" + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list" align="center"> + <el-table-column label="角色编号" align="center" prop="id" /> + <el-table-column label="角色名称" align="center" prop="name" /> + <el-table-column label="角色类型" align="center" prop="type" /> + <el-table-column label="角色标识" align="center" prop="code" /> + <el-table-column label="显示顺序" align="center" prop="sort" /> + <el-table-column label="备注" align="center" prop="remark" /> + <el-table-column label="状态" align="center" prop="status"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> + </template> + </el-table-column> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" /> - <XButton - :loading="actionLoading" - :title="t('dialog.close')" - @click="dialogScopeVisible = false" - /> - </template> - </XModal> + <el-table-column :width="300" label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openModal('update', scope.row.id)" + v-hasPermi="['system:role:update']" + > + 编辑 + </el-button> + <!-- 操作:菜单权限 --> + <el-button + link + type="primary" + preIcon="ep:basketball" + title="菜单权限" + v-hasPermi="['system:permission:assign-role-menu']" + @click="handleScope('menu', scope.row)" + > + 菜单权限 + </el-button> + <!-- 操作:数据权限 --> + <el-button + link + type="primary" + preIcon="ep:coin" + title="数据权限" + v-hasPermi="['system:permission:assign-role-data-scope']" + @click="handleScope('data', scope.row)" + > + 数据权限 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['system:role:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </ContentWrap> + + <!-- 表单弹窗:添加/修改 --> + <RoleForm ref="formRef" @success="getList" /> + <!-- 表单弹窗:菜单权限 --> + <MenuPermissionForm ref="menuPermissionFormRef" @success="getList" /> </template> -<script setup lang="ts" name="Role"> -import type { ElTree } from 'element-plus' -import type { FormExpose } from '@/components/Form' -import { handleTree, defaultProps } from '@/utils/tree' -import { SystemDataScopeEnum } from '@/utils/constants' -import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' -import { rules, allSchemas } from './role.data' +<script setup lang="tsx"> import * as RoleApi from '@/api/system/role' -import { getSimpleMenusList } from '@/api/system/menu' -import { getSimpleDeptList } from '@/api/system/dept' -import * as PermissionApi from '@/api/system/permission' +import RoleForm from './RoleForm.vue' +import MenuPermissionForm from './MenuPermissionForm.vue' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' +import download from '@/utils/download' -const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 -// 列表相关的变量 -const [registerTable, { reload, deleteData }] = useXTable({ - allSchemas: allSchemas, - getListApi: RoleApi.getRolePageApi, - deleteApi: RoleApi.deleteRoleApi -}) - -// ========== CRUD 相关 ========== -const actionLoading = ref(false) // 遮罩层 +const { t } = useI18n() // 国际化 +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const dialogTitle = ref('编辑') // 弹出层标题 const actionType = ref('') // 操作按钮的类型 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const formRef = ref<FormExpose>() // 表单 Ref -const detailData = ref() // 详情 Ref +const modelVisible = ref(false) // 是否显示弹出层 +const queryFormRef = ref() // 搜索的表单 +const exportLoading = ref(false) // 导出的加载中 + +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + code: '', + name: '', + status: undefined, + createTime: [] +}) // 设置标题 const setDialogTile = (type: string) => { dialogTitle.value = t('action.' + type) actionType.value = type - dialogVisible.value = true + modelVisible.value = true } -// 新增操作 -const handleCreate = () => { - setDialogTile('create') +/** 查询角色列表 */ +const getList = async () => { + loading.value = true + try { + const data = await RoleApi.getRolePage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } } -// 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - // 设置数据 - const res = await RoleApi.getRoleApi(rowId) - unref(formRef)?.setValues(res) +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() } -// 详情操作 -const handleDetail = async (rowId: number) => { - setDialogTile('detail') - // 设置数据 - const res = await RoleApi.getRoleApi(rowId) - detailData.value = res +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() } -// 提交按钮 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - actionLoading.value = true - // 提交请求 - try { - const data = unref(formRef)?.formModel as RoleApi.RoleVO - if (actionType.value === 'create') { - await RoleApi.createRoleApi(data) - message.success(t('common.createSuccess')) - } else { - await RoleApi.updateRoleApi(data) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false - } finally { - actionLoading.value = false - // 刷新列表 - await reload() - } - } - }) +/** 添加/修改操作 */ +const formRef = ref() +const openModal = (type: string, id?: number) => { + setDialogTile('编辑') + formRef.value.openModal(type, id) } -// ========== 数据权限 ========== -const dataScopeForm = reactive({ - id: 0, - name: '', - code: '', - dataScope: 0, - checkList: [] -}) -const treeOptions = ref<any[]>([]) // 菜单树形结构 -const treeRef = ref<InstanceType<typeof ElTree>>() -const dialogScopeVisible = ref(false) -const dialogScopeTitle = ref('数据权限') -const actionScopeType = ref('') -const dataScopeDictDatas = ref() -// 选项 -const checkStrictly = ref(true) -const treeNodeAll = ref(false) -// 全选/全不选 -const handleCheckedTreeNodeAll = () => { - treeRef.value!.setCheckedNodes(treeNodeAll.value ? treeOptions.value : []) +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await RoleApi.deleteRole(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} } + +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await RoleApi.exportPostApi(queryParams) + download.excel(data, '角色列表.xls') + } catch { + } finally { + exportLoading.value = false + } +} +/** 数据权限操作 */ +const menuPermissionFormRef = ref() + // 权限操作 const handleScope = async (type: string, row: RoleApi.RoleVO) => { - dataScopeForm.id = row.id - dataScopeForm.name = row.name - dataScopeForm.code = row.code - actionScopeType.value = type - dialogScopeVisible.value = true - if (type === 'menu') { - const menuRes = await getSimpleMenusList() - treeOptions.value = handleTree(menuRes) - const role = await PermissionApi.listRoleMenusApi(row.id) - if (role) { - role?.forEach((item: any) => { - unref(treeRef)?.setChecked(item, true, false) - }) - } - } else if (type === 'data') { - const deptRes = await getSimpleDeptList() - treeOptions.value = handleTree(deptRes) - const role = await RoleApi.getRoleApi(row.id) - dataScopeForm.dataScope = role.dataScope - if (role.dataScopeDeptIds) { - role.dataScopeDeptIds?.forEach((item: any) => { - unref(treeRef)?.setChecked(item, true, false) - }) - } - } + menuPermissionFormRef.value.openModal(type, row) } -// 保存权限 -const submitScope = async () => { - if ('data' === actionScopeType.value) { - const data = ref<PermissionApi.PermissionAssignRoleDataScopeReqVO>({ - roleId: dataScopeForm.id, - dataScope: dataScopeForm.dataScope, - dataScopeDeptIds: - dataScopeForm.dataScope !== SystemDataScopeEnum.DEPT_CUSTOM - ? [] - : (treeRef.value!.getCheckedKeys(false) as unknown as Array<number>) - }) - await PermissionApi.assignRoleDataScopeApi(data.value) - } else if ('menu' === actionScopeType.value) { - const data = ref<PermissionApi.PermissionAssignRoleMenuReqVO>({ - roleId: dataScopeForm.id, - menuIds: [ - ...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>), - ...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>) - ] - }) - await PermissionApi.assignRoleMenuApi(data.value) - } - message.success(t('common.updateSuccess')) - dialogScopeVisible.value = false -} -const init = () => { - dataScopeDictDatas.value = getIntDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE) -} -// ========== 初始化 ========== +/** 初始化 **/ onMounted(() => { - init() + getList() }) </script> -<style scoped> -.card { - width: 100%; - max-height: 400px; - overflow-y: scroll; -} -</style> diff --git a/src/views/system/role/role.data.ts b/src/views/system/role/role.data.ts deleted file mode 100644 index d55b5e21..00000000 --- a/src/views/system/role/role.data.ts +++ /dev/null @@ -1,82 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -// 国际化 -const { t } = useI18n() -// 表单校验 -export const rules = reactive({ - name: [required], - code: [required], - sort: [required] -}) -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - // primaryKey: 'id', - // primaryTitle: '角色编号', - // primaryType: 'seq', - action: true, - actionWidth: '400px', - columns: [ - { - title: '角色编号', - field: 'id', - table: { - width: 200 - } - }, - { - title: '角色名称', - field: 'name', - isSearch: true - }, - { - title: '角色类型', - field: 'type', - dictType: DICT_TYPE.SYSTEM_ROLE_TYPE, - dictClass: 'number', - isForm: false - }, - { - title: '角色标识', - field: 'code', - isSearch: true - }, - { - title: '显示顺序', - field: 'sort' - }, - { - title: t('form.remark'), - field: 'remark', - isTable: false, - form: { - component: 'Input', - componentProps: { - type: 'textarea', - rows: 4 - }, - colProps: { - span: 24 - } - } - }, - { - title: t('common.status'), - field: 'status', - dictType: DICT_TYPE.COMMON_STATUS, - dictClass: 'number', - isSearch: true - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From e00834adf79a92f150f045bf12f89f9c6cd880ad Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 26 Mar 2023 23:03:30 +0800 Subject: [PATCH 114/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E5=85=AC=E4=BC=97=E5=8F=B7=E6=B6=88=E6=81=AF=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20WxLocation=20=E7=9A=84=20icon=20=E6=8A=A5?= =?UTF-8?q?=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/mp/components/wx-location/main.vue | 7 +++---- src/views/mp/message/index.vue | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/views/mp/components/wx-location/main.vue b/src/views/mp/components/wx-location/main.vue index c0d67e29..47eab571 100644 --- a/src/views/mp/components/wx-location/main.vue +++ b/src/views/mp/components/wx-location/main.vue @@ -31,16 +31,15 @@ /> </el-row> <el-row> - <el-icon><Location /></el-icon>{{ label }} + <el-icon><Location /></el-icon> + <Icon icon="ep:location" /> + {{ label }} </el-row> </el-col> </el-link> </div> </template> - <script setup lang="ts" name="WxLocation"> -import { Location } from '@element-plus/icons-vue' - const props = defineProps({ locationX: { required: true, diff --git a/src/views/mp/message/index.vue b/src/views/mp/message/index.vue index a95de2e4..8cd9c773 100644 --- a/src/views/mp/message/index.vue +++ b/src/views/mp/message/index.vue @@ -183,7 +183,7 @@ import { DICT_TYPE, getStrDictOptions } from '@/utils/dict' // import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' // import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' // import WxMsg from '@/views/mp/components/wx-msg/main.vue' -// import WxLocation from '@/views/mp/components/wx-location/main.vue' +import WxLocation from '@/views/mp/components/wx-location/main.vue' // import WxMusic from '@/views/mp/components/wx-music/main.vue' // import WxNews from '@/views/mp/components/wx-news/main.vue' import { parseTime } from '@/utils/formatTime' From 708aef3e10c9b305742d3687cbefb4f09e20fb9f Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 26 Mar 2023 23:15:38 +0800 Subject: [PATCH 115/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E5=85=AC=E4=BC=97=E5=8F=B7=E6=B6=88=E6=81=AF=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20WxVoicePlayer=20=E7=9A=84=20icon=20?= =?UTF-8?q?=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + .../mp/components/wx-voice-play/main.vue | 29 +++++++++---------- src/views/mp/message/index.vue | 2 +- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index a33d0dc7..8f1d24e7 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@zxcvbn-ts/core": "^2.2.1", "animate.css": "^4.1.1", "axios": "^1.3.4", + "benz-amr-recorder": "^1.1.5", "bpmn-js-token-simulation": "^0.10.0", "camunda-bpmn-moddle": "^7.0.1", "cropperjs": "^1.5.13", diff --git a/src/views/mp/components/wx-voice-play/main.vue b/src/views/mp/components/wx-voice-play/main.vue index f98ac681..3260fc05 100644 --- a/src/views/mp/components/wx-voice-play/main.vue +++ b/src/views/mp/components/wx-voice-play/main.vue @@ -11,9 +11,9 @@ --> <template> <div class="wx-voice-div" @click="playVoice"> - <el-icon - ><VideoPlay v-if="playing !== true" /> - <VideoPause v-if="playing === true" /> + <el-icon> + <Icon v-if="playing !== true" icon="ep:video-play" /> + <Icon v-else icon="ep:video-pause" /> <span class="amr-duration" v-if="duration">{{ duration }} 秒</span> </el-icon> <div v-if="content"> @@ -25,19 +25,15 @@ <script setup lang="ts" name="WxVoicePlayer"> // 因为微信语音是 amr 格式,所以需要用到 amr 解码器:https://www.npmjs.com/package/benz-amr-recorder - import BenzAMRRecorder from 'benz-amr-recorder' -import { VideoPause, VideoPlay } from '@element-plus/icons-vue' const props = defineProps({ url: { - // 语音地址,例如说:https://www.iocoder.cn/xxx.amr - type: String, + type: String, // 语音地址,例如说:https://www.iocoder.cn/xxx.amr required: true }, content: { - // 语音文本 - type: String, + type: String, // 语音文本 required: false } }) @@ -46,16 +42,14 @@ const amr = ref() const playing = ref(false) const duration = ref() +/** 处理点击,播放或暂停 */ const playVoice = () => { // 情况一:未初始化,则创建 BenzAMRRecorder - debugger - console.log('进入' + amr.value) if (amr.value === undefined) { - console.log('开始初始化') amrInit() return } - + // 情况二:已经初始化,则根据情况播放或暂时 if (amr.value.isPlaying()) { amrStop() } else { @@ -63,10 +57,9 @@ const playVoice = () => { } } +/** 音频初始化 */ const amrInit = () => { amr.value = new BenzAMRRecorder() - console.log(amr.value) - console.log(props.url) // 设置播放 amr.value.initWithUrl(props.url).then(function () { amrPlay() @@ -77,16 +70,20 @@ const amrInit = () => { playing.value = false }) } + +/** 音频播放 */ const amrPlay = () => { playing.value = true amr.value.play() } + +/** 音频暂停 */ const amrStop = () => { playing.value = false amr.value.stop() } +// TODO 芋艿:下面样式有点问题 </script> - <style lang="scss" scoped> .wx-voice-div { padding: 5px; diff --git a/src/views/mp/message/index.vue b/src/views/mp/message/index.vue index 8cd9c773..de4a4242 100644 --- a/src/views/mp/message/index.vue +++ b/src/views/mp/message/index.vue @@ -181,7 +181,7 @@ <script setup lang="ts" name="MpMessage"> import { DICT_TYPE, getStrDictOptions } from '@/utils/dict' // import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' -// import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' +import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' // import WxMsg from '@/views/mp/components/wx-msg/main.vue' import WxLocation from '@/views/mp/components/wx-location/main.vue' // import WxMusic from '@/views/mp/components/wx-music/main.vue' From f5177337cbe8a948a0044f5c7abe0aa2185157a1 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 26 Mar 2023 23:31:41 +0800 Subject: [PATCH 116/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E5=9B=BE=E6=96=87=E5=8F=91=E8=A1=A8=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/mp/freePublish/index.vue | 85 ++++++++++++++---------------- src/views/mp/message/index.vue | 18 ++++--- 2 files changed, 51 insertions(+), 52 deletions(-) diff --git a/src/views/mp/freePublish/index.vue b/src/views/mp/freePublish/index.vue index 1d9b331e..6ef4a303 100644 --- a/src/views/mp/freePublish/index.vue +++ b/src/views/mp/freePublish/index.vue @@ -1,37 +1,36 @@ <template> + <!-- 搜索工作栏 --> <content-wrap> - <doc-alert title="公众号图文" url="https://doc.iocoder.cn/mp/article/" /> - - <!-- 搜索工作栏 --> <el-form + class="-mb-15px" :model="queryParams" ref="queryFormRef" - size="small" :inline="true" - v-show="showSearch" label-width="68px" > <el-form-item label="公众号" prop="accountId"> - <el-select v-model="queryParams.accountId" placeholder="请选择公众号"> + <el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px"> <el-option - v-for="item in accounts" - :key="parseInt(item.id)" + v-for="item in accountList" + :key="item.id" :label="item.name" - :value="parseInt(item.id)" + :value="item.id" /> </el-select> </el-form-item> <el-form-item> - <el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button> - <el-button :icon="Refresh" @click="resetQuery">重置</el-button> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> </el-form-item> </el-form> + </content-wrap> - <!-- 列表 --> + <!-- 列表 --> + <content-wrap> <div class="waterfall" v-loading="loading"> <div - v-show="item.content && item.content.newsItem" class="waterfall-item" + v-show="item.content && item.content.newsItem" v-for="item in list" :key="item.articleId" > @@ -40,11 +39,12 @@ <el-row justify="center" class="ope-row"> <el-button type="danger" - :icon="Delete" circle @click="handleDelete(item)" v-hasPermi="['mp:free-publish:delete']" - /> + > + <Icon icon="ep:delete" /> + </el-button> </el-row> </div> </div> @@ -61,25 +61,21 @@ <script setup lang="ts" name="freePublish"> import { getFreePublishPage, deleteFreePublish } from '@/api/mp/freePublish' -import { getSimpleAccounts } from '@/api/mp/account' +import * as MpAccountApi from '@/api/mp/account' import WxNews from '@/views/mp/components/wx-news/main.vue' -import { Delete, Search, Refresh } from '@element-plus/icons-vue' - const message = useMessage() // 消息弹窗 -const queryParams = reactive({ - total: 0, // 总页数 - currentPage: 1, // 当前页数 - pageNo: 1, // 当前页数 - accountId: undefined, // 当前页数 - queryParamsSize: 10 // 每页显示多少条 -}) -const loading = ref(false) // 列表的加载中 -const showSearch = ref(true) // 列表的加载中 +const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 const list = ref([]) // 列表的数据 -const accounts = ref([]) // 列表的数据 +const queryParams = reactive({ + currentPage: 1, // 当前页数 + pageNo: 1, // 当前页数 + accountId: undefined // 当前页数 +}) const queryFormRef = ref() // 搜索的表单 +const accountList = ref<MpAccountApi.AccountVO[]>([]) // 公众号账号列表 + /** 查询列表 */ const getList = async () => { // 如果没有选中公众号账号,则进行提示。 @@ -87,6 +83,7 @@ const getList = async () => { message.error('未选中公众号,无法查询已发表图文') return false } + // TODO 改成 await 形式 loading.value = true getFreePublishPage(queryParams) .then((data) => { @@ -106,18 +103,20 @@ const getList = async () => { loading.value = false }) } + /** 搜索按钮操作 */ -const handleQuery = async () => { +const handleQuery = () => { queryParams.pageNo = 1 getList() } /** 重置按钮操作 */ -const resetQuery = async () => { +const resetQuery = () => { queryFormRef.value.resetFields() // 默认选中第一个 - if (accounts.value.length > 0) { - queryParams.accountId = accounts[0].id + if (accountList.value.length > 0) { + // @ts-ignore + queryParams.accountId = accountList.value[0].id } handleQuery() } @@ -125,6 +124,7 @@ const resetQuery = async () => { /** 删除按钮操作 */ const handleDelete = async (item) => { { + // TODO 改成 await 形式 const articleId = item.articleId const accountId = queryParams.accountId message @@ -140,19 +140,16 @@ const handleDelete = async (item) => { } } -onMounted(() => { - getSimpleAccounts().then((response) => { - accounts.value = response - // 默认选中第一个 - if (accounts.value.length > 0) { - queryParams.accountId = accounts.value[0]['id'] - } - // 加载数据 - getList() - }) +onMounted(async () => { + accountList.value = await MpAccountApi.getSimpleAccountList() + // 选中第一个 + if (accountList.value.length > 0) { + // @ts-ignore + queryParams.accountId = accountList.value[0].id + } + await getList() }) </script> - <style lang="scss" scoped> .pagination { float: right; @@ -182,7 +179,7 @@ onMounted(() => { margin-left: 5px; } -/*新增图文*/ +/* 新增图文 */ .left { display: inline-block; width: 35%; diff --git a/src/views/mp/message/index.vue b/src/views/mp/message/index.vue index de4a4242..10145221 100644 --- a/src/views/mp/message/index.vue +++ b/src/views/mp/message/index.vue @@ -12,9 +12,9 @@ <el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px"> <el-option v-for="item in accountList" - :key="parseInt(item.id)" + :key="item.id" :label="item.name" - :value="parseInt(item.id)" + :value="item.id" /> </el-select> </el-form-item> @@ -60,11 +60,13 @@ <!-- 列表 --> <ContentWrap> <el-table v-loading="loading" :data="list"> - <el-table-column label="发送时间" align="center" prop="createTime" width="180"> - <template #default="scope"> - <span>{{ parseTime(scope.row.createTime) }}</span> - </template> - </el-table-column> + <el-table-column + label="发送时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" + /> <el-table-column label="消息类型" align="center" prop="type" width="80" /> <el-table-column label="发送方" align="center" prop="sendFrom" width="80"> <template #default="scope"> @@ -180,13 +182,13 @@ </template> <script setup lang="ts" name="MpMessage"> import { DICT_TYPE, getStrDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' // import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' // import WxMsg from '@/views/mp/components/wx-msg/main.vue' import WxLocation from '@/views/mp/components/wx-location/main.vue' // import WxMusic from '@/views/mp/components/wx-music/main.vue' // import WxNews from '@/views/mp/components/wx-news/main.vue' -import { parseTime } from '@/utils/formatTime' import * as MpAccountApi from '@/api/mp/account' import * as MpMessageApi from '@/api/mp/message' const message = useMessage() // 消息弹窗 From ed5990f22921e06abbe3e146f4566becdb9e663d Mon Sep 17 00:00:00 2001 From: wuxiran <wuxiran@outlook.com> Date: Mon, 27 Mar 2023 03:18:25 +0800 Subject: [PATCH 117/184] =?UTF-8?q?1=E3=80=81=E5=BE=AE=E4=BF=A1=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E6=9B=B4=E6=96=B0vue3.=E8=A6=96=E9=A0=BB=E7=B5=84?= =?UTF-8?q?=E4=BB=B6=E6=9B=B4=E6=96=B0=E4=BD=BF=E7=94=A8video.js=206.0.0?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E3=80=82=202=E3=80=81=E7=9B=AE=E5=89=8Dmp?= =?UTF-8?q?=E4=B8=AD=E8=A6=96=E9=A0=BB=E7=B5=84=E4=BB=B6=E5=8F=AF=E4=BB=A5?= =?UTF-8?q?=E7=B0=A1=E5=96=AE=E7=9A=84=E4=BD=BF=E7=94=A8=E4=BA=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 + src/views/mp/components/wx-reply/main.vue | 1246 ++++++++--------- .../mp/components/wx-video-play/main.vue | 142 +- .../mp/components/wx-voice-play/main.vue | 1 + src/views/mp/freePublish/index.vue | 21 +- src/views/mp/message/index.vue | 12 +- src/views/mp/tag/index.vue | 17 +- 7 files changed, 719 insertions(+), 722 deletions(-) diff --git a/package.json b/package.json index 8f1d24e7..683d09eb 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@form-create/designer": "^3.1.0", "@form-create/element-ui": "^3.1.17", "@iconify/iconify": "^3.1.0", + "@videojs-player/vue": "^1.0.0", "@vueuse/core": "^9.13.0", "@wangeditor/editor": "^5.1.23", "@wangeditor/editor-for-vue": "^5.1.10", @@ -58,6 +59,7 @@ "qs": "^6.11.1", "steady-xml": "^0.1.0", "url": "^0.11.0", + "video.js": "^8.0.4", "vue": "3.2.47", "vue-i18n": "9.2.2", "vue-router": "^4.1.6", diff --git a/src/views/mp/components/wx-reply/main.vue b/src/views/mp/components/wx-reply/main.vue index 57a3cd84..a151b252 100644 --- a/src/views/mp/components/wx-reply/main.vue +++ b/src/views/mp/components/wx-reply/main.vue @@ -1,634 +1,634 @@ -<!--<!–--> -<!-- - Copyright (C) 2018-2019--> -<!-- - All rights reserved, Designed By www.joolun.com--> -<!-- 芋道源码:--> -<!-- ① 移除多余的 rep 为前缀的变量,让 message 消息更简单--> -<!-- ② 代码优化,补充注释,提升阅读性--> -<!-- ③ 优化消息的临时缓存策略,发送消息时,只清理被发送消息的 tab,不会强制切回到 text 输入--> -<!-- ④ 支持发送【视频】消息时,支持新建视频--> -<!--–>--> -<!--<template>--> -<!-- <el-tabs type="border-card" v-model="objData.type" @tab-click="handleClick">--> -<!-- <!– 类型 1:文本 –>--> -<!-- <el-tab-pane name="text">--> -<!-- <span slot="label"><i class="el-icon-document"></i> 文本</span>--> -<!-- <el-input--> -<!-- type="textarea"--> -<!-- :rows="5"--> -<!-- placeholder="请输入内容"--> -<!-- v-model="objData.content"--> -<!-- @input="inputContent"--> -<!-- />--> -<!-- </el-tab-pane>--> -<!-- <!– 类型 2:图片 –>--> -<!-- <el-tab-pane name="image">--> -<!-- <span slot="label"><i class="el-icon-picture"></i> 图片</span>--> -<!-- <el-row>--> -<!-- <!– 情况一:已经选择好素材、或者上传好图片 –>--> -<!-- <div class="select-item" v-if="objData.url">--> -<!-- <img class="material-img" :src="objData.url" />--> -<!-- <p class="item-name" v-if="objData.name">{{ objData.name }}</p>--> -<!-- <el-row class="ope-row">--> -<!-- <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" />--> -<!-- </el-row>--> -<!-- </div>--> -<!-- <!– 情况二:未做完上述操作 –>--> -<!-- <div v-else>--> -<!-- <el-row style="text-align: center">--> -<!-- <!– 选择素材 –>--> -<!-- <el-col :span="12" class="col-select">--> -<!-- <el-button type="success" @click="openMaterial">--> -<!-- 素材库选择<i class="el-icon-circle-check el-icon--right"></i>--> -<!-- </el-button>--> -<!-- <el-dialog--> -<!-- title="选择图片"--> -<!-- v-model:visible="dialogImageVisible"--> -<!-- width="90%"--> -<!-- append-to-body--> -<!-- >--> -<!-- <wx-material-select :obj-data="objData" @selectMaterial="selectMaterial" />--> -<!-- </el-dialog>--> -<!-- </el-col>--> -<!-- <!– 文件上传 –>--> -<!-- <el-col :span="12" class="col-add">--> -<!-- <el-upload--> -<!-- :action="actionUrl"--> -<!-- :headers="headers"--> -<!-- multiple--> -<!-- :limit="1"--> -<!-- :file-list="fileList"--> -<!-- :data="uploadData"--> -<!-- :before-upload="beforeImageUpload"--> -<!-- :on-success="handleUploadSuccess"--> -<!-- >--> -<!-- <el-button type="primary">上传图片</el-button>--> -<!-- <div slot="tip" class="el-upload__tip"--> -<!-- >支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div--> -<!-- >--> -<!-- </el-upload>--> -<!-- </el-col>--> -<!-- </el-row>--> -<!-- </div>--> -<!-- </el-row>--> -<!-- </el-tab-pane>--> -<!-- <!– 类型 3:语音 –>--> -<!-- <el-tab-pane name="voice">--> -<!-- <span slot="label"><i class="el-icon-phone"></i> 语音</span>--> -<!-- <el-row>--> -<!-- <div class="select-item2" v-if="objData.url">--> -<!-- <p class="item-name">{{ objData.name }}</p>--> -<!-- <div class="item-infos">--> -<!-- <wx-voice-player :url="objData.url" />--> -<!-- </div>--> -<!-- <el-row class="ope-row">--> -<!-- <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" />--> -<!-- </el-row>--> -<!-- </div>--> -<!-- <div v-else>--> -<!-- <el-row style="text-align: center">--> -<!-- <!– 选择素材 –>--> -<!-- <el-col :span="12" class="col-select">--> -<!-- <el-button type="success" @click="openMaterial">--> -<!-- 素材库选择<i class="el-icon-circle-check el-icon--right"></i>--> -<!-- </el-button>--> -<!-- <el-dialog--> -<!-- title="选择语音"--> -<!-- v-model:visible="dialogVoiceVisible"--> -<!-- width="90%"--> -<!-- append-to-body--> -<!-- >--> -<!-- <WxMaterialSelect :objData="objData" @selectMaterial="selectMaterial" />--> -<!-- </el-dialog>--> -<!-- </el-col>--> -<!-- <!– 文件上传 –>--> -<!-- <el-col :span="12" class="col-add">--> -<!-- <el-upload--> -<!-- :action="actionUrl"--> -<!-- :headers="headers"--> -<!-- multiple--> -<!-- :limit="1"--> -<!-- :file-list="fileList"--> -<!-- :data="uploadData"--> -<!-- :before-upload="beforeVoiceUpload"--> -<!-- :on-success="handleUploadSuccess"--> -<!-- >--> -<!-- <el-button type="primary">点击上传</el-button>--> -<!-- <div slot="tip" class="el-upload__tip"--> -<!-- >格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s</div--> -<!-- >--> -<!-- </el-upload>--> -<!-- </el-col>--> -<!-- </el-row>--> -<!-- </div>--> -<!-- </el-row>--> -<!-- </el-tab-pane>--> -<!-- <!– 类型 4:视频 –>--> -<!-- <el-tab-pane name="video">--> -<!-- <span slot="label"><i class="el-icon-share"></i> 视频</span>--> -<!-- <el-row>--> -<!-- <el-input v-model="objData.title" placeholder="请输入标题" @input="inputContent" />--> -<!-- <div style="margin: 20px 0"></div>--> -<!-- <el-input v-model="objData.description" placeholder="请输入描述" @input="inputContent" />--> -<!-- <div style="margin: 20px 0"></div>--> -<!-- <div style="text-align: center">--> -<!-- <wx-video-player v-if="objData.url" :url="objData.url" />--> -<!-- </div>--> -<!-- <div style="margin: 20px 0"></div>--> -<!-- <el-row style="text-align: center">--> -<!-- <!– 选择素材 –>--> -<!-- <el-col :span="12">--> -<!-- <el-button type="success" @click="openMaterial">--> -<!-- 素材库选择<i class="el-icon-circle-check el-icon--right"></i>--> -<!-- </el-button>--> -<!-- <el-dialog--> -<!-- title="选择视频"--> -<!-- v-model:visible="dialogVideoVisible"--> -<!-- width="90%"--> -<!-- append-to-body--> -<!-- >--> -<!-- <wx-material-select :objData="objData" @selectMaterial="selectMaterial" />--> -<!-- </el-dialog>--> -<!-- </el-col>--> -<!-- <!– 文件上传 –>--> -<!-- <el-col :span="12">--> -<!-- <el-upload--> -<!-- :action="actionUrl"--> -<!-- :headers="headers"--> -<!-- multiple--> -<!-- :limit="1"--> -<!-- :file-list="fileList"--> -<!-- :data="uploadData"--> -<!-- :before-upload="beforeVideoUpload"--> -<!-- :on-success="handleUploadSuccess"--> -<!-- >--> -<!-- <el-button type="primary"--> -<!-- >新建视频<i class="el-icon-upload el-icon--right"></i--> -<!-- ></el-button>--> -<!-- </el-upload>--> -<!-- </el-col>--> -<!-- </el-row>--> -<!-- </el-row>--> -<!-- </el-tab-pane>--> -<!-- <!– 类型 5:图文 –>--> -<!-- <el-tab-pane name="news">--> -<!-- <span slot="label"><i class="el-icon-news"></i> 图文</span>--> -<!-- <el-row>--> -<!-- <div class="select-item" v-if="objData.articles">--> -<!-- <wx-news :articles="objData.articles" />--> -<!-- <el-row class="ope-row">--> -<!-- <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" />--> -<!-- </el-row>--> -<!-- </div>--> -<!-- <!– 选择素材 –>--> -<!-- <div v-if="!objData.content">--> -<!-- <el-row style="text-align: center">--> -<!-- <el-col :span="24">--> -<!-- <el-button type="success" @click="openMaterial"--> -<!-- >{{ newsType === '1' ? '选择已发布图文' : '选择草稿箱图文'--> -<!-- }}<i class="el-icon-circle-check el-icon--right"></i--> -<!-- ></el-button>--> -<!-- </el-col>--> -<!-- </el-row>--> -<!-- </div>--> -<!-- <el-dialog title="选择图文" v-model:visible="dialogNewsVisible" width="90%" append-to-body>--> -<!-- <wx-material-select--> -<!-- :objData="objData"--> -<!-- @selectMaterial="selectMaterial"--> -<!-- :newsType="newsType"--> -<!-- />--> -<!-- </el-dialog>--> -<!-- </el-row>--> -<!-- </el-tab-pane>--> -<!-- <!– 类型 6:音乐 –>--> -<!-- <el-tab-pane name="music">--> -<!-- <span slot="label"><i class="el-icon-service"></i> 音乐</span>--> -<!-- <el-row>--> -<!-- <el-col :span="6">--> -<!-- <div class="thumb-div">--> -<!-- <img style="width: 100px" v-if="objData.thumbMediaUrl" :src="objData.thumbMediaUrl" />--> -<!-- <i v-else class="el-icon-plus avatar-uploader-icon"></i>--> -<!-- <div class="thumb-but">--> -<!-- <el-upload--> -<!-- :action="actionUrl"--> -<!-- :headers="headers"--> -<!-- multiple--> -<!-- :limit="1"--> -<!-- :file-list="fileList"--> -<!-- :data="uploadData"--> -<!-- :before-upload="beforeThumbImageUpload"--> -<!-- :on-success="handleUploadSuccess"--> -<!-- >--> -<!-- <el-button slot="trigger" size="mini" type="text">本地上传</el-button>--> -<!-- <el-button size="mini" type="text" @click="openMaterial" style="margin-left: 5px"--> -<!-- >素材库选择</el-button--> -<!-- >--> -<!-- </el-upload>--> -<!-- </div>--> -<!-- </div>--> -<!-- <el-dialog--> -<!-- title="选择图片"--> -<!-- v-model:visible="dialogThumbVisible"--> -<!-- width="80%"--> -<!-- append-to-body--> -<!-- >--> -<!-- <wx-material-select--> -<!-- :objData="{ type: 'image', accountId: objData.accountId }"--> -<!-- @selectMaterial="selectMaterial"--> -<!-- />--> -<!-- </el-dialog>--> -<!-- </el-col>--> -<!-- <el-col :span="18">--> -<!-- <el-input v-model="objData.title" placeholder="请输入标题" @input="inputContent" />--> -<!-- <div style="margin: 20px 0"></div>--> -<!-- <el-input v-model="objData.description" placeholder="请输入描述" @input="inputContent" />--> -<!-- </el-col>--> -<!-- </el-row>--> -<!-- <div style="margin: 20px 0"></div>--> -<!-- <el-input v-model="objData.musicUrl" placeholder="请输入音乐链接" @input="inputContent" />--> -<!-- <div style="margin: 20px 0"></div>--> -<!-- <el-input--> -<!-- v-model="objData.hqMusicUrl"--> -<!-- placeholder="请输入高质量音乐链接"--> -<!-- @input="inputContent"--> -<!-- />--> -<!-- </el-tab-pane>--> -<!-- </el-tabs>--> -<!--</template>--> +<!-- + - Copyright (C) 2018-2019 + - All rights reserved, Designed By www.joolun.com + 芋道源码: + ① 移除多余的 rep 为前缀的变量,让 message 消息更简单 + ② 代码优化,补充注释,提升阅读性 + ③ 优化消息的临时缓存策略,发送消息时,只清理被发送消息的 tab,不会强制切回到 text 输入 + ④ 支持发送【视频】消息时,支持新建视频 +--> +<template> + <el-tabs type="border-card" v-model="objData.type" @tab-click="handleClick"> + <!-- 类型 1:文本 --> + <el-tab-pane name="text"> + <span slot="label"><i class="el-icon-document"></i> 文本</span> + <el-input + type="textarea" + :rows="5" + placeholder="请输入内容" + v-model="objData.content" + @input="inputContent" + /> + </el-tab-pane> + <!-- 类型 2:图片 --> + <el-tab-pane name="image"> + <span slot="label"><i class="el-icon-picture"></i> 图片</span> + <el-row> + <!-- 情况一:已经选择好素材、或者上传好图片 --> + <div class="select-item" v-if="objData.url"> + <img class="material-img" :src="objData.url" /> + <p class="item-name" v-if="objData.name">{{ objData.name }}</p> + <el-row class="ope-row"> + <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" /> + </el-row> + </div> + <!-- 情况二:未做完上述操作 --> + <div v-else> + <el-row style="text-align: center"> + <!-- 选择素材 --> + <el-col :span="12" class="col-select"> + <el-button type="success" @click="openMaterial"> + 素材库选择<i class="el-icon-circle-check el-icon--right"></i> + </el-button> + <el-dialog + title="选择图片" + v-model:visible="dialogImageVisible" + width="90%" + append-to-body + > + <wx-material-select :obj-data="objData" @selectMaterial="selectMaterial" /> + </el-dialog> + </el-col> + <!-- 文件上传 --> + <el-col :span="12" class="col-add"> + <el-upload + :action="actionUrl" + :headers="headers" + multiple + :limit="1" + :file-list="fileList" + :data="uploadData" + :before-upload="beforeImageUpload" + :on-success="handleUploadSuccess" + > + <el-button type="primary">上传图片</el-button> + <div slot="tip" class="el-upload__tip" + >支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div + > + </el-upload> + </el-col> + </el-row> + </div> + </el-row> + </el-tab-pane> + <!-- 类型 3:语音 --> + <el-tab-pane name="voice"> + <span slot="label"><i class="el-icon-phone"></i> 语音</span> + <el-row> + <div class="select-item2" v-if="objData.url"> + <p class="item-name">{{ objData.name }}</p> + <div class="item-infos"> + <wx-voice-player :url="objData.url" /> + </div> + <el-row class="ope-row"> + <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" /> + </el-row> + </div> + <div v-else> + <el-row style="text-align: center"> + <!-- 选择素材 --> + <el-col :span="12" class="col-select"> + <el-button type="success" @click="openMaterial"> + 素材库选择<i class="el-icon-circle-check el-icon--right"></i> + </el-button> + <el-dialog + title="选择语音" + v-model:visible="dialogVoiceVisible" + width="90%" + append-to-body + > + <WxMaterialSelect :objData="objData" @selectMaterial="selectMaterial" /> + </el-dialog> + </el-col> + <!-- 文件上传 --> + <el-col :span="12" class="col-add"> + <el-upload + :action="actionUrl" + :headers="headers" + multiple + :limit="1" + :file-list="fileList" + :data="uploadData" + :before-upload="beforeVoiceUpload" + :on-success="handleUploadSuccess" + > + <el-button type="primary">点击上传</el-button> + <div slot="tip" class="el-upload__tip" + >格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s</div + > + </el-upload> + </el-col> + </el-row> + </div> + </el-row> + </el-tab-pane> + <!-- 类型 4:视频 --> + <el-tab-pane name="video"> + <span slot="label"><i class="el-icon-share"></i> 视频</span> + <el-row> + <el-input v-model="objData.title" placeholder="请输入标题" @input="inputContent" /> + <div style="margin: 20px 0"></div> + <el-input v-model="objData.description" placeholder="请输入描述" @input="inputContent" /> + <div style="margin: 20px 0"></div> + <div style="text-align: center"> + <wx-video-player v-if="objData.url" :url="objData.url" /> + </div> + <div style="margin: 20px 0"></div> + <el-row style="text-align: center"> + <!-- 选择素材 --> + <el-col :span="12"> + <el-button type="success" @click="openMaterial"> + 素材库选择<i class="el-icon-circle-check el-icon--right"></i> + </el-button> + <el-dialog + title="选择视频" + v-model:visible="dialogVideoVisible" + width="90%" + append-to-body + > + <wx-material-select :objData="objData" @selectMaterial="selectMaterial" /> + </el-dialog> + </el-col> + <!-- 文件上传 --> + <el-col :span="12"> + <el-upload + :action="actionUrl" + :headers="headers" + multiple + :limit="1" + :file-list="fileList" + :data="uploadData" + :before-upload="beforeVideoUpload" + :on-success="handleUploadSuccess" + > + <el-button type="primary" + >新建视频<i class="el-icon-upload el-icon--right"></i + ></el-button> + </el-upload> + </el-col> + </el-row> + </el-row> + </el-tab-pane> + <!-- 类型 5:图文 --> + <el-tab-pane name="news"> + <span slot="label"><i class="el-icon-news"></i> 图文</span> + <el-row> + <div class="select-item" v-if="objData.articles"> + <wx-news :articles="objData.articles" /> + <el-row class="ope-row"> + <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" /> + </el-row> + </div> + <!-- 选择素材 --> + <div v-if="!objData.content"> + <el-row style="text-align: center"> + <el-col :span="24"> + <el-button type="success" @click="openMaterial" + >{{ newsType === '1' ? '选择已发布图文' : '选择草稿箱图文' + }}<i class="el-icon-circle-check el-icon--right"></i + ></el-button> + </el-col> + </el-row> + </div> + <el-dialog title="选择图文" v-model:visible="dialogNewsVisible" width="90%" append-to-body> + <wx-material-select + :objData="objData" + @selectMaterial="selectMaterial" + :newsType="newsType" + /> + </el-dialog> + </el-row> + </el-tab-pane> + <!-- 类型 6:音乐 --> + <el-tab-pane name="music"> + <span slot="label"><i class="el-icon-service"></i> 音乐</span> + <el-row> + <el-col :span="6"> + <div class="thumb-div"> + <img style="width: 100px" v-if="objData.thumbMediaUrl" :src="objData.thumbMediaUrl" /> + <i v-else class="el-icon-plus avatar-uploader-icon"></i> + <div class="thumb-but"> + <el-upload + :action="actionUrl" + :headers="headers" + multiple + :limit="1" + :file-list="fileList" + :data="uploadData" + :before-upload="beforeThumbImageUpload" + :on-success="handleUploadSuccess" + > + <el-button slot="trigger" size="mini" type="text">本地上传</el-button> + <el-button size="mini" type="text" @click="openMaterial" style="margin-left: 5px" + >素材库选择</el-button + > + </el-upload> + </div> + </div> + <el-dialog + title="选择图片" + v-model:visible="dialogThumbVisible" + width="80%" + append-to-body + > + <wx-material-select + :objData="{ type: 'image', accountId: objData.accountId }" + @selectMaterial="selectMaterial" + /> + </el-dialog> + </el-col> + <el-col :span="18"> + <el-input v-model="objData.title" placeholder="请输入标题" @input="inputContent" /> + <div style="margin: 20px 0"></div> + <el-input v-model="objData.description" placeholder="请输入描述" @input="inputContent" /> + </el-col> + </el-row> + <div style="margin: 20px 0"></div> + <el-input v-model="objData.musicUrl" placeholder="请输入音乐链接" @input="inputContent" /> + <div style="margin: 20px 0"></div> + <el-input + v-model="objData.hqMusicUrl" + placeholder="请输入高质量音乐链接" + @input="inputContent" + /> + </el-tab-pane> + </el-tabs> +</template> -<!--<script>--> -<!--import WxNews from '@/views/mp/components/wx-news/main.vue'--> -<!--import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'--> -<!--import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'--> -<!--import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'--> +<script> +import WxNews from '@/views/mp/components/wx-news/main.vue' +import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue' +import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' +import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' -<!--import { getAccessToken } from '@/utils/auth'--> +import { getAccessToken } from '@/utils/auth' -<!--export default {--> -<!-- name: 'WxReplySelect',--> -<!-- components: {--> -<!-- WxNews,--> -<!-- WxMaterialSelect,--> -<!-- WxVoicePlayer,--> -<!-- WxVideoPlayer--> -<!-- },--> -<!-- props: {--> -<!-- objData: {--> -<!-- // 消息对象。--> -<!-- type: Object, // 设置为 Object 的原因,方便属性的传递--> -<!-- required: true--> -<!-- },--> -<!-- newsType: {--> -<!-- // 图文类型:1、已发布图文;2、草稿箱图文--> -<!-- type: String,--> -<!-- default: '1'--> -<!-- }--> -<!-- },--> -<!-- data() {--> -<!-- return {--> -<!-- tempPlayerObj: {--> -<!-- type: '2'--> -<!-- },--> +export default { + name: 'WxReplySelect', + components: { + WxNews, + WxMaterialSelect, + WxVoicePlayer, + WxVideoPlayer + }, + props: { + objData: { + // 消息对象。 + type: Object, // 设置为 Object 的原因,方便属性的传递 + required: true + }, + newsType: { + // 图文类型:1、已发布图文;2、草稿箱图文 + type: String, + default: '1' + } + }, + data() { + return { + tempPlayerObj: { + type: '2' + }, -<!-- tempObj: new Map().set(--> -<!-- // 临时缓存,切换消息类型的 tab 的时候,可以保存对应的数据;--> -<!-- this.objData.type, // 消息类型--> -<!-- Object.assign({}, this.objData)--> -<!-- ), // 消息内容--> + tempObj: new Map().set( + // 临时缓存,切换消息类型的 tab 的时候,可以保存对应的数据; + this.objData.type, // 消息类型 + Object.assign({}, this.objData) + ), // 消息内容 -<!-- // ========== 素材选择的弹窗,是否可见 ==========--> -<!-- dialogNewsVisible: false, // 图文--> -<!-- dialogImageVisible: false, // 图片--> -<!-- dialogVoiceVisible: false, // 语音--> -<!-- dialogVideoVisible: false, // 视频--> -<!-- dialogThumbVisible: false, // 缩略图--> + // ========== 素材选择的弹窗,是否可见 ========== + dialogNewsVisible: false, // 图文 + dialogImageVisible: false, // 图片 + dialogVoiceVisible: false, // 语音 + dialogVideoVisible: false, // 视频 + dialogThumbVisible: false, // 缩略图 -<!-- // ========== 文件上传(图片、语音、视频) ==========--> -<!-- fileList: [], // 文件列表--> -<!-- uploadData: {--> -<!-- accountId: undefined,--> -<!-- type: this.objData.type,--> -<!-- title: '',--> -<!-- introduction: ''--> -<!-- },--> -<!-- actionUrl: process.env.VUE_APP_BASE_API + '/admin-api/mp/material/upload-temporary',--> -<!-- headers: { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部--> -<!-- }--> -<!-- },--> -<!-- methods: {--> -<!-- beforeThumbImageUpload(file) {--> -<!-- const isType =--> -<!-- file.type === 'image/jpeg' ||--> -<!-- file.type === 'image/png' ||--> -<!-- file.type === 'image/gif' ||--> -<!-- file.type === 'image/bmp' ||--> -<!-- file.type === 'image/jpg'--> -<!-- if (!isType) {--> -<!-- this.$message.error('上传图片格式不对!')--> -<!-- return false--> -<!-- }--> -<!-- const isLt = file.size / 1024 / 1024 < 2--> -<!-- if (!isLt) {--> -<!-- this.$message.error('上传图片大小不能超过 2M!')--> -<!-- return false--> -<!-- }--> -<!-- this.uploadData.accountId = this.objData.accountId--> -<!-- return true--> -<!-- },--> -<!-- beforeVoiceUpload(file) {--> -<!-- // 校验格式--> -<!-- const isType =--> -<!-- file.type === 'audio/mp3' ||--> -<!-- file.type === 'audio/mpeg' ||--> -<!-- file.type === 'audio/wma' ||--> -<!-- file.type === 'audio/wav' ||--> -<!-- file.type === 'audio/amr'--> -<!-- if (!isType) {--> -<!-- this.$message.error('上传语音格式不对!' + file.type)--> -<!-- return false--> -<!-- }--> -<!-- // 校验大小--> -<!-- const isLt = file.size / 1024 / 1024 < 2--> -<!-- if (!isLt) {--> -<!-- this.$message.error('上传语音大小不能超过 2M!')--> -<!-- return false--> -<!-- }--> -<!-- this.uploadData.accountId = this.objData.accountId--> -<!-- return true--> -<!-- },--> -<!-- beforeImageUpload(file) {--> -<!-- // 校验格式--> -<!-- const isType =--> -<!-- file.type === 'image/jpeg' ||--> -<!-- file.type === 'image/png' ||--> -<!-- file.type === 'image/gif' ||--> -<!-- file.type === 'image/bmp' ||--> -<!-- file.type === 'image/jpg'--> -<!-- if (!isType) {--> -<!-- this.$message.error('上传图片格式不对!')--> -<!-- return false--> -<!-- }--> -<!-- // 校验大小--> -<!-- const isLt = file.size / 1024 / 1024 < 2--> -<!-- if (!isLt) {--> -<!-- this.$message.error('上传图片大小不能超过 2M!')--> -<!-- return false--> -<!-- }--> -<!-- this.uploadData.accountId = this.objData.accountId--> -<!-- return true--> -<!-- },--> -<!-- beforeVideoUpload(file) {--> -<!-- // 校验格式--> -<!-- const isType = file.type === 'video/mp4'--> -<!-- if (!isType) {--> -<!-- this.$message.error('上传视频格式不对!')--> -<!-- return false--> -<!-- }--> -<!-- // 校验大小--> -<!-- const isLt = file.size / 1024 / 1024 < 10--> -<!-- if (!isLt) {--> -<!-- this.$message.error('上传视频大小不能超过 10M!')--> -<!-- return false--> -<!-- }--> -<!-- this.uploadData.accountId = this.objData.accountId--> -<!-- return true--> -<!-- },--> -<!-- handleUploadSuccess(response, file, fileList) {--> -<!-- if (response.code !== 0) {--> -<!-- this.$message.error('上传出错:' + response.msg)--> -<!-- return false--> -<!-- }--> + // ========== 文件上传(图片、语音、视频) ========== + fileList: [], // 文件列表 + uploadData: { + accountId: undefined, + type: this.objData.type, + title: '', + introduction: '' + }, + actionUrl: process.env.VUE_APP_BASE_API + '/admin-api/mp/material/upload-temporary', + headers: { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部 + } + }, + methods: { + beforeThumbImageUpload(file) { + const isType = + file.type === 'image/jpeg' || + file.type === 'image/png' || + file.type === 'image/gif' || + file.type === 'image/bmp' || + file.type === 'image/jpg' + if (!isType) { + this.$message.error('上传图片格式不对!') + return false + } + const isLt = file.size / 1024 / 1024 < 2 + if (!isLt) { + this.$message.error('上传图片大小不能超过 2M!') + return false + } + this.uploadData.accountId = this.objData.accountId + return true + }, + beforeVoiceUpload(file) { + // 校验格式 + const isType = + file.type === 'audio/mp3' || + file.type === 'audio/mpeg' || + file.type === 'audio/wma' || + file.type === 'audio/wav' || + file.type === 'audio/amr' + if (!isType) { + this.$message.error('上传语音格式不对!' + file.type) + return false + } + // 校验大小 + const isLt = file.size / 1024 / 1024 < 2 + if (!isLt) { + this.$message.error('上传语音大小不能超过 2M!') + return false + } + this.uploadData.accountId = this.objData.accountId + return true + }, + beforeImageUpload(file) { + // 校验格式 + const isType = + file.type === 'image/jpeg' || + file.type === 'image/png' || + file.type === 'image/gif' || + file.type === 'image/bmp' || + file.type === 'image/jpg' + if (!isType) { + this.$message.error('上传图片格式不对!') + return false + } + // 校验大小 + const isLt = file.size / 1024 / 1024 < 2 + if (!isLt) { + this.$message.error('上传图片大小不能超过 2M!') + return false + } + this.uploadData.accountId = this.objData.accountId + return true + }, + beforeVideoUpload(file) { + // 校验格式 + const isType = file.type === 'video/mp4' + if (!isType) { + this.$message.error('上传视频格式不对!') + return false + } + // 校验大小 + const isLt = file.size / 1024 / 1024 < 10 + if (!isLt) { + this.$message.error('上传视频大小不能超过 10M!') + return false + } + this.uploadData.accountId = this.objData.accountId + return true + }, + handleUploadSuccess(response, file, fileList) { + if (response.code !== 0) { + this.$message.error('上传出错:' + response.msg) + return false + } -<!-- // 清空上传时的各种数据--> -<!-- this.fileList = []--> -<!-- this.uploadData.title = ''--> -<!-- this.uploadData.introduction = ''--> + // 清空上传时的各种数据 + this.fileList = [] + this.uploadData.title = '' + this.uploadData.introduction = '' -<!-- // 上传好的文件,本质是个素材,所以可以进行选中--> -<!-- let item = response.data--> -<!-- this.selectMaterial(item)--> -<!-- },--> -<!-- /**--> -<!-- * 切换消息类型的 tab--> -<!-- *--> -<!-- * @param tab tab--> -<!-- */--> -<!-- handleClick(tab) {--> -<!-- // 设置后续文件上传的文件类型--> -<!-- this.uploadData.type = this.objData.type--> -<!-- if (this.uploadData.type === 'music') {--> -<!-- // 【音乐】上传的是缩略图--> -<!-- this.uploadData.type = 'thumb'--> -<!-- }--> + // 上传好的文件,本质是个素材,所以可以进行选中 + let item = response.data + this.selectMaterial(item) + }, + /** + * 切换消息类型的 tab + * + * @param tab tab + */ + handleClick(tab) { + // 设置后续文件上传的文件类型 + this.uploadData.type = this.objData.type + if (this.uploadData.type === 'music') { + // 【音乐】上传的是缩略图 + this.uploadData.type = 'thumb' + } -<!-- // 从 tempObj 临时缓存中,获取对应的数据,并设置回 objData--> -<!-- let tempObjItem = this.tempObj.get(this.objData.type)--> -<!-- if (tempObjItem) {--> -<!-- this.objData.content = tempObjItem.content ? tempObjItem.content : null--> -<!-- this.objData.mediaId = tempObjItem.mediaId ? tempObjItem.mediaId : null--> -<!-- this.objData.url = tempObjItem.url ? tempObjItem.url : null--> -<!-- this.objData.name = tempObjItem.url ? tempObjItem.name : null--> -<!-- this.objData.title = tempObjItem.title ? tempObjItem.title : null--> -<!-- this.objData.description = tempObjItem.description ? tempObjItem.description : null--> -<!-- return--> -<!-- }--> -<!-- // 如果获取不到,需要把 objData 复原--> -<!-- // 必须使用 $set 赋值,不然 input 无法输入内容--> -<!-- this.$set(this.objData, 'content', '')--> -<!-- this.$delete(this.objData, 'mediaId')--> -<!-- this.$delete(this.objData, 'url')--> -<!-- this.$set(this.objData, 'title', '')--> -<!-- this.$set(this.objData, 'description', '')--> -<!-- },--> -<!-- /**--> -<!-- * 选择素材,将设置设置到 objData 变量--> -<!-- *--> -<!-- * @param item 素材--> -<!-- */--> -<!-- selectMaterial(item) {--> -<!-- // 选择好素材,所以隐藏弹窗--> -<!-- this.closeMaterial()--> + // 从 tempObj 临时缓存中,获取对应的数据,并设置回 objData + let tempObjItem = this.tempObj.get(this.objData.type) + if (tempObjItem) { + this.objData.content = tempObjItem.content ? tempObjItem.content : null + this.objData.mediaId = tempObjItem.mediaId ? tempObjItem.mediaId : null + this.objData.url = tempObjItem.url ? tempObjItem.url : null + this.objData.name = tempObjItem.url ? tempObjItem.name : null + this.objData.title = tempObjItem.title ? tempObjItem.title : null + this.objData.description = tempObjItem.description ? tempObjItem.description : null + return + } + // 如果获取不到,需要把 objData 复原 + // 必须使用 $set 赋值,不然 input 无法输入内容 + this.$set(this.objData, 'content', '') + this.$delete(this.objData, 'mediaId') + this.$delete(this.objData, 'url') + this.$set(this.objData, 'title', '') + this.$set(this.objData, 'description', '') + }, + /** + * 选择素材,将设置设置到 objData 变量 + * + * @param item 素材 + */ + selectMaterial(item) { + // 选择好素材,所以隐藏弹窗 + this.closeMaterial() -<!-- // 创建 tempObjItem 对象,并设置对应的值--> -<!-- let tempObjItem = {}--> -<!-- tempObjItem.type = this.objData.type--> -<!-- if (this.objData.type === 'news') {--> -<!-- tempObjItem.articles = item.content.newsItem--> -<!-- this.objData.articles = item.content.newsItem--> -<!-- } else if (this.objData.type === 'music') {--> -<!-- // 音乐需要特殊处理,因为选择的是图片的缩略图--> -<!-- tempObjItem.thumbMediaId = item.mediaId--> -<!-- this.objData.thumbMediaId = item.mediaId--> -<!-- tempObjItem.thumbMediaUrl = item.url--> -<!-- this.objData.thumbMediaUrl = item.url--> -<!-- // title、introduction、musicUrl、hqMusicUrl:从 objData 到 tempObjItem,避免上传素材后,被覆盖掉--> -<!-- tempObjItem.title = this.objData.title || ''--> -<!-- tempObjItem.introduction = this.objData.introduction || ''--> -<!-- tempObjItem.musicUrl = this.objData.musicUrl || ''--> -<!-- tempObjItem.hqMusicUrl = this.objData.hqMusicUrl || ''--> -<!-- } else if (this.objData.type === 'image' || this.objData.type === 'voice') {--> -<!-- tempObjItem.mediaId = item.mediaId--> -<!-- this.objData.mediaId = item.mediaId--> -<!-- tempObjItem.url = item.url--> -<!-- this.objData.url = item.url--> -<!-- tempObjItem.name = item.name--> -<!-- this.objData.name = item.name--> -<!-- } else if (this.objData.type === 'video') {--> -<!-- tempObjItem.mediaId = item.mediaId--> -<!-- this.objData.mediaId = item.mediaId--> -<!-- tempObjItem.url = item.url--> -<!-- this.objData.url = item.url--> -<!-- tempObjItem.name = item.name--> -<!-- this.objData.name = item.name--> -<!-- // title、introduction:从 item 到 tempObjItem,因为素材里有 title、introduction--> -<!-- if (item.title) {--> -<!-- this.objData.title = item.title || ''--> -<!-- tempObjItem.title = item.title || ''--> -<!-- }--> -<!-- if (item.introduction) {--> -<!-- this.objData.description = item.introduction || '' // 消息使用的是 description,素材使用的是 introduction,所以转换下--> -<!-- tempObjItem.description = item.introduction || ''--> -<!-- }--> -<!-- } else if (this.objData.type === 'text') {--> -<!-- this.objData.content = item.content || ''--> -<!-- }--> -<!-- // 最终设置到临时缓存--> -<!-- this.tempObj.set(this.objData.type, tempObjItem)--> -<!-- },--> -<!-- openMaterial() {--> -<!-- if (this.objData.type === 'news') {--> -<!-- this.dialogNewsVisible = true--> -<!-- } else if (this.objData.type === 'image') {--> -<!-- this.dialogImageVisible = true--> -<!-- } else if (this.objData.type === 'voice') {--> -<!-- this.dialogVoiceVisible = true--> -<!-- } else if (this.objData.type === 'video') {--> -<!-- this.dialogVideoVisible = true--> -<!-- } else if (this.objData.type === 'music') {--> -<!-- this.dialogThumbVisible = true--> -<!-- }--> -<!-- },--> -<!-- closeMaterial() {--> -<!-- this.dialogNewsVisible = false--> -<!-- this.dialogImageVisible = false--> -<!-- this.dialogVoiceVisible = false--> -<!-- this.dialogVideoVisible = false--> -<!-- this.dialogThumbVisible = false--> -<!-- },--> -<!-- deleteObj() {--> -<!-- if (this.objData.type === 'news') {--> -<!-- this.$delete(this.objData, 'articles')--> -<!-- } else if (this.objData.type === 'image') {--> -<!-- this.objData.mediaId = null--> -<!-- this.$delete(this.objData, 'url')--> -<!-- this.objData.name = null--> -<!-- } else if (this.objData.type === 'voice') {--> -<!-- this.objData.mediaId = null--> -<!-- this.$delete(this.objData, 'url')--> -<!-- this.objData.name = null--> -<!-- } else if (this.objData.type === 'video') {--> -<!-- this.objData.mediaId = null--> -<!-- this.$delete(this.objData, 'url')--> -<!-- this.objData.name = null--> -<!-- this.objData.title = null--> -<!-- this.objData.description = null--> -<!-- } else if (this.objData.type === 'music') {--> -<!-- this.objData.thumbMediaId = null--> -<!-- this.objData.thumbMediaUrl = null--> -<!-- this.objData.title = null--> -<!-- this.objData.description = null--> -<!-- this.objData.musicUrl = null--> -<!-- this.objData.hqMusicUrl = null--> -<!-- } else if (this.objData.type === 'text') {--> -<!-- this.objData.content = null--> -<!-- }--> -<!-- // 覆盖缓存--> -<!-- this.tempObj.set(this.objData.type, Object.assign({}, this.objData))--> -<!-- },--> -<!-- /**--> -<!-- * 输入时,缓存每次 objData 到 tempObj 中--> -<!-- *--> -<!-- * why?不确定为什么 v-model="objData.content" 不能自动缓存,所以通过这样的方式--> -<!-- */--> -<!-- inputContent(str) {--> -<!-- // 覆盖缓存--> -<!-- this.tempObj.set(this.objData.type, Object.assign({}, this.objData))--> -<!-- }--> -<!-- }--> -<!--}--> -<!--</script>--> + // 创建 tempObjItem 对象,并设置对应的值 + let tempObjItem = {} + tempObjItem.type = this.objData.type + if (this.objData.type === 'news') { + tempObjItem.articles = item.content.newsItem + this.objData.articles = item.content.newsItem + } else if (this.objData.type === 'music') { + // 音乐需要特殊处理,因为选择的是图片的缩略图 + tempObjItem.thumbMediaId = item.mediaId + this.objData.thumbMediaId = item.mediaId + tempObjItem.thumbMediaUrl = item.url + this.objData.thumbMediaUrl = item.url + // title、introduction、musicUrl、hqMusicUrl:从 objData 到 tempObjItem,避免上传素材后,被覆盖掉 + tempObjItem.title = this.objData.title || '' + tempObjItem.introduction = this.objData.introduction || '' + tempObjItem.musicUrl = this.objData.musicUrl || '' + tempObjItem.hqMusicUrl = this.objData.hqMusicUrl || '' + } else if (this.objData.type === 'image' || this.objData.type === 'voice') { + tempObjItem.mediaId = item.mediaId + this.objData.mediaId = item.mediaId + tempObjItem.url = item.url + this.objData.url = item.url + tempObjItem.name = item.name + this.objData.name = item.name + } else if (this.objData.type === 'video') { + tempObjItem.mediaId = item.mediaId + this.objData.mediaId = item.mediaId + tempObjItem.url = item.url + this.objData.url = item.url + tempObjItem.name = item.name + this.objData.name = item.name + // title、introduction:从 item 到 tempObjItem,因为素材里有 title、introduction + if (item.title) { + this.objData.title = item.title || '' + tempObjItem.title = item.title || '' + } + if (item.introduction) { + this.objData.description = item.introduction || '' // 消息使用的是 description,素材使用的是 introduction,所以转换下 + tempObjItem.description = item.introduction || '' + } + } else if (this.objData.type === 'text') { + this.objData.content = item.content || '' + } + // 最终设置到临时缓存 + this.tempObj.set(this.objData.type, tempObjItem) + }, + openMaterial() { + if (this.objData.type === 'news') { + this.dialogNewsVisible = true + } else if (this.objData.type === 'image') { + this.dialogImageVisible = true + } else if (this.objData.type === 'voice') { + this.dialogVoiceVisible = true + } else if (this.objData.type === 'video') { + this.dialogVideoVisible = true + } else if (this.objData.type === 'music') { + this.dialogThumbVisible = true + } + }, + closeMaterial() { + this.dialogNewsVisible = false + this.dialogImageVisible = false + this.dialogVoiceVisible = false + this.dialogVideoVisible = false + this.dialogThumbVisible = false + }, + deleteObj() { + if (this.objData.type === 'news') { + this.$delete(this.objData, 'articles') + } else if (this.objData.type === 'image') { + this.objData.mediaId = null + this.$delete(this.objData, 'url') + this.objData.name = null + } else if (this.objData.type === 'voice') { + this.objData.mediaId = null + this.$delete(this.objData, 'url') + this.objData.name = null + } else if (this.objData.type === 'video') { + this.objData.mediaId = null + this.$delete(this.objData, 'url') + this.objData.name = null + this.objData.title = null + this.objData.description = null + } else if (this.objData.type === 'music') { + this.objData.thumbMediaId = null + this.objData.thumbMediaUrl = null + this.objData.title = null + this.objData.description = null + this.objData.musicUrl = null + this.objData.hqMusicUrl = null + } else if (this.objData.type === 'text') { + this.objData.content = null + } + // 覆盖缓存 + this.tempObj.set(this.objData.type, Object.assign({}, this.objData)) + }, + /** + * 输入时,缓存每次 objData 到 tempObj 中 + * + * why?不确定为什么 v-model="objData.content" 不能自动缓存,所以通过这样的方式 + */ + inputContent(str) { + // 覆盖缓存 + this.tempObj.set(this.objData.type, Object.assign({}, this.objData)) + } + } +} +</script> -<!--<style lang="scss" scoped>--> -<!--.public-account-management {--> -<!-- .el-input {--> -<!-- width: 70%;--> -<!-- margin-right: 2%;--> -<!-- }--> -<!--}--> -<!--.pagination {--> -<!-- text-align: right;--> -<!-- margin-right: 25px;--> -<!--}--> -<!--.select-item {--> -<!-- width: 280px;--> -<!-- padding: 10px;--> -<!-- margin: 0 auto 10px auto;--> -<!-- border: 1px solid #eaeaea;--> -<!--}--> -<!--.select-item2 {--> -<!-- padding: 10px;--> -<!-- margin: 0 auto 10px auto;--> -<!-- border: 1px solid #eaeaea;--> -<!--}--> -<!--.ope-row {--> -<!-- padding-top: 10px;--> -<!-- text-align: center;--> -<!--}--> -<!--.item-name {--> -<!-- font-size: 12px;--> -<!-- overflow: hidden;--> -<!-- text-overflow: ellipsis;--> -<!-- white-space: nowrap;--> -<!-- text-align: center;--> -<!--}--> -<!--.el-form-item__content {--> -<!-- line-height: unset !important;--> -<!--}--> -<!--.col-select {--> -<!-- border: 1px solid rgb(234, 234, 234);--> -<!-- padding: 50px 0px;--> -<!-- height: 160px;--> -<!-- width: 49.5%;--> -<!--}--> -<!--.col-select2 {--> -<!-- border: 1px solid rgb(234, 234, 234);--> -<!-- padding: 50px 0px;--> -<!-- height: 160px;--> -<!--}--> -<!--.col-add {--> -<!-- border: 1px solid rgb(234, 234, 234);--> -<!-- padding: 50px 0px;--> -<!-- height: 160px;--> -<!-- width: 49.5%;--> -<!-- float: right;--> -<!--}--> -<!--.avatar-uploader-icon {--> -<!-- border: 1px solid #d9d9d9;--> -<!-- font-size: 28px;--> -<!-- color: #8c939d;--> -<!-- width: 100px !important;--> -<!-- height: 100px !important;--> -<!-- line-height: 100px !important;--> -<!-- text-align: center;--> -<!--}--> -<!--.material-img {--> -<!-- width: 100%;--> -<!--}--> -<!--.thumb-div {--> -<!-- display: inline-block;--> -<!-- text-align: center;--> -<!--}--> -<!--.item-infos {--> -<!-- width: 30%;--> -<!-- margin: auto;--> -<!--}--> -<!--</style>--> +<style lang="scss" scoped> +.public-account-management { + .el-input { + width: 70%; + margin-right: 2%; + } +} +.pagination { + text-align: right; + margin-right: 25px; +} +.select-item { + width: 280px; + padding: 10px; + margin: 0 auto 10px auto; + border: 1px solid #eaeaea; +} +.select-item2 { + padding: 10px; + margin: 0 auto 10px auto; + border: 1px solid #eaeaea; +} +.ope-row { + padding-top: 10px; + text-align: center; +} +.item-name { + font-size: 12px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: center; +} +.el-form-item__content { + line-height: unset !important; +} +.col-select { + border: 1px solid rgb(234, 234, 234); + padding: 50px 0px; + height: 160px; + width: 49.5%; +} +.col-select2 { + border: 1px solid rgb(234, 234, 234); + padding: 50px 0px; + height: 160px; +} +.col-add { + border: 1px solid rgb(234, 234, 234); + padding: 50px 0px; + height: 160px; + width: 49.5%; + float: right; +} +.avatar-uploader-icon { + border: 1px solid #d9d9d9; + font-size: 28px; + color: #8c939d; + width: 100px !important; + height: 100px !important; + line-height: 100px !important; + text-align: center; +} +.material-img { + width: 100%; +} +.thumb-div { + display: inline-block; + text-align: center; +} +.item-infos { + width: 30%; + margin: auto; +} +</style> diff --git a/src/views/mp/components/wx-video-play/main.vue b/src/views/mp/components/wx-video-play/main.vue index 880d10f8..7679a70a 100644 --- a/src/views/mp/components/wx-video-play/main.vue +++ b/src/views/mp/components/wx-video-play/main.vue @@ -8,110 +8,84 @@ 存在的问题:mediaId 有效期是 3 天,超过时间后无法播放 2)重构后的做法:后端接收到微信公众号的视频消息后,将视频消息的 media_id 的文件内容保存到文件服务器中,这样前端可以直接使用 URL 播放。 ② 体验优化:弹窗关闭后,自动暂停视频的播放 + --> <template> - <div> + <div @click="playVideo()"> <!-- 提示 --> - <div @click="playVideo()"> - <el-icon> - <VideoPlay /> - </el-icon> + <div> + <Icon icon="ep:video-play" class="mr-5px" /> <p>点击播放视频</p> </div> <!-- 弹窗播放 --> <el-dialog + v-model="dialogVideo" title="视频播放" - v-model:visible="dialogVideo" width="40%" append-to-body @close="closeDialog" > - <video-player - v-if="playerOptions.sources[0].src" - class="video-player vjs-custom-skin" - ref="videoPlayerRef" - :playsinline="true" - :options="playerOptions" - @play="onPlayerPlay($event)" - @pause="onPlayerPause($event)" - /> + <template #footer> + <video-player + v-if="dialogVideo" + class="video-player vjs-big-play-centered" + :src="url" + poster="" + crossorigin="anonymous" + playsinline + controls + :volume="0.6" + :height="320" + :playback-rates="[0.7, 1.0, 1.5, 2.0]" + /> + </template> + <!-- 事件,暫時沒用 + @mounted="handleMounted"--> + <!-- @ready="handleEvent($event)"--> + <!-- @play="handleEvent($event)"--> + <!-- @pause="handleEvent($event)"--> + <!-- @ended="handleEvent($event)"--> + <!-- @loadeddata="handleEvent($event)"--> + <!-- @waiting="handleEvent($event)"--> + <!-- @playing="handleEvent($event)"--> + <!-- @canplay="handleEvent($event)"--> + <!-- @canplaythrough="handleEvent($event)"--> + <!-- @timeupdate="handleEvent(player?.currentTime())"--> </el-dialog> </div> </template> -<script setup lang="ts" name="WxVideoPlayer"> -// 引入 videoPlayer 相关组件。教程:https://juejin.cn/post/6923056942281654285 -import { videoPlayer } from 'vue-video-player' +<script lang="ts" name="WxVideoPlayer"> +//升级videojs6.0版本,重寫6.0版本 +import 'video.js/dist/video-js.css' +import { defineComponent } from 'vue' +import { VideoPlayer } from '@videojs-player/vue' import 'video.js/dist/video-js.css' -import 'vue-video-player/src/custom-theme.css' -import { VideoPlay } from '@element-plus/icons-vue' -const props = defineProps({ - url: { - // 视频地址,例如说:https://www.iocoder.cn/xxx.mp4 - type: String, - required: true - } -}) -const videoPlayerRef = ref() -const dialogVideo = ref(false) -const playerOptions = reactive({ - playbackRates: [0.5, 1.0, 1.5, 2.0], // 播放速度 - autoplay: false, // 如果 true,浏览器准备好时开始回放。 - muted: false, // 默认情况下将会消除任何音频。 - loop: false, // 导致视频一结束就重新开始。 - preload: 'auto', // 建议浏览器在 <video> 加载元素后是否应该开始下载视频数据。auto 浏览器选择最佳行为,立即开始加载视频(如果浏览器支持) - language: 'zh-CN', - aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3") - fluid: true, // 当true时,Video.js player 将拥有流体大小。换句话说,它将按比例缩放以适应其容器。 - sources: [ - { - type: 'video/mp4', - src: '' // 你的视频地址(必填)【重要】 +export default defineComponent({ + components: { + VideoPlayer + }, + props: { + url: { + // 视频地址,例如说:https://vjs.zencdn.net/v/oceans.mp4 + type: String, + required: true } - ], - poster: '', // 你的封面地址 - width: document.documentElement.clientWidth, - notSupportedMessage: '此视频暂无法播放,请稍后再试', //允许覆盖 Video.js 无法播放媒体源时显示的默认信息。 - controlBar: { - timeDivider: true, - durationDisplay: true, - remainingTimeDisplay: false, - fullscreenToggle: true //全屏按钮 + }, + setup() { + // const videoPlayerRef = ref(null) + const dialogVideo = ref(false) + + const handleEvent = (log) => { + console.log('Basic player event', log) + } + const playVideo = () => { + dialogVideo.value = true + } + + return { handleEvent, playVideo, dialogVideo } } }) - -const playVideo = () => { - dialogVideo.value = true - playerOptions.sources[0].src = props.url -} -const closeDialog = () => { - // 暂停播放 - // videoPlayerRef.player.pause() -} -// onPlayerPlay(player) {}, -// // // eslint-disable-next-line @typescript-eslint/no-unused-vars -// // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars -// onPlayerPause(player) {} - -// methods: { -// playVideo() { -// this.dialogVideo = true -// // 设置地址 -// this.playerOptions.sources[0]['src'] = this.url -// }, -// closeDialog() { -// // 暂停播放 -// this.$refs.videoPlayer.player.pause() -// }, -// -// //todo player组件引入可能有问题 -// -// // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars -// onPlayerPlay(player) {}, -// // // eslint-disable-next-line @typescript-eslint/no-unused-vars -// // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars -// onPlayerPause(player) {} -// } </script> diff --git a/src/views/mp/components/wx-voice-play/main.vue b/src/views/mp/components/wx-voice-play/main.vue index 3260fc05..9ec8e2e9 100644 --- a/src/views/mp/components/wx-voice-play/main.vue +++ b/src/views/mp/components/wx-voice-play/main.vue @@ -25,6 +25,7 @@ <script setup lang="ts" name="WxVoicePlayer"> // 因为微信语音是 amr 格式,所以需要用到 amr 解码器:https://www.npmjs.com/package/benz-amr-recorder + import BenzAMRRecorder from 'benz-amr-recorder' const props = defineProps({ diff --git a/src/views/mp/freePublish/index.vue b/src/views/mp/freePublish/index.vue index 6ef4a303..05f3dec6 100644 --- a/src/views/mp/freePublish/index.vue +++ b/src/views/mp/freePublish/index.vue @@ -19,8 +19,14 @@ </el-select> </el-form-item> <el-form-item> - <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> - <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px" /> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px" /> + 重置 + </el-button> </el-form-item> </el-form> </content-wrap> @@ -63,12 +69,19 @@ import { getFreePublishPage, deleteFreePublish } from '@/api/mp/freePublish' import * as MpAccountApi from '@/api/mp/account' import WxNews from '@/views/mp/components/wx-news/main.vue' + const message = useMessage() // 消息弹窗 const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 const list = ref([]) // 列表的数据 -const queryParams = reactive({ +interface QueryParams { + currentPage: number | undefined | string + pageNo: number | undefined | string + accountId: number | undefined | string +} + +const queryParams: QueryParams = reactive({ currentPage: 1, // 当前页数 pageNo: 1, // 当前页数 accountId: undefined // 当前页数 @@ -115,7 +128,6 @@ const resetQuery = () => { queryFormRef.value.resetFields() // 默认选中第一个 if (accountList.value.length > 0) { - // @ts-ignore queryParams.accountId = accountList.value[0].id } handleQuery() @@ -144,7 +156,6 @@ onMounted(async () => { accountList.value = await MpAccountApi.getSimpleAccountList() // 选中第一个 if (accountList.value.length > 0) { - // @ts-ignore queryParams.accountId = accountList.value[0].id } await getList() diff --git a/src/views/mp/message/index.vue b/src/views/mp/message/index.vue index 10145221..f218de39 100644 --- a/src/views/mp/message/index.vue +++ b/src/views/mp/message/index.vue @@ -181,17 +181,17 @@ </ContentWrap> </template> <script setup lang="ts" name="MpMessage"> -import { DICT_TYPE, getStrDictOptions } from '@/utils/dict' -import { dateFormatter } from '@/utils/formatTime' -// import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' +import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' -// import WxMsg from '@/views/mp/components/wx-msg/main.vue' +import WxMsg from '@/views/mp/components/wx-msg/main.vue' import WxLocation from '@/views/mp/components/wx-location/main.vue' -// import WxMusic from '@/views/mp/components/wx-music/main.vue' -// import WxNews from '@/views/mp/components/wx-news/main.vue' +import WxMusic from '@/views/mp/components/wx-music/main.vue' +import WxNews from '@/views/mp/components/wx-news/main.vue' import * as MpAccountApi from '@/api/mp/account' import * as MpMessageApi from '@/api/mp/message' const message = useMessage() // 消息弹窗 +import { DICT_TYPE, getStrDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 diff --git a/src/views/mp/tag/index.vue b/src/views/mp/tag/index.vue index 84e6fc17..f4872271 100644 --- a/src/views/mp/tag/index.vue +++ b/src/views/mp/tag/index.vue @@ -28,13 +28,21 @@ /> </el-form-item> <el-form-item> - <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> - <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px" /> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px" /> + 重置 + </el-button> <el-button type="primary" plain @click="openForm('create')" v-hasPermi="['mp:tag:create']"> - <Icon icon="ep:plus" class="mr-5px" /> 新增 + <Icon icon="ep:plus" class="mr-5px" /> + 新增 </el-button> <el-button type="success" plain @click="handleSync" v-hasPermi="['mp:tag:sync']"> - <Icon icon="ep:refresh" class="mr-5px" /> 同步 + <Icon icon="ep:refresh" class="mr-5px" /> + 同步 </el-button> </el-form-item> </el-form> @@ -91,6 +99,7 @@ import { dateFormatter } from '@/utils/formatTime' import * as MpTagApi from '@/api/mp/tag' import * as MpAccountApi from '@/api/mp/account' import TagForm from './TagForm.vue' + const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 From 6ce9ab875c659ef669cb5fbd4dc006b3d6d47b0f Mon Sep 17 00:00:00 2001 From: fessor <352475718@qq.com> Date: Mon, 27 Mar 2023 01:44:23 +0000 Subject: [PATCH 118/184] =?UTF-8?q?feat:=20ep=E9=87=8D=E5=86=99=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: fessor <352475718@qq.com> --- src/views/system/user/AddForm.vue | 223 ++++++ src/views/system/user/ImportForm.vue | 153 +++++ src/views/system/user/RoleForm.vue | 90 +++ src/views/system/user/index.vue | 979 +++++++++++++-------------- src/views/system/user/utils.ts | 44 ++ 5 files changed, 977 insertions(+), 512 deletions(-) create mode 100644 src/views/system/user/AddForm.vue create mode 100644 src/views/system/user/ImportForm.vue create mode 100644 src/views/system/user/RoleForm.vue create mode 100644 src/views/system/user/utils.ts diff --git a/src/views/system/user/AddForm.vue b/src/views/system/user/AddForm.vue new file mode 100644 index 00000000..12dc1737 --- /dev/null +++ b/src/views/system/user/AddForm.vue @@ -0,0 +1,223 @@ +<template> + <!-- 添加或修改参数配置对话框 --> + <el-dialog + :title="title" + :modelValue="modelValue" + width="600px" + append-to-body + @close="closeDialog" + > + <el-form ref="formRef" :model="formData" :rules="rules" label-width="80px"> + <el-row> + <el-col :span="12"> + <el-form-item label="用户昵称" prop="nickname"> + <el-input v-model="formData.nickname" placeholder="请输入用户昵称" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="归属部门" prop="deptId"> + <el-tree-select + node-key="id" + v-model="formData.deptId" + :data="deptOptions" + :props="defaultProps" + check-strictly + placeholder="请选择归属部门" + /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="手机号码" prop="mobile"> + <el-input v-model="formData.mobile" placeholder="请输入手机号码" maxlength="11" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="邮箱" prop="email"> + <el-input v-model="formData.email" placeholder="请输入邮箱" maxlength="50" /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item v-if="formData.id === undefined" label="用户名称" prop="username"> + <el-input v-model="formData.username" placeholder="请输入用户名称" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item v-if="formData.id === undefined" label="用户密码" prop="password"> + <el-input + v-model="formData.password" + placeholder="请输入用户密码" + type="password" + show-password + /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="用户性别"> + <el-select v-model="formData.sex" placeholder="请选择"> + <el-option + v-for="dict in sexDictDatas" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="岗位"> + <el-select v-model="formData.postIds" multiple placeholder="请选择"> + <el-option + v-for="item in postOptions" + :key="item.id" + :label="item.name" + :value="item.id" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="24"> + <el-form-item label="备注"> + <el-input v-model="formData.remark" type="textarea" placeholder="请输入内容" /> + </el-form-item> + </el-col> + </el-row> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </template> + </el-dialog> +</template> +<script lang="ts" setup> +import { PostVO } from '@/api/system/post' +import { createUserApi, updateUserApi } from '@/api/system/user' +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { defaultProps } from '@/utils/tree' +import { ElForm, FormItemRule } from 'element-plus' +import { Arrayable } from 'element-plus/es/utils' + +type Form = InstanceType<typeof ElForm> +interface Props { + deptOptions?: Tree[] + postOptions?: PostVO[] //岗位列表 + modelValue: boolean + formInitValue?: Recordable & Partial<typeof initParams> +} + +const props = withDefaults(defineProps<Props>(), { + deptOptions: () => [], + postOptions: () => [], + modelValue: false, + formInitValue: () => ({}) +}) +const emits = defineEmits(['update:modelValue', 'success']) + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 +// 弹出层标题 +const title = computed(() => { + return formData.value?.id ? '修改用户' : '添加用户' +}) + +// 性别字典 +const sexDictDatas = getDictOptions(DICT_TYPE.SYSTEM_USER_SEX) + +// 表单初始化参数 +const initParams = { + nickname: '', + deptId: '', + mobile: '', + email: '', + id: undefined, + username: '', + password: '', + sex: 1, + postIds: [], + remark: '', + status: '0', + roleIds: [] +} + +// 校验规则 +const rules = { + username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }], + nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }], + password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }], + email: [ + { + type: 'email', + message: "'请输入正确的邮箱地址", + trigger: ['blur', 'change'] + } + ], + mobile: [ + { + pattern: /^(?:(?:\+|00)86)?1(?:3[\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\d]|9[189])\d{8}$/, + message: '请输入正确的手机号码', + trigger: 'blur' + } + ] +} as Partial<Record<string, Arrayable<FormItemRule>>> +const formRef = ref<Form | null>() +const formData = ref<Recordable>({ ...initParams }) +watch( + () => props.formInitValue, + (val) => { + formData.value = { ...val } + }, + { deep: true } +) + +const resetForm = () => { + let form = formRef?.value + if (!form) return + formData.value = { ...initParams } + form && (form as Form).resetFields() +} +const closeDialog = () => { + emits('update:modelValue', false) +} +// 操作成功 +const operateOk = () => { + emits('success', true) + closeDialog() +} +const submitForm = () => { + let form = formRef.value as Form + form.validate(async (valid) => { + let data = formData.value + if (valid) { + try { + if (data?.id !== undefined) { + await updateUserApi(data) + message.success(t('common.updateSuccess')) + operateOk() + } else { + await createUserApi(data) + message.success(t('common.createSuccess')) + operateOk() + } + } catch (err) { + console.error(err) + } + } + }) +} +const cancel = () => { + closeDialog() +} + +defineExpose({ + resetForm +}) +</script> \ No newline at end of file diff --git a/src/views/system/user/ImportForm.vue b/src/views/system/user/ImportForm.vue new file mode 100644 index 00000000..b029730a --- /dev/null +++ b/src/views/system/user/ImportForm.vue @@ -0,0 +1,153 @@ +<template> + <el-dialog + :title="upload.title" + :modelValue="modelValue" + width="400px" + append-to-body + @close="closeDialog" + > + <el-upload + ref="uploadRef" + accept=".xlsx, .xls" + :limit="1" + :headers="upload.headers" + :action="upload.url + '?updateSupport=' + upload.updateSupport" + :disabled="upload.isUploading" + :on-progress="handleFileUploadProgress" + :on-success="handleFileSuccess" + :on-exceed="handleExceed" + :on-error="excelUploadError" + :auto-upload="false" + drag + > + <Icon icon="ep:upload" /> + <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> + <template #tip> + <div class="el-upload__tip text-center"> + <div class="el-upload__tip"> + <el-checkbox v-model="upload.updateSupport" /> 是否更新已经存在的用户数据 + </div> + + <span>仅允许导入xls、xlsx格式文件。</span> + <el-link + type="primary" + :underline="false" + style="font-size: 12px; vertical-align: baseline" + @click="importTemplate" + >下载模板</el-link + > + </div> + </template> + </el-upload> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submitFileForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </template> + </el-dialog> +</template> + +<script lang="ts" setup> +import { importUserTemplateApi } from '@/api/system/user' +import { getAccessToken, getTenantId } from '@/utils/auth' +import download from '@/utils/download' + +interface Props { + modelValue: boolean +} + +// const props = +withDefaults(defineProps<Props>(), { + modelValue: false +}) + +const emits = defineEmits(['update:modelValue', 'success']) + +const message = useMessage() // 消息弹窗 + +const uploadRef = ref() + +// 用户导入参数 +const upload = reactive({ + // // 是否显示弹出层(用户导入) + // open: false, + // 弹出层标题(用户导入) + title: '用户导入', + // 是否禁用上传 + isUploading: false, + // 是否更新已经存在的用户数据 + updateSupport: 0, + // 设置上传的请求头部 + headers: { + Authorization: 'Bearer ' + getAccessToken(), + 'tenant-id': getTenantId() + }, + // 上传的地址 + url: import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import' +}) + +// 文件上传中处理 +const handleFileUploadProgress = () => { + upload.isUploading = true +} +// 文件上传成功处理 +const handleFileSuccess = (response: any) => { + if (response.code !== 0) { + message.error(response.msg) + return + } + upload.isUploading = false + uploadRef.value?.clearFiles() + // 拼接提示语 + const data = response.data + let text = '上传成功数量:' + data.createUsernames.length + ';' + for (let username of data.createUsernames) { + text += '< ' + username + ' >' + } + text += '更新成功数量:' + data.updateUsernames.length + ';' + for (const username of data.updateUsernames) { + text += '< ' + username + ' >' + } + text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';' + for (const username in data.failureUsernames) { + text += '< ' + username + ': ' + data.failureUsernames[username] + ' >' + } + message.alert(text) + emits('success') + closeDialog() +} + +// 文件数超出提示 +const handleExceed = (): void => { + message.error('最多只能上传一个文件!') +} +// 上传错误提示 +const excelUploadError = (): void => { + message.error('导入数据失败,请您重新上传!') +} + +/** 下载模板操作 */ +const importTemplate = async () => { + try { + const res = await importUserTemplateApi() + download.excel(res, '用户导入模版.xls') + } catch (error) { + console.error(error) + } +} + +/* 弹框按钮操作 */ +// 点击取消 +const cancel = () => { + closeDialog() +} +// 关闭弹窗 +const closeDialog = () => { + emits('update:modelValue', false) +} +// 提交上传文件 +const submitFileForm = () => { + uploadRef.value?.submit() +} +</script> \ No newline at end of file diff --git a/src/views/system/user/RoleForm.vue b/src/views/system/user/RoleForm.vue new file mode 100644 index 00000000..694e5eeb --- /dev/null +++ b/src/views/system/user/RoleForm.vue @@ -0,0 +1,90 @@ +<template> + <el-dialog title="分配角色" :modelValue="show" width="500px" append-to-body @close="closeDialog"> + <el-form :model="formData" label-width="80px" ref="formRef"> + <el-form-item label="用户名称"> + <el-input v-model="formData.username" :disabled="true" /> + </el-form-item> + <el-form-item label="用户昵称"> + <el-input v-model="formData.nickname" :disabled="true" /> + </el-form-item> + <el-form-item label="角色"> + <el-select v-model="formData.roleIds" multiple placeholder="请选择"> + <el-option + v-for="item in roleOptions" + :key="parseInt(item.id)" + :label="item.name" + :value="parseInt(item.id)" + /> + </el-select> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submit">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </template> + </el-dialog> +</template> + +<script setup lang="ts"> +import { assignUserRoleApi, PermissionAssignUserRoleReqVO } from '@/api/system/permission' + +interface Props { + show: boolean + roleOptions: any[] + formInitValue?: Recordable & Partial<typeof initParams> +} + +const props = withDefaults(defineProps<Props>(), { + show: false, + roleOptions: () => [], + formInitValue: () => ({}) +}) +const emits = defineEmits(['update:show', 'success']) + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +// 表单初始化参数 +const initParams = { + nickname: '', + id: 0, + username: '', + roleIds: [] as number[] +} +const formData = ref<Recordable>({ ...initParams }) +watch( + () => props.formInitValue, + (val) => { + formData.value = { ...val } + }, + { deep: true } +) +/* 弹框按钮操作 */ +// 点击取消 +const cancel = () => { + closeDialog() +} +// 关闭弹窗 +const closeDialog = () => { + emits('update:show', false) +} +// 提交 +const submit = async () => { + const data = ref<PermissionAssignUserRoleReqVO>({ + userId: formData.value.id, + roleIds: formData.value.roleIds + }) + try { + await assignUserRoleApi(data.value) + message.success(t('common.updateSuccess')) + emits('success', true) + closeDialog() + } catch (error) { + console.error(error) + } +} +</script> + +<style></style> \ No newline at end of file diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index 542ae2f0..ce034c4e 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -1,307 +1,325 @@ <template> - <div class="flex"> - <el-card class="w-1/5 user" :gutter="12" shadow="always"> - <template #header> - <div class="card-header"> - <span>部门列表</span> - <XTextButton title="修改部门" @click="handleDeptEdit()" /> - </div> - </template> - <el-input v-model="filterText" placeholder="搜索部门" /> - <el-scrollbar height="650"> - <el-tree - ref="treeRef" - node-key="id" - default-expand-all - :data="deptOptions" - :props="defaultProps" - :highlight-current="true" - :filter-node-method="filterNode" - :expand-on-click-node="false" - @node-click="handleDeptNodeClick" - /> - </el-scrollbar> - </el-card> - <el-card class="w-4/5 user" style="margin-left: 10px" :gutter="12" shadow="hover"> - <template #header> - <div class="card-header"> - <span>{{ tableTitle }}</span> - </div> - </template> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <!-- 操作:新增 --> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['system:user:create']" - @click="handleCreate()" - /> - <!-- 操作:导入用户 --> - <XButton - type="warning" - preIcon="ep:upload" - :title="t('action.import')" - v-hasPermi="['system:user:import']" - @click="importDialogVisible = true" - /> - <!-- 操作:导出用户 --> - <XButton - type="warning" - preIcon="ep:download" - :title="t('action.export')" - v-hasPermi="['system:user:export']" - @click="exportList('用户数据.xls')" - /> - </template> - <template #status_default="{ row }"> - <el-switch - v-model="row.status" - :active-value="0" - :inactive-value="1" - @change="handleStatusChange(row)" - /> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:编辑 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['system:user:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['system:user:update']" - @click="handleDetail(row.id)" - /> - <el-dropdown - class="p-0.5" - v-hasPermi="[ - 'system:user:update-password', - 'system:permission:assign-user-role', - 'system:user:delete' - ]" + <div class="app-container"> + <content-wrap> + <!-- 搜索工作栏 --> + <el-row :gutter="20"> + <!--部门数据--> + <el-col :span="4" :xs="24"> + <div class="head-container"> + <el-input + v-model="deptName" + placeholder="请输入部门名称" + clearable + style="margin-bottom: 20px" + > + <template #prefix> + <Icon icon="ep:search" /> + </template> + </el-input> + </div> + <div class="head-container"> + <el-tree + :data="deptOptions" + :props="defaultProps" + :expand-on-click-node="false" + :filter-node-method="filterNode" + ref="treeRef" + node-key="id" + default-expand-all + highlight-current + @node-click="handleDeptNodeClick" + /> + </div> + </el-col> + <!--用户数据--> + <el-col :span="20" :xs="24"> + <el-form + :model="queryParams" + ref="queryFormRef" + :inline="true" + v-show="showSearch" + label-width="68px" > - <XTextButton :title="t('action.more')" postIcon="ep:arrow-down" /> - <template #dropdown> - <el-dropdown-menu> - <el-dropdown-item> - <!-- 操作:重置密码 --> - <XTextButton - preIcon="ep:key" - title="重置密码" - v-hasPermi="['system:user:update-password']" - @click="handleResetPwd(row)" - /> - </el-dropdown-item> - <el-dropdown-item> - <!-- 操作:分配角色 --> - <XTextButton - preIcon="ep:key" - title="分配角色" - v-hasPermi="['system:permission:assign-user-role']" - @click="handleRole(row)" - /> - </el-dropdown-item> - <el-dropdown-item> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['system:user:delete']" - @click="deleteData(row.id)" - /> - </el-dropdown-item> - </el-dropdown-menu> - </template> - </el-dropdown> - </template> - </XTable> - </el-card> - </div> - <XModal v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(添加 / 修改) --> - <Form - v-if="['create', 'update'].includes(actionType)" - :rules="rules" - :schema="allSchemas.formSchema" - ref="formRef" - > - <template #deptId="form"> - <el-tree-select - node-key="id" - v-model="form['deptId']" - :props="defaultProps" - :data="deptOptions" - check-strictly - /> - </template> - <template #postIds="form"> - <el-select v-model="form['postIds']" multiple :placeholder="t('common.selectText')"> - <el-option - v-for="item in postOptions" - :key="item.id" - :label="item.name" - :value="(item.id as unknown as number)" - /> - </el-select> - </template> - </Form> - <!-- 对话框(详情) --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - > - <template #deptId="{ row }"> - <el-tag>{{ dataFormater(row.deptId) }}</el-tag> - </template> - <template #postIds="{ row }"> - <template v-if="row.postIds !== ''"> - <el-tag v-for="(post, index) in row.postIds" :key="index" index=""> - <template v-for="postObj in postOptions"> - {{ post === postObj.id ? postObj.name : '' }} - </template> - </el-tag> - </template> - <template v-else> </template> - </template> - </Descriptions> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="loading" - @click="submitForm()" - /> - <!-- 按钮:关闭 --> - <XButton :loading="loading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> - <!-- 分配用户角色 --> - <XModal v-model="roleDialogVisible" title="分配角色"> - <el-form :model="userRole" label-width="140px" :inline="true"> - <el-form-item label="用户名称"> - <el-tag>{{ userRole.username }}</el-tag> - </el-form-item> - <el-form-item label="用户昵称"> - <el-tag>{{ userRole.nickname }}</el-tag> - </el-form-item> - <el-form-item label="角色"> - <el-transfer - v-model="userRole.roleIds" - :titles="['角色列表', '已选择']" - :props="{ - key: 'id', - label: 'name' - }" - :data="roleOptions" - /> - </el-form-item> - </el-form> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton type="primary" :title="t('action.save')" :loading="loading" @click="submitRole()" /> - <!-- 按钮:关闭 --> - <XButton :title="t('dialog.close')" @click="roleDialogVisible = false" /> - </template> - </XModal> - <!-- 导入 --> - <XModal v-model="importDialogVisible" :title="importDialogTitle"> - <el-form class="drawer-multiColumn-form" label-width="150px"> - <el-form-item label="模板下载 :"> - <XButton type="primary" prefix="ep:download" title="点击下载" @click="handleImportTemp()" /> - </el-form-item> - <el-form-item label="文件上传 :"> - <el-upload - ref="uploadRef" - :action="updateUrl + '?updateSupport=' + updateSupport" - :headers="uploadHeaders" - :drag="true" - :limit="1" - :multiple="true" - :show-file-list="true" - :disabled="uploadDisabled" - :before-upload="beforeExcelUpload" - :on-exceed="handleExceed" - :on-success="handleFileSuccess" - :on-error="excelUploadError" - :auto-upload="false" - accept="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" - > - <Icon icon="ep:upload-filled" /> - <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> - <template #tip> - <div class="el-upload__tip">请上传 .xls , .xlsx 标准格式文件</div> - </template> - </el-upload> - </el-form-item> - <el-form-item label="是否更新已经存在的用户数据:"> - <el-checkbox v-model="updateSupport" /> - </el-form-item> - </el-form> - <template #footer> - <!-- 按钮:保存 --> - <XButton - type="warning" - preIcon="ep:upload-filled" - :title="t('action.save')" - @click="submitFileForm()" - /> - <!-- 按钮:关闭 --> - <XButton :title="t('dialog.close')" @click="importDialogVisible = false" /> - </template> - </XModal> -</template> -<script setup lang="ts" name="User"> -import type { ElTree, UploadRawFile, UploadInstance } from 'element-plus' -import { handleTree, defaultProps } from '@/utils/tree' -import download from '@/utils/download' -import { CommonStatusEnum } from '@/utils/constants' -import { getAccessToken, getTenantId } from '@/utils/auth' -import type { FormExpose } from '@/components/Form' -import { rules, allSchemas } from './user.data' -import * as UserApi from '@/api/system/user' -import { getSimpleDeptList } from '@/api/system/dept' -import { getSimpleRoleList } from '@/api/system/role' -import { getSimplePostList, PostVO } from '@/api/system/post' -import { - aassignUserRoleApi, - listUserRolesApi, - PermissionAssignUserRoleReqVO -} from '@/api/system/permission' + <el-form-item label="用户名称" prop="username"> + <el-input + v-model="queryParams.username" + placeholder="请输入用户名称" + clearable + style="width: 240px" + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="手机号码" prop="mobile"> + <el-input + v-model="queryParams.mobile" + placeholder="请输入手机号码" + clearable + style="width: 240px" + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select + v-model="queryParams.status" + placeholder="用户状态" + clearable + style="width: 240px" + > + <el-option + v-for="dict in statusDictDatas" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + style="width: 240px" + value-format="YYYY-MM-DD HH:mm:ss" + type="datetimerange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + /> + </el-form-item> + <el-form-item> + <el-button type="primary" @click="handleQuery" + ><Icon icon="ep:search" />搜索</el-button + > + <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button> + </el-form-item> + </el-form> + + <el-row :gutter="10" class="mb-8px"> + <el-col :span="1.5"> + <el-button + type="primary" + plain + size="small" + @click="handleAdd" + v-hasPermi="['system:user:create']" + ><Icon icon="ep:plus" />新增</el-button + > + </el-col> + <el-col :span="1.5"> + <el-button + type="info" + size="small" + @click="handleImport" + v-hasPermi="['system:user:import']" + ><Icon icon="ep:upload" />导入</el-button + > + </el-col> + <el-col :span="1.5"> + <el-button + type="warning" + size="small" + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['system:user:export']" + ><Icon icon="ep:download" />导出</el-button + > + </el-col> + </el-row> + <el-table v-loading="loading" :data="userList"> + <el-table-column + label="用户编号" + align="center" + key="id" + prop="id" + v-if="columns[0].visible" + /> + <el-table-column + label="用户名称" + align="center" + key="username" + prop="username" + v-if="columns[1].visible" + :show-overflow-tooltip="true" + /> + <el-table-column + label="用户昵称" + align="center" + key="nickname" + prop="nickname" + v-if="columns[2].visible" + :show-overflow-tooltip="true" + /> + <el-table-column + label="部门" + align="center" + key="deptName" + prop="dept.name" + v-if="columns[3].visible" + :show-overflow-tooltip="true" + /> + <el-table-column + label="手机号码" + align="center" + key="mobile" + prop="mobile" + v-if="columns[4].visible" + width="120" + /> + <el-table-column label="状态" key="status" v-if="columns[5].visible" align="center"> + <template #default="scope"> + <el-switch + v-model="scope.row.status" + :active-value="0" + :inactive-value="1" + @change="handleStatusChange(scope.row)" + /> + </template> + </el-table-column> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + v-if="columns[6].visible" + width="160" + > + <template #default="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column + label="操作" + align="center" + width="160" + class-name="small-padding fixed-width" + > + <template #default="scope"> + <div class="flex justify-center items-center"> + <el-button + type="primary" + link + @click="handleUpdate(scope.row)" + v-hasPermi="['system:user:update']" + ><Icon icon="ep:edit" />修改</el-button + > + <el-dropdown + @command="(command) => handleCommand(command, scope.$index, scope.row)" + v-hasPermi="[ + 'system:user:delete', + 'system:user:update-password', + 'system:permission:assign-user-role' + ]" + > + <el-button type="primary" link><Icon icon="ep:d-arrow-right" />更多</el-button> + <template #dropdown> + <el-dropdown-menu> + <!-- div包住避免控制台报错:Runtime directive used on component with non-element root node --> + <div v-if="scope.row.id !== 1" v-hasPermi="['system:user:delete']"> + <el-dropdown-item command="handleDelete" type="text" + ><Icon icon="ep:delete" />删除</el-dropdown-item + > + </div> + <div v-hasPermi="['system:user:update-password']"> + <el-dropdown-item command="handleResetPwd" type="text" + ><Icon icon="ep:key" />重置密码</el-dropdown-item + ></div + > + <div v-hasPermi="['system:permission:assign-user-role']"> + <el-dropdown-item command="handleRole" type="text" + ><Icon icon="ep:circle-check" />分配角色</el-dropdown-item + ></div + > + </el-dropdown-menu> + </template> + </el-dropdown> + </div> + </template> + </el-table-column> + </el-table> + <pagination + v-show="total > 0" + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </el-col> + </el-row> + </content-wrap> + <!-- 添加或修改用户对话框 --> + <AddForm + ref="addEditFormRef" + v-model="showAddDialog" + :dept-options="deptOptions" + :post-options="postOptions" + :form-init-value="addFormInitValue" + @success="getList" + /> + <!-- 用户导入对话框 --> + <ImportForm v-model="importDialogVisible" @success="getList" /> + <!-- 分配角色 --> + <RoleForm + ref="roleFormRef" + v-model:show="roleDialogVisible" + :role-options="roleOptions" + :form-init-value="userRole" + @success="getList" + /> + </div> +</template> + +<script setup lang="ts" name="User"> +import type { ElTree } from 'element-plus' +import { handleTree, defaultProps } from '@/utils/tree' +// 原vue3版本api方法都是Api结尾觉得见名知义,个人觉得这个可以形成规范 +import { getSimpleDeptList as getSimpleDeptListApi } from '@/api/system/dept' +import { getSimplePostList as getSimplePostListApi, PostVO } from '@/api/system/post' +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { + deleteUserApi, + exportUserApi, + resetUserPwdApi, + updateUserStatusApi, + UserVO +} from '@/api/system/user' +import { parseTime } from './utils' +import AddForm from './AddForm.vue' +import ImportForm from './ImportForm.vue' +import RoleForm from './RoleForm.vue' +import { getUserApi, getUserPageApi } from '@/api/system/user' +import { getSimpleRoleList as getSimpleRoleListApi } from '@/api/system/role' +import { listUserRolesApi } from '@/api/system/permission' +import { CommonStatusEnum } from '@/utils/constants' +import download from '@/utils/download' -const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 +const { t } = useI18n() // 国际化 const queryParams = reactive({ - deptId: null -}) -// ========== 列表相关 ========== -const tableTitle = ref('用户列表') -// 列表相关的变量 -const [registerTable, { reload, deleteData, exportList }] = useXTable({ - allSchemas: allSchemas, - params: queryParams, - getListApi: UserApi.getUserPageApi, - deleteApi: UserApi.deleteUserApi, - exportListApi: UserApi.exportUserApi + pageNo: 1, + pageSize: 10, + username: undefined, + mobile: undefined, + status: undefined, + deptId: undefined, + createTime: [] }) +const showSearch = ref(true) +const showAddDialog = ref(false) + +// 数据字典- +const statusDictDatas = getDictOptions(DICT_TYPE.COMMON_STATUS) + // ========== 创建部门树结构 ========== -const filterText = ref('') +const deptName = ref('') +watch( + () => deptName.value, + (val) => { + treeRef.value?.filter(val) + } +) const deptOptions = ref<Tree[]>([]) // 树形结构 const treeRef = ref<InstanceType<typeof ElTree>>() const getTree = async () => { - const res = await getSimpleDeptList() + const res = await getSimpleDeptListApi() + deptOptions.value = [] deptOptions.value.push(...handleTree(res)) } const filterNode = (value: string, data: Tree) => { @@ -310,156 +328,171 @@ const filterNode = (value: string, data: Tree) => { } const handleDeptNodeClick = async (row: { [key: string]: any }) => { queryParams.deptId = row.id - await reload() + getList() } -const { push } = useRouter() -const handleDeptEdit = () => { - push('/system/dept') -} -watch(filterText, (val) => { - treeRef.value!.filter(val) -}) -// ========== CRUD 相关 ========== -const loading = ref(false) // 遮罩层 -const actionType = ref('') // 操作按钮的类型 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const formRef = ref<FormExpose>() // 表单 Ref -const postOptions = ref<PostVO[]>([]) //岗位列表 // 获取岗位列表 +const postOptions = ref<PostVO[]>([]) //岗位列表 const getPostOptions = async () => { - const res = await getSimplePostList() + const res = await getSimplePostListApi() postOptions.value.push(...res) } -const dataFormater = (val) => { - return deptFormater(deptOptions.value, val) -} -//部门回显 -const deptFormater = (ary, val: any) => { - var o = '' - if (ary && val) { - for (const v of ary) { - if (v.id == val) { - o = v.name - if (o) return o - } else if (v.children?.length) { - o = deptFormater(v.children, val) - if (o) return o - } - } - return o - } else { - return val - } -} - -// 设置标题 -const setDialogTile = async (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true -} - -// 新增操作 -const handleCreate = async () => { - setDialogTile('create') - // 重置表单 - await nextTick() - if (allSchemas.formSchema[0].field !== 'username') { - unref(formRef)?.addSchema( - { - field: 'username', - label: '用户账号', - component: 'Input' - }, - 0 - ) - unref(formRef)?.addSchema( - { - field: 'password', - label: '用户密码', - component: 'InputPassword' - }, - 1 - ) - } -} - -// 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - await nextTick() - unref(formRef)?.delSchema('username') - unref(formRef)?.delSchema('password') - // 设置数据 - const res = await UserApi.getUserApi(rowId) - unref(formRef)?.setValues(res) -} -const detailData = ref() - -// 详情操作 -const handleDetail = async (rowId: number) => { - // 设置数据 - const res = await UserApi.getUserApi(rowId) - detailData.value = res - await setDialogTile('detail') -} - -// 提交按钮 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - // 提交请求 - try { - const data = unref(formRef)?.formModel as UserApi.UserVO - if (actionType.value === 'create') { - loading.value = true - await UserApi.createUserApi(data) - message.success(t('common.createSuccess')) - } else { - loading.value = true - await UserApi.updateUserApi(data) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false - } finally { - // unref(formRef)?.setSchema(allSchemas.formSchema) - // 刷新列表 - await reload() - loading.value = false - } - } +// 用户列表 +const userList = ref<UserVO[]>([]) +const loading = ref(false) +const total = ref(0) +const columns = ref([ + { key: 0, label: `用户编号`, visible: true }, + { key: 1, label: `用户名称`, visible: true }, + { key: 2, label: `用户昵称`, visible: true }, + { key: 3, label: `部门`, visible: true }, + { key: 4, label: `手机号码`, visible: true }, + { key: 5, label: `状态`, visible: true }, + { key: 6, label: `创建时间`, visible: true } +]) +/* 查询列表 */ +const getList = () => { + loading.value = true + getUserPageApi(queryParams).then((response) => { + userList.value = response.list + total.value = response.total + loading.value = false }) } -// 改变用户状态操作 -const handleStatusChange = async (row: UserApi.UserVO) => { - const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const queryFormRef = ref() +const resetQuery = () => { + queryFormRef.value?.resetFields() + handleQuery() +} + +// 添加或编辑 +const addEditFormRef = ref() +// 添加用户 +const handleAdd = () => { + addEditFormRef?.value.resetForm() + // 获得下拉数据 + getTree() + // 打开表单,并设置初始化 + showAddDialog.value = true +} + +// 用户导入 +const handleImport = () => { + importDialogVisible.value = true +} + +// 用户导出 +const exportLoading = ref(false) +const handleExport = () => { + message + .confirm('是否确认导出所有用户数据项?') + .then(() => { + // 处理查询参数 + let params = { ...queryParams } + params.pageNo = 1 + params.pageSize = 99999 + exportLoading.value = true + return exportUserApi(params) + }) + .then((response) => { + download.excel(response, '用户数据.xls') + }) + .catch(() => {}) + .finally(() => { + exportLoading.value = false + }) +} + +// 操作分发 +const handleCommand = (command: string, index: number, row: UserVO) => { + console.log(index) + switch (command) { + case 'handleUpdate': + handleUpdate(row) //修改客户信息 + break + case 'handleDelete': + handleDelete(row) //红号变更 + break + case 'handleResetPwd': + handleResetPwd(row) + break + case 'handleRole': + handleRole(row) + break + default: + break + } +} + +// 用户状态修改 +const handleStatusChange = (row: UserVO) => { + let text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' message .confirm('确认要"' + text + '""' + row.username + '"用户吗?', t('common.reminder')) - .then(async () => { + .then(function () { row.status = row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.ENABLE : CommonStatusEnum.DISABLE - await UserApi.updateUserStatusApi(row.id, row.status) + return updateUserStatusApi(row.id, row.status) + }) + .then(() => { message.success(text + '成功') // 刷新列表 - await reload() + getList() }) .catch(() => { row.status = row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE }) } -// 重置密码 -const handleResetPwd = (row: UserApi.UserVO) => { - message.prompt('请输入"' + row.username + '"的新密码', t('common.reminder')).then(({ value }) => { - UserApi.resetUserPwdApi(row.id, value).then(() => { - message.success('修改成功,新密码是:' + value) - }) + +// 具体数据单行操作 +const addFormInitValue = ref<Recordable>({}) +/** 修改按钮操作 */ +const handleUpdate = (row: UserVO) => { + addEditFormRef.value?.resetForm() + getTree() + const id = row.id + getUserApi(id).then((response) => { + addFormInitValue.value = response + showAddDialog.value = true }) } + +// 删除用户 +const handleDelete = (row: UserVO) => { + const ids = row.id + message + .confirm('是否确认删除用户编号为"' + ids + '"的数据项?') + .then(() => { + return deleteUserApi(ids) + }) + .then(() => { + getList() + message.success('删除成功') + }) + .catch(() => {}) +} + +// 重置密码 +const handleResetPwd = (row: UserVO) => { + message.prompt('请输入"' + row.username + '"的新密码', t('common.reminder')).then(({ value }) => { + resetUserPwdApi(row.id, value) + .then(() => { + message.success('修改成功,新密码是:' + value) + }) + .catch((e) => { + console.error(e) + }) + }) +} + // 分配角色 const roleDialogVisible = ref(false) const roleOptions = ref() @@ -469,108 +502,30 @@ const userRole = reactive({ nickname: '', roleIds: [] }) -const handleRole = async (row: UserApi.UserVO) => { +const handleRole = async (row: UserVO) => { + addEditFormRef.value?.resetForm() userRole.id = row.id userRole.username = row.username userRole.nickname = row.nickname - // 获得角色拥有的权限集合 + + // 获得角色列表 + const roleOpt = await getSimpleRoleListApi() + roleOptions.value = [...roleOpt] + + // 获得角色拥有的菜单集合 const roles = await listUserRolesApi(row.id) userRole.roleIds = roles - // 获取角色列表 - const roleOpt = await getSimpleRoleList() - roleOptions.value = roleOpt + roleDialogVisible.value = true } -// 提交 -const submitRole = async () => { - const data = ref<PermissionAssignUserRoleReqVO>({ - userId: userRole.id, - roleIds: userRole.roleIds - }) - await aassignUserRoleApi(data.value) - message.success(t('common.updateSuccess')) - roleDialogVisible.value = false -} -// ========== 导入相关 ========== -// TODO @星语:这个要不要把导入用户,封装成一个小组件?可选哈 + +/* 用户导入 */ const importDialogVisible = ref(false) -const uploadDisabled = ref(false) -const importDialogTitle = ref('用户导入') -const updateSupport = ref(0) -let updateUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import' -const uploadHeaders = ref() -// 下载导入模版 -const handleImportTemp = async () => { - const res = await UserApi.importUserTemplateApi() - download.excel(res, '用户导入模版.xls') -} -// 文件上传之前判断 -const beforeExcelUpload = (file: UploadRawFile) => { - const isExcel = - file.type === 'application/vnd.ms-excel' || - file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' - const isLt5M = file.size / 1024 / 1024 < 5 - if (!isExcel) message.error('上传文件只能是 xls / xlsx 格式!') - if (!isLt5M) message.error('上传文件大小不能超过 5MB!') - return isExcel && isLt5M -} -// 文件上传 -const uploadRef = ref<UploadInstance>() -const submitFileForm = () => { - uploadHeaders.value = { - Authorization: 'Bearer ' + getAccessToken(), - 'tenant-id': getTenantId() - } - uploadDisabled.value = true - uploadRef.value!.submit() -} -// 文件上传成功 -const handleFileSuccess = async (response: any): Promise<void> => { - if (response.code !== 0) { - message.error(response.msg) - return - } - importDialogVisible.value = false - uploadDisabled.value = false - const data = response.data - let text = '上传成功数量:' + data.createUsernames.length + ';' - for (let username of data.createUsernames) { - text += '< ' + username + ' >' - } - text += '更新成功数量:' + data.updateUsernames.length + ';' - for (const username of data.updateUsernames) { - text += '< ' + username + ' >' - } - text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';' - for (const username in data.failureUsernames) { - text += '< ' + username + ': ' + data.failureUsernames[username] + ' >' - } - message.alert(text) - await reload() -} -// 文件数超出提示 -const handleExceed = (): void => { - message.error('最多只能上传一个文件!') -} -// 上传错误提示 -const excelUploadError = (): void => { - message.error('导入数据失败,请您重新上传!') -} + // ========== 初始化 ========== onMounted(async () => { + getList() await getPostOptions() await getTree() }) </script> - -<style scoped> -.user { - height: 780px; - max-height: 800px; -} -.card-header { - display: flex; - justify-content: space-between; - align-items: center; -} -</style> diff --git a/src/views/system/user/utils.ts b/src/views/system/user/utils.ts new file mode 100644 index 00000000..3eb97aba --- /dev/null +++ b/src/views/system/user/utils.ts @@ -0,0 +1,44 @@ +export const parseTime = (time) => { + if (!time) { + return null + } + const format = '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'object') { + date = time + } else { + if (typeof time === 'string' && /^[0-9]+$/.test(time)) { + time = parseInt(time) + } else if (typeof time === 'string') { + time = time + .replace(new RegExp(/-/gm), '/') + .replace('T', ' ') + .replace(new RegExp(/\.[\d]{3}/gm), '') + } + if (typeof time === 'number' && time.toString().length === 10) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { + let value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { + return ['日', '一', '二', '三', '四', '五', '六'][value] + } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return time_str +} \ No newline at end of file From d9e79250e2de6afe1c04bb26893510b1f91b7b5a Mon Sep 17 00:00:00 2001 From: fessor <352475718@qq.com> Date: Mon, 27 Mar 2023 02:12:18 +0000 Subject: [PATCH 119/184] =?UTF-8?q?fix:=20=E6=A0=BC=E5=BC=8F=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: fessor <352475718@qq.com> --- src/views/system/user/AddForm.vue | 2 +- src/views/system/user/ImportForm.vue | 2 +- src/views/system/user/RoleForm.vue | 2 +- src/views/system/user/utils.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/views/system/user/AddForm.vue b/src/views/system/user/AddForm.vue index 12dc1737..9a4d6029 100644 --- a/src/views/system/user/AddForm.vue +++ b/src/views/system/user/AddForm.vue @@ -220,4 +220,4 @@ const cancel = () => { defineExpose({ resetForm }) -</script> \ No newline at end of file +</script> diff --git a/src/views/system/user/ImportForm.vue b/src/views/system/user/ImportForm.vue index b029730a..4bfa4631 100644 --- a/src/views/system/user/ImportForm.vue +++ b/src/views/system/user/ImportForm.vue @@ -150,4 +150,4 @@ const closeDialog = () => { const submitFileForm = () => { uploadRef.value?.submit() } -</script> \ No newline at end of file +</script> diff --git a/src/views/system/user/RoleForm.vue b/src/views/system/user/RoleForm.vue index 694e5eeb..cb5603fe 100644 --- a/src/views/system/user/RoleForm.vue +++ b/src/views/system/user/RoleForm.vue @@ -87,4 +87,4 @@ const submit = async () => { } </script> -<style></style> \ No newline at end of file +<style></style> diff --git a/src/views/system/user/utils.ts b/src/views/system/user/utils.ts index 3eb97aba..6473c2c9 100644 --- a/src/views/system/user/utils.ts +++ b/src/views/system/user/utils.ts @@ -41,4 +41,4 @@ export const parseTime = (time) => { return value || 0 }) return time_str -} \ No newline at end of file +} From 3dbcbf4cf06e0b06a2599488b771cf4fa605e53b Mon Sep 17 00:00:00 2001 From: fessor <352475718@qq.com> Date: Mon, 27 Mar 2023 02:18:41 +0000 Subject: [PATCH 120/184] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9api=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E4=B8=8E=E5=91=BD=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: fessor <352475718@qq.com> --- src/api/system/permission/index.ts | 2 +- src/api/system/user/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/system/permission/index.ts b/src/api/system/permission/index.ts index aa355dfc..c529f884 100644 --- a/src/api/system/permission/index.ts +++ b/src/api/system/permission/index.ts @@ -37,6 +37,6 @@ export const listUserRolesApi = async (userId: number) => { } // 赋予用户角色 -export const aassignUserRoleApi = async (data: PermissionAssignUserRoleReqVO) => { +export const assignUserRoleApi = async (data: PermissionAssignUserRoleReqVO) => { return await request.post({ url: '/system/permission/assign-user-role', data }) } diff --git a/src/api/system/user/index.ts b/src/api/system/user/index.ts index 058b320a..e488c0d7 100644 --- a/src/api/system/user/index.ts +++ b/src/api/system/user/index.ts @@ -43,12 +43,12 @@ export const getUserApi = (id: number) => { } // 新增用户 -export const createUserApi = (data: UserVO) => { +export const createUserApi = (data: UserVO | Recordable) => { return request.post({ url: '/system/user/create', data }) } // 修改用户 -export const updateUserApi = (data: UserVO) => { +export const updateUserApi = (data: UserVO | Recordable) => { return request.put({ url: '/system/user/update', data }) } From 6e321c49468d55512d11a4861cd2d27a12799272 Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Mon, 27 Mar 2023 10:32:17 +0800 Subject: [PATCH 121/184] =?UTF-8?q?update:=20=E5=8E=BB=E9=99=A4=E6=B2=A1?= =?UTF-8?q?=E5=BF=85=E8=A6=81=E7=9A=84declare?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/global.d.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/types/global.d.ts b/types/global.d.ts index 3685ffbd..5e292687 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -1,29 +1,29 @@ export {} declare global { - declare interface Fn<T = any> { + interface Fn<T = any> { (...arg: T[]): T } - declare type Nullable<T> = T | null + type Nullable<T> = T | null - declare type ElRef<T extends HTMLElement = HTMLDivElement> = Nullable<T> + type ElRef<T extends HTMLElement = HTMLDivElement> = Nullable<T> - declare type Recordable<T = any, K = string> = Record<K extends null | undefined ? string : K, T> + type Recordable<T = any, K = string> = Record<K extends null | undefined ? string : K, T> - declare type ComponentRef<T> = InstanceType<T> + type ComponentRef<T> = InstanceType<T> - declare type LocaleType = 'zh-CN' | 'en' + type LocaleType = 'zh-CN' | 'en' - declare type AxiosHeaders = + type AxiosHeaders = | 'application/json' | 'application/x-www-form-urlencoded' | 'multipart/form-data' - declare type AxiosMethod = 'get' | 'post' | 'delete' | 'put' | 'GET' | 'POST' | 'DELETE' | 'PUT' + type AxiosMethod = 'get' | 'post' | 'delete' | 'put' | 'GET' | 'POST' | 'DELETE' | 'PUT' - declare type AxiosResponseType = 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream' + type AxiosResponseType = 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream' - declare interface AxiosConfig { + interface AxiosConfig { params?: any data?: any url?: string @@ -32,17 +32,17 @@ declare global { responseType?: AxiosResponseType } - declare interface IResponse<T = any> { + interface IResponse<T = any> { code: string data: T extends any ? T : T & any } - declare interface PageParam { + interface PageParam { pageSize?: number pageNo?: number } - declare interface Tree { + interface Tree { id: number name: string children?: Tree[] | any[] From c105f314f5be1a9c988c67d2a10851a8382916dd Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Mon, 27 Mar 2023 10:34:41 +0800 Subject: [PATCH 122/184] =?UTF-8?q?update:=20=E6=B7=BB=E5=8A=A0=E7=9F=AD?= =?UTF-8?q?=E4=BF=A1=E6=B8=A0=E9=81=93=E5=88=97=E8=A1=A8=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=EF=BC=8C=E8=A7=A3=E5=86=B3=E6=89=BE=E4=B8=8D=E5=88=B0=E5=B1=9E?= =?UTF-8?q?=E6=80=A7signature?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/sms/template/index.vue | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/views/system/sms/template/index.vue b/src/views/system/sms/template/index.vue index f43b8f62..906436a5 100644 --- a/src/views/system/sms/template/index.vue +++ b/src/views/system/sms/template/index.vue @@ -166,12 +166,7 @@ width="180" :formatter="dateFormatter" /> - <el-table-column - label="操作" - align="center" - width="210" - fixed="right" - > + <el-table-column label="操作" align="center" width="210" fixed="right"> <template #default="scope"> <el-button link @@ -241,7 +236,7 @@ const queryParams = reactive({ createTime: [] }) const exportLoading = ref(false) // 导出的加载中 -const channelList = ref([]) // 短信渠道列表 +const channelList = ref<SmsChannelApi.SmsChannelVO[]>([]) // 短信渠道列表 /** 查询列表 */ const getList = async () => { From e87df6439f53f628b938145fa3e9531c16b6bc0d Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Mon, 27 Mar 2023 10:35:49 +0800 Subject: [PATCH 123/184] =?UTF-8?q?update:=20=E5=8E=BB=E9=99=A4=E6=B2=A1?= =?UTF-8?q?=E5=BF=85=E8=A6=81=E7=9A=84declare?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/router.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/router.d.ts b/types/router.d.ts index 10ba0c15..9b08b805 100644 --- a/types/router.d.ts +++ b/types/router.d.ts @@ -54,7 +54,7 @@ type Component<T = any> = | (() => Promise<T>) declare global { - declare interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> { + interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> { name: string meta: RouteMeta component?: Component | string @@ -64,7 +64,7 @@ declare global { keepAlive?: boolean } - declare interface AppCustomRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> { + interface AppCustomRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> { icon: any name: string meta: RouteMeta From 2c50854be1345364d7b7e172554017fc14846b55 Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Mon, 27 Mar 2023 10:37:11 +0800 Subject: [PATCH 124/184] =?UTF-8?q?add:=20=E6=8A=8AIcon=E3=80=81DictTag?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=88=B0=E5=85=A8=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/components.d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/types/components.d.ts b/types/components.d.ts index 85db5663..9d0ba09a 100644 --- a/types/components.d.ts +++ b/types/components.d.ts @@ -1,6 +1,7 @@ declare module 'vue' { export interface GlobalComponents { - Icon: typeof import('../components/Icon/src/Icon.vue')['default'] + Icon: typeof import('@/components/Icon')['Icon'] + DictTag: typeof import('@/components/DictTag')['DictTag'] } } From e08dcc9d8de68d7b3cfe5a682b96ed1c69b32325 Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Mon, 27 Mar 2023 11:37:58 +0800 Subject: [PATCH 125/184] =?UTF-8?q?update:=20=E9=87=8D=E6=9E=84=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E7=AE=A1=E7=90=86=E9=83=A8=E9=97=A8=E5=9B=9E=E6=98=BE?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/user/index.vue | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index 542ae2f0..2bc1893b 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -332,26 +332,24 @@ const getPostOptions = async () => { const res = await getSimplePostList() postOptions.value.push(...res) } -const dataFormater = (val) => { - return deptFormater(deptOptions.value, val) -} +const dataFormater = computed(() => (deptId: number) => deptFormater(deptOptions.value, deptId)) + //部门回显 -const deptFormater = (ary, val: any) => { - var o = '' - if (ary && val) { - for (const v of ary) { - if (v.id == val) { - o = v.name - if (o) return o - } else if (v.children?.length) { - o = deptFormater(v.children, val) - if (o) return o +const deptFormater = (arr: Tree[], deptId: number) => { + let deptName = '' + if (arr && deptId) { + for (const item of arr) { + if (item.id === deptId) { + deptName = item.name + break + } + if (item.children) { + deptName = deptFormater(item.children, deptId) ?? '' + break } } - return o - } else { - return val } + return deptName } // 设置标题 From 581b301eed5382d6508369d7e80e41ca5c5c817d Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Mon, 27 Mar 2023 12:02:43 +0800 Subject: [PATCH 126/184] =?UTF-8?q?update:=20=E4=BF=AE=E5=A4=8D=E8=A7=92?= =?UTF-8?q?=E8=89=B2=E7=AE=A1=E7=90=86=E7=B3=BB=E5=88=97=E9=97=AE=E9=A2=98?= =?UTF-8?q?=EF=BC=8C=E8=A7=A3=E5=86=B3=E9=A1=B5=E9=9D=A2=E6=89=93=E4=B8=8D?= =?UTF-8?q?=E5=BC=80=E6=8C=89=E9=92=AE=E5=8A=9F=E8=83=BD=E5=A4=B1=E6=95=88?= =?UTF-8?q?=E7=AD=89=E9=97=AE=E9=A2=98=EF=BC=81=EF=BC=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/role/index.ts | 19 +++++++++++++------ src/types/auto-components.d.ts | 2 -- src/views/system/role/MenuPermissionForm.vue | 8 ++++---- src/views/system/role/RoleForm.vue | 4 ++-- src/views/system/role/index.vue | 2 +- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/api/system/role/index.ts b/src/api/system/role/index.ts index 9692548a..7cb85951 100644 --- a/src/api/system/role/index.ts +++ b/src/api/system/role/index.ts @@ -23,7 +23,7 @@ export interface UpdateStatusReqVO { } // 查询角色列表 -export const getRolePageApi = async (params: RolePageReqVO) => { +export const getRolePage = async (params: RolePageReqVO) => { return await request.get({ url: '/system/role/page', params }) } @@ -33,26 +33,33 @@ export const getSimpleRoleList = async (): Promise<RoleVO[]> => { } // 查询角色详情 -export const getRoleApi = async (id: number) => { +export const getRole = async (id: number) => { return await request.get({ url: '/system/role/get?id=' + id }) } // 新增角色 -export const createRoleApi = async (data: RoleVO) => { +export const createRole = async (data: RoleVO) => { return await request.post({ url: '/system/role/create', data }) } // 修改角色 -export const updateRoleApi = async (data: RoleVO) => { +export const updateRole = async (data: RoleVO) => { return await request.put({ url: '/system/role/update', data }) } // 修改角色状态 -export const updateRoleStatusApi = async (data: UpdateStatusReqVO) => { +export const updateRoleStatus = async (data: UpdateStatusReqVO) => { return await request.put({ url: '/system/role/update-status', data }) } // 删除角色 -export const deleteRoleApi = async (id: number) => { +export const deleteRole = async (id: number) => { return await request.delete({ url: '/system/role/delete?id=' + id }) } +// 导出角色 +export const exportRole = (params) => { + return request.download({ + url: '/system/role/export-excel', + params + }) +} diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index 04eb4d9e..f6f6a5f9 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -52,7 +52,6 @@ declare module '@vue/runtime-core' { ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] - ElImage: typeof import('element-plus/es')['ElImage'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] @@ -100,7 +99,6 @@ declare module '@vue/runtime-core' { ScriptTask: typeof import('./../components/bpmnProcessDesigner/package/penal/task/task-components/ScriptTask.vue')['default'] Search: typeof import('./../components/Search/src/Search.vue')['default'] SignalAndMessage: typeof import('./../components/bpmnProcessDesigner/package/penal/signal-message/SignalAndMessage.vue')['default'] - Src: typeof import('./../components/RightToolbar/src/index.vue')['default'] Sticky: typeof import('./../components/Sticky/src/Sticky.vue')['default'] Table: typeof import('./../components/Table/src/Table.vue')['default'] Tooltip: typeof import('./../components/Tooltip/src/Tooltip.vue')['default'] diff --git a/src/views/system/role/MenuPermissionForm.vue b/src/views/system/role/MenuPermissionForm.vue index 4628ef24..650fb659 100644 --- a/src/views/system/role/MenuPermissionForm.vue +++ b/src/views/system/role/MenuPermissionForm.vue @@ -96,8 +96,8 @@ import type { FormExpose } from '@/components/Form' import { handleTree, defaultProps } from '@/utils/tree' import { SystemDataScopeEnum } from '@/utils/constants' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' -import { listSimpleMenusApi } from '@/api/system/menu' -import { listSimpleDeptApi } from '@/api/system/dept' +import * as MenuApi from '@/api/system/menu' +import * as DeptApi from '@/api/system/dept' import * as PermissionApi from '@/api/system/permission' // ========== CRUD 相关 ========== const actionLoading = ref(false) // 遮罩层 @@ -131,7 +131,7 @@ const openModal = async (type: string, row: RoleApi.RoleVO) => { actionScopeType.value = type dialogScopeVisible.value = true if (type === 'menu') { - const menuRes = await listSimpleMenusApi() + const menuRes = await MenuApi.getSimpleMenusList() treeOptions.value = handleTree(menuRes) const role = await PermissionApi.listRoleMenusApi(row.id) if (role) { @@ -140,7 +140,7 @@ const openModal = async (type: string, row: RoleApi.RoleVO) => { }) } } else if (type === 'data') { - const deptRes = await listSimpleDeptApi() + const deptRes = await DeptApi.getSimpleDeptList() treeOptions.value = handleTree(deptRes) const role = await RoleApi.getRole(row.id) dataScopeForm.dataScope = role.dataScope diff --git a/src/views/system/role/RoleForm.vue b/src/views/system/role/RoleForm.vue index 0fdb130b..a72eba19 100644 --- a/src/views/system/role/RoleForm.vue +++ b/src/views/system/role/RoleForm.vue @@ -44,12 +44,11 @@ <script setup lang="ts"> import { getDictOptions } from '@/utils/dict' import { CommonStatusEnum } from '@/utils/constants' -import type { FormExpose } from '@/components/Form' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import * as RoleApi from '@/api/system/role' // ========== CRUD 相关 ========== const dialogTitle = ref('edit') // 弹出层标题 -const formRef = ref<FormExpose>() // 表单 Ref +const formRef = ref() // 表单 Ref const { t } = useI18n() // 国际化 const dataScopeDictDatas = ref() const message = useMessage() // 消息弹窗 @@ -97,6 +96,7 @@ const resetForm = () => { formData.value = { id: undefined, name: '', + type: '', code: '', sort: undefined, status: CommonStatusEnum.ENABLE, diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue index 12ef7845..68f2008a 100644 --- a/src/views/system/role/index.vue +++ b/src/views/system/role/index.vue @@ -228,7 +228,7 @@ const handleExport = async () => { await message.exportConfirm() // 发起导出 exportLoading.value = true - const data = await RoleApi.exportPostApi(queryParams) + const data = await RoleApi.exportRole(queryParams) download.excel(data, '角色列表.xls') } catch { } finally { From c462bbace536a1ac37076381ff995075b008ff12 Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Mon, 27 Mar 2023 12:11:40 +0800 Subject: [PATCH 127/184] =?UTF-8?q?update=EF=BC=9A=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E8=A7=A3=E5=86=B3ts=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/sms/template/SmsTemplateForm.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/system/sms/template/SmsTemplateForm.vue b/src/views/system/sms/template/SmsTemplateForm.vue index e6bdce6c..03684215 100644 --- a/src/views/system/sms/template/SmsTemplateForm.vue +++ b/src/views/system/sms/template/SmsTemplateForm.vue @@ -44,7 +44,7 @@ <el-radio v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" - :label="parseInt(dict.value)" + :label="parseInt(dict.value as string)" > {{ dict.label }} </el-radio> @@ -96,7 +96,7 @@ const formRules = reactive({ channelId: [{ required: true, message: '短信渠道编号不能为空', trigger: 'change' }] }) const formRef = ref() // 表单 Ref -const channelList = ref([]) // 短信渠道列表 +const channelList = ref<SmsChannelApi.SmsChannelVO[]>([]) // 短信渠道列表 const open = async (type: string, id?: number) => { modelVisible.value = true From 1a4311309dc4d3e1ba1a1adc4ca0e38aa840fd20 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Mon, 27 Mar 2023 12:29:34 +0800 Subject: [PATCH 128/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E8=A7=92=E8=89=B2=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/role/index.ts | 9 +----- src/types/auto-components.d.ts | 1 + src/views/system/notice/form.vue | 1 - src/views/system/role/RoleForm.vue | 49 +++++++--------------------- src/views/system/role/index.vue | 51 +++++++++++++----------------- 5 files changed, 36 insertions(+), 75 deletions(-) diff --git a/src/api/system/role/index.ts b/src/api/system/role/index.ts index 7cb85951..902d5ca6 100644 --- a/src/api/system/role/index.ts +++ b/src/api/system/role/index.ts @@ -10,20 +10,13 @@ export interface RoleVO { createTime: Date } -export interface RolePageReqVO extends PageParam { - name?: string - code?: string - status?: number - createTime?: Date[] -} - export interface UpdateStatusReqVO { id: number status: number } // 查询角色列表 -export const getRolePage = async (params: RolePageReqVO) => { +export const getRolePage = async (params: PageParam) => { return await request.get({ url: '/system/role/page', params }) } diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index f6f6a5f9..cb8ff559 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -52,6 +52,7 @@ declare module '@vue/runtime-core' { ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] + ElImage: typeof import('element-plus/es')['ElImage'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] diff --git a/src/views/system/notice/form.vue b/src/views/system/notice/form.vue index 141dd131..87e75623 100644 --- a/src/views/system/notice/form.vue +++ b/src/views/system/notice/form.vue @@ -46,7 +46,6 @@ <script setup lang="ts"> import { DICT_TYPE, getDictOptions } from '@/utils/dict' import * as NoticeApi from '@/api/system/notice' - const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 diff --git a/src/views/system/role/RoleForm.vue b/src/views/system/role/RoleForm.vue index a72eba19..e6444a03 100644 --- a/src/views/system/role/RoleForm.vue +++ b/src/views/system/role/RoleForm.vue @@ -1,5 +1,5 @@ <template> - <Dialog :title="dialogTitle" v-model="modelVisible" width="800"> + <Dialog :title="modelTitle" v-model="modelVisible"> <el-form ref="formRef" :model="formData" @@ -10,9 +10,6 @@ <el-form-item label="角色名称" prop="name"> <el-input v-model="formData.name" placeholder="请输入角色名称" /> </el-form-item> - <el-form-item label="角色类型" prop="type"> - <el-input :model-value="formData.type" placeholder="请输入角色类型" height="150px" /> - </el-form-item> <el-form-item label="角色标识" prop="code"> <el-input :model-value="formData.code" placeholder="请输入角色标识" height="150px" /> </el-form-item> @@ -22,10 +19,10 @@ <el-form-item label="状态" prop="status"> <el-select v-model="formData.status" placeholder="请选择状态" clearable> <el-option - v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" - :key="parseInt(dict.value)" + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" :label="dict.label" - :value="parseInt(dict.value)" + :value="dict.value" /> </el-select> </el-form-item> @@ -34,34 +31,25 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> <script setup lang="ts"> -import { getDictOptions } from '@/utils/dict' -import { CommonStatusEnum } from '@/utils/constants' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { CommonStatusEnum } from '@/utils/constants' import * as RoleApi from '@/api/system/role' -// ========== CRUD 相关 ========== -const dialogTitle = ref('edit') // 弹出层标题 -const formRef = ref() // 表单 Ref const { t } = useI18n() // 国际化 -const dataScopeDictDatas = ref() const message = useMessage() // 消息弹窗 const modelVisible = ref(false) // 弹窗的是否展示 const modelTitle = ref('') // 弹窗的标题 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formType = ref('') // 表单的类型:create - 新增;update - 修改 - const formData = ref({ id: undefined, name: '', - type: '', code: '', sort: undefined, status: CommonStatusEnum.ENABLE, @@ -74,9 +62,10 @@ const formRules = reactive({ status: [{ required: true, message: '岗位状态不能为空', trigger: 'change' }], remark: [{ required: false, message: '岗位内容不能为空', trigger: 'blur' }] }) +const formRef = ref() // 表单 Ref /** 打开弹窗 */ -const openModal = async (type: string, id?: number) => { +const open = async (type: string, id?: number) => { modelVisible.value = true modelTitle.value = t('action.' + type) formType.value = type @@ -91,12 +80,12 @@ const openModal = async (type: string, id?: number) => { } } } + /** 重置表单 */ const resetForm = () => { formData.value = { id: undefined, name: '', - type: '', code: '', sort: undefined, status: CommonStatusEnum.ENABLE, @@ -104,7 +93,8 @@ const resetForm = () => { } formRef.value?.resetFields() } -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + /** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 const submitForm = async () => { @@ -130,19 +120,4 @@ const submitForm = async () => { formLoading.value = false } } - -const init = () => { - dataScopeDictDatas.value = getIntDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE) -} -// ========== 初始化 ========== -onMounted(() => { - init() -}) </script> -<style scoped> -.card { - width: 100%; - max-height: 400px; - overflow-y: scroll; -} -</style> diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue index 68f2008a..0e75d67d 100644 --- a/src/views/system/role/index.vue +++ b/src/views/system/role/index.vue @@ -14,6 +14,7 @@ placeholder="请输入角色名称" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="角色标识" prop="code"> @@ -22,10 +23,11 @@ placeholder="请输入角色标识" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="状态" prop="status"> - <el-select v-model="queryParams.status" placeholder="请选择状态" clearable> + <el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px"> <el-option v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" @@ -48,7 +50,12 @@ <el-form-item> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> - <el-button type="primary" @click="openModal('create')" v-hasPermi="['system:role:create']"> + <el-button + type="primary" + plain + @click="openForm('create')" + v-hasPermi="['system:role:create']" + > <Icon icon="ep:plus" class="mr-5px" /> 新增 </el-button> <el-button @@ -66,7 +73,7 @@ <!-- 列表 --> <ContentWrap> - <el-table v-loading="loading" :data="list" align="center"> + <el-table v-loading="loading" :data="list"> <el-table-column label="角色编号" align="center" prop="id" /> <el-table-column label="角色名称" align="center" prop="name" /> <el-table-column label="角色类型" align="center" prop="type" /> @@ -90,12 +97,11 @@ <el-button link type="primary" - @click="openModal('update', scope.row.id)" + @click="openForm('update', scope.row.id)" v-hasPermi="['system:role:update']" > 编辑 </el-button> - <!-- 操作:菜单权限 --> <el-button link type="primary" @@ -106,7 +112,6 @@ > 菜单权限 </el-button> - <!-- 操作:数据权限 --> <el-button link type="primary" @@ -149,18 +154,12 @@ import MenuPermissionForm from './MenuPermissionForm.vue' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' - const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 + const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 const list = ref([]) // 列表的数据 -const dialogTitle = ref('编辑') // 弹出层标题 -const actionType = ref('') // 操作按钮的类型 -const modelVisible = ref(false) // 是否显示弹出层 -const queryFormRef = ref() // 搜索的表单 -const exportLoading = ref(false) // 导出的加载中 - const queryParams = reactive({ pageNo: 1, pageSize: 10, @@ -169,13 +168,8 @@ const queryParams = reactive({ status: undefined, createTime: [] }) - -// 设置标题 -const setDialogTile = (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - modelVisible.value = true -} +const queryFormRef = ref() // 搜索的表单 +const exportLoading = ref(false) // 导出的加载中 /** 查询角色列表 */ const getList = async () => { @@ -203,9 +197,14 @@ const resetQuery = () => { /** 添加/修改操作 */ const formRef = ref() -const openModal = (type: string, id?: number) => { - setDialogTile('编辑') - formRef.value.openModal(type, id) +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) +} + +/** 数据权限操作 */ +const menuPermissionFormRef = ref() +const handleScope = async (type: string, row: RoleApi.RoleVO) => { + menuPermissionFormRef.value.openForm(type, row) } /** 删除按钮操作 */ @@ -235,13 +234,7 @@ const handleExport = async () => { exportLoading.value = false } } -/** 数据权限操作 */ -const menuPermissionFormRef = ref() -// 权限操作 -const handleScope = async (type: string, row: RoleApi.RoleVO) => { - menuPermissionFormRef.value.openModal(type, row) -} /** 初始化 **/ onMounted(() => { getList() From 4563f6a947d4287768a74da28a812cc70ea25cfa Mon Sep 17 00:00:00 2001 From: fessor <352475718@qq.com> Date: Mon, 27 Mar 2023 04:32:33 +0000 Subject: [PATCH 129/184] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20?= =?UTF-8?q?src/views/system/user/user.data.ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/user/user.data.ts | 137 ----------------------------- 1 file changed, 137 deletions(-) delete mode 100644 src/views/system/user/user.data.ts diff --git a/src/views/system/user/user.data.ts b/src/views/system/user/user.data.ts deleted file mode 100644 index 3a702c04..00000000 --- a/src/views/system/user/user.data.ts +++ /dev/null @@ -1,137 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -// 国际化 -const { t } = useI18n() -const validateMobile = (rule: any, value: any, callback: any) => { - const reg = /^1(3[0-9]|4[01456879]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\d{8}$/ - if (value === '') { - callback(new Error('请输入联系手机')) - } else { - if (!reg.test(value)) { - callback(new Error('请输入正确的手机号')) - } else { - callback() - } - } -} -// 表单校验 -export const rules = reactive({ - username: [required], - nickname: [required], - password: [required], - deptId: [required], - email: [ - { required: true, message: t('profile.rules.mail'), trigger: 'blur' }, - { - type: 'email', - message: t('profile.rules.truemail'), - trigger: ['blur', 'change'] - } - ], - status: [required], - mobile: [ - required, - { - len: 11, - trigger: 'blur', - message: '请输入正确的手机号码' - }, - { validator: validateMobile, trigger: 'blur' } - ] -}) -// crudSchemas -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'id', - primaryTitle: '用户编号', - action: true, - actionWidth: '200px', - columns: [ - { - title: '用户账号', - field: 'username', - isSearch: true - }, - { - title: '用户密码', - field: 'password', - isDetail: false, - isTable: false, - form: { - component: 'InputPassword' - } - }, - { - title: '用户' + t('profile.user.sex'), - field: 'sex', - dictType: DICT_TYPE.SYSTEM_USER_SEX, - dictClass: 'number', - table: { show: false } - }, - { - title: '用户昵称', - field: 'nickname' - }, - { - title: '用户邮箱', - field: 'email' - }, - { - title: '手机号码', - field: 'mobile', - isSearch: true - }, - { - title: '部门', - field: 'deptId', - isTable: false - }, - { - title: '岗位', - field: 'postIds', - isTable: false - }, - { - title: t('common.status'), - field: 'status', - dictType: DICT_TYPE.COMMON_STATUS, - dictClass: 'number', - isSearch: true, - table: { - slots: { - default: 'status_default' - } - } - }, - { - title: '最后登录时间', - field: 'loginDate', - formatter: 'formatDate', - isForm: false - }, - { - title: '最后登录IP', - field: 'loginIp', - isTable: false, - isForm: false - }, - { - title: t('form.remark'), - field: 'remark', - isTable: false - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isTable: false, - isForm: false, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From f33935868db6ce4afda865aa712fcc595e53c00d Mon Sep 17 00:00:00 2001 From: dlarmor <121919810@qq.com> Date: Mon, 27 Mar 2023 14:06:30 +0800 Subject: [PATCH 130/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E8=AE=BE=E6=96=BD=20->=20=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/infra/codegen/index.ts | 22 +- src/api/infra/codegen/types.ts | 2 +- src/locales/zh-CN.ts | 3 +- src/views/infra/codegen/EditTable.vue | 95 +++-- .../codegen/components/BasicInfoForm.vue | 220 +++------- .../codegen/components/CloumInfoForm.vue | 137 ------- .../codegen/components/ColumInfoForm.vue | 157 ++++++++ .../codegen/components/GenerateInfoForm.vue | 379 ++++++++++++++++++ .../infra/codegen/components/ImportTable.vue | 110 ++--- .../infra/codegen/components/Preview.vue | 65 +-- src/views/infra/codegen/components/index.ts | 5 +- src/views/infra/codegen/index.vue | 261 +++++++++--- 12 files changed, 966 insertions(+), 490 deletions(-) delete mode 100644 src/views/infra/codegen/components/CloumInfoForm.vue create mode 100644 src/views/infra/codegen/components/ColumInfoForm.vue create mode 100644 src/views/infra/codegen/components/GenerateInfoForm.vue diff --git a/src/api/infra/codegen/index.ts b/src/api/infra/codegen/index.ts index 54f00ff0..993bd552 100644 --- a/src/api/infra/codegen/index.ts +++ b/src/api/infra/codegen/index.ts @@ -2,56 +2,56 @@ import request from '@/config/axios' import type { CodegenUpdateReqVO, CodegenCreateListReqVO } from './types' // 查询列表代码生成表定义 -export const getCodegenTablePageApi = (params) => { +export const getCodegenTablePage = (params) => { return request.get({ url: '/infra/codegen/table/page', params }) } // 查询详情代码生成表定义 -export const getCodegenTableApi = (id: number) => { +export const getCodegenTable = (id: number) => { return request.get({ url: '/infra/codegen/detail?tableId=' + id }) } // 新增代码生成表定义 -export const createCodegenTableApi = (data: CodegenCreateListReqVO) => { +export const createCodegenTable = (data: CodegenCreateListReqVO) => { return request.post({ url: '/infra/codegen/create', data }) } // 修改代码生成表定义 -export const updateCodegenTableApi = (data: CodegenUpdateReqVO) => { +export const updateCodegenTable = (data: CodegenUpdateReqVO) => { return request.put({ url: '/infra/codegen/update', data }) } // 基于数据库的表结构,同步数据库的表和字段定义 -export const syncCodegenFromDBApi = (id: number) => { +export const syncCodegenFromDB = (id: number) => { return request.put({ url: '/infra/codegen/sync-from-db?tableId=' + id }) } // 基于 SQL 建表语句,同步数据库的表和字段定义 -export const syncCodegenFromSQLApi = (id: number, sql: string) => { +export const syncCodegenFromSQL = (id: number, sql: string) => { return request.put({ url: '/infra/codegen/sync-from-sql?tableId=' + id + '&sql=' + sql }) } // 预览生成代码 -export const previewCodegenApi = (id: number) => { +export const previewCodegen = (id: number) => { return request.get({ url: '/infra/codegen/preview?tableId=' + id }) } // 下载生成代码 -export const downloadCodegenApi = (id: number) => { +export const downloadCodegen = (id: number) => { return request.download({ url: '/infra/codegen/download?tableId=' + id }) } // 获得表定义 -export const getSchemaTableListApi = (params) => { +export const getSchemaTableList = (params) => { return request.get({ url: '/infra/codegen/db/table/list', params }) } // 基于数据库的表结构,创建代码生成器的表定义 -export const createCodegenListApi = (data) => { +export const createCodegenList = (data) => { return request.post({ url: '/infra/codegen/create-list', data }) } // 删除代码生成表定义 -export const deleteCodegenTableApi = (id: number) => { +export const deleteCodegenTable = (id: number) => { return request.delete({ url: '/infra/codegen/delete?tableId=' + id }) } diff --git a/src/api/infra/codegen/types.ts b/src/api/infra/codegen/types.ts index be6a66ed..e4e78843 100644 --- a/src/api/infra/codegen/types.ts +++ b/src/api/infra/codegen/types.ts @@ -52,7 +52,7 @@ export type CodegenPreviewVO = { code: string } export type CodegenUpdateReqVO = { - table: CodegenTableVO + table: CodegenTableVO | any columns: CodegenColumnVO[] } export type CodegenCreateListReqVO = { diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 6f46f1ab..ea1392c4 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -298,7 +298,8 @@ export default { typeUpdate: '字典类型编辑', dataCreate: '字典数据新增', dataUpdate: '字典数据编辑', - fileUpload: '上传文件' + fileUpload: '上传文件', + back: '返回' }, dialog: { dialog: '弹窗', diff --git a/src/views/infra/codegen/EditTable.vue b/src/views/infra/codegen/EditTable.vue index 820d23ca..34ba3bd6 100644 --- a/src/views/infra/codegen/EditTable.vue +++ b/src/views/infra/codegen/EditTable.vue @@ -1,67 +1,74 @@ <template> - <ContentWrap> - <ContentDetailWrap :title="title" @back="push('/infra/codegen')"> - <el-tabs v-model="activeName"> - <el-tab-pane label="基本信息" name="basicInfo"> - <BasicInfoForm ref="basicInfoRef" :basicInfo="tableCurrentRow" /> - </el-tab-pane> - <el-tab-pane label="字段信息" name="cloum"> - <CloumInfoForm ref="cloumInfoRef" :info="cloumCurrentRow" /> - </el-tab-pane> - </el-tabs> - <template #right> - <XButton - type="primary" - :title="t('action.save')" - :loading="loading" - @click="submitForm()" - /> - </template> - </ContentDetailWrap> - </ContentWrap> + <content-wrap v-loading="loading"> + <el-tabs v-model="activeName"> + <el-tab-pane label="基本信息" name="basicInfo"> + <basic-info-form ref="basicInfoRef" :table="formData.table" /> + </el-tab-pane> + <el-tab-pane label="字段信息" name="colum"> + <colum-info-form ref="columInfoRef" :columns="formData.columns" /> + </el-tab-pane> + <el-tab-pane label="生成信息" name="generateInfo"> + <generate-info-form ref="generateInfoRef" :table="formData.table" /> + </el-tab-pane> + </el-tabs> + <el-form label-width="100px"> + <el-form-item style="text-align: center; margin-left: -100px; margin-top: 10px"> + <el-button type="primary" @click="submitForm" :loading="submitLoading"> + {{ t('action.save') }} + </el-button> + <el-button @click="close">{{ t('action.back') }}</el-button> + </el-form-item> + </el-form> + </content-wrap> </template> <script setup lang="ts"> -import { BasicInfoForm, CloumInfoForm } from './components' -import { getCodegenTableApi, updateCodegenTableApi } from '@/api/infra/codegen' -import { CodegenTableVO, CodegenColumnVO, CodegenUpdateReqVO } from '@/api/infra/codegen/types' +import { BasicInfoForm, ColumInfoForm, GenerateInfoForm } from './components' +import * as CodegenApi from '@/api/infra/codegen' +import ContentWrap from '@/components/ContentWrap/src/ContentWrap.vue' +import { useTagsViewStore } from '@/store/modules/tagsView' +import { CodegenUpdateReqVO } from '@/api/infra/codegen/types' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 -const { push } = useRouter() +const { push, currentRoute } = useRouter() const { query } = useRoute() +const { delView } = useTagsViewStore() const loading = ref(false) -const title = ref('代码生成') +const submitLoading = ref(false) const activeName = ref('basicInfo') -const cloumInfoRef = ref(null) -const tableCurrentRow = ref<CodegenTableVO>() -const cloumCurrentRow = ref<CodegenColumnVO[]>([]) const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>() +const columInfoRef = ref<ComponentRef<typeof ColumInfoForm>>() +const generateInfoRef = ref<ComponentRef<typeof GenerateInfoForm>>() +const formData = ref<CodegenUpdateReqVO>({ + table: {}, + columns: [] +}) -const getList = async () => { +const getDetail = async () => { const id = query.id as unknown as number if (id) { + loading.value = true // 获取表详细信息 - const res = await getCodegenTableApi(id) - title.value = '修改[ ' + res.table.tableName + ' ]生成配置' - tableCurrentRow.value = res.table - cloumCurrentRow.value = res.columns + formData.value = await CodegenApi.getCodegenTable(id) + loading.value = false } } const submitForm = async () => { - const basicInfo = unref(basicInfoRef) - const basicForm = await basicInfo?.elFormRef?.validate()?.catch(() => {}) - if (basicForm) { - const basicInfoData = (await basicInfo?.getFormData()) as CodegenTableVO - const genTable: CodegenUpdateReqVO = { - table: basicInfoData, - columns: cloumCurrentRow.value - } - await updateCodegenTableApi(genTable) + if (!unref(formData)) return + try { + await unref(basicInfoRef)?.validate() + await unref(generateInfoRef)?.validate() + await CodegenApi.updateCodegenTable(unref(formData)) message.success(t('common.updateSuccess')) push('/infra/codegen') - } + } catch {} +} +/** 关闭按钮 */ +const close = () => { + delView(unref(currentRoute)) + push('/infra/codegen') } onMounted(() => { - getList() + getDetail() }) </script> diff --git a/src/views/infra/codegen/components/BasicInfoForm.vue b/src/views/infra/codegen/components/BasicInfoForm.vue index 2009553f..21b280ee 100644 --- a/src/views/infra/codegen/components/BasicInfoForm.vue +++ b/src/views/infra/codegen/components/BasicInfoForm.vue @@ -1,183 +1,93 @@ <template> - <Form :rules="rules" @register="register" /> + <el-form ref="formRef" :model="formData" :rules="rules" label-width="120px"> + <el-row> + <el-col :span="12"> + <el-form-item label="表名称" prop="tableName"> + <el-input placeholder="请输入仓库名称" v-model="formData.tableName" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="表描述" prop="tableComment"> + <el-input placeholder="请输入" v-model="formData.tableComment" /> + </el-form-item> + </el-col> + + <el-col :span="12"> + <el-form-item prop="className"> + <template #label> + <span> + 实体类名称 + <el-tooltip + content="默认去除表名的前缀。如果存在重复,则需要手动添加前缀,避免 MyBatis 报 Alias 重复的问题。" + placement="top" + > + <Icon icon="ep:question-filled" class="" /> + </el-tooltip> + </span> + </template> + + <el-input placeholder="请输入" v-model="formData.className" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="作者" prop="author"> + <el-input placeholder="请输入" v-model="formData.author" /> + </el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="备注" prop="remark"> + <el-input type="textarea" :rows="3" v-model="formData.remark" /> + </el-form-item> + </el-col> + </el-row> + </el-form> </template> <script setup lang="ts"> -import { useForm } from '@/hooks/web/useForm' -import { FormSchema } from '@/types/form' import { CodegenTableVO } from '@/api/infra/codegen/types' -import { getIntDictOptions } from '@/utils/dict' -import { listSimpleMenusApi } from '@/api/system/menu' -import { handleTree, defaultProps } from '@/utils/tree' import { PropType } from 'vue' +const emits = defineEmits(['update:basicInfo']) const props = defineProps({ - basicInfo: { + table: { type: Object as PropType<Nullable<CodegenTableVO>>, default: () => null } }) -const templateTypeOptions = getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE) -const sceneOptions = getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE) -const menuOptions = ref<any>([]) // 树形结构 -const getTree = async () => { - const res = await listSimpleMenusApi() - menuOptions.value = handleTree(res) -} +const formRef = ref() +const formData = ref({ + tableName: '', + tableComment: '', + className: '', + author: '', + remark: '' +}) const rules = reactive({ tableName: [required], tableComment: [required], className: [required], - author: [required], - templateType: [required], - scene: [required], - moduleName: [required], - businessName: [required], - businessPackage: [required], - classComment: [required] -}) -const schema = reactive<FormSchema[]>([ - { - label: '上级菜单', - field: 'parentMenuId', - component: 'TreeSelect', - componentProps: { - data: menuOptions, - props: defaultProps, - checkStrictly: true, - nodeKey: 'id' - }, - labelMessage: '分配到指定菜单下,例如 系统管理', - colProps: { - span: 24 - } - }, - { - label: '表名称', - field: 'tableName', - component: 'Input', - colProps: { - span: 12 - } - }, - { - label: '表描述', - field: 'tableComment', - component: 'Input', - colProps: { - span: 12 - } - }, - { - label: '实体类名称', - field: 'className', - component: 'Input', - colProps: { - span: 12 - } - }, - { - label: '类名称', - field: 'className', - component: 'Input', - labelMessage: '类名称(首字母大写),例如SysUser、SysMenu、SysDictData 等等', - colProps: { - span: 12 - } - }, - { - label: '生成模板', - field: 'templateType', - component: 'Select', - componentProps: { - options: templateTypeOptions - }, - colProps: { - span: 12 - } - }, - { - label: '生成场景', - field: 'scene', - component: 'Select', - componentProps: { - options: sceneOptions - }, - colProps: { - span: 12 - } - }, - { - label: '模块名', - field: 'moduleName', - component: 'Input', - labelMessage: '模块名,即一级目录,例如 system、infra、tool 等等', - colProps: { - span: 12 - } - }, - { - label: '业务名', - field: 'businessName', - component: 'Input', - labelMessage: '业务名,即二级目录,例如 user、permission、dict 等等', - colProps: { - span: 12 - } - }, - { - label: '类描述', - field: 'classComment', - component: 'Input', - labelMessage: '用作类描述,例如 用户', - colProps: { - span: 12 - } - }, - { - label: '作者', - field: 'author', - component: 'Input', - colProps: { - span: 12 - } - }, - { - label: '备注', - field: 'remark', - component: 'Input', - componentProps: { - type: 'textarea', - rows: 4 - }, - colProps: { - span: 24 - } - } -]) -const { register, methods, elFormRef } = useForm({ - schema + author: [required] }) + watch( - () => props.basicInfo, - (basicInfo) => { - if (!basicInfo) return - const { setValues } = methods - setValues(basicInfo) + () => props.table, + (table) => { + if (!table) return + formData.value = table }, { deep: true, immediate: true } ) -// ========== 初始化 ========== -onMounted(async () => { - await getTree() -}) - +watch( + () => formData.value, + (val) => { + emits('update:basicInfo', val) + } +) defineExpose({ - elFormRef, - getFormData: methods.getFormData + validate: async () => unref(formRef)?.validate() }) </script> diff --git a/src/views/infra/codegen/components/CloumInfoForm.vue b/src/views/infra/codegen/components/CloumInfoForm.vue deleted file mode 100644 index 5a60c546..00000000 --- a/src/views/infra/codegen/components/CloumInfoForm.vue +++ /dev/null @@ -1,137 +0,0 @@ -<template> - <vxe-table - ref="dragTable" - border - :data="info" - max-height="600" - stripe - class="xtable-scrollbar" - :column-config="{ resizable: true }" - > - <vxe-column title="字段列名" field="columnName" fixed="left" width="10%" /> - <vxe-colgroup title="基础属性"> - <vxe-column title="字段描述" field="columnComment" width="10%"> - <template #default="{ row }"> - <vxe-input v-model="row.columnComment" placeholder="请输入字段描述" /> - </template> - </vxe-column> - <vxe-column title="物理类型" field="dataType" width="10%" /> - <vxe-column title="Java类型" width="10%" field="javaType"> - <template #default="{ row }"> - <vxe-select v-model="row.javaType" placeholder="请选择Java类型"> - <vxe-option label="Long" value="Long" /> - <vxe-option label="String" value="String" /> - <vxe-option label="Integer" value="Integer" /> - <vxe-option label="Double" value="Double" /> - <vxe-option label="BigDecimal" value="BigDecimal" /> - <vxe-option label="LocalDateTime" value="LocalDateTime" /> - <vxe-option label="Boolean" value="Boolean" /> - </vxe-select> - </template> - </vxe-column> - <vxe-column title="java属性" width="8%" field="javaField"> - <template #default="{ row }"> - <vxe-input v-model="row.javaField" placeholder="请输入java属性" /> - </template> - </vxe-column> - </vxe-colgroup> - <vxe-colgroup title="增删改查"> - <vxe-column title="插入" width="40px" field="createOperation"> - <template #default="{ row }"> - <vxe-checkbox true-label="true" false-label="false" v-model="row.createOperation" /> - </template> - </vxe-column> - <vxe-column title="编辑" width="40px" field="updateOperation"> - <template #default="{ row }"> - <vxe-checkbox true-label="true" false-label="false" v-model="row.updateOperation" /> - </template> - </vxe-column> - <vxe-column title="列表" width="40px" field="listOperationResult"> - <template #default="{ row }"> - <vxe-checkbox true-label="true" false-label="false" v-model="row.listOperationResult" /> - </template> - </vxe-column> - <vxe-column title="查询" width="40px" field="listOperation"> - <template #default="{ row }"> - <vxe-checkbox true-label="true" false-label="false" v-model="row.listOperation" /> - </template> - </vxe-column> - <vxe-column title="允许空" width="40px" field="nullable"> - <template #default="{ row }"> - <vxe-checkbox true-label="true" false-label="false" v-model="row.nullable" /> - </template> - </vxe-column> - <vxe-column title="查询方式" width="60px" field="listOperationCondition"> - <template #default="{ row }"> - <vxe-select v-model="row.listOperationCondition" placeholder="请选择查询方式"> - <vxe-option label="=" value="=" /> - <vxe-option label="!=" value="!=" /> - <vxe-option label=">" value=">" /> - <vxe-option label=">=" value=">=" /> - <vxe-option label="<" value="<>" /> - <vxe-option label="<=" value="<=" /> - <vxe-option label="LIKE" value="LIKE" /> - <vxe-option label="BETWEEN" value="BETWEEN" /> - </vxe-select> - </template> - </vxe-column> - </vxe-colgroup> - <vxe-column title="显示类型" width="10%" field="htmlType"> - <template #default="{ row }"> - <vxe-select v-model="row.htmlType" placeholder="请选择显示类型"> - <vxe-option label="文本框" value="input" /> - <vxe-option label="文本域" value="textarea" /> - <vxe-option label="下拉框" value="select" /> - <vxe-option label="单选框" value="radio" /> - <vxe-option label="复选框" value="checkbox" /> - <vxe-option label="日期控件" value="datetime" /> - <vxe-option label="图片上传" value="imageUpload" /> - <vxe-option label="文件上传" value="fileUpload" /> - <vxe-option label="富文本控件" value="editor" /> - </vxe-select> - </template> - </vxe-column> - <vxe-column title="字典类型" width="10%" field="dictType"> - <template #default="{ row }"> - <vxe-select v-model="row.dictType" clearable filterable placeholder="请选择字典类型"> - <vxe-option - v-for="dict in dictOptions" - :key="dict.id" - :label="dict.name" - :value="dict.type" - /> - </vxe-select> - </template> - </vxe-column> - <vxe-column title="示例" field="example"> - <template #default="{ row }"> - <vxe-input v-model="row.example" placeholder="请输入示例" /> - </template> - </vxe-column> - </vxe-table> -</template> -<script setup lang="ts"> -import { PropType } from 'vue' -import { DictTypeVO } from '@/api/system/dict/types' -import { CodegenColumnVO } from '@/api/infra/codegen/types' -import { listSimpleDictType } from '@/api/system/dict/dict.type' - -const props = defineProps({ - info: { - type: Array as unknown as PropType<CodegenColumnVO[]>, - default: () => null - } -}) -/** 查询字典下拉列表 */ -const dictOptions = ref<DictTypeVO[]>() -const getDictOptions = async () => { - const res = await listSimpleDictType() - dictOptions.value = res -} -onMounted(async () => { - await getDictOptions() -}) -defineExpose({ - info: props.info -}) -</script> diff --git a/src/views/infra/codegen/components/ColumInfoForm.vue b/src/views/infra/codegen/components/ColumInfoForm.vue new file mode 100644 index 00000000..33d29978 --- /dev/null +++ b/src/views/infra/codegen/components/ColumInfoForm.vue @@ -0,0 +1,157 @@ +<template> + <el-table ref="dragTable" :data="formData" row-key="columnId" :max-height="tableHeight"> + <el-table-column + label="字段列名" + prop="columnName" + min-width="10%" + :show-overflow-tooltip="true" + /> + <el-table-column label="字段描述" min-width="10%"> + <template #default="scope"> + <el-input v-model="scope.row.columnComment" /> + </template> + </el-table-column> + <el-table-column + label="物理类型" + prop="dataType" + min-width="10%" + :show-overflow-tooltip="true" + /> + <el-table-column label="Java类型" min-width="11%"> + <template #default="scope"> + <el-select v-model="scope.row.javaType"> + <el-option label="Long" value="Long" /> + <el-option label="String" value="String" /> + <el-option label="Integer" value="Integer" /> + <el-option label="Double" value="Double" /> + <el-option label="BigDecimal" value="BigDecimal" /> + <el-option label="LocalDateTime" value="LocalDateTime" /> + <el-option label="Boolean" value="Boolean" /> + </el-select> + </template> + </el-table-column> + <el-table-column label="java属性" min-width="10%"> + <template #default="scope"> + <el-input v-model="scope.row.javaField" /> + </template> + </el-table-column> + <el-table-column label="插入" min-width="4%"> + <template #default="scope"> + <el-checkbox true-label="true" false-label="false" v-model="scope.row.createOperation" /> + </template> + </el-table-column> + <el-table-column label="编辑" min-width="4%"> + <template #default="scope"> + <el-checkbox true-label="true" false-label="false" v-model="scope.row.updateOperation" /> + </template> + </el-table-column> + <el-table-column label="列表" min-width="4%"> + <template #default="scope"> + <el-checkbox + true-label="true" + false-label="false" + v-model="scope.row.listOperationResult" + /> + </template> + </el-table-column> + <el-table-column label="查询" min-width="4%"> + <template #default="scope"> + <el-checkbox true-label="true" false-label="false" v-model="scope.row.listOperation" /> + </template> + </el-table-column> + <el-table-column label="查询方式" min-width="10%"> + <template #default="scope"> + <el-select v-model="scope.row.listOperationCondition"> + <el-option label="=" value="=" /> + <el-option label="!=" value="!=" /> + <el-option label=">" value=">" /> + <el-option label=">=" value=">=" /> + <el-option label="<" value="<>" /> + <el-option label="<=" value="<=" /> + <el-option label="LIKE" value="LIKE" /> + <el-option label="BETWEEN" value="BETWEEN" /> + </el-select> + </template> + </el-table-column> + <el-table-column label="允许空" min-width="5%"> + <template #default="scope"> + <el-checkbox true-label="true" false-label="false" v-model="scope.row.nullable" /> + </template> + </el-table-column> + <el-table-column label="显示类型" min-width="12%"> + <template #default="scope"> + <el-select v-model="scope.row.htmlType"> + <el-option label="文本框" value="input" /> + <el-option label="文本域" value="textarea" /> + <el-option label="下拉框" value="select" /> + <el-option label="单选框" value="radio" /> + <el-option label="复选框" value="checkbox" /> + <el-option label="日期控件" value="datetime" /> + <el-option label="图片上传" value="imageUpload" /> + <el-option label="文件上传" value="fileUpload" /> + <el-option label="富文本控件" value="editor" /> + </el-select> + </template> + </el-table-column> + <el-table-column label="字典类型" min-width="12%"> + <template #default="scope"> + <el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择"> + <el-option + v-for="dict in dictOptions" + :key="dict.id" + :label="dict.name" + :value="dict.type" + /> + </el-select> + </template> + </el-table-column> + <el-table-column label="示例" min-width="10%"> + <template #default="scope"> + <el-input v-model="scope.row.example" /> + </template> + </el-table-column> + </el-table> +</template> +<script setup lang="ts"> +import { PropType } from 'vue' +import { CodegenColumnVO } from '@/api/infra/codegen/types' +import { DictTypeVO, listSimpleDictType } from '@/api/system/dict/dict.type' + +const emits = defineEmits(['update:columns']) +const props = defineProps({ + columns: { + type: Array as unknown as PropType<CodegenColumnVO[]>, + default: () => null + } +}) + +const formData = ref<CodegenColumnVO[]>([]) +const tableHeight = document.documentElement.scrollHeight - 350 + 'px' + +/** 查询字典下拉列表 */ +const dictOptions = ref<DictTypeVO[]>() +const getDictOptions = async () => { + dictOptions.value = await listSimpleDictType() +} +onMounted(async () => { + await getDictOptions() +}) + +watch( + () => props.columns, + (columns) => { + if (!columns) return + formData.value = columns + }, + { + deep: true, + immediate: true + } +) +watch( + () => formData.value, + (val) => { + emits('update:columns', val) + } +) +</script> diff --git a/src/views/infra/codegen/components/GenerateInfoForm.vue b/src/views/infra/codegen/components/GenerateInfoForm.vue new file mode 100644 index 00000000..9a64c800 --- /dev/null +++ b/src/views/infra/codegen/components/GenerateInfoForm.vue @@ -0,0 +1,379 @@ +<template> + <el-form ref="formRef" :model="formData" :rules="rules" label-width="150px"> + <el-row> + <el-col :span="12"> + <el-form-item prop="templateType" label="生成模板"> + <el-select v-model="formData.templateType" @change="tplSelectChange"> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE)" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item prop="scene" label="生成场景"> + <el-select v-model="formData.scene"> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE)" + :key="parseInt(dict.value)" + :label="dict.label" + :value="parseInt(dict.value)" + /> + </el-select> + </el-form-item> + </el-col> + + <!-- <el-col :span="12">--> + <!-- <el-form-item prop="packageName">--> + <!-- <span slot="label">--> + <!-- 生成包路径--> + <!-- <el-tooltip content="生成在哪个java包下,例如 com.ruoyi.system" placement="top">--> + <!-- <i class="el-icon-question"></i>--> + <!-- </el-tooltip>--> + <!-- </span>--> + <!-- <el-input v-model="formData.packageName" />--> + <!-- </el-form-item>--> + <!-- </el-col>--> + + <el-col :span="12"> + <el-form-item prop="moduleName"> + <template #label> + <span> + 模块名 + <el-tooltip + content="模块名,即一级目录,例如 system、infra、tool 等等" + placement="top" + > + <Icon icon="ep:question-filled" /> + </el-tooltip> + </span> + </template> + <el-input v-model="formData.moduleName" /> + </el-form-item> + </el-col> + + <el-col :span="12"> + <el-form-item prop="businessName"> + <template #label> + <span> + 业务名 + <el-tooltip + content="业务名,即二级目录,例如 user、permission、dict 等等" + placement="top" + > + <Icon icon="ep:question-filled" /> + </el-tooltip> + </span> + </template> + <el-input v-model="formData.businessName" /> + </el-form-item> + </el-col> + + <!-- <el-col :span="12">--> + <!-- <el-form-item prop="businessPackage">--> + <!-- <span slot="label">--> + <!-- 业务包--> + <!-- <el-tooltip content="业务包,自定义二级目录。例如说,我们希望将 dictType 和 dictData 归类成 dict 业务" placement="top">--> + <!-- <i class="el-icon-question"></i>--> + <!-- </el-tooltip>--> + <!-- </span>--> + <!-- <el-input v-model="formData.businessPackage" />--> + <!-- </el-form-item>--> + <!-- </el-col>--> + + <el-col :span="12"> + <el-form-item prop="className"> + <template #label> + <span> + 类名称 + <el-tooltip + content="类名称(首字母大写),例如SysUser、SysMenu、SysDictData 等等" + placement="top" + > + <Icon icon="ep:question-filled" /> + </el-tooltip> + </span> + </template> + <el-input v-model="formData.className" /> + </el-form-item> + </el-col> + + <el-col :span="12"> + <el-form-item prop="classComment"> + <template #label> + <span> + 类描述 + <el-tooltip content="用作类描述,例如 用户" placement="top"> + <Icon icon="ep:question-filled" /> + </el-tooltip> + </span> + </template> + <el-input v-model="formData.classComment" /> + </el-form-item> + </el-col> + + <el-col :span="12"> + <el-form-item> + <template #label> + <span> + 上级菜单 + <el-tooltip content="分配到指定菜单下,例如 系统管理" placement="top"> + <Icon icon="ep:question-filled" /> + </el-tooltip> + </span> + </template> + <el-tree-select + v-model="formData.parentMenuId" + placeholder="请选择系统菜单" + node-key="id" + check-strictly + :data="menus" + :props="menuTreeProps" + /> + </el-form-item> + </el-col> + + <el-col :span="24" v-if="formData.genType === '1'"> + <el-form-item prop="genPath"> + <template #label> + <span> + 自定义路径 + <el-tooltip + content="填写磁盘绝对路径,若不填写,则生成到当前Web项目下" + placement="top" + > + <Icon icon="ep:question-filled" /> + </el-tooltip> + </span> + </template> + <el-input v-model="formData.genPath"> + <template #append> + <el-dropdown> + <el-button type="primary"> + 最近路径快速选择 + <i class="el-icon-arrow-down el-icon--right"></i> + </el-button> + <template #dropdown> + <el-dropdown-menu> + <el-dropdown-item @click="formData.genPath = '/'"> + 恢复默认的生成基础路径 + </el-dropdown-item> + </el-dropdown-menu> + </template> + </el-dropdown> + </template> + </el-input> + </el-form-item> + </el-col> + </el-row> + + <el-row v-show="formData.tplCategory === 'tree'"> + <h4 class="form-header">其他信息</h4> + <el-col :span="12"> + <el-form-item> + <template #label> + <span> + 树编码字段 + <el-tooltip content="树显示的编码字段名, 如:dept_id" placement="top"> + <Icon icon="ep:question-filled" /> + </el-tooltip> + </span> + </template> + <el-select v-model="formData.treeCode" placeholder="请选择"> + <el-option + v-for="(column, index) in formData.columns" + :key="index" + :label="column.columnName + ':' + column.columnComment" + :value="column.columnName" + /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item> + <template #label> + <span> + 树父编码字段 + <el-tooltip content="树显示的父编码字段名, 如:parent_Id" placement="top"> + <Icon icon="ep:question-filled" /> + </el-tooltip> + </span> + </template> + <el-select v-model="formData.treeParentCode" placeholder="请选择"> + <el-option + v-for="(column, index) in formData.columns" + :key="index" + :label="column.columnName + ':' + column.columnComment" + :value="column.columnName" + /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item> + <template #label> + <span> + 树名称字段 + <el-tooltip content="树节点的显示名称字段名, 如:dept_name" placement="top"> + <Icon icon="ep:question-filled" /> + </el-tooltip> + </span> + </template> + + <el-select v-model="formData.treeName" placeholder="请选择"> + <el-option + v-for="(column, index) in formData.columns" + :key="index" + :label="column.columnName + ':' + column.columnComment" + :value="column.columnName" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row v-show="formData.tplCategory === 'sub'"> + <h4 class="form-header">关联信息</h4> + <el-col :span="12"> + <el-form-item> + <template #label> + <span> + 关联子表的表名 + <el-tooltip content="关联子表的表名, 如:sys_user" placement="top"> + <Icon icon="ep:question-filled" /> + </el-tooltip> + </span> + </template> + <el-select v-model="formData.subTableName" placeholder="请选择" @change="subSelectChange"> + <el-option + v-for="(table0, index) in tables" + :key="index" + :label="table0.tableName + ':' + table0.tableComment" + :value="table0.tableName" + /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item> + <template #label> + <span> + 子表关联的外键名 + <el-tooltip content="子表关联的外键名, 如:user_id" placement="top"> + <Icon icon="ep:question-filled" /> + </el-tooltip> + </span> + </template> + <el-select v-model="formData.subTableFkName" placeholder="请选择"> + <el-option + v-for="(column, index) in subColumns" + :key="index" + :label="column.columnName + ':' + column.columnComment" + :value="column.columnName" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + </el-form> +</template> +<script setup lang="ts"> +import { CodegenTableVO } from '@/api/infra/codegen/types' +import * as MenuApi from '@/api/system/menu' +import { PropType } from 'vue' +import { getDictOptions, DICT_TYPE } from '@/utils/dict' +import { handleTree } from '@/utils/tree' + +const message = useMessage() // 消息弹窗 +const emits = defineEmits(['update:basicInfo']) +const props = defineProps({ + table: { + type: Object as PropType<Nullable<CodegenTableVO>>, + default: () => null + } +}) + +const formRef = ref() +const formData = ref({ + templateType: null, + scene: null, + moduleName: '', + businessName: '', + className: '', + classComment: '', + parentMenuId: null, + genPath: '', + treeCode: '', + treeParentCode: '', + treeName: '', + tplCategory: '', + subTableName: '', + subTableFkName: '', + genType: '' +}) + +const rules = reactive({ + templateType: [required], + scene: [required], + moduleName: [required], + businessName: [required], + businessPackage: [required], + className: [required], + classComment: [required] +}) + +const tables = ref([]) +const subColumns = ref([]) +const menus = ref<any[]>([]) +const menuTreeProps = { + label: 'name' +} + +/** 选择子表名触发 */ +const subSelectChange = () => { + formData.value.subTableFkName = '' +} +/** 选择生成模板触发 */ +const tplSelectChange = (value) => { + if (value !== 1) { + // TODO 芋艿:暂时不考虑支持树形结构 + message.error( + '暂时不考虑支持【树形】和【主子表】的代码生成。原因是:导致 vm 模板过于复杂,不利于胖友二次开发' + ) + return false + } + if (value !== 'sub') { + formData.value.subTableName = '' + formData.value.subTableFkName = '' + } +} + +watch( + () => props.table, + (table) => { + if (!table) return + formData.value = table as any + }, + { + deep: true, + immediate: true + } +) +watch( + () => formData.value, + (val) => { + emits('update:basicInfo', val) + } +) +onMounted(async () => { + try { + const resp = await MenuApi.listSimpleMenusApi() + menus.value = handleTree(resp) + } catch {} +}) +defineExpose({ + validate: async () => unref(formRef)?.validate() +}) +</script> diff --git a/src/views/infra/codegen/components/ImportTable.vue b/src/views/infra/codegen/components/ImportTable.vue index 38a81541..7eebbe65 100644 --- a/src/views/infra/codegen/components/ImportTable.vue +++ b/src/views/infra/codegen/components/ImportTable.vue @@ -1,9 +1,8 @@ <template> - <!-- 导入表 --> - <XModal title="导入表" v-model="visible"> - <el-form :model="queryParams" ref="queryRef" :inline="true"> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form :model="queryParams" ref="queryFormRef" :inline="true"> <el-form-item label="数据源" prop="dataSourceConfigId"> - <el-select v-model="queryParams.dataSourceConfigId" placeholder="请选择数据源" clearable> + <el-select v-model="queryParams.dataSourceConfigId" placeholder="请选择数据源"> <el-option v-for="config in dataSourceConfigs" :key="config.id" @@ -13,52 +12,64 @@ </el-select> </el-form-item> <el-form-item label="表名称" prop="name"> - <el-input v-model="queryParams.name" placeholder="请输入表名称" clearable /> + <el-input + v-model="queryParams.name" + placeholder="请输入表名称" + clearable + @keyup.enter="handleQuery" + /> </el-form-item> <el-form-item label="表描述" prop="comment"> - <el-input v-model="queryParams.comment" placeholder="请输入表描述" clearable /> + <el-input + v-model="queryParams.comment" + placeholder="请输入表描述" + clearable + @keyup.enter="handleQuery" + /> </el-form-item> <el-form-item> - <XButton - type="primary" - preIcon="ep:search" - :title="t('common.query')" - @click="handleQuery()" - /> - <XButton preIcon="ep:refresh-right" :title="t('common.reset')" @click="resetQuery()" /> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> </el-form-item> </el-form> - <vxe-table - ref="xTable" - :data="dbTableList" - v-loading="dbLoading" - :checkbox-config="{ highlight: true, range: true }" - height="260px" - class="xtable-scrollbar" - > - <vxe-column type="checkbox" width="60" /> - <vxe-column field="name" title="表名称" /> - <vxe-column field="comment" title="表描述" /> - </vxe-table> + + <el-row> + <el-table + v-loading="dbLoading" + @row-click="clickRow" + ref="tableRef" + :data="dbTableList" + @selection-change="handleSelectionChange" + height="260px" + > + <el-table-column type="selection" width="55" /> + <el-table-column prop="name" label="表名称" :show-overflow-tooltip="true" /> + <el-table-column prop="comment" label="表描述" :show-overflow-tooltip="true" /> + </el-table> + </el-row> + <template #footer> <div class="dialog-footer"> - <XButton type="primary" :title="t('action.import')" @click="handleImportTable()" /> - <XButton :title="t('dialog.close')" @click="handleClose()" /> + <el-button @click="handleImportTable" type="primary" :disabled="tables.length === 0">{{ + t('action.import') + }}</el-button> + <el-button @click="handleClose">{{ t('dialog.close') }}</el-button> </div> </template> - </XModal> + </Dialog> </template> <script setup lang="ts"> -import { VxeTableInstance } from 'vxe-table' import type { DatabaseTableVO } from '@/api/infra/codegen/types' -import { getSchemaTableListApi, createCodegenListApi } from '@/api/infra/codegen' -import { getDataSourceConfigListApi, DataSourceConfigVO } from '@/api/infra/dataSourceConfig' +import * as CodegenApi from '@/api/infra/codegen' +import { getDataSourceConfigList, DataSourceConfigVO } from '@/api/infra/dataSourceConfig' +import { ElTable } from 'element-plus' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 const emit = defineEmits(['ok']) -// ======== 显示页面 ======== -const visible = ref(false) + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('导入表') // 弹窗的标题 const dbLoading = ref(true) const queryParams = reactive({ name: undefined, @@ -67,10 +78,9 @@ const queryParams = reactive({ }) const dataSourceConfigs = ref<DataSourceConfigVO[]>([]) const show = async () => { - const res = await getDataSourceConfigListApi() - dataSourceConfigs.value = res - queryParams.dataSourceConfigId = dataSourceConfigs.value[0].id - visible.value = true + dataSourceConfigs.value = await getDataSourceConfigList() + queryParams.dataSourceConfigId = dataSourceConfigs.value[0].id as number + modelVisible.value = true await getList() } /** 查询表数据 */ @@ -79,8 +89,7 @@ const dbTableList = ref<DatabaseTableVO[]>([]) /** 查询表数据 */ const getList = async () => { dbLoading.value = true - const res = await getSchemaTableListApi(queryParams) - dbTableList.value = res + dbTableList.value = await CodegenApi.getSchemaTableList(queryParams) dbLoading.value = false } // 查询操作 @@ -91,23 +100,22 @@ const handleQuery = async () => { const resetQuery = async () => { queryParams.name = undefined queryParams.comment = undefined - queryParams.dataSourceConfigId = 0 + queryParams.dataSourceConfigId = dataSourceConfigs.value[0].id as number await getList() } -const xTable = ref<VxeTableInstance>() +const tableRef = ref<typeof ElTable>() /** 多选框选中数据 */ const tables = ref<string[]>([]) - +const clickRow = (row) => { + unref(tableRef)?.toggleRowSelection(row) +} +// 多选框选中数据 +const handleSelectionChange = (selection) => { + tables.value = selection.map((item) => item.name) +} /** 导入按钮操作 */ const handleImportTable = async () => { - if (xTable.value?.getCheckboxRecords().length === 0) { - message.error('请选择要导入的表') - return - } - xTable.value?.getCheckboxRecords().forEach((item) => { - tables.value.push(item.name) - }) - await createCodegenListApi({ + await CodegenApi.createCodegenList({ dataSourceConfigId: queryParams.dataSourceConfigId, tableNames: tables.value }) @@ -116,7 +124,7 @@ const handleImportTable = async () => { handleClose() } const handleClose = () => { - visible.value = false + modelVisible.value = false tables.value = [] } defineExpose({ diff --git a/src/views/infra/codegen/components/Preview.vue b/src/views/infra/codegen/components/Preview.vue index 2d9482ff..82307d59 100644 --- a/src/views/infra/codegen/components/Preview.vue +++ b/src/views/infra/codegen/components/Preview.vue @@ -1,5 +1,11 @@ <template> - <XModal title="预览" v-model="preview.open"> + <Dialog + :title="modelTitle" + v-model="modelVisible" + align-center + width="60%" + class="app-infra-codegen-preview-container" + > <div class="flex"> <el-card class="w-1/4" :gutter="12" shadow="hover"> <el-scrollbar height="calc(100vh - 88px - 40px - 50px)"> @@ -10,6 +16,7 @@ :expand-on-click-node="false" highlight-current @node-click="handleNodeClick" + default-expand-all /> </el-scrollbar> </el-card> @@ -21,38 +28,34 @@ :name="item.filePath" :key="item.filePath" > - <XTextButton style="float: right" :title="t('common.copy')" @click="copy(item.code)" /> + <el-button text type="primary" class="float-right" @click="copy(item.code)"> + {{ t('common.copy') }} + </el-button> <pre>{{ item.code }}</pre> </el-tab-pane> </el-tabs> </el-card> </div> - </XModal> + </Dialog> </template> <script setup lang="ts"> import { useClipboard } from '@vueuse/core' import { handleTree2 } from '@/utils/tree' -import { previewCodegenApi } from '@/api/infra/codegen' -import { CodegenTableVO, CodegenPreviewVO } from '@/api/infra/codegen/types' +import * as CodegenApi from '@/api/infra/codegen' +import { CodegenPreviewVO } from '@/api/infra/codegen/types' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('代码预览') // 弹窗的标题 // ======== 显示页面 ======== const preview = reactive({ - open: false, - titel: '代码预览', fileTree: [], activeName: '' }) const previewCodegen = ref<CodegenPreviewVO[]>() -const show = async (row: CodegenTableVO) => { - const res = await previewCodegenApi(row.id) - let file = handleFiles(res) - previewCodegen.value = res - preview.fileTree = handleTree2(file, 'id', 'parentId', 'children', '/') - preview.activeName = res[0].filePath - preview.open = true -} + const handleNodeClick = async (data, node) => { if (node && !node.isLeaf) { return false @@ -132,14 +135,30 @@ const copy = async (text: string) => { const { copy, copied, isSupported } = useClipboard({ source: text }) if (!isSupported) { message.error(t('common.copyError')) - } else { - await copy() - if (unref(copied)) { - message.success(t('common.copySuccess')) - } + return + } + await copy() + if (unref(copied)) { + message.success(t('common.copySuccess')) } } -defineExpose({ - show -}) + +/** 打开弹窗 */ +const openModal = async (id: number) => { + modelVisible.value = true + const res = await CodegenApi.previewCodegen(id) + let file = handleFiles(res) + previewCodegen.value = res + preview.fileTree = handleTree2(file, 'id', 'parentId', 'children', '/') + preview.activeName = res[0].filePath +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 </script> +<style lang="scss"> +.app-infra-codegen-preview-container { + .el-scrollbar .el-scrollbar__wrap .el-scrollbar__view { + white-space: nowrap; + display: inline-block; + } +} +</style> diff --git a/src/views/infra/codegen/components/index.ts b/src/views/infra/codegen/components/index.ts index b84c5a03..71d0587f 100644 --- a/src/views/infra/codegen/components/index.ts +++ b/src/views/infra/codegen/components/index.ts @@ -1,5 +1,6 @@ import BasicInfoForm from './BasicInfoForm.vue' -import CloumInfoForm from './CloumInfoForm.vue' +import ColumInfoForm from './ColumInfoForm.vue' +import GenerateInfoForm from './GenerateInfoForm.vue' import ImportTable from './ImportTable.vue' import Preview from './Preview.vue' -export { BasicInfoForm, CloumInfoForm, ImportTable, Preview } +export { BasicInfoForm, ColumInfoForm, GenerateInfoForm, ImportTable, Preview } diff --git a/src/views/infra/codegen/index.vue b/src/views/infra/codegen/index.vue index 8337d2d4..591f99d3 100644 --- a/src/views/infra/codegen/index.vue +++ b/src/views/infra/codegen/index.vue @@ -1,56 +1,124 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <!-- 操作:导入 --> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.import')" - v-hasPermi="['infra:codegen:create']" - @click="openImportTable()" + <!-- 搜索 --> + <content-wrap> + <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true"> + <el-form-item label="表名称" prop="tableName"> + <el-input + v-model="queryParams.tableName" + placeholder="请输入表名称" + clearable + @keyup.enter="handleQuery" /> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:预览 --> - <XTextButton - preIcon="ep:view" - :title="t('action.preview')" - v-hasPermi="['infra:codegen:query']" - @click="handlePreview(row)" + </el-form-item> + <el-form-item label="表描述" prop="tableComment"> + <el-input + v-model="queryParams.tableComment" + placeholder="请输入表描述" + clearable + @keyup.enter="handleQuery" /> - <!-- 操作:编辑 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['infra:codegen:update']" - @click="handleUpdate(row.id)" + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + value-format="yyyy-MM-dd HH:mm:ss" + type="daterange" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['infra:codegen:delete']" - @click="deleteData(row.id)" - /> - <!-- 操作:同步 --> - <XTextButton - preIcon="ep:refresh" - :title="t('action.sync')" - v-hasPermi="['infra:codegen:update']" - @click="handleSynchDb(row)" - /> - <!-- 操作:生成 --> - <XTextButton - preIcon="ep:download" - :title="t('action.generate')" - v-hasPermi="['infra:codegen:download']" - @click="handleGenTable(row)" - /> - </template> - </XTable> - </ContentWrap> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" />搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button> + <el-button type="primary" v-hasPermi="['infra:codegen:create']" @click="openImportTable()"> + <Icon icon="ep:zoom-in" class="mr-5px" />{{ t('action.import') }} + </el-button> + </el-form-item> + </el-form> + </content-wrap> + + <!-- 列表 --> + <content-wrap> + <el-table v-loading="loading" :data="list"> + <el-table-column label="数据源" align="center" :formatter="dataSourceConfigNameFormat" /> + <el-table-column label="表名称" align="center" prop="tableName" width="200" /> + <el-table-column + label="表描述" + align="center" + prop="tableComment" + :show-overflow-tooltip="true" + width="120" + /> + <el-table-column label="实体" align="center" prop="className" width="200" /> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column + label="更新时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column + label="操作" + align="center" + width="300px" + class-name="small-padding fixed-width" + > + <template #default="scope"> + <el-button + link + type="primary" + @click="handlePreview(scope.row)" + v-hasPermi="['infra:codegen:preview']" + >预览</el-button + > + <el-button + link + type="primary" + @click="handleUpdate(scope.row.id)" + v-hasPermi="['infra:codegen:update']" + >编辑</el-button + > + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['infra:codegen:delete']" + >删除</el-button + > + <el-button + link + type="primary" + @click="handleSynchDb(scope.row)" + v-hasPermi="['infra:codegen:update']" + >同步</el-button + > + <el-button + link + type="primary" + @click="handleGenTable(scope.row)" + v-hasPermi="['infra:codegen:download']" + >生成代码</el-button + > + </template> + </el-table-column> + </el-table> + <pagination + v-show="total > 0" + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + <!-- 弹窗:导入表 --> <ImportTable ref="importRef" @ok="reload()" /> <!-- 弹窗:预览代码 --> @@ -59,19 +127,70 @@ <script setup lang="ts" name="Codegen"> import download from '@/utils/download' import * as CodegenApi from '@/api/infra/codegen' +import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig' import { CodegenTableVO } from '@/api/infra/codegen/types' -import { allSchemas } from './codegen.data' import { ImportTable, Preview } from './components' +import ContentWrap from '@/components/ContentWrap/src/ContentWrap.vue' +import { DataSourceConfigVO } from '@/api/infra/dataSourceConfig' +import { dateFormatter } from '@/utils/formatTime' -const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 +const { t } = useI18n() // 国际化 const { push } = useRouter() // 路由跳转 -// 列表相关的变量 -const [registerTable, { reload, deleteData }] = useXTable({ - allSchemas: allSchemas, - getListApi: CodegenApi.getCodegenTablePageApi, - deleteApi: CodegenApi.deleteCodegenTableApi + +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + tableName: undefined, + tableComment: undefined, + createTime: [] }) +const queryFormRef = ref() // 搜索的表单 + +const dataSourceConfigs = ref<DataSourceConfigVO[]>([]) // 数据源列表 + +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await CodegenApi.getCodegenTablePage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 初始化 **/ +onMounted(async () => { + getList() + dataSourceConfigs.value = await DataSourceConfigApi.getDataSourceConfigList() +}) + +// 数据源配置的名字 +const dataSourceConfigNameFormat = (row) => { + for (const config of dataSourceConfigs.value) { + if (row.dataSourceConfigId === config.id) { + return config.name + } + } + return '未知【' + row.leaderUserId + '】' +} // 导入操作 const importRef = ref() @@ -81,27 +200,39 @@ const openImportTable = () => { // 预览操作 const previewRef = ref() const handlePreview = (row: CodegenTableVO) => { - previewRef.value.show(row) + previewRef.value.openModal(row.id) } // 编辑操作 const handleUpdate = (rowId: number) => { push('/codegen/edit?id=' + rowId) } + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await CodegenApi.deleteCodegenTable(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} // 同步操作 -const handleSynchDb = (row: CodegenTableVO) => { +const handleSynchDb = async (row: CodegenTableVO) => { // 基于 DB 同步 const tableName = row.tableName - message - .confirm('确认要强制同步' + tableName + '表结构吗?', t('common.reminder')) - .then(async () => { - await CodegenApi.syncCodegenFromDBApi(row.id) - message.success('同步成功') - }) + try { + await message.confirm('确认要强制同步' + tableName + '表结构吗?', t('common.reminder')) + await CodegenApi.syncCodegenFromDB(row.id) + message.success('同步成功') + } catch {} } // 生成代码操作 const handleGenTable = async (row: CodegenTableVO) => { - const res = await CodegenApi.downloadCodegenApi(row.id) + const res = await CodegenApi.downloadCodegen(row.id) download.zip(res, 'codegen-' + row.className + '.zip') } </script> From bedcd2205eb1c3cb9cbb4ff6fe73adedf14156ad Mon Sep 17 00:00:00 2001 From: dlarmor <121919810@qq.com> Date: Mon, 27 Mar 2023 14:18:35 +0800 Subject: [PATCH 131/184] =?UTF-8?q?update:=20=E8=A7=A3=E5=86=B3=E5=90=88?= =?UTF-8?q?=E5=B9=B6=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/infra/codegen/components/GenerateInfoForm.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/infra/codegen/components/GenerateInfoForm.vue b/src/views/infra/codegen/components/GenerateInfoForm.vue index 9a64c800..92bac8dd 100644 --- a/src/views/infra/codegen/components/GenerateInfoForm.vue +++ b/src/views/infra/codegen/components/GenerateInfoForm.vue @@ -369,7 +369,7 @@ watch( ) onMounted(async () => { try { - const resp = await MenuApi.listSimpleMenusApi() + const resp = await MenuApi.getSimpleMenusList() menus.value = handleTree(resp) } catch {} }) From 51344a5967df87a90904e321ee8dd636c9ac5999 Mon Sep 17 00:00:00 2001 From: zhangjinlong <zhangjinlong@sdcreditech.com> Date: Mon, 27 Mar 2023 18:18:29 +0800 Subject: [PATCH 132/184] =?UTF-8?q?Vue3=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E5=85=AC=E4=BC=97=E5=8F=B7=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/dateUtils.ts | 79 ++++++ src/views/mp/statistics/index.vue | 404 +++++++++++++++++++++++++++++- 2 files changed, 482 insertions(+), 1 deletion(-) create mode 100644 src/utils/dateUtils.ts diff --git a/src/utils/dateUtils.ts b/src/utils/dateUtils.ts new file mode 100644 index 00000000..617a88af --- /dev/null +++ b/src/utils/dateUtils.ts @@ -0,0 +1,79 @@ +/** + * 将毫秒,转换成时间字符串。例如说,xx 分钟 + * + * @param ms 毫秒 + * @returns {string} 字符串 + */ +export function getDate(ms) { + const day = Math.floor(ms / (24 * 60 * 60 * 1000)) + const hour = Math.floor(ms / (60 * 60 * 1000) - day * 24) + const minute = Math.floor(ms / (60 * 1000) - day * 24 * 60 - hour * 60) + const second = Math.floor(ms / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60) + if (day > 0) { + return day + '天' + hour + '小时' + minute + '分钟' + } + if (hour > 0) { + return hour + '小时' + minute + '分钟' + } + if (minute > 0) { + return minute + '分钟' + } + if (second > 0) { + return second + '秒' + } else { + return 0 + '秒' + } +} + +export function beginOfDay(date) { + return new Date(date.getFullYear(), date.getMonth(), date.getDate()) +} + +export function endOfDay(date) { + return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999) +} + +export function betweenDay(date1, date2) { + date1 = convertDate(date1) + date2 = convertDate(date2) + // 计算差值 + return Math.floor((date2.getTime() - date1.getTime()) / (24 * 3600 * 1000)) +} + +export function formatDate(date, fmt) { + date = convertDate(date) + const o = { + 'M+': date.getMonth() + 1, //月份 + 'd+': date.getDate(), //日 + 'H+': date.getHours(), //小时 + 'm+': date.getMinutes(), //分 + 's+': date.getSeconds(), //秒 + 'q+': Math.floor((date.getMonth() + 3) / 3), //季度 + S: date.getMilliseconds() //毫秒 + } + if (/(y+)/.test(fmt)) { + // 年份 + fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) + } + for (const k in o) { + if (new RegExp('(' + k + ')').test(fmt)) { + fmt = fmt.replace( + RegExp.$1, + RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length) + ) + } + } + return fmt +} + +export function addTime(date, time) { + date = convertDate(date) + return new Date(date.getTime() + time) +} + +export function convertDate(date) { + if (typeof date === 'string') { + return new Date(date) + } + return date +} diff --git a/src/views/mp/statistics/index.vue b/src/views/mp/statistics/index.vue index 497f72ec..9411b674 100644 --- a/src/views/mp/statistics/index.vue +++ b/src/views/mp/statistics/index.vue @@ -1,3 +1,405 @@ <template> - <span>开发中</span> + <!-- 搜索工作栏 --> + <content-wrap> + <el-form ref="queryForm" class="-mb-15px" :inline="true" label-width="68px"> + <el-form-item label="公众号" prop="accountId"> + <el-select v-model="accountId" @change="getSummary"> + <el-option + v-for="item in accounts" + :key="parseInt(item.id)" + :label="item.name" + :value="parseInt(item.id)" + /> + </el-select> + </el-form-item> + <el-form-item label="时间范围" prop="dateRange"> + <el-date-picker + v-model="dateRange" + style="width: 260px" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="defaultTime" + @change="getSummary" + /> + </el-form-item> + </el-form> + </content-wrap> + + <!-- 图表 --> + <content-wrap> + <el-row> + <el-col :span="12" class="card-box"> + <el-card> + <template #header> + <div> + <span>用户增减数据</span> + </div> + </template> + <div class="el-table el-table--enable-row-hover el-table--medium"> + <div ref="userSummaryChartRef" style="height: 420px"></div> + </div> + </el-card> + </el-col> + <el-col :span="12" class="card-box"> + <el-card> + <template #header> + <div> + <span>累计用户数据</span> + </div> + </template> + <div class="el-table el-table--enable-row-hover el-table--medium"> + <div ref="userCumulateChartRef" style="height: 420px"></div> + </div> + </el-card> + </el-col> + <el-col :span="12" class="card-box"> + <el-card> + <template #header> + <div> + <span>消息概况数据</span> + </div> + </template> + <div class="el-table el-table--enable-row-hover el-table--medium"> + <div ref="upstreamMessageChartRef" style="height: 420px"></div> + </div> + </el-card> + </el-col> + <el-col :span="12" class="card-box"> + <el-card> + <template #header> + <div> + <span>接口分析数据</span> + </div> + </template> + <div class="el-table el-table--enable-row-hover el-table--medium"> + <div ref="interfaceSummaryChartRef" style="height: 420px"></div> + </div> + </el-card> + </el-col> + </el-row> + </content-wrap> </template> + +<script setup lang="ts" name="MpStatistics"> +// 引入基本模板 +import * as echarts from 'echarts' +import { + getInterfaceSummary, + getUpstreamMessage, + getUserCumulate, + getUserSummary +} from '@/api/mp/statistics' +import { addTime, beginOfDay, betweenDay, endOfDay, formatDate } from '@/utils/dateUtils' +import { getSimpleAccountList } from '@/api/mp/account' + +const message = useMessage() // 消息弹窗 + +const defaultTime = ref<[Date, Date]>([ + new Date(2000, 1, 1, 0, 0, 0), + new Date(2000, 2, 1, 23, 59, 59) +]) +const dateRange = ref([ + beginOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24 * 7)), + endOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24)) +]) // -1 天 +const accountId = ref() +const accounts = ref([ + { + id: '0', + name: '' + } +]) + +const userSummaryChartRef = ref() +const userCumulateChartRef = ref() +const upstreamMessageChartRef = ref() +const interfaceSummaryChartRef = ref() + +const xAxisDate = ref([] as any[]) // X 轴的日期范围 +const userSummaryOption = reactive({ + // 用户增减数据 + color: ['#67C23A', '#e5323e'], + legend: { + data: ['新增用户', '取消关注的用户'] + }, + tooltip: {}, + xAxis: { + data: [] as any[] // X 轴的日期范围 + }, + yAxis: { + minInterval: 1 + }, + series: [ + { + name: '新增用户', + type: 'bar', + label: { + show: true + }, + barGap: 0, + data: [] as any[] // 新增用户的数据 + }, + { + name: '取消关注的用户', + type: 'bar', + label: { + show: true + }, + data: [] as any[] // 取消关注的用户的数据 + } + ] +}) +const userCumulateOption = reactive({ + // 累计用户数据 + legend: { + data: ['累计用户量'] + }, + xAxis: { + type: 'category', + data: [] as any[] + }, + yAxis: { + minInterval: 1 + }, + series: [ + { + name: '累计用户量', + data: [] as any[], // 累计用户量的数据 + type: 'line', + smooth: true, + label: { + show: true + } + } + ] +}) +const upstreamMessageOption = reactive({ + // 消息发送概况数据 + color: ['#67C23A', '#e5323e'], + legend: { + data: ['用户发送人数', '用户发送条数'] + }, + tooltip: {}, + xAxis: { + data: [] as any[] // X 轴的日期范围 + }, + yAxis: { + minInterval: 1 + }, + series: [ + { + name: '用户发送人数', + type: 'line', + smooth: true, + label: { + show: true + }, + data: [] as any[] // 用户发送人数的数据 + }, + { + name: '用户发送条数', + type: 'line', + smooth: true, + label: { + show: true + }, + data: [] as any[] // 用户发送条数的数据 + } + ] +}) +const interfaceSummaryOption = reactive({ + // 接口分析况数据 + color: ['#67C23A', '#e5323e', '#E6A23C', '#409EFF'], + legend: { + data: ['被动回复用户消息的次数', '失败次数', '最大耗时', '总耗时'] + }, + tooltip: {}, + xAxis: { + data: [] as any[] // X 轴的日期范围 + }, + yAxis: {}, + series: [ + { + name: '被动回复用户消息的次数', + type: 'bar', + label: { + show: true + }, + barGap: 0, + data: [] as any[] // 被动回复用户消息的次数的数据 + }, + { + name: '失败次数', + type: 'bar', + label: { + show: true + }, + data: [] as any[] // 失败次数的数据 + }, + { + name: '最大耗时', + type: 'bar', + label: { + show: true + }, + data: [] as any[] // 最大耗时的数据 + }, + { + name: '总耗时', + type: 'bar', + label: { + show: true + }, + data: [] as any[] // 总耗时的数据 + } + ] +}) + +onMounted(async () => { + // 获取公众号下拉列表 + await getAccountList() + // 加载数据 + getSummary() +}) +const getAccountList = async () => { + const data = await getSimpleAccountList() + accounts.value = data + // 默认选中第一个 + if (accounts.value.length > 0) { + accountId.value = accounts.value[0].id + } +} +const getSummary = () => { + // 如果没有选中公众号账号,则进行提示。 + if (!accountId) { + message.error('未选中公众号,无法统计数据') + return false + } + // 必须选择 7 天内,因为公众号有时间跨度限制为 7 + if (betweenDay(dateRange.value[0], dateRange.value[1]) >= 7) { + message.error('时间间隔 7 天以内,请重新选择') + return false + } + xAxisDate.value = [] + const days = betweenDay(dateRange.value[0], dateRange.value[1]) // 相差天数 + for (let i = 0; i <= days; i++) { + xAxisDate.value.push( + formatDate(addTime(dateRange.value[0], 3600 * 1000 * 24 * i), 'yyyy-MM-dd') + ) + } + + // 初始化图表 + initUserSummaryChart() + initUserCumulateChart() + initUpstreamMessageChart() + interfaceSummaryChart() +} +const initUserSummaryChart = async () => { + userSummaryOption.xAxis.data = [] + userSummaryOption.series[0].data = [] + userSummaryOption.series[1].data = [] + try { + const data = await getUserSummary({ + accountId: accountId.value, + date: [ + formatDate(dateRange.value[0], 'yyyy-MM-dd HH:mm:ss'), + formatDate(dateRange.value[1], 'yyyy-MM-dd HH:mm:ss') + ] + }) + + userSummaryOption.xAxis.data = xAxisDate.value + // 处理数据 + xAxisDate.value.forEach((date, index) => { + data.forEach((item) => { + // 匹配日期 + const refDate = formatDate(new Date(item.refDate), 'yyyy-MM-dd') + if (refDate.indexOf(date) === -1) { + return + } + // 设置数据到对应的位置 + userSummaryOption.series[0].data[index] = item.newUser + userSummaryOption.series[1].data[index] = item.cancelUser + }) + }) + // 绘制图表 + const userSummaryChart = echarts.init(userSummaryChartRef.value) + userSummaryChart.setOption(userSummaryOption) + } catch {} +} +const initUserCumulateChart = async () => { + userCumulateOption.xAxis.data = [] + userCumulateOption.series[0].data = [] + // 发起请求 + try { + const data = await getUserCumulate({ + accountId: accountId.value, + date: [ + formatDate(dateRange.value[0], 'yyyy-MM-dd HH:mm:ss'), + formatDate(dateRange.value[1], 'yyyy-MM-dd HH:mm:ss') + ] + }) + userCumulateOption.xAxis.data = xAxisDate.value + // 处理数据 + data.forEach((item, index) => { + userCumulateOption.series[0].data[index] = item.cumulateUser + }) + // 绘制图表 + const userCumulateChart = echarts.init(userCumulateChartRef.value) + userCumulateChart.setOption(userCumulateOption) + } catch {} +} +const initUpstreamMessageChart = async () => { + upstreamMessageOption.xAxis.data = [] + upstreamMessageOption.series[0].data = [] + upstreamMessageOption.series[1].data = [] + // 发起请求 + try { + const data = await getUpstreamMessage({ + accountId: accountId.value, + date: [ + formatDate(dateRange.value[0], 'yyyy-MM-dd HH:mm:ss'), + formatDate(dateRange.value[1], 'yyyy-MM-dd HH:mm:ss') + ] + }) + upstreamMessageOption.xAxis.data = xAxisDate.value + // 处理数据 + data.forEach((item, index) => { + upstreamMessageOption.series[0].data[index] = item.messageUser + upstreamMessageOption.series[1].data[index] = item.messageCount + }) + // 绘制图表 + const upstreamMessageChart = echarts.init(upstreamMessageChartRef.value) + upstreamMessageChart.setOption(upstreamMessageOption) + } catch {} +} +const interfaceSummaryChart = async () => { + interfaceSummaryOption.xAxis.data = [] + interfaceSummaryOption.series[0].data = [] + interfaceSummaryOption.series[1].data = [] + interfaceSummaryOption.series[2].data = [] + interfaceSummaryOption.series[3].data = [] + // 发起请求 + try { + const data = await getInterfaceSummary({ + accountId: accountId.value, + date: [ + formatDate(dateRange.value[0], 'yyyy-MM-dd HH:mm:ss'), + formatDate(dateRange.value[1], 'yyyy-MM-dd HH:mm:ss') + ] + }) + interfaceSummaryOption.xAxis.data = xAxisDate.value + // 处理数据 + data.forEach((item, index) => { + interfaceSummaryOption.series[0].data[index] = item.callbackCount + interfaceSummaryOption.series[1].data[index] = item.failCount + interfaceSummaryOption.series[2].data[index] = item.maxTimeCost + interfaceSummaryOption.series[3].data[index] = item.totalTimeCost + }) + // 绘制图表 + const interfaceSummaryChart = echarts.init(interfaceSummaryChartRef.value) + interfaceSummaryChart.setOption(interfaceSummaryOption) + } catch {} +} +</script> From c5a3fa1f94d981fc498f92153ed49e300518920f Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Tue, 28 Mar 2023 00:10:42 +0800 Subject: [PATCH 133/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E5=AE=9E=E4=BE=8B=E7=9A=84=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/router/modules/remaining.ts | 2 +- src/types/auto-components.d.ts | 2 + .../detail/TaskUpdateAssigneeForm.vue | 81 +++++++++++ .../{detail.vue => detail/index.vue} | 136 +++--------------- 4 files changed, 107 insertions(+), 114 deletions(-) create mode 100644 src/views/bpm/processInstance/detail/TaskUpdateAssigneeForm.vue rename src/views/bpm/processInstance/{detail.vue => detail/index.vue} (76%) diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index 58d5601b..380e3618 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -284,7 +284,7 @@ const remainingRouter: AppRouteRecordRaw[] = [ }, { path: '/process-instance/detail', - component: () => import('@/views/bpm/processInstance/detail.vue'), + component: () => import('@/views/bpm/processInstance/detail/index.vue'), name: 'BpmProcessInstanceDetail', meta: { noCache: true, diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index cb8ff559..480691fc 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -73,6 +73,8 @@ declare module '@vue/runtime-core' { ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] ElTag: typeof import('element-plus/es')['ElTag'] + ElTimeline: typeof import('element-plus/es')['ElTimeline'] + ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTransfer: typeof import('element-plus/es')['ElTransfer'] ElTree: typeof import('element-plus/es')['ElTree'] diff --git a/src/views/bpm/processInstance/detail/TaskUpdateAssigneeForm.vue b/src/views/bpm/processInstance/detail/TaskUpdateAssigneeForm.vue new file mode 100644 index 00000000..7f9cfd48 --- /dev/null +++ b/src/views/bpm/processInstance/detail/TaskUpdateAssigneeForm.vue @@ -0,0 +1,81 @@ +<template> + <Dialog title="转派审批人" v-model="modelVisible" width="500"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="110px" + v-loading="formLoading" + > + <el-form-item label="新审批人" prop="assigneeUserId"> + <el-select v-model="formData.assigneeUserId" clearable style="width: 100%"> + <el-option + v-for="item in userList" + :key="item.id" + :label="item.nickname" + :value="item.id" + /> + </el-select> + </el-form-item> + </el-form> + <template #footer> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import * as TaskApi from '@/api/bpm/task' +import * as UserApi from '@/api/system/user' + +const modelVisible = ref(false) // 弹窗的是否展示 +const formLoading = ref(false) // 表单的加载中 +const formData = ref({ + id: '', + assigneeUserId: undefined +}) +const formRules = ref({ + assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }] +}) + +const formRef = ref() // 表单 Ref +const userList = ref<any[]>([]) // 用户列表 + +/** 打开弹窗 */ +const open = async (id: string) => { + modelVisible.value = true + resetForm() + formData.value.id = id + // 获得用户列表 + userList.value = await UserApi.getSimpleUserList() +} +defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + await TaskApi.updateTaskAssignee(formData.value) + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: '', + assigneeUserId: undefined + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/bpm/processInstance/detail.vue b/src/views/bpm/processInstance/detail/index.vue similarity index 76% rename from src/views/bpm/processInstance/detail.vue rename to src/views/bpm/processInstance/detail/index.vue index a34d8a1a..e811475f 100644 --- a/src/views/bpm/processInstance/detail.vue +++ b/src/views/bpm/processInstance/detail/index.vue @@ -33,31 +33,21 @@ </el-form-item> </el-form> <div style="margin-left: 10%; margin-bottom: 20px; font-size: 14px"> - <XButton - pre-icon="ep:select" - type="success" - title="通过" - @click="handleAudit(item, true)" - /> - <XButton - pre-icon="ep:close" - type="danger" - title="不通过" - @click="handleAudit(item, false)" - /> - <XButton - pre-icon="ep:edit" - type="primary" - title="转办" - @click="handleUpdateAssignee(item)" - /> - <XButton - pre-icon="ep:position" - type="primary" - title="委派" - @click="handleDelegate(item)" - /> - <XButton pre-icon="ep:back" type="warning" title="委派" @click="handleBack(item)" /> + <el-button type="success" @click="handleAudit(item, true)"> + <Icon icon="ep:select" /> 通过 + </el-button> + <el-button type="danger" @click="handleAudit(item, false)"> + <Icon icon="ep:close" /> 不通过 + </el-button> + <el-button type="primary" @click="openTaskUpdateAssigneeForm(item.id)"> + <Icon icon="ep:edit" /> 转办 + </el-button> + <el-button type="primary" @click="handleDelegate(item)"> + <Icon icon="ep:position" /> 委派 + </el-button> + <el-button type="warning" @click="handleBack(item)"> + <Icon icon="ep:back" /> 回退 + </el-button> </div> </el-col> </el-card> @@ -85,7 +75,7 @@ processInstance.businessKey " > - <XButton type="primary" preIcon="ep:view" title="点击查看" /> + <el-button type="primary"><Icon icon="ep:view" /> 点击查看</el-button> </router-link> </div> </el-card> @@ -153,42 +143,8 @@ /> </el-card> - <!-- 对话框(转派审批人) --> - <XModal v-model="updateAssigneeVisible" title="转派审批人" width="500"> - <el-form - ref="updateAssigneeFormRef" - :model="updateAssigneeForm" - :rules="updateAssigneeRules" - label-width="110px" - > - <el-form-item label="新审批人" prop="assigneeUserId"> - <el-select v-model="updateAssigneeForm.assigneeUserId" clearable style="width: 100%"> - <el-option - v-for="item in userOptions" - :key="parseInt(item.id)" - :label="item.nickname" - :value="parseInt(item.id)" - /> - </el-select> - </el-form-item> - </el-form> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton - type="primary" - :title="t('action.save')" - :loading="updateAssigneeLoading" - @click="submitUpdateAssigneeForm" - /> - <!-- 按钮:关闭 --> - <XButton - :loading="updateAssigneeLoading" - :title="t('dialog.close')" - @click="updateAssigneeLoading = false" - /> - </template> - </XModal> + <!-- 弹窗:转派审批人 --> + <TaskUpdateAssigneeForm ref="taskUpdateAssigneeFormRef" @success="getDetail" /> </ContentWrap> </template> <script setup lang="ts"> @@ -200,13 +156,12 @@ import * as TaskApi from '@/api/bpm/task' import * as ActivityApi from '@/api/bpm/activity' import { formatPast2 } from '@/utils/formatTime' import { setConfAndFields2 } from '@/utils/formCreate' -// import { OptionAttrs } from '@form-create/element-ui/types/config' import type { ApiAttrs } from '@form-create/element-ui/types/config' import { useUserStore } from '@/store/modules/user' +import TaskUpdateAssigneeForm from './TaskUpdateAssigneeForm.vue' const { query } = useRoute() // 查询参数 const message = useMessage() // 消息弹窗 -const { t } = useI18n() // 国际化 const { proxy } = getCurrentInstance() as any // ========== 审批信息 ========== @@ -294,55 +249,11 @@ const getTimelineItemType = (item) => { } // ========== 审批记录 ========== -const updateAssigneeVisible = ref(false) -const updateAssigneeLoading = ref(false) -const updateAssigneeForm = ref({ - id: undefined, - assigneeUserId: undefined -}) -const updateAssigneeRules = ref({ - assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }] -}) -const updateAssigneeFormRef = ref() -const userOptions = ref<any[]>([]) -// 处理转派审批人 -const handleUpdateAssignee = (task) => { - // 设置表单 - resetUpdateAssigneeForm() - updateAssigneeForm.value.id = task.id - // 设置为打开 - updateAssigneeVisible.value = true -} - -// 提交转派审批人 -const submitUpdateAssigneeForm = async () => { - // 1. 校验表单 - const elForm = unref(updateAssigneeFormRef) - if (!elForm) return - const valid = await elForm.validate() - if (!valid) return - - // 2.1 提交审批 - updateAssigneeLoading.value = true - try { - await TaskApi.updateTaskAssignee(updateAssigneeForm.value) - // 2.2 设置为隐藏 - updateAssigneeVisible.value = false - // 加载最新数据 - getDetail() - } finally { - updateAssigneeLoading.value = false - } -} - -// 重置转派审批人表单 -const resetUpdateAssigneeForm = () => { - updateAssigneeForm.value = { - id: undefined, - assigneeUserId: undefined - } - updateAssigneeFormRef.value?.resetFields() +/** 转派审批人 */ +const taskUpdateAssigneeFormRef = ref() +const openTaskUpdateAssigneeForm = (id: string) => { + taskUpdateAssigneeFormRef.value.open(id) } /** 处理审批退回的操作 */ @@ -375,7 +286,6 @@ const activityList = ref([]) // ========== 初始化 ========== onMounted(() => { - // 加载详情 getDetail() // 加载用户的列表 UserApi.getSimpleUserList().then((data) => { From 16c5e3ea0171c7e147072c8ca49513ea81e2c9fe Mon Sep 17 00:00:00 2001 From: wuxiran <wuxiran@outlook.com> Date: Tue, 28 Mar 2023 01:18:37 +0800 Subject: [PATCH 134/184] =?UTF-8?q?1=E3=80=81=E5=BE=AE=E4=BF=A1=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E6=9B=B4=E6=96=B0=E7=AC=AC=E4=B8=89=E6=B3=A2=EF=BC=8C?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E9=A1=B5=E9=9D=A2=E8=83=BD=E6=AD=A3=E5=B8=B8?= =?UTF-8?q?=E5=B1=95=E7=A4=BA=E4=BA=86=E3=80=82=E4=BD=86=E6=98=AF=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E5=8F=AF=E8=83=BD=E8=BF=98=E8=A6=81=E9=94=99=E4=B9=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/imgs/profile.jpg | Bin 0 -> 7885 bytes src/assets/imgs/wechat.png | Bin 0 -> 1881 bytes .../mp/components/wx-material-select/main.vue | 302 +++++++ src/views/mp/components/wx-msg/main.vue | 309 ++++--- src/views/mp/components/wx-reply/main.vue | 753 ++++++++++-------- .../mp/components/wx-video-play/main.vue | 8 +- src/views/mp/message/index.vue | 39 +- 7 files changed, 904 insertions(+), 507 deletions(-) create mode 100644 src/assets/imgs/profile.jpg create mode 100644 src/assets/imgs/wechat.png create mode 100644 src/views/mp/components/wx-material-select/main.vue diff --git a/src/assets/imgs/profile.jpg b/src/assets/imgs/profile.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e4bcf879884f76657292f2e792bf9efbb4090f63 GIT binary patch literal 7885 zcmbuEWl$Vlx3*_+2sR8pKyW8G!Gl{E+$C6m;1)=L-~<M@;6rd3+&zL$aJNB%I|&W} z0*B{$-><%^^Y^UXf4aJQ@4l+;d#|-uKg>U@0*KX=RFwcgAOHY-9Ds*KKsW&F-@wMf z!N$YG#ls^cz{e*bBqJhz9AxyQl%$UrJuMRrH4PK3AUiWNyP$*suYiP#jEstjfr*b# zF7W?4!9zCygahaY^rHhA0B9f}ItcjC3!nu6(9qC<|9tCz0|N^k6NrWlz`=b$0dW5F zU~BN5Cs7V9yYHR&>nUuH`e`If0hiOFuWs1gt%FfSI5%4^rs_!KMZCtC5u3t}&@c;_ z^^f7sE&dPNVoV8wtCy;yeuY8Um6fe*;=A8Q>KEL9rLSBMYSx$zT*)=8Tj~g>g#1+q zX|XPEZg4l*`~+Q2TaWG{_C7RA&05y~w2-Txv#9UAgZ}(o%<GH(SN@7^MVpR(;r&;n zn)$=|CUU!If3Yed`fQv<p+8^&TAf9QJ*wv~guJr1L+_IhRP=b7cHf-GzIGM%oYZBr zu<@YVh&p*PR&AD)Lf5}X^E?L?Z7`9~_~)SAY>Dh7Ra0$0$CgLzu(*QEU{v4gh}1VC zB_K*~=s9vGC~tLwwMHB?d;TY9BW?ahqWojat7*qDilNTm=ZX%BB6UNrCVr1ecUib? z8;Wu=Ia%=v9tY*mkbyqBQ9;i(*_0Mzy`T6E4k5-~uLQIx73{RPO@H`$nfswXpmZFI zYu?4SgDxOreH%XQvwUwV?Ia&C{xu}iZ#~F(q}hv<2AS?(5T244Od8lVi<>9VZT)fS z%g2~3PmTesNqXzKz{#Ul78eE?q!nN5&K*)7qk+vcHmCg;l{c)r=R`m`x?TJRsLtwT z4_~BZF?AX!-0`)Zr$52qEFykwhjb8TjSFPuX=3+9!-gEjzs_Dge1FXCY|8{)<Mvem z(+z&Q*&Mlo`o%BmZ1<0oONP$5(dqqIgs74tZ_edmm&$xD{nd+_Tp+C1pYumQcTa-t z7vJDUI<A&R{(O`gH4yNR(Eh*D0BGnKKmaDzzdA!Be$-#4Qp)>akI0sFjW38jH&5sD zzz>3-7oez^fP`c_Z`mDhvPgu*aINr!hQ!g38N$wm&atRwnlM{aEkVetsWF*+dB1b! zqwwx9?`S#u?HoGp5Wwvx)z?-_Hd&_K7s#K+r+88ciLGvtQ)geqt+)K{g%-`e!kUiv zkCd}e2HW45rWXXVCR{!c?>nn4DY&vIm#v`^GM?eq>PT3*BL48A`MWUCTd4yu8%Y*X zZve(lt>!2-LZ!Wv=kBwVpI1ca*2?%LQ0i7z%szpW+d{R8Qjgpa;3Me&;syXQFtM=z z1xNn}e1W~oK)vZ_%;k~zaN5WIQ^~P!9K>HN^Mk;>f30+wZv^SQPC66HO4fZ2={KII z&x~imcK2suK4~8+>i9id48znFqPG^;l<fTDGVysk;0tggwmC-QMB_40>XQgzO#kN_ zU#bh^Gye@=k+XKm&P6LP@pdQaSbnvICv-V^$vXOeg@SblPhP2*GVFMf(&H6QQ7apF zbMnM@OnHBDuxkjQgNZq&$!K!9=x}oCH^vpJ1tWNhO&o*C+dBlS_^f5UeR>i;ahdEN z6+dM+z^iC6e*iq9a}hs+q5;v-G0*|%|L!l3HyD@zED#Bp0h^Scn2b??iCIucR_?!h z4G<Hsz(`|vifF-Q(N_=X-(!3CwCEFSpGz2FJ<H;J%8f^+Az3$>v>|Z<V@!47Fob`7 zK^3OOYm^jz=4#{I_x_NJi?O8Yo5}w8vr5*VV=#g~T)|oS=npkz+})zFz0VtTg546Q zlP#yS)~B)xCPfj?>iV&*NmVMP58VwSakK^6IaS%AwFJYuT0fxP;#0%HGT}a+ZUIc$ z@=83A7;?_WX(UYmQ@{7w3#gCL$uB2vB_S+Y153sFinyzauR529T0Ro_vMARuBCERW zY_u~n0uZ#9g?Wd=X|s_`Z)`zD4G)pbdlA)$P2Azue$BC;&EPa=YL5`O+~2><ju&8o z7gfu6J_|n+CWjDS2$`ibkkshM51&TyZ>8bFqM~7$-S+W5Pp>~zd$uX63z!5laO}sS zvq@U`?WYm8`O11B{vx0lr1*-Qszl6lAi47Lt!N`lnTcj-K%)5*KE*I&v!Xt;cQzUr zUFt8wy#}6Ay_fS4#|Bo{NA!4~aJNge-^Cwf!AS#Cd2C4SVd}q@50a)4S?e5yiAhf7 zT)OkxGWSovOdoh`p$@ZOrd6RC_Ib>rtn1aq02#TeDpi>e0CqEKnD;YBXOX|tVIuR1 z>myR)b<CY{AJvCgEv${d>MXWw?G0-!4v32nc#n4A286R_e+1rVJC3~5RW2}`eGcM5 zM$gw~i@H?w24_T_Cr^qJkc65A-+F6`x4QqDKE=RbBR#+At_i7?;ozSS-}<5@lVi_n z*23vyv{IGLJ7+quP*u|B?o>l1R5KB*13qe=HaTY*%3E)ypzF#k_=DyF%h=5YSgSG$ zR5b6F^*gFX@>)e}Ml>ejZE9v1WK;JyPHETW*nqMck{F(*Xd5%`54KF<nso3sN-w$* zW@H)X$?be0M2I@s%~t16e+OJP<@Xk8@aYnZA+0ZL)(;%smN`>M9;E2t-(Wjc+Z6No zGobOY=)UFik0DL^srCG_lF@5G%T(f}YsVLj@#%EYxF!lS7vnz4M2d1uw}=A~wU@HX z3)Ml^bKV^>QO&~SWUfMz>q2cPB&9rL_e;byNS~s)9Wz<JEx<R%(A^aYTa1lG$m5Ys z(l6$1C+zk0Z&UxO5!9~Dx&k$`Y1=pr<`b6vsd3IE=M9huKqNOtdOI$Iy=@FrI)4dM zd>Urt%(IT?v>P6~P<h{np{q>xvET#4xSWl3va`@?RUyU}K`o?SMskmIL~FmJ$eq64 z>t=5Bj4-#)+g{F4z&aL8ud#r{^=_KI*%w&6h*BSjYJc$r?pd0jS|Rc|X;K(7Kp=sV zYk!Y@kok?9$Q@g1eS#267_@ces@UWpdHwkuPYuDZeFWZk6q7jx=)mwsxUBFMR^DI+ zE^5$`#S}bD7w5d7E4Kk&dq46DM~Yoczg>FS+p>wX*3rKOpE{%uu9_m7K5b$j`i7vY zVNy6yPw+Y8Z&w^-D?iR0Z&<#?bx$PAw-)sEDzlrfcU3hrQcY)5f0)G^`5hVDLq8NG z+o}sk8GztqZ715g`17$t3JR!aO8N)nD&y9SmW^Sd<Aj_i*>SJip14Y>zbX)NC(6(U zkhCP@d_C^SCckb;g|?3IZ6AZSldX-DSr}5A>g!?BpbEQLMRfz*Y3~Nx^=QUQdBp%p zw&~i$SCHKAK8C~w`M>`94Jla`I>XV*(x;bIhzao0luAT;Ez8#X^~TgIW`<6Q`s7c% zF`FH;_iW#L^oOIcfDlsVoliH(tGuT!v@Rp_FhllnlB!m+`irQU-|`Mzh38eyvJ@ua zD`Kb~nZ8}K^L}~^(zBS=fXejxHX4^6@-YC$*;=88GVosSCB^C(<dyj{uH4jX>5?-2 zz!3)*E3@uj1~rx!c$A&nG`ifEtZkP&Pp~r1)M1IcM%TW&NrqG~yb5?Jrx`~&LDpH= z2H?*d&Fcri>Dce~E+4Z$sgp@SV>_DNxZxT%UFjih-(FZ@7$yazicF>wpL%t>t<ELF zLQY=aa3~c0Ed+;<G<F9u7o^yyL6*z-<eC>}>S<Gjiq7XsY!FJNT9D1~EyS-Mh0a7P z{_(RrW&eDy6R2oZi%I!zT}TZvMkQbc4HRWkZ`d5$*m}8^y7pajU{pf=Zh@DmFsfTp z)H<R@z%?(OJ+xC~e)T+>qre7DEg9F;4}IVW%r?a4Sug%>?8GJZ#f-EnpCm=sw_E<z zN^Hch0rxoHT@4T)wFa=7&vZ={3@}U-M4}8ygg%e*T^j)fLYrIydDDd8HlH#1b-O>* z^gI9#$CBZyD`YI)Iy*_d+!z=N+M^yI@!$}C_4k-Nl$b3YrS8-*9I_-_Zx%Ibbn`ce zRv%vsLIq`i+Z`J|zt@xN+t-uZ9bL6{H)5A$d*dG0NrJCPHiRorf@$t)Fe4*GveCwM z(R;dj_nBzo16QEzZ;T1A6vNcx@{%YRJh_jgOTlalMAoGvPo~fOogb8j5a{=`QIolF zM|@p-_Ixu8u5b1<|E}RiGHQ0kNUZ0tZ#O50yVG>VV~&;}emnsH(Sev)XgHXd|0HQ3 z8UP)H!N5;K43^cl!lY*u@CecIBxMqm3r$MSCzH4Kn!wVvsq6Z0&c=EHnDWLa@h6fQ z{-Js@b37JWRghRL!^H3Pl&?kB_LUV^Eqqj6)CTw)QsewvfF^0v((u{@JVPqgMVd2Q z)reYikc<s`s#@yzIeDwwD09!jwL#~v4{BK<Bx=QYlnJy@WMb|&F&gwqbRO68j$7Ft z%|lu>xfK*{t8NLuoG0YV3-a{|^tJ;dX@hfugGszNr}lcJW?S_XsaRsap?Hq)xC5lD zewU%O(nS#FtUCFe$4z`rA}>$MNeGS2g#JE6x*ijKRBPbf1~uXL%^@dWjLQcYgv>5! z!l5uY?kPtO`7EhC#=MKjwsih@m9vN|A4Z$dtiebc3t?}(Po(<SnY}<b@kL^OX9LL1 zaV6cQQkjq?(0Ij4w};h1su0RPCQq9iN@>|UKBYCeca}|NR@X35>gy^JuWM`U&LmCj z=sfu(6~Re_`GQ&T(l7ep)yv?8oT+9#+^Q-#W|i`lpkeVlZjQF`0Mc7zLq=8%@%*fj zPI+X4CQE1faL><q7p__wy^Le+J@hh6m;@RHI_QGh@%H=T+H-te8yMY<W6!Kptx@K# zo6O|F+b$P9W@0RPUi#UU7xJC>0^%JI5JYsilW(4T8vVOKw1bp0g{RmkKdh##mh}TZ zn$g?KtV*s>Lzj|*4hW*X-gIoK>yW8V-;f&#fIe>s*?XIBp?C0upjGjL)x9iul?b?Z zPBM6KEqMCknN(o6)1hPNO4j5c@~)YwEFgBbJOr9X&n@|+hCXCt(_Y^8iKkc>4dtpH zG4>KKuu}%!^QRbW<gp{n(mcS|P+NZn2e$E7mTXUrN3QB`bG<*pQR)&!4-%b4t;NL# z@F~#dHxM%DE|iJ@muIQwZ|L_0&pSjKwEDNt_x}VHTUsLvDYf@_S^~wfCru4Cp(crB zcTtcq+8z<y&G+%L(M2EIo>I|}DE0bLl_B)92|C!Rm@_VB4*$xF1B~9%hUCna7n<&b z2|ZHrI?XfEqzLD(RZT6Uq2r*I-E^0X?k4++14-V~fdWINihfgc+KJA;zKswSl$OfM zX_zavy>EU(O7zpZVi%*ji8*Pi7G`Gk(-YrL#xLE2VEP`F-a|waf@vZpD7=@fp^tVB zUrkm<N!N$#Tr97Iy9)HIgDM{YI9h7J;Cn^_XcLdGB^iZ#f&Ov=49NY`C~>Iwi${x| zdrU}YU1Vm|Wjy|`)Ic47&l1c2uHHRq+BEj#s-WgG#k9XBbGFERjc&$yGb6s;>JB~- zEiSEmF0WD6OUKK!DoA_`d84z}B;Y4WZ=P8@)Wv+u`Y^(<CiUm|nCro9Jy+F8Wv|PO z5vKB4oK4^Zpzlf$nm<|QR8z}nZ<Vqm)ZWoF^UA-MoRI_QMGWmM{%Q08c>e&%=xcre z*qfvV7kwMXo+FuN`tb4_|CK-~maFJV=1C}5-D3W!^ppt|x^YQ;?YE=$2f&X=t;RA- zvG1QQjzvC(dVM`p(n6atDe@yEx=i<n$QC;uIGvTZwv1{w<6+x{Sy+seeBr(ceg{@H zY&f*X>hlNcv?4>IF5`A1%sZKHkjM@G<T!l;;x=0h@D*79o>q?C#PBP7tM2QW`>_1_ z6Y~ro2>VTnxUk>LH_k`1t=PN%qKEXb6(ujK&h*<1gwuKzG@ysX2O}re&np7k18xrz z8p!jfx!YG(<GfCu%0E`0UJ10VJioV_hiY%Wi>$xh<4{UYnRIIq<=xCvo^b5mu~Rmm znZAy)sAl<=IFw+`$$(FIwt&*KUm6G)yEUJ?u<@~p@+mwYn=D5jP?o;rzO14yU)pn> zhUicDwVQ@<6v{#Smd(DYb_0K<^<s|#*ES&1OxQg(U&k#FlTR)<^kok%1#QS6nPrzB z8P2nm1FL`4c6uZdUPu3S5|j9hI3nL0ijFCJ061SLH`Xv;fLe!9{!DT(AXHrVbM*!y zcHAOX+{{oqTrIG`QSt`=O|9ki;2~F<%=f4CQ>G;zpBgl+o23-$v)au*Uc+?qa?RrH z44S^1H-iRGragXP&N)G#(}K&hA&2O55;#v)k9e%xL1~RlHA{f{3gK5X{t_#gLLZ+_ z_m0`@z0DHWxjiJZ9nL>uH$`7}KD5bKJMK-Hs!N?K-6h*0N3YvMqP-K3|D*<E(A%l_ zk-6M{!Ng&J-DhgeT8?VGd{J)}QN2a0_BD-4hRLo8c$<;?2}(!w=K*l{$!+Ty#%Ue0 zNJ(1D=0rhGS=SzYPZ$+xNm3<Mt$L~-9@icp1jl*0&a~8`x4}`0UOL;qPBFT=?HDwx zN3_nr*WfkVL-*O{M^y0_n9!67VagBTV>T^5bPi_x;CJVOcIyJfm6b(gR*3;P3DpmP zQmB65?HhP1@Y{*dRJu;hGQ##$bcyP4Yjj7Oo%@4=dnq$)br6{tb|95nIqt9^B(7fL z?XdFe%8ut*g{pC-6B<*4C#VfAfU!lpE35^oLRsInvTzY}O^haD1!l{Zd%Cv5B>Jbm zXII^A=(wjP7NjBaeZ}7G)kwrO(k|QFysbQzC-t5fxdYa~J&mN(9>y*T9M$_7&2eIY z{RjFirk!02;a1-5$x7;iZ5N}0HH$*`qhBsHcy1&90KmTt<xK86&GUl8K;ShkV053z zE9D|nV!M{58yW1gVE<;l4bFYN1bKD~R^(}{Z6u8g8Bv@UH0?OH3s=3XjzB~ZbLR;E z&lW1wlYQ#>g^>q2`7skdE23d2w~mr{$A|6x{z1bBz8U!Hk$3`+*i|U{z0*ta(TN*V zG5!nYZ+rTi&L0vev)-D0;^ME%E+ak<!9{N|epKlf1ny;(r8MykY-G8ta^8q$h^&)U zp4f_QkZo#NI=Bxed82tovb?im2o<sJ2(%)vpadDs_k=wkfNQXUJ%skUcx`e!WLqS0 z5bGNFMRA!#su$oIbhgZWdzF~;qZ>+_TouNwfx+3@tMhkJ>jq?%Z>!%&Z^^Q=ytCN> z6NpfOJ+P*>a=E9by303*xSw+7Ay;(6ZX<8|ESR1MTIsz`p+4oq?IvP5qzx5~mtwf- zqD+F9kAw>^mty#a;7q>i`at}fB<9?HO@eQUDp6|K#{}zW5*uX!lpIrGFS|#WV%)Pn z076I!0aqS7{G@)DM1yRCQE<!e)U~S~3n60gOSB1<6SJ4Hn~kT$Q?lRJmV$N_7j3V? zK@@Wh*9sdAHuKNn!`oh{%I92=GFvgWSjzt7{-z&x{a7n#BpCLRDIuixE4q7&cZ`-) ztydvZog?~x*j^S(u?zSaU#CqgbqzB}YA}XuVbGo25cXZD4zE~6D9m5}7CA({T3s7F zM_f3RR#tO+i@YclY6SlnRy>A$?zX%NIRnS4^}ok*w!?)V2~gWKP-b(ldkFdI@P&jW z?O<<(hYdFktgb>6-dyS3(DhoJ<H9vqS&`t!3X>lMKm($oV_{%Ewt@bQe=AH927U}; zMgdt5u;4#crd&uJDU*DXXXwO#`hh^;3qYQmx?C4$vVHXD`U);$g%alo0w$A*_aUtZ zXk>3j*9(HvbrB!q4ae0S%{wx(1uy{z?4l;d=cyU*<M?GJWM0<=55~`DD!){mq_TE% zO|GzYsG~n2|I2_H2Rj8RaUn|6E8E3bEmhY_T0SB1jT{jOorMr9j@flQrFJEEg4kZU zBr$kq1A||No6`z!&p3O(DOp2ZG!L|+UyE>!!*ish^rK2r(&P!<Wo_+wYE^tDfpz^y zO|YclMHUVb#<k8mDvVAaBSj=3CcJXLtcb7#yF2&&Ci&N!*CEo3y;J&EUpmx`btzJ# zgO6OB?{|7|g{imo2GnnCseh|wmv5Q~=Gl07frW0nyDF8=X#|}|Yw5nLrR(7_xH6V} z5OdLw%=?Cta)iDmd}4A5)?&5Jj2pJjX8ZYmQg<s`G22o5H<x4BZ_fM^-b1_YkRgr< zM4~NExN{%;%W3W4ltc(5nbvai0q_PhV%3Gib8v@nDoB>gnoRJiN~3LU(M$AbXFS=m z*-MgY5kkNgAVV=jeyhj-JC&>fIsLAUQ!8Z9ti(m{0pR}SYevE5&q>SLLFa*pjMO+q zX3k|ZfM#(aEyL*=Ggu_9B<^i##99v|lv}2e+q<qgeUB>7LE(6CC_4KxdR`;FwULUj zTjzA@PNA|Y;_ox2$92MUSfEL@61HzIX9M%9z$zsQp^%c6=doVFgV>>T3~%PF(Mr<A zUed5hN9ai6Nnw<~czsDcwy~txMORm5K!#)K9fwf@70Nc4(q75o;|u+B8zZkEKtqo8 z^G$Tu4;MEFbrXYA)5Zrt%JtUA(W7EQdIbj+^Iqqq4$5;(rGl%iPpE$JDaT>e=Hyv= zB307?hoIhje({W%?(^+blc5!%Jkh9lQAne95p~om`E!vNRRd&MYKWsV2Ga>-2n_1< zGm5^!8ALJ>@N=-#a5^ke*;rbt_n%d@h^~^18(`YZWE4wIBPV$od+%@vH!7W@;<5<G z4|oMTNz%F9p}|o<fMJycHo(>#g+QO*5IziCUS57RWtJm|P5{=bccfA2iFlNDm3BwJ z7%nWn<9N{SyQYyQ39Ma6%KA-1St==o>+wprEzcKmpD>k3a9z|8VFYVf=K-fzOTg^q zif~kAnX|;8S*7(Zn><*v^QJJ=y!fjuu+h1ncb`>neXmVOa6eW<@J--L*uvUx$}ng^ zxELc$Xeq~XJ6?1x{STIXZ4RkFa+mQJRGoSdKAt`Nl+i)!HiRNWJ~O6dYn#uD@@^;S z(f-dK?f>ZiNB2MeY5ITm2ciGtei8vpMp<nSFuj}=R!C@FQvQF=l#ku=7k~iDK^A*? ze+3L>@{PpEL52M~w0tF&vbb<FOMdzsJ44FxI&t>QPEElrA6=QuL~yc=sjRq_(PqW` zW}X(?72&a5m(vxoCiSq#ae|?X91-675Ap=>U4_@Se$rE;!B2ABqznDkbH>LC1~U#P zB$RtLxd|7)b8EzxU~LcS_O*s|7uc~Iak1tujomfGd{`jDC$?&TRop<cqHrBNzmwAL zKHqPaN81wriFzd0H~fWseD=PeG*kGDVI!oc^WHvt@7kfieREK*Aa0;-?OV6Tt0zyk z*9BOW$n{kKayc5(jL~FctY<x+0+igWEY1X+5<~bZe6hMqqa5PiPG{syR+Un$&9{zn zMGU2DNBpo@xQK~^k>w>6CPXPZ)46r7mRYl1+Hu-*2ij|dgr9)lkKbm%=DdsMyA*qM zWt7Pu02`2`mDj)E(s2I!ZCdyPKo>$74fid^Y9gc`Gz-xk&*+HK{`mR<K-oXW+4jh@ zQ4v|=#rGC&ov`W3Wju_QNO8aaJ}i`<=Qic<u&sXD6>B*5p!F{?EVD=i6o!Ger8l%J zsvcIXyIh#11*ozbupbd@F_5q9FCQ+-Q7|uXWf_H#tNX$v^e}Pv>O2he1|_GSyxo*P zw4wK2DO>K@e!=?scTftyz=dIzY+Y3AR!p>P>f4FF5Axyg9+o>82BZD&FxB97<m^I5 ztx&#Zr!~(T!wmJDqgz2xWwh_Jjg3ShSHX&4y<N))@kob+aAX4B>#rsD`G){<%aO^A z>cOSq!5_sU6`2GJU<yCayEfX`?>!}X@eb<d=79=SYH8$eDDrQ`m0#tP)jwkan44ky zXN26ua0V(TepmUqZDr~PW7W-reb^837^#a0a7~h<VBKa!M+iIsmT3F6qR%M}&LBYa z;W%&NDWW7^lN=K-;=W($;y6*!Z>Iy+{d$%rMsxY%<sGiOT0=2I^ZBn}GOf#(Os*^r zIKgi$4=wheB@fegalT&aoA;DXbiw%?!HDARH6PxiTdFn-Q2ji9nL<>`)On{azhwdA zI&%BFm7<6ya)zSa*&y~M$HAfNjOC9=dY36|1@YZya(jUjImq%i0_0_*b=C8YHL~%8 z;t@^7A+`6@>B4FIO{<?N@+d|h9G0hbal8YMdYJAaVyexJ)tDHnI2_1B9;>^=LgarO NvOP|&09Jii_#cBOpN9Yd literal 0 HcmV?d00001 diff --git a/src/assets/imgs/wechat.png b/src/assets/imgs/wechat.png new file mode 100644 index 0000000000000000000000000000000000000000..6afc5e41cfc29d258e0c6135981a7a09b7bcf126 GIT binary patch literal 1881 zcmV-f2d4OmP)<h;3K|Lk000e1NJLTq0021v001%w1^@s6=HGKh00001b5ch_0Itp) z=>Px+6G=otRA@uxT5F6HRTlovt)4-Yt`>DxqM{h1piEaoG!it+;>Ku9<e|d&%*J&M zBTQEV$!2|FcJPt7Mv>`iG12%)6qSb>G)jC#QP~(>XJx8!Ncf>R3nRXU>6!tR>8fLH z&-BPlcUM(+R|8q^kNH#I`M&erd#dK%bDQux7!;JpamncoOxXzv({Uli+0)aoy&Fxi zgW|7-ry`T%jK<l5l?5Q4!N3FpCIAQ(Nn|e=dzh#la5kg9ersQI`w!J5TuH2NlVM}p z#e$r9%$Nt@oJz!oV%`O?RoP#+dfy%GnJRdEhkBYEyDD~SeaN|&iEi=D>JPYsh*xLg z=_mZbD@VDSjI=d2u_EpTV{+vZ?vV_<2cWG_H(#rjMoE)uN^1inQ7hpBUdB=@VJ2&# zSJb3xM4tobrdkMkaJE{yxiV<o0+XttJr3ZupoPld3*6`klMy5KF9!dkjD%_eWyY+& zg!xfW<ijSzDeYEbJn2EaPktzfun{(<KwtrYsXmk04Cb{Y=yYbx0Wi}ojubvCvn2hc zPi|M(ut_x)`!|>`Dvjn&heT6<yyJ^JCY+4kOhnIkB|_k)jGo?*$3=|B3mD=*9^c)! zt&{CJXzD!Z#qaezXHrdR*Mae(>&y}!$;8dSkH)Ae?JF=Qm5e2{TXFM(B5|Eyoa>Qb zi=~@uyi9mKA2ex1-va0|S1@Cjl`zjd6m$He(<V6~XD5I<w^%{Sq~7@FUmuE54edJs z4XzUlel??~ul6$G^_(Kf=%q~bju%iqXTr_OfY{W#d|NM8%^a)N_1^~2Dz8`qJ##RG zdwyEnu}5NadtZq6z2d;=%i05z4=msDjVIOxK&qiF0q|IvB-;R-0^kqj#4*7n#z{l5 zr9y26z9C>YfOA2lTk($P%F!8(B{iu%3c|8#NUDitV7DNwa~y0EA>Nbu?!+#%Zpl`| zS!mMGWT{>nB&i=RX1p&z>&T{cvi!#-r=KUtUPy$~m@yH+X#h_IhzaOoa2J@m2-Ja) zvp%yVz0Z%1#K>~p3*cnG0Ri9!K=e0Rf?*>%ML^7F@KpdVER*{sfL|vCo4pKE#L#v# zaB7(X6~X03!V%dU_cx$N-CK7Lh*wlZbZ8J^7c=hf)6J(!aZw}{eTA9iOxAj=v~;sI zoQhpZoHI8gV8L}x2>16dHn$hNlaZu$7ZV-`Sa1{_zisL1%hhCb1&HpenHHmm34a}Z zHl?tk%_U${&4huEE#16GO=<}c+G?mXNX_VT$eG)(Z{L#FlJDf13<R<fE+`XVJ3HcP zCi`MPd)xpg;D1DzpNX6Q&3#?vpRl2AB;c9=Me<b3$l<Ll=(8GdyHhgSt;FD-dC+81 z`y&xP3^<W){-rL4G1&(QyvU841TUh4$_6`R%kAD$2G1^qXV4RUdPlQrM6V#w?F{^> z<X2VjqD)Kr$={AzNo@@Xi-R&spoic%>wT=^8cR1@^18<x(`GwxO6_aEuynK0Csw|n zxz+sb(7alkObR{*Fg^&QitWU*iqhh>ig2*fT57iXNu0r3`t<ZAez3zI9pdH@?GLq0 zYlXl+12FlurJIu?NiEHU;h8VV%Y2itl{fs9D?kZL?=bw7Yfg`7ZDAuT6>R}is!F3D zS-LqZVrT~#7+WP*wc$I8wj?7)>{<rP<BjWK*hT#X#m?SoE1?kw<!ZC7G`?hWIbvvk zW<VO1=f<)IgCAt@TfPp)WieL<cD*JTzq(Pg712=v^@}1a?s7uTR~5tt9^z_JzT5!4 zS%K<K>&FwOswejRRN_jn0?G<Q7(ck03@5b>M7iE-*~8LJ*>%_n!r5g92N}TF>amg< z7w8c^%fQRTd_F)^0ge^BuM9CJW(o4j;mRO$E|-g#7nL+QE)|_6m_8UH&4TE8F+eMO zmv7ruN+{Q&tVcr#*}XmAJKu&+2whHunqA3x8Xmpl866NYqE9nuF2FB{tg_0@oST?x zXs;is)g<C(dCBW?KN-7E4A4`Rn#&sxC$%|8WHlL&TM1LPTL(`eqsOtv^ivM#pLctf zgHJ#>8GVq5Zubcf0@iKm=5XK2^EU%QC`yjQdGD#%*)#Sn>+H(se%MS78C?tDhM*>{ z2+z+#h?%`D+hm7-cpO%fIisoQ3NXoT-I##Qerk*Cy!|bmvX4As9Cnj~MztAWNTY%M z50#sbQj>N>Oy-kOliFX1_$mg?uX<Gp*IRM(rD8ls)MP#(`MW%M55nMu1RA;rDO%2- z0Q@N!p8#UYhz>ksla(-wPS#Ojve@iP<BG5+!e$ycroQ_?b9=!ZW%%LqPogmcvh0^i zj#4Fi)S4`(e8;b8)NDt&+kx{~e>`2#b^O0DIsA?r(JL%Hz4@s4x|Xv_L12Fc30Q=P TM8y=K00000NkvXXu0mjf4<3N5 literal 0 HcmV?d00001 diff --git a/src/views/mp/components/wx-material-select/main.vue b/src/views/mp/components/wx-material-select/main.vue new file mode 100644 index 00000000..26b747e2 --- /dev/null +++ b/src/views/mp/components/wx-material-select/main.vue @@ -0,0 +1,302 @@ +<!-- + - Copyright (C) 2018-2019 + - All rights reserved, Designed By www.joolun.com + 芋道源码: + ① 移除 avue 组件,使用 ElementUI 原生组件 +--> +<template> + <!-- 类型:图片 --> + <div v-if="objData.type === 'image'"> + <div class="waterfall" v-loading="loading"> + <div class="waterfall-item" v-for="item in list" :key="item.mediaId"> + <img class="material-img" :src="item.url" /> + <p class="item-name">{{ item.name }}</p> + <el-row class="ope-row"> + <el-button type="success" @click="selectMaterialFun(item)" + >选择 + <i class="el-icon-circle-check el-icon--right"></i> + </el-button> + </el-row> + </div> + </div> + <!-- 分页组件 --> + <pagination + v-show="total > 0" + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getMaterialPageFun" + /> + </div> + <!-- 类型:语音 --> + <div v-else-if="objData.type === 'voice'"> + <!-- 列表 --> + <el-table v-loading="loading" :data="list"> + <el-table-column label="编号" align="center" prop="mediaId" /> + <el-table-column label="文件名" align="center" prop="name" /> + <el-table-column label="语音" align="center"> + <template #default="scope"> + <wx-voice-player :url="scope.row.url" /> + </template> + </el-table-column> + <el-table-column label="上传时间" align="center" prop="createTime" width="180"> + <template #default="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column + label="操作" + align="center" + fixed="right" + class-name="small-padding fixed-width" + > + <template #default="scope"> + <el-button type="text" icon="el-icon-circle-plus" @click="selectMaterialFun(scope.row)" + >选择 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页组件 --> + <pagination + v-show="total > 0" + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getPage" + /> + </div> + <div v-else-if="objData.type === 'video'"> + <!-- 列表 --> + <el-table v-loading="loading" :data="list"> + <el-table-column label="编号" align="center" prop="mediaId" /> + <el-table-column label="文件名" align="center" prop="name" /> + <el-table-column label="标题" align="center" prop="title" /> + <el-table-column label="介绍" align="center" prop="introduction" /> + <el-table-column label="视频" align="center"> + <template #default="scope"> + <wx-video-player :url="scope.row.url" /> + </template> + </el-table-column> + <el-table-column label="上传时间" align="center" prop="createTime" width="180"> + <template #default="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column + label="操作" + align="center" + fixed="right" + class-name="small-padding fixed-width" + > + <template #default="scope"> + <el-button type="text" icon="el-icon-circle-plus" @click="selectMaterialFun(scope.row)" + >选择 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页组件 --> + <pagination + v-show="total > 0" + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getMaterialPageFun" + /> + </div> + <div v-else-if="objData.type === 'news'"> + <div class="waterfall" v-loading="loading"> + <div class="waterfall-item" v-for="item in list" :key="item.mediaId"> + <div v-if="item.content && item.content.newsItem"> + <wx-news :articles="item.content.newsItem" /> + <el-row class="ope-row"> + <el-button type="success" @click="selectMaterialFun(item)"> + 选择<i class="el-icon-circle-check el-icon--right"></i> + </el-button> + </el-row> + </div> + </div> + </div> + <!-- 分页组件 --> + <pagination + v-show="total > 0" + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getMaterialPageFun" + /> + </div> +</template> + +<script lang="ts" name="WxMaterialSelect"> +import WxNews from '@/views/mp/components/wx-news/main.vue' +import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' +import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' +import { getMaterialPage } from '@/api/mp/material' +import { getFreePublishPage } from '@/api/mp/freePublish' +import { getDraftPage } from '@/api/mp/draft' +import { dateFormatter, parseTime } from '@/utils/formatTime' +import { defineComponent, PropType } from 'vue' + +export default defineComponent({ + components: { + WxNews, + WxVoicePlayer, + WxVideoPlayer + }, + props: { + objData: { + type: Object, // type - 类型;accountId - 公众号账号编号 + required: true + }, + newsType: { + // 图文类型:1、已发布图文;2、草稿箱图文 + type: String as PropType<string>, + default: '1' + } + }, + setup(props, ctx) { + // 遮罩层 + const loading = ref(false) + // 总条数 + const total = ref(0) + // 数据列表 + const list = ref([]) + // 查询参数 + const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + accountId: props.objData.accountId + }) + const objDataRef = reactive(props.objData) + const newsTypeRef = ref(props.newsType) + + const selectMaterialFun = (item) => { + ctx.emit('selectMaterial', item) + } + /** 搜索按钮操作 */ + const handleQuery = () => { + queryParams.pageNo = 1 + getPage() + } + const getPage = () => { + loading.value = true + if (objDataRef.type === 'news' && newsTypeRef.value === '1') { + // 【图文】+ 【已发布】 + getFreePublishPageFun() + } else if (objDataRef.type === 'news' && newsTypeRef.value === '2') { + // 【图文】+ 【草稿】 + getDraftPageFun() + } else { + // 【素材】 + getMaterialPageFun() + } + } + + const getMaterialPageFun = async () => { + let data = await getMaterialPage({ + ...queryParams, + type: objDataRef.type + }) + list.value = data.list + total.value = data.total + loading.value = false + } + const getFreePublishPageFun = async () => { + let data = await getFreePublishPage(queryParams) + data.list.foreach((item) => { + const newsItem = item.content.newsItem + newsItem.forEach((article) => { + article.picUrl = article.thumbUrl + }) + }) + list.value = data.list + total.value = data.total + loading.value = false + } + + const getDraftPageFun = async () => { + let data = await getDraftPage(queryParams) + data.list.forEach((item) => { + const newsItem = item.content.newsItem + newsItem.forEach((article) => { + article.picUrl = article.thumbUrl + }) + }) + list.value = data.list + total.value = data.total + loading.value = false + } + + onMounted(async () => { + getPage() + }) + return { + handleQuery, + dateFormatter, + selectMaterialFun, + getMaterialPageFun, + getPage, + parseTime, + newsTypeRef, + queryParams, + objDataRef, + list, + total, + loading + } + } +}) +</script> + +<style lang="scss" scoped> +/*瀑布流样式*/ +.waterfall { + width: 100%; + column-gap: 10px; + column-count: 5; + margin: 0 auto; +} + +.waterfall-item { + padding: 10px; + margin-bottom: 10px; + break-inside: avoid; + border: 1px solid #eaeaea; +} + +.material-img { + width: 100%; +} + +p { + line-height: 30px; +} + +@media (min-width: 992px) and (max-width: 1300px) { + .waterfall { + column-count: 3; + } + p { + color: red; + } +} + +@media (min-width: 768px) and (max-width: 991px) { + .waterfall { + column-count: 2; + } + p { + color: orange; + } +} + +@media (max-width: 767px) { + .waterfall { + column-count: 1; + } +} + +/*瀑布流样式*/ +</style> diff --git a/src/views/mp/components/wx-msg/main.vue b/src/views/mp/components/wx-msg/main.vue index b514a73e..215b4f97 100644 --- a/src/views/mp/components/wx-msg/main.vue +++ b/src/views/mp/components/wx-msg/main.vue @@ -6,7 +6,7 @@ ② 代码优化,补充注释,提升阅读性 --> <template> - <div class="msg-main"> + <ContentWrap> <div class="msg-div" :id="'msg-div' + nowStr"> <!-- 加载更多 --> <div v-loading="loading"></div> @@ -26,9 +26,9 @@ :src="item.sendFrom === 1 ? user.avatar : mp.avatar" class="avue-comment__avatar" /> - <div class="avue-comment__author">{{ - item.sendFrom === 1 ? user.nickname : mp.nickname - }}</div> + <div class="avue-comment__author" + >{{ item.sendFrom === 1 ? user.nickname : mp.nickname }} + </div> </div> <div class="avue-comment__main"> <div class="avue-comment__header"> @@ -40,37 +40,41 @@ > <!-- 【事件】区域 --> <div v-if="item.type === 'event' && item.event === 'subscribe'"> - <el-tag type="success" size="mini">关注</el-tag> + <el-tag type="success">关注</el-tag> </div> <div v-else-if="item.type === 'event' && item.event === 'unsubscribe'"> - <el-tag type="danger" size="mini">取消关注</el-tag> + <el-tag type="danger">取消关注</el-tag> </div> <div v-else-if="item.type === 'event' && item.event === 'CLICK'"> - <el-tag size="mini">点击菜单</el-tag>【{{ item.eventKey }}】 + <el-tag>点击菜单</el-tag> + 【{{ item.eventKey }}】 </div> <div v-else-if="item.type === 'event' && item.event === 'VIEW'"> - <el-tag size="mini">点击菜单链接</el-tag>【{{ item.eventKey }}】 + <el-tag>点击菜单链接</el-tag> + 【{{ item.eventKey }}】 </div> <div v-else-if="item.type === 'event' && item.event === 'scancode_waitmsg'"> - <el-tag size="mini">扫码结果</el-tag>【{{ item.eventKey }}】 + <el-tag>扫码结果</el-tag> + 【{{ item.eventKey }}】 </div> <div v-else-if="item.type === 'event' && item.event === 'scancode_push'"> - <el-tag size="mini">扫码结果</el-tag>【{{ item.eventKey }}】 + <el-tag>扫码结果</el-tag> + 【{{ item.eventKey }}】 </div> <div v-else-if="item.type === 'event' && item.event === 'pic_sysphoto'"> - <el-tag size="mini">系统拍照发图</el-tag> + <el-tag>系统拍照发图</el-tag> </div> <div v-else-if="item.type === 'event' && item.event === 'pic_photo_or_album'"> - <el-tag size="mini">拍照或者相册</el-tag> + <el-tag>拍照或者相册</el-tag> </div> <div v-else-if="item.type === 'event' && item.event === 'pic_weixin'"> - <el-tag size="mini">微信相册</el-tag> + <el-tag>微信相册</el-tag> </div> <div v-else-if="item.type === 'event' && item.event === 'location_select'"> - <el-tag size="mini">选择地理位置</el-tag> + <el-tag>选择地理位置</el-tag> </div> <div v-else-if="item.type === 'event'"> - <el-tag type="danger" size="mini">未知事件类型</el-tag> + <el-tag type="danger">未知事件类型</el-tag> </div> <!-- 【消息】区域 --> <div v-else-if="item.type === 'text'">{{ item.content }}</div> @@ -124,10 +128,10 @@ <wx-reply-select ref="replySelect" :objData="objData" /> <el-button type="success" size="small" class="send-but" @click="sendMsg">发送(S)</el-button> </div> - </div> + </ContentWrap> </template> -<script> +<script lang="ts" name="WxMsg"> import { getMessagePage, sendMessage } from '@/api/mp/message' import WxReplySelect from '@/views/mp/components/wx-reply/main.vue' import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' @@ -136,9 +140,14 @@ import WxNews from '@/views/mp/components/wx-news/main.vue' import WxLocation from '@/views/mp/components/wx-location/main.vue' import WxMusic from '@/views/mp/components/wx-music/main.vue' import { getUser } from '@/api/mp/mpuser' +import { defineComponent } from 'vue' -export default { - name: 'WxMsg', +const message = useMessage() // 消息弹窗 +import profile from '@/assets/imgs/profile.jpg' +import wechat from '@/assets/imgs/wechat.png' +import { parseTime } from '@/utils/formatTime' + +export default defineComponent({ components: { WxReplySelect, WxVideoPlayer, @@ -153,160 +162,144 @@ export default { required: true } }, - data() { - return { - nowStr: new Date().getTime(), // 当前的时间戳,用于每次消息加载后,回到原位置;具体见 :id="'msg-div' + nowStr" 处 - loading: false, // 消息列表是否正在加载中 - loadMore: true, // 是否可以加载更多 - list: [], // 消息列表 - queryParams: { - pageNo: 1, // 当前页数 - pageSize: 14, // 每页显示多少条 - accountId: undefined - }, - user: { - // 由于微信不再提供昵称,直接使用“用户”展示 - nickname: '用户', - avatar: require('@/assets/images/profile.jpg'), - accountId: 0 // 公众号账号编号 - }, - mp: { - nickname: '公众号', - avatar: require('@/assets/images/wechat.png') - }, - - // ========= 消息发送 ========= - sendLoading: false, // 发送消息是否加载中 - objData: { - // 微信发送消息 - type: 'text' - } - } - }, - created() { - // 获得用户信息 - getUser(this.userId).then((response) => { - this.user.nickname = - response.data.nickname && response.data.nickname.length > 0 - ? response.data.nickname - : this.user.nickname - this.user.avatar = - response.data.avatar && this.user.avatar.length > 0 - ? response.data.avatar - : this.user.avatar - this.user.accountId = response.data.accountId - // 设置公众号账号编号 - this.queryParams.accountId = response.data.accountId - this.objData.accountId = response.data.accountId - - // 加载消息 - console.log(this.queryParams) - this.refreshChange() + setup(props) { + const nowStr = ref(new Date().getTime()) // 当前的时间戳,用于每次消息加载后,回到原位置;具体见 :id="'msg-div' + nowStr" 处 + const loading = ref(false) // 消息列表是否正在加载中 + const loadMore = ref(true) // 是否可以加载更多 + const list = ref<any[]>([]) // 消息列表 + const queryParams = reactive({ + pageNo: 1, // 当前页数 + pageSize: 14, // 每页显示多少条 + accountId: undefined }) - }, - methods: { - sendMsg() { - if (!this.objData) { + const user = reactive({ + // 由于微信不再提供昵称,直接使用“用户”展示 + nickname: '用户', + avatar: profile, + accountId: 0 // 公众号账号编号 + }) + const mp = reactive({ + nickname: '公众号', + avatar: wechat + }) + + // ========= 消息发送 ========= + const sendLoading = ref(false) // 发送消息是否加载中 + const objData = reactive({ + // 微信发送消息 + type: 'text', + accountId: null, + articles: [] + }) + + const replySelect = ref(null) + // 执行发送 + const sendMsg = async () => { + if (!objData) { return } - // 公众号限制:客服消息,公众号只允许发送一条 - if (this.objData.type === 'news' && this.objData.articles.length > 1) { - this.objData.articles = [this.objData.articles[0]] - this.$message({ - showClose: true, - message: '图文消息条数限制在 1 条以内,已默认发送第一条', - type: 'success' - }) + // // 公众号限制:客服消息,公众号只允许发送一条 + if (objData.type === 'news' && objData.articles.length > 1) { + objData.articles = [objData.articles[0]] + message.success('图文消息条数限制在 1 条以内,已默认发送第一条') } - - // 执行发送 - this.sendLoading = true - sendMessage( - Object.assign( - { - userId: this.userId - }, - { - ...this.objData - } - ) - ) - .then((response) => { - this.sendLoading = false - // 添加到消息列表,并滚动 - this.list = [...this.list, ...[response.data]] - this.scrollToBottom() - // 重置 objData 状态 - this.$refs['replySelect'].deleteObj() // 重置,避免 tab 的数据未清理 - }) - .catch(() => { - this.sendLoading = false - }) - }, - loadingMore() { - this.queryParams.pageNo++ - this.getPage(this.queryParams) - }, - getPage(page, params) { - this.loading = true - getMessagePage( + let data = await sendMessage(Object.assign({ userId: props.userId }, { ...objData })) + sendLoading.value = false + list.value = [...list.value, ...[data]] + scrollToBottom() + //ts檢查的時候會判斷這個組件可能是空的,所以需要進行斷言。 + //避免 tab 的数据未清理 + const deleteObj = (replySelect.value as any).deleteObj + if (deleteObj) { + deleteObj() + } + } + const loadingMore = () => { + queryParams.pageNo++ + getPage(queryParams, null) + } + const getPage = async (page, params) => { + loading.value = true + let dataTemp = await getMessagePage( Object.assign( { pageNo: page.pageNo, pageSize: page.pageSize, - userId: this.userId, + userId: props.userId, accountId: page.accountId }, params ) - ).then((response) => { - // 计算当前的滚动高度 - const msgDiv = document.getElementById('msg-div' + this.nowStr) - let scrollHeight = 0 - if (msgDiv) { - scrollHeight = msgDiv.scrollHeight - } - - // 处理数据 - const data = response.data.list.reverse() - this.list = [...data, ...this.list] - this.loading = false - if (data.length < this.queryParams.pageSize || data.length === 0) { - this.loadMore = false - } - this.queryParams.pageNo = page.pageNo - this.queryParams.pageSize = page.pageSize - - // 滚动到原来的位置 - if (this.queryParams.pageNo === 1) { - // 定位到消息底部 - this.scrollToBottom() - } else if (data.length !== 0) { - // 定位滚动条 - this.$nextTick(() => { - if (scrollHeight !== 0) { - msgDiv.scrollTop = - document.getElementById('msg-div' + this.nowStr).scrollHeight - scrollHeight - 100 + ) + const msgDiv = document.getElementById('msg-div' + nowStr.value) + let scrollHeight = 0 + if (msgDiv) { + scrollHeight = msgDiv.scrollHeight + } + // 处理数据 + let data = dataTemp.list.reverse() + list.value = [...data, ...list.value] + loading.value = false + if (data.length < queryParams.pageSize || data.length === 0) { + loadMore.value = false + } + queryParams.pageNo = page.pageNo + queryParams.pageSize = page.pageSize + // 滚动到原来的位置 + if (queryParams.pageNo === 1) { + // 定位到消息底部 + scrollToBottom() + } else if (data.length !== 0) { + // 定位滚动条 + await nextTick(() => { + if (scrollHeight !== 0) { + let div = document.getElementById('msg-div' + nowStr.value) + if (div && msgDiv) { + msgDiv.scrollTop = div.scrollHeight - scrollHeight - 100 } - }) - } - }) - }, - /** - * 刷新回调 - */ - refreshChange() { - this.getPage(this.queryParams) - }, + } + }) + } + } + const refreshChange = () => { + getPage(queryParams, null) + } /** 定位到消息底部 */ - scrollToBottom: function () { - this.$nextTick(() => { - let div = document.getElementById('msg-div' + this.nowStr) - div.scrollTop = div.scrollHeight + const scrollToBottom = () => { + nextTick(() => { + let div = document.getElementById('msg-div' + nowStr.value) + if (div) { + div.scrollTop = div.scrollHeight + } }) } + + onMounted(async () => { + let data = await getUser(props.userId) + user.nickname = data.nickname && data.nickname.length > 0 ? data.nickname : user.nickname + user.avatar = data.avatar && user.avatar.length > 0 ? data.avatar : user.avatar + user.accountId = data.accountId + queryParams.accountId = data.accountId + objData.accountId = data.accountId + refreshChange() + }) + return { + sendMsg, + loadingMore, + parseTime, + scrollToBottom, + objData, + mp, + user, + queryParams, + list, + loadMore, + loading, + nowStr, + sendLoading + } } -} +}) </script> <style lang="scss" scoped> /* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc */ @@ -317,6 +310,7 @@ export default { margin-top: -30px; padding: 10px; } + .msg-div { height: 50vh; overflow: auto; @@ -324,13 +318,16 @@ export default { margin-left: 10px; margin-right: 10px; } + .msg-send { padding: 10px; } + .avatar-div { text-align: center; width: 80px; } + .send-but { float: right; margin-top: 8px !important; diff --git a/src/views/mp/components/wx-reply/main.vue b/src/views/mp/components/wx-reply/main.vue index a151b252..a31e9462 100644 --- a/src/views/mp/components/wx-reply/main.vue +++ b/src/views/mp/components/wx-reply/main.vue @@ -8,100 +8,165 @@ ④ 支持发送【视频】消息时,支持新建视频 --> <template> - <el-tabs type="border-card" v-model="objData.type" @tab-click="handleClick"> + <el-tabs type="border-card" v-model="objDataRef.type" @tab-click="handleClick"> <!-- 类型 1:文本 --> <el-tab-pane name="text"> - <span slot="label"><i class="el-icon-document"></i> 文本</span> + <template #label> + <el-row align="middle"> + <icon icon="ep:document" /> + 文本 + </el-row> + </template> <el-input type="textarea" :rows="5" placeholder="请输入内容" - v-model="objData.content" + v-model="objDataRef.content" @input="inputContent" /> </el-tab-pane> <!-- 类型 2:图片 --> <el-tab-pane name="image"> - <span slot="label"><i class="el-icon-picture"></i> 图片</span> - <el-row> - <!-- 情况一:已经选择好素材、或者上传好图片 --> - <div class="select-item" v-if="objData.url"> - <img class="material-img" :src="objData.url" /> - <p class="item-name" v-if="objData.name">{{ objData.name }}</p> - <el-row class="ope-row"> - <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" /> - </el-row> - </div> - <!-- 情况二:未做完上述操作 --> - <div v-else> - <el-row style="text-align: center"> - <!-- 选择素材 --> - <el-col :span="12" class="col-select"> - <el-button type="success" @click="openMaterial"> - 素材库选择<i class="el-icon-circle-check el-icon--right"></i> - </el-button> - <el-dialog - title="选择图片" - v-model:visible="dialogImageVisible" - width="90%" - append-to-body - > - <wx-material-select :obj-data="objData" @selectMaterial="selectMaterial" /> - </el-dialog> - </el-col> - <!-- 文件上传 --> - <el-col :span="12" class="col-add"> - <el-upload - :action="actionUrl" - :headers="headers" - multiple - :limit="1" - :file-list="fileList" - :data="uploadData" - :before-upload="beforeImageUpload" - :on-success="handleUploadSuccess" - > - <el-button type="primary">上传图片</el-button> - <div slot="tip" class="el-upload__tip" + <template #label> + <el-row align="middle"> + <icon icon="ep:picture" class="mr-5px" /> + 图片 + </el-row> + </template> + <!-- 情况一:已经选择好素材、或者上传好图片 --> + <div class="select-item" v-if="objDataRef.url"> + <img class="material-img" :src="objDataRef.url" /> + <p class="item-name" v-if="objDataRef.name">{{ objDataRef.name }}</p> + <el-row class="ope-row"> + <el-button type="danger" circle @click="deleteObj"> + <icon icon="ep:delete" /> + </el-button> + </el-row> + </div> + <!-- 情况二:未做完上述操作 --> + <el-row v-else style="text-align: center" align="middle"> + <!-- 选择素材 --> + <el-col :span="12" class="col-select"> + <el-button type="success" @click="openMaterial"> + 素材库选择 + <icon icon="ep:circle-check" /> + </el-button> + <el-dialog title="选择图片" v-model="dialogImageVisible" width="90%" append-to-body> + <wx-material-select :obj-data="objDataRef" @selectMaterial="selectMaterial" /> + </el-dialog> + </el-col> + <!-- 文件上传 --> + <el-col :span="12" class="col-add"> + <el-upload + :action="actionUrl" + :headers="headers" + multiple + :limit="1" + :file-list="fileList" + :data="uploadData" + :before-upload="beforeImageUpload" + :on-success="handleUploadSuccess" + > + <el-button type="primary">上传图片</el-button> + <template #tip> + <span> + <div class="el-upload__tip" >支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div - > - </el-upload> - </el-col> - </el-row> - </div> + ></span + > + </template> + </el-upload> + </el-col> </el-row> </el-tab-pane> <!-- 类型 3:语音 --> <el-tab-pane name="voice"> - <span slot="label"><i class="el-icon-phone"></i> 语音</span> - <el-row> - <div class="select-item2" v-if="objData.url"> - <p class="item-name">{{ objData.name }}</p> - <div class="item-infos"> - <wx-voice-player :url="objData.url" /> - </div> - <el-row class="ope-row"> - <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" /> - </el-row> + <template #label> + <el-row align="middle"> + <icon icon="ep:phone" /> + 语音 + </el-row> + </template> + + <div class="select-item2" v-if="objDataRef.url"> + <p class="item-name">{{ objDataRef.name }}</p> + <div class="item-infos"> + <wx-voice-player :url="objDataRef.url" /> </div> - <div v-else> - <el-row style="text-align: center"> + <el-row class="ope-row"> + <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" /> + </el-row> + </div> + <el-row v-else style="text-align: center"> + <!-- 选择素材 --> + <el-col :span="12" class="col-select"> + <el-button type="success" @click="openMaterial"> + 素材库选择<i class="el-icon-circle-check el-icon--right"></i> + </el-button> + <el-dialog title="选择语音" v-model="dialogVoiceVisible" width="90%" append-to-body> + <WxMaterialSelect :objData="objData" @selectMaterial="selectMaterial" /> + </el-dialog> + </el-col> + <!-- 文件上传 --> + <el-col :span="12" class="col-add"> + <el-upload + :action="actionUrl" + :headers="headers" + multiple + :limit="1" + :file-list="fileList" + :data="uploadData" + :before-upload="beforeVoiceUpload" + :on-success="handleUploadSuccess" + > + <el-button type="primary">点击上传</el-button> + <template #tip> + <div class="el-upload__tip" + >格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s + </div> + </template> + </el-upload> + </el-col> + </el-row> + </el-tab-pane> + <!-- 类型 4:视频 --> + <el-tab-pane name="video"> + <template #label> + <el-row align="middle"> + <icon icon="ep:share" /> + 视频 + </el-row> + </template> + <el-row> + <el-input + v-model="objDataRef.title" + class="input-margin-bottom" + placeholder="请输入标题" + @input="inputContent" + /> + <el-input + class="input-margin-bottom" + v-model="objDataRef.description" + placeholder="请输入描述" + @input="inputContent" + /> + <div style="text-align: center"> + <wx-video-player v-if="objDataRef.url" :url="objDataRef.url" /> + </div> + <el-col> + <el-row style="text-align: center" align="middle"> <!-- 选择素材 --> - <el-col :span="12" class="col-select"> + <el-col :span="12"> <el-button type="success" @click="openMaterial"> - 素材库选择<i class="el-icon-circle-check el-icon--right"></i> + 素材库选择 + <icon icon="ep:circle-check" /> </el-button> - <el-dialog - title="选择语音" - v-model:visible="dialogVoiceVisible" - width="90%" - append-to-body - > - <WxMaterialSelect :objData="objData" @selectMaterial="selectMaterial" /> + <el-dialog title="选择视频" v-model="dialogVideoVisible" width="90%" append-to-body> + <wx-material-select :objData="objDataRef" @selectMaterial="selectMaterial" /> </el-dialog> </el-col> <!-- 文件上传 --> - <el-col :span="12" class="col-add"> + <el-col :span="12"> <el-upload :action="actionUrl" :headers="headers" @@ -109,90 +174,50 @@ :limit="1" :file-list="fileList" :data="uploadData" - :before-upload="beforeVoiceUpload" + :before-upload="beforeVideoUpload" :on-success="handleUploadSuccess" > - <el-button type="primary">点击上传</el-button> - <div slot="tip" class="el-upload__tip" - >格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s</div - > + <el-button type="primary" + >新建视频 + <icon icon="ep:upload" /> + </el-button> </el-upload> </el-col> </el-row> - </div> - </el-row> - </el-tab-pane> - <!-- 类型 4:视频 --> - <el-tab-pane name="video"> - <span slot="label"><i class="el-icon-share"></i> 视频</span> - <el-row> - <el-input v-model="objData.title" placeholder="请输入标题" @input="inputContent" /> - <div style="margin: 20px 0"></div> - <el-input v-model="objData.description" placeholder="请输入描述" @input="inputContent" /> - <div style="margin: 20px 0"></div> - <div style="text-align: center"> - <wx-video-player v-if="objData.url" :url="objData.url" /> - </div> - <div style="margin: 20px 0"></div> - <el-row style="text-align: center"> - <!-- 选择素材 --> - <el-col :span="12"> - <el-button type="success" @click="openMaterial"> - 素材库选择<i class="el-icon-circle-check el-icon--right"></i> - </el-button> - <el-dialog - title="选择视频" - v-model:visible="dialogVideoVisible" - width="90%" - append-to-body - > - <wx-material-select :objData="objData" @selectMaterial="selectMaterial" /> - </el-dialog> - </el-col> - <!-- 文件上传 --> - <el-col :span="12"> - <el-upload - :action="actionUrl" - :headers="headers" - multiple - :limit="1" - :file-list="fileList" - :data="uploadData" - :before-upload="beforeVideoUpload" - :on-success="handleUploadSuccess" - > - <el-button type="primary" - >新建视频<i class="el-icon-upload el-icon--right"></i - ></el-button> - </el-upload> - </el-col> - </el-row> + </el-col> </el-row> </el-tab-pane> <!-- 类型 5:图文 --> <el-tab-pane name="news"> - <span slot="label"><i class="el-icon-news"></i> 图文</span> + <template #label> + <el-row align="middle"> + <icon icon="ep:reading" /> + 图文 + </el-row> + </template> <el-row> - <div class="select-item" v-if="objData.articles"> - <wx-news :articles="objData.articles" /> - <el-row class="ope-row"> - <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" /> - </el-row> + <div class="select-item" v-if="objDataRef.articles.size > 0"> + <wx-news :articles="objDataRef.articles" /> + <el-col class="ope-row"> + <el-button type="danger" circle @click="deleteObj"> + <icon icon="ep:delete" /> + </el-button> + </el-col> </div> <!-- 选择素材 --> - <div v-if="!objData.content"> - <el-row style="text-align: center"> + <el-col :span="24" v-if="!objDataRef.content"> + <el-row style="text-align: center" align="middle"> <el-col :span="24"> <el-button type="success" @click="openMaterial" - >{{ newsType === '1' ? '选择已发布图文' : '选择草稿箱图文' - }}<i class="el-icon-circle-check el-icon--right"></i - ></el-button> + >{{ newsType === '1' ? '选择已发布图文' : '选择草稿箱图文' }} + <icon icon="ep:circle-check" /> + </el-button> </el-col> </el-row> - </div> - <el-dialog title="选择图文" v-model:visible="dialogNewsVisible" width="90%" append-to-body> + </el-col> + <el-dialog title="选择图文" v-model="dialogNewsVisible" width="90%" append-to-body> <wx-material-select - :objData="objData" + :objData="objDataRef" @selectMaterial="selectMaterial" :newsType="newsType" /> @@ -201,53 +226,69 @@ </el-tab-pane> <!-- 类型 6:音乐 --> <el-tab-pane name="music"> - <span slot="label"><i class="el-icon-service"></i> 音乐</span> - <el-row> + <template #label> + <el-row align="middle"> + <icon icon="ep:service" /> + 音乐 + </el-row> + </template> + <el-row align="middle" justify="center"> <el-col :span="6"> - <div class="thumb-div"> - <img style="width: 100px" v-if="objData.thumbMediaUrl" :src="objData.thumbMediaUrl" /> - <i v-else class="el-icon-plus avatar-uploader-icon"></i> - <div class="thumb-but"> - <el-upload - :action="actionUrl" - :headers="headers" - multiple - :limit="1" - :file-list="fileList" - :data="uploadData" - :before-upload="beforeThumbImageUpload" - :on-success="handleUploadSuccess" - > - <el-button slot="trigger" size="mini" type="text">本地上传</el-button> - <el-button size="mini" type="text" @click="openMaterial" style="margin-left: 5px" - >素材库选择</el-button - > - </el-upload> - </div> - </div> - <el-dialog - title="选择图片" - v-model:visible="dialogThumbVisible" - width="80%" - append-to-body - > + <el-row align="middle" justify="center" class="thumb-div"> + <el-col :span="24"> + <el-row align="middle" justify="center"> + <img + style="width: 100px" + v-if="objDataRef.thumbMediaUrl" + :src="objDataRef.thumbMediaUrl" + /> + <icon v-else icon="ep:plus" /> + </el-row> + <el-row align="middle" justify="center" style="margin-top: 2%"> + <div class="thumb-but"> + <el-upload + :action="actionUrl" + :headers="headers" + multiple + :limit="1" + :file-list="fileList" + :data="uploadData" + :before-upload="beforeThumbImageUpload" + :on-success="handleUploadSuccess" + > + <template #trigger> + <el-button type="text">本地上传</el-button> + </template> + <el-button type="text" @click="openMaterial" style="margin-left: 5px" + >素材库选择 + </el-button> + </el-upload> + </div> + </el-row> + </el-col> + </el-row> + <el-dialog title="选择图片" v-model="dialogThumbVisible" width="80%" append-to-body> <wx-material-select - :objData="{ type: 'image', accountId: objData.accountId }" + :objData="{ type: 'image', accountId: objDataRef.accountId }" @selectMaterial="selectMaterial" /> </el-dialog> </el-col> <el-col :span="18"> - <el-input v-model="objData.title" placeholder="请输入标题" @input="inputContent" /> + <el-input v-model="objDataRef.title" placeholder="请输入标题" @input="inputContent" /> <div style="margin: 20px 0"></div> - <el-input v-model="objData.description" placeholder="请输入描述" @input="inputContent" /> + <el-input + v-model="objDataRef.description" + placeholder="请输入描述" + @input="inputContent" + /> </el-col> </el-row> <div style="margin: 20px 0"></div> - <el-input v-model="objData.musicUrl" placeholder="请输入音乐链接" @input="inputContent" /> + <el-input v-model="objDataRef.musicUrl" placeholder="请输入音乐链接" @input="inputContent" /> <div style="margin: 20px 0"></div> <el-input - v-model="objData.hqMusicUrl" + v-model="objDataRef.hqMusicUrl" placeholder="请输入高质量音乐链接" @input="inputContent" /> @@ -255,16 +296,16 @@ </el-tabs> </template> -<script> +<script lang="ts" name="WxReplySelect"> import WxNews from '@/views/mp/components/wx-news/main.vue' import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue' import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue' import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue' import { getAccessToken } from '@/utils/auth' +import { defineComponent } from 'vue' -export default { - name: 'WxReplySelect', +export default defineComponent({ components: { WxNews, WxMaterialSelect, @@ -283,39 +324,29 @@ export default { default: '1' } }, - data() { - return { - tempPlayerObj: { - type: '2' - }, - - tempObj: new Map().set( - // 临时缓存,切换消息类型的 tab 的时候,可以保存对应的数据; - this.objData.type, // 消息类型 - Object.assign({}, this.objData) - ), // 消息内容 - - // ========== 素材选择的弹窗,是否可见 ========== - dialogNewsVisible: false, // 图文 - dialogImageVisible: false, // 图片 - dialogVoiceVisible: false, // 语音 - dialogVideoVisible: false, // 视频 - dialogThumbVisible: false, // 缩略图 - - // ========== 文件上传(图片、语音、视频) ========== - fileList: [], // 文件列表 - uploadData: { - accountId: undefined, - type: this.objData.type, - title: '', - introduction: '' - }, - actionUrl: process.env.VUE_APP_BASE_API + '/admin-api/mp/material/upload-temporary', - headers: { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部 - } - }, - methods: { - beforeThumbImageUpload(file) { + setup(props) { + const objDataRef = reactive(props.objData) + const message = useMessage() // 消息弹窗 + const tempObj = new Map().set(objDataRef.type, Object.assign({}, objDataRef)) + // ========== 素材选择的弹窗,是否可见 ========== + const dialogNewsVisible = ref(false) // 图文 + const dialogImageVisible = ref(false) // 图片 + const dialogVoiceVisible = ref(false) // 语音 + const dialogVideoVisible = ref(false) // 视频 + const dialogThumbVisible = ref(false) // 缩略图 + // ========== 文件上传(图片、语音、视频) ========== + const fileList = ref([]) + const uploadData = reactive({ + accountId: undefined, + type: objDataRef.type, + title: '', + introduction: '' + }) + const actionUrl = ref( + import.meta.env.VITE_API_BASEPATH + '/admin-api/mp/material/upload-temporary' + ) + const headers = ref({ Authorization: 'Bearer ' + getAccessToken() }) // 设置上传的请求头部 + const beforeThumbImageUpload = (file) => { const isType = file.type === 'image/jpeg' || file.type === 'image/png' || @@ -323,18 +354,18 @@ export default { file.type === 'image/bmp' || file.type === 'image/jpg' if (!isType) { - this.$message.error('上传图片格式不对!') + message.error('上传图片格式不对!') return false } const isLt = file.size / 1024 / 1024 < 2 if (!isLt) { - this.$message.error('上传图片大小不能超过 2M!') + message.error('上传图片大小不能超过 2M!') return false } - this.uploadData.accountId = this.objData.accountId + uploadData.accountId = objDataRef.accountId return true - }, - beforeVoiceUpload(file) { + } + const beforeVoiceUpload = (file) => { // 校验格式 const isType = file.type === 'audio/mp3' || @@ -343,19 +374,19 @@ export default { file.type === 'audio/wav' || file.type === 'audio/amr' if (!isType) { - this.$message.error('上传语音格式不对!' + file.type) + message.error('上传语音格式不对!' + file.type) return false } // 校验大小 const isLt = file.size / 1024 / 1024 < 2 if (!isLt) { - this.$message.error('上传语音大小不能超过 2M!') + message.error('上传语音大小不能超过 2M!') return false } - this.uploadData.accountId = this.objData.accountId + uploadData.accountId = objDataRef.accountId return true - }, - beforeImageUpload(file) { + } + const beforeImageUpload = (file) => { // 校验格式 const isType = file.type === 'image/jpeg' || @@ -364,197 +395,232 @@ export default { file.type === 'image/bmp' || file.type === 'image/jpg' if (!isType) { - this.$message.error('上传图片格式不对!') + message.error('上传图片格式不对!') return false } // 校验大小 const isLt = file.size / 1024 / 1024 < 2 if (!isLt) { - this.$message.error('上传图片大小不能超过 2M!') + message.error('上传图片大小不能超过 2M!') return false } - this.uploadData.accountId = this.objData.accountId + uploadData.accountId = objDataRef.accountId return true - }, - beforeVideoUpload(file) { + } + const beforeVideoUpload = (file) => { // 校验格式 const isType = file.type === 'video/mp4' if (!isType) { - this.$message.error('上传视频格式不对!') + message.error('上传视频格式不对!') return false } // 校验大小 const isLt = file.size / 1024 / 1024 < 10 if (!isLt) { - this.$message.error('上传视频大小不能超过 10M!') + message.error('上传视频大小不能超过 10M!') return false } - this.uploadData.accountId = this.objData.accountId + uploadData.accountId = objDataRef.accountId return true - }, - handleUploadSuccess(response, file, fileList) { + } + const handleUploadSuccess = (response) => { if (response.code !== 0) { - this.$message.error('上传出错:' + response.msg) + message.error('上传出错:' + response.msg) return false } // 清空上传时的各种数据 - this.fileList = [] - this.uploadData.title = '' - this.uploadData.introduction = '' + fileList.value = [] + uploadData.title = '' + uploadData.introduction = '' // 上传好的文件,本质是个素材,所以可以进行选中 let item = response.data - this.selectMaterial(item) - }, + selectMaterial(item) + } /** * 切换消息类型的 tab * - * @param tab tab + * @param tab tab 没用 暂时删了tab */ - handleClick(tab) { + const handleClick = () => { // 设置后续文件上传的文件类型 - this.uploadData.type = this.objData.type - if (this.uploadData.type === 'music') { + uploadData.type = objDataRef.type + if (uploadData.type === 'music') { // 【音乐】上传的是缩略图 - this.uploadData.type = 'thumb' + uploadData.type = 'thumb' } - // 从 tempObj 临时缓存中,获取对应的数据,并设置回 objData - let tempObjItem = this.tempObj.get(this.objData.type) + // 从 tempObj 临时缓存中,获取对应的数据,并设置回 objDataRef + let tempObjItem = tempObj.get(objDataRef.type) if (tempObjItem) { - this.objData.content = tempObjItem.content ? tempObjItem.content : null - this.objData.mediaId = tempObjItem.mediaId ? tempObjItem.mediaId : null - this.objData.url = tempObjItem.url ? tempObjItem.url : null - this.objData.name = tempObjItem.url ? tempObjItem.name : null - this.objData.title = tempObjItem.title ? tempObjItem.title : null - this.objData.description = tempObjItem.description ? tempObjItem.description : null + objDataRef.content = tempObjItem.content ? tempObjItem.content : null + objDataRef.mediaId = tempObjItem.mediaId ? tempObjItem.mediaId : null + objDataRef.url = tempObjItem.url ? tempObjItem.url : null + objDataRef.name = tempObjItem.url ? tempObjItem.name : null + objDataRef.title = tempObjItem.title ? tempObjItem.title : null + objDataRef.description = tempObjItem.description ? tempObjItem.description : null return } - // 如果获取不到,需要把 objData 复原 + // 如果获取不到,需要把 objDataRef 复原 // 必须使用 $set 赋值,不然 input 无法输入内容 - this.$set(this.objData, 'content', '') - this.$delete(this.objData, 'mediaId') - this.$delete(this.objData, 'url') - this.$set(this.objData, 'title', '') - this.$set(this.objData, 'description', '') - }, + objDataRef.content = '' + objDataRef.mediaId = '' + objDataRef.url = '' + objDataRef.title = '' + objDataRef.description = '' + } /** - * 选择素材,将设置设置到 objData 变量 + * 选择素材,将设置设置到 objDataRef 变量 * * @param item 素材 */ - selectMaterial(item) { + const selectMaterial = (item) => { // 选择好素材,所以隐藏弹窗 - this.closeMaterial() + closeMaterial() // 创建 tempObjItem 对象,并设置对应的值 - let tempObjItem = {} - tempObjItem.type = this.objData.type - if (this.objData.type === 'news') { + let tempObjItem = { + type: '', + articles: '', + thumbMediaId: '', + thumbMediaUrl: '', + introduction: '', + title: '', + musicUrl: '', + hqMusicUrl: '', + mediaId: '', + url: '', + name: '', + description: '' + } + tempObjItem.type = objDataRef.type + if (objDataRef.type === 'news') { tempObjItem.articles = item.content.newsItem - this.objData.articles = item.content.newsItem - } else if (this.objData.type === 'music') { + objDataRef.articles = item.content.newsItem + } else if (objDataRef.type === 'music') { // 音乐需要特殊处理,因为选择的是图片的缩略图 tempObjItem.thumbMediaId = item.mediaId - this.objData.thumbMediaId = item.mediaId + objDataRef.thumbMediaId = item.mediaId tempObjItem.thumbMediaUrl = item.url - this.objData.thumbMediaUrl = item.url - // title、introduction、musicUrl、hqMusicUrl:从 objData 到 tempObjItem,避免上传素材后,被覆盖掉 - tempObjItem.title = this.objData.title || '' - tempObjItem.introduction = this.objData.introduction || '' - tempObjItem.musicUrl = this.objData.musicUrl || '' - tempObjItem.hqMusicUrl = this.objData.hqMusicUrl || '' - } else if (this.objData.type === 'image' || this.objData.type === 'voice') { + objDataRef.thumbMediaUrl = item.url + // title、introduction、musicUrl、hqMusicUrl:从 objDataRef 到 tempObjItem,避免上传素材后,被覆盖掉 + tempObjItem.title = objDataRef.title || '' + tempObjItem.introduction = objDataRef.introduction || '' + tempObjItem.musicUrl = objDataRef.musicUrl || '' + tempObjItem.hqMusicUrl = objDataRef.hqMusicUrl || '' + } else if (objDataRef.type === 'image' || objDataRef.type === 'voice') { tempObjItem.mediaId = item.mediaId - this.objData.mediaId = item.mediaId + objDataRef.mediaId = item.mediaId tempObjItem.url = item.url - this.objData.url = item.url + objDataRef.url = item.url tempObjItem.name = item.name - this.objData.name = item.name - } else if (this.objData.type === 'video') { + objDataRef.name = item.name + } else if (objDataRef.type === 'video') { tempObjItem.mediaId = item.mediaId - this.objData.mediaId = item.mediaId + objDataRef.mediaId = item.mediaId tempObjItem.url = item.url - this.objData.url = item.url + objDataRef.url = item.url tempObjItem.name = item.name - this.objData.name = item.name + objDataRef.name = item.name // title、introduction:从 item 到 tempObjItem,因为素材里有 title、introduction if (item.title) { - this.objData.title = item.title || '' + objDataRef.title = item.title || '' tempObjItem.title = item.title || '' } if (item.introduction) { - this.objData.description = item.introduction || '' // 消息使用的是 description,素材使用的是 introduction,所以转换下 + objDataRef.description = item.introduction || '' // 消息使用的是 description,素材使用的是 introduction,所以转换下 tempObjItem.description = item.introduction || '' } - } else if (this.objData.type === 'text') { - this.objData.content = item.content || '' + } else if (objDataRef.type === 'text') { + objDataRef.content = item.content || '' } // 最终设置到临时缓存 - this.tempObj.set(this.objData.type, tempObjItem) - }, - openMaterial() { - if (this.objData.type === 'news') { - this.dialogNewsVisible = true - } else if (this.objData.type === 'image') { - this.dialogImageVisible = true - } else if (this.objData.type === 'voice') { - this.dialogVoiceVisible = true - } else if (this.objData.type === 'video') { - this.dialogVideoVisible = true - } else if (this.objData.type === 'music') { - this.dialogThumbVisible = true + tempObj.set(objDataRef.type, tempObjItem) + } + const openMaterial = () => { + if (objDataRef.type === 'news') { + dialogNewsVisible.value = true + } else if (objDataRef.type === 'image') { + dialogImageVisible.value = true + } else if (objDataRef.type === 'voice') { + dialogVoiceVisible.value = true + } else if (objDataRef.type === 'video') { + dialogVideoVisible.value = true + } else if (objDataRef.type === 'music') { + dialogThumbVisible.value = true } - }, - closeMaterial() { - this.dialogNewsVisible = false - this.dialogImageVisible = false - this.dialogVoiceVisible = false - this.dialogVideoVisible = false - this.dialogThumbVisible = false - }, - deleteObj() { - if (this.objData.type === 'news') { - this.$delete(this.objData, 'articles') - } else if (this.objData.type === 'image') { - this.objData.mediaId = null - this.$delete(this.objData, 'url') - this.objData.name = null - } else if (this.objData.type === 'voice') { - this.objData.mediaId = null - this.$delete(this.objData, 'url') - this.objData.name = null - } else if (this.objData.type === 'video') { - this.objData.mediaId = null - this.$delete(this.objData, 'url') - this.objData.name = null - this.objData.title = null - this.objData.description = null - } else if (this.objData.type === 'music') { - this.objData.thumbMediaId = null - this.objData.thumbMediaUrl = null - this.objData.title = null - this.objData.description = null - this.objData.musicUrl = null - this.objData.hqMusicUrl = null - } else if (this.objData.type === 'text') { - this.objData.content = null + } + const closeMaterial = () => { + dialogNewsVisible.value = false + dialogImageVisible.value = false + dialogVoiceVisible.value = false + dialogVideoVisible.value = false + dialogThumbVisible.value = false + } + const deleteObj = () => { + if (objDataRef.type === 'news') { + objDataRef.articles = '' + } else if (objDataRef.type === 'image') { + objDataRef.mediaId = null + objDataRef.url = null + objDataRef.name = null + } else if (objDataRef.type === 'voice') { + objDataRef.mediaId = null + objDataRef.url = null + objDataRef.name = null + } else if (objDataRef.type === 'video') { + objDataRef.mediaId = null + objDataRef.url = null + objDataRef.name = null + objDataRef.title = null + objDataRef.description = null + } else if (objDataRef.type === 'music') { + objDataRef.thumbMediaId = null + objDataRef.thumbMediaUrl = null + objDataRef.title = null + objDataRef.description = null + objDataRef.musicUrl = null + objDataRef.hqMusicUrl = null + } else if (objDataRef.type === 'text') { + objDataRef.content = null } // 覆盖缓存 - this.tempObj.set(this.objData.type, Object.assign({}, this.objData)) - }, + tempObj.set(objDataRef.type, Object.assign({}, objDataRef)) + } /** - * 输入时,缓存每次 objData 到 tempObj 中 + * 输入时,缓存每次 objDataRef 到 tempObj 中 * - * why?不确定为什么 v-model="objData.content" 不能自动缓存,所以通过这样的方式 + * why?不确定为什么 v-model="objDataRef.content" 不能自动缓存,所以通过这样的方式 */ - inputContent(str) { + const inputContent = () => { // 覆盖缓存 - this.tempObj.set(this.objData.type, Object.assign({}, this.objData)) + tempObj.set(objDataRef.type, Object.assign({}, objDataRef)) + } + return { + inputContent, + dialogNewsVisible, + deleteObj, + openMaterial, + handleClick, + beforeImageUpload, + beforeVoiceUpload, + handleUploadSuccess, + beforeVideoUpload, + selectMaterial, + dialogImageVisible, + dialogVoiceVisible, + dialogThumbVisible, + actionUrl, + objDataRef, + headers, + fileList, + beforeThumbImageUpload, + uploadData, + dialogVideoVisible } } -} +}) </script> <style lang="scss" scoped> @@ -564,25 +630,34 @@ export default { margin-right: 2%; } } + .pagination { text-align: right; margin-right: 25px; } + .select-item { width: 280px; padding: 10px; margin: 0 auto 10px auto; border: 1px solid #eaeaea; } + .select-item2 { padding: 10px; margin: 0 auto 10px auto; border: 1px solid #eaeaea; } + .ope-row { padding-top: 10px; text-align: center; } + +.input-margin-bottom { + margin-bottom: 2%; +} + .item-name { font-size: 12px; overflow: hidden; @@ -590,20 +665,24 @@ export default { white-space: nowrap; text-align: center; } + .el-form-item__content { line-height: unset !important; } + .col-select { border: 1px solid rgb(234, 234, 234); padding: 50px 0px; height: 160px; width: 49.5%; } + .col-select2 { border: 1px solid rgb(234, 234, 234); padding: 50px 0px; height: 160px; } + .col-add { border: 1px solid rgb(234, 234, 234); padding: 50px 0px; @@ -611,6 +690,7 @@ export default { width: 49.5%; float: right; } + .avatar-uploader-icon { border: 1px solid #d9d9d9; font-size: 28px; @@ -620,13 +700,16 @@ export default { line-height: 100px !important; text-align: center; } + .material-img { width: 100%; } + .thumb-div { display: inline-block; text-align: center; } + .item-infos { width: 30%; margin: auto; diff --git a/src/views/mp/components/wx-video-play/main.vue b/src/views/mp/components/wx-video-play/main.vue index 7679a70a..7dc4347c 100644 --- a/src/views/mp/components/wx-video-play/main.vue +++ b/src/views/mp/components/wx-video-play/main.vue @@ -19,13 +19,7 @@ </div> <!-- 弹窗播放 --> - <el-dialog - v-model="dialogVideo" - title="视频播放" - width="40%" - append-to-body - @close="closeDialog" - > + <el-dialog v-model="dialogVideo" title="视频播放" width="40%" append-to-body> <template #footer> <video-player v-if="dialogVideo" diff --git a/src/views/mp/message/index.vue b/src/views/mp/message/index.vue index f218de39..35420e6f 100644 --- a/src/views/mp/message/index.vue +++ b/src/views/mp/message/index.vue @@ -51,8 +51,14 @@ /> </el-form-item> <el-form-item> - <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> - <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px" /> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px" /> + 重置 + </el-button> </el-form-item> </el-form> </ContentWrap> @@ -85,16 +91,20 @@ <el-tag type="danger">取消关注</el-tag> </div> <div v-else-if="scope.row.type === 'event' && scope.row.event === 'CLICK'"> - <el-tag>点击菜单</el-tag>【{{ scope.row.eventKey }}】 + <el-tag>点击菜单</el-tag> + 【{{ scope.row.eventKey }}】 </div> <div v-else-if="scope.row.type === 'event' && scope.row.event === 'VIEW'"> - <el-tag>点击菜单链接</el-tag>【{{ scope.row.eventKey }}】 + <el-tag>点击菜单链接</el-tag> + 【{{ scope.row.eventKey }}】 </div> <div v-else-if="scope.row.type === 'event' && scope.row.event === 'scancode_waitmsg'"> - <el-tag>扫码结果</el-tag>【{{ scope.row.eventKey }}】 + <el-tag>扫码结果</el-tag> + 【{{ scope.row.eventKey }}】 </div> <div v-else-if="scope.row.type === 'event' && scope.row.event === 'scancode_push'"> - <el-tag>扫码结果</el-tag>【{{ scope.row.eventKey }}】 + <el-tag>扫码结果</el-tag> + 【{{ scope.row.eventKey }}】 </div> <div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_sysphoto'"> <el-tag>系统拍照发图</el-tag> @@ -125,7 +135,8 @@ <wx-video-player :url="scope.row.mediaUrl" style="margin-top: 10px" /> </div> <div v-else-if="scope.row.type === 'link'"> - <el-tag>链接</el-tag>: + <el-tag>链接</el-tag> + : <a :href="scope.row.url" target="_blank">{{ scope.row.title }}</a> </div> <div v-else-if="scope.row.type === 'location'"> @@ -175,8 +186,10 @@ /> <!-- 发送消息的弹窗 --> - <el-dialog title="粉丝消息列表" v-model:visible="open" width="50%"> - <wx-msg :user-id="userId" v-if="open" /> + <el-dialog title="粉丝消息列表" v-model="open" @click="openDialog()" width="50%"> + <template #footer> + <wx-msg :user-id="userId" v-if="open" /> + </template> </el-dialog> </ContentWrap> </template> @@ -189,6 +202,7 @@ import WxMusic from '@/views/mp/components/wx-music/main.vue' import WxNews from '@/views/mp/components/wx-news/main.vue' import * as MpAccountApi from '@/api/mp/account' import * as MpMessageApi from '@/api/mp/message' + const message = useMessage() // 消息弹窗 import { DICT_TYPE, getStrDictOptions } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' @@ -248,6 +262,13 @@ const handleSend = async (row) => { open.value = true } +const openDialog = () => { + open.value = true +} +// const closeDiaLog = () => { +// open.value = false +// } + /** 初始化 **/ onMounted(async () => { accountList.value = await MpAccountApi.getSimpleAccountList() From 9e6da44881591df07f20cc3e7d3804ddb4e4fca1 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Tue, 28 Mar 2023 01:19:25 +0800 Subject: [PATCH 135/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E5=AE=9E=E4=BE=8B=E7=9A=84=E8=AF=A6=E6=83=85?= =?UTF-8?q?=EF=BC=88=E6=B5=81=E7=A8=8B=E5=9B=BE=E9=A2=84=E8=A7=88=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../detail/ProcessInstanceBpmnViewer.vue | 53 ++++++++++++++++ .../bpm/processInstance/detail/index.vue | 60 ++++--------------- 2 files changed, 63 insertions(+), 50 deletions(-) create mode 100644 src/views/bpm/processInstance/detail/ProcessInstanceBpmnViewer.vue diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceBpmnViewer.vue b/src/views/bpm/processInstance/detail/ProcessInstanceBpmnViewer.vue new file mode 100644 index 00000000..a06ac368 --- /dev/null +++ b/src/views/bpm/processInstance/detail/ProcessInstanceBpmnViewer.vue @@ -0,0 +1,53 @@ +<template> + <el-card class="box-card" v-loading="loading"> + <template #header> + <span class="el-icon-picture-outline">流程图</span> + </template> + <my-process-viewer + key="designer" + :value="bpmnXml" + v-bind="bpmnControlForm" + :prefix="bpmnControlForm.prefix" + :activityData="activityList" + :processInstanceData="processInstance" + :taskData="tasks" + /> + </el-card> +</template> +<script setup lang="ts"> +import { propTypes } from '@/utils/propTypes' +import * as ActivityApi from '@/api/bpm/activity' +// import * as DefinitionApi from '@/api/bpm/definition' + +const props = defineProps({ + loading: propTypes.bool, // 是否加载中 + id: propTypes.string, // 流程实例的编号 + processInstance: propTypes.any, // 流程实例的信息 + tasks: propTypes.array, // 流程任务的数组 + bpmnXml: propTypes.string // BPMN XML +}) + +const bpmnControlForm = ref({ + prefix: 'flowable' +}) +const activityList = ref([]) // 任务列表 +// const bpmnXML = computed(() => { // TODO 芋艿:不晓得为啊哈不能这么搞 +// if (!props.processInstance || !props.processInstance.processDefinition) { +// return +// } +// return DefinitionApi.getProcessDefinitionBpmnXML(props.processInstance.processDefinition.id) +// }) + +/** 初始化 */ +onMounted(async () => { + activityList.value = await ActivityApi.getActivityList({ + processInstanceId: props.id + }) +}) +</script> +<style> +.box-card { + width: 100%; + margin-bottom: 20px; +} +</style> diff --git a/src/views/bpm/processInstance/detail/index.vue b/src/views/bpm/processInstance/detail/index.vue index e811475f..1651f97a 100644 --- a/src/views/bpm/processInstance/detail/index.vue +++ b/src/views/bpm/processInstance/detail/index.vue @@ -127,21 +127,13 @@ </el-card> <!-- 高亮流程图 --> - <el-card class="box-card" v-loading="processInstanceLoading"> - <template #header> - <span class="el-icon-picture-outline">流程图</span> - </template> - <my-process-viewer - key="designer" - v-model="bpmnXML" - :value="bpmnXML" - v-bind="bpmnControlForm" - :prefix="bpmnControlForm.prefix" - :activityData="activityList" - :processInstanceData="processInstance" - :taskData="tasks" - /> - </el-card> + <ProcessInstanceBpmnViewer + :id="id" + :process-instance="processInstance" + :loading="processInstanceLoading" + :tasks="tasks" + :bpmn-xml="bpmnXML" + /> <!-- 弹窗:转派审批人 --> <TaskUpdateAssigneeForm ref="taskUpdateAssigneeFormRef" @success="getDetail" /> @@ -149,16 +141,15 @@ </template> <script setup lang="ts"> import { parseTime } from '@/utils/formatTime' -import * as UserApi from '@/api/system/user' import * as ProcessInstanceApi from '@/api/bpm/processInstance' -import * as DefinitionApi from '@/api/bpm/definition' import * as TaskApi from '@/api/bpm/task' -import * as ActivityApi from '@/api/bpm/activity' import { formatPast2 } from '@/utils/formatTime' import { setConfAndFields2 } from '@/utils/formCreate' import type { ApiAttrs } from '@form-create/element-ui/types/config' import { useUserStore } from '@/store/modules/user' import TaskUpdateAssigneeForm from './TaskUpdateAssigneeForm.vue' +import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue' +import * as DefinitionApi from '@/api/bpm/definition' const { query } = useRoute() // 查询参数 const message = useMessage() // 消息弹窗 @@ -249,6 +240,7 @@ const getTimelineItemType = (item) => { } // ========== 审批记录 ========== +const bpmnXML = ref('') /** 转派审批人 */ const taskUpdateAssigneeFormRef = ref() @@ -265,32 +257,12 @@ const handleDelegate = async (task) => { /** 处理审批退回的操作 */ const handleBack = async (task) => { message.error('暂不支持【退回】功能!') - // 可参考 http://blog.wya1.com/article/636697030/details/7296 - // const data = { - // id: task.id, - // assigneeUserId: 1 - // } - // backTask(data).then(response => { - // this.$modal.msgSuccess("回退成功!"); - // this.getDetail(); // 获得最新详情 - // }); console.log(task) } -// ========== 高亮流程图 ========== -const bpmnXML = ref(null) -const bpmnControlForm = ref({ - prefix: 'flowable' -}) -const activityList = ref([]) - // ========== 初始化 ========== onMounted(() => { getDetail() - // 加载用户的列表 - UserApi.getSimpleUserList().then((data) => { - userOptions.value.push(...data) - }) }) const getDetail = () => { @@ -324,13 +296,6 @@ const getDetail = () => { DefinitionApi.getProcessDefinitionBpmnXML(processDefinition.id).then((data) => { bpmnXML.value = data }) - - // 加载活动列表 - ActivityApi.getActivityList({ - processInstanceId: data.id - }).then((data) => { - activityList.value = data - }) }) .finally(() => { processInstanceLoading.value = false @@ -387,12 +352,7 @@ const getDetail = () => { }) } </script> - <style lang="scss"> -.my-process-designer { - height: calc(100vh - 200px); -} - .box-card { width: 100%; margin-bottom: 20px; From 4388d6565a0856b7034b7574871e174d35ab4aef Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Tue, 28 Mar 2023 07:29:32 +0800 Subject: [PATCH 136/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E5=AE=9E=E4=BE=8B=E7=9A=84=E8=AF=A6=E6=83=85?= =?UTF-8?q?=EF=BC=88=E6=B5=81=E7=A8=8B=E4=BB=BB=E5=8A=A1=E5=88=97=E8=A1=A8?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../detail/ProcessInstanceTaskList.vue | 89 ++++++++++++ .../bpm/processInstance/detail/index.vue | 129 +++--------------- 2 files changed, 109 insertions(+), 109 deletions(-) create mode 100644 src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue b/src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue new file mode 100644 index 00000000..3bbabcc6 --- /dev/null +++ b/src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue @@ -0,0 +1,89 @@ +<template> + <el-card class="box-card" v-loading="loading"> + <template #header> + <span class="el-icon-picture-outline">审批记录</span> + </template> + <el-col :span="16" :offset="4"> + <div class="block"> + <el-timeline> + <el-timeline-item + v-for="(item, index) in tasks" + :key="index" + :icon="getTimelineItemIcon(item)" + :type="getTimelineItemType(item)" + > + <p style="font-weight: 700">任务:{{ item.name }}</p> + <el-card :body-style="{ padding: '10px' }"> + <label v-if="item.assigneeUser" style="font-weight: normal; margin-right: 30px"> + 审批人:{{ item.assigneeUser.nickname }} + <el-tag type="info" size="small">{{ item.assigneeUser.deptName }}</el-tag> + </label> + <label style="font-weight: normal" v-if="item.createTime">创建时间:</label> + <label style="color: #8a909c; font-weight: normal"> + {{ parseTime(item?.createTime) }} + </label> + <label v-if="item.endTime" style="margin-left: 30px; font-weight: normal"> + 审批时间: + </label> + <label v-if="item.endTime" style="color: #8a909c; font-weight: normal"> + {{ parseTime(item?.endTime) }} + </label> + <label v-if="item.durationInMillis" style="margin-left: 30px; font-weight: normal"> + 耗时: + </label> + <label v-if="item.durationInMillis" style="color: #8a909c; font-weight: normal"> + {{ formatPast2(item?.durationInMillis) }} + </label> + <p v-if="item.reason"> + <el-tag :type="getTimelineItemType(item)">{{ item.reason }}</el-tag> + </p> + </el-card> + </el-timeline-item> + </el-timeline> + </div> + </el-col> + </el-card> +</template> +<script setup lang="ts"> +import { parseTime, formatPast2 } from '@/utils/formatTime' +import { propTypes } from '@/utils/propTypes' + +defineProps({ + loading: propTypes.bool, // 是否加载中 + tasks: propTypes.array // 流程任务的数组 +}) + +/** 获得任务对应的 icon */ +const getTimelineItemIcon = (item) => { + if (item.result === 1) { + return 'el-icon-time' + } + if (item.result === 2) { + return 'el-icon-check' + } + if (item.result === 3) { + return 'el-icon-close' + } + if (item.result === 4) { + return 'el-icon-remove-outline' + } + return '' +} + +/** 获得任务对应的颜色 */ +const getTimelineItemType = (item) => { + if (item.result === 1) { + return 'primary' + } + if (item.result === 2) { + return 'success' + } + if (item.result === 3) { + return 'danger' + } + if (item.result === 4) { + return 'info' + } + return '' +} +</script> diff --git a/src/views/bpm/processInstance/detail/index.vue b/src/views/bpm/processInstance/detail/index.vue index 1651f97a..54296389 100644 --- a/src/views/bpm/processInstance/detail/index.vue +++ b/src/views/bpm/processInstance/detail/index.vue @@ -81,50 +81,7 @@ </el-card> <!-- 审批记录 --> - <el-card class="box-card" v-loading="tasksLoad"> - <template #header> - <span class="el-icon-picture-outline">审批记录</span> - </template> - <el-col :span="16" :offset="4"> - <div class="block"> - <el-timeline> - <el-timeline-item - v-for="(item, index) in tasks" - :key="index" - :icon="getTimelineItemIcon(item)" - :type="getTimelineItemType(item)" - > - <p style="font-weight: 700">任务:{{ item.name }}</p> - <el-card :body-style="{ padding: '10px' }"> - <label v-if="item.assigneeUser" style="font-weight: normal; margin-right: 30px"> - 审批人:{{ item.assigneeUser.nickname }} - <el-tag type="info" size="small">{{ item.assigneeUser.deptName }}</el-tag> - </label> - <label style="font-weight: normal" v-if="item.createTime">创建时间:</label> - <label style="color: #8a909c; font-weight: normal"> - {{ parseTime(item?.createTime) }} - </label> - <label v-if="item.endTime" style="margin-left: 30px; font-weight: normal"> - 审批时间: - </label> - <label v-if="item.endTime" style="color: #8a909c; font-weight: normal"> - {{ parseTime(item?.endTime) }} - </label> - <label v-if="item.durationInMillis" style="margin-left: 30px; font-weight: normal"> - 耗时: - </label> - <label v-if="item.durationInMillis" style="color: #8a909c; font-weight: normal"> - {{ formatPast2(item?.durationInMillis) }} - </label> - <p v-if="item.reason"> - <el-tag :type="getTimelineItemType(item)">{{ item.reason }}</el-tag> - </p> - </el-card> - </el-timeline-item> - </el-timeline> - </div> - </el-col> - </el-card> + <ProcessInstanceTaskList :loading="tasksLoad" :tasks="tasks" /> <!-- 高亮流程图 --> <ProcessInstanceBpmnViewer @@ -140,38 +97,46 @@ </ContentWrap> </template> <script setup lang="ts"> -import { parseTime } from '@/utils/formatTime' -import * as ProcessInstanceApi from '@/api/bpm/processInstance' -import * as TaskApi from '@/api/bpm/task' -import { formatPast2 } from '@/utils/formatTime' import { setConfAndFields2 } from '@/utils/formCreate' import type { ApiAttrs } from '@form-create/element-ui/types/config' import { useUserStore } from '@/store/modules/user' +import * as DefinitionApi from '@/api/bpm/definition' +import * as ProcessInstanceApi from '@/api/bpm/processInstance' +import * as TaskApi from '@/api/bpm/task' import TaskUpdateAssigneeForm from './TaskUpdateAssigneeForm.vue' import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue' -import * as DefinitionApi from '@/api/bpm/definition' - +import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue' const { query } = useRoute() // 查询参数 const message = useMessage() // 消息弹窗 const { proxy } = getCurrentInstance() as any -// ========== 审批信息 ========== -const id = query.id as unknown as number +const userId = useUserStore().getUser.id // 当前登录的编号 +const id = query.id as unknown as number // 流程实例的编号 const processInstanceLoading = ref(false) // 流程实例的加载中 const processInstance = ref<any>({}) // 流程实例 +const bpmnXML = ref('') // BPMN XML +const tasksLoad = ref(true) // 任务的加载中 +const tasks = ref<any[]>([]) // 任务列表 +// ========== 审批信息 ========== const runningTasks = ref<any[]>([]) // 运行中的任务 const auditForms = ref<any[]>([]) // 审批任务的表单 const auditRule = reactive({ reason: [{ required: true, message: '审批建议不能为空', trigger: 'blur' }] }) +// ========== 申请信息 ========== +const fApi = ref<ApiAttrs>() // +const detailForm = ref({ + // 流程表单详情 + rule: [], + option: {}, + value: {} +}) -// 处理审批通过和不通过的操作 +/** 处理审批通过和不通过的操作 */ const handleAudit = async (task, pass) => { // 1.1 获得对应表单 const index = runningTasks.value.indexOf(task) const auditFormRef = proxy.$refs['form' + index][0] - // alert(auditFormRef) - // 1.2 校验表单 const elForm = unref(auditFormRef) if (!elForm) return @@ -194,54 +159,6 @@ const handleAudit = async (task, pass) => { getDetail() } -// ========== 申请信息 ========== -const fApi = ref<ApiAttrs>() -const userId = useUserStore().getUser.id // 当前登录的编号 -// 流程表单详情 -const detailForm = ref({ - rule: [], - option: {}, - value: {} -}) - -// ========== 审批记录 ========== -const tasksLoad = ref(true) -const tasks = ref<any[]>([]) - -const getTimelineItemIcon = (item) => { - if (item.result === 1) { - return 'el-icon-time' - } - if (item.result === 2) { - return 'el-icon-check' - } - if (item.result === 3) { - return 'el-icon-close' - } - if (item.result === 4) { - return 'el-icon-remove-outline' - } - return '' -} -const getTimelineItemType = (item) => { - if (item.result === 1) { - return 'primary' - } - if (item.result === 2) { - return 'success' - } - if (item.result === 3) { - return 'danger' - } - if (item.result === 4) { - return 'info' - } - return '' -} - -// ========== 审批记录 ========== -const bpmnXML = ref('') - /** 转派审批人 */ const taskUpdateAssigneeFormRef = ref() const openTaskUpdateAssigneeForm = (id: string) => { @@ -352,9 +269,3 @@ const getDetail = () => { }) } </script> -<style lang="scss"> -.box-card { - width: 100%; - margin-bottom: 20px; -} -</style> From 7587acedb0e7ccbc3993acaf694b50f69a66b66f Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Tue, 28 Mar 2023 07:45:18 +0800 Subject: [PATCH 137/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E5=AE=9E=E4=BE=8B=E7=9A=84=E8=AF=A6=E6=83=85?= =?UTF-8?q?=EF=BC=88=E6=B5=81=E7=A8=8B=E4=BB=BB=E5=8A=A1=E5=88=97=E8=A1=A8?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bpm/processInstance/detail/index.vue | 184 +++++++++--------- 1 file changed, 95 insertions(+), 89 deletions(-) diff --git a/src/views/bpm/processInstance/detail/index.vue b/src/views/bpm/processInstance/detail/index.vue index 54296389..3a48b3b3 100644 --- a/src/views/bpm/processInstance/detail/index.vue +++ b/src/views/bpm/processInstance/detail/index.vue @@ -97,9 +97,9 @@ </ContentWrap> </template> <script setup lang="ts"> +import { useUserStore } from '@/store/modules/user' import { setConfAndFields2 } from '@/utils/formCreate' import type { ApiAttrs } from '@form-create/element-ui/types/config' -import { useUserStore } from '@/store/modules/user' import * as DefinitionApi from '@/api/bpm/definition' import * as ProcessInstanceApi from '@/api/bpm/processInstance' import * as TaskApi from '@/api/bpm/task' @@ -177,95 +177,101 @@ const handleBack = async (task) => { console.log(task) } -// ========== 初始化 ========== +/** 获得详情 */ +const getDetail = () => { + // 1. 获得流程实例相关 + getProcessInstance() + // 2. 获得流程任务列表(审批记录) + getTaskList() +} + +/** 加载流程实例 */ +const getProcessInstance = async () => { + try { + processInstanceLoading.value = true + const data = await ProcessInstanceApi.getProcessInstanceApi(id) + if (!data) { + message.error('查询不到流程信息!') + return + } + processInstance.value = data + + // 设置表单信息 + const processDefinition = data.processDefinition + if (processDefinition.formType === 10) { + setConfAndFields2( + detailForm, + processDefinition.formConf, + processDefinition.formFields, + data.formVariables + ) + nextTick().then(() => { + fApi.value?.fapi?.btn.show(false) + fApi.value?.fapi?.resetBtn.show(false) + fApi.value?.fapi?.disabled(true) + }) + } + + // 加载流程图 + bpmnXML.value = await DefinitionApi.getProcessDefinitionBpmnXML(processDefinition.id) + } finally { + processInstanceLoading.value = false + } +} + +/** 加载任务列表 */ +const getTaskList = async () => { + try { + // 获得未取消的任务 + tasksLoad.value = true + const data = await TaskApi.getTaskListByProcessInstanceId(id) + tasks.value = [] + // 1.1 移除已取消的审批 + data.forEach((task) => { + if (task.result !== 4) { + tasks.value.push(task) + } + }) + // 1.2 排序,将未完成的排在前面,已完成的排在后面; + tasks.value.sort((a, b) => { + // 有已完成的情况,按照完成时间倒序 + if (a.endTime && b.endTime) { + return b.endTime - a.endTime + } else if (a.endTime) { + return 1 + } else if (b.endTime) { + return -1 + // 都是未完成,按照创建时间倒序 + } else { + return b.createTime - a.createTime + } + }) + + // 获得需要自己审批的任务 + runningTasks.value = [] + auditForms.value = [] + tasks.value.forEach((task) => { + // 2.1 只有待处理才需要 + if (task.result !== 1) { + return + } + // 2.2 自己不是处理人 + if (!task.assigneeUser || task.assigneeUser.id !== userId) { + return + } + // 2.3 添加到处理任务 + runningTasks.value.push({ ...task }) + auditForms.value.push({ + reason: '' + }) + }) + } finally { + tasksLoad.value = false + } +} + +/** 初始化 */ onMounted(() => { getDetail() }) - -const getDetail = () => { - // 1. 获得流程实例相关 - processInstanceLoading.value = true - ProcessInstanceApi.getProcessInstanceApi(id) - .then((data) => { - if (!data) { - message.error('查询不到流程信息!') - return - } - processInstance.value = data - - // 设置表单信息 - const processDefinition = data.processDefinition - if (processDefinition.formType === 10) { - setConfAndFields2( - detailForm, - processDefinition.formConf, - processDefinition.formFields, - data.formVariables - ) - nextTick().then(() => { - fApi.value?.fapi?.btn.show(false) - fApi.value?.fapi?.resetBtn.show(false) - fApi.value?.fapi?.disabled(true) - }) - } - - // 加载流程图 - DefinitionApi.getProcessDefinitionBpmnXML(processDefinition.id).then((data) => { - bpmnXML.value = data - }) - }) - .finally(() => { - processInstanceLoading.value = false - }) - - // 2. 获得流程任务列表(审批记录) - tasksLoad.value = true - runningTasks.value = [] - auditForms.value = [] - TaskApi.getTaskListByProcessInstanceId(id) - .then((data) => { - // 审批记录 - tasks.value = [] - // 移除已取消的审批 - data.forEach((task) => { - if (task.result !== 4) { - tasks.value.push(task) - } - }) - // 排序,将未完成的排在前面,已完成的排在后面; - tasks.value.sort((a, b) => { - // 有已完成的情况,按照完成时间倒序 - if (a.endTime && b.endTime) { - return b.endTime - a.endTime - } else if (a.endTime) { - return 1 - } else if (b.endTime) { - return -1 - // 都是未完成,按照创建时间倒序 - } else { - return b.createTime - a.createTime - } - }) - - // 需要审核的记录 - tasks.value.forEach((task) => { - // 1.1 只有待处理才需要 - if (task.result !== 1) { - return - } - // 1.2 自己不是处理人 - if (!task.assigneeUser || task.assigneeUser.id !== userId) { - return - } - // 2. 添加到处理任务 - runningTasks.value.push({ ...task }) - auditForms.value.push({ - reason: '' - }) - }) - }) - .finally(() => { - tasksLoad.value = false - }) -} </script> From ddd6bbbee1718a12b1af0b32dc4ac088e90d4e26 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Tue, 28 Mar 2023 07:51:46 +0800 Subject: [PATCH 138/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E5=AE=9E=E4=BE=8B=E7=9A=84=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E7=9A=84=E7=9B=AE=E5=BD=95=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{create.vue => create/index.vue} | 34 +++---------------- .../{ => create}/process.create.ts | 0 .../detail/ProcessInstanceBpmnViewer.vue | 8 +++-- 3 files changed, 9 insertions(+), 33 deletions(-) rename src/views/bpm/processInstance/{create.vue => create/index.vue} (84%) rename src/views/bpm/processInstance/{ => create}/process.create.ts (100%) diff --git a/src/views/bpm/processInstance/create.vue b/src/views/bpm/processInstance/create/index.vue similarity index 84% rename from src/views/bpm/processInstance/create.vue rename to src/views/bpm/processInstance/create/index.vue index b6fc0f49..023788c3 100644 --- a/src/views/bpm/processInstance/create.vue +++ b/src/views/bpm/processInstance/create/index.vue @@ -37,31 +37,20 @@ /> </el-col> </el-card> - <el-card class="box-card"> - <div class="clearfix"> - <span class="el-icon-picture-outline">流程图</span> - </div> - <!-- TODO 芋艿:待完成??? --> - <my-process-viewer - key="designer" - v-model="bpmnXML" - :value="bpmnXML" - v-bind="bpmnControlForm" - :prefix="bpmnControlForm.prefix" - /> - </el-card> + <!-- 流程图预览 --> + <ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML" /> </div> </ContentWrap> </template> <script setup lang="ts"> +import { DICT_TYPE } from '@/utils/dict' // 业务相关的 import import { allSchemas } from './process.create' import * as DefinitionApi from '@/api/bpm/definition' import * as ProcessInstanceApi from '@/api/bpm/processInstance' import { setConfAndFields2 } from '@/utils/formCreate' import type { ApiAttrs } from '@form-create/element-ui/types/config' -import { DICT_TYPE } from '@/utils/dict' - +import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue' const router = useRouter() // 路由 const message = useMessage() // 消息 @@ -126,7 +115,6 @@ const submitForm = async (formData) => { }) // 提示 message.success('发起流程成功') - // this.$tab.closeOpenPage(); router.go(-1) } finally { fApi.value.btn.loading(false) @@ -137,18 +125,4 @@ const submitForm = async (formData) => { // // BPMN 数据 const bpmnXML = ref(null) -const bpmnControlForm = ref({ - prefix: 'flowable' -}) </script> - -<style lang="scss"> -.my-process-designer { - height: calc(100vh - 200px); -} - -.box-card { - width: 100%; - margin-bottom: 20px; -} -</style> diff --git a/src/views/bpm/processInstance/process.create.ts b/src/views/bpm/processInstance/create/process.create.ts similarity index 100% rename from src/views/bpm/processInstance/process.create.ts rename to src/views/bpm/processInstance/create/process.create.ts diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceBpmnViewer.vue b/src/views/bpm/processInstance/detail/ProcessInstanceBpmnViewer.vue index a06ac368..3a3e244b 100644 --- a/src/views/bpm/processInstance/detail/ProcessInstanceBpmnViewer.vue +++ b/src/views/bpm/processInstance/detail/ProcessInstanceBpmnViewer.vue @@ -40,9 +40,11 @@ const activityList = ref([]) // 任务列表 /** 初始化 */ onMounted(async () => { - activityList.value = await ActivityApi.getActivityList({ - processInstanceId: props.id - }) + if (props.id) { + activityList.value = await ActivityApi.getActivityList({ + processInstanceId: props.id + }) + } }) </script> <style> From d7891fe7592bc32d24efe81875dcdc467482ee90 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Tue, 28 Mar 2023 08:10:11 +0800 Subject: [PATCH 139/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E5=AE=9E=E4=BE=8B=E7=9A=84=E5=88=9B=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/bpm/processInstance/index.ts | 1 + src/router/modules/remaining.ts | 2 +- .../bpm/processInstance/create/index.vue | 126 +++++++++--------- .../processInstance/create/process.create.ts | 39 ------ 4 files changed, 66 insertions(+), 102 deletions(-) delete mode 100644 src/views/bpm/processInstance/create/process.create.ts diff --git a/src/api/bpm/processInstance/index.ts b/src/api/bpm/processInstance/index.ts index d48253c3..95591ae3 100644 --- a/src/api/bpm/processInstance/index.ts +++ b/src/api/bpm/processInstance/index.ts @@ -4,6 +4,7 @@ export type Task = { id: string name: string } + export type ProcessInstanceVO = { id: number name: string diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index 380e3618..4d8c3dac 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -272,7 +272,7 @@ const remainingRouter: AppRouteRecordRaw[] = [ }, { path: '/process-instance/create', - component: () => import('@/views/bpm/processInstance/create.vue'), + component: () => import('@/views/bpm/processInstance/create/index.vue'), name: 'BpmProcessInstanceCreate', meta: { noCache: true, diff --git a/src/views/bpm/processInstance/create/index.vue b/src/views/bpm/processInstance/create/index.vue index 023788c3..dd043eae 100644 --- a/src/views/bpm/processInstance/create/index.vue +++ b/src/views/bpm/processInstance/create/index.vue @@ -1,51 +1,53 @@ <template> - <ContentWrap> - <!-- 第一步,通过流程定义的列表,选择对应的流程 --> - <div v-if="!selectProcessInstance"> - <XTable @register="registerTable"> - <!-- 流程分类 --> - <template #category_default="{ row }"> - <DictTag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="Number(row?.category)" /> + <!-- 第一步,通过流程定义的列表,选择对应的流程 --> + <ContentWrap v-if="!selectProcessInstance"> + <el-table v-loading="loading" :data="list"> + <el-table-column label="流程名称" align="center" prop="name" /> + <el-table-column label="流程分类" align="center" prop="category"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" /> </template> - <template #version_default="{ row }"> - <el-tag v-if="row">v{{ row.version }}</el-tag> + </el-table-column> + <el-table-column label="流程版本" align="center" prop="version"> + <template #default="scope"> + <el-tag>v{{ scope.row.version }}</el-tag> </template> - <template #actionbtns_default="{ row }"> - <XTextButton preIcon="ep:plus" title="选择" @click="handleSelect(row)" /> + </el-table-column> + <el-table-column label="流程描述" align="center" prop="description" /> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button link type="primary" @click="handleSelect(scope.row)"> + <Icon icon="ep:plus" /> 选择 + </el-button> </template> - </XTable> - </div> - <!-- 第二步,填写表单,进行流程的提交 --> - <div v-else> - <el-card class="box-card"> - <div class="clearfix"> - <span class="el-icon-document">申请信息【{{ selectProcessInstance.name }}】</span> - <XButton - style="float: right" - type="primary" - preIcon="ep:delete" - title="选择其它流程" - @click="selectProcessInstance = undefined" - /> - </div> - <el-col :span="16" :offset="6" style="margin-top: 20px"> - <form-create - :rule="detailForm.rule" - v-model:api="fApi" - :option="detailForm.option" - @submit="submitForm" - /> - </el-col> - </el-card> - <!-- 流程图预览 --> - <ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML" /> - </div> + </el-table-column> + </el-table> + </ContentWrap> + + <!-- 第二步,填写表单,进行流程的提交 --> + <ContentWrap v-else> + <el-card class="box-card"> + <div class="clearfix"> + <span class="el-icon-document">申请信息【{{ selectProcessInstance.name }}】</span> + <el-button style="float: right" type="primary" @click="selectProcessInstance = undefined"> + <Icon icon="ep:delete" /> 选择其它流程 + </el-button> + </div> + <el-col :span="16" :offset="6" style="margin-top: 20px"> + <form-create + :rule="detailForm.rule" + v-model:api="fApi" + :option="detailForm.option" + @submit="submitForm" + /> + </el-col> + </el-card> + <!-- 流程图预览 --> + <ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML" /> </ContentWrap> </template> <script setup lang="ts"> import { DICT_TYPE } from '@/utils/dict' -// 业务相关的 import -import { allSchemas } from './process.create' import * as DefinitionApi from '@/api/bpm/definition' import * as ProcessInstanceApi from '@/api/bpm/processInstance' import { setConfAndFields2 } from '@/utils/formCreate' @@ -55,28 +57,32 @@ const router = useRouter() // 路由 const message = useMessage() // 消息 // ========== 列表相关 ========== - -const [registerTable] = useXTable({ - allSchemas: allSchemas, - params: { - suspensionState: 1 - }, - getListApi: DefinitionApi.getProcessDefinitionList, - isList: true +const loading = ref(true) // 列表的加载中 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + suspensionState: 1 }) +/** 查询列表 */ +const getList = async () => { + loading.value = true + try { + list.value = await DefinitionApi.getProcessDefinitionList(queryParams) + } finally { + loading.value = false + } +} + // ========== 表单相关 ========== - +const bpmnXML = ref(null) // BPMN 数据 const fApi = ref<ApiAttrs>() - -// 流程表单详情 const detailForm = ref({ + // 流程表单详情 rule: [], option: {} }) - -// 流程表单 const selectProcessInstance = ref() // 选择的流程实例 + /** 处理选择流程的按钮操作 **/ const handleSelect = async (row) => { // 设置选择的流程 @@ -86,11 +92,8 @@ const handleSelect = async (row) => { if (row.formType == 10) { // 设置表单 setConfAndFields2(detailForm, row.formConf, row.formFields) - // 加载流程图 - DefinitionApi.getProcessDefinitionBpmnXML(row.id).then((response) => { - bpmnXML.value = response - }) + bpmnXML.value = await DefinitionApi.getProcessDefinitionBpmnXML(row.id) // 情况二:业务表单 } else if (row.formCustomCreatePath) { await router.push({ @@ -105,7 +108,6 @@ const submitForm = async (formData) => { if (!fApi.value || !selectProcessInstance.value) { return } - // 提交请求 fApi.value.btn.loading(true) try { @@ -121,8 +123,8 @@ const submitForm = async (formData) => { } } -// ========== 流程图相关 ========== - -// // BPMN 数据 -const bpmnXML = ref(null) +/** 初始化 */ +onMounted(() => { + getList() +}) </script> diff --git a/src/views/bpm/processInstance/create/process.create.ts b/src/views/bpm/processInstance/create/process.create.ts deleted file mode 100644 index 7516c0b4..00000000 --- a/src/views/bpm/processInstance/create/process.create.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' - -// crudSchemas -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: null, - action: true, - columns: [ - { - title: '流程名称', - field: 'name' - }, - { - title: '流程分类', - field: 'category', - dictType: DICT_TYPE.BPM_MODEL_CATEGORY, - dictClass: 'number', - table: { - slots: { - default: 'category_default' - } - } - }, - { - title: '流程版本', - field: 'version', - table: { - slots: { - default: 'version_default' - } - } - }, - { - title: '流程描述', - field: 'description' - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From 66d412c10ada2e8200a72b947c38073b7019b532 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Tue, 28 Mar 2023 09:24:12 +0800 Subject: [PATCH 140/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E5=85=AC=E4=BC=97=E5=8F=B7=E6=A0=87=E7=AD=BE=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/mp/account/index.vue | 14 +++--- src/views/mp/statistics/index.vue | 71 +++++++++++++------------------ 2 files changed, 36 insertions(+), 49 deletions(-) diff --git a/src/views/mp/account/index.vue b/src/views/mp/account/index.vue index 3489998b..2629c658 100644 --- a/src/views/mp/account/index.vue +++ b/src/views/mp/account/index.vue @@ -121,13 +121,13 @@ const queryFormRef = ref() // 搜索的表单 /** 查询列表 */ const getList = async () => { loading.value = true - // 处理查询参数 - let params = { ...queryParams } - // 执行查询 - const data = await AccountApi.getAccountPage(params) - list.value = data.list - total.value = data.total - loading.value = false + try { + const data = await AccountApi.getAccountPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } } /** 搜索按钮操作 */ diff --git a/src/views/mp/statistics/index.vue b/src/views/mp/statistics/index.vue index 9411b674..d1dcb53c 100644 --- a/src/views/mp/statistics/index.vue +++ b/src/views/mp/statistics/index.vue @@ -5,7 +5,7 @@ <el-form-item label="公众号" prop="accountId"> <el-select v-model="accountId" @change="getSummary"> <el-option - v-for="item in accounts" + v-for="item in accountList" :key="parseInt(item.id)" :label="item.name" :value="parseInt(item.id)" @@ -83,17 +83,16 @@ </template> <script setup lang="ts" name="MpStatistics"> -// 引入基本模板 import * as echarts from 'echarts' import { getInterfaceSummary, getUpstreamMessage, getUserCumulate, getUserSummary -} from '@/api/mp/statistics' -import { addTime, beginOfDay, betweenDay, endOfDay, formatDate } from '@/utils/dateUtils' -import { getSimpleAccountList } from '@/api/mp/account' - +} from '@/api/mp/statistics' // TODO 改成 StatisticsApi 整体引入 +import { addTime, beginOfDay, betweenDay, endOfDay } from '@/utils/dateUtils' // TODO 可以改到 formatTime 里 +import { formatDate } from '@/utils/formatTime' +import * as MpAccountApi from '@/api/mp/account' const message = useMessage() // 消息弹窗 const defaultTime = ref<[Date, Date]>([ @@ -105,18 +104,13 @@ const dateRange = ref([ endOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24)) ]) // -1 天 const accountId = ref() -const accounts = ref([ - { - id: '0', - name: '' - } -]) +const accountList = ref<MpAccountApi.AccountVO[]>([]) // 公众号账号列表 const userSummaryChartRef = ref() const userCumulateChartRef = ref() const upstreamMessageChartRef = ref() const interfaceSummaryChartRef = ref() - +// TODO 看看能不能用 https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/echart.html 组件 const xAxisDate = ref([] as any[]) // X 轴的日期范围 const userSummaryOption = reactive({ // 用户增减数据 @@ -257,20 +251,16 @@ const interfaceSummaryOption = reactive({ ] }) -onMounted(async () => { - // 获取公众号下拉列表 - await getAccountList() - // 加载数据 - getSummary() -}) +/** 加载公众号账号的列表 */ const getAccountList = async () => { - const data = await getSimpleAccountList() - accounts.value = data + accountList.value = await MpAccountApi.getSimpleAccountList() // 默认选中第一个 - if (accounts.value.length > 0) { - accountId.value = accounts.value[0].id + if (accountList.value.length > 0) { + accountId.value = accountList.value[0].id } } + +// TODO 一些方法的注释补全下 const getSummary = () => { // 如果没有选中公众号账号,则进行提示。 if (!accountId) { @@ -286,7 +276,7 @@ const getSummary = () => { const days = betweenDay(dateRange.value[0], dateRange.value[1]) // 相差天数 for (let i = 0; i <= days; i++) { xAxisDate.value.push( - formatDate(addTime(dateRange.value[0], 3600 * 1000 * 24 * i), 'yyyy-MM-dd') + formatDate(addTime(dateRange.value[0], 3600 * 1000 * 24 * i), 'YYYY-MM-DD') ) } @@ -296,6 +286,7 @@ const getSummary = () => { initUpstreamMessageChart() interfaceSummaryChart() } + const initUserSummaryChart = async () => { userSummaryOption.xAxis.data = [] userSummaryOption.series[0].data = [] @@ -303,18 +294,14 @@ const initUserSummaryChart = async () => { try { const data = await getUserSummary({ accountId: accountId.value, - date: [ - formatDate(dateRange.value[0], 'yyyy-MM-dd HH:mm:ss'), - formatDate(dateRange.value[1], 'yyyy-MM-dd HH:mm:ss') - ] + date: [formatDate(dateRange.value[0]), formatDate(dateRange.value[1])] }) - userSummaryOption.xAxis.data = xAxisDate.value // 处理数据 xAxisDate.value.forEach((date, index) => { data.forEach((item) => { // 匹配日期 - const refDate = formatDate(new Date(item.refDate), 'yyyy-MM-dd') + const refDate = formatDate(new Date(item.refDate), 'YYYY-MM-DD') if (refDate.indexOf(date) === -1) { return } @@ -328,6 +315,7 @@ const initUserSummaryChart = async () => { userSummaryChart.setOption(userSummaryOption) } catch {} } + const initUserCumulateChart = async () => { userCumulateOption.xAxis.data = [] userCumulateOption.series[0].data = [] @@ -335,10 +323,7 @@ const initUserCumulateChart = async () => { try { const data = await getUserCumulate({ accountId: accountId.value, - date: [ - formatDate(dateRange.value[0], 'yyyy-MM-dd HH:mm:ss'), - formatDate(dateRange.value[1], 'yyyy-MM-dd HH:mm:ss') - ] + date: [formatDate(dateRange.value[0]), formatDate(dateRange.value[1])] }) userCumulateOption.xAxis.data = xAxisDate.value // 处理数据 @@ -358,10 +343,7 @@ const initUpstreamMessageChart = async () => { try { const data = await getUpstreamMessage({ accountId: accountId.value, - date: [ - formatDate(dateRange.value[0], 'yyyy-MM-dd HH:mm:ss'), - formatDate(dateRange.value[1], 'yyyy-MM-dd HH:mm:ss') - ] + date: [formatDate(dateRange.value[0]), formatDate(dateRange.value[1])] }) upstreamMessageOption.xAxis.data = xAxisDate.value // 处理数据 @@ -384,10 +366,7 @@ const interfaceSummaryChart = async () => { try { const data = await getInterfaceSummary({ accountId: accountId.value, - date: [ - formatDate(dateRange.value[0], 'yyyy-MM-dd HH:mm:ss'), - formatDate(dateRange.value[1], 'yyyy-MM-dd HH:mm:ss') - ] + date: [formatDate(dateRange.value[0]), formatDate(dateRange.value[1])] }) interfaceSummaryOption.xAxis.data = xAxisDate.value // 处理数据 @@ -402,4 +381,12 @@ const interfaceSummaryChart = async () => { interfaceSummaryChart.setOption(interfaceSummaryOption) } catch {} } + +/** 初始化 */ +onMounted(async () => { + // 获取公众号下拉列表 + await getAccountList() + // 加载数据 + getSummary() +}) </script> From 658150c1c8e7f7d49cbb378a450914ba5c12bc98 Mon Sep 17 00:00:00 2001 From: kinlon92 <zhangjinlong1211@126.com> Date: Tue, 28 Mar 2023 18:23:58 +0800 Subject: [PATCH 141/184] =?UTF-8?q?Vue3=E9=87=8D=E6=9E=84=EF=BC=9A?= =?UTF-8?q?=E5=85=AC=E4=BC=97=E5=8F=B7=E7=BB=9F=E8=AE=A1=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20TODO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/dateUtils.ts | 79 ---------------------------- src/utils/formatTime.ts | 51 ++++++++++++++++++ src/views/mp/statistics/index.vue | 87 +++++++++++-------------------- 3 files changed, 82 insertions(+), 135 deletions(-) delete mode 100644 src/utils/dateUtils.ts diff --git a/src/utils/dateUtils.ts b/src/utils/dateUtils.ts deleted file mode 100644 index 617a88af..00000000 --- a/src/utils/dateUtils.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * 将毫秒,转换成时间字符串。例如说,xx 分钟 - * - * @param ms 毫秒 - * @returns {string} 字符串 - */ -export function getDate(ms) { - const day = Math.floor(ms / (24 * 60 * 60 * 1000)) - const hour = Math.floor(ms / (60 * 60 * 1000) - day * 24) - const minute = Math.floor(ms / (60 * 1000) - day * 24 * 60 - hour * 60) - const second = Math.floor(ms / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60) - if (day > 0) { - return day + '天' + hour + '小时' + minute + '分钟' - } - if (hour > 0) { - return hour + '小时' + minute + '分钟' - } - if (minute > 0) { - return minute + '分钟' - } - if (second > 0) { - return second + '秒' - } else { - return 0 + '秒' - } -} - -export function beginOfDay(date) { - return new Date(date.getFullYear(), date.getMonth(), date.getDate()) -} - -export function endOfDay(date) { - return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999) -} - -export function betweenDay(date1, date2) { - date1 = convertDate(date1) - date2 = convertDate(date2) - // 计算差值 - return Math.floor((date2.getTime() - date1.getTime()) / (24 * 3600 * 1000)) -} - -export function formatDate(date, fmt) { - date = convertDate(date) - const o = { - 'M+': date.getMonth() + 1, //月份 - 'd+': date.getDate(), //日 - 'H+': date.getHours(), //小时 - 'm+': date.getMinutes(), //分 - 's+': date.getSeconds(), //秒 - 'q+': Math.floor((date.getMonth() + 3) / 3), //季度 - S: date.getMilliseconds() //毫秒 - } - if (/(y+)/.test(fmt)) { - // 年份 - fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) - } - for (const k in o) { - if (new RegExp('(' + k + ')').test(fmt)) { - fmt = fmt.replace( - RegExp.$1, - RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length) - ) - } - } - return fmt -} - -export function addTime(date, time) { - date = convertDate(date) - return new Date(date.getTime() + time) -} - -export function convertDate(date) { - if (typeof date === 'string') { - return new Date(date) - } - return date -} diff --git a/src/utils/formatTime.ts b/src/utils/formatTime.ts index 008bc929..d4dea9b5 100644 --- a/src/utils/formatTime.ts +++ b/src/utils/formatTime.ts @@ -196,3 +196,54 @@ export const dateFormatter = (row, column, cellValue) => { } return formatDate(cellValue) } + +/** + * 设置起始日期,时间为00:00:00 + * @param param 传入日期 + * @returns 带时间00:00:00的日期 + */ +export function beginOfDay(param: Date) { + return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 0, 0, 0, 0) +} + +/** + * 设置结束日期,时间为23:59:59 + * @param param 传入日期 + * @returns 带时间23:59:59的日期 + */ +export function endOfDay(param: Date) { + return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 23, 59, 59, 999) +} + +/** + * 计算两个日期间隔天数 + * @param param1 日期1 + * @param param2 日期2 + */ +export function betweenDay(param1: Date, param2: Date) { + param1 = convertDate(param1) + param2 = convertDate(param2) + // 计算差值 + return Math.floor((param2.getTime() - param1.getTime()) / (24 * 3600 * 1000)) +} + +/** + * 日期计算 + * @param param1 日期 + * @param param2 添加的时间 + */ +export function addTime(param1: Date, param2: number) { + param1 = convertDate(param1) + return new Date(param1.getTime() + param2) +} + +/** + * 日期转换 + * @param param 日期 + */ +export function convertDate(param: Date | string) { + if (typeof param === 'string') { + return new Date(param) + } + return param +} diff --git a/src/views/mp/statistics/index.vue b/src/views/mp/statistics/index.vue index d1dcb53c..c625f9dc 100644 --- a/src/views/mp/statistics/index.vue +++ b/src/views/mp/statistics/index.vue @@ -6,9 +6,9 @@ <el-select v-model="accountId" @change="getSummary"> <el-option v-for="item in accountList" - :key="parseInt(item.id)" + :key="item.id" :label="item.name" - :value="parseInt(item.id)" + :value="item.id" /> </el-select> </el-form-item> @@ -37,9 +37,7 @@ <span>用户增减数据</span> </div> </template> - <div class="el-table el-table--enable-row-hover el-table--medium"> - <div ref="userSummaryChartRef" style="height: 420px"></div> - </div> + <Echart :options="userSummaryOption" :height="420" /> </el-card> </el-col> <el-col :span="12" class="card-box"> @@ -49,9 +47,7 @@ <span>累计用户数据</span> </div> </template> - <div class="el-table el-table--enable-row-hover el-table--medium"> - <div ref="userCumulateChartRef" style="height: 420px"></div> - </div> + <Echart :options="userCumulateOption" :height="420" /> </el-card> </el-col> <el-col :span="12" class="card-box"> @@ -61,9 +57,7 @@ <span>消息概况数据</span> </div> </template> - <div class="el-table el-table--enable-row-hover el-table--medium"> - <div ref="upstreamMessageChartRef" style="height: 420px"></div> - </div> + <Echart :options="upstreamMessageOption" :height="420" /> </el-card> </el-col> <el-col :span="12" class="card-box"> @@ -73,9 +67,7 @@ <span>接口分析数据</span> </div> </template> - <div class="el-table el-table--enable-row-hover el-table--medium"> - <div ref="interfaceSummaryChartRef" style="height: 420px"></div> - </div> + <Echart :options="interfaceSummaryOption" :height="420" /> </el-card> </el-col> </el-row> @@ -83,38 +75,28 @@ </template> <script setup lang="ts" name="MpStatistics"> -import * as echarts from 'echarts' -import { - getInterfaceSummary, - getUpstreamMessage, - getUserCumulate, - getUserSummary -} from '@/api/mp/statistics' // TODO 改成 StatisticsApi 整体引入 -import { addTime, beginOfDay, betweenDay, endOfDay } from '@/utils/dateUtils' // TODO 可以改到 formatTime 里 -import { formatDate } from '@/utils/formatTime' +import * as StatisticsApi from '@/api/mp/statistics' +import { formatDate, addTime, betweenDay, beginOfDay, endOfDay } from '@/utils/formatTime' import * as MpAccountApi from '@/api/mp/account' const message = useMessage() // 消息弹窗 +// 默认时间 开始时间00:00:00 结束时间23:59:59 const defaultTime = ref<[Date, Date]>([ new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59) ]) +// 默认开始时间是当前日期-7,结束时间是当前日期-1 const dateRange = ref([ beginOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24 * 7)), endOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24)) -]) // -1 天 +]) const accountId = ref() const accountList = ref<MpAccountApi.AccountVO[]>([]) // 公众号账号列表 -const userSummaryChartRef = ref() -const userCumulateChartRef = ref() -const upstreamMessageChartRef = ref() -const interfaceSummaryChartRef = ref() -// TODO 看看能不能用 https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/echart.html 组件 const xAxisDate = ref([] as any[]) // X 轴的日期范围 +// 用户增减数据图表配置项 const userSummaryOption = reactive({ - // 用户增减数据 - color: ['#67C23A', '#e5323e'], + color: ['#67C23A', '#E5323E'], legend: { data: ['新增用户', '取消关注的用户'] }, @@ -145,8 +127,8 @@ const userSummaryOption = reactive({ } ] }) +// 累计用户数据图表配置项 const userCumulateOption = reactive({ - // 累计用户数据 legend: { data: ['累计用户量'] }, @@ -169,9 +151,9 @@ const userCumulateOption = reactive({ } ] }) +// 消息发送概况数据图表配置项 const upstreamMessageOption = reactive({ - // 消息发送概况数据 - color: ['#67C23A', '#e5323e'], + color: ['#67C23A', '#E5323E'], legend: { data: ['用户发送人数', '用户发送条数'] }, @@ -203,9 +185,9 @@ const upstreamMessageOption = reactive({ } ] }) +// 接口分析况数据图表配置项 const interfaceSummaryOption = reactive({ - // 接口分析况数据 - color: ['#67C23A', '#e5323e', '#E6A23C', '#409EFF'], + color: ['#67C23A', '#E5323E', '#E6A23C', '#409EFF'], legend: { data: ['被动回复用户消息的次数', '失败次数', '最大耗时', '总耗时'] }, @@ -260,7 +242,7 @@ const getAccountList = async () => { } } -// TODO 一些方法的注释补全下 +/** 加载数据 */ const getSummary = () => { // 如果没有选中公众号账号,则进行提示。 if (!accountId) { @@ -272,30 +254,33 @@ const getSummary = () => { message.error('时间间隔 7 天以内,请重新选择') return false } + // 清空横坐标日期 xAxisDate.value = [] + // 横坐标加载日期数据 const days = betweenDay(dateRange.value[0], dateRange.value[1]) // 相差天数 for (let i = 0; i <= days; i++) { xAxisDate.value.push( formatDate(addTime(dateRange.value[0], 3600 * 1000 * 24 * i), 'YYYY-MM-DD') ) } - // 初始化图表 initUserSummaryChart() initUserCumulateChart() initUpstreamMessageChart() interfaceSummaryChart() } - +/** 用户增减数据 */ const initUserSummaryChart = async () => { userSummaryOption.xAxis.data = [] userSummaryOption.series[0].data = [] userSummaryOption.series[1].data = [] try { - const data = await getUserSummary({ + // 用户增减数据 + const data = await StatisticsApi.getUserSummary({ accountId: accountId.value, date: [formatDate(dateRange.value[0]), formatDate(dateRange.value[1])] }) + // 横坐标 userSummaryOption.xAxis.data = xAxisDate.value // 处理数据 xAxisDate.value.forEach((date, index) => { @@ -310,18 +295,15 @@ const initUserSummaryChart = async () => { userSummaryOption.series[1].data[index] = item.cancelUser }) }) - // 绘制图表 - const userSummaryChart = echarts.init(userSummaryChartRef.value) - userSummaryChart.setOption(userSummaryOption) } catch {} } - +/** 累计用户数据 */ const initUserCumulateChart = async () => { userCumulateOption.xAxis.data = [] userCumulateOption.series[0].data = [] // 发起请求 try { - const data = await getUserCumulate({ + const data = await StatisticsApi.getUserCumulate({ accountId: accountId.value, date: [formatDate(dateRange.value[0]), formatDate(dateRange.value[1])] }) @@ -330,18 +312,16 @@ const initUserCumulateChart = async () => { data.forEach((item, index) => { userCumulateOption.series[0].data[index] = item.cumulateUser }) - // 绘制图表 - const userCumulateChart = echarts.init(userCumulateChartRef.value) - userCumulateChart.setOption(userCumulateOption) } catch {} } +/** 消息概况数据 */ const initUpstreamMessageChart = async () => { upstreamMessageOption.xAxis.data = [] upstreamMessageOption.series[0].data = [] upstreamMessageOption.series[1].data = [] // 发起请求 try { - const data = await getUpstreamMessage({ + const data = await StatisticsApi.getUpstreamMessage({ accountId: accountId.value, date: [formatDate(dateRange.value[0]), formatDate(dateRange.value[1])] }) @@ -351,11 +331,9 @@ const initUpstreamMessageChart = async () => { upstreamMessageOption.series[0].data[index] = item.messageUser upstreamMessageOption.series[1].data[index] = item.messageCount }) - // 绘制图表 - const upstreamMessageChart = echarts.init(upstreamMessageChartRef.value) - upstreamMessageChart.setOption(upstreamMessageOption) } catch {} } +/** 接口分析数据 */ const interfaceSummaryChart = async () => { interfaceSummaryOption.xAxis.data = [] interfaceSummaryOption.series[0].data = [] @@ -364,7 +342,7 @@ const interfaceSummaryChart = async () => { interfaceSummaryOption.series[3].data = [] // 发起请求 try { - const data = await getInterfaceSummary({ + const data = await StatisticsApi.getInterfaceSummary({ accountId: accountId.value, date: [formatDate(dateRange.value[0]), formatDate(dateRange.value[1])] }) @@ -376,9 +354,6 @@ const interfaceSummaryChart = async () => { interfaceSummaryOption.series[2].data[index] = item.maxTimeCost interfaceSummaryOption.series[3].data[index] = item.totalTimeCost }) - // 绘制图表 - const interfaceSummaryChart = echarts.init(interfaceSummaryChartRef.value) - interfaceSummaryChart.setOption(interfaceSummaryOption) } catch {} } From b8ce330c12700f9de73013308656964e80e2739b Mon Sep 17 00:00:00 2001 From: Siyu Kong <boil@vip.qq.com> Date: Tue, 28 Mar 2023 20:35:31 +0800 Subject: [PATCH 142/184] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E4=BA=A7?= =?UTF-8?q?=E5=93=81=E5=88=86=E7=B1=BB=E6=A8=A1=E5=9D=97=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?&api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/mall/product/category.ts | 60 +++++++++ src/views/mall/product/category/form.vue | 149 ++++++++++++++++++++++ src/views/mall/product/category/index.vue | 137 ++++++++++++++++++++ src/views/mall/product/category/utils.ts | 44 +++++++ 4 files changed, 390 insertions(+) create mode 100644 src/api/mall/product/category.ts create mode 100644 src/views/mall/product/category/form.vue create mode 100644 src/views/mall/product/category/index.vue create mode 100644 src/views/mall/product/category/utils.ts diff --git a/src/api/mall/product/category.ts b/src/api/mall/product/category.ts new file mode 100644 index 00000000..7ae81285 --- /dev/null +++ b/src/api/mall/product/category.ts @@ -0,0 +1,60 @@ +import request from '@/config/axios' + +/** + * 产品分类 + */ +export interface CategoryVO { + /** + * 分类编号 + */ + id?: number + /** + * 父分类编号 + */ + parentId?: number + /** + * 分类名称 + */ + name: string + /** + * 分类图片 + */ + picUrl: string + /** + * 分类排序 + */ + sort?: number + /** + * 分类描述 + */ + description?: string + /** + * 开启状态 + */ + status: number +} + +// 创建商品分类 +export const createCategory = (data: CategoryVO) => { + return request.post({ url: '/product/category/create', data }) +} + +// 更新商品分类 +export const updateCategory = (data: CategoryVO) => { + return request.put({ url: '/product/category/update', data }) +} + +// 删除商品分类 +export const deleteCategory = (id: number) => { + return request.delete({ url: `/product/category/delete?id=${id}` }) +} + +// 获得商品分类 +export const getCategory = (id: number) => { + return request.get({ url: `/product/category/get?id=${id}` }) +} + +// 获得商品分类列表 +export const getCategoryList = (params: any) => { + return request.get({ url: '/product/category/list', params }) +} diff --git a/src/views/mall/product/category/form.vue b/src/views/mall/product/category/form.vue new file mode 100644 index 00000000..e2b6f62a --- /dev/null +++ b/src/views/mall/product/category/form.vue @@ -0,0 +1,149 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="上级分类" prop="parentId"> + <el-tree-select + v-model="formData.parentId" + :data="parentCategoryOptions" + check-strictly + :render-after-expand="false" + placeholder="上级分类" + :props="{ label: 'name', value: 'id' }" + default-expand-all + /> + </el-form-item> + <el-form-item label="分类名称" prop="name"> + <el-input v-model="formData.name" placeholder="请输入分类名称" /> + </el-form-item> + <el-form-item label="分类图片" prop="picUrl"> + <UploadImg v-model="formData.picUrl" :limit="1" :is-show-tip="false" /> + <div v-if="formData.parentId === 0" style="font-size: 10px">推荐 200x100 图片分辨率</div> + <div v-else style="font-size: 10px">推荐 100x100 图片分辨率</div> + </el-form-item> + <el-form-item label="分类排序" prop="sort"> + <el-input-number v-model="formData.sort" controls-position="right" :min="0" /> + </el-form-item> + <el-form-item label="开启状态" prop="status"> + <el-radio-group v-model="formData.status"> + <el-radio + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="parseInt(dict.value)" + >{{ dict.label }} + </el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="分类描述"> + <el-input v-model="formData.description" type="textarea" placeholder="请输入分类描述" /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import * as ProductCategoryApi from '@/api/mall/product/category' +import { handleTree } from '@/utils/tree' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 + +const defaultFormData: ProductCategoryApi.CategoryVO = { + id: undefined, + name: '', + picUrl: '', + status: 0, + description: '' +} +const formData = ref({ ...defaultFormData }) +const formRules = reactive({ + parentId: [{ required: true, message: '请选择上级分类', trigger: 'blur' }], + name: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }], + picUrl: [{ required: true, message: '分类图片不能为空', trigger: 'blur' }], + sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }], + status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +const list = ref([]) +const parentCategoryOptions = ref<any[]>([]) + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + getList() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await ProductCategoryApi.getCategory(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +// 获取上级分类选项 +const getList = async () => { + try { + const data = await ProductCategoryApi.getCategoryList({}) + list.value = data + const tree = handleTree(data, 'id', 'parentId') + const menu = { id: 0, name: '顶级分类', children: tree } + parentCategoryOptions.value = [menu] + } finally { + } +} + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as ProductCategoryApi.CategoryVO + if (formType.value === 'create') { + await ProductCategoryApi.createCategory(data) + message.success(t('common.createSuccess')) + } else { + await ProductCategoryApi.updateCategory(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { ...defaultFormData } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/mall/product/category/index.vue b/src/views/mall/product/category/index.vue new file mode 100644 index 00000000..12f51cff --- /dev/null +++ b/src/views/mall/product/category/index.vue @@ -0,0 +1,137 @@ +<template> + <content-wrap> + <!-- 搜索工作栏 --> + <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px"> + <el-form-item label="分类名称" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入分类名称" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button type="primary" @click="openModal('create')" v-hasPermi="['infra:config:create']"> + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> + </el-form-item> + </el-form> + + <!-- 列表 --> + <el-table v-loading="loading" :data="list" align="center" default-expand-all row-key="id"> + <el-table-column label="分类名称" prop="name" sortable /> + <el-table-column label="分类图片" align="center" prop="picUrl"> + <template #default="scope"> + <img + v-if="scope.row.picUrl" + :src="scope.row.picUrl" + alt="分类图片" + style="height: 100px" + /> + </template> + </el-table-column> + <el-table-column label="分类排序" align="center" prop="sort" /> + <el-table-column label="开启状态" align="center" prop="status"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> + </template> + </el-table-column> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openModal('update', scope.row.id)" + v-hasPermi="['infra:config:update']" + > + 编辑 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['infra:config:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + </content-wrap> + + <!-- 表单弹窗:添加/修改 --> + <config-form ref="modalRef" @success="getList" /> +</template> +<script setup lang="ts" name="Config"> +import { DICT_TYPE } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' +import * as ProductCategoryApi from '@/api/mall/product/category' +import ConfigForm from './form.vue' +import { handleTree } from '@/utils/tree' + +const message = useMessage() // 消息弹窗 +const { t } = useI18n() // 国际化 + +const loading = ref(true) // 列表的加载中 +const list = ref<any[]>([]) // 列表的数据 +const queryParams = reactive({ + name: undefined +}) +const queryFormRef = ref() // 搜索的表单 + +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await ProductCategoryApi.getCategoryList(queryParams) + list.value = handleTree(data, 'id', 'parentId') + console.info(list) + } finally { + loading.value = false + } +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, id) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await ProductCategoryApi.deleteCategory(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +/** 初始化 **/ +onMounted(() => { + getList() +}) +</script> diff --git a/src/views/mall/product/category/utils.ts b/src/views/mall/product/category/utils.ts new file mode 100644 index 00000000..a3774f22 --- /dev/null +++ b/src/views/mall/product/category/utils.ts @@ -0,0 +1,44 @@ +export const parseTime = (time) => { + if (!time) { + return null + } + const format = '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'object') { + date = time + } else { + if (typeof time === 'string' && /^[0-9]+$/.test(time)) { + time = parseInt(time) + } else if (typeof time === 'string') { + time = time + .replace(new RegExp(/-/gm), '/') + .replace('T', ' ') + .replace(new RegExp(/\.[\d]{3}/gm), '') + } + if (typeof time === 'number' && time.toString().length === 10) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { + let value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { + return ['日', '一', '二', '三', '四', '五', '六'][value] + } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return time_str +} From a4dea6c25465cea7c4ebf82e5ba3b9ed85166890 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Tue, 28 Mar 2023 23:18:10 +0800 Subject: [PATCH 143/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E7=AB=99=E5=86=85=E4=BF=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/notify/message/index.ts | 22 +-- src/views/bpm/definition/index.vue | 2 +- src/views/bpm/form/index.vue | 2 +- src/views/bpm/group/index.vue | 2 +- src/views/bpm/model/index.vue | 2 +- src/views/bpm/taskAssignRule/index.vue | 2 +- src/views/infra/apiErrorLog/index.vue | 2 +- src/views/infra/config/index.vue | 2 +- src/views/infra/dataSourceConfig/index.vue | 2 +- src/views/infra/file/index.vue | 2 +- src/views/infra/fileConfig/index.vue | 2 +- src/views/mp/message/index.vue | 2 +- src/views/mp/tag/index.vue | 2 +- src/views/system/dict/data.vue | 2 +- src/views/system/loginlog/index.vue | 2 +- src/views/system/menu/index.vue | 2 +- .../{detail.vue => NotifyMessageDetail.vue} | 10 +- src/views/system/notify/message/index.vue | 43 ++--- .../{detail.vue => MyNotifyMessageDetail.vue} | 6 +- src/views/system/notify/my/index.vue | 157 +++++++----------- src/views/system/oauth2/token/index.vue | 2 +- src/views/system/operatelog/index.vue | 2 +- src/views/system/sensitiveWord/index.vue | 2 +- src/views/system/sms/channel/index.vue | 2 +- src/views/system/tenant/index.vue | 2 +- 25 files changed, 107 insertions(+), 171 deletions(-) rename src/views/system/notify/message/{detail.vue => NotifyMessageDetail.vue} (87%) rename src/views/system/notify/my/{detail.vue => MyNotifyMessageDetail.vue} (89%) diff --git a/src/api/system/notify/message/index.ts b/src/api/system/notify/message/index.ts index a42d75c2..e24c3f8c 100644 --- a/src/api/system/notify/message/index.ts +++ b/src/api/system/notify/message/index.ts @@ -15,31 +15,13 @@ export interface NotifyMessageVO { readTime: Date } -export interface NotifyMessagePageReqVO extends PageParam { - userId?: number - userType?: number - templateCode?: string - templateType?: number - createTime?: Date[] -} - -export interface NotifyMessageMyPageReqVO extends PageParam { - readStatus?: boolean - createTime?: Date[] -} - // 查询站内信消息列表 -export const getNotifyMessagePageApi = async (params: NotifyMessagePageReqVO) => { +export const getNotifyMessagePage = async (params: PageParam) => { return await request.get({ url: '/system/notify-message/page', params }) } -// 查询站内信消息详情 -export const getNotifyMessageApi = async (id: number) => { - return await request.get({ url: '/system/notify-message/get?id=' + id }) -} - // 获得我的站内信分页 -export const getMyNotifyMessagePage = async (params: NotifyMessageMyPageReqVO) => { +export const getMyNotifyMessagePage = async (params: PageParam) => { return await request.get({ url: '/system/notify-message/my-page', params }) } diff --git a/src/views/bpm/definition/index.vue b/src/views/bpm/definition/index.vue index ce643ff6..66a6a23b 100644 --- a/src/views/bpm/definition/index.vue +++ b/src/views/bpm/definition/index.vue @@ -110,7 +110,7 @@ const queryParams = reactive({ key: query.key }) -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/bpm/form/index.vue b/src/views/bpm/form/index.vue index 7e14b3e3..616e60be 100644 --- a/src/views/bpm/form/index.vue +++ b/src/views/bpm/form/index.vue @@ -103,7 +103,7 @@ const queryParams = reactive({ }) const queryFormRef = ref() // 搜索的表单 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/bpm/group/index.vue b/src/views/bpm/group/index.vue index 5829ebad..0b52e9a2 100644 --- a/src/views/bpm/group/index.vue +++ b/src/views/bpm/group/index.vue @@ -132,7 +132,7 @@ const queryParams = reactive({ const queryFormRef = ref() // 搜索的表单 const userList = ref([]) // 用户列表 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/bpm/model/index.vue b/src/views/bpm/model/index.vue index b19ed956..9a0d725f 100644 --- a/src/views/bpm/model/index.vue +++ b/src/views/bpm/model/index.vue @@ -248,7 +248,7 @@ const queryParams = reactive({ }) const queryFormRef = ref() // 搜索的表单 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/bpm/taskAssignRule/index.vue b/src/views/bpm/taskAssignRule/index.vue index feea80a2..4a4e76f8 100644 --- a/src/views/bpm/taskAssignRule/index.vue +++ b/src/views/bpm/taskAssignRule/index.vue @@ -56,7 +56,7 @@ const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表 const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表 const taskAssignScriptDictDatas = getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_SCRIPT) -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/infra/apiErrorLog/index.vue b/src/views/infra/apiErrorLog/index.vue index cfe4adb1..800b5043 100644 --- a/src/views/infra/apiErrorLog/index.vue +++ b/src/views/infra/apiErrorLog/index.vue @@ -182,7 +182,7 @@ const queryParams = reactive({ const queryFormRef = ref() // 搜索的表单 const exportLoading = ref(false) // 导出的加载中 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/infra/config/index.vue b/src/views/infra/config/index.vue index fcc3c12c..5fcafc07 100644 --- a/src/views/infra/config/index.vue +++ b/src/views/infra/config/index.vue @@ -153,7 +153,7 @@ const queryParams = reactive({ const queryFormRef = ref() // 搜索的表单 const exportLoading = ref(false) // 导出的加载中 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/infra/dataSourceConfig/index.vue b/src/views/infra/dataSourceConfig/index.vue index ff44cdd5..ab3523e7 100644 --- a/src/views/infra/dataSourceConfig/index.vue +++ b/src/views/infra/dataSourceConfig/index.vue @@ -66,7 +66,7 @@ const { t } = useI18n() // 国际化 const loading = ref(true) // 列表的加载中 const list = ref([]) // 列表的数据 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/infra/file/index.vue b/src/views/infra/file/index.vue index adf9d3a4..32b35562 100644 --- a/src/views/infra/file/index.vue +++ b/src/views/infra/file/index.vue @@ -104,7 +104,7 @@ const queryParams = reactive({ }) const queryFormRef = ref() // 搜索的表单 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/infra/fileConfig/index.vue b/src/views/infra/fileConfig/index.vue index ed0cfed7..ca0fac15 100644 --- a/src/views/infra/fileConfig/index.vue +++ b/src/views/infra/fileConfig/index.vue @@ -130,7 +130,7 @@ const queryParams = reactive({ }) const queryFormRef = ref() // 搜索的表单 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/mp/message/index.vue b/src/views/mp/message/index.vue index 10145221..d9ddce0d 100644 --- a/src/views/mp/message/index.vue +++ b/src/views/mp/message/index.vue @@ -210,7 +210,7 @@ const open = ref(false) // 是否显示弹出层 const userId = ref(0) // 操作的用户编号 const accountList = ref<MpAccountApi.AccountVO[]>([]) // 公众号账号列表 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { // 如果没有选中公众号账号,则进行提示。 if (!queryParams.accountId) { diff --git a/src/views/mp/tag/index.vue b/src/views/mp/tag/index.vue index 84e6fc17..ec083114 100644 --- a/src/views/mp/tag/index.vue +++ b/src/views/mp/tag/index.vue @@ -106,7 +106,7 @@ const queryParams = reactive({ const queryFormRef = ref() // 搜索的表单 const accountList = ref<MpAccountApi.AccountVO[]>([]) // 公众号账号列表 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { // 如果没有选中公众号账号,则进行提示。 if (!queryParams.accountId) { diff --git a/src/views/system/dict/data.vue b/src/views/system/dict/data.vue index 7b159481..8492262a 100644 --- a/src/views/system/dict/data.vue +++ b/src/views/system/dict/data.vue @@ -130,7 +130,7 @@ const queryFormRef = ref() // 搜索的表单 const exportLoading = ref(false) // 导出的加载中 const dicts = ref<DictTypeApi.DictTypeVO[]>() // 字典类型的列表 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/system/loginlog/index.vue b/src/views/system/loginlog/index.vue index 7b1aca5f..c48957a7 100644 --- a/src/views/system/loginlog/index.vue +++ b/src/views/system/loginlog/index.vue @@ -123,7 +123,7 @@ const queryParams = reactive({ const queryFormRef = ref() // 搜索的表单 const exportLoading = ref(false) // 导出的加载中 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue index 3baf3148..8feb066b 100644 --- a/src/views/system/menu/index.vue +++ b/src/views/system/menu/index.vue @@ -126,7 +126,7 @@ const queryFormRef = ref() // 搜索的表单 const isExpandAll = ref(false) // 是否展开,默认全部折叠 const refreshTable = ref(true) // 重新渲染表格状态 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/system/notify/message/detail.vue b/src/views/system/notify/message/NotifyMessageDetail.vue similarity index 87% rename from src/views/system/notify/message/detail.vue rename to src/views/system/notify/message/NotifyMessageDetail.vue index 0b146113..283575bb 100644 --- a/src/views/system/notify/message/detail.vue +++ b/src/views/system/notify/message/NotifyMessageDetail.vue @@ -1,5 +1,5 @@ <template> - <Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="500" width="800"> + <Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="500"> <el-descriptions border :column="1"> <el-descriptions-item label="编号" min-width="120"> {{ detailData.id }} @@ -32,10 +32,10 @@ <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="detailData.readStatus" /> </el-descriptions-item> <el-descriptions-item label="阅读时间"> - {{ formatDate(detailData.readTime, 'YYYY-MM-DD HH:mm:ss') }} + {{ formatDate(detailData.readTime) }} </el-descriptions-item> <el-descriptions-item label="创建时间"> - {{ formatDate(detailData.createTime, 'YYYY-MM-DD HH:mm:ss') }} + {{ formatDate(detailData.createTime) }} </el-descriptions-item> </el-descriptions> </Dialog> @@ -50,7 +50,7 @@ const detailLoading = ref(false) // 表单的加载中 const detailData = ref() // 详情数据 /** 打开弹窗 */ -const openModal = async (data: NotifyMessageApi.NotifyMessageVO) => { +const open = async (data: NotifyMessageApi.NotifyMessageVO) => { modelVisible.value = true // 设置数据 detailLoading.value = true @@ -60,5 +60,5 @@ const openModal = async (data: NotifyMessageApi.NotifyMessageVO) => { detailLoading.value = false } } -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 </script> diff --git a/src/views/system/notify/message/index.vue b/src/views/system/notify/message/index.vue index 06f98c62..9a39e7f6 100644 --- a/src/views/system/notify/message/index.vue +++ b/src/views/system/notify/message/index.vue @@ -25,10 +25,10 @@ class="!w-240px" > <el-option - v-for="dict in getDictOptions(DICT_TYPE.USER_TYPE)" - :key="parseInt(dict.value)" + v-for="dict in getIntDictOptions(DICT_TYPE.USER_TYPE)" + :key="dict.value" :label="dict.label" - :value="parseInt(dict.value)" + :value="dict.value" /> </el-select> </el-form-item> @@ -49,10 +49,10 @@ class="!w-240px" > <el-option - v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE)" - :key="parseInt(dict.value)" + v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE)" + :key="dict.value" :label="dict.label" - :value="parseInt(dict.value)" + :value="dict.value" /> </el-select> </el-form-item> @@ -76,7 +76,7 @@ <!-- 列表 --> <content-wrap> - <el-table ref="tableRef" v-loading="loading" :data="list" :height="tableHeight"> + <el-table v-loading="loading" :data="list"> <el-table-column label="编号" align="center" prop="id" /> <el-table-column label="用户类型" align="center" prop="userType"> <template #default="scope"> @@ -84,7 +84,6 @@ </template> </el-table-column> <el-table-column label="用户编号" align="center" prop="userId" width="80" /> - <el-table-column label="模版编号" align="center" prop="templateId" width="80" /> <el-table-column label="模板编码" align="center" prop="templateCode" width="80" /> <el-table-column label="发送人名称" align="center" prop="templateNickname" width="180" /> <el-table-column @@ -127,12 +126,12 @@ width="180" :formatter="dateFormatter" /> - <el-table-column label="操作" align="center"> + <el-table-column label="操作" align="center" fixed="right"> <template #default="scope"> <el-button link type="primary" - @click="openModal(scope.row)" + @click="openDetail(scope.row)" v-hasPermi="['system:notify-message:query']" > 详情 @@ -150,13 +149,13 @@ </content-wrap> <!-- 表单弹窗:详情 --> - <notify-message-detail ref="modalRef" /> + <NotifyMessageDetail ref="detailRef" /> </template> <script setup lang="ts" name="NotifyMessage"> -import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' import * as NotifyMessageApi from '@/api/system/notify/message' -import NotifyMessageDetail from './detail.vue' +import NotifyMessageDetail from './NotifyMessageDetail.vue' const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 @@ -171,14 +170,12 @@ const queryParams = reactive({ createTime: [] }) const queryFormRef = ref() // 搜索的表单 -const tableRef = ref() -const tableHeight = ref() // table高度 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { - const data = await NotifyMessageApi.getNotifyMessagePageApi(queryParams) + const data = await NotifyMessageApi.getNotifyMessagePage(queryParams) list.value = data.list total.value = data.total } finally { @@ -199,19 +196,13 @@ const resetQuery = () => { } /** 详情操作 */ -const modalRef = ref() -const openModal = (data: NotifyMessageApi.NotifyMessageVO) => { - modalRef.value.openModal(data) +const detailRef = ref() +const openDetail = (data: NotifyMessageApi.NotifyMessageVO) => { + detailRef.value.open(data) } /** 初始化 **/ onMounted(() => { getList() - // TODO 感觉表格自适应高度体验很好的,目前简单实现 需要进一步优化,如根据筛选条件展开或收缩改变盒子高度时 - tableHeight.value = window.innerHeight - tableRef.value.$el.offsetTop - 85 - 155 - // 监听浏览器高度变化 - window.onresize = () => { - tableHeight.value = window.innerHeight - tableRef.value.$el.offsetTop - 85 - 155 - } }) </script> diff --git a/src/views/system/notify/my/detail.vue b/src/views/system/notify/my/MyNotifyMessageDetail.vue similarity index 89% rename from src/views/system/notify/my/detail.vue rename to src/views/system/notify/my/MyNotifyMessageDetail.vue index ea9ae961..45af259d 100644 --- a/src/views/system/notify/my/detail.vue +++ b/src/views/system/notify/my/MyNotifyMessageDetail.vue @@ -1,5 +1,5 @@ <template> - <Dialog title="消息详情" v-model="modelVisible" :scroll="true" :max-height="500" width="800"> + <Dialog title="消息详情" v-model="modelVisible" :scroll="true" :max-height="500"> <el-descriptions border :column="1"> <el-descriptions-item label="发送人"> {{ detailData.templateNickname }} @@ -32,7 +32,7 @@ const detailLoading = ref(false) // 表单的加载中 const detailData = ref() // 详情数据 /** 打开弹窗 */ -const openModal = async (data: NotifyMessageApi.NotifyMessageVO) => { +const open = async (data: NotifyMessageApi.NotifyMessageVO) => { modelVisible.value = true // 设置数据 detailLoading.value = true @@ -42,5 +42,5 @@ const openModal = async (data: NotifyMessageApi.NotifyMessageVO) => { detailLoading.value = false } } -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 </script> diff --git a/src/views/system/notify/my/index.vue b/src/views/system/notify/my/index.vue index d889e184..d423a98c 100644 --- a/src/views/system/notify/my/index.vue +++ b/src/views/system/notify/my/index.vue @@ -16,7 +16,7 @@ class="!w-240px" > <el-option - v-for="dict in getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" + v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" :key="dict.value" :label="dict.label" :value="dict.value" @@ -37,40 +37,26 @@ <el-form-item> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> - <el-button @click="handleUpdateList" - ><Icon icon="ep:reading" class="mr-5px" /> 标记已读</el-button - > - <el-button @click="handleUpdateAll" - ><Icon icon="ep:reading" class="mr-5px" /> 全部已读</el-button - > + <el-button @click="handleUpdateList"> + <Icon icon="ep:reading" class="mr-5px" /> 标记已读 + </el-button> + <el-button @click="handleUpdateAll"> + <Icon icon="ep:reading" class="mr-5px" /> 全部已读 + </el-button> </el-form-item> </el-form> - <!-- <el-row :gutter="10" class="mb8"> - <el-col :span="1.5"> - <el-button @click="handleUpdateList" - ><Icon icon="ep:refresh" class="mr-5px" /> 标记已读</el-button - > - </el-col> - <el-col :span="1.5"> - <el-button @click="handleUpdateAll" - ><Icon icon="ep:refresh" class="mr-5px" /> 全部已读</el-button - > - </el-col> - </el-row> --> </content-wrap> <content-wrap> <!-- 列表 --> <el-table - ref="tableRef" v-loading="loading" :data="list" - :height="tableHeight" - :row-key="getRowKeys" + ref="tableRef" + row-key="id" @selection-change="handleSelectionChange" > <el-table-column type="selection" :selectable="selectable" :reserve-selection="true" /> - <!-- <el-table-column label="编号" align="center" prop="id" /> --> <el-table-column label="发送人" align="center" prop="templateNickname" width="180" /> <el-table-column label="发送时间" @@ -107,9 +93,9 @@ <el-button link :type="scope.row.readStatus ? 'primary' : 'warning'" - @click="openModal(scope.row)" + @click="openDetail(scope.row)" > - {{ scope.row.readStatus ? '详情' : '查阅' }} + {{ scope.row.readStatus ? '详情' : '已读' }} </el-button> </template> </el-table-column> @@ -124,15 +110,14 @@ </content-wrap> <!-- 表单弹窗:详情 --> - <my-notify-message-detail ref="modalRef" /> + <MyNotifyMessageDetail ref="detailRef" /> </template> <script setup lang="ts" name="MyNotifyMessage"> -import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { DICT_TYPE, getBoolDictOptions } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' import * as NotifyMessageApi from '@/api/system/notify/message' -import MyNotifyMessageDetail from './detail.vue' - +import MyNotifyMessageDetail from './MyNotifyMessageDetail.vue' const message = useMessage() // 消息 const loading = ref(true) // 列表的加载中 @@ -145,13 +130,10 @@ const queryParams = reactive({ createTime: [] }) const queryFormRef = ref() // 搜索的表单 -const tableRef = ref() -const tableHeight = ref() // table高度 -const selectedNum = ref(0) //表格select选中的条数 -const selectedIds = ref([] as any[]) //表格select复选框选中的id -const multipleSelection = ref([]) +const tableRef = ref() // 表格的 Ref +const selectedIds = ref<number[]>([]) // 表格的选中 ID 数组 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { @@ -163,40 +145,6 @@ const getList = async () => { } } -/** 详情操作 */ -const modalRef = ref() -const openModal = (data: NotifyMessageApi.NotifyMessageVO) => { - if (!data.readStatus) { - handleUpdate(data.id) - } - modalRef.value.openModal(data) -} - -// 标记指定 id 已读 -const handleUpdate = async (ids) => { - await NotifyMessageApi.updateNotifyMessageRead(ids) - //message.success('标记已读成功!') - handleQuery() -} - -// 标记全部已读 -const handleUpdateAll = async () => { - await NotifyMessageApi.updateAllNotifyMessageRead() - message.success('全部已读成功!') - handleQuery() -} - -// 标记勾选 ids 已读 -const handleUpdateList = async () => { - if (selectedIds.value.length === 0) { - return - } - await NotifyMessageApi.updateNotifyMessageRead(selectedIds.value) - message.success('批量已读成功!') - tableRef.value.clearSelection() - handleQuery() -} - /** 搜索按钮操作 */ const handleQuery = () => { queryParams.pageNo = 1 @@ -210,41 +158,56 @@ const resetQuery = () => { handleQuery() } -/** 初始化 **/ -onMounted(() => { - getList() - // TODO 感觉表格自适应高度体验好些,目前简单实现 需要进一步优化 - tableHeight.value = window.innerHeight - tableRef.value.$el.offsetTop - 85 - 155 - // 监听浏览器高度变化 - window.onresize = () => { - tableHeight.value = window.innerHeight - tableRef.value.$el.offsetTop - 85 - 155 +/** 详情操作 */ +const detailRef = ref() +const openDetail = (data: NotifyMessageApi.NotifyMessageVO) => { + if (!data.readStatus) { + handleReadOne(data.id) } -}) + detailRef.value.open(data) +} -// 禁用某行复选框 +/** 标记一条站内信已读 */ +const handleReadOne = async (id) => { + await NotifyMessageApi.updateNotifyMessageRead(id) + await getList() +} + +/** 标记全部站内信已读 **/ +const handleUpdateAll = async () => { + await NotifyMessageApi.updateAllNotifyMessageRead() + message.success('全部已读成功!') + tableRef.value.clearSelection() + await getList() +} + +/** 标记一些站内信已读 **/ +const handleUpdateList = async () => { + if (selectedIds.value.length === 0) { + return + } + await NotifyMessageApi.updateNotifyMessageRead(selectedIds.value) + message.success('批量已读成功!') + tableRef.value.clearSelection() + await getList() +} + +/** 某一行,是否允许选中 */ const selectable = (row) => { return !row.readStatus } -//选中的list -const getRowKeys = (row) => { - return row.id -} -//当表格选择项发生变化时会触发该事件 -const handleSelectionChange = (val) => { - // 解决来回切换页面,也无法清除上次选中情况 - multipleSelection.value = val - selectedNum.value = multipleSelection.value.length +/** 当表格选择项发生变化时会触发该事件 */ +const handleSelectionChange = (array: NotifyMessageApi.NotifyMessageVO[]) => { selectedIds.value = [] - if (val) { - undefined - val.forEach((row) => { - undefined - if (row) { - undefined - selectedIds.value.push(row.id) - } - }) + if (!array) { + return } + array.forEach((row) => selectedIds.value.push(row.id)) } + +/** 初始化 **/ +onMounted(() => { + getList() +}) </script> diff --git a/src/views/system/oauth2/token/index.vue b/src/views/system/oauth2/token/index.vue index 7e8aca22..a809e452 100644 --- a/src/views/system/oauth2/token/index.vue +++ b/src/views/system/oauth2/token/index.vue @@ -115,7 +115,7 @@ const queryParams = reactive({ }) const queryFormRef = ref() // 搜索的表单 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/system/operatelog/index.vue b/src/views/system/operatelog/index.vue index d7490ba8..c5713be1 100644 --- a/src/views/system/operatelog/index.vue +++ b/src/views/system/operatelog/index.vue @@ -156,7 +156,7 @@ const queryParams = reactive({ const queryFormRef = ref() // 搜索的表单 const exportLoading = ref(false) // 导出的加载中 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/system/sensitiveWord/index.vue b/src/views/system/sensitiveWord/index.vue index cf1fdb82..5b73132b 100644 --- a/src/views/system/sensitiveWord/index.vue +++ b/src/views/system/sensitiveWord/index.vue @@ -168,7 +168,7 @@ const queryFormRef = ref() // 搜索的表单 const exportLoading = ref(false) // 导出的加载中 const tagList = ref([]) // 标签数组 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/system/sms/channel/index.vue b/src/views/system/sms/channel/index.vue index 65d18029..62cd7cc2 100644 --- a/src/views/system/sms/channel/index.vue +++ b/src/views/system/sms/channel/index.vue @@ -146,7 +146,7 @@ const queryParams = reactive({ createTime: [] }) -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/system/tenant/index.vue b/src/views/system/tenant/index.vue index e316992d..1991fbea 100644 --- a/src/views/system/tenant/index.vue +++ b/src/views/system/tenant/index.vue @@ -191,7 +191,7 @@ const queryFormRef = ref() // 搜索的表单 const exportLoading = ref(false) // 导出的加载中 const packageList = ref([]) //租户套餐列表 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { From 337b80ca3d316dbdfab8bcec00815d875dbdb570 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Tue, 28 Mar 2023 23:28:29 +0800 Subject: [PATCH 144/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E5=85=AC=E4=BC=97=E5=8F=B7=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/mp/statistics/index.vue | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/views/mp/statistics/index.vue b/src/views/mp/statistics/index.vue index c625f9dc..a692365c 100644 --- a/src/views/mp/statistics/index.vue +++ b/src/views/mp/statistics/index.vue @@ -1,9 +1,9 @@ <template> <!-- 搜索工作栏 --> <content-wrap> - <el-form ref="queryForm" class="-mb-15px" :inline="true" label-width="68px"> + <el-form class="-mb-15px" ref="queryForm" :inline="true" label-width="68px"> <el-form-item label="公众号" prop="accountId"> - <el-select v-model="accountId" @change="getSummary"> + <el-select v-model="accountId" @change="getSummary" class="!w-240px"> <el-option v-for="item in accountList" :key="item.id" @@ -15,13 +15,12 @@ <el-form-item label="时间范围" prop="dateRange"> <el-date-picker v-model="dateRange" - style="width: 260px" type="daterange" - range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" - :default-time="defaultTime" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" @change="getSummary" + class="!w-240px" /> </el-form-item> </el-form> @@ -75,22 +74,17 @@ </template> <script setup lang="ts" name="MpStatistics"> -import * as StatisticsApi from '@/api/mp/statistics' import { formatDate, addTime, betweenDay, beginOfDay, endOfDay } from '@/utils/formatTime' +import * as StatisticsApi from '@/api/mp/statistics' import * as MpAccountApi from '@/api/mp/account' const message = useMessage() // 消息弹窗 -// 默认时间 开始时间00:00:00 结束时间23:59:59 -const defaultTime = ref<[Date, Date]>([ - new Date(2000, 1, 1, 0, 0, 0), - new Date(2000, 2, 1, 23, 59, 59) -]) // 默认开始时间是当前日期-7,结束时间是当前日期-1 const dateRange = ref([ beginOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24 * 7)), endOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24)) ]) -const accountId = ref() +const accountId = ref() // 选中的公众号编号 const accountList = ref<MpAccountApi.AccountVO[]>([]) // 公众号账号列表 const xAxisDate = ref([] as any[]) // X 轴的日期范围 @@ -269,6 +263,7 @@ const getSummary = () => { initUpstreamMessageChart() interfaceSummaryChart() } + /** 用户增减数据 */ const initUserSummaryChart = async () => { userSummaryOption.xAxis.data = [] @@ -297,6 +292,7 @@ const initUserSummaryChart = async () => { }) } catch {} } + /** 累计用户数据 */ const initUserCumulateChart = async () => { userCumulateOption.xAxis.data = [] @@ -314,6 +310,7 @@ const initUserCumulateChart = async () => { }) } catch {} } + /** 消息概况数据 */ const initUpstreamMessageChart = async () => { upstreamMessageOption.xAxis.data = [] @@ -333,6 +330,7 @@ const initUpstreamMessageChart = async () => { }) } catch {} } + /** 接口分析数据 */ const interfaceSummaryChart = async () => { interfaceSummaryOption.xAxis.data = [] From 7ab8aa71685c9e52fd5fc8584f8f6cbece4f81bb Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Tue, 28 Mar 2023 23:50:32 +0800 Subject: [PATCH 145/184] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIE?= =?UTF-8?q?W=20=E7=94=A8=E6=88=B7=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/user/RoleForm.vue | 9 ++++----- src/views/system/user/index.vue | 18 ++++++++++++------ 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/views/system/user/RoleForm.vue b/src/views/system/user/RoleForm.vue index cb5603fe..0cb82df8 100644 --- a/src/views/system/user/RoleForm.vue +++ b/src/views/system/user/RoleForm.vue @@ -8,12 +8,12 @@ <el-input v-model="formData.nickname" :disabled="true" /> </el-form-item> <el-form-item label="角色"> - <el-select v-model="formData.roleIds" multiple placeholder="请选择"> + <el-select v-model="formData.roleIds" multiple placeholder="请选择角色"> <el-option v-for="item in roleOptions" - :key="parseInt(item.id)" + :key="item.id" :label="item.name" - :value="parseInt(item.id)" + :value="item.id" /> </el-select> </el-form-item> @@ -28,6 +28,7 @@ </template> <script setup lang="ts"> +// TODO el-dialog 用 Dialog 组件 import { assignUserRoleApi, PermissionAssignUserRoleReqVO } from '@/api/system/permission' interface Props { @@ -86,5 +87,3 @@ const submit = async () => { } } </script> - -<style></style> diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index ce034c4e..641f8bd4 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -269,6 +269,7 @@ import type { ElTree } from 'element-plus' import { handleTree, defaultProps } from '@/utils/tree' // 原vue3版本api方法都是Api结尾觉得见名知义,个人觉得这个可以形成规范 +// TODO 使用 DeptApi 这种形式哈 import { getSimpleDeptList as getSimpleDeptListApi } from '@/api/system/dept' import { getSimplePostList as getSimplePostListApi, PostVO } from '@/api/system/post' import { DICT_TYPE, getDictOptions } from '@/utils/dict' @@ -279,16 +280,15 @@ import { updateUserStatusApi, UserVO } from '@/api/system/user' -import { parseTime } from './utils' -import AddForm from './AddForm.vue' -import ImportForm from './ImportForm.vue' -import RoleForm from './RoleForm.vue' +import { parseTime } from './utils' // TODO 可以使用 formatTime 里的方法 +import AddForm from './AddForm.vue' // TODO 改成 UserForm +import ImportForm from './ImportForm.vue' // TODO 改成 UserImportForm +import RoleForm from './RoleForm.vue' // TODO 改成 UserAssignRoleForm import { getUserApi, getUserPageApi } from '@/api/system/user' import { getSimpleRoleList as getSimpleRoleListApi } from '@/api/system/role' import { listUserRolesApi } from '@/api/system/permission' import { CommonStatusEnum } from '@/utils/constants' import download from '@/utils/download' - const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -304,10 +304,11 @@ const queryParams = reactive({ const showSearch = ref(true) const showAddDialog = ref(false) -// 数据字典- +// 数据字典- // TODO 可以直接 vue 那 getIntDictOptions,这样一方面少个变量,也可以 getIntDictOptions const statusDictDatas = getDictOptions(DICT_TYPE.COMMON_STATUS) // ========== 创建部门树结构 ========== +// TODO 要不把部门树拆成一个左侧的组件,然后点击后触发 handleDeptNodeClick const deptName = ref('') watch( () => deptName.value, @@ -375,6 +376,7 @@ const resetQuery = () => { // 添加或编辑 const addEditFormRef = ref() // 添加用户 +// TODO 可以参考别的模块哈,openForm;然后 tree 和 position 可以里面在加载下,让组件自己维护自己哈。 const handleAdd = () => { addEditFormRef?.value.resetForm() // 获得下拉数据 @@ -389,6 +391,7 @@ const handleImport = () => { } // 用户导出 +// TODO 改成 await 的风格; const exportLoading = ref(false) const handleExport = () => { message @@ -432,6 +435,7 @@ const handleCommand = (command: string, index: number, row: UserVO) => { } // 用户状态修改 +// TODO 改成 await 的风格; const handleStatusChange = (row: UserVO) => { let text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' message @@ -466,6 +470,7 @@ const handleUpdate = (row: UserVO) => { } // 删除用户 +// TODO 改成 await 的风格; const handleDelete = (row: UserVO) => { const ids = row.id message @@ -481,6 +486,7 @@ const handleDelete = (row: UserVO) => { } // 重置密码 +// TODO 改成 await 的风格; const handleResetPwd = (row: UserVO) => { message.prompt('请输入"' + row.username + '"的新密码', t('common.reminder')).then(({ value }) => { resetUserPwdApi(row.id, value) From 17ef65df06ee091554a502c33f4e2069216b67d7 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Wed, 29 Mar 2023 00:01:39 +0800 Subject: [PATCH 146/184] =?UTF-8?q?=E5=90=8C=E6=AD=A5=20master=20=E6=9C=80?= =?UTF-8?q?=E6=96=B0=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/axios/request.ts | 50 ------------------------------------- 1 file changed, 50 deletions(-) delete mode 100644 src/config/axios/request.ts diff --git a/src/config/axios/request.ts b/src/config/axios/request.ts deleted file mode 100644 index d65842c2..00000000 --- a/src/config/axios/request.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { service } from './service' - -import { config } from './config' - -const { default_headers } = config - -const request = (option: any) => { - const { url, method, params, data, headersType, responseType } = option - return service({ - url: url, - method, - params, - data, - responseType: responseType, - headers: { - 'Content-Type': headersType || default_headers - } - }) -} -export default { - get: async <T = any>(option: any) => { - const res = await request({ method: 'GET', ...option }) - return res as unknown as T - }, - post: async <T = any>(option: any) => { - const res = await request({ method: 'POST', ...option }) - return res as unknown as T - }, - delete: async <T = any>(option: any) => { - const res = await request({ method: 'DELETE', ...option }) - return res as unknown as T - }, - put: async <T = any>(option: any) => { - const res = await request({ method: 'PUT', ...option }) - return res as unknown as T - }, - patch: async <T = any>(option: any) => { - const res = await request({ method: 'PATCH', ...option }) - return res as unknown as T - }, - download: async <T = any>(option: any) => { - const res = await request({ method: 'GET', responseType: 'blob', ...option }) - return res as unknown as Promise<T> - }, - upload: async <T = any>(option: any) => { - option.headersType = 'multipart/form-data' - const res = await request({ method: 'POST', ...option }) - return res as unknown as Promise<T> - } -} From 76c5c7c1de79517f4585af16eb48a0f854366f77 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Wed, 29 Mar 2023 09:09:34 +0800 Subject: [PATCH 147/184] =?UTF-8?q?REVIEW=20=E4=BB=A3=E7=A0=81=E7=94=9F?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/infra/codegen/index.ts | 70 ++++++++++++++- src/api/infra/codegen/types.ts | 61 ------------- src/locales/zh-CN.ts | 3 +- src/views/infra/codegen/EditTable.vue | 2 +- src/views/infra/codegen/codegen.data.ts | 53 ----------- src/views/infra/codegen/index.vue | 112 ++++++++++++------------ 6 files changed, 128 insertions(+), 173 deletions(-) delete mode 100644 src/api/infra/codegen/types.ts delete mode 100644 src/views/infra/codegen/codegen.data.ts diff --git a/src/api/infra/codegen/index.ts b/src/api/infra/codegen/index.ts index 993bd552..64701efe 100644 --- a/src/api/infra/codegen/index.ts +++ b/src/api/infra/codegen/index.ts @@ -1,8 +1,74 @@ import request from '@/config/axios' -import type { CodegenUpdateReqVO, CodegenCreateListReqVO } from './types' + +export type CodegenTableVO = { + id: number + tableId: number + isParentMenuIdValid: boolean + dataSourceConfigId: number + scene: number + tableName: string + tableComment: string + remark: string + moduleName: string + businessName: string + className: string + classComment: string + author: string + createTime: Date + updateTime: Date + templateType: number + parentMenuId: number +} + +export type CodegenColumnVO = { + id: number + tableId: number + columnName: string + dataType: string + columnComment: string + nullable: number + primaryKey: number + autoIncrement: string + ordinalPosition: number + javaType: string + javaField: string + dictType: string + example: string + createOperation: number + updateOperation: number + listOperation: number + listOperationCondition: string + listOperationResult: number + htmlType: string +} + +export type DatabaseTableVO = { + name: string + comment: string +} + +export type CodegenDetailVO = { + table: CodegenTableVO + columns: CodegenColumnVO[] +} + +export type CodegenPreviewVO = { + filePath: string + code: string +} + +export type CodegenUpdateReqVO = { + table: CodegenTableVO | any + columns: CodegenColumnVO[] +} + +export type CodegenCreateListReqVO = { + dataSourceConfigId: number + tableNames: string[] +} // 查询列表代码生成表定义 -export const getCodegenTablePage = (params) => { +export const getCodegenTablePage = (params: PageParam) => { return request.get({ url: '/infra/codegen/table/page', params }) } diff --git a/src/api/infra/codegen/types.ts b/src/api/infra/codegen/types.ts deleted file mode 100644 index e4e78843..00000000 --- a/src/api/infra/codegen/types.ts +++ /dev/null @@ -1,61 +0,0 @@ -export type CodegenTableVO = { - id: number - tableId: number - isParentMenuIdValid: boolean - dataSourceConfigId: number - scene: number - tableName: string - tableComment: string - remark: string - moduleName: string - businessName: string - className: string - classComment: string - author: string - createTime: Date - updateTime: Date - templateType: number - parentMenuId: number -} - -export type CodegenColumnVO = { - id: number - tableId: number - columnName: string - dataType: string - columnComment: string - nullable: number - primaryKey: number - autoIncrement: string - ordinalPosition: number - javaType: string - javaField: string - dictType: string - example: string - createOperation: number - updateOperation: number - listOperation: number - listOperationCondition: string - listOperationResult: number - htmlType: string -} -export type DatabaseTableVO = { - name: string - comment: string -} -export type CodegenDetailVO = { - table: CodegenTableVO - columns: CodegenColumnVO[] -} -export type CodegenPreviewVO = { - filePath: string - code: string -} -export type CodegenUpdateReqVO = { - table: CodegenTableVO | any - columns: CodegenColumnVO[] -} -export type CodegenCreateListReqVO = { - dataSourceConfigId: number - tableNames: string[] -} diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index ea1392c4..6f46f1ab 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -298,8 +298,7 @@ export default { typeUpdate: '字典类型编辑', dataCreate: '字典数据新增', dataUpdate: '字典数据编辑', - fileUpload: '上传文件', - back: '返回' + fileUpload: '上传文件' }, dialog: { dialog: '弹窗', diff --git a/src/views/infra/codegen/EditTable.vue b/src/views/infra/codegen/EditTable.vue index 34ba3bd6..79922ffc 100644 --- a/src/views/infra/codegen/EditTable.vue +++ b/src/views/infra/codegen/EditTable.vue @@ -16,7 +16,7 @@ <el-button type="primary" @click="submitForm" :loading="submitLoading"> {{ t('action.save') }} </el-button> - <el-button @click="close">{{ t('action.back') }}</el-button> + <el-button @click="close">返回</el-button> </el-form-item> </el-form> </content-wrap> diff --git a/src/views/infra/codegen/codegen.data.ts b/src/views/infra/codegen/codegen.data.ts deleted file mode 100644 index f3723a35..00000000 --- a/src/views/infra/codegen/codegen.data.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -const { t } = useI18n() // 国际化 - -// 表单校验 -export const rules = reactive({ - title: [required], - type: [required], - status: [required] -}) - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'seq', - action: true, - actionWidth: '400px', - columns: [ - { - title: '表名称', - field: 'tableName', - isSearch: true - }, - { - title: '表描述', - field: 'tableComment', - isSearch: true - }, - { - title: '实体', - field: 'className', - isSearch: true - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - }, - { - title: t('common.updateTime'), - field: 'updateTime', - formatter: 'formatDate', - isForm: false - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) diff --git a/src/views/infra/codegen/index.vue b/src/views/infra/codegen/index.vue index 591f99d3..b975779a 100644 --- a/src/views/infra/codegen/index.vue +++ b/src/views/infra/codegen/index.vue @@ -1,13 +1,20 @@ <template> <!-- 搜索 --> <content-wrap> - <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true"> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > <el-form-item label="表名称" prop="tableName"> <el-input v-model="queryParams.tableName" placeholder="请输入表名称" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="表描述" prop="tableComment"> @@ -16,23 +23,25 @@ placeholder="请输入表描述" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="创建时间" prop="createTime"> <el-date-picker v-model="queryParams.createTime" - value-format="yyyy-MM-dd HH:mm:ss" + value-format="YYYY-MM-dd HH:mm:ss" type="daterange" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="!w-240px" /> </el-form-item> <el-form-item> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" />搜索</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button> <el-button type="primary" v-hasPermi="['infra:codegen:create']" @click="openImportTable()"> - <Icon icon="ep:zoom-in" class="mr-5px" />{{ t('action.import') }} + <Icon icon="ep:zoom-in" class="mr-5px" /> 导入 </el-button> </el-form-item> </el-form> @@ -41,14 +50,20 @@ <!-- 列表 --> <content-wrap> <el-table v-loading="loading" :data="list"> - <el-table-column label="数据源" align="center" :formatter="dataSourceConfigNameFormat" /> + <el-table-column label="数据源" align="center"> + <template #default="scope"> + {{ + dataSourceConfigList.find((config) => config.id === scope.row.dataSourceConfigId)?.name + }} + </template> + </el-table-column> <el-table-column label="表名称" align="center" prop="tableName" width="200" /> <el-table-column label="表描述" align="center" prop="tableComment" :show-overflow-tooltip="true" - width="120" + width="200" /> <el-table-column label="实体" align="center" prop="className" width="200" /> <el-table-column @@ -65,53 +80,53 @@ width="180" :formatter="dateFormatter" /> - <el-table-column - label="操作" - align="center" - width="300px" - class-name="small-padding fixed-width" - > + <el-table-column label="操作" align="center" width="300px" fixed="right"> <template #default="scope"> <el-button link type="primary" @click="handlePreview(scope.row)" v-hasPermi="['infra:codegen:preview']" - >预览</el-button > + 预览 + </el-button> <el-button link type="primary" @click="handleUpdate(scope.row.id)" v-hasPermi="['infra:codegen:update']" - >编辑</el-button > + 编辑 + </el-button> <el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['infra:codegen:delete']" - >删除</el-button > + 删除 + </el-button> <el-button link type="primary" - @click="handleSynchDb(scope.row)" + @click="handleSyncDB(scope.row)" v-hasPermi="['infra:codegen:update']" - >同步</el-button > + 同步 + </el-button> <el-button link type="primary" @click="handleGenTable(scope.row)" v-hasPermi="['infra:codegen:download']" - >生成代码</el-button > + 生成代码 + </el-button> </template> </el-table-column> </el-table> - <pagination - v-show="total > 0" + <!-- 分页 --> + <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @@ -120,20 +135,16 @@ </content-wrap> <!-- 弹窗:导入表 --> - <ImportTable ref="importRef" @ok="reload()" /> + <ImportTable ref="importRef" @ok="getList" /> <!-- 弹窗:预览代码 --> <Preview ref="previewRef" /> </template> <script setup lang="ts" name="Codegen"> +import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' import * as CodegenApi from '@/api/infra/codegen' import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig' -import { CodegenTableVO } from '@/api/infra/codegen/types' import { ImportTable, Preview } from './components' -import ContentWrap from '@/components/ContentWrap/src/ContentWrap.vue' -import { DataSourceConfigVO } from '@/api/infra/dataSourceConfig' -import { dateFormatter } from '@/utils/formatTime' - const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 const { push } = useRouter() // 路由跳转 @@ -149,8 +160,7 @@ const queryParams = reactive({ createTime: [] }) const queryFormRef = ref() // 搜索的表单 - -const dataSourceConfigs = ref<DataSourceConfigVO[]>([]) // 数据源列表 +const dataSourceConfigList = ref<DataSourceConfigApi.DataSourceConfigVO[]>([]) // 数据源列表 /** 查询参数列表 */ const getList = async () => { @@ -176,35 +186,21 @@ const resetQuery = () => { handleQuery() } -/** 初始化 **/ -onMounted(async () => { - getList() - dataSourceConfigs.value = await DataSourceConfigApi.getDataSourceConfigList() -}) - -// 数据源配置的名字 -const dataSourceConfigNameFormat = (row) => { - for (const config of dataSourceConfigs.value) { - if (row.dataSourceConfigId === config.id) { - return config.name - } - } - return '未知【' + row.leaderUserId + '】' -} - // 导入操作 const importRef = ref() const openImportTable = () => { importRef.value.show() } -// 预览操作 -const previewRef = ref() -const handlePreview = (row: CodegenTableVO) => { - previewRef.value.openModal(row.id) + +/** 编辑操作 */ +const handleUpdate = (id: number) => { + push('/codegen/edit?id=' + id) } -// 编辑操作 -const handleUpdate = (rowId: number) => { - push('/codegen/edit?id=' + rowId) + +/** 预览操作 */ +const previewRef = ref() +const handlePreview = (row: CodegenApi.CodegenTableVO) => { + previewRef.value.openModal(row.id) } /** 删除按钮操作 */ @@ -219,8 +215,9 @@ const handleDelete = async (id: number) => { await getList() } catch {} } -// 同步操作 -const handleSynchDb = async (row: CodegenTableVO) => { + +/** 同步操作 */ +const handleSyncDB = async (row: CodegenApi.CodegenTableVO) => { // 基于 DB 同步 const tableName = row.tableName try { @@ -230,9 +227,16 @@ const handleSynchDb = async (row: CodegenTableVO) => { } catch {} } -// 生成代码操作 -const handleGenTable = async (row: CodegenTableVO) => { +/** 生成代码操作 */ +const handleGenTable = async (row: CodegenApi.CodegenTableVO) => { const res = await CodegenApi.downloadCodegen(row.id) download.zip(res, 'codegen-' + row.className + '.zip') } + +/** 初始化 **/ +onMounted(async () => { + await getList() + // 加载数据源列表 + dataSourceConfigList.value = await DataSourceConfigApi.getDataSourceConfigList() +}) </script> From d5f3f0add1f91982402bdf2dd6508cfc08b70123 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Wed, 29 Mar 2023 13:57:16 +0800 Subject: [PATCH 148/184] =?UTF-8?q?REVIEW=20=E4=BB=A3=E7=A0=81=E7=94=9F?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/infra/codegen/EditTable.vue | 56 +++++++++-------- src/views/infra/config/config.data.ts | 90 --------------------------- 2 files changed, 31 insertions(+), 115 deletions(-) delete mode 100644 src/views/infra/config/config.data.ts diff --git a/src/views/infra/codegen/EditTable.vue b/src/views/infra/codegen/EditTable.vue index 79922ffc..714b2761 100644 --- a/src/views/infra/codegen/EditTable.vue +++ b/src/views/infra/codegen/EditTable.vue @@ -1,5 +1,5 @@ <template> - <content-wrap v-loading="loading"> + <content-wrap v-loading="formLoading"> <el-tabs v-model="activeName"> <el-tab-pane label="基本信息" name="basicInfo"> <basic-info-form ref="basicInfoRef" :table="formData.table" /> @@ -11,63 +11,69 @@ <generate-info-form ref="generateInfoRef" :table="formData.table" /> </el-tab-pane> </el-tabs> - <el-form label-width="100px"> - <el-form-item style="text-align: center; margin-left: -100px; margin-top: 10px"> - <el-button type="primary" @click="submitForm" :loading="submitLoading"> - {{ t('action.save') }} - </el-button> + <el-form> + <el-form-item style="float: right"> + <el-button type="primary" @click="submitForm" :loading="formLoading">保存</el-button> <el-button @click="close">返回</el-button> </el-form-item> </el-form> </content-wrap> </template> <script setup lang="ts"> +import { useTagsViewStore } from '@/store/modules/tagsView' import { BasicInfoForm, ColumInfoForm, GenerateInfoForm } from './components' import * as CodegenApi from '@/api/infra/codegen' -import ContentWrap from '@/components/ContentWrap/src/ContentWrap.vue' -import { useTagsViewStore } from '@/store/modules/tagsView' -import { CodegenUpdateReqVO } from '@/api/infra/codegen/types' - const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 -const { push, currentRoute } = useRouter() -const { query } = useRoute() -const { delView } = useTagsViewStore() -const loading = ref(false) -const submitLoading = ref(false) -const activeName = ref('basicInfo') +const { push, currentRoute } = useRouter() // 路由 +const { query } = useRoute() // 查询参数 +const { delView } = useTagsViewStore() // 视图操作 + +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const activeName = ref('basicInfo') // Tag 激活的窗口 const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>() const columInfoRef = ref<ComponentRef<typeof ColumInfoForm>>() const generateInfoRef = ref<ComponentRef<typeof GenerateInfoForm>>() -const formData = ref<CodegenUpdateReqVO>({ +const formData = ref<CodegenApi.CodegenUpdateReqVO>({ table: {}, columns: [] }) +/** 获得详情 */ const getDetail = async () => { const id = query.id as unknown as number - if (id) { - loading.value = true - // 获取表详细信息 + if (!id) { + return + } + formLoading.value = true + try { formData.value = await CodegenApi.getCodegenTable(id) - loading.value = false + } finally { + formLoading.value = false } } + +/** 提交按钮 */ const submitForm = async () => { + // 参数校验 if (!unref(formData)) return + await unref(basicInfoRef)?.validate() + await unref(generateInfoRef)?.validate() try { - await unref(basicInfoRef)?.validate() - await unref(generateInfoRef)?.validate() - await CodegenApi.updateCodegenTable(unref(formData)) + // 提交请求 + await CodegenApi.updateCodegenTable(formData.value) message.success(t('common.updateSuccess')) - push('/infra/codegen') + close() } catch {} } + /** 关闭按钮 */ const close = () => { delView(unref(currentRoute)) push('/infra/codegen') } + +/** 初始化 */ onMounted(() => { getDetail() }) diff --git a/src/views/infra/config/config.data.ts b/src/views/infra/config/config.data.ts deleted file mode 100644 index 41acfa15..00000000 --- a/src/views/infra/config/config.data.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -const { t } = useI18n() // 国际化 - -// 表单校验 -export const rules = reactive({ - category: [required], - name: [required], - key: [required], - value: [required] -}) - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: null, - action: true, - columns: [ - { - title: '参数分类', - field: 'category' - }, - { - title: '参数名称', - field: 'name', - isSearch: true - }, - { - title: '参数键名', - field: 'key', - isSearch: true - }, - { - title: '参数键值', - field: 'value' - }, - { - title: '系统内置', - field: 'type', - dictType: DICT_TYPE.INFRA_CONFIG_TYPE, - dictClass: 'number', - isSearch: true - }, - { - title: '是否可见', - field: 'visible', - table: { - slots: { - default: 'visible_default' - } - }, - form: { - component: 'RadioButton', - componentProps: { - options: [ - { label: '是', value: true }, - { label: '否', value: false } - ] - } - } - }, - { - title: t('form.remark'), - field: 'remark', - isTable: false, - form: { - component: 'Input', - componentProps: { - type: 'textarea', - rows: 4 - }, - colProps: { - span: 24 - } - } - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From b1e74a1d053566d051d5af8ad62588c09f2db3ba Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Wed, 29 Mar 2023 15:07:50 +0800 Subject: [PATCH 149/184] =?UTF-8?q?REVIEW=20=E4=BB=A3=E7=A0=81=E7=94=9F?= =?UTF-8?q?=E6=88=90(ImportTable)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codegen/{components => }/ImportTable.vue | 125 ++++++++++-------- .../Preview.vue => PreviewCode.vue} | 0 src/views/infra/codegen/components/index.ts | 4 +- src/views/infra/codegen/index.vue | 9 +- 4 files changed, 73 insertions(+), 65 deletions(-) rename src/views/infra/codegen/{components => }/ImportTable.vue (51%) rename src/views/infra/codegen/{components/Preview.vue => PreviewCode.vue} (100%) diff --git a/src/views/infra/codegen/components/ImportTable.vue b/src/views/infra/codegen/ImportTable.vue similarity index 51% rename from src/views/infra/codegen/components/ImportTable.vue rename to src/views/infra/codegen/ImportTable.vue index 7eebbe65..b89b2923 100644 --- a/src/views/infra/codegen/components/ImportTable.vue +++ b/src/views/infra/codegen/ImportTable.vue @@ -1,10 +1,15 @@ <template> - <Dialog :title="modelTitle" v-model="modelVisible"> - <el-form :model="queryParams" ref="queryFormRef" :inline="true"> + <Dialog title="导入表" v-model="modelVisible" width="800px"> + <!-- 搜索栏 --> + <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px"> <el-form-item label="数据源" prop="dataSourceConfigId"> - <el-select v-model="queryParams.dataSourceConfigId" placeholder="请选择数据源"> + <el-select + v-model="queryParams.dataSourceConfigId" + placeholder="请选择数据源" + class="!w-240px" + > <el-option - v-for="config in dataSourceConfigs" + v-for="config in dataSourceConfigList" :key="config.id" :label="config.name" :value="config.id" @@ -16,7 +21,8 @@ v-model="queryParams.name" placeholder="请输入表名称" clearable - @keyup.enter="handleQuery" + @keyup.enter="getList" + class="!w-240px" /> </el-form-item> <el-form-item label="表描述" prop="comment"> @@ -24,19 +30,20 @@ v-model="queryParams.comment" placeholder="请输入表描述" clearable - @keyup.enter="handleQuery" + @keyup.enter="getList" + class="!w-240px" /> </el-form-item> <el-form-item> - <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="getList"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> </el-form-item> </el-form> - + <!-- 列表 --> <el-row> <el-table - v-loading="dbLoading" - @row-click="clickRow" + v-loading="dbTableLoading" + @row-click="handleRowClick" ref="tableRef" :data="dbTableList" @selection-change="handleSelectionChange" @@ -47,87 +54,89 @@ <el-table-column prop="comment" label="表描述" :show-overflow-tooltip="true" /> </el-table> </el-row> - + <!-- 操作 --> <template #footer> - <div class="dialog-footer"> - <el-button @click="handleImportTable" type="primary" :disabled="tables.length === 0">{{ - t('action.import') - }}</el-button> - <el-button @click="handleClose">{{ t('dialog.close') }}</el-button> - </div> + <el-button @click="handleImportTable" type="primary" :disabled="tableList.length === 0"> + 导入 + </el-button> + <el-button @click="close">关闭</el-button> </template> </Dialog> </template> <script setup lang="ts"> -import type { DatabaseTableVO } from '@/api/infra/codegen/types' import * as CodegenApi from '@/api/infra/codegen' -import { getDataSourceConfigList, DataSourceConfigVO } from '@/api/infra/dataSourceConfig' +import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig' import { ElTable } from 'element-plus' - -const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 -const emit = defineEmits(['ok']) const modelVisible = ref(false) // 弹窗的是否展示 -const modelTitle = ref('导入表') // 弹窗的标题 -const dbLoading = ref(true) +const dbTableLoading = ref(true) // 数据源的加载中 +const dbTableList = ref<CodegenApi.DatabaseTableVO[]>([]) // 表的列表 const queryParams = reactive({ name: undefined, comment: undefined, dataSourceConfigId: 0 }) -const dataSourceConfigs = ref<DataSourceConfigVO[]>([]) -const show = async () => { - dataSourceConfigs.value = await getDataSourceConfigList() - queryParams.dataSourceConfigId = dataSourceConfigs.value[0].id as number - modelVisible.value = true - await getList() -} -/** 查询表数据 */ -const dbTableList = ref<DatabaseTableVO[]>([]) +const queryFormRef = ref() // 搜索的表单 +const dataSourceConfigList = ref<DataSourceConfigApi.DataSourceConfigVO[]>([]) // 数据源列表 /** 查询表数据 */ const getList = async () => { - dbLoading.value = true - dbTableList.value = await CodegenApi.getSchemaTableList(queryParams) - dbLoading.value = false + dbTableLoading.value = true + try { + dbTableList.value = await CodegenApi.getSchemaTableList(queryParams) + } finally { + dbTableLoading.value = false + } } -// 查询操作 -const handleQuery = async () => { - await getList() -} -// 重置操作 + +/** 重置操作 */ const resetQuery = async () => { queryParams.name = undefined queryParams.comment = undefined - queryParams.dataSourceConfigId = dataSourceConfigs.value[0].id as number + queryParams.dataSourceConfigId = dataSourceConfigList.value[0].id as number await getList() } -const tableRef = ref<typeof ElTable>() -/** 多选框选中数据 */ -const tables = ref<string[]>([]) -const clickRow = (row) => { + +/** 打开弹窗 */ +const open = async () => { + // 加载数据源的列表 + dataSourceConfigList.value = await DataSourceConfigApi.getDataSourceConfigList() + queryParams.dataSourceConfigId = dataSourceConfigList.value[0].id as number + modelVisible.value = true + // 加载表的列表 + await getList() +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 关闭弹窗 */ +const close = () => { + modelVisible.value = false + tableList.value = [] +} + +const tableRef = ref<typeof ElTable>() // 表格的 Ref +const tableList = ref<string[]>([]) // 选中的表名 + +/** 处理某一行的点击 */ +const handleRowClick = (row) => { unref(tableRef)?.toggleRowSelection(row) } -// 多选框选中数据 + +/** 多选框选中数据 */ const handleSelectionChange = (selection) => { - tables.value = selection.map((item) => item.name) + tableList.value = selection.map((item) => item.name) } + /** 导入按钮操作 */ const handleImportTable = async () => { await CodegenApi.createCodegenList({ dataSourceConfigId: queryParams.dataSourceConfigId, - tableNames: tables.value + tableNames: tableList.value }) message.success('导入成功') - emit('ok') - handleClose() + emit('success') + close() } -const handleClose = () => { - modelVisible.value = false - tables.value = [] -} -defineExpose({ - show -}) +const emit = defineEmits(['success']) </script> diff --git a/src/views/infra/codegen/components/Preview.vue b/src/views/infra/codegen/PreviewCode.vue similarity index 100% rename from src/views/infra/codegen/components/Preview.vue rename to src/views/infra/codegen/PreviewCode.vue diff --git a/src/views/infra/codegen/components/index.ts b/src/views/infra/codegen/components/index.ts index 71d0587f..1634a76f 100644 --- a/src/views/infra/codegen/components/index.ts +++ b/src/views/infra/codegen/components/index.ts @@ -1,6 +1,4 @@ import BasicInfoForm from './BasicInfoForm.vue' import ColumInfoForm from './ColumInfoForm.vue' import GenerateInfoForm from './GenerateInfoForm.vue' -import ImportTable from './ImportTable.vue' -import Preview from './Preview.vue' -export { BasicInfoForm, ColumInfoForm, GenerateInfoForm, ImportTable, Preview } +export { BasicInfoForm, ColumInfoForm, GenerateInfoForm } diff --git a/src/views/infra/codegen/index.vue b/src/views/infra/codegen/index.vue index b975779a..64a270e5 100644 --- a/src/views/infra/codegen/index.vue +++ b/src/views/infra/codegen/index.vue @@ -135,16 +135,17 @@ </content-wrap> <!-- 弹窗:导入表 --> - <ImportTable ref="importRef" @ok="getList" /> + <ImportTable ref="importRef" success="getList" /> <!-- 弹窗:预览代码 --> - <Preview ref="previewRef" /> + <PreviewCode ref="previewRef" /> </template> <script setup lang="ts" name="Codegen"> import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' import * as CodegenApi from '@/api/infra/codegen' import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig' -import { ImportTable, Preview } from './components' +import ImportTable from './ImportTable.vue' +import PreviewCode from './PreviewCode.vue' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 const { push } = useRouter() // 路由跳转 @@ -189,7 +190,7 @@ const resetQuery = () => { // 导入操作 const importRef = ref() const openImportTable = () => { - importRef.value.show() + importRef.value.open() } /** 编辑操作 */ From cc41c20709e13739bcd00233fb5ffb8d8b89a02d Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Wed, 29 Mar 2023 15:29:24 +0800 Subject: [PATCH 150/184] =?UTF-8?q?update=EF=BC=9A=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E8=A7=92=E8=89=B2=E7=AE=A1=E7=90=86=E6=89=93=E4=B8=8D=E5=BC=80?= =?UTF-8?q?=E6=9D=83=E9=99=90=E8=A1=A8=E5=8D=95=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/role/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue index 0e75d67d..af8e0d3a 100644 --- a/src/views/system/role/index.vue +++ b/src/views/system/role/index.vue @@ -204,7 +204,7 @@ const openForm = (type: string, id?: number) => { /** 数据权限操作 */ const menuPermissionFormRef = ref() const handleScope = async (type: string, row: RoleApi.RoleVO) => { - menuPermissionFormRef.value.openForm(type, row) + menuPermissionFormRef.value.openModal(type, row) } /** 删除按钮操作 */ From 86c6073f1f9173341db549b5eb30b52b3a9e5949 Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Wed, 29 Mar 2023 15:31:34 +0800 Subject: [PATCH 151/184] =?UTF-8?q?update=EF=BC=9A=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E8=A7=92=E8=89=B2=E7=AE=A1=E7=90=86=E8=8F=9C=E5=8D=95=E6=9D=83?= =?UTF-8?q?=E9=99=90=E8=A1=A8=E5=8D=95tree=E7=BB=84=E4=BB=B6=E4=B8=80?= =?UTF-8?q?=E7=9B=B4=E6=98=BE=E7=A4=BA=E5=8A=A0=E8=BD=BD=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/role/MenuPermissionForm.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/system/role/MenuPermissionForm.vue b/src/views/system/role/MenuPermissionForm.vue index 650fb659..70971781 100644 --- a/src/views/system/role/MenuPermissionForm.vue +++ b/src/views/system/role/MenuPermissionForm.vue @@ -59,7 +59,7 @@ show-checkbox :check-strictly="!checkStrictly" :props="defaultProps" - :data="dataScopeForm" + :data="treeOptions" empty-text="加载中,请稍后" /> </el-card> From 64b974deafccaf106424228b8301d0fae8507a15 Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Wed, 29 Mar 2023 17:49:17 +0800 Subject: [PATCH 152/184] =?UTF-8?q?add:=20Vue3=E9=87=8D=E6=9E=84=E7=A7=9F?= =?UTF-8?q?=E6=88=B7=E5=A5=97=E9=A4=90=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/tenantPackage/index.ts | 10 +- src/types/auto-components.d.ts | 4 - src/views/system/tenantPackage/form.vue | 199 +++++++++++ src/views/system/tenantPackage/index.vue | 320 +++++++++--------- .../tenantPackage/tenantPackage.data.ts | 73 ---- 5 files changed, 360 insertions(+), 246 deletions(-) create mode 100644 src/views/system/tenantPackage/form.vue delete mode 100644 src/views/system/tenantPackage/tenantPackage.data.ts diff --git a/src/api/system/tenantPackage/index.ts b/src/api/system/tenantPackage/index.ts index e431a9ee..37f7067a 100644 --- a/src/api/system/tenantPackage/index.ts +++ b/src/api/system/tenantPackage/index.ts @@ -20,27 +20,27 @@ export interface TenantPackagePageReqVO extends PageParam { } // 查询租户套餐列表 -export const getTenantPackageTypePageApi = (params: TenantPackagePageReqVO) => { +export const getTenantPackageTypePage = (params: TenantPackagePageReqVO) => { return request.get({ url: '/system/tenant-package/page', params }) } // 获得租户 -export const getTenantPackageApi = (id: number) => { +export const getTenantPackage = (id: number) => { return request.get({ url: '/system/tenant-package/get?id=' + id }) } // 新增租户套餐 -export const createTenantPackageTypeApi = (data: TenantPackageVO) => { +export const createTenantPackageType = (data: TenantPackageVO) => { return request.post({ url: '/system/tenant-package/create', data }) } // 修改租户套餐 -export const updateTenantPackageTypeApi = (data: TenantPackageVO) => { +export const updateTenantPackageType = (data: TenantPackageVO) => { return request.put({ url: '/system/tenant-package/update', data }) } // 删除租户套餐 -export const deleteTenantPackageTypeApi = (id: number) => { +export const deleteTenantPackageType = (id: number) => { return request.delete({ url: '/system/tenant-package/delete?id=' + id }) } // 获取租户套餐精简信息列表 diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index 480691fc..4b1eb7ef 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -52,7 +52,6 @@ declare module '@vue/runtime-core' { ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] - ElImage: typeof import('element-plus/es')['ElImage'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] @@ -73,10 +72,7 @@ declare module '@vue/runtime-core' { ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] ElTag: typeof import('element-plus/es')['ElTag'] - ElTimeline: typeof import('element-plus/es')['ElTimeline'] - ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] - ElTransfer: typeof import('element-plus/es')['ElTransfer'] ElTree: typeof import('element-plus/es')['ElTree'] ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElUpload: typeof import('element-plus/es')['ElUpload'] diff --git a/src/views/system/tenantPackage/form.vue b/src/views/system/tenantPackage/form.vue new file mode 100644 index 00000000..e90222e7 --- /dev/null +++ b/src/views/system/tenantPackage/form.vue @@ -0,0 +1,199 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible" width="50%"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="套餐名" prop="name"> + <el-input v-model="formData.name" placeholder="请输入套餐名" /> + </el-form-item> + <el-form-item label="菜单权限"> + <el-card class="cardHeight"> + <template #header> + 父子联动(选中父节点,自动选择子节点): + <el-switch + v-model="menuCheckStrictly" + inline-prompt + active-text="是" + inactive-text="否" + /> + 全选/全不选: + <el-switch + v-model="treeNodeAll" + inline-prompt + active-text="是" + inactive-text="否" + @change="handleCheckedTreeNodeAll" + /> + 全部展开/折叠: + <el-switch + v-model="menuExpand" + inline-prompt + active-text="展开" + inactive-text="折叠" + @change="handleCheckedTreeExpand" + /> + </template> + <el-tree + ref="treeRef" + node-key="id" + :check-strictly="!menuCheckStrictly" + show-checkbox + :props="defaultProps" + :data="menuOptions as any[]" + empty-text="加载中,请稍候" + /> + </el-card> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-radio-group v-model="formData.status"> + <el-radio + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="parseInt(dict.value)" + > + {{ dict.label }} + </el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="formData.remark" placeholder="请输入备注" /> + </el-form-item> + </el-form> + <template #footer> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script setup lang="ts" name="TenantPackageForm"> +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { CommonStatusEnum } from '@/utils/constants' +import type { FormRules } from 'element-plus' +import { defaultProps } from '@/utils/tree' +// 业务相关 +import * as TenantPackageApi from '@/api/system/tenantPackage' +import { getSimpleMenusList } from '@/api/system/menu' +import { ElTree } from 'element-plus' +import { handleTree } from '@/utils/tree' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData: Record<string, any> = ref<Record<string, any>>({ + id: null, + name: null, + remark: null, + menuIds: [], + status: CommonStatusEnum.ENABLE +}) +const formRules: FormRules = ref<FormRules>({ + name: [{ required: true, message: '套餐名不能为空', trigger: 'blur' }], + status: [{ required: true, message: '状态不能为空', trigger: 'blur' }], + menuIds: [{ required: true, message: '关联的菜单编号不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref +const menuOptions = ref<any[]>([]) // 树形结构数据 +const menuCheckStrictly = ref(false) // 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 true +const menuExpand = ref(false) // 展开/折叠 +const treeRef = ref<InstanceType<typeof ElTree>>() // 树组件Ref +const treeNodeAll = ref(false) // 全选/全不选 + +// 全选/全不选 +const handleCheckedTreeNodeAll = () => { + treeRef.value!.setCheckedNodes(treeNodeAll.value ? menuOptions.value : []) +} +// 全部(展开/折叠)TODO:for循环全部展开和折叠树组件数据 +const handleCheckedTreeExpand = () => { + const nodes = treeRef.value!.store.nodesMap + for (let node in nodes) { + nodes[node].expanded = !nodes[node].expanded + } +} + +/** 打开弹窗 */ +const open = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 加载Menu列表 + menuOptions.value = handleTree(await getSimpleMenusList()) + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + const res = await TenantPackageApi.getTenantPackage(id) + // 设置选中 + formData.value = res + // 设置选中 + res.menuIds?.forEach((item: any) => { + treeRef.value?.setChecked(item, true, false) + }) + } finally { + formLoading.value = false + } + } +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as TenantPackageApi.TenantPackageVO + data.menuIds = [ + ...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>), + ...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>) + ] + if (formType.value === 'create') { + await TenantPackageApi.createTenantPackageType(data) + message.success(t('common.createSuccess')) + } else { + await TenantPackageApi.updateTenantPackageType(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: null, + name: null, + remark: null, + menuIds: [], + status: CommonStatusEnum.ENABLE + } + treeRef.value?.setCheckedNodes([]) + treeNodeAll.value = false + menuExpand.value = false + // 设置为非严格,继续使用半选中 + menuCheckStrictly.value = false + formRef.value?.resetFields() +} +</script> +<style lang="scss" scoped> +.cardHeight { + width: 100%; + max-height: 400px; + overflow-y: scroll; +} +</style> diff --git a/src/views/system/tenantPackage/index.vue b/src/views/system/tenantPackage/index.vue index 07ea39c6..955bf0da 100644 --- a/src/views/system/tenantPackage/index.vue +++ b/src/views/system/tenantPackage/index.vue @@ -1,187 +1,179 @@ <template> + <!-- 搜索 --> <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - @click="handleCreate()" + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="套餐名" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入套餐名" + clearable + @keyup.enter="handleQuery" /> - </template> - <template #actionbtns_default="{ row }"> - <XTextButton preIcon="ep:edit" :title="t('action.edit')" @click="handleUpdate(row.id)" /> - <XTextButton preIcon="ep:delete" :title="t('action.del')" @click="deleteData(row.id)" /> - </template> - </XTable> - <XModal v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(添加 / 修改) --> - <Form - v-if="['create', 'update'].includes(actionType)" - :schema="allSchemas.formSchema" - :rules="rules" - ref="formRef" - > - <template #menuIds> - <el-card class="cardHeight"> - <template #header> - <div class="card-header"> - 全选/全不选: - <el-switch - v-model="treeNodeAll" - inline-prompt - active-text="是" - inactive-text="否" - @change="handleCheckedTreeNodeAll()" - /> - </div> - </template> - <el-tree - ref="treeRef" - node-key="id" - show-checkbox - :props="defaultProps" - :data="menuOptions" - empty-text="加载中,请稍候" - /> - </el-card> - </template> - </Form> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="loading" - @click="submitForm()" + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="请选择状态" clearable> + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + style="width: 240px" + type="daterange" + value-format="YYYY-MM-DD HH:mm:ss" + start-placeholder="开始日期" + end-placeholder="结束日期" + class="!w-240px" /> - <!-- 按钮:关闭 --> - <XButton :loading="loading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> + </el-form-item> + + <el-form-item> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px" /> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px" /> + 重置 + </el-button> + <el-button + type="primary" + @click="handleCreate('create')" + v-hasPermi="['system:tenant-package:create']" + > + <Icon icon="ep:plus" class="mr-5px" /> + 新增 + </el-button> + </el-form-item> + </el-form> </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list" align="center"> + <el-table-column label="套餐编号" align="center" prop="id" width="120" /> + <el-table-column label="套餐名" align="center" prop="name" /> + <el-table-column label="状态" align="center" prop="status" width="100"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> + </template> + </el-table-column> + <el-table-column label="备注" align="center" prop="remark" /> + <el-table-column label="创建时间" align="center" prop="createTime" width="180"> + <template #default="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-edit" + @click="handleUpdate('update', scope.row.id)" + v-hasPermi="['system:tenant-package:update']" + >修改 + </el-button> + <el-button + size="mini" + type="text" + icon="el-icon-delete" + @click="handleDelete(scope.row.id)" + v-hasPermi="['system:tenant-package:delete']" + >删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </ContentWrap> + <TenantPackageForm ref="formRef" @success="getList" /> </template> <script setup lang="ts" name="TenantPackage"> -import { handleTree, defaultProps } from '@/utils/tree' -import type { FormExpose } from '@/components/Form' -import type { ElTree } from 'element-plus' +import TenantPackageForm from './form.vue' // 业务相关的 import -import { rules, allSchemas } from './tenantPackage.data' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { parseTime } from '@/utils/formatTime' import * as TenantPackageApi from '@/api/system/tenantPackage' -import { getSimpleMenusList } from '@/api/system/menu' -const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 +const { t } = useI18n() // 国际化 -const menuOptions = ref<any[]>([]) // 树形结构 -const menuExpand = ref(false) -const menuNodeAll = ref(false) -const treeRef = ref<InstanceType<typeof ElTree>>() -const treeNodeAll = ref(false) -const formRef = ref<FormExpose>() // 表单 Ref -const loading = ref(false) // 遮罩层 -const actionType = ref('') // 操作按钮的类型 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 - -// 全选/全不选 -const handleCheckedTreeNodeAll = () => { - treeRef.value!.setCheckedNodes(treeNodeAll.value ? menuOptions.value : []) -} - -const validateCategory = (rule: any, value: any, callback: any) => { - if (!treeRef.value!.getCheckedKeys().length) { - callback(new Error('该项为必填项')) - } else { - callback() - } -} -rules.menuIds = [{ required: true, validator: validateCategory, trigger: 'blur' }] - -const getTree = async () => { - const res = await getSimpleMenusList() - menuOptions.value = handleTree(res) -} - -const [registerTable, { reload, deleteData }] = useXTable({ - allSchemas: allSchemas, - getListApi: TenantPackageApi.getTenantPackageTypePageApi, - deleteApi: TenantPackageApi.deleteTenantPackageTypeApi +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams: Record<string, any> = ref<Record<string, any>>({ + pageNo: 1, + pageSize: 10, + name: null, + status: null, + remark: null, + createTime: [] }) +const queryFormRef = ref() // 搜索的表单 +const formRef = ref() // 表单 Ref -// 设置标题 -const setDialogTile = (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true +/** 查询列表 */ +const getList = () => { + loading.value = true + // 执行查询 + TenantPackageApi.getTenantPackageTypePage(queryParams.value).then((response) => { + list.value = response.list + total.value = response.total + loading.value = false + }) +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + // 表单重置 + queryFormRef.value?.resetFields() + getList() } // 新增操作 -const handleCreate = () => { - //重置菜单树 - unref(treeRef)?.setCheckedKeys([]) - menuExpand.value = false - menuNodeAll.value = false - setDialogTile('create') +const handleCreate = (type: string) => { + formRef.value.open(type) } // 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - // 设置数据 - const res = await TenantPackageApi.getTenantPackageApi(rowId) - unref(formRef)?.setValues(res) - // 设置选中 - res.menuIds?.forEach((item: any) => { - unref(treeRef)?.setChecked(item, true, false) - }) +const handleUpdate = async (type: string, id?: number) => { + formRef.value.open(type, id) } - -// 提交按钮 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - loading.value = true - // 提交请求 - try { - const data = unref(formRef)?.formModel as TenantPackageApi.TenantPackageVO - data.menuIds = [ - ...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>), - ...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>) - ] - if (actionType.value === 'create') { - await TenantPackageApi.createTenantPackageTypeApi(data) - message.success(t('common.createSuccess')) - } else { - await TenantPackageApi.updateTenantPackageTypeApi(data) - message.success(t('common.updateSuccess')) - } - // 操作成功,重新加载列表 - dialogVisible.value = false - } finally { - loading.value = false - // 刷新列表 - await reload() - } - } - }) +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await TenantPackageApi.deleteTenantPackageType(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} } - -// ========== 初始化 ========== -onMounted(async () => { - await getTree() -}) -// getList() +getList() </script> -<style scoped> -.cardHeight { - width: 100%; - max-height: 400px; - overflow-y: scroll; -} -</style> diff --git a/src/views/system/tenantPackage/tenantPackage.data.ts b/src/views/system/tenantPackage/tenantPackage.data.ts deleted file mode 100644 index d57dee29..00000000 --- a/src/views/system/tenantPackage/tenantPackage.data.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -const { t } = useI18n() // 国际化 - -// 表单校验 -export const rules = reactive({ - name: [required], - id: [required], - type: [required], - remark: [required], - status: [required], - menuIds: [required] -}) - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'id', - primaryTitle: '套餐编号', - action: true, - columns: [ - { - title: '套餐名称', - field: 'name', - isSearch: true - }, - { - title: t('common.status'), - field: 'status', - dictType: DICT_TYPE.COMMON_STATUS, - dictClass: 'number', - isSearch: true - }, - { - title: '菜单权限', - field: 'menuIds', - isTable: false, - form: { - colProps: { - span: 24 - } - } - }, - { - title: t('form.remark'), - field: 'remark', - isTable: true, - isSearch: true, - form: { - component: 'Input', - componentProps: { - type: 'textarea', - rows: 4 - }, - colProps: { - span: 24 - } - } - }, - { - title: '创建时间', - field: 'createTime', - formatter: 'formatDate', - isForm: false, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From a9df1463f010baf98d72566477672ac859aabdac Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Wed, 29 Mar 2023 17:50:13 +0800 Subject: [PATCH 153/184] =?UTF-8?q?add:=20Vue3=E9=87=8D=E6=9E=84=E7=A7=9F?= =?UTF-8?q?=E6=88=B7=E5=A5=97=E9=A4=90=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/tenantPackage/form.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/system/tenantPackage/form.vue b/src/views/system/tenantPackage/form.vue index e90222e7..d642402c 100644 --- a/src/views/system/tenantPackage/form.vue +++ b/src/views/system/tenantPackage/form.vue @@ -111,7 +111,7 @@ const handleCheckedTreeNodeAll = () => { } // 全部(展开/折叠)TODO:for循环全部展开和折叠树组件数据 const handleCheckedTreeExpand = () => { - const nodes = treeRef.value!.store.nodesMap + const nodes = treeRef.value?.store.nodesMap for (let node in nodes) { nodes[node].expanded = !nodes[node].expanded } From ca1b6df5ca3a48ef17f347135c218f3d1e15c8bc Mon Sep 17 00:00:00 2001 From: fengjingtao <fessor@139.com> Date: Wed, 29 Mar 2023 20:21:58 +0800 Subject: [PATCH 154/184] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E7=AE=A1?= =?UTF-8?q?=E7=90=86review=E4=BB=A5=E5=90=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 22 +- src/types/auto-components.d.ts | 11 - src/views/system/user/index0.vue | 576 +++++++++++++++++++++++++++++++ 3 files changed, 597 insertions(+), 12 deletions(-) create mode 100644 src/views/system/user/index0.vue diff --git a/.vscode/settings.json b/.vscode/settings.json index 38cc3052..3e9f1774 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -40,5 +40,25 @@ "i18n-ally.displayLanguage": "zh-CN", "i18n-ally.enabledFrameworks": ["vue", "react"], "god.tsconfig": "./tsconfig.json", - "vue-i18n.i18nPaths": "src/locales" + "vue-i18n.i18nPaths": "src/locales", + "workbench.colorCustomizations": { + "activityBar.activeBackground": "#65c89b", + "activityBar.background": "#65c89b", + "activityBar.foreground": "#15202b", + "activityBar.inactiveForeground": "#15202b99", + "activityBarBadge.background": "#945bc4", + "activityBarBadge.foreground": "#e7e7e7", + "commandCenter.border": "#15202b99", + "sash.hoverBorder": "#65c89b", + "statusBar.background": "#42b883", + "statusBar.foreground": "#15202b", + "statusBarItem.hoverBackground": "#359268", + "statusBarItem.remoteBackground": "#42b883", + "statusBarItem.remoteForeground": "#15202b", + "titleBar.activeBackground": "#42b883", + "titleBar.activeForeground": "#15202b", + "titleBar.inactiveBackground": "#42b88399", + "titleBar.inactiveForeground": "#15202b99" + }, + "peacock.color": "#42b883" } diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index be71517c..3607ce04 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -23,8 +23,6 @@ declare module '@vue/runtime-core' { DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default'] Echart: typeof import('./../components/Echart/src/Echart.vue')['default'] Editor: typeof import('./../components/Editor/src/Editor.vue')['default'] - ElAutoResizer: typeof import('element-plus/es')['ElAutoResizer'] - ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElBadge: typeof import('element-plus/es')['ElBadge'] ElButton: typeof import('element-plus/es')['ElButton'] ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] @@ -54,31 +52,22 @@ declare module '@vue/runtime-core' { ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] - ElImage: typeof import('element-plus/es')['ElImage'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] - ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElLink: typeof import('element-plus/es')['ElLink'] ElOption: typeof import('element-plus/es')['ElOption'] ElPagination: typeof import('element-plus/es')['ElPagination'] ElPopover: typeof import('element-plus/es')['ElPopover'] - ElRadio: typeof import('element-plus/es')['ElRadio'] - ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] - ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRow: typeof import('element-plus/es')['ElRow'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElSelect: typeof import('element-plus/es')['ElSelect'] ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] - ElSpace: typeof import('element-plus/es')['ElSpace'] ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElTable: typeof import('element-plus/es')['ElTable'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] - ElTableV2: typeof import('element-plus/es')['ElTableV2'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] ElTag: typeof import('element-plus/es')['ElTag'] - ElTimeline: typeof import('element-plus/es')['ElTimeline'] - ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTransfer: typeof import('element-plus/es')['ElTransfer'] ElTree: typeof import('element-plus/es')['ElTree'] diff --git a/src/views/system/user/index0.vue b/src/views/system/user/index0.vue new file mode 100644 index 00000000..2f9ba9b0 --- /dev/null +++ b/src/views/system/user/index0.vue @@ -0,0 +1,576 @@ +<template> + <div class="flex"> + <el-card class="w-1/5 user" :gutter="12" shadow="always"> + <template #header> + <div class="card-header"> + <span>部门列表</span> + <XTextButton title="修改部门" @click="handleDeptEdit()" /> + </div> + </template> + <el-input v-model="filterText" placeholder="搜索部门" /> + <el-scrollbar height="650"> + <el-tree + ref="treeRef" + node-key="id" + default-expand-all + :data="deptOptions" + :props="defaultProps" + :highlight-current="true" + :filter-node-method="filterNode" + :expand-on-click-node="false" + @node-click="handleDeptNodeClick" + /> + </el-scrollbar> + </el-card> + <el-card class="w-4/5 user" style="margin-left: 10px" :gutter="12" shadow="hover"> + <template #header> + <div class="card-header"> + <span>{{ tableTitle }}</span> + </div> + </template> + <!-- 列表 --> + <XTable @register="registerTable"> + <template #toolbar_buttons> + <!-- 操作:新增 --> + <XButton + type="primary" + preIcon="ep:zoom-in" + :title="t('action.add')" + v-hasPermi="['system:user:create']" + @click="handleCreate()" + /> + <!-- 操作:导入用户 --> + <XButton + type="warning" + preIcon="ep:upload" + :title="t('action.import')" + v-hasPermi="['system:user:import']" + @click="importDialogVisible = true" + /> + <!-- 操作:导出用户 --> + <XButton + type="warning" + preIcon="ep:download" + :title="t('action.export')" + v-hasPermi="['system:user:export']" + @click="exportList('用户数据.xls')" + /> + </template> + <template #status_default="{ row }"> + <el-switch + v-model="row.status" + :active-value="0" + :inactive-value="1" + @change="handleStatusChange(row)" + /> + </template> + <template #actionbtns_default="{ row }"> + <!-- 操作:编辑 --> + <XTextButton + preIcon="ep:edit" + :title="t('action.edit')" + v-hasPermi="['system:user:update']" + @click="handleUpdate(row.id)" + /> + <!-- 操作:详情 --> + <XTextButton + preIcon="ep:view" + :title="t('action.detail')" + v-hasPermi="['system:user:update']" + @click="handleDetail(row.id)" + /> + <el-dropdown + class="p-0.5" + v-hasPermi="[ + 'system:user:update-password', + 'system:permission:assign-user-role', + 'system:user:delete' + ]" + > + <XTextButton :title="t('action.more')" postIcon="ep:arrow-down" /> + <template #dropdown> + <el-dropdown-menu> + <el-dropdown-item> + <!-- 操作:重置密码 --> + <XTextButton + preIcon="ep:key" + title="重置密码" + v-hasPermi="['system:user:update-password']" + @click="handleResetPwd(row)" + /> + </el-dropdown-item> + <el-dropdown-item> + <!-- 操作:分配角色 --> + <XTextButton + preIcon="ep:key" + title="分配角色" + v-hasPermi="['system:permission:assign-user-role']" + @click="handleRole(row)" + /> + </el-dropdown-item> + <el-dropdown-item> + <!-- 操作:删除 --> + <XTextButton + preIcon="ep:delete" + :title="t('action.del')" + v-hasPermi="['system:user:delete']" + @click="deleteData(row.id)" + /> + </el-dropdown-item> + </el-dropdown-menu> + </template> + </el-dropdown> + </template> + </XTable> + </el-card> + </div> + <XModal v-model="dialogVisible" :title="dialogTitle"> + <!-- 对话框(添加 / 修改) --> + <Form + v-if="['create', 'update'].includes(actionType)" + :rules="rules" + :schema="allSchemas.formSchema" + ref="formRef" + > + <template #deptId="form"> + <el-tree-select + node-key="id" + v-model="form['deptId']" + :props="defaultProps" + :data="deptOptions" + check-strictly + /> + </template> + <template #postIds="form"> + <el-select v-model="form['postIds']" multiple :placeholder="t('common.selectText')"> + <el-option + v-for="item in postOptions" + :key="item.id" + :label="item.name" + :value="(item.id as unknown as number)" + /> + </el-select> + </template> + </Form> + <!-- 对话框(详情) --> + <Descriptions + v-if="actionType === 'detail'" + :schema="allSchemas.detailSchema" + :data="detailData" + > + <template #deptId="{ row }"> + <el-tag>{{ dataFormater(row.deptId) }}</el-tag> + </template> + <template #postIds="{ row }"> + <template v-if="row.postIds !== ''"> + <el-tag v-for="(post, index) in row.postIds" :key="index" index=""> + <template v-for="postObj in postOptions"> + {{ post === postObj.id ? postObj.name : '' }} + </template> + </el-tag> + </template> + <template v-else> </template> + </template> + </Descriptions> + <!-- 操作按钮 --> + <template #footer> + <!-- 按钮:保存 --> + <XButton + v-if="['create', 'update'].includes(actionType)" + type="primary" + :title="t('action.save')" + :loading="loading" + @click="submitForm()" + /> + <!-- 按钮:关闭 --> + <XButton :loading="loading" :title="t('dialog.close')" @click="dialogVisible = false" /> + </template> + </XModal> + <!-- 分配用户角色 --> + <XModal v-model="roleDialogVisible" title="分配角色"> + <el-form :model="userRole" label-width="140px" :inline="true"> + <el-form-item label="用户名称"> + <el-tag>{{ userRole.username }}</el-tag> + </el-form-item> + <el-form-item label="用户昵称"> + <el-tag>{{ userRole.nickname }}</el-tag> + </el-form-item> + <el-form-item label="角色"> + <el-transfer + v-model="userRole.roleIds" + :titles="['角色列表', '已选择']" + :props="{ + key: 'id', + label: 'name' + }" + :data="roleOptions" + /> + </el-form-item> + </el-form> + <!-- 操作按钮 --> + <template #footer> + <!-- 按钮:保存 --> + <XButton type="primary" :title="t('action.save')" :loading="loading" @click="submitRole()" /> + <!-- 按钮:关闭 --> + <XButton :title="t('dialog.close')" @click="roleDialogVisible = false" /> + </template> + </XModal> + <!-- 导入 --> + <XModal v-model="importDialogVisible" :title="importDialogTitle"> + <el-form class="drawer-multiColumn-form" label-width="150px"> + <el-form-item label="模板下载 :"> + <XButton type="primary" prefix="ep:download" title="点击下载" @click="handleImportTemp()" /> + </el-form-item> + <el-form-item label="文件上传 :"> + <el-upload + ref="uploadRef" + :action="updateUrl + '?updateSupport=' + updateSupport" + :headers="uploadHeaders" + :drag="true" + :limit="1" + :multiple="true" + :show-file-list="true" + :disabled="uploadDisabled" + :before-upload="beforeExcelUpload" + :on-exceed="handleExceed" + :on-success="handleFileSuccess" + :on-error="excelUploadError" + :auto-upload="false" + accept="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + > + <Icon icon="ep:upload-filled" /> + <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> + <template #tip> + <div class="el-upload__tip">请上传 .xls , .xlsx 标准格式文件</div> + </template> + </el-upload> + </el-form-item> + <el-form-item label="是否更新已经存在的用户数据:"> + <el-checkbox v-model="updateSupport" /> + </el-form-item> + </el-form> + <template #footer> + <!-- 按钮:保存 --> + <XButton + type="warning" + preIcon="ep:upload-filled" + :title="t('action.save')" + @click="submitFileForm()" + /> + <!-- 按钮:关闭 --> + <XButton :title="t('dialog.close')" @click="importDialogVisible = false" /> + </template> + </XModal> +</template> +<script setup lang="ts" name="User"> +import type { ElTree, UploadRawFile, UploadInstance } from 'element-plus' +import { handleTree, defaultProps } from '@/utils/tree' +import download from '@/utils/download' +import { CommonStatusEnum } from '@/utils/constants' +import { getAccessToken, getTenantId } from '@/utils/auth' +import type { FormExpose } from '@/components/Form' +import { rules, allSchemas } from './user.data' +import * as UserApi from '@/api/system/user' +import { listSimpleDeptApi } from '@/api/system/dept' +import { listSimpleRolesApi } from '@/api/system/role' +import { listSimplePostsApi, PostVO } from '@/api/system/post' +import { + aassignUserRoleApi, + listUserRolesApi, + PermissionAssignUserRoleReqVO +} from '@/api/system/permission' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const queryParams = reactive({ + deptId: null +}) +// ========== 列表相关 ========== +const tableTitle = ref('用户列表') +// 列表相关的变量 +const [registerTable, { reload, deleteData, exportList }] = useXTable({ + allSchemas: allSchemas, + params: queryParams, + getListApi: UserApi.getUserPageApi, + deleteApi: UserApi.deleteUserApi, + exportListApi: UserApi.exportUserApi +}) +// ========== 创建部门树结构 ========== +const filterText = ref('') +const deptOptions = ref<Tree[]>([]) // 树形结构 +const treeRef = ref<InstanceType<typeof ElTree>>() +const getTree = async () => { + const res = await listSimpleDeptApi() + deptOptions.value.push(...handleTree(res)) +} +const filterNode = (value: string, data: Tree) => { + if (!value) return true + return data.name.includes(value) +} +const handleDeptNodeClick = async (row: { [key: string]: any }) => { + queryParams.deptId = row.id + await reload() +} +const { push } = useRouter() +const handleDeptEdit = () => { + push('/system/dept') +} +watch(filterText, (val) => { + treeRef.value!.filter(val) +}) +// ========== CRUD 相关 ========== +const loading = ref(false) // 遮罩层 +const actionType = ref('') // 操作按钮的类型 +const dialogVisible = ref(false) // 是否显示弹出层 +const dialogTitle = ref('edit') // 弹出层标题 +const formRef = ref<FormExpose>() // 表单 Ref +const postOptions = ref<PostVO[]>([]) //岗位列表 + +// 获取岗位列表 +const getPostOptions = async () => { + const res = await listSimplePostsApi() + postOptions.value.push(...res) +} +const dataFormater = (val) => { + return deptFormater(deptOptions.value, val) +} +//部门回显 +const deptFormater = (ary, val: any) => { + var o = '' + if (ary && val) { + for (const v of ary) { + if (v.id == val) { + o = v.name + if (o) return o + } else if (v.children?.length) { + o = deptFormater(v.children, val) + if (o) return o + } + } + return o + } else { + return val + } +} + +// 设置标题 +const setDialogTile = async (type: string) => { + dialogTitle.value = t('action.' + type) + actionType.value = type + dialogVisible.value = true +} + +// 新增操作 +const handleCreate = async () => { + setDialogTile('create') + // 重置表单 + await nextTick() + if (allSchemas.formSchema[0].field !== 'username') { + unref(formRef)?.addSchema( + { + field: 'username', + label: '用户账号', + component: 'Input' + }, + 0 + ) + unref(formRef)?.addSchema( + { + field: 'password', + label: '用户密码', + component: 'InputPassword' + }, + 1 + ) + } +} + +// 修改操作 +const handleUpdate = async (rowId: number) => { + setDialogTile('update') + await nextTick() + unref(formRef)?.delSchema('username') + unref(formRef)?.delSchema('password') + // 设置数据 + const res = await UserApi.getUserApi(rowId) + unref(formRef)?.setValues(res) +} +const detailData = ref() + +// 详情操作 +const handleDetail = async (rowId: number) => { + // 设置数据 + const res = await UserApi.getUserApi(rowId) + detailData.value = res + await setDialogTile('detail') +} + +// 提交按钮 +const submitForm = async () => { + const elForm = unref(formRef)?.getElFormRef() + if (!elForm) return + elForm.validate(async (valid) => { + if (valid) { + // 提交请求 + try { + const data = unref(formRef)?.formModel as UserApi.UserVO + if (actionType.value === 'create') { + loading.value = true + await UserApi.createUserApi(data) + message.success(t('common.createSuccess')) + } else { + loading.value = true + await UserApi.updateUserApi(data) + message.success(t('common.updateSuccess')) + } + dialogVisible.value = false + } finally { + // unref(formRef)?.setSchema(allSchemas.formSchema) + // 刷新列表 + await reload() + loading.value = false + } + } + }) +} +// 改变用户状态操作 +const handleStatusChange = async (row: UserApi.UserVO) => { + const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' + message + .confirm('确认要"' + text + '""' + row.username + '"用户吗?', t('common.reminder')) + .then(async () => { + row.status = + row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.ENABLE : CommonStatusEnum.DISABLE + await UserApi.updateUserStatusApi(row.id, row.status) + message.success(text + '成功') + // 刷新列表 + await reload() + }) + .catch(() => { + row.status = + row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE + }) +} +// 重置密码 +const handleResetPwd = (row: UserApi.UserVO) => { + message.prompt('请输入"' + row.username + '"的新密码', t('common.reminder')).then(({ value }) => { + UserApi.resetUserPwdApi(row.id, value).then(() => { + message.success('修改成功,新密码是:' + value) + }) + }) +} +// 分配角色 +const roleDialogVisible = ref(false) +const roleOptions = ref() +const userRole = reactive({ + id: 0, + username: '', + nickname: '', + roleIds: [] +}) +const handleRole = async (row: UserApi.UserVO) => { + userRole.id = row.id + userRole.username = row.username + userRole.nickname = row.nickname + // 获得角色拥有的权限集合 + const roles = await listUserRolesApi(row.id) + userRole.roleIds = roles + // 获取角色列表 + const roleOpt = await listSimpleRolesApi() + roleOptions.value = roleOpt + roleDialogVisible.value = true +} +// 提交 +const submitRole = async () => { + const data = ref<PermissionAssignUserRoleReqVO>({ + userId: userRole.id, + roleIds: userRole.roleIds + }) + await aassignUserRoleApi(data.value) + message.success(t('common.updateSuccess')) + roleDialogVisible.value = false +} +// ========== 导入相关 ========== +// TODO @星语:这个要不要把导入用户,封装成一个小组件?可选哈 +const importDialogVisible = ref(false) +const uploadDisabled = ref(false) +const importDialogTitle = ref('用户导入') +const updateSupport = ref(0) +let updateUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import' +const uploadHeaders = ref() +// 下载导入模版 +const handleImportTemp = async () => { + const res = await UserApi.importUserTemplateApi() + download.excel(res, '用户导入模版.xls') +} +// 文件上传之前判断 +const beforeExcelUpload = (file: UploadRawFile) => { + const isExcel = + file.type === 'application/vnd.ms-excel' || + file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + const isLt5M = file.size / 1024 / 1024 < 5 + if (!isExcel) message.error('上传文件只能是 xls / xlsx 格式!') + if (!isLt5M) message.error('上传文件大小不能超过 5MB!') + return isExcel && isLt5M +} +// 文件上传 +const uploadRef = ref<UploadInstance>() +const submitFileForm = () => { + uploadHeaders.value = { + Authorization: 'Bearer ' + getAccessToken(), + 'tenant-id': getTenantId() + } + uploadDisabled.value = true + uploadRef.value!.submit() +} +// 文件上传成功 +const handleFileSuccess = async (response: any): Promise<void> => { + if (response.code !== 0) { + message.error(response.msg) + return + } + importDialogVisible.value = false + uploadDisabled.value = false + const data = response.data + let text = '上传成功数量:' + data.createUsernames.length + ';' + for (let username of data.createUsernames) { + text += '< ' + username + ' >' + } + text += '更新成功数量:' + data.updateUsernames.length + ';' + for (const username of data.updateUsernames) { + text += '< ' + username + ' >' + } + text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';' + for (const username in data.failureUsernames) { + text += '< ' + username + ': ' + data.failureUsernames[username] + ' >' + } + message.alert(text) + await reload() +} +// 文件数超出提示 +const handleExceed = (): void => { + message.error('最多只能上传一个文件!') +} +// 上传错误提示 +const excelUploadError = (): void => { + message.error('导入数据失败,请您重新上传!') +} +// ========== 初始化 ========== +onMounted(async () => { + await getPostOptions() + await getTree() +}) +</script> + +<style scoped> +.user { + height: 780px; + max-height: 800px; +} +.card-header { + display: flex; + justify-content: space-between; + align-items: center; +} +</style> From 66460539ea8337acc1ce83527bb7346898bb6683 Mon Sep 17 00:00:00 2001 From: fengjingtao <fessor@139.com> Date: Wed, 29 Mar 2023 22:07:53 +0800 Subject: [PATCH 155/184] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E7=94=A8=E6=88=B7=E7=AE=A1=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/auto-components.d.ts | 4 - .../UserAssignRoleForm.vue} | 197 +++--- .../system/user/components/UserDeptTree.vue | 51 ++ .../{AddForm.vue => components/UserForm.vue} | 460 +++++++------- .../UserImportForm.vue} | 307 +++++----- src/views/system/user/index.vue | 252 ++------ src/views/system/user/index0.vue | 576 ------------------ src/views/system/user/utils.ts | 44 -- 8 files changed, 596 insertions(+), 1295 deletions(-) rename src/views/system/user/{RoleForm.vue => components/UserAssignRoleForm.vue} (62%) create mode 100644 src/views/system/user/components/UserDeptTree.vue rename src/views/system/user/{AddForm.vue => components/UserForm.vue} (76%) rename src/views/system/user/{ImportForm.vue => components/UserImportForm.vue} (89%) delete mode 100644 src/views/system/user/index0.vue delete mode 100644 src/views/system/user/utils.ts diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index 52d25d1e..8fcd30dd 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -67,11 +67,7 @@ declare module '@vue/runtime-core' { ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] - ElTag: typeof import('element-plus/es')['ElTag'] - ElTimeline: typeof import('element-plus/es')['ElTimeline'] - ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] - ElTransfer: typeof import('element-plus/es')['ElTransfer'] ElTree: typeof import('element-plus/es')['ElTree'] ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElUpload: typeof import('element-plus/es')['ElUpload'] diff --git a/src/views/system/user/RoleForm.vue b/src/views/system/user/components/UserAssignRoleForm.vue similarity index 62% rename from src/views/system/user/RoleForm.vue rename to src/views/system/user/components/UserAssignRoleForm.vue index 0cb82df8..bbf37739 100644 --- a/src/views/system/user/RoleForm.vue +++ b/src/views/system/user/components/UserAssignRoleForm.vue @@ -1,89 +1,108 @@ -<template> - <el-dialog title="分配角色" :modelValue="show" width="500px" append-to-body @close="closeDialog"> - <el-form :model="formData" label-width="80px" ref="formRef"> - <el-form-item label="用户名称"> - <el-input v-model="formData.username" :disabled="true" /> - </el-form-item> - <el-form-item label="用户昵称"> - <el-input v-model="formData.nickname" :disabled="true" /> - </el-form-item> - <el-form-item label="角色"> - <el-select v-model="formData.roleIds" multiple placeholder="请选择角色"> - <el-option - v-for="item in roleOptions" - :key="item.id" - :label="item.name" - :value="item.id" - /> - </el-select> - </el-form-item> - </el-form> - <template #footer> - <div class="dialog-footer"> - <el-button type="primary" @click="submit">确 定</el-button> - <el-button @click="cancel">取 消</el-button> - </div> - </template> - </el-dialog> -</template> - -<script setup lang="ts"> -// TODO el-dialog 用 Dialog 组件 -import { assignUserRoleApi, PermissionAssignUserRoleReqVO } from '@/api/system/permission' - -interface Props { - show: boolean - roleOptions: any[] - formInitValue?: Recordable & Partial<typeof initParams> -} - -const props = withDefaults(defineProps<Props>(), { - show: false, - roleOptions: () => [], - formInitValue: () => ({}) -}) -const emits = defineEmits(['update:show', 'success']) - -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 - -// 表单初始化参数 -const initParams = { - nickname: '', - id: 0, - username: '', - roleIds: [] as number[] -} -const formData = ref<Recordable>({ ...initParams }) -watch( - () => props.formInitValue, - (val) => { - formData.value = { ...val } - }, - { deep: true } -) -/* 弹框按钮操作 */ -// 点击取消 -const cancel = () => { - closeDialog() -} -// 关闭弹窗 -const closeDialog = () => { - emits('update:show', false) -} -// 提交 -const submit = async () => { - const data = ref<PermissionAssignUserRoleReqVO>({ - userId: formData.value.id, - roleIds: formData.value.roleIds - }) - try { - await assignUserRoleApi(data.value) - message.success(t('common.updateSuccess')) - emits('success', true) - closeDialog() - } catch (error) { - console.error(error) - } -} -</script> +<template> + <Dialog + title="分配角色" + :modelValue="showDialog" + width="500px" + append-to-body + @close="closeDialog" + > + <el-form :model="formData" label-width="80px" ref="formRef"> + <el-form-item label="用户名称"> + <el-input v-model="formData.username" :disabled="true" /> + </el-form-item> + <el-form-item label="用户昵称"> + <el-input v-model="formData.nickname" :disabled="true" /> + </el-form-item> + <el-form-item label="角色"> + <el-select v-model="formData.roleIds" multiple placeholder="请选择角色"> + <el-option + v-for="item in roleOptions" + :key="item.id" + :label="item.name" + :value="item.id" + /> + </el-select> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submit">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </template> + </Dialog> +</template> + +<script setup lang="ts"> +import { + assignUserRoleApi, + listUserRolesApi, + PermissionAssignUserRoleReqVO +} from '@/api/system/permission' +import { UserVO } from '@/api/system/user' +import * as RoleApi from '@/api/system/role' + +const emits = defineEmits(['success']) + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +// 表单初始化参数 +const initParams = { + nickname: '', + id: 0, + username: '', + roleIds: [] as number[] +} +const formData = ref<Recordable>({ ...initParams }) + +/* 弹框按钮操作 */ +// 点击取消 +const cancel = () => { + closeDialog() +} +// 关闭弹窗 +const closeDialog = () => { + showDialog.value = false +} +// 提交 +const submit = async () => { + const data = ref<PermissionAssignUserRoleReqVO>({ + userId: formData.value.id, + roleIds: formData.value.roleIds + }) + try { + await assignUserRoleApi(data.value) + message.success(t('common.updateSuccess')) + emits('success', true) + closeDialog() + } catch (error) { + console.error(error) + } +} + +const roleOptions = ref() +const userRole = reactive(initParams) +const showDialog = ref(false) +const formRef = ref() +const openForm = async (row: UserVO) => { + formRef.value?.resetFields() + userRole.id = row.id + userRole.username = row.username + userRole.nickname = row.nickname + + // 获得角色列表 + const roleOpt = await RoleApi.getSimpleRoleList() + roleOptions.value = [...roleOpt] + + // 获得角色拥有的菜单集合 + const roles = await listUserRolesApi(row.id) + userRole.roleIds = roles + formData.value = { ...userRole } + + showDialog.value = true +} +defineExpose({ + openForm +}) +</script> diff --git a/src/views/system/user/components/UserDeptTree.vue b/src/views/system/user/components/UserDeptTree.vue new file mode 100644 index 00000000..59004a92 --- /dev/null +++ b/src/views/system/user/components/UserDeptTree.vue @@ -0,0 +1,51 @@ +<template> + <div class="head-container"> + <el-input v-model="deptName" placeholder="请输入部门名称" clearable style="margin-bottom: 20px"> + <template #prefix> + <Icon icon="ep:search" /> + </template> + </el-input> + </div> + <div class="head-container"> + <el-tree + :data="deptOptions" + :props="defaultProps" + :expand-on-click-node="false" + :filter-node-method="filterNode" + ref="treeRef" + node-key="id" + default-expand-all + highlight-current + @node-click="handleDeptNodeClick" + /> + </div> +</template> + +<script setup lang="ts" name="UserDeptTree"> +import { ElTree } from 'element-plus' +import * as DeptApi from '@/api/system/dept' +import { defaultProps, handleTree } from '@/utils/tree' + +const emits = defineEmits(['node-click']) +const deptName = ref('') +const deptOptions = ref<Tree[]>([]) // 树形结构 +const treeRef = ref<InstanceType<typeof ElTree>>() +const getTree = async () => { + const res = await DeptApi.getSimpleDeptList() + deptOptions.value = [] + deptOptions.value.push(...handleTree(res)) +} + +const filterNode = (value: string, data: Tree) => { + if (!value) return true + return data.name.includes(value) +} + +const handleDeptNodeClick = async (row: { [key: string]: any }) => { + emits('node-click', row) +} + +onMounted(async () => { + await getTree() +}) +</script> diff --git a/src/views/system/user/AddForm.vue b/src/views/system/user/components/UserForm.vue similarity index 76% rename from src/views/system/user/AddForm.vue rename to src/views/system/user/components/UserForm.vue index 9a4d6029..4ea21607 100644 --- a/src/views/system/user/AddForm.vue +++ b/src/views/system/user/components/UserForm.vue @@ -1,223 +1,237 @@ -<template> - <!-- 添加或修改参数配置对话框 --> - <el-dialog - :title="title" - :modelValue="modelValue" - width="600px" - append-to-body - @close="closeDialog" - > - <el-form ref="formRef" :model="formData" :rules="rules" label-width="80px"> - <el-row> - <el-col :span="12"> - <el-form-item label="用户昵称" prop="nickname"> - <el-input v-model="formData.nickname" placeholder="请输入用户昵称" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="归属部门" prop="deptId"> - <el-tree-select - node-key="id" - v-model="formData.deptId" - :data="deptOptions" - :props="defaultProps" - check-strictly - placeholder="请选择归属部门" - /> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="12"> - <el-form-item label="手机号码" prop="mobile"> - <el-input v-model="formData.mobile" placeholder="请输入手机号码" maxlength="11" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="邮箱" prop="email"> - <el-input v-model="formData.email" placeholder="请输入邮箱" maxlength="50" /> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="12"> - <el-form-item v-if="formData.id === undefined" label="用户名称" prop="username"> - <el-input v-model="formData.username" placeholder="请输入用户名称" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item v-if="formData.id === undefined" label="用户密码" prop="password"> - <el-input - v-model="formData.password" - placeholder="请输入用户密码" - type="password" - show-password - /> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="12"> - <el-form-item label="用户性别"> - <el-select v-model="formData.sex" placeholder="请选择"> - <el-option - v-for="dict in sexDictDatas" - :key="parseInt(dict.value)" - :label="dict.label" - :value="parseInt(dict.value)" - /> - </el-select> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="岗位"> - <el-select v-model="formData.postIds" multiple placeholder="请选择"> - <el-option - v-for="item in postOptions" - :key="item.id" - :label="item.name" - :value="item.id" - /> - </el-select> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="24"> - <el-form-item label="备注"> - <el-input v-model="formData.remark" type="textarea" placeholder="请输入内容" /> - </el-form-item> - </el-col> - </el-row> - </el-form> - <template #footer> - <div class="dialog-footer"> - <el-button type="primary" @click="submitForm">确 定</el-button> - <el-button @click="cancel">取 消</el-button> - </div> - </template> - </el-dialog> -</template> -<script lang="ts" setup> -import { PostVO } from '@/api/system/post' -import { createUserApi, updateUserApi } from '@/api/system/user' -import { DICT_TYPE, getDictOptions } from '@/utils/dict' -import { defaultProps } from '@/utils/tree' -import { ElForm, FormItemRule } from 'element-plus' -import { Arrayable } from 'element-plus/es/utils' - -type Form = InstanceType<typeof ElForm> -interface Props { - deptOptions?: Tree[] - postOptions?: PostVO[] //岗位列表 - modelValue: boolean - formInitValue?: Recordable & Partial<typeof initParams> -} - -const props = withDefaults(defineProps<Props>(), { - deptOptions: () => [], - postOptions: () => [], - modelValue: false, - formInitValue: () => ({}) -}) -const emits = defineEmits(['update:modelValue', 'success']) - -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 -// 弹出层标题 -const title = computed(() => { - return formData.value?.id ? '修改用户' : '添加用户' -}) - -// 性别字典 -const sexDictDatas = getDictOptions(DICT_TYPE.SYSTEM_USER_SEX) - -// 表单初始化参数 -const initParams = { - nickname: '', - deptId: '', - mobile: '', - email: '', - id: undefined, - username: '', - password: '', - sex: 1, - postIds: [], - remark: '', - status: '0', - roleIds: [] -} - -// 校验规则 -const rules = { - username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }], - nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }], - password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }], - email: [ - { - type: 'email', - message: "'请输入正确的邮箱地址", - trigger: ['blur', 'change'] - } - ], - mobile: [ - { - pattern: /^(?:(?:\+|00)86)?1(?:3[\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\d]|9[189])\d{8}$/, - message: '请输入正确的手机号码', - trigger: 'blur' - } - ] -} as Partial<Record<string, Arrayable<FormItemRule>>> -const formRef = ref<Form | null>() -const formData = ref<Recordable>({ ...initParams }) -watch( - () => props.formInitValue, - (val) => { - formData.value = { ...val } - }, - { deep: true } -) - -const resetForm = () => { - let form = formRef?.value - if (!form) return - formData.value = { ...initParams } - form && (form as Form).resetFields() -} -const closeDialog = () => { - emits('update:modelValue', false) -} -// 操作成功 -const operateOk = () => { - emits('success', true) - closeDialog() -} -const submitForm = () => { - let form = formRef.value as Form - form.validate(async (valid) => { - let data = formData.value - if (valid) { - try { - if (data?.id !== undefined) { - await updateUserApi(data) - message.success(t('common.updateSuccess')) - operateOk() - } else { - await createUserApi(data) - message.success(t('common.createSuccess')) - operateOk() - } - } catch (err) { - console.error(err) - } - } - }) -} -const cancel = () => { - closeDialog() -} - -defineExpose({ - resetForm -}) -</script> +<template> + <!-- 添加或修改参数配置对话框 --> + <Dialog :title="title" :modelValue="showDialog" width="600px" append-to-body @close="closeDialog"> + <el-form ref="formRef" :model="formData" :rules="rules" label-width="80px"> + <el-row> + <el-col :span="12"> + <el-form-item label="用户昵称" prop="nickname"> + <el-input v-model="formData.nickname" placeholder="请输入用户昵称" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="归属部门" prop="deptId"> + <el-tree-select + node-key="id" + v-model="formData.deptId" + :data="deptOptions" + :props="defaultProps" + check-strictly + placeholder="请选择归属部门" + /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="手机号码" prop="mobile"> + <el-input v-model="formData.mobile" placeholder="请输入手机号码" maxlength="11" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="邮箱" prop="email"> + <el-input v-model="formData.email" placeholder="请输入邮箱" maxlength="50" /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item v-if="formData.id === undefined" label="用户名称" prop="username"> + <el-input v-model="formData.username" placeholder="请输入用户名称" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item v-if="formData.id === undefined" label="用户密码" prop="password"> + <el-input + v-model="formData.password" + placeholder="请输入用户密码" + type="password" + show-password + /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="用户性别"> + <el-select v-model="formData.sex" placeholder="请选择"> + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)" + :key="dict.value as number" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="岗位"> + <el-select v-model="formData.postIds" multiple placeholder="请选择"> + <el-option + v-for="item in postOptions" + :key="item.id" + :label="item.name" + :value="item.id as number" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="24"> + <el-form-item label="备注"> + <el-input v-model="formData.remark" type="textarea" placeholder="请输入内容" /> + </el-form-item> + </el-col> + </el-row> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script lang="ts" setup> +import { PostVO } from '@/api/system/post' +import * as PostApi from '@/api/system/post' +import { createUserApi, getUserApi, updateUserApi } from '@/api/system/user' +import * as DeptApi from '@/api/system/dept' + +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { defaultProps, handleTree } from '@/utils/tree' +import { ElForm, FormItemRule } from 'element-plus' +import { Arrayable } from 'element-plus/es/utils' +import { UserVO } from '@/api/login/types' + +type Form = InstanceType<typeof ElForm> + +const emits = defineEmits(['success']) + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const showDialog = ref(false) +// 弹出层标题 +const title = computed(() => { + return formData.value?.id ? '修改用户' : '添加用户' +}) + +const deptOptions = ref<Tree[]>([]) // 树形结构 +const getTree = async () => { + const res = await DeptApi.getSimpleDeptList() + deptOptions.value = [] + deptOptions.value.push(...handleTree(res)) +} +// 获取岗位列表 +const postOptions = ref<PostVO[]>([]) //岗位列表 +const getPostOptions = async () => { + const res = (await PostApi.getSimplePostList()) as PostVO[] + postOptions.value.push(...res) +} + +// 表单初始化参数 +const initParams = { + nickname: '', + deptId: '', + mobile: '', + email: '', + id: undefined, + username: '', + password: '', + sex: 1, + postIds: [], + remark: '', + status: '0', + roleIds: [] +} + +// 校验规则 +const rules = { + username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }], + nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }], + password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }], + email: [ + { + type: 'email', + message: '请输入正确的邮箱地址', + trigger: ['blur', 'change'] + } + ], + mobile: [ + { + pattern: /^(?:(?:\+|00)86)?1(?:3[\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\d]|9[189])\d{8}$/, + message: '请输入正确的手机号码', + trigger: 'blur' + } + ] +} as Partial<Record<string, Arrayable<FormItemRule>>> +const formRef = ref<Form | null>() +const formData = ref<Recordable>({ ...initParams }) + +const resetForm = () => { + let form = formRef?.value + if (!form) return + formData.value = { ...initParams } + form && (form as Form).resetFields() +} +const closeDialog = () => { + showDialog.value = false +} +// 操作成功 +const operateOk = () => { + emits('success', true) + closeDialog() +} +const submitForm = () => { + let form = formRef.value as Form + form.validate(async (valid) => { + let data = formData.value + if (valid) { + try { + if (data?.id !== undefined) { + await updateUserApi(data) + message.success(t('common.updateSuccess')) + operateOk() + } else { + await createUserApi(data) + message.success(t('common.createSuccess')) + operateOk() + } + } catch (err) { + console.error(err) + } + } + }) +} +/* 取消 */ +const cancel = () => { + closeDialog() +} + +/* 打开弹框 */ +const openForm = (row: undefined | UserVO) => { + resetForm() + getTree() // 部门树 + if (row && row.id) { + const id = row.id + getUserApi(id).then((response) => { + formData.value = response + }) + } else { + formData.value = { ...initParams } + } + + showDialog.value = true +} + +// ========== 初始化 ========== +onMounted(async () => { + await getPostOptions() +}) + +defineExpose({ + resetForm, + openForm +}) +</script> diff --git a/src/views/system/user/ImportForm.vue b/src/views/system/user/components/UserImportForm.vue similarity index 89% rename from src/views/system/user/ImportForm.vue rename to src/views/system/user/components/UserImportForm.vue index 4bfa4631..f63936e2 100644 --- a/src/views/system/user/ImportForm.vue +++ b/src/views/system/user/components/UserImportForm.vue @@ -1,153 +1,154 @@ -<template> - <el-dialog - :title="upload.title" - :modelValue="modelValue" - width="400px" - append-to-body - @close="closeDialog" - > - <el-upload - ref="uploadRef" - accept=".xlsx, .xls" - :limit="1" - :headers="upload.headers" - :action="upload.url + '?updateSupport=' + upload.updateSupport" - :disabled="upload.isUploading" - :on-progress="handleFileUploadProgress" - :on-success="handleFileSuccess" - :on-exceed="handleExceed" - :on-error="excelUploadError" - :auto-upload="false" - drag - > - <Icon icon="ep:upload" /> - <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> - <template #tip> - <div class="el-upload__tip text-center"> - <div class="el-upload__tip"> - <el-checkbox v-model="upload.updateSupport" /> 是否更新已经存在的用户数据 - </div> - - <span>仅允许导入xls、xlsx格式文件。</span> - <el-link - type="primary" - :underline="false" - style="font-size: 12px; vertical-align: baseline" - @click="importTemplate" - >下载模板</el-link - > - </div> - </template> - </el-upload> - <template #footer> - <div class="dialog-footer"> - <el-button type="primary" @click="submitFileForm">确 定</el-button> - <el-button @click="cancel">取 消</el-button> - </div> - </template> - </el-dialog> -</template> - -<script lang="ts" setup> -import { importUserTemplateApi } from '@/api/system/user' -import { getAccessToken, getTenantId } from '@/utils/auth' -import download from '@/utils/download' - -interface Props { - modelValue: boolean -} - -// const props = -withDefaults(defineProps<Props>(), { - modelValue: false -}) - -const emits = defineEmits(['update:modelValue', 'success']) - -const message = useMessage() // 消息弹窗 - -const uploadRef = ref() - -// 用户导入参数 -const upload = reactive({ - // // 是否显示弹出层(用户导入) - // open: false, - // 弹出层标题(用户导入) - title: '用户导入', - // 是否禁用上传 - isUploading: false, - // 是否更新已经存在的用户数据 - updateSupport: 0, - // 设置上传的请求头部 - headers: { - Authorization: 'Bearer ' + getAccessToken(), - 'tenant-id': getTenantId() - }, - // 上传的地址 - url: import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import' -}) - -// 文件上传中处理 -const handleFileUploadProgress = () => { - upload.isUploading = true -} -// 文件上传成功处理 -const handleFileSuccess = (response: any) => { - if (response.code !== 0) { - message.error(response.msg) - return - } - upload.isUploading = false - uploadRef.value?.clearFiles() - // 拼接提示语 - const data = response.data - let text = '上传成功数量:' + data.createUsernames.length + ';' - for (let username of data.createUsernames) { - text += '< ' + username + ' >' - } - text += '更新成功数量:' + data.updateUsernames.length + ';' - for (const username of data.updateUsernames) { - text += '< ' + username + ' >' - } - text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';' - for (const username in data.failureUsernames) { - text += '< ' + username + ': ' + data.failureUsernames[username] + ' >' - } - message.alert(text) - emits('success') - closeDialog() -} - -// 文件数超出提示 -const handleExceed = (): void => { - message.error('最多只能上传一个文件!') -} -// 上传错误提示 -const excelUploadError = (): void => { - message.error('导入数据失败,请您重新上传!') -} - -/** 下载模板操作 */ -const importTemplate = async () => { - try { - const res = await importUserTemplateApi() - download.excel(res, '用户导入模版.xls') - } catch (error) { - console.error(error) - } -} - -/* 弹框按钮操作 */ -// 点击取消 -const cancel = () => { - closeDialog() -} -// 关闭弹窗 -const closeDialog = () => { - emits('update:modelValue', false) -} -// 提交上传文件 -const submitFileForm = () => { - uploadRef.value?.submit() -} -</script> +<template> + <Dialog + :title="upload.title" + :modelValue="showDialog" + width="400px" + append-to-body + @close="closeDialog" + > + <el-upload + ref="uploadRef" + accept=".xlsx, .xls" + :limit="1" + :headers="upload.headers" + :action="upload.url + '?updateSupport=' + upload.updateSupport" + :disabled="upload.isUploading" + :on-progress="handleFileUploadProgress" + :on-success="handleFileSuccess" + :on-exceed="handleExceed" + :on-error="excelUploadError" + :auto-upload="false" + drag + > + <Icon icon="ep:upload" /> + <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> + <template #tip> + <div class="el-upload__tip text-center"> + <div class="el-upload__tip"> + <el-checkbox v-model="upload.updateSupport" /> 是否更新已经存在的用户数据 + </div> + + <span>仅允许导入xls、xlsx格式文件。</span> + <el-link + type="primary" + :underline="false" + style="font-size: 12px; vertical-align: baseline" + @click="importTemplate" + >下载模板</el-link + > + </div> + </template> + </el-upload> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submitFileForm">确 定</el-button> + <el-button @click="cancel">取 消</el-button> + </div> + </template> + </Dialog> +</template> + +<script lang="ts" setup> +import { importUserTemplateApi } from '@/api/system/user' +import { getAccessToken, getTenantId } from '@/utils/auth' +import download from '@/utils/download' + +const emits = defineEmits(['success']) + +const message = useMessage() // 消息弹窗 + +const showDialog = ref(false) +const uploadRef = ref() + +// 用户导入参数 +const upload = reactive({ + // // 是否显示弹出层(用户导入) + // open: false, + // 弹出层标题(用户导入) + title: '用户导入', + // 是否禁用上传 + isUploading: false, + // 是否更新已经存在的用户数据 + updateSupport: 0, + // 设置上传的请求头部 + headers: { + Authorization: 'Bearer ' + getAccessToken(), + 'tenant-id': getTenantId() + }, + // 上传的地址 + url: import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import' +}) + +// 文件上传中处理 +const handleFileUploadProgress = () => { + upload.isUploading = true +} +// 文件上传成功处理 +const handleFileSuccess = (response: any) => { + if (response.code !== 0) { + message.error(response.msg) + return + } + upload.isUploading = false + uploadRef.value?.clearFiles() + // 拼接提示语 + const data = response.data + let text = '上传成功数量:' + data.createUsernames.length + ';' + for (let username of data.createUsernames) { + text += '< ' + username + ' >' + } + text += '更新成功数量:' + data.updateUsernames.length + ';' + for (const username of data.updateUsernames) { + text += '< ' + username + ' >' + } + text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';' + for (const username in data.failureUsernames) { + text += '< ' + username + ': ' + data.failureUsernames[username] + ' >' + } + message.alert(text) + emits('success') + closeDialog() +} + +// 文件数超出提示 +const handleExceed = (): void => { + message.error('最多只能上传一个文件!') +} +// 上传错误提示 +const excelUploadError = (e): void => { + console.log(e) + message.error('导入数据失败,请您重新上传!') +} + +/** 下载模板操作 */ +const importTemplate = async () => { + try { + const res = await importUserTemplateApi() + download.excel(res, '用户导入模版.xls') + } catch (error) { + console.error(error) + } +} + +/* 弹框按钮操作 */ +// 点击取消 +const cancel = () => { + closeDialog() +} +// 关闭弹窗 +const closeDialog = () => { + showDialog.value = false +} +// 提交上传文件 +const submitFileForm = () => { + uploadRef.value?.submit() +} + +const openForm = () => { + uploadRef.value?.clearFiles() + showDialog.value = true +} +defineExpose({ + openForm +}) +</script> diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index 5b286cc7..1c36d376 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -5,31 +5,7 @@ <el-row :gutter="20"> <!--部门数据--> <el-col :span="4" :xs="24"> - <div class="head-container"> - <el-input - v-model="deptName" - placeholder="请输入部门名称" - clearable - style="margin-bottom: 20px" - > - <template #prefix> - <Icon icon="ep:search" /> - </template> - </el-input> - </div> - <div class="head-container"> - <el-tree - :data="deptOptions" - :props="defaultProps" - :expand-on-click-node="false" - :filter-node-method="filterNode" - ref="treeRef" - node-key="id" - default-expand-all - highlight-current - @node-click="handleDeptNodeClick" - /> - </div> + <UserDeptTree @node-click="handleDeptNodeClick" /> </el-col> <!--用户数据--> <el-col :span="20" :xs="24"> @@ -66,10 +42,10 @@ style="width: 240px" > <el-option - v-for="dict in statusDictDatas" - :key="parseInt(dict.value)" + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value as number" :label="dict.label" - :value="parseInt(dict.value)" + :value="dict.value as number" /> </el-select> </el-form-item> @@ -244,51 +220,34 @@ </el-row> </content-wrap> <!-- 添加或修改用户对话框 --> - <AddForm - ref="addEditFormRef" - v-model="showAddDialog" - :dept-options="deptOptions" - :post-options="postOptions" - :form-init-value="addFormInitValue" - @success="getList" - /> + <UserForm ref="userFormRef" @success="getList" /> <!-- 用户导入对话框 --> - <ImportForm v-model="importDialogVisible" @success="getList" /> + <UserImportForm ref="userImportFormRef" @success="getList" /> <!-- 分配角色 --> - <RoleForm - ref="roleFormRef" - v-model:show="roleDialogVisible" - :role-options="roleOptions" - :form-init-value="userRole" - @success="getList" - /> + <UserAssignRoleForm ref="userAssignRoleFormRef" @success="getList" /> </div> </template> <script setup lang="ts" name="User"> -import type { ElTree } from 'element-plus' -import { handleTree, defaultProps } from '@/utils/tree' -// 原vue3版本api方法都是Api结尾觉得见名知义,个人觉得这个可以形成规范 -// TODO 使用 DeptApi 这种形式哈 -import { getSimpleDeptList as getSimpleDeptListApi } from '@/api/system/dept' -import { getSimplePostList as getSimplePostListApi, PostVO } from '@/api/system/post' -import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import download from '@/utils/download' +import { parseTime } from '@/utils/formatTime' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { CommonStatusEnum } from '@/utils/constants' + import { deleteUserApi, exportUserApi, resetUserPwdApi, updateUserStatusApi, + getUserPageApi, UserVO } from '@/api/system/user' -import { parseTime } from './utils' // TODO 可以使用 formatTime 里的方法 -import AddForm from './AddForm.vue' // TODO 改成 UserForm -import ImportForm from './ImportForm.vue' // TODO 改成 UserImportForm -import RoleForm from './RoleForm.vue' // TODO 改成 UserAssignRoleForm -import { getUserApi, getUserPageApi } from '@/api/system/user' -import { getSimpleRoleList as getSimpleRoleListApi } from '@/api/system/role' -import { listUserRolesApi } from '@/api/system/permission' -import { CommonStatusEnum } from '@/utils/constants' -import download from '@/utils/download' + +import UserForm from './components/UserForm.vue' +import UserImportForm from './components/UserImportForm.vue' +import UserAssignRoleForm from './components/UserAssignRoleForm.vue' +import UserDeptTree from './components/UserDeptTree.vue' + const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -302,42 +261,12 @@ const queryParams = reactive({ createTime: [] }) const showSearch = ref(true) -const showAddDialog = ref(false) -// 数据字典- // TODO 可以直接 vue 那 getIntDictOptions,这样一方面少个变量,也可以 getIntDictOptions -const statusDictDatas = getDictOptions(DICT_TYPE.COMMON_STATUS) - -// ========== 创建部门树结构 ========== -// TODO 要不把部门树拆成一个左侧的组件,然后点击后触发 handleDeptNodeClick -const deptName = ref('') -watch( - () => deptName.value, - (val) => { - treeRef.value?.filter(val) - } -) -const deptOptions = ref<Tree[]>([]) // 树形结构 -const treeRef = ref<InstanceType<typeof ElTree>>() -const getTree = async () => { - const res = await getSimpleDeptListApi() - deptOptions.value = [] - deptOptions.value.push(...handleTree(res)) -} -const filterNode = (value: string, data: Tree) => { - if (!value) return true - return data.name.includes(value) -} const handleDeptNodeClick = async (row: { [key: string]: any }) => { queryParams.deptId = row.id getList() } -// 获取岗位列表 -const postOptions = ref<PostVO[]>([]) //岗位列表 -const getPostOptions = async () => { - const res = await getSimplePostListApi() - postOptions.value.push(...res) -} // 用户列表 const userList = ref<UserVO[]>([]) const loading = ref(false) @@ -374,37 +303,30 @@ const resetQuery = () => { } // 添加或编辑 -const addEditFormRef = ref() +const userFormRef = ref() // 添加用户 -// TODO 可以参考别的模块哈,openForm;然后 tree 和 position 可以里面在加载下,让组件自己维护自己哈。 const handleAdd = () => { - addEditFormRef?.value.resetForm() - // 获得下拉数据 - getTree() - // 打开表单,并设置初始化 - showAddDialog.value = true + userFormRef.value?.openForm() } // 用户导入 +const userImportFormRef = ref() const handleImport = () => { - importDialogVisible.value = true + userImportFormRef.value?.openForm() } // 用户导出 -// TODO 改成 await 的风格; const exportLoading = ref(false) const handleExport = () => { message .confirm('是否确认导出所有用户数据项?') - .then(() => { + .then(async () => { // 处理查询参数 let params = { ...queryParams } params.pageNo = 1 params.pageSize = 99999 exportLoading.value = true - return exportUserApi(params) - }) - .then((response) => { + const response = await exportUserApi(params) download.excel(response, '用户数据.xls') }) .catch(() => {}) @@ -435,17 +357,14 @@ const handleCommand = (command: string, index: number, row: UserVO) => { } // 用户状态修改 -// TODO 改成 await 的风格; const handleStatusChange = (row: UserVO) => { let text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' message .confirm('确认要"' + text + '""' + row.username + '"用户吗?', t('common.reminder')) - .then(function () { + .then(async () => { row.status = row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.ENABLE : CommonStatusEnum.DISABLE - return updateUserStatusApi(row.id, row.status) - }) - .then(() => { + await updateUserStatusApi(row.id, row.status) message.success(text + '成功') // 刷新列表 getList() @@ -457,126 +376,47 @@ const handleStatusChange = (row: UserVO) => { } // 具体数据单行操作 -const addFormInitValue = ref<Recordable>({}) /** 修改按钮操作 */ const handleUpdate = (row: UserVO) => { - addEditFormRef.value?.resetForm() - getTree() - const id = row.id - getUserApi(id).then((response) => { - addFormInitValue.value = response - showAddDialog.value = true - }) + userFormRef.value?.openForm(row) } // 删除用户 -// TODO 改成 await 的风格; const handleDelete = (row: UserVO) => { const ids = row.id message .confirm('是否确认删除用户编号为"' + ids + '"的数据项?') - .then(() => { - return deleteUserApi(ids) - }) - .then(() => { - getList() + .then(async () => { + await deleteUserApi(ids) message.success('删除成功') + getList() + }) + .catch((e) => { + console.error(e) }) - .catch(() => {}) } // 重置密码 -// TODO 改成 await 的风格; const handleResetPwd = (row: UserVO) => { - message.prompt('请输入"' + row.username + '"的新密码', t('common.reminder')).then(({ value }) => { - resetUserPwdApi(row.id, value) - .then(() => { - message.success('修改成功,新密码是:' + value) - }) - .catch((e) => { - console.error(e) - }) - }) + message + .prompt('请输入"' + row.username + '"的新密码', t('common.reminder')) + .then(async ({ value }) => { + await resetUserPwdApi(row.id, value) + message.success('修改成功,新密码是:' + value) + }) + .catch((e) => { + console.error(e) + }) } // 分配角色 -const roleDialogVisible = ref(false) -const roleOptions = ref() -const userRole = reactive({ - id: 0, - username: '', - nickname: '', - roleIds: [] -}) -const handleRole = async (row: UserVO) => { - addEditFormRef.value?.resetForm() - userRole.id = row.id - userRole.username = row.username - userRole.nickname = row.nickname - - // 获得角色列表 - const roleOpt = await getSimpleRoleListApi() - roleOptions.value = [...roleOpt] - - // 获得角色拥有的菜单集合 - const roles = await listUserRolesApi(row.id) - userRole.roleIds = roles - - roleDialogVisible.value = true +const userAssignRoleFormRef = ref() +const handleRole = (row: UserVO) => { + userAssignRoleFormRef.value?.openForm(row) } -/* 用户导入 */ -const importDialogVisible = ref(false) - // ========== 初始化 ========== onMounted(async () => { getList() - await getPostOptions() - await getTree() }) - -const parseTime = (time) => { - if (!time) { - return null - } - const format = '{y}-{m}-{d} {h}:{i}:{s}' - let date - if (typeof time === 'object') { - date = time - } else { - if (typeof time === 'string' && /^[0-9]+$/.test(time)) { - time = parseInt(time) - } else if (typeof time === 'string') { - time = time - .replace(new RegExp(/-/gm), '/') - .replace('T', ' ') - .replace(new RegExp(/\.[\d]{3}/gm), '') - } - if (typeof time === 'number' && time.toString().length === 10) { - time = time * 1000 - } - date = new Date(time) - } - const formatObj = { - y: date.getFullYear(), - m: date.getMonth() + 1, - d: date.getDate(), - h: date.getHours(), - i: date.getMinutes(), - s: date.getSeconds(), - a: date.getDay() - } - const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { - let value = formatObj[key] - // Note: getDay() returns 0 on Sunday - if (key === 'a') { - return ['日', '一', '二', '三', '四', '五', '六'][value] - } - if (result.length > 0 && value < 10) { - value = '0' + value - } - return value || 0 - }) - return time_str -} </script> diff --git a/src/views/system/user/index0.vue b/src/views/system/user/index0.vue deleted file mode 100644 index 2f9ba9b0..00000000 --- a/src/views/system/user/index0.vue +++ /dev/null @@ -1,576 +0,0 @@ -<template> - <div class="flex"> - <el-card class="w-1/5 user" :gutter="12" shadow="always"> - <template #header> - <div class="card-header"> - <span>部门列表</span> - <XTextButton title="修改部门" @click="handleDeptEdit()" /> - </div> - </template> - <el-input v-model="filterText" placeholder="搜索部门" /> - <el-scrollbar height="650"> - <el-tree - ref="treeRef" - node-key="id" - default-expand-all - :data="deptOptions" - :props="defaultProps" - :highlight-current="true" - :filter-node-method="filterNode" - :expand-on-click-node="false" - @node-click="handleDeptNodeClick" - /> - </el-scrollbar> - </el-card> - <el-card class="w-4/5 user" style="margin-left: 10px" :gutter="12" shadow="hover"> - <template #header> - <div class="card-header"> - <span>{{ tableTitle }}</span> - </div> - </template> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <!-- 操作:新增 --> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - v-hasPermi="['system:user:create']" - @click="handleCreate()" - /> - <!-- 操作:导入用户 --> - <XButton - type="warning" - preIcon="ep:upload" - :title="t('action.import')" - v-hasPermi="['system:user:import']" - @click="importDialogVisible = true" - /> - <!-- 操作:导出用户 --> - <XButton - type="warning" - preIcon="ep:download" - :title="t('action.export')" - v-hasPermi="['system:user:export']" - @click="exportList('用户数据.xls')" - /> - </template> - <template #status_default="{ row }"> - <el-switch - v-model="row.status" - :active-value="0" - :inactive-value="1" - @change="handleStatusChange(row)" - /> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:编辑 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['system:user:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['system:user:update']" - @click="handleDetail(row.id)" - /> - <el-dropdown - class="p-0.5" - v-hasPermi="[ - 'system:user:update-password', - 'system:permission:assign-user-role', - 'system:user:delete' - ]" - > - <XTextButton :title="t('action.more')" postIcon="ep:arrow-down" /> - <template #dropdown> - <el-dropdown-menu> - <el-dropdown-item> - <!-- 操作:重置密码 --> - <XTextButton - preIcon="ep:key" - title="重置密码" - v-hasPermi="['system:user:update-password']" - @click="handleResetPwd(row)" - /> - </el-dropdown-item> - <el-dropdown-item> - <!-- 操作:分配角色 --> - <XTextButton - preIcon="ep:key" - title="分配角色" - v-hasPermi="['system:permission:assign-user-role']" - @click="handleRole(row)" - /> - </el-dropdown-item> - <el-dropdown-item> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['system:user:delete']" - @click="deleteData(row.id)" - /> - </el-dropdown-item> - </el-dropdown-menu> - </template> - </el-dropdown> - </template> - </XTable> - </el-card> - </div> - <XModal v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(添加 / 修改) --> - <Form - v-if="['create', 'update'].includes(actionType)" - :rules="rules" - :schema="allSchemas.formSchema" - ref="formRef" - > - <template #deptId="form"> - <el-tree-select - node-key="id" - v-model="form['deptId']" - :props="defaultProps" - :data="deptOptions" - check-strictly - /> - </template> - <template #postIds="form"> - <el-select v-model="form['postIds']" multiple :placeholder="t('common.selectText')"> - <el-option - v-for="item in postOptions" - :key="item.id" - :label="item.name" - :value="(item.id as unknown as number)" - /> - </el-select> - </template> - </Form> - <!-- 对话框(详情) --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - > - <template #deptId="{ row }"> - <el-tag>{{ dataFormater(row.deptId) }}</el-tag> - </template> - <template #postIds="{ row }"> - <template v-if="row.postIds !== ''"> - <el-tag v-for="(post, index) in row.postIds" :key="index" index=""> - <template v-for="postObj in postOptions"> - {{ post === postObj.id ? postObj.name : '' }} - </template> - </el-tag> - </template> - <template v-else> </template> - </template> - </Descriptions> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="loading" - @click="submitForm()" - /> - <!-- 按钮:关闭 --> - <XButton :loading="loading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> - <!-- 分配用户角色 --> - <XModal v-model="roleDialogVisible" title="分配角色"> - <el-form :model="userRole" label-width="140px" :inline="true"> - <el-form-item label="用户名称"> - <el-tag>{{ userRole.username }}</el-tag> - </el-form-item> - <el-form-item label="用户昵称"> - <el-tag>{{ userRole.nickname }}</el-tag> - </el-form-item> - <el-form-item label="角色"> - <el-transfer - v-model="userRole.roleIds" - :titles="['角色列表', '已选择']" - :props="{ - key: 'id', - label: 'name' - }" - :data="roleOptions" - /> - </el-form-item> - </el-form> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton type="primary" :title="t('action.save')" :loading="loading" @click="submitRole()" /> - <!-- 按钮:关闭 --> - <XButton :title="t('dialog.close')" @click="roleDialogVisible = false" /> - </template> - </XModal> - <!-- 导入 --> - <XModal v-model="importDialogVisible" :title="importDialogTitle"> - <el-form class="drawer-multiColumn-form" label-width="150px"> - <el-form-item label="模板下载 :"> - <XButton type="primary" prefix="ep:download" title="点击下载" @click="handleImportTemp()" /> - </el-form-item> - <el-form-item label="文件上传 :"> - <el-upload - ref="uploadRef" - :action="updateUrl + '?updateSupport=' + updateSupport" - :headers="uploadHeaders" - :drag="true" - :limit="1" - :multiple="true" - :show-file-list="true" - :disabled="uploadDisabled" - :before-upload="beforeExcelUpload" - :on-exceed="handleExceed" - :on-success="handleFileSuccess" - :on-error="excelUploadError" - :auto-upload="false" - accept="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" - > - <Icon icon="ep:upload-filled" /> - <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> - <template #tip> - <div class="el-upload__tip">请上传 .xls , .xlsx 标准格式文件</div> - </template> - </el-upload> - </el-form-item> - <el-form-item label="是否更新已经存在的用户数据:"> - <el-checkbox v-model="updateSupport" /> - </el-form-item> - </el-form> - <template #footer> - <!-- 按钮:保存 --> - <XButton - type="warning" - preIcon="ep:upload-filled" - :title="t('action.save')" - @click="submitFileForm()" - /> - <!-- 按钮:关闭 --> - <XButton :title="t('dialog.close')" @click="importDialogVisible = false" /> - </template> - </XModal> -</template> -<script setup lang="ts" name="User"> -import type { ElTree, UploadRawFile, UploadInstance } from 'element-plus' -import { handleTree, defaultProps } from '@/utils/tree' -import download from '@/utils/download' -import { CommonStatusEnum } from '@/utils/constants' -import { getAccessToken, getTenantId } from '@/utils/auth' -import type { FormExpose } from '@/components/Form' -import { rules, allSchemas } from './user.data' -import * as UserApi from '@/api/system/user' -import { listSimpleDeptApi } from '@/api/system/dept' -import { listSimpleRolesApi } from '@/api/system/role' -import { listSimplePostsApi, PostVO } from '@/api/system/post' -import { - aassignUserRoleApi, - listUserRolesApi, - PermissionAssignUserRoleReqVO -} from '@/api/system/permission' - -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 - -const queryParams = reactive({ - deptId: null -}) -// ========== 列表相关 ========== -const tableTitle = ref('用户列表') -// 列表相关的变量 -const [registerTable, { reload, deleteData, exportList }] = useXTable({ - allSchemas: allSchemas, - params: queryParams, - getListApi: UserApi.getUserPageApi, - deleteApi: UserApi.deleteUserApi, - exportListApi: UserApi.exportUserApi -}) -// ========== 创建部门树结构 ========== -const filterText = ref('') -const deptOptions = ref<Tree[]>([]) // 树形结构 -const treeRef = ref<InstanceType<typeof ElTree>>() -const getTree = async () => { - const res = await listSimpleDeptApi() - deptOptions.value.push(...handleTree(res)) -} -const filterNode = (value: string, data: Tree) => { - if (!value) return true - return data.name.includes(value) -} -const handleDeptNodeClick = async (row: { [key: string]: any }) => { - queryParams.deptId = row.id - await reload() -} -const { push } = useRouter() -const handleDeptEdit = () => { - push('/system/dept') -} -watch(filterText, (val) => { - treeRef.value!.filter(val) -}) -// ========== CRUD 相关 ========== -const loading = ref(false) // 遮罩层 -const actionType = ref('') // 操作按钮的类型 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const formRef = ref<FormExpose>() // 表单 Ref -const postOptions = ref<PostVO[]>([]) //岗位列表 - -// 获取岗位列表 -const getPostOptions = async () => { - const res = await listSimplePostsApi() - postOptions.value.push(...res) -} -const dataFormater = (val) => { - return deptFormater(deptOptions.value, val) -} -//部门回显 -const deptFormater = (ary, val: any) => { - var o = '' - if (ary && val) { - for (const v of ary) { - if (v.id == val) { - o = v.name - if (o) return o - } else if (v.children?.length) { - o = deptFormater(v.children, val) - if (o) return o - } - } - return o - } else { - return val - } -} - -// 设置标题 -const setDialogTile = async (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true -} - -// 新增操作 -const handleCreate = async () => { - setDialogTile('create') - // 重置表单 - await nextTick() - if (allSchemas.formSchema[0].field !== 'username') { - unref(formRef)?.addSchema( - { - field: 'username', - label: '用户账号', - component: 'Input' - }, - 0 - ) - unref(formRef)?.addSchema( - { - field: 'password', - label: '用户密码', - component: 'InputPassword' - }, - 1 - ) - } -} - -// 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - await nextTick() - unref(formRef)?.delSchema('username') - unref(formRef)?.delSchema('password') - // 设置数据 - const res = await UserApi.getUserApi(rowId) - unref(formRef)?.setValues(res) -} -const detailData = ref() - -// 详情操作 -const handleDetail = async (rowId: number) => { - // 设置数据 - const res = await UserApi.getUserApi(rowId) - detailData.value = res - await setDialogTile('detail') -} - -// 提交按钮 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - // 提交请求 - try { - const data = unref(formRef)?.formModel as UserApi.UserVO - if (actionType.value === 'create') { - loading.value = true - await UserApi.createUserApi(data) - message.success(t('common.createSuccess')) - } else { - loading.value = true - await UserApi.updateUserApi(data) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false - } finally { - // unref(formRef)?.setSchema(allSchemas.formSchema) - // 刷新列表 - await reload() - loading.value = false - } - } - }) -} -// 改变用户状态操作 -const handleStatusChange = async (row: UserApi.UserVO) => { - const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' - message - .confirm('确认要"' + text + '""' + row.username + '"用户吗?', t('common.reminder')) - .then(async () => { - row.status = - row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.ENABLE : CommonStatusEnum.DISABLE - await UserApi.updateUserStatusApi(row.id, row.status) - message.success(text + '成功') - // 刷新列表 - await reload() - }) - .catch(() => { - row.status = - row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE - }) -} -// 重置密码 -const handleResetPwd = (row: UserApi.UserVO) => { - message.prompt('请输入"' + row.username + '"的新密码', t('common.reminder')).then(({ value }) => { - UserApi.resetUserPwdApi(row.id, value).then(() => { - message.success('修改成功,新密码是:' + value) - }) - }) -} -// 分配角色 -const roleDialogVisible = ref(false) -const roleOptions = ref() -const userRole = reactive({ - id: 0, - username: '', - nickname: '', - roleIds: [] -}) -const handleRole = async (row: UserApi.UserVO) => { - userRole.id = row.id - userRole.username = row.username - userRole.nickname = row.nickname - // 获得角色拥有的权限集合 - const roles = await listUserRolesApi(row.id) - userRole.roleIds = roles - // 获取角色列表 - const roleOpt = await listSimpleRolesApi() - roleOptions.value = roleOpt - roleDialogVisible.value = true -} -// 提交 -const submitRole = async () => { - const data = ref<PermissionAssignUserRoleReqVO>({ - userId: userRole.id, - roleIds: userRole.roleIds - }) - await aassignUserRoleApi(data.value) - message.success(t('common.updateSuccess')) - roleDialogVisible.value = false -} -// ========== 导入相关 ========== -// TODO @星语:这个要不要把导入用户,封装成一个小组件?可选哈 -const importDialogVisible = ref(false) -const uploadDisabled = ref(false) -const importDialogTitle = ref('用户导入') -const updateSupport = ref(0) -let updateUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import' -const uploadHeaders = ref() -// 下载导入模版 -const handleImportTemp = async () => { - const res = await UserApi.importUserTemplateApi() - download.excel(res, '用户导入模版.xls') -} -// 文件上传之前判断 -const beforeExcelUpload = (file: UploadRawFile) => { - const isExcel = - file.type === 'application/vnd.ms-excel' || - file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' - const isLt5M = file.size / 1024 / 1024 < 5 - if (!isExcel) message.error('上传文件只能是 xls / xlsx 格式!') - if (!isLt5M) message.error('上传文件大小不能超过 5MB!') - return isExcel && isLt5M -} -// 文件上传 -const uploadRef = ref<UploadInstance>() -const submitFileForm = () => { - uploadHeaders.value = { - Authorization: 'Bearer ' + getAccessToken(), - 'tenant-id': getTenantId() - } - uploadDisabled.value = true - uploadRef.value!.submit() -} -// 文件上传成功 -const handleFileSuccess = async (response: any): Promise<void> => { - if (response.code !== 0) { - message.error(response.msg) - return - } - importDialogVisible.value = false - uploadDisabled.value = false - const data = response.data - let text = '上传成功数量:' + data.createUsernames.length + ';' - for (let username of data.createUsernames) { - text += '< ' + username + ' >' - } - text += '更新成功数量:' + data.updateUsernames.length + ';' - for (const username of data.updateUsernames) { - text += '< ' + username + ' >' - } - text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';' - for (const username in data.failureUsernames) { - text += '< ' + username + ': ' + data.failureUsernames[username] + ' >' - } - message.alert(text) - await reload() -} -// 文件数超出提示 -const handleExceed = (): void => { - message.error('最多只能上传一个文件!') -} -// 上传错误提示 -const excelUploadError = (): void => { - message.error('导入数据失败,请您重新上传!') -} -// ========== 初始化 ========== -onMounted(async () => { - await getPostOptions() - await getTree() -}) -</script> - -<style scoped> -.user { - height: 780px; - max-height: 800px; -} -.card-header { - display: flex; - justify-content: space-between; - align-items: center; -} -</style> diff --git a/src/views/system/user/utils.ts b/src/views/system/user/utils.ts deleted file mode 100644 index 6473c2c9..00000000 --- a/src/views/system/user/utils.ts +++ /dev/null @@ -1,44 +0,0 @@ -export const parseTime = (time) => { - if (!time) { - return null - } - const format = '{y}-{m}-{d} {h}:{i}:{s}' - let date - if (typeof time === 'object') { - date = time - } else { - if (typeof time === 'string' && /^[0-9]+$/.test(time)) { - time = parseInt(time) - } else if (typeof time === 'string') { - time = time - .replace(new RegExp(/-/gm), '/') - .replace('T', ' ') - .replace(new RegExp(/\.[\d]{3}/gm), '') - } - if (typeof time === 'number' && time.toString().length === 10) { - time = time * 1000 - } - date = new Date(time) - } - const formatObj = { - y: date.getFullYear(), - m: date.getMonth() + 1, - d: date.getDate(), - h: date.getHours(), - i: date.getMinutes(), - s: date.getSeconds(), - a: date.getDay() - } - const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { - let value = formatObj[key] - // Note: getDay() returns 0 on Sunday - if (key === 'a') { - return ['日', '一', '二', '三', '四', '五', '六'][value] - } - if (result.length > 0 && value < 10) { - value = '0' + value - } - return value || 0 - }) - return time_str -} From e6dac1ebda5e35bd57921b231364c0bb85211d65 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Wed, 29 Mar 2023 22:25:53 +0800 Subject: [PATCH 156/184] =?UTF-8?q?REVIEW=20=E4=BB=A3=E7=A0=81=E9=A2=84?= =?UTF-8?q?=E8=A7=88(PreviewCode)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/tree.ts | 10 ++-- src/views/infra/codegen/PreviewCode.vue | 74 +++++++++++++++++-------- src/views/infra/codegen/index.vue | 4 +- 3 files changed, 57 insertions(+), 31 deletions(-) diff --git a/src/utils/tree.ts b/src/utils/tree.ts index 8f1f92cc..20f97a38 100644 --- a/src/utils/tree.ts +++ b/src/utils/tree.ts @@ -276,7 +276,7 @@ export const handleTree = (data: any[], id?: string, parentId?: string, children export const handleTree2 = (data, id, parentId, children, rootId) => { id = id || 'id' parentId = parentId || 'parentId' - children = children || 'children' + // children = children || 'children' rootId = rootId || Math.min( @@ -285,16 +285,16 @@ export const handleTree2 = (data, id, parentId, children, rootId) => { }) ) || 0 - //对源数据深度克隆 + // 对源数据深度克隆 const cloneData = JSON.parse(JSON.stringify(data)) - //循环所有项 + // 循环所有项 const treeData = cloneData.filter((father) => { const branchArr = cloneData.filter((child) => { - //返回每一项的子级数组 + // 返回每一项的子级数组 return father[id] === child[parentId] }) branchArr.length > 0 ? (father.children = branchArr) : '' - //返回第一层 + // 返回第一层 return father[parentId] === rootId }) return treeData !== '' ? treeData : data diff --git a/src/views/infra/codegen/PreviewCode.vue b/src/views/infra/codegen/PreviewCode.vue index 82307d59..df521f5a 100644 --- a/src/views/infra/codegen/PreviewCode.vue +++ b/src/views/infra/codegen/PreviewCode.vue @@ -1,13 +1,20 @@ <template> <Dialog - :title="modelTitle" + title="代码预览" v-model="modelVisible" align-center - width="60%" + width="80%" class="app-infra-codegen-preview-container" > <div class="flex"> - <el-card class="w-1/4" :gutter="12" shadow="hover"> + <!-- 代码目录树 --> + <el-card + class="w-1/3" + :gutter="12" + shadow="hover" + v-loading="loading" + element-loading-text="生成文件目录中..." + > <el-scrollbar height="calc(100vh - 88px - 40px - 50px)"> <el-tree ref="treeRef" @@ -20,7 +27,14 @@ /> </el-scrollbar> </el-card> - <el-card class="w-3/4 ml-3" :gutter="12" shadow="hover"> + <!-- 代码 --> + <el-card + class="w-2/3 ml-3" + :gutter="12" + shadow="hover" + v-loading="loading" + element-loading-text="加载代码中..." + > <el-tabs v-model="preview.activeName"> <el-tab-pane v-for="item in previewCodegen" @@ -31,7 +45,9 @@ <el-button text type="primary" class="float-right" @click="copy(item.code)"> {{ t('common.copy') }} </el-button> - <pre>{{ item.code }}</pre> + <div v-highlight> + <code>{{ item.code }}</code> + </div> </el-tab-pane> </el-tabs> </el-card> @@ -42,33 +58,53 @@ import { useClipboard } from '@vueuse/core' import { handleTree2 } from '@/utils/tree' import * as CodegenApi from '@/api/infra/codegen' -import { CodegenPreviewVO } from '@/api/infra/codegen/types' - const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 const modelVisible = ref(false) // 弹窗的是否展示 -const modelTitle = ref('代码预览') // 弹窗的标题 -// ======== 显示页面 ======== +const loading = ref(false) // 加载中的状态 const preview = reactive({ - fileTree: [], - activeName: '' + fileTree: [], // 文件树 + activeName: '' // 激活的文件名 }) -const previewCodegen = ref<CodegenPreviewVO[]>() +const previewCodegen = ref<CodegenApi.CodegenPreviewVO[]>() +/** 点击文件 */ const handleNodeClick = async (data, node) => { if (node && !node.isLeaf) { return false } preview.activeName = data.id } + /** 生成 files 目录 **/ interface filesType { id: string label: string parentId: string } -const handleFiles = (datas: CodegenPreviewVO[]) => { + +/** 打开弹窗 */ +const open = async (id: number) => { + modelVisible.value = true + try { + loading.value = true + // 生成代码 + const data = await CodegenApi.previewCodegen(id) + previewCodegen.value = data + // 处理文件 + let file = handleFiles(data) + preview.fileTree = handleTree2(file, 'id', 'parentId', 'children', '/') + // 点击首个文件 + preview.activeName = data[0].filePath + } finally { + loading.value = false + } +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 处理文件 */ +const handleFiles = (datas: CodegenApi.CodegenPreviewVO[]) => { let exists = {} // key:file 的 id;value:true let files: filesType[] = [] // 遍历每个元素 @@ -130,6 +166,7 @@ const handleFiles = (datas: CodegenPreviewVO[]) => { } return files } + /** 复制 **/ const copy = async (text: string) => { const { copy, copied, isSupported } = useClipboard({ source: text }) @@ -142,17 +179,6 @@ const copy = async (text: string) => { message.success(t('common.copySuccess')) } } - -/** 打开弹窗 */ -const openModal = async (id: number) => { - modelVisible.value = true - const res = await CodegenApi.previewCodegen(id) - let file = handleFiles(res) - previewCodegen.value = res - preview.fileTree = handleTree2(file, 'id', 'parentId', 'children', '/') - preview.activeName = res[0].filePath -} -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 </script> <style lang="scss"> .app-infra-codegen-preview-container { diff --git a/src/views/infra/codegen/index.vue b/src/views/infra/codegen/index.vue index 64a270e5..34c81e58 100644 --- a/src/views/infra/codegen/index.vue +++ b/src/views/infra/codegen/index.vue @@ -187,7 +187,7 @@ const resetQuery = () => { handleQuery() } -// 导入操作 +/** 导入操作 */ const importRef = ref() const openImportTable = () => { importRef.value.open() @@ -201,7 +201,7 @@ const handleUpdate = (id: number) => { /** 预览操作 */ const previewRef = ref() const handlePreview = (row: CodegenApi.CodegenTableVO) => { - previewRef.value.openModal(row.id) + previewRef.value.open(row.id) } /** 删除按钮操作 */ From 98afdfc04074b48599399d26e9addd05820a822c Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Wed, 29 Mar 2023 22:38:19 +0800 Subject: [PATCH 157/184] =?UTF-8?q?REVIEW=20=E4=BB=A3=E7=A0=81=E7=94=9F?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/dict/dict.type.ts | 2 +- .../codegen/components/BasicInfoForm.vue | 16 +++------- .../codegen/components/ColumInfoForm.vue | 26 +++++++--------- .../codegen/components/GenerateInfoForm.vue | 30 ++++++++----------- src/views/system/dict/data.vue | 2 +- 5 files changed, 29 insertions(+), 47 deletions(-) diff --git a/src/api/system/dict/dict.type.ts b/src/api/system/dict/dict.type.ts index 0d9faeb2..ed2969f1 100644 --- a/src/api/system/dict/dict.type.ts +++ b/src/api/system/dict/dict.type.ts @@ -10,7 +10,7 @@ export type DictTypeVO = { } // 查询字典(精简)列表 -export const listSimpleDictType = () => { +export const getSimpleDictTypeList = () => { return request.get({ url: '/system/dict-type/list-all-simple' }) } diff --git a/src/views/infra/codegen/components/BasicInfoForm.vue b/src/views/infra/codegen/components/BasicInfoForm.vue index 21b280ee..73174cbc 100644 --- a/src/views/infra/codegen/components/BasicInfoForm.vue +++ b/src/views/infra/codegen/components/BasicInfoForm.vue @@ -11,7 +11,6 @@ <el-input placeholder="请输入" v-model="formData.tableComment" /> </el-form-item> </el-col> - <el-col :span="12"> <el-form-item prop="className"> <template #label> @@ -25,7 +24,6 @@ </el-tooltip> </span> </template> - <el-input placeholder="请输入" v-model="formData.className" /> </el-form-item> </el-col> @@ -43,13 +41,12 @@ </el-form> </template> <script setup lang="ts"> -import { CodegenTableVO } from '@/api/infra/codegen/types' +import * as CodegenApi from '@/api/infra/codegen' import { PropType } from 'vue' -const emits = defineEmits(['update:basicInfo']) const props = defineProps({ table: { - type: Object as PropType<Nullable<CodegenTableVO>>, + type: Object as PropType<Nullable<CodegenApi.CodegenTableVO>>, default: () => null } }) @@ -62,7 +59,6 @@ const formData = ref({ author: '', remark: '' }) - const rules = reactive({ tableName: [required], tableComment: [required], @@ -70,6 +66,7 @@ const rules = reactive({ author: [required] }) +/** 监听 table 属性,复制给 formData 属性 */ watch( () => props.table, (table) => { @@ -81,12 +78,7 @@ watch( immediate: true } ) -watch( - () => formData.value, - (val) => { - emits('update:basicInfo', val) - } -) + defineExpose({ validate: async () => unref(formRef)?.validate() }) diff --git a/src/views/infra/codegen/components/ColumInfoForm.vue b/src/views/infra/codegen/components/ColumInfoForm.vue index 33d29978..5d2a036d 100644 --- a/src/views/infra/codegen/components/ColumInfoForm.vue +++ b/src/views/infra/codegen/components/ColumInfoForm.vue @@ -114,28 +114,24 @@ </template> <script setup lang="ts"> import { PropType } from 'vue' -import { CodegenColumnVO } from '@/api/infra/codegen/types' -import { DictTypeVO, listSimpleDictType } from '@/api/system/dict/dict.type' +import * as CodegenApi from '@/api/infra/codegen' +import * as DictDataApi from '@/api/system/dict/dict.type' -const emits = defineEmits(['update:columns']) const props = defineProps({ columns: { - type: Array as unknown as PropType<CodegenColumnVO[]>, + type: Array as unknown as PropType<CodegenApi.CodegenColumnVO[]>, default: () => null } }) -const formData = ref<CodegenColumnVO[]>([]) +const formData = ref<CodegenApi.CodegenColumnVO[]>([]) const tableHeight = document.documentElement.scrollHeight - 350 + 'px' /** 查询字典下拉列表 */ -const dictOptions = ref<DictTypeVO[]>() +const dictOptions = ref<DictDataApi.DictTypeVO[]>() const getDictOptions = async () => { - dictOptions.value = await listSimpleDictType() + dictOptions.value = await DictDataApi.getSimpleDictTypeList() } -onMounted(async () => { - await getDictOptions() -}) watch( () => props.columns, @@ -148,10 +144,8 @@ watch( immediate: true } ) -watch( - () => formData.value, - (val) => { - emits('update:columns', val) - } -) + +onMounted(async () => { + await getDictOptions() +}) </script> diff --git a/src/views/infra/codegen/components/GenerateInfoForm.vue b/src/views/infra/codegen/components/GenerateInfoForm.vue index 92bac8dd..356ee6df 100644 --- a/src/views/infra/codegen/components/GenerateInfoForm.vue +++ b/src/views/infra/codegen/components/GenerateInfoForm.vue @@ -5,10 +5,10 @@ <el-form-item prop="templateType" label="生成模板"> <el-select v-model="formData.templateType" @change="tplSelectChange"> <el-option - v-for="dict in getDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE)" - :key="parseInt(dict.value)" + v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE)" + :key="dict.value" :label="dict.label" - :value="parseInt(dict.value)" + :value="dict.value" /> </el-select> </el-form-item> @@ -17,10 +17,10 @@ <el-form-item prop="scene" label="生成场景"> <el-select v-model="formData.scene"> <el-option - v-for="dict in getDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE)" - :key="parseInt(dict.value)" + v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE)" + :key="dict.value" :label="dict.label" - :value="parseInt(dict.value)" + :value="dict.value" /> </el-select> </el-form-item> @@ -280,17 +280,16 @@ </el-form> </template> <script setup lang="ts"> -import { CodegenTableVO } from '@/api/infra/codegen/types' +import { getIntDictOptions, DICT_TYPE } from '@/utils/dict' +import { handleTree } from '@/utils/tree' +import * as CodegenApi from '@/api/infra/codegen' import * as MenuApi from '@/api/system/menu' import { PropType } from 'vue' -import { getDictOptions, DICT_TYPE } from '@/utils/dict' -import { handleTree } from '@/utils/tree' const message = useMessage() // 消息弹窗 -const emits = defineEmits(['update:basicInfo']) const props = defineProps({ table: { - type: Object as PropType<Nullable<CodegenTableVO>>, + type: Object as PropType<Nullable<CodegenApi.CodegenTableVO>>, default: () => null } }) @@ -335,6 +334,7 @@ const menuTreeProps = { const subSelectChange = () => { formData.value.subTableFkName = '' } + /** 选择生成模板触发 */ const tplSelectChange = (value) => { if (value !== 1) { @@ -361,18 +361,14 @@ watch( immediate: true } ) -watch( - () => formData.value, - (val) => { - emits('update:basicInfo', val) - } -) + onMounted(async () => { try { const resp = await MenuApi.getSimpleMenusList() menus.value = handleTree(resp) } catch {} }) + defineExpose({ validate: async () => unref(formRef)?.validate() }) diff --git a/src/views/system/dict/data.vue b/src/views/system/dict/data.vue index 8492262a..41bcbaa8 100644 --- a/src/views/system/dict/data.vue +++ b/src/views/system/dict/data.vue @@ -190,7 +190,7 @@ const handleExport = async () => { /** 查询字典(精简)列表 */ const getDictList = async () => { - dicts.value = await DictTypeApi.listSimpleDictType() + dicts.value = await DictTypeApi.getSimpleDictTypeList() } /** 初始化 **/ From 3c6d96d9f03f729be2930d857237c2c3d581539d Mon Sep 17 00:00:00 2001 From: fengjingtao <fessor@139.com> Date: Wed, 29 Mar 2023 23:28:23 +0800 Subject: [PATCH 158/184] =?UTF-8?q?fix:=20=E6=81=A2=E5=A4=8D=E6=9F=90?= =?UTF-8?q?=E4=BA=9B=E4=B8=8D=E4=B8=8A=E4=BC=A0git=E7=9A=84=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 22 +--------------------- src/types/auto-components.d.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3e9f1774..38cc3052 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -40,25 +40,5 @@ "i18n-ally.displayLanguage": "zh-CN", "i18n-ally.enabledFrameworks": ["vue", "react"], "god.tsconfig": "./tsconfig.json", - "vue-i18n.i18nPaths": "src/locales", - "workbench.colorCustomizations": { - "activityBar.activeBackground": "#65c89b", - "activityBar.background": "#65c89b", - "activityBar.foreground": "#15202b", - "activityBar.inactiveForeground": "#15202b99", - "activityBarBadge.background": "#945bc4", - "activityBarBadge.foreground": "#e7e7e7", - "commandCenter.border": "#15202b99", - "sash.hoverBorder": "#65c89b", - "statusBar.background": "#42b883", - "statusBar.foreground": "#15202b", - "statusBarItem.hoverBackground": "#359268", - "statusBarItem.remoteBackground": "#42b883", - "statusBarItem.remoteForeground": "#15202b", - "titleBar.activeBackground": "#42b883", - "titleBar.activeForeground": "#15202b", - "titleBar.inactiveBackground": "#42b88399", - "titleBar.inactiveForeground": "#15202b99" - }, - "peacock.color": "#42b883" + "vue-i18n.i18nPaths": "src/locales" } diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index 8fcd30dd..480691fc 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -52,12 +52,17 @@ declare module '@vue/runtime-core' { ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] + ElImage: typeof import('element-plus/es')['ElImage'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] + ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElLink: typeof import('element-plus/es')['ElLink'] ElOption: typeof import('element-plus/es')['ElOption'] ElPagination: typeof import('element-plus/es')['ElPagination'] ElPopover: typeof import('element-plus/es')['ElPopover'] + ElRadio: typeof import('element-plus/es')['ElRadio'] + ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] + ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRow: typeof import('element-plus/es')['ElRow'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElSelect: typeof import('element-plus/es')['ElSelect'] @@ -67,7 +72,11 @@ declare module '@vue/runtime-core' { ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] + ElTag: typeof import('element-plus/es')['ElTag'] + ElTimeline: typeof import('element-plus/es')['ElTimeline'] + ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] + ElTransfer: typeof import('element-plus/es')['ElTransfer'] ElTree: typeof import('element-plus/es')['ElTree'] ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElUpload: typeof import('element-plus/es')['ElUpload'] From 216ad84938afb74248c5073118ddce547e48fac3 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Wed, 29 Mar 2023 23:43:22 +0800 Subject: [PATCH 159/184] =?UTF-8?q?REVIEW=20=E7=A7=9F=E6=88=B7=E5=A5=97?= =?UTF-8?q?=E9=A4=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/tenantPackage/index.ts | 15 +--- src/types/auto-components.d.ts | 4 + .../{form.vue => TenantPackageForm.vue} | 57 ++++++------- src/views/system/tenantPackage/index.vue | 79 +++++++++---------- 4 files changed, 68 insertions(+), 87 deletions(-) rename src/views/system/tenantPackage/{form.vue => TenantPackageForm.vue} (86%) diff --git a/src/api/system/tenantPackage/index.ts b/src/api/system/tenantPackage/index.ts index 37f7067a..01d139e2 100644 --- a/src/api/system/tenantPackage/index.ts +++ b/src/api/system/tenantPackage/index.ts @@ -12,15 +12,8 @@ export interface TenantPackageVO { createTime: Date } -export interface TenantPackagePageReqVO extends PageParam { - name?: string - status?: number - remark?: string - createTime?: Date[] -} - // 查询租户套餐列表 -export const getTenantPackageTypePage = (params: TenantPackagePageReqVO) => { +export const getTenantPackagePage = (params: PageParam) => { return request.get({ url: '/system/tenant-package/page', params }) } @@ -30,17 +23,17 @@ export const getTenantPackage = (id: number) => { } // 新增租户套餐 -export const createTenantPackageType = (data: TenantPackageVO) => { +export const createTenantPackage = (data: TenantPackageVO) => { return request.post({ url: '/system/tenant-package/create', data }) } // 修改租户套餐 -export const updateTenantPackageType = (data: TenantPackageVO) => { +export const updateTenantPackage = (data: TenantPackageVO) => { return request.put({ url: '/system/tenant-package/update', data }) } // 删除租户套餐 -export const deleteTenantPackageType = (id: number) => { +export const deleteTenantPackage = (id: number) => { return request.delete({ url: '/system/tenant-package/delete?id=' + id }) } // 获取租户套餐精简信息列表 diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index 4b1eb7ef..480691fc 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -52,6 +52,7 @@ declare module '@vue/runtime-core' { ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] + ElImage: typeof import('element-plus/es')['ElImage'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] @@ -72,7 +73,10 @@ declare module '@vue/runtime-core' { ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] ElTag: typeof import('element-plus/es')['ElTag'] + ElTimeline: typeof import('element-plus/es')['ElTimeline'] + ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] + ElTransfer: typeof import('element-plus/es')['ElTransfer'] ElTree: typeof import('element-plus/es')['ElTree'] ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElUpload: typeof import('element-plus/es')['ElUpload'] diff --git a/src/views/system/tenantPackage/form.vue b/src/views/system/tenantPackage/TenantPackageForm.vue similarity index 86% rename from src/views/system/tenantPackage/form.vue rename to src/views/system/tenantPackage/TenantPackageForm.vue index d642402c..089dd55c 100644 --- a/src/views/system/tenantPackage/form.vue +++ b/src/views/system/tenantPackage/TenantPackageForm.vue @@ -1,5 +1,5 @@ <template> - <Dialog :title="modelTitle" v-model="modelVisible" width="50%"> + <Dialog :title="modelTitle" v-model="modelVisible"> <el-form ref="formRef" :model="formData" @@ -13,13 +13,6 @@ <el-form-item label="菜单权限"> <el-card class="cardHeight"> <template #header> - 父子联动(选中父节点,自动选择子节点): - <el-switch - v-model="menuCheckStrictly" - inline-prompt - active-text="是" - inactive-text="否" - /> 全选/全不选: <el-switch v-model="treeNodeAll" @@ -43,7 +36,7 @@ :check-strictly="!menuCheckStrictly" show-checkbox :props="defaultProps" - :data="menuOptions as any[]" + :data="menuOptions" empty-text="加载中,请稍候" /> </el-card> @@ -51,7 +44,7 @@ <el-form-item label="状态" prop="status"> <el-radio-group v-model="formData.status"> <el-radio - v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" :label="parseInt(dict.value)" > @@ -70,30 +63,28 @@ </Dialog> </template> <script setup lang="ts" name="TenantPackageForm"> -import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { CommonStatusEnum } from '@/utils/constants' -import type { FormRules } from 'element-plus' import { defaultProps } from '@/utils/tree' -// 业务相关 import * as TenantPackageApi from '@/api/system/tenantPackage' -import { getSimpleMenusList } from '@/api/system/menu' +import * as MenuApi from '@/api/system/menu' import { ElTree } from 'element-plus' import { handleTree } from '@/utils/tree' - const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 + const modelVisible = ref(false) // 弹窗的是否展示 const modelTitle = ref('') // 弹窗的标题 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formType = ref('') // 表单的类型:create - 新增;update - 修改 -const formData: Record<string, any> = ref<Record<string, any>>({ +const formData = ref({ id: null, name: null, remark: null, menuIds: [], status: CommonStatusEnum.ENABLE }) -const formRules: FormRules = ref<FormRules>({ +const formRules = reactive({ name: [{ required: true, message: '套餐名不能为空', trigger: 'blur' }], status: [{ required: true, message: '状态不能为空', trigger: 'blur' }], menuIds: [{ required: true, message: '关联的菜单编号不能为空', trigger: 'blur' }] @@ -105,26 +96,12 @@ const menuExpand = ref(false) // 展开/折叠 const treeRef = ref<InstanceType<typeof ElTree>>() // 树组件Ref const treeNodeAll = ref(false) // 全选/全不选 -// 全选/全不选 -const handleCheckedTreeNodeAll = () => { - treeRef.value!.setCheckedNodes(treeNodeAll.value ? menuOptions.value : []) -} -// 全部(展开/折叠)TODO:for循环全部展开和折叠树组件数据 -const handleCheckedTreeExpand = () => { - const nodes = treeRef.value?.store.nodesMap - for (let node in nodes) { - nodes[node].expanded = !nodes[node].expanded - } -} - /** 打开弹窗 */ const open = async (type: string, id?: number) => { modelVisible.value = true modelTitle.value = t('action.' + type) formType.value = type resetForm() - // 加载Menu列表 - menuOptions.value = handleTree(await getSimpleMenusList()) // 修改时,设置数据 if (id) { formLoading.value = true @@ -140,6 +117,8 @@ const open = async (type: string, id?: number) => { formLoading.value = false } } + // 加载Menu列表 + menuOptions.value = handleTree(await MenuApi.getSimpleMenusList()) } defineExpose({ open }) // 提供 open 方法,用于打开弹窗 @@ -159,10 +138,10 @@ const submitForm = async () => { ...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>) ] if (formType.value === 'create') { - await TenantPackageApi.createTenantPackageType(data) + await TenantPackageApi.createTenantPackage(data) message.success(t('common.createSuccess')) } else { - await TenantPackageApi.updateTenantPackageType(data) + await TenantPackageApi.updateTenantPackage(data) message.success(t('common.updateSuccess')) } modelVisible.value = false @@ -189,6 +168,18 @@ const resetForm = () => { menuCheckStrictly.value = false formRef.value?.resetFields() } + +// 全选/全不选 +const handleCheckedTreeNodeAll = () => { + treeRef.value!.setCheckedNodes(treeNodeAll.value ? menuOptions.value : []) +} +// 全部(展开/折叠)TODO:for循环全部展开和折叠树组件数据 +const handleCheckedTreeExpand = () => { + const nodes = treeRef.value?.store.nodesMap + for (let node in nodes) { + nodes[node].expanded = !nodes[node].expanded + } +} </script> <style lang="scss" scoped> .cardHeight { diff --git a/src/views/system/tenantPackage/index.vue b/src/views/system/tenantPackage/index.vue index 955bf0da..ac24ea94 100644 --- a/src/views/system/tenantPackage/index.vue +++ b/src/views/system/tenantPackage/index.vue @@ -14,10 +14,11 @@ placeholder="请输入套餐名" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="状态" prop="status"> - <el-select v-model="queryParams.status" placeholder="请选择状态" clearable> + <el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px"> <el-option v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" @@ -29,7 +30,6 @@ <el-form-item label="创建时间" prop="createTime"> <el-date-picker v-model="queryParams.createTime" - style="width: 240px" type="daterange" value-format="YYYY-MM-DD HH:mm:ss" start-placeholder="开始日期" @@ -37,19 +37,12 @@ class="!w-240px" /> </el-form-item> - <el-form-item> - <el-button @click="handleQuery"> - <Icon icon="ep:search" class="mr-5px" /> - 搜索 - </el-button> - <el-button @click="resetQuery"> - <Icon icon="ep:refresh" class="mr-5px" /> - 重置 - </el-button> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> <el-button type="primary" - @click="handleCreate('create')" + @click="openForm('create')" v-hasPermi="['system:tenant-package:create']" > <Icon icon="ep:plus" class="mr-5px" /> @@ -72,26 +65,26 @@ <el-table-column label="备注" align="center" prop="remark" /> <el-table-column label="创建时间" align="center" prop="createTime" width="180"> <template #default="scope"> - <span>{{ parseTime(scope.row.createTime) }}</span> + <span>{{ formatDate(scope.row.createTime) }}</span> </template> </el-table-column> <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <template #default="scope"> <el-button - size="mini" - type="text" - icon="el-icon-edit" - @click="handleUpdate('update', scope.row.id)" + link + type="primary" + @click="openForm('update', scope.row.id)" v-hasPermi="['system:tenant-package:update']" - >修改 + > + 修改 </el-button> <el-button - size="mini" - type="text" - icon="el-icon-delete" + link + type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['system:tenant-package:delete']" - >删除 + > + 删除 </el-button> </template> </el-table-column> @@ -104,15 +97,15 @@ @pagination="getList" /> </ContentWrap> + + <!-- 表单弹窗:添加/修改 --> <TenantPackageForm ref="formRef" @success="getList" /> </template> <script setup lang="ts" name="TenantPackage"> -import TenantPackageForm from './form.vue' -// 业务相关的 import import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' -import { parseTime } from '@/utils/formatTime' +import { formatDate } from '@/utils/formatTime' import * as TenantPackageApi from '@/api/system/tenantPackage' - +import TenantPackageForm from './TenantPackageForm.vue' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -128,17 +121,17 @@ const queryParams: Record<string, any> = ref<Record<string, any>>({ createTime: [] }) const queryFormRef = ref() // 搜索的表单 -const formRef = ref() // 表单 Ref /** 查询列表 */ -const getList = () => { +const getList = async () => { loading.value = true - // 执行查询 - TenantPackageApi.getTenantPackageTypePage(queryParams.value).then((response) => { - list.value = response.list - total.value = response.total + try { + const data = await TenantPackageApi.getTenantPackagePage(queryParams.value) + list.value = data.list + total.value = data.total + } finally { loading.value = false - }) + } } /** 搜索按钮操作 */ @@ -149,31 +142,31 @@ const handleQuery = () => { /** 重置按钮操作 */ const resetQuery = () => { - // 表单重置 queryFormRef.value?.resetFields() getList() } -// 新增操作 -const handleCreate = (type: string) => { - formRef.value.open(type) -} - -// 修改操作 -const handleUpdate = async (type: string, id?: number) => { +/** 添加/修改操作 */ +const formRef = ref() +const openForm = (type: string, id?: number) => { formRef.value.open(type, id) } + /** 删除按钮操作 */ const handleDelete = async (id: number) => { try { // 删除的二次确认 await message.delConfirm() // 发起删除 - await TenantPackageApi.deleteTenantPackageType(id) + await TenantPackageApi.deleteTenantPackage(id) message.success(t('common.delSuccess')) // 刷新列表 await getList() } catch {} } -getList() + +/** 初始化 **/ +onMounted(() => { + getList() +}) </script> From 84ab75b475a4c0bcaf12754892dbb6df62acf52a Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Thu, 30 Mar 2023 09:35:35 +0800 Subject: [PATCH 160/184] =?UTF-8?q?REVIEW=20=E7=A7=9F=E6=88=B7=E5=A5=97?= =?UTF-8?q?=E9=A4=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tenantPackage/TenantPackageForm.vue | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/views/system/tenantPackage/TenantPackageForm.vue b/src/views/system/tenantPackage/TenantPackageForm.vue index 089dd55c..e96ff7db 100644 --- a/src/views/system/tenantPackage/TenantPackageForm.vue +++ b/src/views/system/tenantPackage/TenantPackageForm.vue @@ -33,7 +33,6 @@ <el-tree ref="treeRef" node-key="id" - :check-strictly="!menuCheckStrictly" show-checkbox :props="defaultProps" :data="menuOptions" @@ -91,7 +90,6 @@ const formRules = reactive({ }) const formRef = ref() // 表单 Ref const menuOptions = ref<any[]>([]) // 树形结构数据 -const menuCheckStrictly = ref(false) // 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 true const menuExpand = ref(false) // 展开/折叠 const treeRef = ref<InstanceType<typeof ElTree>>() // 树组件Ref const treeNodeAll = ref(false) // 全选/全不选 @@ -102,6 +100,8 @@ const open = async (type: string, id?: number) => { modelTitle.value = t('action.' + type) formType.value = type resetForm() + // 加载 Menu 列表。注意,必须放在前面,不然下面 setChecked 没数据节点 + menuOptions.value = handleTree(await MenuApi.getSimpleMenusList()) // 修改时,设置数据 if (id) { formLoading.value = true @@ -110,15 +110,13 @@ const open = async (type: string, id?: number) => { // 设置选中 formData.value = res // 设置选中 - res.menuIds?.forEach((item: any) => { - treeRef.value?.setChecked(item, true, false) + res.menuIds.forEach((menuId: number) => { + treeRef.value.setChecked(menuId, true, false) }) } finally { formLoading.value = false } } - // 加载Menu列表 - menuOptions.value = handleTree(await MenuApi.getSimpleMenusList()) } defineExpose({ open }) // 提供 open 方法,用于打开弹窗 @@ -134,8 +132,8 @@ const submitForm = async () => { try { const data = formData.value as unknown as TenantPackageApi.TenantPackageVO data.menuIds = [ - ...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>), - ...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>) + ...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>), // 获得当前选中节点 + ...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>) // 获得半选中的父节点 ] if (formType.value === 'create') { await TenantPackageApi.createTenantPackage(data) @@ -154,6 +152,10 @@ const submitForm = async () => { /** 重置表单 */ const resetForm = () => { + // 重置选项 + treeNodeAll.value = false + menuExpand.value = false + // 重置表单 formData.value = { id: null, name: null, @@ -162,10 +164,6 @@ const resetForm = () => { status: CommonStatusEnum.ENABLE } treeRef.value?.setCheckedNodes([]) - treeNodeAll.value = false - menuExpand.value = false - // 设置为非严格,继续使用半选中 - menuCheckStrictly.value = false formRef.value?.resetFields() } @@ -173,6 +171,7 @@ const resetForm = () => { const handleCheckedTreeNodeAll = () => { treeRef.value!.setCheckedNodes(treeNodeAll.value ? menuOptions.value : []) } + // 全部(展开/折叠)TODO:for循环全部展开和折叠树组件数据 const handleCheckedTreeExpand = () => { const nodes = treeRef.value?.store.nodesMap From ad16456379276950e09e122a0eb4501a3ebc0be7 Mon Sep 17 00:00:00 2001 From: puhui999 <puhui999@163.com> Date: Thu, 30 Mar 2023 10:18:11 +0800 Subject: [PATCH 161/184] =?UTF-8?q?update:=20=E4=BF=AE=E5=A4=8Dtree?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E5=9C=A8=E5=B7=B2=E6=9C=89=E5=B1=95=E5=BC=80?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E5=90=8E=E5=85=A8=E9=83=A8=E5=B1=95=E5=BC=80?= =?UTF-8?q?=E5=92=8C=E6=8A=98=E5=8F=A0=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/auto-components.d.ts | 9 --------- src/views/system/tenantPackage/TenantPackageForm.vue | 2 ++ 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index 480691fc..a04c98b3 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -52,16 +52,13 @@ declare module '@vue/runtime-core' { ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] - ElImage: typeof import('element-plus/es')['ElImage'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] - ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElLink: typeof import('element-plus/es')['ElLink'] ElOption: typeof import('element-plus/es')['ElOption'] ElPagination: typeof import('element-plus/es')['ElPagination'] ElPopover: typeof import('element-plus/es')['ElPopover'] ElRadio: typeof import('element-plus/es')['ElRadio'] - ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRow: typeof import('element-plus/es')['ElRow'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] @@ -72,13 +69,7 @@ declare module '@vue/runtime-core' { ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] - ElTag: typeof import('element-plus/es')['ElTag'] - ElTimeline: typeof import('element-plus/es')['ElTimeline'] - ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] - ElTransfer: typeof import('element-plus/es')['ElTransfer'] - ElTree: typeof import('element-plus/es')['ElTree'] - ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElUpload: typeof import('element-plus/es')['ElUpload'] Error: typeof import('./../components/Error/src/Error.vue')['default'] FlowCondition: typeof import('./../components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue')['default'] diff --git a/src/views/system/tenantPackage/TenantPackageForm.vue b/src/views/system/tenantPackage/TenantPackageForm.vue index e96ff7db..82fc351f 100644 --- a/src/views/system/tenantPackage/TenantPackageForm.vue +++ b/src/views/system/tenantPackage/TenantPackageForm.vue @@ -69,6 +69,7 @@ import * as TenantPackageApi from '@/api/system/tenantPackage' import * as MenuApi from '@/api/system/menu' import { ElTree } from 'element-plus' import { handleTree } from '@/utils/tree' + const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -176,6 +177,7 @@ const handleCheckedTreeNodeAll = () => { const handleCheckedTreeExpand = () => { const nodes = treeRef.value?.store.nodesMap for (let node in nodes) { + if (nodes[node].expanded === menuExpand.value) continue nodes[node].expanded = !nodes[node].expanded } } From 4906cecd142534a912108aa04a71a4fe12bf9281 Mon Sep 17 00:00:00 2001 From: Siyu Kong <boil@vip.qq.com> Date: Thu, 30 Mar 2023 16:57:27 +0800 Subject: [PATCH 162/184] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=95=86?= =?UTF-8?q?=E5=93=81=E5=B1=9E=E6=80=A7api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/mall/product/property.ts | 103 +++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 src/api/mall/product/property.ts diff --git a/src/api/mall/product/property.ts b/src/api/mall/product/property.ts new file mode 100644 index 00000000..dd693c5c --- /dev/null +++ b/src/api/mall/product/property.ts @@ -0,0 +1,103 @@ +import request from '@/config/axios' + +/** + * 商品属性 + */ +export interface PropertyVO { + id?: number + /** 名称 */ + name: string + /** 备注 */ + remark?: string +} + +/** + * 属性值 + */ +export interface PropertyValueVO { + id?: number + /** 属性项的编号 */ + propertyId?: number + /** 名称 */ + name: string + /** 备注 */ + remark?: string +} + +/** + * 商品属性值的明细 + */ +export interface PropertyValueDetailVO { + /** 属性项的编号 */ + propertyId: number // 属性的编号 + /** 属性的名称 */ + propertyName: string + /** 属性值的编号 */ + valueId: number + /** 属性值的名称 */ + valueName: string +} + +// ------------------------ 属性项 ------------------- + +// 创建属性项 +export const createProperty = (data: PropertyVO) => { + return request.post({ url: '/product/property/create', data }) +} + +// 更新属性项 +export const updateProperty = (data: PropertyVO) => { + return request.put({ url: '/product/property/update', data }) +} + +// 删除属性项 +export const deleteProperty = (id: number) => { + return request.delete({ url: `/product/property/delete?id=${id}` }) +} + +// 获得属性项 +export const getProperty = (id: number): Promise<PropertyVO> => { + return request.get({ url: `/product/property/get?id=${id}` }) +} + +// 获得属性项分页 +export const getPropertyPage = (params: PageParam & any) => { + return request.get({ url: '/product/property/page', params }) +} + +// 获得属性项列表 +export const getPropertyList = (params: any) => { + return request.get({ url: '/product/property/list', params }) +} + +// 获得属性项列表 +export const getPropertyListAndValue = (params: any) => { + return request.get({ url: '/product/property/get-value-list', params }) +} + +// ------------------------ 属性值 ------------------- + +// 获得属性值分页 +export const getPropertyValuePage = (params: PageParam & any) => { + return request.get({ url: '/product/property/value/page', params }) +} + +// 获得属性值 +export const getPropertyValue = (id: number): Promise<PropertyValueVO> => { + return request.get({ url: `/product/property/value/get?id=${id}` }) +} + +// 创建属性值 +export const createPropertyValue = (data: PropertyValueVO) => { + return request.post({ url: '/product/property/value/create', data }) +} + +// 更新属性值 +export const updatePropertyValue = (data: PropertyValueVO) => { + return request.put({ url: '/product/property/value/update', data }) +} + +// 删除属性值 +export const deletePropertyValue = (id: number) => { + return request.delete({ url: `/product/property/value/delete?id=${id}` }) +} From fdbae2dd37c345173d3166231d7f4712d8c0aaf3 Mon Sep 17 00:00:00 2001 From: Siyu Kong <boil@vip.qq.com> Date: Thu, 30 Mar 2023 16:57:48 +0800 Subject: [PATCH 163/184] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=95=86?= =?UTF-8?q?=E5=93=81=E5=B1=9E=E6=80=A7=E5=80=BC=E8=B7=AF=E7=94=B1=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/router/modules/remaining.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index 43375961..d768f217 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -294,6 +294,22 @@ const remainingRouter: AppRouteRecordRaw[] = [ } } ] + }, + { + path: '/property', + component: Layout, + name: 'property', + meta: { + hidden: true + }, + children: [ + { + path: 'value/:propertyId(\\d+)', + component: () => import('@/views/mall/product/property/value/index.vue'), + name: 'PropertyValue', + meta: { title: '商品属性值', icon: '', activeMenu: '/product/property' } + } + ] } ] From 1724f2c674d7d902953fbfe03a5b8840a525038f Mon Sep 17 00:00:00 2001 From: Siyu Kong <boil@vip.qq.com> Date: Thu, 30 Mar 2023 16:58:06 +0800 Subject: [PATCH 164/184] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=95=86?= =?UTF-8?q?=E5=93=81=E5=B1=9E=E6=80=A7(=E5=80=BC)=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/mall/product/property/form.vue | 94 ++++++++++ src/views/mall/product/property/index.vue | 149 ++++++++++++++++ .../mall/product/property/value/form.vue | 101 +++++++++++ .../mall/product/property/value/index.vue | 162 ++++++++++++++++++ 4 files changed, 506 insertions(+) create mode 100644 src/views/mall/product/property/form.vue create mode 100644 src/views/mall/product/property/index.vue create mode 100644 src/views/mall/product/property/value/form.vue create mode 100644 src/views/mall/product/property/value/index.vue diff --git a/src/views/mall/product/property/form.vue b/src/views/mall/product/property/form.vue new file mode 100644 index 00000000..360af99b --- /dev/null +++ b/src/views/mall/product/property/form.vue @@ -0,0 +1,94 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="名称" prop="name"> + <el-input v-model="formData.name" placeholder="请输入名称" /> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="formData.remark" type="textarea" placeholder="请输入内容" /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import * as PropertyApi from '@/api/mall/product/property' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const defaultFormData: PropertyApi.PropertyVO = { + id: undefined, + name: '' +} +const formData = ref({ ...defaultFormData }) +const formRules = reactive({ + name: [{ required: true, message: '名称不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await PropertyApi.getProperty(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as PropertyApi.PropertyVO + if (formType.value === 'create') { + await PropertyApi.createProperty(data) + message.success(t('common.createSuccess')) + } else { + await PropertyApi.updateProperty(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { ...defaultFormData } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/mall/product/property/index.vue b/src/views/mall/product/property/index.vue new file mode 100644 index 00000000..36cb5a11 --- /dev/null +++ b/src/views/mall/product/property/index.vue @@ -0,0 +1,149 @@ +<template> + <content-wrap> + <!-- 搜索工作栏 --> + <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px"> + <el-form-item label="名称" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入名称" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + value-format="YYYY-MM-DD HH:mm:ss" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button type="primary" @click="openModal('create')" v-hasPermi="['infra:config:create']"> + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> + </el-form-item> + </el-form> + + <!-- 列表 --> + <el-table v-loading="loading" :data="list" align="center"> + <el-table-column label="编号" align="center" prop="id" /> + <el-table-column label="名称" align="center" :show-overflow-tooltip="true"> + <template #default="scope"> + <router-link :to="'/property/value/' + scope.row.id" class="link-type"> + <span>{{ scope.row.name }}</span> + </router-link> + </template> + </el-table-column> + <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" /> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openModal('update', scope.row.id)" + v-hasPermi="['infra:config:update']" + > + 编辑 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['infra:config:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + + <!-- 表单弹窗:添加/修改 --> + <property-form ref="modalRef" @success="getList" /> +</template> +<script setup lang="ts" name="Config"> +import { dateFormatter } from '@/utils/formatTime' +import * as PropertyApi from '@/api/mall/product/property' +import PropertyForm from './form.vue' +const message = useMessage() // 消息弹窗 +const { t } = useI18n() // 国际化 + +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + name: undefined, + createTime: [] +}) +const queryFormRef = ref() // 搜索的表单 + +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await PropertyApi.getPropertyPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, id) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await PropertyApi.deleteProperty(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +/** 初始化 **/ +onMounted(() => { + getList() +}) +</script> diff --git a/src/views/mall/product/property/value/form.vue b/src/views/mall/product/property/value/form.vue new file mode 100644 index 00000000..51224986 --- /dev/null +++ b/src/views/mall/product/property/value/form.vue @@ -0,0 +1,101 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="属性id" prop="category"> + <el-input v-model="formData.propertyId" disabled="" /> + </el-form-item> + <el-form-item label="名称" prop="name"> + <el-input v-model="formData.name" placeholder="请输入名称" /> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="formData.remark" type="textarea" placeholder="请输入内容" /> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </div> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import * as PropertyApi from '@/api/mall/product/property' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const defaultFormData: PropertyApi.PropertyValueVO = { + id: undefined, + propertyId: undefined, + name: '', + remark: '' +} +const formData = ref({ ...defaultFormData }) +const formRules = reactive({ + propertyId: [{ required: true, message: '属性不能为空', trigger: 'blur' }], + name: [{ required: true, message: '名称不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async (type: string, propertyId: number, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + defaultFormData.propertyId = propertyId + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await PropertyApi.getPropertyValue(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as PropertyApi.PropertyValueVO + if (formType.value === 'create') { + await PropertyApi.createPropertyValue(data) + message.success(t('common.createSuccess')) + } else { + await PropertyApi.updatePropertyValue(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { ...defaultFormData } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/mall/product/property/value/index.vue b/src/views/mall/product/property/value/index.vue new file mode 100644 index 00000000..03b021ab --- /dev/null +++ b/src/views/mall/product/property/value/index.vue @@ -0,0 +1,162 @@ +<template> + <content-wrap> + <!-- 搜索工作栏 --> + <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px"> + <el-form-item label="属性项" prop="propertyId"> + <el-select v-model="queryParams.propertyId"> + <el-option + v-for="item in propertyOptions" + :key="item.id" + :label="item.name" + :value="item.id" + /> + </el-select> + </el-form-item> + <el-form-item label="名称" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入名称" + clearable + @keyup.enter="handleQuery" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button type="primary" @click="openModal('create')" v-hasPermi="['infra:config:create']"> + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> + </el-form-item> + </el-form> + + <!-- 列表 --> + <el-table v-loading="loading" :data="list" align="center"> + <el-table-column label="编号" align="center" prop="id" /> + <el-table-column label="名称" align="center" prop="name" :show-overflow-tooltip="true" /> + <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" /> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + width="180" + :formatter="dateFormatter" + /> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openModal('update', scope.row.id)" + v-hasPermi="['infra:config:update']" + > + 编辑 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['infra:config:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + + <!-- 表单弹窗:添加/修改 --> + <value-form ref="modalRef" @success="getList" /> +</template> +<script setup lang="ts" name="Config"> +import { dateFormatter } from '@/utils/formatTime' +import * as PropertyApi from '@/api/mall/product/property' +import ValueForm from './form.vue' +const message = useMessage() // 消息弹窗 +const { t } = useI18n() // 国际化 + +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const propertyOptions = ref<any[]>([]) +const defaultPropertyId = ref() +const queryParams = reactive<any>({ + pageNo: 1, + pageSize: 10, + name: undefined, + propertyId: undefined +}) +const queryFormRef = ref() // 搜索的表单 + +/** 查询参数列表 */ +const getList = async () => { + loading.value = true + try { + const data = await PropertyApi.getPropertyValuePage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 属性项下拉框数据 */ +const getPropertyList = async () => { + const data = await PropertyApi.getPropertyList({}) + propertyOptions.value = data +} + +/** 查询字典类型详细 */ +const getProperty = async (propertyId: number) => { + const data = await PropertyApi.getProperty(propertyId) + queryParams.propertyId = data.id + defaultPropertyId.value = data.id + await getList() +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, defaultPropertyId, id) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await PropertyApi.deleteProperty(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +/** 初始化 **/ +const router = useRouter() +onMounted(() => { + const propertyId: number = + router.currentRoute.value.params && (router.currentRoute.value.params.propertyId as any) + getProperty(propertyId) + getPropertyList() +}) +</script> From 854bf851e899c8175b6efadfcf4196be3c1e5bda Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Thu, 30 Mar 2023 21:38:46 +0800 Subject: [PATCH 165/184] =?UTF-8?q?REVIEW=20=E7=A7=9F=E6=88=B7=E5=A5=97?= =?UTF-8?q?=E9=A4=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/auto-components.d.ts | 9 +++++++++ .../tenantPackage/TenantPackageForm.vue | 19 ++++++++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index a04c98b3..480691fc 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -52,13 +52,16 @@ declare module '@vue/runtime-core' { ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] + ElImage: typeof import('element-plus/es')['ElImage'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] + ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElLink: typeof import('element-plus/es')['ElLink'] ElOption: typeof import('element-plus/es')['ElOption'] ElPagination: typeof import('element-plus/es')['ElPagination'] ElPopover: typeof import('element-plus/es')['ElPopover'] ElRadio: typeof import('element-plus/es')['ElRadio'] + ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRow: typeof import('element-plus/es')['ElRow'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] @@ -69,7 +72,13 @@ declare module '@vue/runtime-core' { ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] + ElTag: typeof import('element-plus/es')['ElTag'] + ElTimeline: typeof import('element-plus/es')['ElTimeline'] + ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] + ElTransfer: typeof import('element-plus/es')['ElTransfer'] + ElTree: typeof import('element-plus/es')['ElTree'] + ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElUpload: typeof import('element-plus/es')['ElUpload'] Error: typeof import('./../components/Error/src/Error.vue')['default'] FlowCondition: typeof import('./../components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue')['default'] diff --git a/src/views/system/tenantPackage/TenantPackageForm.vue b/src/views/system/tenantPackage/TenantPackageForm.vue index 82fc351f..a713deea 100644 --- a/src/views/system/tenantPackage/TenantPackageForm.vue +++ b/src/views/system/tenantPackage/TenantPackageForm.vue @@ -69,7 +69,6 @@ import * as TenantPackageApi from '@/api/system/tenantPackage' import * as MenuApi from '@/api/system/menu' import { ElTree } from 'element-plus' import { handleTree } from '@/utils/tree' - const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -92,7 +91,7 @@ const formRules = reactive({ const formRef = ref() // 表单 Ref const menuOptions = ref<any[]>([]) // 树形结构数据 const menuExpand = ref(false) // 展开/折叠 -const treeRef = ref<InstanceType<typeof ElTree>>() // 树组件Ref +const treeRef = ref<ElTree>() // 树组件 Ref const treeNodeAll = ref(false) // 全选/全不选 /** 打开弹窗 */ @@ -133,8 +132,8 @@ const submitForm = async () => { try { const data = formData.value as unknown as TenantPackageApi.TenantPackageVO data.menuIds = [ - ...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>), // 获得当前选中节点 - ...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>) // 获得半选中的父节点 + ...(treeRef.value.getCheckedKeys(false) as unknown as Array<number>), // 获得当前选中节点 + ...(treeRef.value.getHalfCheckedKeys() as unknown as Array<number>) // 获得半选中的父节点 ] if (formType.value === 'create') { await TenantPackageApi.createTenantPackage(data) @@ -168,17 +167,19 @@ const resetForm = () => { formRef.value?.resetFields() } -// 全选/全不选 +/** 全选/全不选 */ const handleCheckedTreeNodeAll = () => { - treeRef.value!.setCheckedNodes(treeNodeAll.value ? menuOptions.value : []) + treeRef.value.setCheckedNodes(treeNodeAll.value ? menuOptions.value : []) } -// 全部(展开/折叠)TODO:for循环全部展开和折叠树组件数据 +/** 展开/折叠全部 */ const handleCheckedTreeExpand = () => { const nodes = treeRef.value?.store.nodesMap for (let node in nodes) { - if (nodes[node].expanded === menuExpand.value) continue - nodes[node].expanded = !nodes[node].expanded + if (nodes[node].expanded === menuExpand.value) { + continue + } + nodes[node].expanded = menuExpand.value } } </script> From e0469bf64e236fbf2d2bba5e32e19545b736ba69 Mon Sep 17 00:00:00 2001 From: admin <> Date: Thu, 30 Mar 2023 21:42:42 +0800 Subject: [PATCH 166/184] =?UTF-8?q?=E9=87=8D=E5=86=99=E5=95=86=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/auto-components.d.ts | 2 - src/views/pay/merchant/form.vue | 110 ++++++++ src/views/pay/merchant/index.vue | 348 +++++++++++++++--------- src/views/pay/merchant/merchant.data.ts | 70 ----- 4 files changed, 327 insertions(+), 203 deletions(-) create mode 100644 src/views/pay/merchant/form.vue delete mode 100644 src/views/pay/merchant/merchant.data.ts diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index a04c98b3..dfa7c415 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -58,8 +58,6 @@ declare module '@vue/runtime-core' { ElOption: typeof import('element-plus/es')['ElOption'] ElPagination: typeof import('element-plus/es')['ElPagination'] ElPopover: typeof import('element-plus/es')['ElPopover'] - ElRadio: typeof import('element-plus/es')['ElRadio'] - ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRow: typeof import('element-plus/es')['ElRow'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElSelect: typeof import('element-plus/es')['ElSelect'] diff --git a/src/views/pay/merchant/form.vue b/src/views/pay/merchant/form.vue new file mode 100644 index 00000000..d89fc7a3 --- /dev/null +++ b/src/views/pay/merchant/form.vue @@ -0,0 +1,110 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible" width="800"> + <!-- 对话框(添加 / 修改) --> + <el-form ref="formRef" :model="form" :rules="formRules" label-width="80px"> + <el-form-item label="商户全称" prop="name"> + <el-input v-model="form.name" placeholder="请输入商户全称" /> + </el-form-item> + <el-form-item label="商户简称" prop="shortName"> + <el-input v-model="form.shortName" placeholder="请输入商户简称" /> + </el-form-item> + <el-form-item label="开启状态" prop="status"> + <el-select v-model="form.status" placeholder="请选择状态" clearable> + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="form.remark" placeholder="请输入备注" /> + </el-form-item> + </el-form> + <template #footer> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import * as MerchantApi from '@/api/pay/merchant' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const form = ref({ + id: undefined, + name: '', + shortName: '', + status: undefined, + remark: '' +}) +const formRules = reactive({ + name: [{ required: true, message: '商户名称不能为空', trigger: 'blur' }], + shortName: [{ required: true, message: '商户简称不能为空', trigger: 'blur' }], + status: [{ required: true, message: '状态不能为空', trigger: 'change' }] +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const openModal = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + form.value = await MerchantApi.getMerchantApi(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = form.value as unknown as MerchantApi.MerchantVO + if (formType.value === 'create') { + await MerchantApi.createMerchantApi(data) + message.success(t('common.createSuccess')) + } else { + await MerchantApi.updateMerchantApi(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + form.value = { + id: undefined, + name: '', + shortName: '', + status: undefined, + remark: '' + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/pay/merchant/index.vue b/src/views/pay/merchant/index.vue index 1ea460ec..e9a9ff30 100644 --- a/src/views/pay/merchant/index.vue +++ b/src/views/pay/merchant/index.vue @@ -1,153 +1,239 @@ <template> - <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <!-- 操作:新增 --> - <XButton + <content-wrap> + <el-form + :model="queryParams" + ref="queryFormRef" + :inline="true" + v-show="showSearch" + label-width="68px" + ><el-form-item label="商户号" prop="no"> + <el-input v-model="queryParams.no" placeholder="请输入商户号" clearable /> + </el-form-item> + <el-form-item label="商户全称" prop="name"> + <el-input v-model="queryParams.name" placeholder="请输入商户全称" clearable /> + </el-form-item> + <el-form-item label="商户简称" prop="shortName"> + <el-input v-model="queryParams.shortName" placeholder="请输入商户简称" clearable /> + </el-form-item> + <el-form-item label="开启状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="字典状态" clearable class="!w-240px"> + <el-option + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="parseInt(dict.value)" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="queryParams.remark" placeholder="请输入备注" clearable /> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + style="width: 240px" + value-format="YYYY-MM-DD HH:mm:ss" + type="datetimerange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + /> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button> + <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + + <!-- 操作工具栏 --> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" + plain + icon="el-icon-plus" + size="small" + @click="openModal('create')" v-hasPermi="['pay:merchant:create']" - @click="handleCreate()" - /> - <!-- 操作:导出 --> - <XButton + >新增</el-button + > + </el-col> + <el-col :span="1.5"> + <el-button type="warning" - preIcon="ep:download" - :title="t('action.export')" + plain + icon="el-icon-download" + size="small" + :loading="exportLoading" + @click="handleExport" v-hasPermi="['pay:merchant:export']" - @click="exportList('商户列表.xls')" - /> - </template> - <template #actionbtns_default="{ row }"> - <!-- 操作:修改 --> - <XTextButton - preIcon="ep:edit" - :title="t('action.edit')" - v-hasPermi="['pay:merchant:update']" - @click="handleUpdate(row.id)" - /> - <!-- 操作:详情 --> - <XTextButton - preIcon="ep:view" - :title="t('action.detail')" - v-hasPermi="['pay:merchant:query']" - @click="handleDetail(row.id)" - /> - <!-- 操作:删除 --> - <XTextButton - preIcon="ep:delete" - :title="t('action.del')" - v-hasPermi="['pay:merchant:delete']" - @click="deleteData(row.id)" - /> - </template> - </XTable> - </ContentWrap> - <XModal v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(添加 / 修改) --> - <Form - v-if="['create', 'update'].includes(actionType)" - :schema="allSchemas.formSchema" - :rules="rules" - ref="formRef" - /> - <!-- 对话框(详情) --> - <Descriptions - v-if="actionType === 'detail'" - :schema="allSchemas.detailSchema" - :data="detailData" - /> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="actionLoading" - @click="submitForm()" + >导出</el-button + > + </el-col> + </el-row> + </content-wrap> + <content-wrap> + <!-- 列表 --> + <el-table v-loading="loading" :data="list"> + <el-table-column label="商户编号" align="center" prop="id" /> + <el-table-column label="商户号" align="center" prop="no" /> + <el-table-column label="商户全称" align="center" prop="name" /> + <el-table-column label="商户简称" align="center" prop="shortName" /> + <el-table-column label="开启状态" align="center" prop="status"> + <template #default="scope"> + <el-switch + v-model="scope.row.status" + :active-value="0" + :inactive-value="1" + @change="handleStatusChange(scope.row)" + /> + </template> + </el-table-column> + <el-table-column label="备注" align="center" prop="remark" /> + <el-table-column + label="创建时间" + :formatter="dateFormatter" + align="center" + prop="createTime" + width="180" /> - <!-- 按钮:关闭 --> - <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-button + link + type="primary" + icon="el-icon-edit" + @click="openModal('update', scope.row.id)" + v-hasPermi="['pay:merchant:update']" + >修改</el-button + > + <el-button + link + type="danger" + icon="el-icon-delete" + @click="handleDelete(scope.row.id)" + v-hasPermi="['pay:merchant:delete']" + >删除</el-button + > + </template> + </el-table-column> + </el-table> + <!-- 分页组件 --> + <pagination + v-show="total > 0" + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + <!-- 表单弹窗:添加/修改 --> + <MerchantForm ref="modalRef" @success="getList" /> + </content-wrap> </template> + <script setup lang="ts" name="Merchant"> -import type { FormExpose } from '@/components/Form' -import { rules, allSchemas } from './merchant.data' +import { DICT_TYPE, getDictOptions } from '@/utils/dict' import * as MerchantApi from '@/api/pay/merchant' - -const { t } = useI18n() // 国际化 +import MerchantForm from './form.vue' +import download from '@/utils/download' +import { dateFormatter } from '@/utils/formatTime' +import { CommonStatusEnum } from '@/utils/constants' const message = useMessage() // 消息弹窗 -// 列表相关的变量 -const [registerTable, { reload, deleteData, exportList }] = useXTable({ - allSchemas: allSchemas, - getListApi: MerchantApi.getMerchantPageApi, - deleteApi: MerchantApi.deleteMerchantApi, - exportListApi: MerchantApi.exportMerchantApi +const { t } = useI18n() // 国际化 + +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + name: '', + shortName: '', + status: undefined, + pageNo: 1, + pageSize: 100 }) +const queryFormRef = ref() // 搜索的表单 +const showSearch = ref(true) +const exportLoading = ref(false) // 导出的加载中 +/** 查询列表 */ +const getList = async () => { + loading.value = true + try { + const data = await MerchantApi.getMerchantPageApi(queryParams) -// ========== CRUD 相关 ========== -const actionLoading = ref(false) // 遮罩层 -const actionType = ref('') // 操作按钮的类型 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 -const formRef = ref<FormExpose>() // 表单 Ref -const detailData = ref() // 详情 Ref - -// 设置标题 -const setDialogTile = (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } } -// 新增操作 -const handleCreate = () => { - setDialogTile('create') +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() } -// 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - // 设置数据 - const res = await MerchantApi.getMerchantApi(rowId) - unref(formRef)?.setValues(res) +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() } -// 详情操作 -const handleDetail = async (rowId: number) => { - setDialogTile('detail') - const res = await MerchantApi.getMerchantApi(rowId) - detailData.value = res +/** 添加/修改操作 */ +const modalRef = ref() +const openModal = (type: string, id?: number) => { + modalRef.value.openModal(type, id) } -// 提交按钮 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - actionLoading.value = true - // 提交请求 - try { - const data = unref(formRef)?.formModel as MerchantApi.MerchantVO - if (actionType.value === 'create') { - await MerchantApi.createMerchantApi(data) - message.success(t('common.createSuccess')) - } else { - await MerchantApi.updateMerchantApi(data) - message.success(t('common.updateSuccess')) - } - dialogVisible.value = false - } finally { - actionLoading.value = false - // 刷新列表 - await reload() - } - } - }) +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await MerchantApi.deleteMerchantApi(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} } + +// 启用状态修改 +const handleStatusChange = (row: MerchantApi.MerchantVO) => { + let info = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' + message + .confirm('确认要"' + info + '""' + row.name + '"商户吗?', t('common.reminder')) + .then(async () => { + row.status = + row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.ENABLE : CommonStatusEnum.DISABLE + await MerchantApi.updateMerchantApi(row) + message.success(info + '成功') + // 刷新列表 + getList() + }) + .catch(() => { + row.status = + row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE + }) +} + +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await MerchantApi.exportMerchantApi(queryParams) + download.excel(data, '商户信息.xls') + } catch { + } finally { + exportLoading.value = false + } +} + +/** 初始化 **/ +onMounted(() => { + getList() +}) </script> diff --git a/src/views/pay/merchant/merchant.data.ts b/src/views/pay/merchant/merchant.data.ts deleted file mode 100644 index e0e0727d..00000000 --- a/src/views/pay/merchant/merchant.data.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -const { t } = useI18n() // 国际化 - -// 表单校验 -export const rules = reactive({ - no: [required], - name: [required], - shortName: [required], - status: [required] -}) - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'seq', - primaryTitle: '商户编号', - action: true, - columns: [ - { - title: '商户号', - field: 'no', - isSearch: true - }, - { - title: '商户全称', - field: 'code', - isSearch: true - }, - { - title: '商户简称', - field: 'shortName', - isSearch: true - }, - { - title: t('common.status'), - field: 'status', - dictType: DICT_TYPE.COMMON_STATUS, - dictClass: 'number', - isSearch: true - }, - { - title: t('form.remark'), - field: 'remark', - isTable: false, - form: { - component: 'Input', - componentProps: { - type: 'textarea', - rows: 4 - }, - colProps: { - span: 24 - } - } - }, - { - title: t('common.createTime'), - field: 'createTime', - formatter: 'formatDate', - isForm: false, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas) From 1434cdabe0f43fc3786216e85eeb56dbdab58c7e Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Thu, 30 Mar 2023 22:59:29 +0800 Subject: [PATCH 167/184] =?UTF-8?q?REVIEW=20=E7=94=A8=E6=88=B7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=EF=BC=88=E8=B0=83=E6=95=B4=E5=B8=83=E5=B1=80=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tenantPackage/TenantPackageForm.vue | 2 +- src/views/system/user/index.vue | 437 +++++++++--------- 2 files changed, 215 insertions(+), 224 deletions(-) diff --git a/src/views/system/tenantPackage/TenantPackageForm.vue b/src/views/system/tenantPackage/TenantPackageForm.vue index a713deea..f5e5343d 100644 --- a/src/views/system/tenantPackage/TenantPackageForm.vue +++ b/src/views/system/tenantPackage/TenantPackageForm.vue @@ -91,7 +91,7 @@ const formRules = reactive({ const formRef = ref() // 表单 Ref const menuOptions = ref<any[]>([]) // 树形结构数据 const menuExpand = ref(false) // 展开/折叠 -const treeRef = ref<ElTree>() // 树组件 Ref +const treeRef = ref<InstanceType<typeof ElTree>>() // 树组件 Ref const treeNodeAll = ref(false) // 全选/全不选 /** 打开弹窗 */ diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index 1c36d376..aeeaced7 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -1,231 +1,220 @@ <template> - <div class="app-container"> - <content-wrap> - <!-- 搜索工作栏 --> - <el-row :gutter="20"> - <!--部门数据--> - <el-col :span="4" :xs="24"> - <UserDeptTree @node-click="handleDeptNodeClick" /> - </el-col> - <!--用户数据--> - <el-col :span="20" :xs="24"> - <el-form - :model="queryParams" - ref="queryFormRef" - :inline="true" - v-show="showSearch" - label-width="68px" - > - <el-form-item label="用户名称" prop="username"> - <el-input - v-model="queryParams.username" - placeholder="请输入用户名称" - clearable - style="width: 240px" - @keyup.enter="handleQuery" - /> - </el-form-item> - <el-form-item label="手机号码" prop="mobile"> - <el-input - v-model="queryParams.mobile" - placeholder="请输入手机号码" - clearable - style="width: 240px" - @keyup.enter="handleQuery" - /> - </el-form-item> - <el-form-item label="状态" prop="status"> - <el-select - v-model="queryParams.status" - placeholder="用户状态" - clearable - style="width: 240px" - > - <el-option - v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" - :key="dict.value as number" - :label="dict.label" - :value="dict.value as number" - /> - </el-select> - </el-form-item> - <el-form-item label="创建时间" prop="createTime"> - <el-date-picker - v-model="queryParams.createTime" - style="width: 240px" - value-format="YYYY-MM-DD HH:mm:ss" - type="datetimerange" - range-separator="-" - start-placeholder="开始日期" - end-placeholder="结束日期" - /> - </el-form-item> - <el-form-item> - <el-button type="primary" @click="handleQuery" - ><Icon icon="ep:search" />搜索</el-button - > - <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button> - </el-form-item> - </el-form> - - <el-row :gutter="10" class="mb-8px"> - <el-col :span="1.5"> - <el-button - type="primary" - plain - size="small" - @click="handleAdd" - v-hasPermi="['system:user:create']" - ><Icon icon="ep:plus" />新增</el-button - > - </el-col> - <el-col :span="1.5"> - <el-button - type="info" - size="small" - @click="handleImport" - v-hasPermi="['system:user:import']" - ><Icon icon="ep:upload" />导入</el-button - > - </el-col> - <el-col :span="1.5"> - <el-button - type="warning" - size="small" - @click="handleExport" - :loading="exportLoading" - v-hasPermi="['system:user:export']" - ><Icon icon="ep:download" />导出</el-button - > - </el-col> - </el-row> - <el-table v-loading="loading" :data="userList"> - <el-table-column - label="用户编号" - align="center" - key="id" - prop="id" - v-if="columns[0].visible" + <!-- 搜索工作栏 --> + <el-row :gutter="20"> + <!--部门数据--> + <el-col :span="4" :xs="24"> + <content-wrap class="h-1/1"> + <UserDeptTree @node-click="handleDeptNodeClick" /> + </content-wrap> + </el-col> + <!--用户数据--> + <el-col :span="20" :xs="24"> + <content-wrap> + <el-form + :model="queryParams" + ref="queryFormRef" + :inline="true" + v-show="showSearch" + label-width="68px" + > + <el-form-item label="用户名称" prop="username"> + <el-input + v-model="queryParams.username" + placeholder="请输入用户名称" + clearable + style="width: 240px" + @keyup.enter="handleQuery" /> - <el-table-column - label="用户名称" - align="center" - key="username" - prop="username" - v-if="columns[1].visible" - :show-overflow-tooltip="true" + </el-form-item> + <el-form-item label="手机号码" prop="mobile"> + <el-input + v-model="queryParams.mobile" + placeholder="请输入手机号码" + clearable + style="width: 240px" + @keyup.enter="handleQuery" /> - <el-table-column - label="用户昵称" - align="center" - key="nickname" - prop="nickname" - v-if="columns[2].visible" - :show-overflow-tooltip="true" - /> - <el-table-column - label="部门" - align="center" - key="deptName" - prop="dept.name" - v-if="columns[3].visible" - :show-overflow-tooltip="true" - /> - <el-table-column - label="手机号码" - align="center" - key="mobile" - prop="mobile" - v-if="columns[4].visible" - width="120" - /> - <el-table-column label="状态" key="status" v-if="columns[5].visible" align="center"> - <template #default="scope"> - <el-switch - v-model="scope.row.status" - :active-value="0" - :inactive-value="1" - @change="handleStatusChange(scope.row)" - /> - </template> - </el-table-column> - <el-table-column - label="创建时间" - align="center" - prop="createTime" - v-if="columns[6].visible" - width="160" + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select + v-model="queryParams.status" + placeholder="用户状态" + clearable + style="width: 240px" > - <template #default="scope"> - <span>{{ parseTime(scope.row.createTime) }}</span> - </template> - </el-table-column> - <el-table-column - label="操作" - align="center" - width="160" - class-name="small-padding fixed-width" + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + style="width: 240px" + value-format="YYYY-MM-DD HH:mm:ss" + type="datetimerange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + /> + </el-form-item> + <el-form-item> + <el-button type="primary" @click="handleQuery"><Icon icon="ep:search" />搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button> + <el-button type="primary" plain @click="handleAdd" v-hasPermi="['system:user:create']"> + <Icon icon="ep:plus" /> 新增 + </el-button> + <el-button + type="info" + size="small" + @click="handleImport" + v-hasPermi="['system:user:import']" > - <template #default="scope"> - <div class="flex justify-center items-center"> - <el-button - type="primary" - link - @click="handleUpdate(scope.row)" - v-hasPermi="['system:user:update']" - ><Icon icon="ep:edit" />修改</el-button - > - <el-dropdown - @command="(command) => handleCommand(command, scope.$index, scope.row)" - v-hasPermi="[ - 'system:user:delete', - 'system:user:update-password', - 'system:permission:assign-user-role' - ]" - > - <el-button type="primary" link><Icon icon="ep:d-arrow-right" />更多</el-button> - <template #dropdown> - <el-dropdown-menu> - <!-- div包住避免控制台报错:Runtime directive used on component with non-element root node --> - <div v-if="scope.row.id !== 1" v-hasPermi="['system:user:delete']"> - <el-dropdown-item command="handleDelete" type="text" - ><Icon icon="ep:delete" />删除</el-dropdown-item - > - </div> - <div v-hasPermi="['system:user:update-password']"> - <el-dropdown-item command="handleResetPwd" type="text" - ><Icon icon="ep:key" />重置密码</el-dropdown-item - ></div - > - <div v-hasPermi="['system:permission:assign-user-role']"> - <el-dropdown-item command="handleRole" type="text" - ><Icon icon="ep:circle-check" />分配角色</el-dropdown-item - ></div - > - </el-dropdown-menu> - </template> - </el-dropdown> - </div> - </template> - </el-table-column> - </el-table> - <pagination - v-show="total > 0" - :total="total" - v-model:page="queryParams.pageNo" - v-model:limit="queryParams.pageSize" - @pagination="getList" + <Icon icon="ep:upload" /> 导入 + </el-button> + <el-button + type="warning" + size="small" + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['system:user:export']" + > + <Icon icon="ep:download" />导出 + </el-button> + </el-form-item> + </el-form> + </content-wrap> + <content-wrap> + <el-table v-loading="loading" :data="userList"> + <el-table-column + label="用户编号" + align="center" + key="id" + prop="id" + v-if="columns[0].visible" /> - </el-col> - </el-row> - </content-wrap> - <!-- 添加或修改用户对话框 --> - <UserForm ref="userFormRef" @success="getList" /> - <!-- 用户导入对话框 --> - <UserImportForm ref="userImportFormRef" @success="getList" /> - <!-- 分配角色 --> - <UserAssignRoleForm ref="userAssignRoleFormRef" @success="getList" /> - </div> + <el-table-column + label="用户名称" + align="center" + key="username" + prop="username" + v-if="columns[1].visible" + :show-overflow-tooltip="true" + /> + <el-table-column + label="用户昵称" + align="center" + key="nickname" + prop="nickname" + v-if="columns[2].visible" + :show-overflow-tooltip="true" + /> + <el-table-column + label="部门" + align="center" + key="deptName" + prop="dept.name" + v-if="columns[3].visible" + :show-overflow-tooltip="true" + /> + <el-table-column + label="手机号码" + align="center" + key="mobile" + prop="mobile" + v-if="columns[4].visible" + width="120" + /> + <el-table-column label="状态" key="status" v-if="columns[5].visible" align="center"> + <template #default="scope"> + <el-switch + v-model="scope.row.status" + :active-value="0" + :inactive-value="1" + @change="handleStatusChange(scope.row)" + /> + </template> + </el-table-column> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + v-if="columns[6].visible" + width="160" + > + <template #default="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column + label="操作" + align="center" + width="160" + class-name="small-padding fixed-width" + > + <template #default="scope"> + <div class="flex justify-center items-center"> + <el-button + type="primary" + link + @click="handleUpdate(scope.row)" + v-hasPermi="['system:user:update']" + ><Icon icon="ep:edit" />修改</el-button + > + <el-dropdown + @command="(command) => handleCommand(command, scope.$index, scope.row)" + v-hasPermi="[ + 'system:user:delete', + 'system:user:update-password', + 'system:permission:assign-user-role' + ]" + > + <el-button type="primary" link><Icon icon="ep:d-arrow-right" />更多</el-button> + <template #dropdown> + <el-dropdown-menu> + <!-- div包住避免控制台报错:Runtime directive used on component with non-element root node --> + <div v-if="scope.row.id !== 1" v-hasPermi="['system:user:delete']"> + <el-dropdown-item command="handleDelete" type="text" + ><Icon icon="ep:delete" />删除</el-dropdown-item + > + </div> + <div v-hasPermi="['system:user:update-password']"> + <el-dropdown-item command="handleResetPwd" type="text" + ><Icon icon="ep:key" />重置密码</el-dropdown-item + ></div + > + <div v-hasPermi="['system:permission:assign-user-role']"> + <el-dropdown-item command="handleRole" type="text" + ><Icon icon="ep:circle-check" />分配角色</el-dropdown-item + ></div + > + </el-dropdown-menu> + </template> + </el-dropdown> + </div> + </template> + </el-table-column> + </el-table> + <pagination + v-show="total > 0" + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </content-wrap> + </el-col> + </el-row> + + <!-- 添加或修改用户对话框 --> + <UserForm ref="userFormRef" @success="getList" /> + <!-- 用户导入对话框 --> + <UserImportForm ref="userImportFormRef" @success="getList" /> + <!-- 分配角色 --> + <UserAssignRoleForm ref="userAssignRoleFormRef" @success="getList" /> </template> <script setup lang="ts" name="User"> @@ -280,7 +269,8 @@ const columns = ref([ { key: 5, label: `状态`, visible: true }, { key: 6, label: `创建时间`, visible: true } ]) -/* 查询列表 */ + +/** 查询列表 */ const getList = () => { loading.value = true getUserPageApi(queryParams).then((response) => { @@ -289,6 +279,7 @@ const getList = () => { loading.value = false }) } + /** 搜索按钮操作 */ const handleQuery = () => { queryParams.pageNo = 1 From 643b3873679d661607909cf70e09923c4c5fcb3d Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Fri, 31 Mar 2023 07:54:33 +0800 Subject: [PATCH 168/184] =?UTF-8?q?REVIEW=20=E7=94=A8=E6=88=B7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=EF=BC=88=E5=88=97=E8=A1=A8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/user/index.ts | 21 +- src/views/system/tenantPackage/index.vue | 2 +- .../UserDeptTree.vue => DeptTree.vue} | 29 +- src/views/system/user/components/UserForm.vue | 10 +- src/views/system/user/index.vue | 269 +++++++----------- 5 files changed, 130 insertions(+), 201 deletions(-) rename src/views/system/user/{components/UserDeptTree.vue => DeptTree.vue} (66%) diff --git a/src/api/system/user/index.ts b/src/api/system/user/index.ts index e488c0d7..3ae4928c 100644 --- a/src/api/system/user/index.ts +++ b/src/api/system/user/index.ts @@ -17,23 +17,8 @@ export interface UserVO { createTime: Date } -export interface UserPageReqVO extends PageParam { - deptId?: number - username?: string - mobile?: string - status?: number - createTime?: Date[] -} - -export interface UserExportReqVO { - code?: string - name?: string - status?: number - createTime?: Date[] -} - // 查询用户管理列表 -export const getUserPageApi = (params: UserPageReqVO) => { +export const getUserPage = (params: PageParam) => { return request.get({ url: '/system/user/page', params }) } @@ -53,12 +38,12 @@ export const updateUserApi = (data: UserVO | Recordable) => { } // 删除用户 -export const deleteUserApi = (id: number) => { +export const deleteUser = (id: number) => { return request.delete({ url: '/system/user/delete?id=' + id }) } // 导出用户 -export const exportUserApi = (params: UserExportReqVO) => { +export const exportUser = (params) => { return request.download({ url: '/system/user/export', params }) } diff --git a/src/views/system/tenantPackage/index.vue b/src/views/system/tenantPackage/index.vue index ac24ea94..7e99815d 100644 --- a/src/views/system/tenantPackage/index.vue +++ b/src/views/system/tenantPackage/index.vue @@ -112,7 +112,7 @@ const { t } = useI18n() // 国际化 const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 const list = ref([]) // 列表的数据 -const queryParams: Record<string, any> = ref<Record<string, any>>({ +const queryParams = reactive({ pageNo: 1, pageSize: 10, name: null, diff --git a/src/views/system/user/components/UserDeptTree.vue b/src/views/system/user/DeptTree.vue similarity index 66% rename from src/views/system/user/components/UserDeptTree.vue rename to src/views/system/user/DeptTree.vue index 59004a92..87ffaf9a 100644 --- a/src/views/system/user/components/UserDeptTree.vue +++ b/src/views/system/user/DeptTree.vue @@ -1,6 +1,6 @@ <template> <div class="head-container"> - <el-input v-model="deptName" placeholder="请输入部门名称" clearable style="margin-bottom: 20px"> + <el-input v-model="deptName" placeholder="请输入部门名称" clearable class="mb-20px"> <template #prefix> <Icon icon="ep:search" /> </template> @@ -8,15 +8,15 @@ </div> <div class="head-container"> <el-tree - :data="deptOptions" + :data="deptList" :props="defaultProps" + node-key="id" :expand-on-click-node="false" :filter-node-method="filterNode" ref="treeRef" - node-key="id" default-expand-all highlight-current - @node-click="handleDeptNodeClick" + @node-click="handleNodeClick" /> </div> </template> @@ -26,25 +26,30 @@ import { ElTree } from 'element-plus' import * as DeptApi from '@/api/system/dept' import { defaultProps, handleTree } from '@/utils/tree' -const emits = defineEmits(['node-click']) const deptName = ref('') -const deptOptions = ref<Tree[]>([]) // 树形结构 +const deptList = ref<Tree[]>([]) // 树形结构 const treeRef = ref<InstanceType<typeof ElTree>>() + +/** 获得部门树 */ const getTree = async () => { const res = await DeptApi.getSimpleDeptList() - deptOptions.value = [] - deptOptions.value.push(...handleTree(res)) + deptList.value = [] + deptList.value.push(...handleTree(res)) } -const filterNode = (value: string, data: Tree) => { - if (!value) return true - return data.name.includes(value) +/** 基于名字过滤 */ +const filterNode = (name: string, data: Tree) => { + if (!name) return true + return data.name.includes(name) } -const handleDeptNodeClick = async (row: { [key: string]: any }) => { +/** 处理部门被点击 */ +const handleNodeClick = async (row: { [key: string]: any }) => { emits('node-click', row) } +const emits = defineEmits(['node-click']) +/** 初始化 */ onMounted(async () => { await getTree() }) diff --git a/src/views/system/user/components/UserForm.vue b/src/views/system/user/components/UserForm.vue index 4ea21607..a69bf971 100644 --- a/src/views/system/user/components/UserForm.vue +++ b/src/views/system/user/components/UserForm.vue @@ -56,7 +56,7 @@ <el-select v-model="formData.sex" placeholder="请选择"> <el-option v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)" - :key="dict.value as number" + :key="dict.value" :label="dict.label" :value="dict.value" /> @@ -70,7 +70,7 @@ v-for="item in postOptions" :key="item.id" :label="item.name" - :value="item.id as number" + :value="item.id" /> </el-select> </el-form-item> @@ -102,7 +102,6 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { defaultProps, handleTree } from '@/utils/tree' import { ElForm, FormItemRule } from 'element-plus' import { Arrayable } from 'element-plus/es/utils' -import { UserVO } from '@/api/login/types' type Form = InstanceType<typeof ElForm> @@ -210,7 +209,8 @@ const cancel = () => { } /* 打开弹框 */ -const openForm = (row: undefined | UserVO) => { +const open = (type: string, id?: number) => { + console.log(type, id) resetForm() getTree() // 部门树 if (row && row.id) { @@ -232,6 +232,6 @@ onMounted(async () => { defineExpose({ resetForm, - openForm + open }) </script> diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index aeeaced7..cdd47436 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -1,20 +1,19 @@ <template> - <!-- 搜索工作栏 --> <el-row :gutter="20"> - <!--部门数据--> + <!-- 左侧部门树 --> <el-col :span="4" :xs="24"> <content-wrap class="h-1/1"> - <UserDeptTree @node-click="handleDeptNodeClick" /> + <DeptTree @node-click="handleDeptNodeClick" /> </content-wrap> </el-col> - <!--用户数据--> <el-col :span="20" :xs="24"> + <!-- 搜索 --> <content-wrap> <el-form + class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" - v-show="showSearch" label-width="68px" > <el-form-item label="用户名称" prop="username"> @@ -22,8 +21,8 @@ v-model="queryParams.username" placeholder="请输入用户名称" clearable - style="width: 240px" @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="手机号码" prop="mobile"> @@ -31,8 +30,8 @@ v-model="queryParams.mobile" placeholder="请输入手机号码" clearable - style="width: 240px" @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="状态" prop="status"> @@ -40,7 +39,7 @@ v-model="queryParams.status" placeholder="用户状态" clearable - style="width: 240px" + class="!w-240px" > <el-option v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" @@ -53,31 +52,30 @@ <el-form-item label="创建时间" prop="createTime"> <el-date-picker v-model="queryParams.createTime" - style="width: 240px" value-format="YYYY-MM-DD HH:mm:ss" type="datetimerange" - range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" + class="!w-240px" /> </el-form-item> <el-form-item> - <el-button type="primary" @click="handleQuery"><Icon icon="ep:search" />搜索</el-button> + <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button> - <el-button type="primary" plain @click="handleAdd" v-hasPermi="['system:user:create']"> + <el-button + type="primary" + plain + @click="openForm('create')" + v-hasPermi="['system:user:create']" + > <Icon icon="ep:plus" /> 新增 </el-button> - <el-button - type="info" - size="small" - @click="handleImport" - v-hasPermi="['system:user:import']" - > + <el-button type="info" plain @click="handleImport" v-hasPermi="['system:user:import']"> <Icon icon="ep:upload" /> 导入 </el-button> <el-button type="warning" - size="small" + plain @click="handleExport" :loading="exportLoading" v-hasPermi="['system:user:export']" @@ -88,28 +86,18 @@ </el-form> </content-wrap> <content-wrap> - <el-table v-loading="loading" :data="userList"> - <el-table-column - label="用户编号" - align="center" - key="id" - prop="id" - v-if="columns[0].visible" - /> + <el-table v-loading="loading" :data="list"> + <el-table-column label="用户编号" align="center" key="id" prop="id" /> <el-table-column label="用户名称" align="center" - key="username" prop="username" - v-if="columns[1].visible" :show-overflow-tooltip="true" /> <el-table-column label="用户昵称" align="center" - key="nickname" prop="nickname" - v-if="columns[2].visible" :show-overflow-tooltip="true" /> <el-table-column @@ -117,18 +105,10 @@ align="center" key="deptName" prop="dept.name" - v-if="columns[3].visible" :show-overflow-tooltip="true" /> - <el-table-column - label="手机号码" - align="center" - key="mobile" - prop="mobile" - v-if="columns[4].visible" - width="120" - /> - <el-table-column label="状态" key="status" v-if="columns[5].visible" align="center"> + <el-table-column label="手机号码" align="center" prop="mobile" width="120" /> + <el-table-column label="状态" key="status"> <template #default="scope"> <el-switch v-model="scope.row.status" @@ -142,28 +122,20 @@ label="创建时间" align="center" prop="createTime" - v-if="columns[6].visible" - width="160" - > - <template #default="scope"> - <span>{{ parseTime(scope.row.createTime) }}</span> - </template> - </el-table-column> - <el-table-column - label="操作" - align="center" - width="160" - class-name="small-padding fixed-width" - > + :formatter="dateFormatter" + width="180" + /> + <el-table-column label="操作" align="center" width="160"> <template #default="scope"> <div class="flex justify-center items-center"> <el-button type="primary" link - @click="handleUpdate(scope.row)" + @click="openForm('update', scope.row.id)" v-hasPermi="['system:user:update']" - ><Icon icon="ep:edit" />修改</el-button > + <Icon icon="ep:edit" />修改 + </el-button> <el-dropdown @command="(command) => handleCommand(command, scope.$index, scope.row)" v-hasPermi="[ @@ -172,25 +144,25 @@ 'system:permission:assign-user-role' ]" > - <el-button type="primary" link><Icon icon="ep:d-arrow-right" />更多</el-button> + <el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button> <template #dropdown> <el-dropdown-menu> <!-- div包住避免控制台报错:Runtime directive used on component with non-element root node --> - <div v-if="scope.row.id !== 1" v-hasPermi="['system:user:delete']"> - <el-dropdown-item command="handleDelete" type="text" - ><Icon icon="ep:delete" />删除</el-dropdown-item - > + <div v-hasPermi="['system:user:delete']"> + <el-dropdown-item command="handleDelete"> + <Icon icon="ep:delete" />删除 + </el-dropdown-item> </div> <div v-hasPermi="['system:user:update-password']"> - <el-dropdown-item command="handleResetPwd" type="text" - ><Icon icon="ep:key" />重置密码</el-dropdown-item - ></div - > + <el-dropdown-item command="handleResetPwd"> + <Icon icon="ep:key" />重置密码 + </el-dropdown-item> + </div> <div v-hasPermi="['system:permission:assign-user-role']"> - <el-dropdown-item command="handleRole" type="text" - ><Icon icon="ep:circle-check" />分配角色</el-dropdown-item - ></div - > + <el-dropdown-item command="handleRole"> + <Icon icon="ep:circle-check" />分配角色 + </el-dropdown-item> + </div> </el-dropdown-menu> </template> </el-dropdown> @@ -198,8 +170,7 @@ </template> </el-table-column> </el-table> - <pagination - v-show="total > 0" + <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @@ -210,36 +181,32 @@ </el-row> <!-- 添加或修改用户对话框 --> - <UserForm ref="userFormRef" @success="getList" /> + <UserForm ref="formRef" @success="getList" /> <!-- 用户导入对话框 --> - <UserImportForm ref="userImportFormRef" @success="getList" /> + <UserImportForm ref="importFormRef" @success="getList" /> <!-- 分配角色 --> - <UserAssignRoleForm ref="userAssignRoleFormRef" @success="getList" /> + <UserAssignRoleForm ref="assignRoleFormRef" @success="getList" /> </template> <script setup lang="ts" name="User"> -import download from '@/utils/download' -import { parseTime } from '@/utils/formatTime' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { dateFormatter } from '@/utils/formatTime' +import download from '@/utils/download' import { CommonStatusEnum } from '@/utils/constants' -import { - deleteUserApi, - exportUserApi, - resetUserPwdApi, - updateUserStatusApi, - getUserPageApi, - UserVO -} from '@/api/system/user' +import { resetUserPwdApi, updateUserStatusApi, UserVO } from '@/api/system/user' +import * as UserApi from '@/api/system/user' import UserForm from './components/UserForm.vue' import UserImportForm from './components/UserImportForm.vue' import UserAssignRoleForm from './components/UserAssignRoleForm.vue' -import UserDeptTree from './components/UserDeptTree.vue' - +import DeptTree from './DeptTree.vue' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数 const queryParams = reactive({ pageNo: 1, pageSize: 10, @@ -249,35 +216,18 @@ const queryParams = reactive({ deptId: undefined, createTime: [] }) -const showSearch = ref(true) - -const handleDeptNodeClick = async (row: { [key: string]: any }) => { - queryParams.deptId = row.id - getList() -} - -// 用户列表 -const userList = ref<UserVO[]>([]) -const loading = ref(false) -const total = ref(0) -const columns = ref([ - { key: 0, label: `用户编号`, visible: true }, - { key: 1, label: `用户名称`, visible: true }, - { key: 2, label: `用户昵称`, visible: true }, - { key: 3, label: `部门`, visible: true }, - { key: 4, label: `手机号码`, visible: true }, - { key: 5, label: `状态`, visible: true }, - { key: 6, label: `创建时间`, visible: true } -]) +const queryFormRef = ref() // 搜索的表单 /** 查询列表 */ -const getList = () => { +const getList = async () => { loading.value = true - getUserPageApi(queryParams).then((response) => { - userList.value = response.list - total.value = response.total + try { + const data = await UserApi.getUserPage(queryParams.value) + list.value = data.list + total.value = data.total + } finally { loading.value = false - }) + } } /** 搜索按钮操作 */ @@ -287,54 +237,35 @@ const handleQuery = () => { } /** 重置按钮操作 */ -const queryFormRef = ref() const resetQuery = () => { queryFormRef.value?.resetFields() handleQuery() } -// 添加或编辑 -const userFormRef = ref() -// 添加用户 -const handleAdd = () => { - userFormRef.value?.openForm() +/** 处理部门被点击 */ +const handleDeptNodeClick = async (row) => { + queryParams.deptId = row.id + await getList() +} + +/** 添加/修改操作 */ +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) } // 用户导入 -const userImportFormRef = ref() +const importFormRef = ref() const handleImport = () => { - userImportFormRef.value?.openForm() -} - -// 用户导出 -const exportLoading = ref(false) -const handleExport = () => { - message - .confirm('是否确认导出所有用户数据项?') - .then(async () => { - // 处理查询参数 - let params = { ...queryParams } - params.pageNo = 1 - params.pageSize = 99999 - exportLoading.value = true - const response = await exportUserApi(params) - download.excel(response, '用户数据.xls') - }) - .catch(() => {}) - .finally(() => { - exportLoading.value = false - }) + importFormRef.value?.openForm() } // 操作分发 -const handleCommand = (command: string, index: number, row: UserVO) => { +const handleCommand = (command: string, index: number, row: UserApi.UserVO) => { console.log(index) switch (command) { - case 'handleUpdate': - handleUpdate(row) //修改客户信息 - break case 'handleDelete': - handleDelete(row) //红号变更 + handleDelete(row.id) break case 'handleResetPwd': handleResetPwd(row) @@ -366,25 +297,33 @@ const handleStatusChange = (row: UserVO) => { }) } -// 具体数据单行操作 -/** 修改按钮操作 */ -const handleUpdate = (row: UserVO) => { - userFormRef.value?.openForm(row) +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await UserApi.deleteUser(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} } -// 删除用户 -const handleDelete = (row: UserVO) => { - const ids = row.id - message - .confirm('是否确认删除用户编号为"' + ids + '"的数据项?') - .then(async () => { - await deleteUserApi(ids) - message.success('删除成功') - getList() - }) - .catch((e) => { - console.error(e) - }) +/** 导出按钮操作 */ +const exportLoading = ref(false) +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await UserApi.exportUser(queryParams) + download.excel(data, '用户数据.xls') + } catch { + } finally { + exportLoading.value = false + } } // 重置密码 @@ -401,13 +340,13 @@ const handleResetPwd = (row: UserVO) => { } // 分配角色 -const userAssignRoleFormRef = ref() +const assignRoleFormRef = ref() const handleRole = (row: UserVO) => { - userAssignRoleFormRef.value?.openForm(row) + assignRoleFormRef.value?.openForm(row) } -// ========== 初始化 ========== -onMounted(async () => { +/** 初始化 */ +onMounted(() => { getList() }) </script> From 7d84f860819d66290ca8d0156e370f0690aba698 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Fri, 31 Mar 2023 13:40:57 +0800 Subject: [PATCH 169/184] =?UTF-8?q?REVIEW=20=E7=94=A8=E6=88=B7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=EF=BC=88=E5=88=86=E9=85=8D=E7=94=A8=E6=88=B7=E8=A7=92?= =?UTF-8?q?=E8=89=B2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/permission/index.ts | 4 +- src/api/system/user/index.ts | 4 +- src/views/system/user/UserAssignRoleForm.vue | 93 +++++++++++++ .../user/components/UserAssignRoleForm.vue | 108 --------------- src/views/system/user/index.vue | 123 +++++++++--------- 5 files changed, 157 insertions(+), 175 deletions(-) create mode 100644 src/views/system/user/UserAssignRoleForm.vue delete mode 100644 src/views/system/user/components/UserAssignRoleForm.vue diff --git a/src/api/system/permission/index.ts b/src/api/system/permission/index.ts index c529f884..6e2adcdf 100644 --- a/src/api/system/permission/index.ts +++ b/src/api/system/permission/index.ts @@ -32,11 +32,11 @@ export const assignRoleDataScopeApi = async (data: PermissionAssignRoleDataScope } // 查询用户拥有的角色数组 -export const listUserRolesApi = async (userId: number) => { +export const getUserRoleList = async (userId: number) => { return await request.get({ url: '/system/permission/list-user-roles?userId=' + userId }) } // 赋予用户角色 -export const assignUserRoleApi = async (data: PermissionAssignUserRoleReqVO) => { +export const assignUserRole = async (data: PermissionAssignUserRoleReqVO) => { return await request.post({ url: '/system/permission/assign-user-role', data }) } diff --git a/src/api/system/user/index.ts b/src/api/system/user/index.ts index 3ae4928c..90b07fe5 100644 --- a/src/api/system/user/index.ts +++ b/src/api/system/user/index.ts @@ -53,7 +53,7 @@ export const importUserTemplateApi = () => { } // 用户密码重置 -export const resetUserPwdApi = (id: number, password: string) => { +export const resetUserPwd = (id: number, password: string) => { const data = { id, password @@ -62,7 +62,7 @@ export const resetUserPwdApi = (id: number, password: string) => { } // 用户状态修改 -export const updateUserStatusApi = (id: number, status: number) => { +export const updateUserStatus = (id: number, status: number) => { const data = { id, status diff --git a/src/views/system/user/UserAssignRoleForm.vue b/src/views/system/user/UserAssignRoleForm.vue new file mode 100644 index 00000000..2dc04e52 --- /dev/null +++ b/src/views/system/user/UserAssignRoleForm.vue @@ -0,0 +1,93 @@ +<template> + <Dialog title="分配角色" v-model="modelVisible"> + <el-form ref="formRef" :model="formData" label-width="80px" v-loading="formLoading"> + <el-form-item label="用户名称"> + <el-input v-model="formData.username" :disabled="true" /> + </el-form-item> + <el-form-item label="用户昵称"> + <el-input v-model="formData.nickname" :disabled="true" /> + </el-form-item> + <el-form-item label="角色"> + <el-select v-model="formData.roleIds" multiple placeholder="请选择角色"> + <el-option v-for="item in roleList" :key="item.id" :label="item.name" :value="item.id" /> + </el-select> + </el-form-item> + </el-form> + <template #footer> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import * as PermissionApi from '@/api/system/permission' +import * as UserApi from '@/api/system/user' +import * as RoleApi from '@/api/system/role' +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formData = ref({ + id: undefined, + nickname: '', + username: '', + roleIds: [] +}) +const formRef = ref() // 表单 Ref +const roleList = ref([]) // 角色的列表 + +/** 打开弹窗 */ +const open = async (row: UserApi.UserVO) => { + modelVisible.value = true + resetForm() + // 设置数据 + formData.value.id = row.id + formData.value.username = row.username + formData.value.nickname = row.nickname + // 获得角色拥有的菜单集合 + formLoading.value = true + try { + formData.value.roleIds = await PermissionApi.getUserRoleList(row.id) + } finally { + formLoading.value = false + } + // 获得角色列表 + roleList.value = await RoleApi.getSimpleRoleList() +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + await PermissionApi.assignUserRole({ + userId: formData.value.id, + roleIds: formData.value.roleIds + }) + message.success(t('common.updateSuccess')) + modelVisible.value = false + // 发送操作成功的事件 + emit('success', true) + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + nickname: '', + username: '', + roleIds: [] + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/system/user/components/UserAssignRoleForm.vue b/src/views/system/user/components/UserAssignRoleForm.vue deleted file mode 100644 index bbf37739..00000000 --- a/src/views/system/user/components/UserAssignRoleForm.vue +++ /dev/null @@ -1,108 +0,0 @@ -<template> - <Dialog - title="分配角色" - :modelValue="showDialog" - width="500px" - append-to-body - @close="closeDialog" - > - <el-form :model="formData" label-width="80px" ref="formRef"> - <el-form-item label="用户名称"> - <el-input v-model="formData.username" :disabled="true" /> - </el-form-item> - <el-form-item label="用户昵称"> - <el-input v-model="formData.nickname" :disabled="true" /> - </el-form-item> - <el-form-item label="角色"> - <el-select v-model="formData.roleIds" multiple placeholder="请选择角色"> - <el-option - v-for="item in roleOptions" - :key="item.id" - :label="item.name" - :value="item.id" - /> - </el-select> - </el-form-item> - </el-form> - <template #footer> - <div class="dialog-footer"> - <el-button type="primary" @click="submit">确 定</el-button> - <el-button @click="cancel">取 消</el-button> - </div> - </template> - </Dialog> -</template> - -<script setup lang="ts"> -import { - assignUserRoleApi, - listUserRolesApi, - PermissionAssignUserRoleReqVO -} from '@/api/system/permission' -import { UserVO } from '@/api/system/user' -import * as RoleApi from '@/api/system/role' - -const emits = defineEmits(['success']) - -const { t } = useI18n() // 国际化 -const message = useMessage() // 消息弹窗 - -// 表单初始化参数 -const initParams = { - nickname: '', - id: 0, - username: '', - roleIds: [] as number[] -} -const formData = ref<Recordable>({ ...initParams }) - -/* 弹框按钮操作 */ -// 点击取消 -const cancel = () => { - closeDialog() -} -// 关闭弹窗 -const closeDialog = () => { - showDialog.value = false -} -// 提交 -const submit = async () => { - const data = ref<PermissionAssignUserRoleReqVO>({ - userId: formData.value.id, - roleIds: formData.value.roleIds - }) - try { - await assignUserRoleApi(data.value) - message.success(t('common.updateSuccess')) - emits('success', true) - closeDialog() - } catch (error) { - console.error(error) - } -} - -const roleOptions = ref() -const userRole = reactive(initParams) -const showDialog = ref(false) -const formRef = ref() -const openForm = async (row: UserVO) => { - formRef.value?.resetFields() - userRole.id = row.id - userRole.username = row.username - userRole.nickname = row.nickname - - // 获得角色列表 - const roleOpt = await RoleApi.getSimpleRoleList() - roleOptions.value = [...roleOpt] - - // 获得角色拥有的菜单集合 - const roles = await listUserRolesApi(row.id) - userRole.roleIds = roles - formData.value = { ...userRole } - - showDialog.value = true -} -defineExpose({ - openForm -}) -</script> diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index cdd47436..e57a62b4 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -193,13 +193,10 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' import { CommonStatusEnum } from '@/utils/constants' - -import { resetUserPwdApi, updateUserStatusApi, UserVO } from '@/api/system/user' import * as UserApi from '@/api/system/user' - import UserForm from './components/UserForm.vue' import UserImportForm from './components/UserImportForm.vue' -import UserAssignRoleForm from './components/UserAssignRoleForm.vue' +import UserAssignRoleForm from './UserAssignRoleForm.vue' import DeptTree from './DeptTree.vue' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -260,54 +257,21 @@ const handleImport = () => { importFormRef.value?.openForm() } -// 操作分发 -const handleCommand = (command: string, index: number, row: UserApi.UserVO) => { - console.log(index) - switch (command) { - case 'handleDelete': - handleDelete(row.id) - break - case 'handleResetPwd': - handleResetPwd(row) - break - case 'handleRole': - handleRole(row) - break - default: - break - } -} - -// 用户状态修改 -const handleStatusChange = (row: UserVO) => { - let text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' - message - .confirm('确认要"' + text + '""' + row.username + '"用户吗?', t('common.reminder')) - .then(async () => { - row.status = - row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.ENABLE : CommonStatusEnum.DISABLE - await updateUserStatusApi(row.id, row.status) - message.success(text + '成功') - // 刷新列表 - getList() - }) - .catch(() => { - row.status = - row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE - }) -} - -/** 删除按钮操作 */ -const handleDelete = async (id: number) => { +/** 修改用户状态 */ +const handleStatusChange = async (row: UserApi.UserVO) => { try { - // 删除的二次确认 - await message.delConfirm() - // 发起删除 - await UserApi.deleteUser(id) - message.success(t('common.delSuccess')) + // 修改状态的二次确认 + const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' + await message.confirm('确认要"' + text + '""' + row.username + '"用户吗?') + // 发起修改状态 + await UserApi.updateUserStatus(row.id, row.status) // 刷新列表 await getList() - } catch {} + } catch { + // 取消后,进行恢复按钮 + row.status = + row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE + } } /** 导出按钮操作 */ @@ -326,23 +290,56 @@ const handleExport = async () => { } } -// 重置密码 -const handleResetPwd = (row: UserVO) => { - message - .prompt('请输入"' + row.username + '"的新密码', t('common.reminder')) - .then(async ({ value }) => { - await resetUserPwdApi(row.id, value) - message.success('修改成功,新密码是:' + value) - }) - .catch((e) => { - console.error(e) - }) +/** 操作分发 */ +const handleCommand = (command: string, index: number, row: UserApi.UserVO) => { + console.log(index) + switch (command) { + case 'handleDelete': + handleDelete(row.id) + break + case 'handleResetPwd': + handleResetPwd(row) + break + case 'handleRole': + handleRole(row) + break + default: + break + } } -// 分配角色 +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await UserApi.deleteUser(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} +} + +/** 重置密码 */ +const handleResetPwd = async (row: UserApi.UserVO) => { + try { + // 重置的二次确认 + const result = await message.prompt( + '请输入"' + row.username + '"的新密码', + t('common.reminder') + ) + const password = result.value + // 发起重置 + await UserApi.resetUserPwd(row.id, password) + message.success('修改成功,新密码是:' + password) + } catch {} +} + +/** 分配角色 */ const assignRoleFormRef = ref() -const handleRole = (row: UserVO) => { - assignRoleFormRef.value?.openForm(row) +const handleRole = (row: UserApi.UserVO) => { + assignRoleFormRef.value.open(row) } /** 初始化 */ From 0a69041602cc4cc20d7e1b29d42810aff95aa558 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Fri, 31 Mar 2023 23:34:19 +0800 Subject: [PATCH 170/184] =?UTF-8?q?REVIEW=20=E7=94=A8=E6=88=B7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=EF=BC=88=E4=BF=AE=E6=94=B9=E7=94=A8=E6=88=B7=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/user/index.ts | 6 +- .../system/user/{components => }/UserForm.vue | 202 ++++++++---------- src/views/system/user/index.vue | 3 +- 3 files changed, 94 insertions(+), 117 deletions(-) rename src/views/system/user/{components => }/UserForm.vue (58%) diff --git a/src/api/system/user/index.ts b/src/api/system/user/index.ts index 90b07fe5..b53a77c5 100644 --- a/src/api/system/user/index.ts +++ b/src/api/system/user/index.ts @@ -23,17 +23,17 @@ export const getUserPage = (params: PageParam) => { } // 查询用户详情 -export const getUserApi = (id: number) => { +export const getUser = (id: number) => { return request.get({ url: '/system/user/get?id=' + id }) } // 新增用户 -export const createUserApi = (data: UserVO | Recordable) => { +export const createUser = (data: UserVO) => { return request.post({ url: '/system/user/create', data }) } // 修改用户 -export const updateUserApi = (data: UserVO | Recordable) => { +export const updateUser = (data: UserVO) => { return request.put({ url: '/system/user/update', data }) } diff --git a/src/views/system/user/components/UserForm.vue b/src/views/system/user/UserForm.vue similarity index 58% rename from src/views/system/user/components/UserForm.vue rename to src/views/system/user/UserForm.vue index a69bf971..ecf67fce 100644 --- a/src/views/system/user/components/UserForm.vue +++ b/src/views/system/user/UserForm.vue @@ -1,7 +1,12 @@ <template> - <!-- 添加或修改参数配置对话框 --> - <Dialog :title="title" :modelValue="showDialog" width="600px" append-to-body @close="closeDialog"> - <el-form ref="formRef" :model="formData" :rules="rules" label-width="80px"> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="80px" + v-loading="formLoading" + > <el-row> <el-col :span="12"> <el-form-item label="用户昵称" prop="nickname"> @@ -13,7 +18,7 @@ <el-tree-select node-key="id" v-model="formData.deptId" - :data="deptOptions" + :data="deptList" :props="defaultProps" check-strictly placeholder="请选择归属部门" @@ -67,7 +72,7 @@ <el-form-item label="岗位"> <el-select v-model="formData.postIds" multiple placeholder="请选择"> <el-option - v-for="item in postOptions" + v-for="item in postList" :key="item.id" :label="item.name" :value="item.id" @@ -85,52 +90,26 @@ </el-row> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button type="primary" @click="submitForm">确 定</el-button> - <el-button @click="cancel">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> <script lang="ts" setup> -import { PostVO } from '@/api/system/post' -import * as PostApi from '@/api/system/post' -import { createUserApi, getUserApi, updateUserApi } from '@/api/system/user' -import * as DeptApi from '@/api/system/dept' - import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { CommonStatusEnum } from '@/utils/constants' import { defaultProps, handleTree } from '@/utils/tree' -import { ElForm, FormItemRule } from 'element-plus' -import { Arrayable } from 'element-plus/es/utils' - -type Form = InstanceType<typeof ElForm> - -const emits = defineEmits(['success']) - +import * as PostApi from '@/api/system/post' +import * as DeptApi from '@/api/system/dept' +import * as UserApi from '@/api/system/user' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 -const showDialog = ref(false) -// 弹出层标题 -const title = computed(() => { - return formData.value?.id ? '修改用户' : '添加用户' -}) - -const deptOptions = ref<Tree[]>([]) // 树形结构 -const getTree = async () => { - const res = await DeptApi.getSimpleDeptList() - deptOptions.value = [] - deptOptions.value.push(...handleTree(res)) -} -// 获取岗位列表 -const postOptions = ref<PostVO[]>([]) //岗位列表 -const getPostOptions = async () => { - const res = (await PostApi.getSimplePostList()) as PostVO[] - postOptions.value.push(...res) -} - -// 表单初始化参数 -const initParams = { +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ nickname: '', deptId: '', mobile: '', @@ -138,15 +117,13 @@ const initParams = { id: undefined, username: '', password: '', - sex: 1, + sex: undefined, postIds: [], remark: '', - status: '0', + status: CommonStatusEnum.ENABLE, roleIds: [] -} - -// 校验规则 -const rules = { +}) +const formRules = reactive({ username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }], nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }], password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }], @@ -164,74 +141,75 @@ const rules = { trigger: 'blur' } ] -} as Partial<Record<string, Arrayable<FormItemRule>>> -const formRef = ref<Form | null>() -const formData = ref<Recordable>({ ...initParams }) +}) +const formRef = ref() // 表单 Ref +const deptList = ref<Tree[]>([]) // 树形结构 +const postList = ref([]) // 岗位列表 -const resetForm = () => { - let form = formRef?.value - if (!form) return - formData.value = { ...initParams } - form && (form as Form).resetFields() -} -const closeDialog = () => { - showDialog.value = false -} -// 操作成功 -const operateOk = () => { - emits('success', true) - closeDialog() -} -const submitForm = () => { - let form = formRef.value as Form - form.validate(async (valid) => { - let data = formData.value - if (valid) { - try { - if (data?.id !== undefined) { - await updateUserApi(data) - message.success(t('common.updateSuccess')) - operateOk() - } else { - await createUserApi(data) - message.success(t('common.createSuccess')) - operateOk() - } - } catch (err) { - console.error(err) - } - } - }) -} -/* 取消 */ -const cancel = () => { - closeDialog() -} - -/* 打开弹框 */ -const open = (type: string, id?: number) => { - console.log(type, id) +/** 打开弹窗 */ +const open = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type resetForm() - getTree() // 部门树 - if (row && row.id) { - const id = row.id - getUserApi(id).then((response) => { - formData.value = response - }) - } else { - formData.value = { ...initParams } + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await UserApi.getUser(id) + } finally { + formLoading.value = false + } } + // 加载部门树 + deptList.value = handleTree(await DeptApi.getSimpleDeptList()) + // 加载岗位列表 + postList.value = await PostApi.getSimplePostList() +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 - showDialog.value = true +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as UserApi.UserVO + if (formType.value === 'create') { + await UserApi.createUser(data) + message.success(t('common.createSuccess')) + } else { + await UserApi.updateUser(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } } -// ========== 初始化 ========== -onMounted(async () => { - await getPostOptions() -}) - -defineExpose({ - resetForm, - open -}) +/** 重置表单 */ +const resetForm = () => { + formData.value = { + nickname: '', + deptId: '', + mobile: '', + email: '', + id: undefined, + username: '', + password: '', + sex: undefined, + postIds: [], + remark: '', + status: CommonStatusEnum.ENABLE, + roleIds: [] + } + formRef.value?.resetFields() +} </script> diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index e57a62b4..aa2d2566 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -187,14 +187,13 @@ <!-- 分配角色 --> <UserAssignRoleForm ref="assignRoleFormRef" @success="getList" /> </template> - <script setup lang="ts" name="User"> import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' import { CommonStatusEnum } from '@/utils/constants' import * as UserApi from '@/api/system/user' -import UserForm from './components/UserForm.vue' +import UserForm from './UserForm.vue' import UserImportForm from './components/UserImportForm.vue' import UserAssignRoleForm from './UserAssignRoleForm.vue' import DeptTree from './DeptTree.vue' From cebe6f93db84937da0cf6e0fa7fd514a8e5864f4 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 1 Apr 2023 00:04:28 +0800 Subject: [PATCH 171/184] =?UTF-8?q?REVIEW=20=E7=94=A8=E6=88=B7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=EF=BC=88=E5=AF=BC=E5=85=A5=E7=94=A8=E6=88=B7=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/user/index.ts | 2 +- src/views/bpm/model/ModelImportForm.vue | 6 +- src/views/system/user/UserImportForm.vue | 129 +++++++++++++++ .../system/user/components/UserImportForm.vue | 154 ------------------ src/views/system/user/index.vue | 6 +- 5 files changed, 136 insertions(+), 161 deletions(-) create mode 100644 src/views/system/user/UserImportForm.vue delete mode 100644 src/views/system/user/components/UserImportForm.vue diff --git a/src/api/system/user/index.ts b/src/api/system/user/index.ts index b53a77c5..6224f0e8 100644 --- a/src/api/system/user/index.ts +++ b/src/api/system/user/index.ts @@ -48,7 +48,7 @@ export const exportUser = (params) => { } // 下载用户导入模板 -export const importUserTemplateApi = () => { +export const importUserTemplate = () => { return request.download({ url: '/system/user/get-import-template' }) } diff --git a/src/views/bpm/model/ModelImportForm.vue b/src/views/bpm/model/ModelImportForm.vue index ac26ac08..fe542622 100644 --- a/src/views/bpm/model/ModelImportForm.vue +++ b/src/views/bpm/model/ModelImportForm.vue @@ -8,7 +8,7 @@ :data="formData" name="bpmnFile" v-model:file-list="fileList" - :drag="true" + drag :auto-upload="false" accept=".bpmn, .xml" :limit="1" @@ -77,7 +77,7 @@ const open = async () => { } defineExpose({ open }) // 提供 open 方法,用于打开弹窗 -/** 重置表单 */ +/** 提交表单 */ const submitForm = async () => { // 校验表单 if (!formRef) return @@ -98,7 +98,7 @@ const submitForm = async () => { /** 文件上传成功 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 -const submitFormSuccess = async (response: any): Promise<void> => { +const submitFormSuccess = async (response: any) => { if (response.code !== 0) { message.error(response.msg) formLoading.value = false diff --git a/src/views/system/user/UserImportForm.vue b/src/views/system/user/UserImportForm.vue new file mode 100644 index 00000000..0e96bf29 --- /dev/null +++ b/src/views/system/user/UserImportForm.vue @@ -0,0 +1,129 @@ +<template> + <Dialog title="用户导入" v-model="modelVisible" width="400"> + <el-upload + ref="uploadRef" + :action="importUrl + '?updateSupport=' + updateSupport" + :headers="uploadHeaders" + v-model:file-list="fileList" + drag + accept=".xlsx, .xls" + :limit="1" + :on-success="submitFormSuccess" + :on-exceed="handleExceed" + :on-error="submitFormError" + :auto-upload="false" + :disabled="formLoading" + > + <Icon icon="ep:upload" /> + <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> + <template #tip> + <div class="el-upload__tip text-center"> + <div class="el-upload__tip"> + <el-checkbox v-model="updateSupport" /> 是否更新已经存在的用户数据 + </div> + <span>仅允许导入 xls、xlsx 格式文件。</span> + <el-link + type="primary" + :underline="false" + style="font-size: 12px; vertical-align: baseline" + @click="importTemplate" + > + 下载模板 + </el-link> + </div> + </template> + </el-upload> + <template #footer> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" setup> +import * as UserApi from '@/api/system/user' +import { getAccessToken, getTenantId } from '@/utils/auth' +import download from '@/utils/download' +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const formLoading = ref(false) // 表单的加载中 +const uploadRef = ref() +const importUrl = + import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import' +const uploadHeaders = ref() // 上传 Header 头 +const fileList = ref([]) // 文件列表 +const updateSupport = ref(0) // 是否更新已经存在的用户数据 + +/** 打开弹窗 */ +const open = () => { + modelVisible.value = true + resetForm() +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const submitForm = async () => { + if (fileList.value.length == 0) { + message.error('请上传文件') + return + } + // 提交请求 + uploadHeaders.value = { + Authorization: 'Bearer ' + getAccessToken(), + 'tenant-id': getTenantId() + } + formLoading.value = true + uploadRef.value!.submit() +} + +/** 文件上传成功 */ +const emits = defineEmits(['success']) +const submitFormSuccess = (response: any) => { + if (response.code !== 0) { + message.error(response.msg) + formLoading.value = false + return + } + // 拼接提示语 + const data = response.data + let text = '上传成功数量:' + data.createUsernames.length + ';' + for (let username of data.createUsernames) { + text += '< ' + username + ' >' + } + text += '更新成功数量:' + data.updateUsernames.length + ';' + for (const username of data.updateUsernames) { + text += '< ' + username + ' >' + } + text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';' + for (const username in data.failureUsernames) { + text += '< ' + username + ': ' + data.failureUsernames[username] + ' >' + } + message.alert(text) + // 发送操作成功的事件 + emits('success') +} + +/** 上传错误提示 */ +const submitFormError = (): void => { + message.error('上传失败,请您重新上传!') + formLoading.value = false +} + +/** 重置表单 */ +const resetForm = () => { + // 重置上传状态和文件 + formLoading.value = false + uploadRef.value?.clearFiles() +} + +/** 文件数超出提示 */ +const handleExceed = (): void => { + message.error('最多只能上传一个文件!') +} + +/** 下载模板操作 */ +const importTemplate = async () => { + const res = await UserApi.importUserTemplate() + download.excel(res, '用户导入模版.xls') +} +</script> diff --git a/src/views/system/user/components/UserImportForm.vue b/src/views/system/user/components/UserImportForm.vue deleted file mode 100644 index f63936e2..00000000 --- a/src/views/system/user/components/UserImportForm.vue +++ /dev/null @@ -1,154 +0,0 @@ -<template> - <Dialog - :title="upload.title" - :modelValue="showDialog" - width="400px" - append-to-body - @close="closeDialog" - > - <el-upload - ref="uploadRef" - accept=".xlsx, .xls" - :limit="1" - :headers="upload.headers" - :action="upload.url + '?updateSupport=' + upload.updateSupport" - :disabled="upload.isUploading" - :on-progress="handleFileUploadProgress" - :on-success="handleFileSuccess" - :on-exceed="handleExceed" - :on-error="excelUploadError" - :auto-upload="false" - drag - > - <Icon icon="ep:upload" /> - <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> - <template #tip> - <div class="el-upload__tip text-center"> - <div class="el-upload__tip"> - <el-checkbox v-model="upload.updateSupport" /> 是否更新已经存在的用户数据 - </div> - - <span>仅允许导入xls、xlsx格式文件。</span> - <el-link - type="primary" - :underline="false" - style="font-size: 12px; vertical-align: baseline" - @click="importTemplate" - >下载模板</el-link - > - </div> - </template> - </el-upload> - <template #footer> - <div class="dialog-footer"> - <el-button type="primary" @click="submitFileForm">确 定</el-button> - <el-button @click="cancel">取 消</el-button> - </div> - </template> - </Dialog> -</template> - -<script lang="ts" setup> -import { importUserTemplateApi } from '@/api/system/user' -import { getAccessToken, getTenantId } from '@/utils/auth' -import download from '@/utils/download' - -const emits = defineEmits(['success']) - -const message = useMessage() // 消息弹窗 - -const showDialog = ref(false) -const uploadRef = ref() - -// 用户导入参数 -const upload = reactive({ - // // 是否显示弹出层(用户导入) - // open: false, - // 弹出层标题(用户导入) - title: '用户导入', - // 是否禁用上传 - isUploading: false, - // 是否更新已经存在的用户数据 - updateSupport: 0, - // 设置上传的请求头部 - headers: { - Authorization: 'Bearer ' + getAccessToken(), - 'tenant-id': getTenantId() - }, - // 上传的地址 - url: import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import' -}) - -// 文件上传中处理 -const handleFileUploadProgress = () => { - upload.isUploading = true -} -// 文件上传成功处理 -const handleFileSuccess = (response: any) => { - if (response.code !== 0) { - message.error(response.msg) - return - } - upload.isUploading = false - uploadRef.value?.clearFiles() - // 拼接提示语 - const data = response.data - let text = '上传成功数量:' + data.createUsernames.length + ';' - for (let username of data.createUsernames) { - text += '< ' + username + ' >' - } - text += '更新成功数量:' + data.updateUsernames.length + ';' - for (const username of data.updateUsernames) { - text += '< ' + username + ' >' - } - text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';' - for (const username in data.failureUsernames) { - text += '< ' + username + ': ' + data.failureUsernames[username] + ' >' - } - message.alert(text) - emits('success') - closeDialog() -} - -// 文件数超出提示 -const handleExceed = (): void => { - message.error('最多只能上传一个文件!') -} -// 上传错误提示 -const excelUploadError = (e): void => { - console.log(e) - message.error('导入数据失败,请您重新上传!') -} - -/** 下载模板操作 */ -const importTemplate = async () => { - try { - const res = await importUserTemplateApi() - download.excel(res, '用户导入模版.xls') - } catch (error) { - console.error(error) - } -} - -/* 弹框按钮操作 */ -// 点击取消 -const cancel = () => { - closeDialog() -} -// 关闭弹窗 -const closeDialog = () => { - showDialog.value = false -} -// 提交上传文件 -const submitFileForm = () => { - uploadRef.value?.submit() -} - -const openForm = () => { - uploadRef.value?.clearFiles() - showDialog.value = true -} -defineExpose({ - openForm -}) -</script> diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index aa2d2566..19990ca9 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -194,7 +194,7 @@ import download from '@/utils/download' import { CommonStatusEnum } from '@/utils/constants' import * as UserApi from '@/api/system/user' import UserForm from './UserForm.vue' -import UserImportForm from './components/UserImportForm.vue' +import UserImportForm from './UserImportForm.vue' import UserAssignRoleForm from './UserAssignRoleForm.vue' import DeptTree from './DeptTree.vue' const message = useMessage() // 消息弹窗 @@ -250,10 +250,10 @@ const openForm = (type: string, id?: number) => { formRef.value.open(type, id) } -// 用户导入 +/** 用户导入 */ const importFormRef = ref() const handleImport = () => { - importFormRef.value?.openForm() + importFormRef.value.open() } /** 修改用户状态 */ From f8878f7a76497d4db10f56c1c6d7ed38d090eda3 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 1 Apr 2023 09:14:59 +0800 Subject: [PATCH 172/184] =?UTF-8?q?REVIEW=20=E5=AE=9A=E6=97=B6=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=EF=BC=88=E5=88=97=E8=A1=A8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types/auto-components.d.ts | 2 + src/views/infra/job/form.vue | 4 +- src/views/infra/job/index.vue | 209 +++++++++++++-------------- src/views/infra/job/utils.ts | 44 ------ src/views/infra/job/view.vue | 2 +- src/views/system/errorCode/index.vue | 3 +- src/views/system/user/index.vue | 40 ++--- 7 files changed, 131 insertions(+), 173 deletions(-) delete mode 100644 src/views/infra/job/utils.ts diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index 480691fc..80a5900f 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -23,6 +23,7 @@ declare module '@vue/runtime-core' { DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default'] Echart: typeof import('./../components/Echart/src/Echart.vue')['default'] Editor: typeof import('./../components/Editor/src/Editor.vue')['default'] + ElAutoResizer: typeof import('element-plus/es')['ElAutoResizer'] ElBadge: typeof import('element-plus/es')['ElBadge'] ElButton: typeof import('element-plus/es')['ElButton'] ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] @@ -70,6 +71,7 @@ declare module '@vue/runtime-core' { ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElTable: typeof import('element-plus/es')['ElTable'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] + ElTableV2: typeof import('element-plus/es')['ElTableV2'] ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] ElTag: typeof import('element-plus/es')['ElTag'] diff --git a/src/views/infra/job/form.vue b/src/views/infra/job/form.vue index 24488fd7..b50bcacb 100644 --- a/src/views/infra/job/form.vue +++ b/src/views/infra/job/form.vue @@ -107,7 +107,7 @@ const formRules = reactive({ const formRef = ref() // 表单 Ref /** 打开弹窗 */ -const openModal = async (type: string, id?: number) => { +const open = async (type: string, id?: number) => { modelVisible.value = true modelTitle.value = t('action.' + type) formType.value = type @@ -122,7 +122,7 @@ const openModal = async (type: string, id?: number) => { } } } -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 /** cron表达式按钮操作 */ const handleShowCron = () => { diff --git a/src/views/infra/job/index.vue b/src/views/infra/job/index.vue index 702b31fe..bc4dfebc 100644 --- a/src/views/infra/job/index.vue +++ b/src/views/infra/job/index.vue @@ -1,19 +1,31 @@ <template> <content-wrap> - <!-- 搜索栏 --> - <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="100px"> + <!-- 搜索工作栏 --> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="100px" + > <el-form-item label="任务名称" prop="name"> <el-input v-model="queryParams.name" placeholder="请输入任务名称" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="任务状态" prop="status"> - <el-select v-model="queryParams.status" placeholder="请选择任务状态" clearable> + <el-select + v-model="queryParams.status" + placeholder="请选择任务状态" + clearable + class="!w-240px" + > <el-option - v-for="dict in getDictOptions(DICT_TYPE.INFRA_JOB_STATUS)" + v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_JOB_STATUS)" :key="dict.value" :label="dict.label" :value="dict.value" @@ -26,6 +38,7 @@ placeholder="请输入处理器的名字" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item> @@ -34,7 +47,7 @@ <el-button type="primary" plain - @click="openModal('create')" + @click="openForm('create')" v-hasPermi="['infra:job:create']" > <Icon icon="ep:plus" class="mr-5px" /> 新增 @@ -48,63 +61,66 @@ > <Icon icon="ep:download" class="mr-5px" /> 导出 </el-button> - <el-button type="info" plain @click="handleJobLog" v-hasPermi="['infra:job:query']"> <Icon icon="ep:zoom-in" class="mr-5px" /> 执行日志 </el-button> </el-form-item> </el-form> + </content-wrap> + <!-- 列表 --> + <content-wrap> <el-table v-loading="loading" :data="list"> <el-table-column label="任务编号" align="center" prop="id" /> <el-table-column label="任务名称" align="center" prop="name" /> <el-table-column label="任务状态" align="center" prop="status"> <template #default="scope"> <dict-tag :type="DICT_TYPE.INFRA_JOB_STATUS" :value="scope.row.status" /> - </template> </el-table-column - >> + </template> + </el-table-column> <el-table-column label="处理器的名字" align="center" prop="handlerName" /> <el-table-column label="处理器的参数" align="center" prop="handlerParam" /> <el-table-column label="CRON 表达式" align="center" prop="cronExpression" /> - <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <el-table-column label="操作" align="center" width="200"> <template #default="scope"> <el-button + type="primary" link - icon="el-icon-edit" - @click="openModal('update', scope.row.id)" + @click="openForm('update', scope.row.id)" v-hasPermi="['infra:job:update']" - >修改</el-button > + 修改 + </el-button> <el-button + type="primary" link - icon="el-icon-check" @click="handleChangeStatus(scope.row)" v-hasPermi="['infra:job:update']" - >{{ scope.row.status === InfraJobStatusEnum.STOP ? '开启' : '暂停' }}</el-button > + {{ scope.row.status === InfraJobStatusEnum.STOP ? '开启' : '暂停' }} + </el-button> <el-button + type="danger" link - icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['infra:job:delete']" - >删除</el-button > + 删除 + </el-button> <el-dropdown - class="mt-1" - :teleported="true" @command="(command) => handleCommand(command, scope.row)" v-hasPermi="['infra:job:trigger', 'infra:job:query']" > - <el-button link icon="el-icon-d-arrow-right">更多</el-button> + <el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button> <template #dropdown> <el-dropdown-menu> - <el-dropdown-item command="handleRun" v-if="hasPermi(['infra:job:trigger'])"> + <el-dropdown-item command="handleRun" v-if="checkPermi(['infra:job:trigger'])"> 执行一次 </el-dropdown-item> - <el-dropdown-item command="handleView" v-if="hasPermi(['infra:job:query'])"> + <el-dropdown-item command="handleView" v-if="checkPermi(['infra:job:query'])"> 任务详细 </el-dropdown-item> - <el-dropdown-item command="handleJobLog" v-if="hasPermi(['infra:job:query'])"> + <el-dropdown-item command="handleJobLog" v-if="checkPermi(['infra:job:query'])"> 调度日志 </el-dropdown-item> </el-dropdown-menu> @@ -114,8 +130,7 @@ </el-table-column> </el-table> <!-- 分页组件 --> - <pagination - v-show="total > 0" + <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @@ -124,23 +139,21 @@ </content-wrap> <!-- 表单弹窗:添加/修改 --> - <job-form ref="modalRef" @success="getList" /> + <job-form ref="formRef" @success="getList" /> <!-- 表单弹窗:查看 --> <job-view ref="viewModalRef" @success="getList" /> </template> - <script setup lang="ts" name="Job"> -import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { checkPermi } from '@/utils/permission' import JobForm from './form.vue' import JobView from './view.vue' import download from '@/utils/download' import * as JobApi from '@/api/infra/job' import { InfraJobStatusEnum } from '@/utils/constants' -import { CACHE_KEY, useCache } from '@/hooks/web/useCache' - const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 -const { push } = useRouter() +const { push } = useRouter() // 路由 const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 @@ -167,30 +180,6 @@ const getList = async () => { } } -const handleChangeStatus = async (row: JobApi.JobVO) => { - const text = row.status === InfraJobStatusEnum.STOP ? '开启' : '关闭' - - const status = - row.status === InfraJobStatusEnum.STOP ? InfraJobStatusEnum.NORMAL : InfraJobStatusEnum.STOP - message - .confirm('确认要' + text + '定时任务编号为"' + row.id + '"的数据项?', t('common.reminder')) - .then(async () => { - row.status = - row.status === InfraJobStatusEnum.NORMAL - ? InfraJobStatusEnum.NORMAL - : InfraJobStatusEnum.STOP - await JobApi.updateJobStatusApi(row.id, status) - message.success(text + '成功') - await getList() - }) - .catch(() => { - row.status = - row.status === InfraJobStatusEnum.NORMAL - ? InfraJobStatusEnum.STOP - : InfraJobStatusEnum.NORMAL - }) -} - /** 搜索按钮操作 */ const handleQuery = () => { queryParams.pageNo = 1 @@ -203,10 +192,47 @@ const resetQuery = () => { handleQuery() } +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await JobApi.exportJobApi(queryParams) + download.excel(data, '定时任务.xls') + } catch { + } finally { + exportLoading.value = false + } +} + /** 添加/修改操作 */ -const modalRef = ref() -const openModal = (type: string, id?: number) => { - modalRef.value.openModal(type, id) +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) +} + +/** 修改状态操作 */ +const handleChangeStatus = async (row: JobApi.JobVO) => { + try { + // 修改状态的二次确认 + const text = row.status === InfraJobStatusEnum.STOP ? '开启' : '关闭' + await message.confirm( + '确认要' + text + '定时任务编号为"' + row.id + '"的数据项?', + t('common.reminder') + ) + const status = + row.status === InfraJobStatusEnum.STOP ? InfraJobStatusEnum.NORMAL : InfraJobStatusEnum.STOP + await JobApi.updateJobStatusApi(row.id, status) + message.success(text + '成功') + // 刷新列表 + await getList() + } catch { + // 取消后,进行恢复按钮 + row.status = + row.status === InfraJobStatusEnum.NORMAL ? InfraJobStatusEnum.STOP : InfraJobStatusEnum.NORMAL + } } /** 删除按钮操作 */ @@ -222,28 +248,6 @@ const handleDelete = async (id: number) => { } catch {} } -/** 查看操作 */ -const viewModalRef = ref() -const handleView = (rowId?: number) => { - viewModalRef.value.openModal(rowId) -} -// 执行日志 -const handleJobLog = (rowId?: number) => { - if (rowId) { - push('/job/job-log?id=' + rowId) - } else { - push('/job/job-log') - } -} -// 执行一次 -const handleRun = (row: JobApi.JobVO) => { - message.confirm('确认要立即执行一次' + row.name + '?', t('common.reminder')).then(async () => { - await JobApi.runJobApi(row.id) - message.success('执行成功') - await getList() - }) -} - /** '更多'操作按钮 */ const handleCommand = (command, row) => { switch (command) { @@ -261,36 +265,31 @@ const handleCommand = (command, row) => { } } -/** 导出按钮操作 */ -const handleExport = async () => { +/** 执行一次 */ +const handleRun = async (row: JobApi.JobVO) => { try { - // 导出的二次确认 - await message.exportConfirm() - // 发起导出 - exportLoading.value = true - const data = await JobApi.exportJobApi(queryParams) - download.excel(data, '定时任务.xls') - } catch { - } finally { - exportLoading.value = false - } + // 二次确认 + await message.confirm('确认要立即执行一次' + row.name + '?', t('common.reminder')) + // 提交执行 + await JobApi.runJobApi(row.id) + message.success('执行成功') + // 刷新列表 + await getList() + } catch {} } -// 权限判断:dropdown 与 v-hasPermi有冲突会造成大量的waring,改用v-if调用此方法 -const hasPermi = (permiKeys: string[]) => { - const { wsCache } = useCache() - const all_permission = '*:*:*' - const permissions = wsCache.get(CACHE_KEY.USER).permissions - - if (permiKeys && permiKeys instanceof Array && permiKeys.length > 0) { - const permissionFlag = permiKeys - - const hasPermissions = permissions.some((permission: string) => { - return all_permission === permission || permissionFlag.includes(permission) - }) - return hasPermissions +/** 查看操作 */ +const viewModalRef = ref() +const handleView = (rowId?: number) => { + viewModalRef.value.openForm(rowId) +} +// 执行日志 +const handleJobLog = (rowId?: number) => { + if (rowId) { + push('/job/job-log?id=' + rowId) + } else { + push('/job/job-log') } - return false } /** 初始化 **/ diff --git a/src/views/infra/job/utils.ts b/src/views/infra/job/utils.ts deleted file mode 100644 index a3774f22..00000000 --- a/src/views/infra/job/utils.ts +++ /dev/null @@ -1,44 +0,0 @@ -export const parseTime = (time) => { - if (!time) { - return null - } - const format = '{y}-{m}-{d} {h}:{i}:{s}' - let date - if (typeof time === 'object') { - date = time - } else { - if (typeof time === 'string' && /^[0-9]+$/.test(time)) { - time = parseInt(time) - } else if (typeof time === 'string') { - time = time - .replace(new RegExp(/-/gm), '/') - .replace('T', ' ') - .replace(new RegExp(/\.[\d]{3}/gm), '') - } - if (typeof time === 'number' && time.toString().length === 10) { - time = time * 1000 - } - date = new Date(time) - } - const formatObj = { - y: date.getFullYear(), - m: date.getMonth() + 1, - d: date.getDate(), - h: date.getHours(), - i: date.getMinutes(), - s: date.getSeconds(), - a: date.getDay() - } - const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { - let value = formatObj[key] - // Note: getDay() returns 0 on Sunday - if (key === 'a') { - return ['日', '一', '二', '三', '四', '五', '六'][value] - } - if (result.length > 0 && value < 10) { - value = '0' + value - } - return value || 0 - }) - return time_str -} diff --git a/src/views/infra/job/view.vue b/src/views/infra/job/view.vue index d195e0e3..c30e235a 100644 --- a/src/views/infra/job/view.vue +++ b/src/views/infra/job/view.vue @@ -40,7 +40,7 @@ </template> <script setup lang="ts" name="JobView"> import * as JobApi from '@/api/infra/job' -import { parseTime } from './utils' +import { parseTime } from '@/utils/formatTime' import { DICT_TYPE } from '@/utils/dict' const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 diff --git a/src/views/system/errorCode/index.vue b/src/views/system/errorCode/index.vue index c95d652c..e94b00f0 100644 --- a/src/views/system/errorCode/index.vue +++ b/src/views/system/errorCode/index.vue @@ -121,8 +121,7 @@ </el-table-column> </el-table> <!-- 分页组件 --> - <pagination - v-show="total > 0" + <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index 19990ca9..b0f7f964 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -137,7 +137,7 @@ <Icon icon="ep:edit" />修改 </el-button> <el-dropdown - @command="(command) => handleCommand(command, scope.$index, scope.row)" + @command="(command) => handleCommand(command, scope.row)" v-hasPermi="[ 'system:user:delete', 'system:user:update-password', @@ -147,22 +147,24 @@ <el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button> <template #dropdown> <el-dropdown-menu> - <!-- div包住避免控制台报错:Runtime directive used on component with non-element root node --> - <div v-hasPermi="['system:user:delete']"> - <el-dropdown-item command="handleDelete"> - <Icon icon="ep:delete" />删除 - </el-dropdown-item> - </div> - <div v-hasPermi="['system:user:update-password']"> - <el-dropdown-item command="handleResetPwd"> - <Icon icon="ep:key" />重置密码 - </el-dropdown-item> - </div> - <div v-hasPermi="['system:permission:assign-user-role']"> - <el-dropdown-item command="handleRole"> - <Icon icon="ep:circle-check" />分配角色 - </el-dropdown-item> - </div> + <el-dropdown-item + command="handleDelete" + v-if="checkPermi(['system:user:delete'])" + > + <Icon icon="ep:delete" />删除 + </el-dropdown-item> + <el-dropdown-item + command="handleResetPwd" + v-if="checkPermi(['system:user:update-password'])" + > + <Icon icon="ep:key" />重置密码 + </el-dropdown-item> + <el-dropdown-item + command="handleRole" + v-if="checkPermi(['system:permission:assign-user-role'])" + > + <Icon icon="ep:circle-check" />分配角色 + </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> @@ -189,6 +191,7 @@ </template> <script setup lang="ts" name="User"> import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { checkPermi } from '@/utils/permission' import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' import { CommonStatusEnum } from '@/utils/constants' @@ -290,8 +293,7 @@ const handleExport = async () => { } /** 操作分发 */ -const handleCommand = (command: string, index: number, row: UserApi.UserVO) => { - console.log(index) +const handleCommand = (command: string, row: UserApi.UserVO) => { switch (command) { case 'handleDelete': handleDelete(row.id) From 20b3eacce9070ab514b03c06c6344993f900cd9a Mon Sep 17 00:00:00 2001 From: Chika <wbs_2018@sina.com> Date: Sat, 1 Apr 2023 10:03:11 +0800 Subject: [PATCH 173/184] =?UTF-8?q?=E3=80=90=E9=87=8D=E6=9E=84=E3=80=91Vue?= =?UTF-8?q?3=20=E7=AE=A1=E7=90=86=E5=90=8E=E5=8F=B0=EF=BC=9A[=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=AE=A1=E7=90=86=20->=20=E8=A7=92=E8=89=B2=E7=AE=A1?= =?UTF-8?q?=E7=90=86]=20=E4=BD=BF=E7=94=A8=20Element=20Plus=20=E5=8E=9F?= =?UTF-8?q?=E7=94=9F=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/system/role/DataPermissionForm.vue | 174 +++++++++++++++++++ src/views/system/role/MenuPermissionForm.vue | 84 ++------- src/views/system/role/index.vue | 14 +- 3 files changed, 203 insertions(+), 69 deletions(-) create mode 100644 src/views/system/role/DataPermissionForm.vue diff --git a/src/views/system/role/DataPermissionForm.vue b/src/views/system/role/DataPermissionForm.vue new file mode 100644 index 00000000..bccc5f46 --- /dev/null +++ b/src/views/system/role/DataPermissionForm.vue @@ -0,0 +1,174 @@ +<template> + <Dialog :title="dialogScopeTitle" v-model="dialogScopeVisible" width="800"> + <el-form + ref="dataPermissionFormRef" + :model="dataScopeForm" + :inline="true" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="角色名称"> + <el-tag>{{ dataScopeForm.name }}</el-tag> + </el-form-item> + <el-form-item label="角色标识"> + <el-tag>{{ dataScopeForm.code }}</el-tag> + </el-form-item> + <!-- 分配角色的数据权限对话框 --> + <el-form-item label="权限范围"> + <el-select v-model="dataScopeForm.dataScope"> + <el-option + v-for="item in dataScopeDictDatas" + :key="item.value" + :label="item.label" + :value="item.value" + /> + </el-select> + </el-form-item> + </el-form> + <!-- 分配角色的菜单权限对话框 --> + <el-row> + <el-col :span="24"> + <el-form-item + label="权限范围" + v-if=" + actionScopeType === 'menu' || + dataScopeForm.dataScope === SystemDataScopeEnum.DEPT_CUSTOM + " + style="display: flex" + > + <el-card class="card" shadow="never"> + <template #header> + 父子联动(选中父节点,自动选择子节点): + <el-switch + v-model="checkStrictly" + inline-prompt + active-text="是" + inactive-text="否" + /> + 全选/全不选: + <el-switch + v-model="treeNodeAll" + inline-prompt + active-text="是" + inactive-text="否" + @change="handleCheckedTreeNodeAll()" + /> + </template> + <el-tree + ref="treeRef" + node-key="id" + show-checkbox + :check-strictly="!checkStrictly" + :props="defaultProps" + :data="treeOptions" + empty-text="加载中,请稍后" + /> + </el-card> + </el-form-item> </el-col + ></el-row> + + <!-- 操作按钮 --> + <template #footer> + <div class="dialog-footer"> + <el-button + :title="t('action.save')" + :loading="actionLoading" + @click="submitScope()" + type="primary" + :disabled="formLoading" + > + 保存 + </el-button> + <el-button + :loading="actionLoading" + :title="t('dialog.close')" + @click="dialogScopeVisible = false" + >取 消</el-button + > + </div> + </template> + </Dialog> +</template> + +<script setup lang="ts"> +import * as RoleApi from '@/api/system/role' +import type { ElTree } from 'element-plus' +import type { FormExpose } from '@/components/Form' +import { handleTree, defaultProps } from '@/utils/tree' +import { SystemDataScopeEnum } from '@/utils/constants' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import * as DeptApi from '@/api/system/dept' +import * as PermissionApi from '@/api/system/permission' +// ========== CRUD 相关 ========== +const actionLoading = ref(false) // 遮罩层 +const dataPermissionFormRef = ref<FormExpose>() // 表单 Ref +const { t } = useI18n() // 国际化 +const dialogScopeTitle = ref('菜单权限') +const dataScopeDictDatas = ref() +const message = useMessage() // 消息弹窗 +const actionScopeType = ref('') +// 选项 +const treeNodeAll = ref(false) +const checkStrictly = ref(true) +const dialogScopeVisible = ref(false) // 弹窗的是否展示 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const treeOptions = ref<any[]>([]) // 菜单树形结构 +const treeRef = ref<InstanceType<typeof ElTree>>() +// ========== 数据权限 ========== +const dataScopeForm = reactive({ + id: 0, + name: '', + code: '', + dataScope: 0, + checkList: [] +}) + +/** 打开弹窗 */ +const openModal = async (type: string, row: RoleApi.RoleVO) => { + dataScopeForm.id = row.id + dataScopeForm.name = row.name + dataScopeForm.code = row.code + actionScopeType.value = type + dialogScopeVisible.value = true + const deptRes = await DeptApi.getSimpleDeptList() + treeOptions.value = handleTree(deptRes) + const role = await RoleApi.getRole(row.id) + dataScopeForm.dataScope = role.dataScope + if (role.dataScopeDeptIds) { + role.dataScopeDeptIds?.forEach((item: any) => { + unref(treeRef)?.setChecked(item, true, false) + }) + } +} + +// 保存权限 +const submitScope = async () => { + const data = ref<PermissionApi.PermissionAssignRoleDataScopeReqVO>({ + roleId: dataScopeForm.id, + dataScope: dataScopeForm.dataScope, + dataScopeDeptIds: + dataScopeForm.dataScope !== SystemDataScopeEnum.DEPT_CUSTOM + ? [] + : (treeRef.value!.getCheckedKeys(false) as unknown as Array<number>) + }) + await PermissionApi.assignRoleDataScopeApi(data.value) + + message.success(t('common.updateSuccess')) + dialogScopeVisible.value = false +} + +// 全选/全不选 +const handleCheckedTreeNodeAll = () => { + treeRef.value!.setCheckedNodes(treeNodeAll.value ? treeOptions.value : []) +} + +const init = () => { + dataScopeDictDatas.value = getIntDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE) +} + +defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +// ========== 初始化 ========== +onMounted(() => { + init() +}) +</script> diff --git a/src/views/system/role/MenuPermissionForm.vue b/src/views/system/role/MenuPermissionForm.vue index 70971781..a7995fec 100644 --- a/src/views/system/role/MenuPermissionForm.vue +++ b/src/views/system/role/MenuPermissionForm.vue @@ -13,28 +13,10 @@ <el-form-item label="角色标识"> <el-tag>{{ dataScopeForm.code }}</el-tag> </el-form-item> - <!-- 分配角色的数据权限对话框 --> - <el-form-item label="权限范围" v-if="actionScopeType === 'data'"> - <el-select v-model="dataScopeForm.dataScope"> - <el-option - v-for="item in dataScopeDictDatas" - :key="item.value" - :label="item.label" - :value="item.value" - /> - </el-select> - </el-form-item> <!-- 分配角色的菜单权限对话框 --> <el-row> <el-col :span="24"> - <el-form-item - label="权限范围" - v-if=" - actionScopeType === 'menu' || - dataScopeForm.dataScope === SystemDataScopeEnum.DEPT_CUSTOM - " - style="display: flex" - > + <el-form-item label="权限范围" style="display: flex"> <el-card class="card" shadow="never"> <template #header> 父子联动(选中父节点,自动选择子节点): @@ -94,10 +76,8 @@ import * as RoleApi from '@/api/system/role' import type { ElTree } from 'element-plus' import type { FormExpose } from '@/components/Form' import { handleTree, defaultProps } from '@/utils/tree' -import { SystemDataScopeEnum } from '@/utils/constants' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import * as MenuApi from '@/api/system/menu' -import * as DeptApi from '@/api/system/dept' import * as PermissionApi from '@/api/system/permission' // ========== CRUD 相关 ========== const actionLoading = ref(false) // 遮罩层 @@ -130,50 +110,27 @@ const openModal = async (type: string, row: RoleApi.RoleVO) => { dataScopeForm.code = row.code actionScopeType.value = type dialogScopeVisible.value = true - if (type === 'menu') { - const menuRes = await MenuApi.getSimpleMenusList() - treeOptions.value = handleTree(menuRes) - const role = await PermissionApi.listRoleMenusApi(row.id) - if (role) { - role?.forEach((item: any) => { - unref(treeRef)?.setChecked(item, true, false) - }) - } - } else if (type === 'data') { - const deptRes = await DeptApi.getSimpleDeptList() - treeOptions.value = handleTree(deptRes) - const role = await RoleApi.getRole(row.id) - dataScopeForm.dataScope = role.dataScope - if (role.dataScopeDeptIds) { - role.dataScopeDeptIds?.forEach((item: any) => { - unref(treeRef)?.setChecked(item, true, false) - }) - } + const menuRes = await MenuApi.getSimpleMenusList() + treeOptions.value = handleTree(menuRes) + const role = await PermissionApi.listRoleMenusApi(row.id) + if (role) { + role?.forEach((item: any) => { + unref(treeRef)?.setChecked(item, true, false) + }) } } // 保存权限 const submitScope = async () => { - if ('data' === actionScopeType.value) { - const data = ref<PermissionApi.PermissionAssignRoleDataScopeReqVO>({ - roleId: dataScopeForm.id, - dataScope: dataScopeForm.dataScope, - dataScopeDeptIds: - dataScopeForm.dataScope !== SystemDataScopeEnum.DEPT_CUSTOM - ? [] - : (treeRef.value!.getCheckedKeys(false) as unknown as Array<number>) - }) - await PermissionApi.assignRoleDataScopeApi(data.value) - } else if ('menu' === actionScopeType.value) { - const data = ref<PermissionApi.PermissionAssignRoleMenuReqVO>({ - roleId: dataScopeForm.id, - menuIds: [ - ...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>), - ...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>) - ] - }) - await PermissionApi.assignRoleMenuApi(data.value) - } + const data = ref<PermissionApi.PermissionAssignRoleMenuReqVO>({ + roleId: dataScopeForm.id, + menuIds: [ + ...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>), + ...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>) + ] + }) + await PermissionApi.assignRoleMenuApi(data.value) + message.success(t('common.updateSuccess')) dialogScopeVisible.value = false } @@ -193,10 +150,3 @@ onMounted(() => { init() }) </script> -<style scoped> -.card { - width: 100%; - max-height: 400px; - overflow-y: scroll; -} -</style> diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue index af8e0d3a..4be8e48c 100644 --- a/src/views/system/role/index.vue +++ b/src/views/system/role/index.vue @@ -146,11 +146,14 @@ <RoleForm ref="formRef" @success="getList" /> <!-- 表单弹窗:菜单权限 --> <MenuPermissionForm ref="menuPermissionFormRef" @success="getList" /> + <!-- 表单弹窗:数据权限 --> + <DataPermissionForm ref="dataPermissionFormRef" @success="getList" /> </template> <script setup lang="tsx"> import * as RoleApi from '@/api/system/role' import RoleForm from './RoleForm.vue' import MenuPermissionForm from './MenuPermissionForm.vue' +import DataPermissionForm from './DataPermissionForm.vue' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' @@ -201,10 +204,17 @@ const openForm = (type: string, id?: number) => { formRef.value.open(type, id) } -/** 数据权限操作 */ +/** 菜单权限操作 */ const menuPermissionFormRef = ref() +/** 数据权限操作 */ +const dataPermissionFormRef = ref() + const handleScope = async (type: string, row: RoleApi.RoleVO) => { - menuPermissionFormRef.value.openModal(type, row) + if (type === 'menu') { + menuPermissionFormRef.value.openModal(type, row) + } else if (type === 'data') { + dataPermissionFormRef.value.openModal(type, row) + } } /** 删除按钮操作 */ From eace25d3a20a8fd0aa5193542fce426b36d4bdbc Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 1 Apr 2023 11:33:50 +0800 Subject: [PATCH 174/184] =?UTF-8?q?REVIEW=20=E5=AE=9A=E6=97=B6=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=EF=BC=88=E8=A1=A8=E5=8D=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Crontab/src/Crontab.vue | 5 +- src/views/infra/job/{form.vue => JobForm.vue} | 70 ++++--------------- src/views/infra/job/index.vue | 4 +- 3 files changed, 19 insertions(+), 60 deletions(-) rename src/views/infra/job/{form.vue => JobForm.vue} (69%) diff --git a/src/components/Crontab/src/Crontab.vue b/src/components/Crontab/src/Crontab.vue index fe33bd5f..0e474fb2 100644 --- a/src/components/Crontab/src/Crontab.vue +++ b/src/components/Crontab/src/Crontab.vue @@ -6,7 +6,10 @@ interface shortcutsType { value: string } const props = defineProps({ - modelValue: { type: String, default: '* * * * * ?' }, + modelValue: { + type: String, + default: '* * * * * ?' + }, shortcuts: { type: Array as PropType<shortcutsType[]>, default: () => [] } }) const defaultValue = ref('') diff --git a/src/views/infra/job/form.vue b/src/views/infra/job/JobForm.vue similarity index 69% rename from src/views/infra/job/form.vue rename to src/views/infra/job/JobForm.vue index b50bcacb..585370bf 100644 --- a/src/views/infra/job/form.vue +++ b/src/views/infra/job/JobForm.vue @@ -1,5 +1,4 @@ <template> - <!-- 添加或修改定时任务对话框 --> <Dialog :title="modelTitle" v-model="modelVisible"> <el-form ref="formRef" @@ -22,14 +21,7 @@ <el-input v-model="formData.handlerParam" placeholder="请输入处理器的参数" /> </el-form-item> <el-form-item label="CRON 表达式" prop="cronExpression"> - <el-input v-model="formData.cronExpression" placeholder="请输入CRON 表达式"> - <template #append> - <el-button type="primary" @click="handleShowCron"> - 生成表达式 - <i class="el-icon-time el-icon--right"></i> - </el-button> - </template> - </el-input> + <crontab v-model="formData.cronExpression" /> </el-form-item> <el-form-item label="重试次数" prop="retryCount"> <el-input @@ -47,30 +39,14 @@ <el-input v-model="formData.monitorTimeout" placeholder="请输入监控超时时间,单位:毫秒" /> </el-form-item> </el-form> - <!-- 操作按钮 --> <template #footer> - <!-- 按钮:保存 --> - <div class="dialog-footer"> - <el-button type="primary" @click="submitForm" :loading="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button type="primary" @click="submitForm" :loading="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> - <el-dialog - title="Cron表达式生成器" - v-model="openCron" - append-to-body - class="scrollbar" - destroy-on-close - > - <crontab @hide="openCron = false" @fill="crontabFill" :expression="expression" /> - </el-dialog> </template> <script setup lang="ts" name="JobForm"> import * as JobApi from '@/api/infra/job' - -const emit = defineEmits(['success', 'crontabFill']) // 定义 success 事件,用于操作成功后的回调 - const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -78,25 +54,13 @@ const modelVisible = ref(false) // 弹窗的是否展示 const modelTitle = ref('') // 弹窗的标题 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formType = ref('') // 表单的类型:create - 新增;update - 修改 -const defaultFormData = { +const formData = ref({ id: undefined, name: '', - status: 0, handlerName: '', handlerParam: '', - cronExpression: '', - retryCount: 0, - retryInterval: 0, - monitorTimeout: 0, - createTime: new Date() -} -const formData = ref({ ...defaultFormData }) - -// 是否显示Cron表达式弹出层 -const openCron = ref(false) -// 传入的表达式 -const expression = ref('') -// 表单校验 + cronExpression: '' +}) const formRules = reactive({ name: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }], handlerName: [{ required: true, message: '处理器的名字不能为空', trigger: 'blur' }], @@ -124,20 +88,8 @@ const open = async (type: string, id?: number) => { } defineExpose({ open }) // 提供 open 方法,用于打开弹窗 -/** cron表达式按钮操作 */ -const handleShowCron = () => { - console.info(123333333333) - expression.value = formData.value.cronExpression - openCron.value = true -} - -// cron表达式填充 -const crontabFill = (expression: string) => { - formData.value.cronExpression = expression - emit('crontabFill', expression) -} - -// 提交按钮 +/** 提交按钮 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 const submitForm = async () => { // 校验表单 if (!formRef) return @@ -165,7 +117,11 @@ const submitForm = async () => { /** 重置表单 */ const resetForm = () => { formData.value = { - ...defaultFormData + id: undefined, + name: '', + handlerName: '', + handlerParam: '', + cronExpression: '' } formRef.value?.resetFields() } diff --git a/src/views/infra/job/index.vue b/src/views/infra/job/index.vue index bc4dfebc..7cb4b98b 100644 --- a/src/views/infra/job/index.vue +++ b/src/views/infra/job/index.vue @@ -139,14 +139,14 @@ </content-wrap> <!-- 表单弹窗:添加/修改 --> - <job-form ref="formRef" @success="getList" /> + <JobForm ref="formRef" @success="getList" /> <!-- 表单弹窗:查看 --> <job-view ref="viewModalRef" @success="getList" /> </template> <script setup lang="ts" name="Job"> import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { checkPermi } from '@/utils/permission' -import JobForm from './form.vue' +import JobForm from './JobForm.vue' import JobView from './view.vue' import download from '@/utils/download' import * as JobApi from '@/api/infra/job' From 65663df3aaf0f21b4807da26c9cad145a9a0f83d Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 1 Apr 2023 13:41:21 +0800 Subject: [PATCH 175/184] =?UTF-8?q?REVIEW=20=E5=AE=9A=E6=97=B6=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=EF=BC=88=E8=AF=A6=E7=BB=86=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/infra/job/JobDetail.vue | 71 ++++++++++++++++++++++++ src/views/infra/job/index.vue | 17 +++--- src/views/infra/job/view.vue | 89 ------------------------------- 3 files changed, 80 insertions(+), 97 deletions(-) create mode 100644 src/views/infra/job/JobDetail.vue delete mode 100644 src/views/infra/job/view.vue diff --git a/src/views/infra/job/JobDetail.vue b/src/views/infra/job/JobDetail.vue new file mode 100644 index 00000000..b8b01584 --- /dev/null +++ b/src/views/infra/job/JobDetail.vue @@ -0,0 +1,71 @@ +<template> + <Dialog title="任务详细" v-model="modelVisible" width="700px"> + <el-descriptions border :column="1"> + <el-descriptions-item label="任务编号" min-width="60"> + {{ detailData.id }} + </el-descriptions-item> + <el-descriptions-item label="任务名称"> + {{ detailData.name }} + </el-descriptions-item> + <el-descriptions-item label="任务名称"> + <dict-tag :type="DICT_TYPE.INFRA_JOB_STATUS" :value="detailData.status" /> + </el-descriptions-item> + <el-descriptions-item label="处理器的名字"> + {{ detailData.handlerName }} + </el-descriptions-item> + <el-descriptions-item label="处理器的参数"> + {{ detailData.handlerParam }} + </el-descriptions-item> + <el-descriptions-item label="Cron 表达式"> + {{ detailData.cronExpression }} + </el-descriptions-item> + <el-descriptions-item label="重试次数"> + {{ detailData.retryCount }} + </el-descriptions-item> + <el-descriptions-item label="重试间隔"> + {{ detailData.retryInterval + ' 毫秒' }} + </el-descriptions-item> + <el-descriptions-item label="监控超时时间"> + {{ detailData.monitorTimeout > 0 ? detailData.monitorTimeout + ' 毫秒' : '未开启' }} + </el-descriptions-item> + <el-descriptions-item label="后续执行时间"> + <el-timeline> + <el-timeline-item + v-for="(nextTime, index) in nextTimes" + :key="index" + :timestamp="formatDate(nextTime)" + > + 第 {{ index + 1 }} 次 + </el-timeline-item> + </el-timeline> + </el-descriptions-item> + </el-descriptions> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE } from '@/utils/dict' +import { formatDate } from '@/utils/formatTime' +import * as JobApi from '@/api/infra/job' + +const modelVisible = ref(false) // 弹窗的是否展示 +const detailLoading = ref(false) // 表单的加载中 +const detailData = ref({}) // 详情数据 +const nextTimes = ref([]) // 下一轮执行时间的数组 + +/** 打开弹窗 */ +const open = async (id: number) => { + modelVisible.value = true + // 查看,设置数据 + if (id) { + detailLoading.value = true + try { + detailData.value = await JobApi.getJobApi(id) + // 获取下一次执行时间 + nextTimes.value = await JobApi.getJobNextTimesApi(id) + } finally { + detailLoading.value = false + } + } +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 +</script> diff --git a/src/views/infra/job/index.vue b/src/views/infra/job/index.vue index 7cb4b98b..13a8ccd5 100644 --- a/src/views/infra/job/index.vue +++ b/src/views/infra/job/index.vue @@ -117,7 +117,7 @@ <el-dropdown-item command="handleRun" v-if="checkPermi(['infra:job:trigger'])"> 执行一次 </el-dropdown-item> - <el-dropdown-item command="handleView" v-if="checkPermi(['infra:job:query'])"> + <el-dropdown-item command="openDetail" v-if="checkPermi(['infra:job:query'])"> 任务详细 </el-dropdown-item> <el-dropdown-item command="handleJobLog" v-if="checkPermi(['infra:job:query'])"> @@ -141,13 +141,13 @@ <!-- 表单弹窗:添加/修改 --> <JobForm ref="formRef" @success="getList" /> <!-- 表单弹窗:查看 --> - <job-view ref="viewModalRef" @success="getList" /> + <JobDetail ref="detailRef" /> </template> <script setup lang="ts" name="Job"> import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { checkPermi } from '@/utils/permission' import JobForm from './JobForm.vue' -import JobView from './view.vue' +import JobDetail from './JobDetail.vue' import download from '@/utils/download' import * as JobApi from '@/api/infra/job' import { InfraJobStatusEnum } from '@/utils/constants' @@ -254,8 +254,8 @@ const handleCommand = (command, row) => { case 'handleRun': handleRun(row) break - case 'handleView': - handleView(row?.id) + case 'openDetail': + openDetail(row.id) break case 'handleJobLog': handleJobLog(row?.id) @@ -279,10 +279,11 @@ const handleRun = async (row: JobApi.JobVO) => { } /** 查看操作 */ -const viewModalRef = ref() -const handleView = (rowId?: number) => { - viewModalRef.value.openForm(rowId) +const detailRef = ref() +const openDetail = (id: number) => { + detailRef.value.open(id) } + // 执行日志 const handleJobLog = (rowId?: number) => { if (rowId) { diff --git a/src/views/infra/job/view.vue b/src/views/infra/job/view.vue deleted file mode 100644 index c30e235a..00000000 --- a/src/views/infra/job/view.vue +++ /dev/null @@ -1,89 +0,0 @@ -<template> - <!-- 任务详细 --> - <Dialog title="任务详细" v-model="modelVisible" width="700px" append-to-body> - <el-form ref="formRef" :model="formData" label-width="200px"> - <el-row> - <el-col :span="24"> - <el-form-item label="任务编号:">{{ formData.id }}</el-form-item> - <el-form-item label="任务名称:">{{ formData.name }}</el-form-item> - <el-form-item label="任务名称:"> - <dict-tag :type="DICT_TYPE.INFRA_JOB_STATUS" :value="formData.status" /> - </el-form-item> - <el-form-item label="处理器的名字:">{{ formData.handlerName }}</el-form-item> - <el-form-item label="处理器的参数:">{{ formData.handlerParam }}</el-form-item> - <el-form-item label="cron表达式:">{{ formData.cronExpression }}</el-form-item> - <el-form-item label="重试次数:">{{ formData.retryCount }}</el-form-item> - <el-form-item label="重试间隔:">{{ formData.retryInterval + ' 毫秒' }}</el-form-item> - <el-form-item label="监控超时时间:">{{ - formData.monitorTimeout > 0 ? formData.monitorTimeout + ' 毫秒' : '未开启' - }}</el-form-item> - <el-form-item label="后续执行时间:"> - <el-timeline class="pt-3"> - <el-timeline-item - v-for="(activity, index) in nextTimes" - :key="index" - :timestamp="parseTime(activity)" - > - 第{{ index + 1 }}次 - </el-timeline-item> - </el-timeline> - </el-form-item> - </el-col> - </el-row> - </el-form> - <template #footer> - <div class="dialog-footer"> - <el-button @click="close">关 闭</el-button> - </div> - </template> - </Dialog> -</template> -<script setup lang="ts" name="JobView"> -import * as JobApi from '@/api/infra/job' -import { parseTime } from '@/utils/formatTime' -import { DICT_TYPE } from '@/utils/dict' - -const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 - -const { t } = useI18n() // 国际化 - -const formRef = ref() // 表单 Ref -const modelVisible = ref(false) // 弹窗的是否展示 -const modelTitle = ref('') // 弹窗的标题 -const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 -const formData = ref({ - id: undefined, - name: '', - handlerParam: '', - handlerName: '', - cronExpression: '', - retryCount: true, - retryInterval: '', - monitorTimeout: 0, - status: 0 -}) -const nextTimes = ref([]) - -/** 打开弹窗 */ -const openModal = async (id?: number) => { - modelVisible.value = true - modelTitle.value = t('action.detail') - // 查看,设置数据 - if (id) { - formLoading.value = true - try { - formData.value = await JobApi.getJobApi(id) - // 获取下一次执行时间 - nextTimes.value = await JobApi.getJobNextTimesApi(id) - } finally { - formLoading.value = false - } - } -} -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 - -const close = () => { - modelVisible.value = false - emit('success') -} -</script> From 8810cb953ebd9579208b2b298a60ca76243e806a Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 1 Apr 2023 14:26:29 +0800 Subject: [PATCH 176/184] =?UTF-8?q?REVIEW=20=E5=AE=9A=E6=97=B6=E6=97=A5?= =?UTF-8?q?=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/infra/job/index.ts | 28 ++----- src/api/infra/jobLog/index.ts | 22 +---- src/router/modules/remaining.ts | 2 +- src/views/infra/job/JobDetail.vue | 4 +- src/views/infra/job/JobForm.vue | 6 +- src/views/infra/job/JobLogView.vue | 74 ----------------- src/views/infra/job/index.vue | 16 ++-- src/views/infra/job/logger/JobLogDetail.vue | 57 +++++++++++++ .../job/{JobLog.vue => logger/index.vue} | 80 +++++++++++-------- 9 files changed, 129 insertions(+), 160 deletions(-) delete mode 100644 src/views/infra/job/JobLogView.vue create mode 100644 src/views/infra/job/logger/JobLogDetail.vue rename src/views/infra/job/{JobLog.vue => logger/index.vue} (74%) diff --git a/src/api/infra/job/index.ts b/src/api/infra/job/index.ts index 63f15da0..c1398d07 100644 --- a/src/api/infra/job/index.ts +++ b/src/api/infra/job/index.ts @@ -13,50 +13,38 @@ export interface JobVO { createTime: Date } -export interface JobPageReqVO extends PageParam { - name?: string - status?: number - handlerName?: string -} - -export interface JobExportReqVO { - name?: string - status?: number - handlerName?: string -} - // 任务列表 -export const getJobPageApi = (params: JobPageReqVO) => { +export const getJobPage = (params: PageParam) => { return request.get({ url: '/infra/job/page', params }) } // 任务详情 -export const getJobApi = (id: number) => { +export const getJob = (id: number) => { return request.get({ url: '/infra/job/get?id=' + id }) } // 新增任务 -export const createJobApi = (data: JobVO) => { +export const createJob = (data: JobVO) => { return request.post({ url: '/infra/job/create', data }) } // 修改定时任务调度 -export const updateJobApi = (data: JobVO) => { +export const updateJob = (data: JobVO) => { return request.put({ url: '/infra/job/update', data }) } // 删除定时任务调度 -export const deleteJobApi = (id: number) => { +export const deleteJob = (id: number) => { return request.delete({ url: '/infra/job/delete?id=' + id }) } // 导出定时任务调度 -export const exportJobApi = (params: JobExportReqVO) => { +export const exportJob = (params) => { return request.download({ url: '/infra/job/export-excel', params }) } // 任务状态修改 -export const updateJobStatusApi = (id: number, status: number) => { +export const updateJobStatus = (id: number, status: number) => { const params = { id, status @@ -70,6 +58,6 @@ export const runJobApi = (id: number) => { } // 获得定时任务的下 n 次执行时间 -export const getJobNextTimesApi = (id: number) => { +export const getJobNextTimes = (id: number) => { return request.get({ url: '/infra/job/get_next_times?id=' + id }) } diff --git a/src/api/infra/jobLog/index.ts b/src/api/infra/jobLog/index.ts index 84b74fbd..f429cd9e 100644 --- a/src/api/infra/jobLog/index.ts +++ b/src/api/infra/jobLog/index.ts @@ -14,34 +14,18 @@ export interface JobLogVO { createTime: string } -export interface JobLogPageReqVO extends PageParam { - jobId?: number - handlerName?: string - beginTime?: string - endTime?: string - status?: number -} - -export interface JobLogExportReqVO { - jobId?: number - handlerName?: string - beginTime?: string - endTime?: string - status?: number -} - // 任务日志列表 -export const getJobLogPageApi = (params: JobLogPageReqVO) => { +export const getJobLogPage = (params: PageParam) => { return request.get({ url: '/infra/job-log/page', params }) } // 任务日志详情 -export const getJobLogApi = (id: number) => { +export const getJobLog = (id: number) => { return request.get({ url: '/infra/job-log/get?id=' + id }) } // 导出定时任务日志 -export const exportJobLogApi = (params: JobLogExportReqVO) => { +export const exportJobLog = (params) => { return request.download({ url: '/infra/job-log/export-excel', params diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index 4d8c3dac..d8b5db64 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -162,7 +162,7 @@ const remainingRouter: AppRouteRecordRaw[] = [ children: [ { path: 'job-log', - component: () => import('@/views/infra/job/JobLog.vue'), + component: () => import('@/views/infra/job/logger/index.vue'), name: 'JobLog', meta: { noCache: true, diff --git a/src/views/infra/job/JobDetail.vue b/src/views/infra/job/JobDetail.vue index b8b01584..081c50de 100644 --- a/src/views/infra/job/JobDetail.vue +++ b/src/views/infra/job/JobDetail.vue @@ -59,9 +59,9 @@ const open = async (id: number) => { if (id) { detailLoading.value = true try { - detailData.value = await JobApi.getJobApi(id) + detailData.value = await JobApi.getJob(id) // 获取下一次执行时间 - nextTimes.value = await JobApi.getJobNextTimesApi(id) + nextTimes.value = await JobApi.getJobNextTimes(id) } finally { detailLoading.value = false } diff --git a/src/views/infra/job/JobForm.vue b/src/views/infra/job/JobForm.vue index 585370bf..5148d181 100644 --- a/src/views/infra/job/JobForm.vue +++ b/src/views/infra/job/JobForm.vue @@ -80,7 +80,7 @@ const open = async (type: string, id?: number) => { if (id) { formLoading.value = true try { - formData.value = await JobApi.getJobApi(id) + formData.value = await JobApi.getJob(id) } finally { formLoading.value = false } @@ -100,10 +100,10 @@ const submitForm = async () => { try { const data = formData.value as unknown as JobApi.JobVO if (formType.value === 'create') { - await JobApi.createJobApi(data) + await JobApi.createJob(data) message.success(t('common.createSuccess')) } else { - await JobApi.updateJobApi(data) + await JobApi.updateJob(data) message.success(t('common.updateSuccess')) } modelVisible.value = false diff --git a/src/views/infra/job/JobLogView.vue b/src/views/infra/job/JobLogView.vue deleted file mode 100644 index c66e0d80..00000000 --- a/src/views/infra/job/JobLogView.vue +++ /dev/null @@ -1,74 +0,0 @@ -<template> - <!-- 调度日志详细 --> - <Dialog title="调度日志详细" v-model="modelVisible" width="700px" append-to-body> - <el-form ref="form" :model="formData" label-width="120px" size="mini"> - <el-row> - <el-col :span="12"> - <el-form-item label="日志编号:">{{ formData.id }}</el-form-item> - <el-form-item label="任务编号:">{{ formData.jobId }}</el-form-item> - <el-form-item label="处理器的名字:">{{ formData.handlerName }}</el-form-item> - <el-form-item label="处理器的参数:">{{ formData.handlerParam }}</el-form-item> - <el-form-item label="第几次执行:">{{ formData.executeIndex }}</el-form-item> - <el-form-item label="执行时间:">{{ - parseTime(formData.beginTime) + ' ~ ' + parseTime(formData.endTime) - }}</el-form-item> - <el-form-item label="执行时长:">{{ formData.duration + ' 毫秒' }}</el-form-item> - <el-form-item label="任务状态:"> - <dict-tag :type="DICT_TYPE.INFRA_JOB_LOG_STATUS" :value="formData.status" /> - </el-form-item> - <el-form-item label="执行结果:">{{ formData.result }}</el-form-item> - </el-col> - </el-row> - </el-form> - <template #footer> - <div class="dialog-footer"> - <el-button @click="close">关 闭</el-button> - </div> - </template> - </Dialog> -</template> -<script setup lang="ts" name="JobView"> -import * as JobLogApi from '@/api/infra/jobLog' -import { DICT_TYPE } from '@/utils/dict' -import { parseTime } from './utils' - -const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 - -const { t } = useI18n() // 国际化 - -const modelVisible = ref(false) // 弹窗的是否展示 -const modelTitle = ref('') // 弹窗的标题 -const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 -const formData = ref({ - id: undefined, - jobId: undefined, - handlerParam: '', - handlerName: '', - executeIndex: '', - beginTime: undefined, - endTime: undefined, - duration: true, - result: '', - status: undefined -}) - -/** 打开弹窗 */ -const openModal = async (id?: number) => { - modelVisible.value = true - modelTitle.value = t('action.detail') - // 查看,设置数据 - if (id) { - formLoading.value = true - try { - formData.value = await JobLogApi.getJobLogApi(id) - } finally { - formLoading.value = false - } - } -} -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 - -const close = () => { - emit('success') -} -</script> diff --git a/src/views/infra/job/index.vue b/src/views/infra/job/index.vue index 13a8ccd5..72f6b95b 100644 --- a/src/views/infra/job/index.vue +++ b/src/views/infra/job/index.vue @@ -172,7 +172,7 @@ const exportLoading = ref(false) // 导出的加载中 const getList = async () => { loading.value = true try { - const data = await JobApi.getJobPageApi(queryParams) + const data = await JobApi.getJobPage(queryParams) list.value = data.list total.value = data.total } finally { @@ -199,7 +199,7 @@ const handleExport = async () => { await message.exportConfirm() // 发起导出 exportLoading.value = true - const data = await JobApi.exportJobApi(queryParams) + const data = await JobApi.exportJob(queryParams) download.excel(data, '定时任务.xls') } catch { } finally { @@ -224,7 +224,7 @@ const handleChangeStatus = async (row: JobApi.JobVO) => { ) const status = row.status === InfraJobStatusEnum.STOP ? InfraJobStatusEnum.NORMAL : InfraJobStatusEnum.STOP - await JobApi.updateJobStatusApi(row.id, status) + await JobApi.updateJobStatus(row.id, status) message.success(text + '成功') // 刷新列表 await getList() @@ -241,7 +241,7 @@ const handleDelete = async (id: number) => { // 删除的二次确认 await message.delConfirm() // 发起删除 - await JobApi.deleteJobApi(id) + await JobApi.deleteJob(id) message.success(t('common.delSuccess')) // 刷新列表 await getList() @@ -284,10 +284,10 @@ const openDetail = (id: number) => { detailRef.value.open(id) } -// 执行日志 -const handleJobLog = (rowId?: number) => { - if (rowId) { - push('/job/job-log?id=' + rowId) +/** 跳转执行日志 */ +const handleJobLog = (id: number) => { + if (id) { + push('/job/job-log?id=' + id) } else { push('/job/job-log') } diff --git a/src/views/infra/job/logger/JobLogDetail.vue b/src/views/infra/job/logger/JobLogDetail.vue new file mode 100644 index 00000000..7c4bab2b --- /dev/null +++ b/src/views/infra/job/logger/JobLogDetail.vue @@ -0,0 +1,57 @@ +<template> + <Dialog title="任务详细" v-model="modelVisible" width="700px"> + <el-descriptions border :column="1"> + <el-descriptions-item label="日志编号" min-width="60"> + {{ detailData.id }} + </el-descriptions-item> + <el-descriptions-item label="任务编号"> + {{ detailData.jobId }} + </el-descriptions-item> + <el-descriptions-item label="处理器的名字"> + {{ detailData.handlerName }} + </el-descriptions-item> + <el-descriptions-item label="处理器的参数"> + {{ detailData.handlerParam }} + </el-descriptions-item> + <el-descriptions-item label="第几次执行"> + {{ detailData.executeIndex }} + </el-descriptions-item> + <el-descriptions-item label="执行时间"> + {{ formatDate(detailData.beginTime) + ' ~ ' + formatDate(detailData.endTime) }} + </el-descriptions-item> + <el-descriptions-item label="执行时长"> + {{ detailData.duration + ' 毫秒' }} + </el-descriptions-item> + <el-descriptions-item label="任务状态"> + <dict-tag :type="DICT_TYPE.INFRA_JOB_LOG_STATUS" :value="detailData.status" /> + </el-descriptions-item> + <el-descriptions-item label="执行结果"> + {{ detailData.duration + ' result' }} + </el-descriptions-item> + </el-descriptions> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE } from '@/utils/dict' +import { formatDate } from '@/utils/formatTime' +import * as JobLogApi from '@/api/infra/jobLog' + +const modelVisible = ref(false) // 弹窗的是否展示 +const detailLoading = ref(false) // 表单的加载中 +const detailData = ref({}) // 详情数据 + +/** 打开弹窗 */ +const open = async (id: number) => { + modelVisible.value = true + // 查看,设置数据 + if (id) { + detailLoading.value = true + try { + detailData.value = await JobLogApi.getJobLog(id) + } finally { + detailLoading.value = false + } + } +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 +</script> diff --git a/src/views/infra/job/JobLog.vue b/src/views/infra/job/logger/index.vue similarity index 74% rename from src/views/infra/job/JobLog.vue rename to src/views/infra/job/logger/index.vue index daa20046..ab28b285 100644 --- a/src/views/infra/job/JobLog.vue +++ b/src/views/infra/job/logger/index.vue @@ -1,37 +1,52 @@ <template> <content-wrap> - <!-- 搜索栏 --> - <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="120px"> + <!-- 搜索工作栏 --> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="120px" + > <el-form-item label="处理器的名字" prop="handlerName"> <el-input v-model="queryParams.handlerName" placeholder="请输入处理器的名字" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="开始执行时间" prop="beginTime"> <el-date-picker - clearable v-model="queryParams.beginTime" type="date" - value-format="YYYY-MM-DD" + value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择开始执行时间" + clearable + class="!w-240px" /> </el-form-item> <el-form-item label="结束执行时间" prop="endTime"> <el-date-picker - clearable v-model="queryParams.endTime" type="date" - value-format="YYYY-MM-DD" + value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择结束执行时间" + clearable + :default-time="new Date('1 23:59:59')" + class="!w-240px" /> </el-form-item> <el-form-item label="任务状态" prop="status"> - <el-select v-model="queryParams.status" placeholder="请选择任务状态" clearable> + <el-select + v-model="queryParams.status" + placeholder="请选择任务状态" + clearable + class="!w-240px" + > <el-option - v-for="dict in getDictOptions(DICT_TYPE.INFRA_JOB_LOG_STATUS)" + v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_JOB_LOG_STATUS)" :key="dict.value" :label="dict.label" :value="dict.value" @@ -52,16 +67,19 @@ </el-button> </el-form-item> </el-form> + </content-wrap> + <!-- 列表 --> + <content-wrap> <el-table v-loading="loading" :data="list"> <el-table-column label="日志编号" align="center" prop="id" /> <el-table-column label="任务编号" align="center" prop="jobId" /> <el-table-column label="处理器的名字" align="center" prop="handlerName" /> <el-table-column label="处理器的参数" align="center" prop="handlerParam" /> <el-table-column label="第几次执行" align="center" prop="executeIndex" /> - <el-table-column label="执行时间" align="center" width="180"> + <el-table-column label="执行时间" align="center" width="170s"> <template #default="scope"> - <span>{{ parseTime(scope.row.beginTime) + ' ~ ' + parseTime(scope.row.endTime) }}</span> + <span>{{ formatDate(scope.row.beginTime) + ' ~ ' + formatDate(scope.row.endTime) }}</span> </template> </el-table-column> <el-table-column label="执行时长" align="center" prop="duration"> @@ -74,40 +92,39 @@ <dict-tag :type="DICT_TYPE.INFRA_JOB_LOG_STATUS" :value="scope.row.status" /> </template> </el-table-column> - <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <el-table-column label="操作" align="center"> <template #default="scope"> <el-button + type="primary" link - icon="el-icon-view" - @click="handleView(scope.row.id)" - :loading="exportLoading" + @click="openDetail(scope.row.id)" v-hasPermi="['infra:job:query']" - >详细 + > + 详细 </el-button> </template> </el-table-column> </el-table> - - <pagination - v-show="total > 0" + <!-- 分页组件 --> + <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" /> </content-wrap> + <!-- 表单弹窗:查看 --> - <log-view ref="viewModalRef" @success="getList" /> + <JobLogDetail ref="detailRef" /> </template> - <script setup lang="ts" name="JobLog"> -import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { formatDate } from '@/utils/formatTime' import download from '@/utils/download' -import LogView from './JobLogView.vue' +import JobLogDetail from './JobLogDetail.vue' import * as JobLogApi from '@/api/infra/jobLog' -import { parseTime } from './utils' - const message = useMessage() // 消息弹窗 +const { query } = useRoute() // 查询参数 const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 @@ -115,6 +132,7 @@ const list = ref([]) // 列表的数据 const queryParams = reactive({ pageNo: 1, pageSize: 10, + jobId: query.id, handlerName: undefined, beginTime: undefined, endTime: undefined, @@ -127,11 +145,7 @@ const exportLoading = ref(false) // 导出的加载中 const getList = async () => { loading.value = true try { - const data = await JobLogApi.getJobLogPageApi({ - ...queryParams, - beginTime: queryParams.beginTime ? queryParams.beginTime + ' 00:00:00' : undefined, - endTime: queryParams.endTime ? queryParams.endTime + ' 23:59:59' : undefined - }) + const data = await JobLogApi.getJobLogPage(queryParams) list.value = data.list total.value = data.total } finally { @@ -152,9 +166,9 @@ const resetQuery = () => { } /** 查看操作 */ -const viewModalRef = ref() -const handleView = (rowId?: number) => { - viewModalRef.value.openModal(rowId) +const detailRef = ref() +const openDetail = (rowId?: number) => { + detailRef.value.open(rowId) } /** 导出按钮操作 */ @@ -164,7 +178,7 @@ const handleExport = async () => { await message.exportConfirm() // 发起导出 exportLoading.value = true - const data = await JobLogApi.exportJobLogApi(queryParams) + const data = await JobLogApi.exportJobLog(queryParams) download.excel(data, '定时任务执行日志.xls') } catch { } finally { From ecde723fa226b065fc4e0761fce79ce387b68ea0 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 1 Apr 2023 17:45:31 +0800 Subject: [PATCH 177/184] =?UTF-8?q?REVIEW=20=E8=A7=92=E8=89=B2=E7=AE=A1?= =?UTF-8?q?=E7=90=86=EF=BC=88=E8=AE=BE=E7=BD=AE=E8=8F=9C=E5=8D=95=E6=9D=83?= =?UTF-8?q?=E9=99=90=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/permission/index.ts | 2 +- src/views/system/role/MenuPermissionForm.vue | 152 ----------------- src/views/system/role/RoleAssignMenuForm.vue | 161 ++++++++++++++++++ src/views/system/role/index.vue | 22 ++- .../tenantPackage/TenantPackageForm.vue | 3 +- 5 files changed, 176 insertions(+), 164 deletions(-) delete mode 100644 src/views/system/role/MenuPermissionForm.vue create mode 100644 src/views/system/role/RoleAssignMenuForm.vue diff --git a/src/api/system/permission/index.ts b/src/api/system/permission/index.ts index 6e2adcdf..958b4718 100644 --- a/src/api/system/permission/index.ts +++ b/src/api/system/permission/index.ts @@ -17,7 +17,7 @@ export interface PermissionAssignRoleDataScopeReqVO { } // 查询角色拥有的菜单权限 -export const listRoleMenusApi = async (roleId: number) => { +export const getRoleMenuList = async (roleId: number) => { return await request.get({ url: '/system/permission/list-role-resources?roleId=' + roleId }) } diff --git a/src/views/system/role/MenuPermissionForm.vue b/src/views/system/role/MenuPermissionForm.vue deleted file mode 100644 index a7995fec..00000000 --- a/src/views/system/role/MenuPermissionForm.vue +++ /dev/null @@ -1,152 +0,0 @@ -<template> - <Dialog :title="dialogScopeTitle" v-model="dialogScopeVisible" width="800"> - <el-form - ref="menuPermissionFormRef" - :model="dataScopeForm" - :inline="true" - label-width="80px" - v-loading="formLoading" - > - <el-form-item label="角色名称"> - <el-tag>{{ dataScopeForm.name }}</el-tag> - </el-form-item> - <el-form-item label="角色标识"> - <el-tag>{{ dataScopeForm.code }}</el-tag> - </el-form-item> - <!-- 分配角色的菜单权限对话框 --> - <el-row> - <el-col :span="24"> - <el-form-item label="权限范围" style="display: flex"> - <el-card class="card" shadow="never"> - <template #header> - 父子联动(选中父节点,自动选择子节点): - <el-switch - v-model="checkStrictly" - inline-prompt - active-text="是" - inactive-text="否" - /> - 全选/全不选: - <el-switch - v-model="treeNodeAll" - inline-prompt - active-text="是" - inactive-text="否" - @change="handleCheckedTreeNodeAll()" - /> - </template> - <el-tree - ref="treeRef" - node-key="id" - show-checkbox - :check-strictly="!checkStrictly" - :props="defaultProps" - :data="treeOptions" - empty-text="加载中,请稍后" - /> - </el-card> - </el-form-item> </el-col - ></el-row> - </el-form> - <!-- 操作按钮 --> - <template #footer> - <div class="dialog-footer"> - <el-button - :title="t('action.save')" - :loading="actionLoading" - @click="submitScope()" - type="primary" - :disabled="formLoading" - > - 保存 - </el-button> - <el-button - :loading="actionLoading" - :title="t('dialog.close')" - @click="dialogScopeVisible = false" - >取 消</el-button - > - </div> - </template> - </Dialog> -</template> - -<script setup lang="ts"> -import * as RoleApi from '@/api/system/role' -import type { ElTree } from 'element-plus' -import type { FormExpose } from '@/components/Form' -import { handleTree, defaultProps } from '@/utils/tree' -import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' -import * as MenuApi from '@/api/system/menu' -import * as PermissionApi from '@/api/system/permission' -// ========== CRUD 相关 ========== -const actionLoading = ref(false) // 遮罩层 -const menuPermissionFormRef = ref<FormExpose>() // 表单 Ref -const { t } = useI18n() // 国际化 -const dialogScopeTitle = ref('菜单权限') -const dataScopeDictDatas = ref() -const message = useMessage() // 消息弹窗 -const actionScopeType = ref('') -// 选项 -const checkStrictly = ref(true) -const treeNodeAll = ref(false) -const dialogScopeVisible = ref(false) // 弹窗的是否展示 -const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 -const treeOptions = ref<any[]>([]) // 菜单树形结构 -const treeRef = ref<InstanceType<typeof ElTree>>() -// ========== 数据权限 ========== -const dataScopeForm = reactive({ - id: 0, - name: '', - code: '', - dataScope: 0, - checkList: [] -}) - -/** 打开弹窗 */ -const openModal = async (type: string, row: RoleApi.RoleVO) => { - dataScopeForm.id = row.id - dataScopeForm.name = row.name - dataScopeForm.code = row.code - actionScopeType.value = type - dialogScopeVisible.value = true - const menuRes = await MenuApi.getSimpleMenusList() - treeOptions.value = handleTree(menuRes) - const role = await PermissionApi.listRoleMenusApi(row.id) - if (role) { - role?.forEach((item: any) => { - unref(treeRef)?.setChecked(item, true, false) - }) - } -} - -// 保存权限 -const submitScope = async () => { - const data = ref<PermissionApi.PermissionAssignRoleMenuReqVO>({ - roleId: dataScopeForm.id, - menuIds: [ - ...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>), - ...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>) - ] - }) - await PermissionApi.assignRoleMenuApi(data.value) - - message.success(t('common.updateSuccess')) - dialogScopeVisible.value = false -} - -// 全选/全不选 -const handleCheckedTreeNodeAll = () => { - treeRef.value!.setCheckedNodes(treeNodeAll.value ? treeOptions.value : []) -} - -const init = () => { - dataScopeDictDatas.value = getIntDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE) -} - -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 -// ========== 初始化 ========== -onMounted(() => { - init() -}) -</script> diff --git a/src/views/system/role/RoleAssignMenuForm.vue b/src/views/system/role/RoleAssignMenuForm.vue new file mode 100644 index 00000000..c016a51f --- /dev/null +++ b/src/views/system/role/RoleAssignMenuForm.vue @@ -0,0 +1,161 @@ +<template> + <Dialog title="菜单权限" v-model="modelVisible"> + <el-form + ref="formRef" + :model="formData" + :inline="true" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="角色名称"> + <el-tag>{{ formData.name }}</el-tag> + </el-form-item> + <el-form-item label="角色标识"> + <el-tag>{{ formData.code }}</el-tag> + </el-form-item> + <el-form-item label="菜单权限"> + <el-card class="cardHeight"> + <template #header> + 全选/全不选: + <el-switch + v-model="treeNodeAll" + inline-prompt + active-text="是" + inactive-text="否" + @change="handleCheckedTreeNodeAll" + /> + 全部展开/折叠: + <el-switch + v-model="menuExpand" + inline-prompt + active-text="展开" + inactive-text="折叠" + @change="handleCheckedTreeExpand" + /> + </template> + <el-tree + ref="treeRef" + node-key="id" + show-checkbox + :props="defaultProps" + :data="menuOptions" + empty-text="加载中,请稍候" + /> + </el-card> + </el-form-item> + </el-form> + <template #footer> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { handleTree, defaultProps } from '@/utils/tree' +import * as RoleApi from '@/api/system/role' +import type { ElTree } from 'element-plus' +import * as MenuApi from '@/api/system/menu' +import * as PermissionApi from '@/api/system/permission' +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formData = reactive({ + id: 0, + name: '', + code: '', + menuIds: [] +}) +const formRef = ref() // 表单 Ref +const menuOptions = ref<any[]>([]) // 菜单树形结构 +const menuExpand = ref(false) // 展开/折叠 +const treeRef = ref<InstanceType<typeof ElTree>>() // 树组件 Ref +const treeNodeAll = ref(false) // 全选/全不选 + +/** 打开弹窗 */ +const open = async (row: RoleApi.RoleVO) => { + modelVisible.value = true + resetForm() + // 加载 Menu 列表。注意,必须放在前面,不然下面 setChecked 没数据节点 + menuOptions.value = handleTree(await MenuApi.getSimpleMenusList()) + // 设置数据 + formData.id = row.id + formData.name = row.name + formData.code = row.code + formLoading.value = true + try { + formData.value.menuIds = await PermissionApi.getRoleMenuList(row.id) + // 设置选中 + formData.value.menuIds.forEach((menuId: number) => { + treeRef.value.setChecked(menuId, true, false) + }) + } finally { + formLoading.value = false + } +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = { + roleId: formData.id, + menuIds: [ + ...(treeRef.value.getCheckedKeys(false) as unknown as Array<number>), // 获得当前选中节点 + ...(treeRef.value.getHalfCheckedKeys() as unknown as Array<number>) // 获得半选中的父节点 + ] + } + await PermissionApi.assignRoleMenuApi(data) + message.success(t('common.updateSuccess')) + modelVisible.value = false + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + // 重置选项 + treeNodeAll.value = false + menuExpand.value = false + // 重置表单 + formData.value = { + id: 0, + name: '', + code: '', + menuIds: [] + } + treeRef.value?.setCheckedNodes([]) + formRef.value?.resetFields() +} + +/** 全选/全不选 */ +const handleCheckedTreeNodeAll = () => { + treeRef.value.setCheckedNodes(treeNodeAll.value ? menuOptions.value : []) +} + +/** 展开/折叠全部 */ +const handleCheckedTreeExpand = () => { + const nodes = treeRef.value?.store.nodesMap + for (let node in nodes) { + if (nodes[node].expanded === menuExpand.value) { + continue + } + nodes[node].expanded = menuExpand.value + } +} +</script> +<style lang="scss" scoped> +.cardHeight { + width: 100%; + max-height: 400px; + overflow-y: scroll; +} +</style> diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue index 4be8e48c..65330fcb 100644 --- a/src/views/system/role/index.vue +++ b/src/views/system/role/index.vue @@ -108,7 +108,7 @@ preIcon="ep:basketball" title="菜单权限" v-hasPermi="['system:permission:assign-role-menu']" - @click="handleScope('menu', scope.row)" + @click="openAssignMenuForm(scope.row)" > 菜单权限 </el-button> @@ -145,18 +145,18 @@ <!-- 表单弹窗:添加/修改 --> <RoleForm ref="formRef" @success="getList" /> <!-- 表单弹窗:菜单权限 --> - <MenuPermissionForm ref="menuPermissionFormRef" @success="getList" /> + <RoleAssignMenuForm ref="assignMenuFormRef" @success="getList" /> <!-- 表单弹窗:数据权限 --> <DataPermissionForm ref="dataPermissionFormRef" @success="getList" /> </template> <script setup lang="tsx"> -import * as RoleApi from '@/api/system/role' -import RoleForm from './RoleForm.vue' -import MenuPermissionForm from './MenuPermissionForm.vue' -import DataPermissionForm from './DataPermissionForm.vue' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' import download from '@/utils/download' +import * as RoleApi from '@/api/system/role' +import RoleForm from './RoleForm.vue' +import RoleAssignMenuForm from './RoleAssignMenuForm.vue' +import DataPermissionForm from './DataPermissionForm.vue' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -204,19 +204,23 @@ const openForm = (type: string, id?: number) => { formRef.value.open(type, id) } -/** 菜单权限操作 */ -const menuPermissionFormRef = ref() /** 数据权限操作 */ const dataPermissionFormRef = ref() const handleScope = async (type: string, row: RoleApi.RoleVO) => { if (type === 'menu') { - menuPermissionFormRef.value.openModal(type, row) + assignMenuFormRef.value.openModal(type, row) } else if (type === 'data') { dataPermissionFormRef.value.openModal(type, row) } } +/** 菜单权限操作 */ +const assignMenuFormRef = ref() +const openAssignMenuForm = async (row: RoleApi.RoleVO) => { + assignMenuFormRef.value.open(row) +} + /** 删除按钮操作 */ const handleDelete = async (id: number) => { try { diff --git a/src/views/system/tenantPackage/TenantPackageForm.vue b/src/views/system/tenantPackage/TenantPackageForm.vue index f5e5343d..6ba78e96 100644 --- a/src/views/system/tenantPackage/TenantPackageForm.vue +++ b/src/views/system/tenantPackage/TenantPackageForm.vue @@ -64,11 +64,10 @@ <script setup lang="ts" name="TenantPackageForm"> import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { CommonStatusEnum } from '@/utils/constants' -import { defaultProps } from '@/utils/tree' +import { defaultProps, handleTree } from '@/utils/tree' import * as TenantPackageApi from '@/api/system/tenantPackage' import * as MenuApi from '@/api/system/menu' import { ElTree } from 'element-plus' -import { handleTree } from '@/utils/tree' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 From 7b700d875cf9d221293b725b790d0b43c29ec6f4 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sat, 1 Apr 2023 22:30:02 +0800 Subject: [PATCH 178/184] =?UTF-8?q?REVIEW=20=E8=A7=92=E8=89=B2=E7=AE=A1?= =?UTF-8?q?=E7=90=86=EF=BC=88=E8=AE=BE=E7=BD=AE=E6=95=B0=E6=8D=AE=E6=9D=83?= =?UTF-8?q?=E9=99=90=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/permission/index.ts | 4 +- src/api/system/role/index.ts | 3 + src/views/system/role/DataPermissionForm.vue | 174 ------------------ src/views/system/role/RoleAssignMenuForm.vue | 13 +- .../system/role/RoleDataPermissionForm.vue | 161 ++++++++++++++++ src/views/system/role/index.vue | 17 +- 6 files changed, 175 insertions(+), 197 deletions(-) delete mode 100644 src/views/system/role/DataPermissionForm.vue create mode 100644 src/views/system/role/RoleDataPermissionForm.vue diff --git a/src/api/system/permission/index.ts b/src/api/system/permission/index.ts index 958b4718..baf2805b 100644 --- a/src/api/system/permission/index.ts +++ b/src/api/system/permission/index.ts @@ -22,12 +22,12 @@ export const getRoleMenuList = async (roleId: number) => { } // 赋予角色菜单权限 -export const assignRoleMenuApi = async (data: PermissionAssignRoleMenuReqVO) => { +export const assignRoleMenu = async (data: PermissionAssignRoleMenuReqVO) => { return await request.post({ url: '/system/permission/assign-role-menu', data }) } // 赋予角色数据权限 -export const assignRoleDataScopeApi = async (data: PermissionAssignRoleDataScopeReqVO) => { +export const assignRoleDataScope = async (data: PermissionAssignRoleDataScopeReqVO) => { return await request.post({ url: '/system/permission/assign-role-data-scope', data }) } diff --git a/src/api/system/role/index.ts b/src/api/system/role/index.ts index 902d5ca6..93636ff0 100644 --- a/src/api/system/role/index.ts +++ b/src/api/system/role/index.ts @@ -7,6 +7,8 @@ export interface RoleVO { sort: number status: number type: number + dataScope: number + dataScopeDeptIds: number[] createTime: Date } @@ -49,6 +51,7 @@ export const updateRoleStatus = async (data: UpdateStatusReqVO) => { export const deleteRole = async (id: number) => { return await request.delete({ url: '/system/role/delete?id=' + id }) } + // 导出角色 export const exportRole = (params) => { return request.download({ diff --git a/src/views/system/role/DataPermissionForm.vue b/src/views/system/role/DataPermissionForm.vue deleted file mode 100644 index bccc5f46..00000000 --- a/src/views/system/role/DataPermissionForm.vue +++ /dev/null @@ -1,174 +0,0 @@ -<template> - <Dialog :title="dialogScopeTitle" v-model="dialogScopeVisible" width="800"> - <el-form - ref="dataPermissionFormRef" - :model="dataScopeForm" - :inline="true" - label-width="80px" - v-loading="formLoading" - > - <el-form-item label="角色名称"> - <el-tag>{{ dataScopeForm.name }}</el-tag> - </el-form-item> - <el-form-item label="角色标识"> - <el-tag>{{ dataScopeForm.code }}</el-tag> - </el-form-item> - <!-- 分配角色的数据权限对话框 --> - <el-form-item label="权限范围"> - <el-select v-model="dataScopeForm.dataScope"> - <el-option - v-for="item in dataScopeDictDatas" - :key="item.value" - :label="item.label" - :value="item.value" - /> - </el-select> - </el-form-item> - </el-form> - <!-- 分配角色的菜单权限对话框 --> - <el-row> - <el-col :span="24"> - <el-form-item - label="权限范围" - v-if=" - actionScopeType === 'menu' || - dataScopeForm.dataScope === SystemDataScopeEnum.DEPT_CUSTOM - " - style="display: flex" - > - <el-card class="card" shadow="never"> - <template #header> - 父子联动(选中父节点,自动选择子节点): - <el-switch - v-model="checkStrictly" - inline-prompt - active-text="是" - inactive-text="否" - /> - 全选/全不选: - <el-switch - v-model="treeNodeAll" - inline-prompt - active-text="是" - inactive-text="否" - @change="handleCheckedTreeNodeAll()" - /> - </template> - <el-tree - ref="treeRef" - node-key="id" - show-checkbox - :check-strictly="!checkStrictly" - :props="defaultProps" - :data="treeOptions" - empty-text="加载中,请稍后" - /> - </el-card> - </el-form-item> </el-col - ></el-row> - - <!-- 操作按钮 --> - <template #footer> - <div class="dialog-footer"> - <el-button - :title="t('action.save')" - :loading="actionLoading" - @click="submitScope()" - type="primary" - :disabled="formLoading" - > - 保存 - </el-button> - <el-button - :loading="actionLoading" - :title="t('dialog.close')" - @click="dialogScopeVisible = false" - >取 消</el-button - > - </div> - </template> - </Dialog> -</template> - -<script setup lang="ts"> -import * as RoleApi from '@/api/system/role' -import type { ElTree } from 'element-plus' -import type { FormExpose } from '@/components/Form' -import { handleTree, defaultProps } from '@/utils/tree' -import { SystemDataScopeEnum } from '@/utils/constants' -import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' -import * as DeptApi from '@/api/system/dept' -import * as PermissionApi from '@/api/system/permission' -// ========== CRUD 相关 ========== -const actionLoading = ref(false) // 遮罩层 -const dataPermissionFormRef = ref<FormExpose>() // 表单 Ref -const { t } = useI18n() // 国际化 -const dialogScopeTitle = ref('菜单权限') -const dataScopeDictDatas = ref() -const message = useMessage() // 消息弹窗 -const actionScopeType = ref('') -// 选项 -const treeNodeAll = ref(false) -const checkStrictly = ref(true) -const dialogScopeVisible = ref(false) // 弹窗的是否展示 -const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 -const treeOptions = ref<any[]>([]) // 菜单树形结构 -const treeRef = ref<InstanceType<typeof ElTree>>() -// ========== 数据权限 ========== -const dataScopeForm = reactive({ - id: 0, - name: '', - code: '', - dataScope: 0, - checkList: [] -}) - -/** 打开弹窗 */ -const openModal = async (type: string, row: RoleApi.RoleVO) => { - dataScopeForm.id = row.id - dataScopeForm.name = row.name - dataScopeForm.code = row.code - actionScopeType.value = type - dialogScopeVisible.value = true - const deptRes = await DeptApi.getSimpleDeptList() - treeOptions.value = handleTree(deptRes) - const role = await RoleApi.getRole(row.id) - dataScopeForm.dataScope = role.dataScope - if (role.dataScopeDeptIds) { - role.dataScopeDeptIds?.forEach((item: any) => { - unref(treeRef)?.setChecked(item, true, false) - }) - } -} - -// 保存权限 -const submitScope = async () => { - const data = ref<PermissionApi.PermissionAssignRoleDataScopeReqVO>({ - roleId: dataScopeForm.id, - dataScope: dataScopeForm.dataScope, - dataScopeDeptIds: - dataScopeForm.dataScope !== SystemDataScopeEnum.DEPT_CUSTOM - ? [] - : (treeRef.value!.getCheckedKeys(false) as unknown as Array<number>) - }) - await PermissionApi.assignRoleDataScopeApi(data.value) - - message.success(t('common.updateSuccess')) - dialogScopeVisible.value = false -} - -// 全选/全不选 -const handleCheckedTreeNodeAll = () => { - treeRef.value!.setCheckedNodes(treeNodeAll.value ? treeOptions.value : []) -} - -const init = () => { - dataScopeDictDatas.value = getIntDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE) -} - -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 -// ========== 初始化 ========== -onMounted(() => { - init() -}) -</script> diff --git a/src/views/system/role/RoleAssignMenuForm.vue b/src/views/system/role/RoleAssignMenuForm.vue index c016a51f..db33811e 100644 --- a/src/views/system/role/RoleAssignMenuForm.vue +++ b/src/views/system/role/RoleAssignMenuForm.vue @@ -1,12 +1,6 @@ <template> <Dialog title="菜单权限" v-model="modelVisible"> - <el-form - ref="formRef" - :model="formData" - :inline="true" - label-width="80px" - v-loading="formLoading" - > + <el-form ref="formRef" :model="formData" label-width="80px" v-loading="formLoading"> <el-form-item label="角色名称"> <el-tag>{{ formData.name }}</el-tag> </el-form-item> @@ -53,7 +47,6 @@ <script setup lang="ts"> import { handleTree, defaultProps } from '@/utils/tree' import * as RoleApi from '@/api/system/role' -import type { ElTree } from 'element-plus' import * as MenuApi from '@/api/system/menu' import * as PermissionApi from '@/api/system/permission' const { t } = useI18n() // 国际化 @@ -70,7 +63,7 @@ const formData = reactive({ const formRef = ref() // 表单 Ref const menuOptions = ref<any[]>([]) // 菜单树形结构 const menuExpand = ref(false) // 展开/折叠 -const treeRef = ref<InstanceType<typeof ElTree>>() // 树组件 Ref +const treeRef = ref() // 菜单树组件 Ref const treeNodeAll = ref(false) // 全选/全不选 /** 打开弹窗 */ @@ -112,7 +105,7 @@ const submitForm = async () => { ...(treeRef.value.getHalfCheckedKeys() as unknown as Array<number>) // 获得半选中的父节点 ] } - await PermissionApi.assignRoleMenuApi(data) + await PermissionApi.assignRoleMenu(data) message.success(t('common.updateSuccess')) modelVisible.value = false } finally { diff --git a/src/views/system/role/RoleDataPermissionForm.vue b/src/views/system/role/RoleDataPermissionForm.vue new file mode 100644 index 00000000..9a331b6a --- /dev/null +++ b/src/views/system/role/RoleDataPermissionForm.vue @@ -0,0 +1,161 @@ +<template> + <Dialog title="菜单权限" v-model="modelVisible" width="800"> + <el-form ref="formRef" :model="formData" label-width="80px" v-loading="formLoading"> + <el-form-item label="角色名称"> + <el-tag>{{ formData.name }}</el-tag> + </el-form-item> + <el-form-item label="角色标识"> + <el-tag>{{ formData.code }}</el-tag> + </el-form-item> + <el-form-item label="权限范围"> + <el-select v-model="formData.dataScope"> + <el-option + v-for="item in getIntDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE)" + :key="item.value" + :label="item.label" + :value="item.value" + /> + </el-select> + </el-form-item> + </el-form> + <el-form-item + label="权限范围" + v-if="formData.dataScope === SystemDataScopeEnum.DEPT_CUSTOM" + style="display: flex" + > + <el-card class="card" shadow="never"> + <template #header> + 全选/全不选: + <el-switch + v-model="treeNodeAll" + inline-prompt + active-text="是" + inactive-text="否" + @change="handleCheckedTreeNodeAll()" + /> + 全部展开/折叠: + <el-switch + v-model="deptExpand" + inline-prompt + active-text="展开" + inactive-text="折叠" + @change="handleCheckedTreeExpand" + /> + 父子联动(选中父节点,自动选择子节点): + <el-switch v-model="checkStrictly" inline-prompt active-text="是" inactive-text="否" /> + </template> + <el-tree + ref="treeRef" + node-key="id" + show-checkbox + :check-strictly="!checkStrictly" + :props="defaultProps" + :data="deptOptions" + empty-text="加载中,请稍后" + default-expand-all + /> + </el-card> + </el-form-item> + <template #footer> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { handleTree, defaultProps } from '@/utils/tree' +import { SystemDataScopeEnum } from '@/utils/constants' +import * as RoleApi from '@/api/system/role' +import * as DeptApi from '@/api/system/dept' +import * as PermissionApi from '@/api/system/permission' +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const modelVisible = ref(false) // 弹窗的是否展示 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formData = reactive({ + id: 0, + name: '', + code: '', + dataScope: undefined, + dataScopeDeptIds: [] +}) +const formRef = ref() // 表单 Ref +const deptOptions = ref<any[]>([]) // 部门树形结构 +const deptExpand = ref(false) // 展开/折叠 +const treeRef = ref() // 菜单树组件 Ref +const treeNodeAll = ref(false) // 全选/全不选 +const checkStrictly = ref(true) // 是否严格模式,即父子不关联 + +/** 打开弹窗 */ +const open = async (row: RoleApi.RoleVO) => { + modelVisible.value = true + resetForm() + // 加载 Dept 列表。注意,必须放在前面,不然下面 setChecked 没数据节点 + deptOptions.value = handleTree(await DeptApi.getSimpleDeptList()) + // 设置数据 + formData.id = row.id + formData.name = row.name + formData.code = row.code + formData.dataScope = row.dataScope + row.dataScopeDeptIds?.forEach((deptId: number) => { + treeRef.value.setChecked(deptId, true, false) + }) +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const submitForm = async () => { + formLoading.value = true + try { + const data = { + roleId: formData.id, + dataScope: formData.dataScope, + dataScopeDeptIds: + formData.dataScope !== SystemDataScopeEnum.DEPT_CUSTOM + ? [] + : treeRef.value.getCheckedKeys(false) + } + await PermissionApi.assignRoleDataScope(data) + message.success(t('common.updateSuccess')) + modelVisible.value = false + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + // 重置选项 + treeNodeAll.value = false + deptExpand.value = false + checkStrictly.value = true + // 重置表单 + formData.value = { + id: 0, + name: '', + code: '', + dataScope: undefined, + dataScopeDeptIds: [] + } + treeRef.value?.setCheckedNodes([]) + formRef.value?.resetFields() +} + +/** 全选/全不选 */ +const handleCheckedTreeNodeAll = () => { + treeRef.value.setCheckedNodes(treeNodeAll.value ? deptOptions.value : []) +} + +/** 展开/折叠全部 */ +const handleCheckedTreeExpand = () => { + const nodes = treeRef.value?.store.nodesMap + for (let node in nodes) { + if (nodes[node].expanded === deptExpand.value) { + continue + } + nodes[node].expanded = deptExpand.value + } +} +</script> diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue index 65330fcb..71f0c415 100644 --- a/src/views/system/role/index.vue +++ b/src/views/system/role/index.vue @@ -118,7 +118,7 @@ preIcon="ep:coin" title="数据权限" v-hasPermi="['system:permission:assign-role-data-scope']" - @click="handleScope('data', scope.row)" + @click="openDataPermissionForm(scope.row)" > 数据权限 </el-button> @@ -145,9 +145,9 @@ <!-- 表单弹窗:添加/修改 --> <RoleForm ref="formRef" @success="getList" /> <!-- 表单弹窗:菜单权限 --> - <RoleAssignMenuForm ref="assignMenuFormRef" @success="getList" /> + <RoleAssignMenuForm ref="assignMenuFormRef" /> <!-- 表单弹窗:数据权限 --> - <DataPermissionForm ref="dataPermissionFormRef" @success="getList" /> + <RoleDataPermissionForm ref="dataPermissionFormRef" /> </template> <script setup lang="tsx"> import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' @@ -156,7 +156,7 @@ import download from '@/utils/download' import * as RoleApi from '@/api/system/role' import RoleForm from './RoleForm.vue' import RoleAssignMenuForm from './RoleAssignMenuForm.vue' -import DataPermissionForm from './DataPermissionForm.vue' +import RoleDataPermissionForm from './RoleDataPermissionForm.vue' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -206,13 +206,8 @@ const openForm = (type: string, id?: number) => { /** 数据权限操作 */ const dataPermissionFormRef = ref() - -const handleScope = async (type: string, row: RoleApi.RoleVO) => { - if (type === 'menu') { - assignMenuFormRef.value.openModal(type, row) - } else if (type === 'data') { - dataPermissionFormRef.value.openModal(type, row) - } +const openDataPermissionForm = async (row: RoleApi.RoleVO) => { + dataPermissionFormRef.value.open(row) } /** 菜单权限操作 */ From 00c3133391fe9927b36354e5be7a4d5830eba996 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 2 Apr 2023 00:17:56 +0800 Subject: [PATCH 179/184] =?UTF-8?q?REVIEW=20=E5=95=86=E5=93=81=E5=88=86?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/infra/codegen/index.vue | 2 +- src/views/infra/job/index.vue | 2 +- src/views/infra/job/logger/index.vue | 2 +- .../category/{form.vue => CategoryForm.vue} | 74 +++++++++---------- src/views/mall/product/category/index.vue | 58 ++++++++------- src/views/mall/product/category/utils.ts | 44 ----------- src/views/system/dept/index.vue | 2 +- 7 files changed, 72 insertions(+), 112 deletions(-) rename src/views/mall/product/category/{form.vue => CategoryForm.vue} (76%) delete mode 100644 src/views/mall/product/category/utils.ts diff --git a/src/views/infra/codegen/index.vue b/src/views/infra/codegen/index.vue index 34c81e58..4325f702 100644 --- a/src/views/infra/codegen/index.vue +++ b/src/views/infra/codegen/index.vue @@ -163,7 +163,7 @@ const queryParams = reactive({ const queryFormRef = ref() // 搜索的表单 const dataSourceConfigList = ref<DataSourceConfigApi.DataSourceConfigVO[]>([]) // 数据源列表 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/infra/job/index.vue b/src/views/infra/job/index.vue index 72f6b95b..e113878a 100644 --- a/src/views/infra/job/index.vue +++ b/src/views/infra/job/index.vue @@ -168,7 +168,7 @@ const queryParams = reactive({ const queryFormRef = ref() // 搜索的表单 const exportLoading = ref(false) // 导出的加载中 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/infra/job/logger/index.vue b/src/views/infra/job/logger/index.vue index ab28b285..f55a0647 100644 --- a/src/views/infra/job/logger/index.vue +++ b/src/views/infra/job/logger/index.vue @@ -141,7 +141,7 @@ const queryParams = reactive({ const queryFormRef = ref() // 搜索的表单 const exportLoading = ref(false) // 导出的加载中 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { diff --git a/src/views/mall/product/category/form.vue b/src/views/mall/product/category/CategoryForm.vue similarity index 76% rename from src/views/mall/product/category/form.vue rename to src/views/mall/product/category/CategoryForm.vue index e2b6f62a..b3e68001 100644 --- a/src/views/mall/product/category/form.vue +++ b/src/views/mall/product/category/CategoryForm.vue @@ -10,11 +10,11 @@ <el-form-item label="上级分类" prop="parentId"> <el-tree-select v-model="formData.parentId" - :data="parentCategoryOptions" - check-strictly - :render-after-expand="false" - placeholder="上级分类" + :data="categoryTree" :props="{ label: 'name', value: 'id' }" + :render-after-expand="false" + placeholder="请选择上级分类" + check-strictly default-expand-all /> </el-form-item> @@ -32,10 +32,11 @@ <el-form-item label="开启状态" prop="status"> <el-radio-group v-model="formData.status"> <el-radio - v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" - :label="parseInt(dict.value)" - >{{ dict.label }} + :label="dict.value" + > + {{ dict.label }} </el-radio> </el-radio-group> </el-form-item> @@ -44,18 +45,16 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> <script setup lang="ts"> -import { DICT_TYPE, getDictOptions } from '@/utils/dict' -import * as ProductCategoryApi from '@/api/mall/product/category' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { CommonStatusEnum } from '@/utils/constants' import { handleTree } from '@/utils/tree' - +import * as ProductCategoryApi from '@/api/mall/product/category' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -63,15 +62,13 @@ const modelVisible = ref(false) // 弹窗的是否展示 const modelTitle = ref('') // 弹窗的标题 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formType = ref('') // 表单的类型:create - 新增;update - 修改 - -const defaultFormData: ProductCategoryApi.CategoryVO = { +const formData = ref({ id: undefined, name: '', picUrl: '', - status: 0, + status: CommonStatusEnum.ENABLE, description: '' -} -const formData = ref({ ...defaultFormData }) +}) const formRules = reactive({ parentId: [{ required: true, message: '请选择上级分类', trigger: 'blur' }], name: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }], @@ -80,17 +77,14 @@ const formRules = reactive({ status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }] }) const formRef = ref() // 表单 Ref - -const list = ref([]) -const parentCategoryOptions = ref<any[]>([]) +const categoryTree = ref<any[]>([]) // 分类树 /** 打开弹窗 */ -const openModal = async (type: string, id?: number) => { +const open = async (type: string, id?: number) => { modelVisible.value = true modelTitle.value = t('action.' + type) formType.value = type resetForm() - getList() // 修改时,设置数据 if (id) { formLoading.value = true @@ -100,20 +94,10 @@ const openModal = async (type: string, id?: number) => { formLoading.value = false } } + // 获得分类树 + await getTree() } -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 - -// 获取上级分类选项 -const getList = async () => { - try { - const data = await ProductCategoryApi.getCategoryList({}) - list.value = data - const tree = handleTree(data, 'id', 'parentId') - const menu = { id: 0, name: '顶级分类', children: tree } - parentCategoryOptions.value = [menu] - } finally { - } -} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 /** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 @@ -143,7 +127,21 @@ const submitForm = async () => { /** 重置表单 */ const resetForm = () => { - formData.value = { ...defaultFormData } + formData.value = { + id: undefined, + name: '', + picUrl: '', + status: CommonStatusEnum.ENABLE, + description: '' + } formRef.value?.resetFields() } + +/** 获得分类树 */ +const getTree = async () => { + const data = await ProductCategoryApi.getCategoryList({}) + const tree = handleTree(data, 'id', 'parentId') + const menu = { id: 0, name: '顶级分类', children: tree } + categoryTree.value = [menu] +} </script> diff --git a/src/views/mall/product/category/index.vue b/src/views/mall/product/category/index.vue index 12f51cff..f91e1450 100644 --- a/src/views/mall/product/category/index.vue +++ b/src/views/mall/product/category/index.vue @@ -1,7 +1,13 @@ <template> - <content-wrap> - <!-- 搜索工作栏 --> - <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px"> + <!-- 搜索工作栏 --> + <ContentWrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > <el-form-item label="分类名称" prop="name"> <el-input v-model="queryParams.name" @@ -13,23 +19,25 @@ <el-form-item> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> - <el-button type="primary" @click="openModal('create')" v-hasPermi="['infra:config:create']"> + <el-button + type="primary" + plain + @click="openForm('create')" + v-hasPermi="['product:category:create']" + > <Icon icon="ep:plus" class="mr-5px" /> 新增 </el-button> </el-form-item> </el-form> + </ContentWrap> - <!-- 列表 --> - <el-table v-loading="loading" :data="list" align="center" default-expand-all row-key="id"> + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list" row-key="id" default-expand-all> <el-table-column label="分类名称" prop="name" sortable /> <el-table-column label="分类图片" align="center" prop="picUrl"> <template #default="scope"> - <img - v-if="scope.row.picUrl" - :src="scope.row.picUrl" - alt="分类图片" - style="height: 100px" - /> + <img v-if="scope.row.picUrl" :src="scope.row.picUrl" alt="分类图片" class="h-100px" /> </template> </el-table-column> <el-table-column label="分类排序" align="center" prop="sort" /> @@ -50,8 +58,8 @@ <el-button link type="primary" - @click="openModal('update', scope.row.id)" - v-hasPermi="['infra:config:update']" + @click="openForm('update', scope.row.id)" + v-hasPermi="['product:category:update']" > 编辑 </el-button> @@ -59,25 +67,24 @@ link type="danger" @click="handleDelete(scope.row.id)" - v-hasPermi="['infra:config:delete']" + v-hasPermi="['product:category:delete']" > 删除 </el-button> </template> </el-table-column> </el-table> - </content-wrap> + </ContentWrap> <!-- 表单弹窗:添加/修改 --> - <config-form ref="modalRef" @success="getList" /> + <CategoryForm ref="formRef" @success="getList" /> </template> -<script setup lang="ts" name="Config"> +<script setup lang="ts" name="ProductCategory"> import { DICT_TYPE } from '@/utils/dict' +import { handleTree } from '@/utils/tree' import { dateFormatter } from '@/utils/formatTime' import * as ProductCategoryApi from '@/api/mall/product/category' -import ConfigForm from './form.vue' -import { handleTree } from '@/utils/tree' - +import CategoryForm from './CategoryForm.vue' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -88,13 +95,12 @@ const queryParams = reactive({ }) const queryFormRef = ref() // 搜索的表单 -/** 查询参数列表 */ +/** 查询列表 */ const getList = async () => { loading.value = true try { const data = await ProductCategoryApi.getCategoryList(queryParams) list.value = handleTree(data, 'id', 'parentId') - console.info(list) } finally { loading.value = false } @@ -112,9 +118,9 @@ const resetQuery = () => { } /** 添加/修改操作 */ -const modalRef = ref() -const openModal = (type: string, id?: number) => { - modalRef.value.openModal(type, id) +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) } /** 删除按钮操作 */ diff --git a/src/views/mall/product/category/utils.ts b/src/views/mall/product/category/utils.ts deleted file mode 100644 index a3774f22..00000000 --- a/src/views/mall/product/category/utils.ts +++ /dev/null @@ -1,44 +0,0 @@ -export const parseTime = (time) => { - if (!time) { - return null - } - const format = '{y}-{m}-{d} {h}:{i}:{s}' - let date - if (typeof time === 'object') { - date = time - } else { - if (typeof time === 'string' && /^[0-9]+$/.test(time)) { - time = parseInt(time) - } else if (typeof time === 'string') { - time = time - .replace(new RegExp(/-/gm), '/') - .replace('T', ' ') - .replace(new RegExp(/\.[\d]{3}/gm), '') - } - if (typeof time === 'number' && time.toString().length === 10) { - time = time * 1000 - } - date = new Date(time) - } - const formatObj = { - y: date.getFullYear(), - m: date.getMonth() + 1, - d: date.getDate(), - h: date.getHours(), - i: date.getMinutes(), - s: date.getSeconds(), - a: date.getDay() - } - const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { - let value = formatObj[key] - // Note: getDay() returns 0 on Sunday - if (key === 'a') { - return ['日', '一', '二', '三', '四', '五', '六'][value] - } - if (result.length > 0 && value < 10) { - value = '0' + value - } - return value || 0 - }) - return time_str -} diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue index ead319ce..20f19013 100644 --- a/src/views/system/dept/index.vue +++ b/src/views/system/dept/index.vue @@ -55,8 +55,8 @@ v-loading="loading" :data="list" row-key="id" + default-expand-all v-if="refreshTable" - :default-expand-all="isExpandAll" > <el-table-column prop="name" label="部门名称" width="260" /> <el-table-column prop="leader" label="负责人" width="120"> From 5a5202d483da115b4c9c01a6f6e3b7a5c07e35c8 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 2 Apr 2023 01:25:31 +0800 Subject: [PATCH 180/184] =?UTF-8?q?REVIEW=20=E5=95=86=E5=93=81=E5=B1=9E?= =?UTF-8?q?=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/mall/product/property.ts | 2 +- src/views/mall/product/category/index.vue | 1 + .../property/{form.vue => PropertyForm.vue} | 15 ++--- src/views/mall/product/property/index.vue | 57 +++++++++++-------- 4 files changed, 44 insertions(+), 31 deletions(-) rename src/views/mall/product/property/{form.vue => PropertyForm.vue} (90%) diff --git a/src/api/mall/product/property.ts b/src/api/mall/product/property.ts index dd693c5c..01c79f9f 100644 --- a/src/api/mall/product/property.ts +++ b/src/api/mall/product/property.ts @@ -61,7 +61,7 @@ export const getProperty = (id: number): Promise<PropertyVO> => { } // 获得属性项分页 -export const getPropertyPage = (params: PageParam & any) => { +export const getPropertyPage = (params: PageParam) => { return request.get({ url: '/product/property/page', params }) } diff --git a/src/views/mall/product/category/index.vue b/src/views/mall/product/category/index.vue index f91e1450..f57e35f8 100644 --- a/src/views/mall/product/category/index.vue +++ b/src/views/mall/product/category/index.vue @@ -14,6 +14,7 @@ placeholder="请输入分类名称" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item> diff --git a/src/views/mall/product/property/form.vue b/src/views/mall/product/property/PropertyForm.vue similarity index 90% rename from src/views/mall/product/property/form.vue rename to src/views/mall/product/property/PropertyForm.vue index 360af99b..393d00ef 100644 --- a/src/views/mall/product/property/form.vue +++ b/src/views/mall/product/property/PropertyForm.vue @@ -24,7 +24,6 @@ </template> <script setup lang="ts"> import * as PropertyApi from '@/api/mall/product/property' - const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -32,18 +31,17 @@ const modelVisible = ref(false) // 弹窗的是否展示 const modelTitle = ref('') // 弹窗的标题 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formType = ref('') // 表单的类型:create - 新增;update - 修改 -const defaultFormData: PropertyApi.PropertyVO = { +const formData = ref({ id: undefined, name: '' -} -const formData = ref({ ...defaultFormData }) +}) const formRules = reactive({ name: [{ required: true, message: '名称不能为空', trigger: 'blur' }] }) const formRef = ref() // 表单 Ref /** 打开弹窗 */ -const openModal = async (type: string, id?: number) => { +const open = async (type: string, id?: number) => { modelVisible.value = true modelTitle.value = t('action.' + type) formType.value = type @@ -58,7 +56,7 @@ const openModal = async (type: string, id?: number) => { } } } -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 /** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 @@ -88,7 +86,10 @@ const submitForm = async () => { /** 重置表单 */ const resetForm = () => { - formData.value = { ...defaultFormData } + formData.value = { + id: undefined, + name: '' + } formRef.value?.resetFields() } </script> diff --git a/src/views/mall/product/property/index.vue b/src/views/mall/product/property/index.vue index 36cb5a11..ea992923 100644 --- a/src/views/mall/product/property/index.vue +++ b/src/views/mall/product/property/index.vue @@ -1,13 +1,20 @@ <template> - <content-wrap> - <!-- 搜索工作栏 --> - <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px"> + <!-- 搜索工作栏 --> + <ContentWrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > <el-form-item label="名称" prop="name"> <el-input v-model="queryParams.name" placeholder="请输入名称" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="创建时间" prop="createTime"> @@ -15,31 +22,32 @@ v-model="queryParams.createTime" value-format="YYYY-MM-DD HH:mm:ss" type="daterange" - range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="!w-240px" /> </el-form-item> <el-form-item> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> - <el-button type="primary" @click="openModal('create')" v-hasPermi="['infra:config:create']"> + <el-button + plain + type="primary" + @click="openForm('create')" + v-hasPermi="['product:property:create']" + > <Icon icon="ep:plus" class="mr-5px" /> 新增 </el-button> </el-form-item> </el-form> + </ContentWrap> - <!-- 列表 --> - <el-table v-loading="loading" :data="list" align="center"> + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list"> <el-table-column label="编号" align="center" prop="id" /> - <el-table-column label="名称" align="center" :show-overflow-tooltip="true"> - <template #default="scope"> - <router-link :to="'/property/value/' + scope.row.id" class="link-type"> - <span>{{ scope.row.name }}</span> - </router-link> - </template> - </el-table-column> + <el-table-column label="名称" align="center" /> <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" /> <el-table-column label="创建时间" @@ -53,16 +61,19 @@ <el-button link type="primary" - @click="openModal('update', scope.row.id)" - v-hasPermi="['infra:config:update']" + @click="openForm('update', scope.row.id)" + v-hasPermi="['product:property:update']" > 编辑 </el-button> + <el-button link type="primary"> + <router-link :to="'/property/value/' + scope.row.id">属性值</router-link> + </el-button> <el-button link type="danger" @click="handleDelete(scope.row.id)" - v-hasPermi="['infra:config:delete']" + v-hasPermi="['product:property:delete']" > 删除 </el-button> @@ -76,15 +87,15 @@ v-model:limit="queryParams.pageSize" @pagination="getList" /> - </content-wrap> + </ContentWrap> <!-- 表单弹窗:添加/修改 --> - <property-form ref="modalRef" @success="getList" /> + <PropertyForm ref="formRef" @success="getList" /> </template> <script setup lang="ts" name="Config"> import { dateFormatter } from '@/utils/formatTime' import * as PropertyApi from '@/api/mall/product/property' -import PropertyForm from './form.vue' +import PropertyForm from './PropertyForm.vue' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -124,9 +135,9 @@ const resetQuery = () => { } /** 添加/修改操作 */ -const modalRef = ref() -const openModal = (type: string, id?: number) => { - modalRef.value.openModal(type, id) +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) } /** 删除按钮操作 */ From b18bf8a95fed33abb562586033e66c95c549babe Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 2 Apr 2023 09:46:45 +0800 Subject: [PATCH 181/184] =?UTF-8?q?REVIEW=20=E5=95=86=E5=93=81=E5=B1=9E?= =?UTF-8?q?=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../value/{form.vue => ValueForm.vue} | 27 ++++--- .../mall/product/property/value/index.vue | 80 +++++++++---------- 2 files changed, 53 insertions(+), 54 deletions(-) rename src/views/mall/product/property/value/{form.vue => ValueForm.vue} (81%) diff --git a/src/views/mall/product/property/value/form.vue b/src/views/mall/product/property/value/ValueForm.vue similarity index 81% rename from src/views/mall/product/property/value/form.vue rename to src/views/mall/product/property/value/ValueForm.vue index 51224986..e113825a 100644 --- a/src/views/mall/product/property/value/form.vue +++ b/src/views/mall/product/property/value/ValueForm.vue @@ -7,7 +7,7 @@ label-width="80px" v-loading="formLoading" > - <el-form-item label="属性id" prop="category"> + <el-form-item label="属性编号" prop="category"> <el-input v-model="formData.propertyId" disabled="" /> </el-form-item> <el-form-item label="名称" prop="name"> @@ -18,16 +18,13 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> <script setup lang="ts"> import * as PropertyApi from '@/api/mall/product/property' - const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -35,13 +32,12 @@ const modelVisible = ref(false) // 弹窗的是否展示 const modelTitle = ref('') // 弹窗的标题 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formType = ref('') // 表单的类型:create - 新增;update - 修改 -const defaultFormData: PropertyApi.PropertyValueVO = { +const formData = ref({ id: undefined, propertyId: undefined, name: '', remark: '' -} -const formData = ref({ ...defaultFormData }) +}) const formRules = reactive({ propertyId: [{ required: true, message: '属性不能为空', trigger: 'blur' }], name: [{ required: true, message: '名称不能为空', trigger: 'blur' }] @@ -49,12 +45,12 @@ const formRules = reactive({ const formRef = ref() // 表单 Ref /** 打开弹窗 */ -const openModal = async (type: string, propertyId: number, id?: number) => { +const open = async (type: string, propertyId: number, id?: number) => { modelVisible.value = true modelTitle.value = t('action.' + type) formType.value = type - defaultFormData.propertyId = propertyId resetForm() + formData.value.propertyId = propertyId // 修改时,设置数据 if (id) { formLoading.value = true @@ -65,7 +61,7 @@ const openModal = async (type: string, propertyId: number, id?: number) => { } } } -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 /** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 @@ -95,7 +91,12 @@ const submitForm = async () => { /** 重置表单 */ const resetForm = () => { - formData.value = { ...defaultFormData } + formData.value = { + id: undefined, + propertyId: undefined, + name: '', + remark: '' + } formRef.value?.resetFields() } </script> diff --git a/src/views/mall/product/property/value/index.vue b/src/views/mall/product/property/value/index.vue index 03b021ab..85383e63 100644 --- a/src/views/mall/product/property/value/index.vue +++ b/src/views/mall/product/property/value/index.vue @@ -1,9 +1,15 @@ <template> - <content-wrap> - <!-- 搜索工作栏 --> - <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px"> + <!-- 搜索工作栏 --> + <ContentWrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > <el-form-item label="属性项" prop="propertyId"> - <el-select v-model="queryParams.propertyId"> + <el-select v-model="queryParams.propertyId" class="!w-240px"> <el-option v-for="item in propertyOptions" :key="item.id" @@ -18,19 +24,27 @@ placeholder="请输入名称" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> - <el-button type="primary" @click="openModal('create')" v-hasPermi="['infra:config:create']"> + <el-button + plain + type="primary" + @click="openForm('create')" + v-hasPermi="['product:property:create']" + > <Icon icon="ep:plus" class="mr-5px" /> 新增 </el-button> </el-form-item> </el-form> + </ContentWrap> - <!-- 列表 --> - <el-table v-loading="loading" :data="list" align="center"> + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list"> <el-table-column label="编号" align="center" prop="id" /> <el-table-column label="名称" align="center" prop="name" :show-overflow-tooltip="true" /> <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" /> @@ -46,8 +60,8 @@ <el-button link type="primary" - @click="openModal('update', scope.row.id)" - v-hasPermi="['infra:config:update']" + @click="openForm('update', scope.row.id)" + v-hasPermi="['product:property:update']" > 编辑 </el-button> @@ -55,7 +69,7 @@ link type="danger" @click="handleDelete(scope.row.id)" - v-hasPermi="['infra:config:delete']" + v-hasPermi="['product:property:delete']" > 删除 </el-button> @@ -69,30 +83,30 @@ v-model:limit="queryParams.pageSize" @pagination="getList" /> - </content-wrap> + </ContentWrap> <!-- 表单弹窗:添加/修改 --> - <value-form ref="modalRef" @success="getList" /> + <ValueForm ref="formRef" @success="getList" /> </template> <script setup lang="ts" name="Config"> import { dateFormatter } from '@/utils/formatTime' import * as PropertyApi from '@/api/mall/product/property' -import ValueForm from './form.vue' +import ValueForm from './ValueForm.vue' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 +const { params } = useRoute() // 查询参数 const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 const list = ref([]) // 列表的数据 -const propertyOptions = ref<any[]>([]) -const defaultPropertyId = ref() -const queryParams = reactive<any>({ +const queryParams = reactive({ pageNo: 1, pageSize: 10, - name: undefined, - propertyId: undefined + propertyId: Number(params.propertyId), + name: undefined }) const queryFormRef = ref() // 搜索的表单 +const propertyOptions = ref([]) // 属性项的列表 /** 查询参数列表 */ const getList = async () => { @@ -106,20 +120,6 @@ const getList = async () => { } } -/** 属性项下拉框数据 */ -const getPropertyList = async () => { - const data = await PropertyApi.getPropertyList({}) - propertyOptions.value = data -} - -/** 查询字典类型详细 */ -const getProperty = async (propertyId: number) => { - const data = await PropertyApi.getProperty(propertyId) - queryParams.propertyId = data.id - defaultPropertyId.value = data.id - await getList() -} - /** 搜索按钮操作 */ const handleQuery = () => { queryParams.pageNo = 1 @@ -133,9 +133,9 @@ const resetQuery = () => { } /** 添加/修改操作 */ -const modalRef = ref() -const openModal = (type: string, id?: number) => { - modalRef.value.openModal(type, defaultPropertyId, id) +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, queryParams.propertyId, id) } /** 删除按钮操作 */ @@ -152,11 +152,9 @@ const handleDelete = async (id: number) => { } /** 初始化 **/ -const router = useRouter() -onMounted(() => { - const propertyId: number = - router.currentRoute.value.params && (router.currentRoute.value.params.propertyId as any) - getProperty(propertyId) - getPropertyList() +onMounted(async () => { + await getList() + // 属性项下拉框数据 + propertyOptions.value = await PropertyApi.getPropertyList({}) }) </script> From a319d090bbcecaf21f60715bc4382107ab9737c8 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 2 Apr 2023 10:24:03 +0800 Subject: [PATCH 182/184] =?UTF-8?q?REVIEW=20=E6=94=AF=E4=BB=98=E5=95=86?= =?UTF-8?q?=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/pay/merchant/index.ts | 17 +- .../merchant/{form.vue => MerchantForm.vue} | 34 ++-- src/views/pay/merchant/index.vue | 175 +++++++++--------- src/views/system/notice/index.vue | 6 +- 4 files changed, 118 insertions(+), 114 deletions(-) rename src/views/pay/merchant/{form.vue => MerchantForm.vue} (73%) diff --git a/src/api/pay/merchant/index.ts b/src/api/pay/merchant/index.ts index b4b6ba51..bfb8f5e4 100644 --- a/src/api/pay/merchant/index.ts +++ b/src/api/pay/merchant/index.ts @@ -29,17 +29,17 @@ export interface MerchantExportReqVO { } // 查询列表支付商户 -export const getMerchantPageApi = (params: MerchantPageReqVO) => { +export const getMerchantPage = (params: MerchantPageReqVO) => { return request.get({ url: '/pay/merchant/page', params }) } // 查询详情支付商户 -export const getMerchantApi = (id: number) => { +export const getMerchant = (id: number) => { return request.get({ url: '/pay/merchant/get?id=' + id }) } // 根据商户名称搜索商户列表 -export const getMerchantListByNameApi = (name: string) => { +export const getMerchantListByName = (name: string) => { return request.get({ url: '/pay/merchant/list-by-name?id=', params: { @@ -49,26 +49,27 @@ export const getMerchantListByNameApi = (name: string) => { } // 新增支付商户 -export const createMerchantApi = (data: MerchantVO) => { +export const createMerchant = (data: MerchantVO) => { return request.post({ url: '/pay/merchant/create', data }) } // 修改支付商户 -export const updateMerchantApi = (data: MerchantVO) => { +export const updateMerchant = (data: MerchantVO) => { return request.put({ url: '/pay/merchant/update', data }) } // 删除支付商户 -export const deleteMerchantApi = (id: number) => { +export const deleteMerchant = (id: number) => { return request.delete({ url: '/pay/merchant/delete?id=' + id }) } // 导出支付商户 -export const exportMerchantApi = (params: MerchantExportReqVO) => { +export const exportMerchant = (params: MerchantExportReqVO) => { return request.download({ url: '/pay/merchant/export-excel', params }) } + // 支付商户状态修改 -export const changeMerchantStatusApi = (id: number, status: number) => { +export const updateMerchantStatus = (id: number, status: number) => { const data = { id, status diff --git a/src/views/pay/merchant/form.vue b/src/views/pay/merchant/MerchantForm.vue similarity index 73% rename from src/views/pay/merchant/form.vue rename to src/views/pay/merchant/MerchantForm.vue index d89fc7a3..1e09fb8c 100644 --- a/src/views/pay/merchant/form.vue +++ b/src/views/pay/merchant/MerchantForm.vue @@ -1,15 +1,14 @@ <template> - <Dialog :title="modelTitle" v-model="modelVisible" width="800"> - <!-- 对话框(添加 / 修改) --> - <el-form ref="formRef" :model="form" :rules="formRules" label-width="80px"> + <Dialog :title="modelTitle" v-model="modelVisible"> + <el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px"> <el-form-item label="商户全称" prop="name"> - <el-input v-model="form.name" placeholder="请输入商户全称" /> + <el-input v-model="formData.name" placeholder="请输入商户全称" /> </el-form-item> <el-form-item label="商户简称" prop="shortName"> - <el-input v-model="form.shortName" placeholder="请输入商户简称" /> + <el-input v-model="formData.shortName" placeholder="请输入商户简称" /> </el-form-item> <el-form-item label="开启状态" prop="status"> - <el-select v-model="form.status" placeholder="请选择状态" clearable> + <el-select v-model="formData.status" placeholder="请选择状态" clearable> <el-option v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" @@ -19,7 +18,7 @@ </el-select> </el-form-item> <el-form-item label="备注" prop="remark"> - <el-input v-model="form.remark" placeholder="请输入备注" /> + <el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" /> </el-form-item> </el-form> <template #footer> @@ -31,6 +30,7 @@ <script setup lang="ts"> import * as MerchantApi from '@/api/pay/merchant' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { CommonStatusEnum } from '@/utils/constants' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -38,11 +38,11 @@ const modelVisible = ref(false) // 弹窗的是否展示 const modelTitle = ref('') // 弹窗的标题 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formType = ref('') // 表单的类型:create - 新增;update - 修改 -const form = ref({ +const formData = ref({ id: undefined, name: '', shortName: '', - status: undefined, + status: CommonStatusEnum.ENABLE, remark: '' }) const formRules = reactive({ @@ -53,7 +53,7 @@ const formRules = reactive({ const formRef = ref() // 表单 Ref /** 打开弹窗 */ -const openModal = async (type: string, id?: number) => { +const open = async (type: string, id?: number) => { modelVisible.value = true modelTitle.value = t('action.' + type) formType.value = type @@ -62,13 +62,13 @@ const openModal = async (type: string, id?: number) => { if (id) { formLoading.value = true try { - form.value = await MerchantApi.getMerchantApi(id) + formData.value = await MerchantApi.getMerchant(id) } finally { formLoading.value = false } } } -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 /** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 @@ -80,12 +80,12 @@ const submitForm = async () => { // 提交请求 formLoading.value = true try { - const data = form.value as unknown as MerchantApi.MerchantVO + const data = formData.value as unknown as MerchantApi.MerchantVO if (formType.value === 'create') { - await MerchantApi.createMerchantApi(data) + await MerchantApi.createMerchant(data) message.success(t('common.createSuccess')) } else { - await MerchantApi.updateMerchantApi(data) + await MerchantApi.updateMerchant(data) message.success(t('common.updateSuccess')) } modelVisible.value = false @@ -98,11 +98,11 @@ const submitForm = async () => { /** 重置表单 */ const resetForm = () => { - form.value = { + formData.value = { id: undefined, name: '', shortName: '', - status: undefined, + status: CommonStatusEnum.ENABLE, remark: '' } formRef.value?.resetFields() diff --git a/src/views/pay/merchant/index.vue b/src/views/pay/merchant/index.vue index e9a9ff30..d2e487c3 100644 --- a/src/views/pay/merchant/index.vue +++ b/src/views/pay/merchant/index.vue @@ -1,79 +1,86 @@ <template> <content-wrap> + <!-- 搜索工作栏 --> <el-form + class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" - v-show="showSearch" label-width="68px" - ><el-form-item label="商户号" prop="no"> - <el-input v-model="queryParams.no" placeholder="请输入商户号" clearable /> + > + <el-form-item label="商户号" prop="no"> + <el-input v-model="queryParams.no" placeholder="请输入商户号" clearable class="!w-240px" /> </el-form-item> <el-form-item label="商户全称" prop="name"> - <el-input v-model="queryParams.name" placeholder="请输入商户全称" clearable /> + <el-input + v-model="queryParams.name" + placeholder="请输入商户全称" + clearable + class="!w-240px" + /> </el-form-item> <el-form-item label="商户简称" prop="shortName"> - <el-input v-model="queryParams.shortName" placeholder="请输入商户简称" clearable /> + <el-input + v-model="queryParams.shortName" + placeholder="请输入商户简称" + clearable + class="!w-240px" + /> </el-form-item> <el-form-item label="开启状态" prop="status"> <el-select v-model="queryParams.status" placeholder="字典状态" clearable class="!w-240px"> <el-option - v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" - :key="parseInt(dict.value)" + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" :label="dict.label" :value="dict.value" /> </el-select> </el-form-item> <el-form-item label="备注" prop="remark"> - <el-input v-model="queryParams.remark" placeholder="请输入备注" clearable /> + <el-input + v-model="queryParams.remark" + placeholder="请输入备注" + clearable + class="!w-240px" + /> </el-form-item> <el-form-item label="创建时间" prop="createTime"> <el-date-picker v-model="queryParams.createTime" - style="width: 240px" value-format="YYYY-MM-DD HH:mm:ss" type="datetimerange" - range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" + class="!w-240px" /> </el-form-item> <el-form-item> - <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button> - <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + <el-button + plain + type="primary" + @click="openForm('create')" + v-hasPermi="['pay:merchant:create']" + > + <Icon icon="ep:plus" class="mr-5px" /> 新增 + </el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['pay:merchant:export']" + > + <Icon icon="ep:download" class="mr-5px" /> 导出 + </el-button> </el-form-item> </el-form> - - <!-- 操作工具栏 --> - <el-row :gutter="10" class="mb8"> - <el-col :span="1.5"> - <el-button - type="primary" - plain - icon="el-icon-plus" - size="small" - @click="openModal('create')" - v-hasPermi="['pay:merchant:create']" - >新增</el-button - > - </el-col> - <el-col :span="1.5"> - <el-button - type="warning" - plain - icon="el-icon-download" - size="small" - :loading="exportLoading" - @click="handleExport" - v-hasPermi="['pay:merchant:export']" - >导出</el-button - > - </el-col> - </el-row> </content-wrap> + + <!-- 列表 --> <content-wrap> - <!-- 列表 --> <el-table v-loading="loading" :data="list"> <el-table-column label="商户编号" align="center" prop="id" /> <el-table-column label="商户号" align="center" prop="no" /> @@ -92,52 +99,51 @@ <el-table-column label="备注" align="center" prop="remark" /> <el-table-column label="创建时间" - :formatter="dateFormatter" align="center" prop="createTime" + :formatter="dateFormatter" width="180" /> - <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <el-table-column label="操作" align="center"> <template #default="scope"> <el-button link type="primary" - icon="el-icon-edit" - @click="openModal('update', scope.row.id)" + @click="openForm('update', scope.row.id)" v-hasPermi="['pay:merchant:update']" - >修改</el-button > + 修改 + </el-button> <el-button link type="danger" - icon="el-icon-delete" @click="handleDelete(scope.row.id)" v-hasPermi="['pay:merchant:delete']" - >删除</el-button > + 删除 + </el-button> </template> </el-table-column> </el-table> - <!-- 分页组件 --> - <pagination - v-show="total > 0" + <!-- 分页 --> + <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" /> - <!-- 表单弹窗:添加/修改 --> - <MerchantForm ref="modalRef" @success="getList" /> </content-wrap> -</template> + <!-- 表单弹窗:添加/修改 --> + <MerchantForm ref="formRef" @success="getList" /> +</template> <script setup lang="ts" name="Merchant"> -import { DICT_TYPE, getDictOptions } from '@/utils/dict' -import * as MerchantApi from '@/api/pay/merchant' -import MerchantForm from './form.vue' -import download from '@/utils/download' -import { dateFormatter } from '@/utils/formatTime' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { CommonStatusEnum } from '@/utils/constants' +import { dateFormatter } from '@/utils/formatTime' +import download from '@/utils/download' +import * as MerchantApi from '@/api/pay/merchant' +import MerchantForm from './MerchantForm.vue' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -145,21 +151,20 @@ const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 const list = ref([]) // 列表的数据 const queryParams = reactive({ + pageNo: 1, + pageSize: 10, name: '', shortName: '', - status: undefined, - pageNo: 1, - pageSize: 100 + status: undefined }) const queryFormRef = ref() // 搜索的表单 -const showSearch = ref(true) const exportLoading = ref(false) // 导出的加载中 + /** 查询列表 */ const getList = async () => { loading.value = true try { - const data = await MerchantApi.getMerchantPageApi(queryParams) - + const data = await MerchantApi.getMerchantPage(queryParams) list.value = data.list total.value = data.total } finally { @@ -180,9 +185,9 @@ const resetQuery = () => { } /** 添加/修改操作 */ -const modalRef = ref() -const openModal = (type: string, id?: number) => { - modalRef.value.openModal(type, id) +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) } /** 删除按钮操作 */ @@ -191,30 +196,28 @@ const handleDelete = async (id: number) => { // 删除的二次确认 await message.delConfirm() // 发起删除 - await MerchantApi.deleteMerchantApi(id) + await MerchantApi.deleteMerchant(id) message.success(t('common.delSuccess')) // 刷新列表 await getList() } catch {} } -// 启用状态修改 -const handleStatusChange = (row: MerchantApi.MerchantVO) => { - let info = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' - message - .confirm('确认要"' + info + '""' + row.name + '"商户吗?', t('common.reminder')) - .then(async () => { - row.status = - row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.ENABLE : CommonStatusEnum.DISABLE - await MerchantApi.updateMerchantApi(row) - message.success(info + '成功') - // 刷新列表 - getList() - }) - .catch(() => { - row.status = - row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE - }) +/** 修改状态操作 */ +const handleStatusChange = async (row: MerchantApi.MerchantVO) => { + try { + // 修改状态的二次确认 + const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' + await message.confirm('确认要"' + text + '""' + row.name + '"商户吗?', t('common.reminder')) + // 发起修改状态 + await MerchantApi.updateMerchantStatus(row.id, row.status) + // 刷新列表 + await getList() + } catch { + // 取消后,进行恢复按钮 + row.status = + row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE + } } /** 导出按钮操作 */ @@ -224,7 +227,7 @@ const handleExport = async () => { await message.exportConfirm() // 发起导出 exportLoading.value = true - const data = await MerchantApi.exportMerchantApi(queryParams) + const data = await MerchantApi.exportMerchant(queryParams) download.excel(data, '商户信息.xls') } catch { } finally { diff --git a/src/views/system/notice/index.vue b/src/views/system/notice/index.vue index 65076f9f..340eeaa4 100644 --- a/src/views/system/notice/index.vue +++ b/src/views/system/notice/index.vue @@ -114,11 +114,11 @@ const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 const list = ref([]) // 列表的数据 const queryParams = reactive({ + pageNo: 1, + pageSize: 10, title: '', type: undefined, - status: undefined, - pageNo: 1, - pageSize: 100 + status: undefined }) const queryFormRef = ref() // 搜索的表单 From a56a2f6099ae741096290e644f91acea54bc242c Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 2 Apr 2023 11:05:51 +0800 Subject: [PATCH 183/184] =?UTF-8?q?REVIEW=20OAuth2=20=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/system/oauth2/client.ts | 22 +++++------ .../client/{form.vue => ClientForm.vue} | 38 +++++++++---------- src/views/system/oauth2/client/index.vue | 34 ++++++++++------- 3 files changed, 48 insertions(+), 46 deletions(-) rename src/views/system/oauth2/client/{form.vue => ClientForm.vue} (88%) diff --git a/src/api/system/oauth2/client.ts b/src/api/system/oauth2/client.ts index 4c06386d..6f71acad 100644 --- a/src/api/system/oauth2/client.ts +++ b/src/api/system/oauth2/client.ts @@ -21,31 +21,27 @@ export interface OAuth2ClientVO { createTime: Date } -export interface OAuth2ClientPageReqVO extends PageParam { - name?: string - status?: number -} -// 查询 OAuth2列表 -export const getOAuth2ClientPageApi = (params: OAuth2ClientPageReqVO) => { +// 查询 OAuth2 客户端的列表 +export const getOAuth2ClientPage = (params: PageParam) => { return request.get({ url: '/system/oauth2-client/page', params }) } -// 查询 OAuth2详情 -export const getOAuth2ClientApi = (id: number) => { +// 查询 OAuth2 客户端的详情 +export const getOAuth2Client = (id: number) => { return request.get({ url: '/system/oauth2-client/get?id=' + id }) } -// 新增 OAuth2 -export const createOAuth2ClientApi = (data: OAuth2ClientVO) => { +// 新增 OAuth2 客户端 +export const createOAuth2Client = (data: OAuth2ClientVO) => { return request.post({ url: '/system/oauth2-client/create', data }) } -// 修改 OAuth2 -export const updateOAuth2ClientApi = (data: OAuth2ClientVO) => { +// 修改 OAuth2 客户端 +export const updateOAuth2Client = (data: OAuth2ClientVO) => { return request.put({ url: '/system/oauth2-client/update', data }) } // 删除 OAuth2 -export const deleteOAuth2ClientApi = (id: number) => { +export const deleteOAuth2Client = (id: number) => { return request.delete({ url: '/system/oauth2-client/delete?id=' + id }) } diff --git a/src/views/system/oauth2/client/form.vue b/src/views/system/oauth2/client/ClientForm.vue similarity index 88% rename from src/views/system/oauth2/client/form.vue rename to src/views/system/oauth2/client/ClientForm.vue index 2800bd1f..2e2281f5 100644 --- a/src/views/system/oauth2/client/form.vue +++ b/src/views/system/oauth2/client/ClientForm.vue @@ -1,10 +1,10 @@ <template> - <Dialog :title="modelTitle" v-model="modelVisible" width="800"> + <Dialog :title="modelTitle" v-model="modelVisible" scroll max-height="500px"> <el-form ref="formRef" :model="formData" :rules="formRules" - label-width="120px" + label-width="160px" v-loading="formLoading" > <el-form-item label="客户端编号" prop="secret"> @@ -17,7 +17,7 @@ <el-input v-model="formData.name" placeholder="请输入应用名" /> </el-form-item> <el-form-item label="应用图标"> - <imageUpload v-model="formData.logo" :limit="1" /> + <UploadImg v-model="formData.logo" :limit="1" /> </el-form-item> <el-form-item label="应用描述"> <el-input type="textarea" v-model="formData.description" placeholder="请输入应用名" /> @@ -25,11 +25,12 @@ <el-form-item label="状态" prop="status"> <el-radio-group v-model="formData.status"> <el-radio - v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" - :label="parseInt(dict.value)" - >{{ dict.label }}</el-radio + :label="dict.value" > + {{ dict.label }} + </el-radio> </el-radio-group> </el-form-item> <el-form-item label="访问令牌的有效期" prop="accessTokenValiditySeconds"> @@ -47,7 +48,7 @@ style="width: 500px" > <el-option - v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_OAUTH2_GRANT_TYPE)" + v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_OAUTH2_GRANT_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value" @@ -137,15 +138,14 @@ </el-form-item> </el-form> <template #footer> - <div class="dialog-footer"> - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> - <el-button @click="modelVisible = false">取 消</el-button> - </div> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> </template> </Dialog> </template> <script setup lang="ts"> -import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { CommonStatusEnum } from '@/utils/constants' import * as ClientApi from '@/api/system/oauth2/client' const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 @@ -161,7 +161,7 @@ const formData = ref({ name: undefined, logo: undefined, description: undefined, - status: DICT_TYPE.COMMON_STATUS, + status: CommonStatusEnum.ENABLE, accessTokenValiditySeconds: 30 * 60, refreshTokenValiditySeconds: 30 * 24 * 60, redirectUris: [], @@ -190,7 +190,7 @@ const formRules = reactive({ const formRef = ref() // 表单 Ref /** 打开弹窗 */ -const openModal = async (type: string, id?: number) => { +const open = async (type: string, id?: number) => { modelVisible.value = true modelTitle.value = t('action.' + type) formType.value = type @@ -199,13 +199,13 @@ const openModal = async (type: string, id?: number) => { if (id) { formLoading.value = true try { - formData.value = await ClientApi.getOAuth2ClientApi(id) + formData.value = await ClientApi.getOAuth2Client(id) } finally { formLoading.value = false } } } -defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗 +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 /** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 @@ -219,10 +219,10 @@ const submitForm = async () => { try { const data = formData.value as unknown as ClientApi.OAuth2ClientVO if (formType.value === 'create') { - await ClientApi.createOAuth2ClientApi(data) + await ClientApi.createOAuth2Client(data) message.success(t('common.createSuccess')) } else { - await ClientApi.updateOAuth2ClientApi(data) + await ClientApi.updateOAuth2Client(data) message.success(t('common.updateSuccess')) } modelVisible.value = false @@ -242,7 +242,7 @@ const resetForm = () => { name: undefined, logo: undefined, description: undefined, - status: DICT_TYPE.COMMON_STATUS, + status: CommonStatusEnum.ENABLE, accessTokenValiditySeconds: 30 * 60, refreshTokenValiditySeconds: 30 * 24 * 60, redirectUris: [], diff --git a/src/views/system/oauth2/client/index.vue b/src/views/system/oauth2/client/index.vue index ff196c6f..fa04e6d3 100644 --- a/src/views/system/oauth2/client/index.vue +++ b/src/views/system/oauth2/client/index.vue @@ -14,12 +14,13 @@ placeholder="请输入应用名" clearable @keyup.enter="handleQuery" + class="!w-240px" /> </el-form-item> <el-form-item label="状态" prop="status"> - <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small"> + <el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px"> <el-option - v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" :label="dict.label" :value="dict.value" @@ -29,7 +30,12 @@ <el-form-item> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> - <el-button type="primary" @click="openModal('create')" v-hasPermi="['infra:config:create']"> + <el-button + plain + type="primary" + @click="openForm('create')" + v-hasPermi="['system:oauth2-client:create']" + > <Icon icon="ep:plus" class="mr-5px" /> 新增 </el-button> </el-form-item> @@ -82,8 +88,8 @@ <el-button link type="primary" - @click="openModal('update', scope.row.id)" - v-hasPermi="['infra:config:update']" + @click="openForm('update', scope.row.id)" + v-hasPermi="['system:oauth2-client:update']" > 编辑 </el-button> @@ -91,7 +97,7 @@ link type="danger" @click="handleDelete(scope.row.id)" - v-hasPermi="['infra:config:delete']" + v-hasPermi="['system:oauth2-client:delete']" > 删除 </el-button> @@ -108,13 +114,13 @@ </content-wrap> <!-- 表单弹窗:添加/修改 --> - <ClientForm ref="modalRef" @success="getList" /> + <ClientForm ref="formRef" @success="getList" /> </template> <script setup lang="ts"> -import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { dateFormatter } from '@/utils/formatTime' import * as ClientApi from '@/api/system/oauth2/client' -import ClientForm from './form.vue' +import ClientForm from './ClientForm.vue' const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -133,7 +139,7 @@ const queryFormRef = ref() // 搜索的表单 const getList = async () => { loading.value = true try { - const data = await ClientApi.getOAuth2ClientPageApi(queryParams) + const data = await ClientApi.getOAuth2ClientPage(queryParams) list.value = data.list total.value = data.total } finally { @@ -154,9 +160,9 @@ const resetQuery = () => { } /** 添加/修改操作 */ -const modalRef = ref() -const openModal = (type: string, id?: number) => { - modalRef.value.openModal(type, id) +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) } /** 删除按钮操作 */ @@ -165,7 +171,7 @@ const handleDelete = async (id: number) => { // 删除的二次确认 await message.delConfirm() // 发起删除 - await ClientApi.deleteOAuth2ClientApi(id) + await ClientApi.deleteOAuth2Client(id) message.success(t('common.delSuccess')) // 刷新列表 await getList() From 87a5ddfd2ca19e09d70629e2d22ee54d50f22534 Mon Sep 17 00:00:00 2001 From: YunaiV <zhijiantianya@gmail.com> Date: Sun, 2 Apr 2023 11:48:54 +0800 Subject: [PATCH 184/184] =?UTF-8?q?REVIEW=20=E5=9B=BE=E6=96=87=E5=8F=91?= =?UTF-8?q?=E8=A1=A8=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/mp/freePublish/index.vue | 92 +++++++++--------------------- src/views/mp/tag/index.vue | 25 +++----- 2 files changed, 34 insertions(+), 83 deletions(-) diff --git a/src/views/mp/freePublish/index.vue b/src/views/mp/freePublish/index.vue index 05f3dec6..8a3d5285 100644 --- a/src/views/mp/freePublish/index.vue +++ b/src/views/mp/freePublish/index.vue @@ -19,14 +19,8 @@ </el-select> </el-form-item> <el-form-item> - <el-button @click="handleQuery"> - <Icon icon="ep:search" class="mr-5px" /> - 搜索 - </el-button> - <el-button @click="resetQuery"> - <Icon icon="ep:refresh" class="mr-5px" /> - 重置 - </el-button> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> </el-form-item> </el-form> </content-wrap> @@ -41,7 +35,6 @@ :key="item.articleId" > <wx-news :articles="item.content.newsItem" /> - <!-- 操作 --> <el-row justify="center" class="ope-row"> <el-button type="danger" @@ -54,9 +47,8 @@ </el-row> </div> </div> - <!-- 分页组件 --> - <pagination - v-show="total > 0" + <!-- 分页 --> + <Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @@ -66,24 +58,18 @@ </template> <script setup lang="ts" name="freePublish"> -import { getFreePublishPage, deleteFreePublish } from '@/api/mp/freePublish' +import * as FreePublishApi from '@/api/mp/freePublish' import * as MpAccountApi from '@/api/mp/account' import WxNews from '@/views/mp/components/wx-news/main.vue' - const message = useMessage() // 消息弹窗 +const { t } = useI18n() // 国际化 const loading = ref(true) // 列表的加载中 const total = ref(0) // 列表的总页数 const list = ref([]) // 列表的数据 -interface QueryParams { - currentPage: number | undefined | string - pageNo: number | undefined | string - accountId: number | undefined | string -} - -const queryParams: QueryParams = reactive({ - currentPage: 1, // 当前页数 - pageNo: 1, // 当前页数 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, accountId: undefined // 当前页数 }) const queryFormRef = ref() // 搜索的表单 @@ -96,25 +82,14 @@ const getList = async () => { message.error('未选中公众号,无法查询已发表图文') return false } - // TODO 改成 await 形式 - loading.value = true - getFreePublishPage(queryParams) - .then((data) => { - console.log(data) - // 将 thumbUrl 转成 picUrl,保证 wx-news 组件可以预览封面 - data.list.forEach((item) => { - console.log(item) - const newsItem = item.content.newsItem - newsItem.forEach((article) => { - article.picUrl = article.thumbUrl - }) - }) - list.value = data.list - total.value = data.total - }) - .finally(() => { - loading.value = false - }) + try { + loading.value = true + const data = await FreePublishApi.getFreePublishPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } } /** 搜索按钮操作 */ @@ -135,21 +110,15 @@ const resetQuery = () => { /** 删除按钮操作 */ const handleDelete = async (item) => { - { - // TODO 改成 await 形式 - const articleId = item.articleId - const accountId = queryParams.accountId - message - .confirm('删除后用户将无法访问此页面,确定删除?') - .then(function () { - return deleteFreePublish(accountId, articleId) - }) - .then(() => { - getList() - message.success('删除成功') - }) - .catch(() => {}) - } + try { + // 删除的二次确认 + await message.delConfirm('删除后用户将无法访问此页面,确定删除?') + // 发起删除 + await FreePublishApi.deleteFreePublish(queryParams.accountId, item.articleId) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} } onMounted(async () => { @@ -162,15 +131,6 @@ onMounted(async () => { }) </script> <style lang="scss" scoped> -.pagination { - float: right; - margin-right: 25px; -} - -.add_but { - padding: 10px; -} - .ope-row { margin-top: 5px; text-align: center; diff --git a/src/views/mp/tag/index.vue b/src/views/mp/tag/index.vue index bedfbad8..8a4b731d 100644 --- a/src/views/mp/tag/index.vue +++ b/src/views/mp/tag/index.vue @@ -12,9 +12,9 @@ <el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px"> <el-option v-for="item in accountList" - :key="parseInt(item.id)" + :key="item.id" :label="item.name" - :value="parseInt(item.id)" + :value="item.id" /> </el-select> </el-form-item> @@ -28,21 +28,15 @@ /> </el-form-item> <el-form-item> - <el-button @click="handleQuery"> - <Icon icon="ep:search" class="mr-5px" /> - 搜索 - </el-button> - <el-button @click="resetQuery"> - <Icon icon="ep:refresh" class="mr-5px" /> - 重置 - </el-button> + <el-form-item> + <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> + </el-form-item> <el-button type="primary" plain @click="openForm('create')" v-hasPermi="['mp:tag:create']"> - <Icon icon="ep:plus" class="mr-5px" /> - 新增 + <Icon icon="ep:plus" class="mr-5px" /> 新增 </el-button> <el-button type="success" plain @click="handleSync" v-hasPermi="['mp:tag:sync']"> - <Icon icon="ep:refresh" class="mr-5px" /> - 同步 + <Icon icon="ep:refresh" class="mr-5px" /> 同步 </el-button> </el-form-item> </el-form> @@ -99,7 +93,6 @@ import { dateFormatter } from '@/utils/formatTime' import * as MpTagApi from '@/api/mp/tag' import * as MpAccountApi from '@/api/mp/account' import TagForm from './TagForm.vue' - const message = useMessage() // 消息弹窗 const { t } = useI18n() // 国际化 @@ -143,7 +136,6 @@ const resetQuery = () => { queryFormRef.value.resetFields() // 默认选中第一个 if (accountList.value.length > 0) { - // @ts-ignore queryParams.accountId = accountList.value[0].id } handleQuery() @@ -184,7 +176,6 @@ onMounted(async () => { accountList.value = await MpAccountApi.getSimpleAccountList() // 选中第一个 if (accountList.value.length > 0) { - // @ts-ignore queryParams.accountId = accountList.value[0].id } await getList()