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>
+          &nbsp; &nbsp;
         </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 />&nbsp;&nbsp;&nbsp;&nbsp;' + username
+      }
+      text += '<br />更新成功数量:' + data.updateUsernames.length
+      for (const username of data.updateUsernames) {
+        text += '<br />&nbsp;&nbsp;&nbsp;&nbsp;' + username
+      }
+      text += '<br />更新失败数量:' + Object.keys(data.failureUsernames).length
+      for (const username in data.failureUsernames) {
+        text += '<br />&nbsp;&nbsp;&nbsp;&nbsp;' + 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) }} &nbsp;
-        </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) }} &nbsp;
-        </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 />&nbsp;&nbsp;&nbsp;&nbsp;' + username
-      }
-      text += '<br />更新成功数量:' + data.updateUsernames.length
-      for (const username of data.updateUsernames) {
-        text += '<br />&nbsp;&nbsp;&nbsp;&nbsp;' + username
-      }
-      text += '<br />更新失败数量:' + Object.keys(data.failureUsernames).length
-      for (const username in data.failureUsernames) {
-        text += '<br />&nbsp;&nbsp;&nbsp;&nbsp;' + 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 @@
+<!--&lt;!&ndash;-->
+<!--  - Copyright (C) 2018-2019-->
+<!--  - All rights reserved, Designed By www.joolun.com-->
+<!--  芋道源码:-->
+<!--  ① 移除多余的 rep 为前缀的变量,让 message 消息更简单-->
+<!--  ② 代码优化,补充注释,提升阅读性-->
+<!--  ③ 优化消息的临时缓存策略,发送消息时,只清理被发送消息的 tab,不会强制切回到 text 输入-->
+<!--  ④ 支持发送【视频】消息时,支持新建视频-->
+<!--&ndash;&gt;-->
+<!--<template>-->
+<!--  <el-tabs type="border-card" v-model="objData.type" @tab-click="handleClick">-->
+<!--    &lt;!&ndash; 类型 1:文本 &ndash;&gt;-->
+<!--    <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>-->
+<!--    &lt;!&ndash; 类型 2:图片 &ndash;&gt;-->
+<!--    <el-tab-pane name="image">-->
+<!--      <span slot="label"><i class="el-icon-picture"></i> 图片</span>-->
+<!--      <el-row>-->
+<!--        &lt;!&ndash; 情况一:已经选择好素材、或者上传好图片 &ndash;&gt;-->
+<!--        <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>-->
+<!--        &lt;!&ndash; 情况二:未做完上述操作 &ndash;&gt;-->
+<!--        <div v-else>-->
+<!--          <el-row style="text-align: center">-->
+<!--            &lt;!&ndash; 选择素材 &ndash;&gt;-->
+<!--            <el-col :span="12" class="col-select">-->
+<!--              <el-button type="success" @click="openMaterial">-->
+<!--                素材库选择<i class="el-icon-circle-check el-icon&#45;&#45;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>-->
+<!--            &lt;!&ndash; 文件上传 &ndash;&gt;-->
+<!--            <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>-->
+<!--    &lt;!&ndash; 类型 3:语音 &ndash;&gt;-->
+<!--    <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">-->
+<!--            &lt;!&ndash; 选择素材 &ndash;&gt;-->
+<!--            <el-col :span="12" class="col-select">-->
+<!--              <el-button type="success" @click="openMaterial">-->
+<!--                素材库选择<i class="el-icon-circle-check el-icon&#45;&#45;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>-->
+<!--            &lt;!&ndash; 文件上传 &ndash;&gt;-->
+<!--            <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>-->
+<!--    &lt;!&ndash; 类型 4:视频 &ndash;&gt;-->
+<!--    <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">-->
+<!--          &lt;!&ndash; 选择素材 &ndash;&gt;-->
+<!--          <el-col :span="12">-->
+<!--            <el-button type="success" @click="openMaterial">-->
+<!--              素材库选择<i class="el-icon-circle-check el-icon&#45;&#45;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>-->
+<!--          &lt;!&ndash; 文件上传 &ndash;&gt;-->
+<!--          <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&#45;&#45;right"></i-->
+<!--              ></el-button>-->
+<!--            </el-upload>-->
+<!--          </el-col>-->
+<!--        </el-row>-->
+<!--      </el-row>-->
+<!--    </el-tab-pane>-->
+<!--    &lt;!&ndash; 类型 5:图文 &ndash;&gt;-->
+<!--    <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>-->
+<!--        &lt;!&ndash; 选择素材 &ndash;&gt;-->
+<!--        <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&#45;&#45;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>-->
+<!--    &lt;!&ndash; 类型 6:音乐 &ndash;&gt;-->
+<!--    <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>
-          &nbsp;
-        </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>
-          &nbsp; &nbsp;
         </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 @@
-<!--&lt;!&ndash;-->
-<!--  - Copyright (C) 2018-2019-->
-<!--  - All rights reserved, Designed By www.joolun.com-->
-<!--  芋道源码:-->
-<!--  ① 移除多余的 rep 为前缀的变量,让 message 消息更简单-->
-<!--  ② 代码优化,补充注释,提升阅读性-->
-<!--  ③ 优化消息的临时缓存策略,发送消息时,只清理被发送消息的 tab,不会强制切回到 text 输入-->
-<!--  ④ 支持发送【视频】消息时,支持新建视频-->
-<!--&ndash;&gt;-->
-<!--<template>-->
-<!--  <el-tabs type="border-card" v-model="objData.type" @tab-click="handleClick">-->
-<!--    &lt;!&ndash; 类型 1:文本 &ndash;&gt;-->
-<!--    <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>-->
-<!--    &lt;!&ndash; 类型 2:图片 &ndash;&gt;-->
-<!--    <el-tab-pane name="image">-->
-<!--      <span slot="label"><i class="el-icon-picture"></i> 图片</span>-->
-<!--      <el-row>-->
-<!--        &lt;!&ndash; 情况一:已经选择好素材、或者上传好图片 &ndash;&gt;-->
-<!--        <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>-->
-<!--        &lt;!&ndash; 情况二:未做完上述操作 &ndash;&gt;-->
-<!--        <div v-else>-->
-<!--          <el-row style="text-align: center">-->
-<!--            &lt;!&ndash; 选择素材 &ndash;&gt;-->
-<!--            <el-col :span="12" class="col-select">-->
-<!--              <el-button type="success" @click="openMaterial">-->
-<!--                素材库选择<i class="el-icon-circle-check el-icon&#45;&#45;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>-->
-<!--            &lt;!&ndash; 文件上传 &ndash;&gt;-->
-<!--            <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>-->
-<!--    &lt;!&ndash; 类型 3:语音 &ndash;&gt;-->
-<!--    <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">-->
-<!--            &lt;!&ndash; 选择素材 &ndash;&gt;-->
-<!--            <el-col :span="12" class="col-select">-->
-<!--              <el-button type="success" @click="openMaterial">-->
-<!--                素材库选择<i class="el-icon-circle-check el-icon&#45;&#45;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>-->
-<!--            &lt;!&ndash; 文件上传 &ndash;&gt;-->
-<!--            <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>-->
-<!--    &lt;!&ndash; 类型 4:视频 &ndash;&gt;-->
-<!--    <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">-->
-<!--          &lt;!&ndash; 选择素材 &ndash;&gt;-->
-<!--          <el-col :span="12">-->
-<!--            <el-button type="success" @click="openMaterial">-->
-<!--              素材库选择<i class="el-icon-circle-check el-icon&#45;&#45;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>-->
-<!--          &lt;!&ndash; 文件上传 &ndash;&gt;-->
-<!--          <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&#45;&#45;right"></i-->
-<!--              ></el-button>-->
-<!--            </el-upload>-->
-<!--          </el-col>-->
-<!--        </el-row>-->
-<!--      </el-row>-->
-<!--    </el-tab-pane>-->
-<!--    &lt;!&ndash; 类型 5:图文 &ndash;&gt;-->
-<!--    <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>-->
-<!--        &lt;!&ndash; 选择素材 &ndash;&gt;-->
-<!--        <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&#45;&#45;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>-->
-<!--    &lt;!&ndash; 类型 6:音乐 &ndash;&gt;-->
-<!--    <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()