From b583be5465970411153abad0ae85661059818f14 Mon Sep 17 00:00:00 2001
From: gexinzhineng/gxzn27 <1348660141@qq.com>
Date: Thu, 16 Mar 2023 13:51:54 +0800
Subject: [PATCH 01/86] =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E7=AE=A1=E7=90=86?=
 =?UTF-8?q?=E4=B8=AD=E7=9A=84=E5=9C=B0=E5=8C=BA=E7=AE=A1=E7=90=86=E9=A1=B5?=
 =?UTF-8?q?=E9=9D=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/api/system/area/index.ts       |  15 ++++
 src/views/system/area/area.data.ts |  23 ++++++
 src/views/system/area/index.vue    | 126 +++++++++++++++++++++++++++++
 3 files changed, 164 insertions(+)
 create mode 100644 src/api/system/area/index.ts
 create mode 100644 src/views/system/area/area.data.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..7d8a9d7c
--- /dev/null
+++ b/src/api/system/area/index.ts
@@ -0,0 +1,15 @@
+import request from '@/config/axios/request'
+
+// 获得地区树
+export const getAreaTree = async (content?: any) => {
+  return await request.get({
+    url: '/system/area/tree',
+    params: content
+  })
+}
+// 获得 IP 对应的地区名
+export const getAreaByIp = async (ip) => {
+  return await request.get({
+    url: '/system/area/get-by-ip?ip=' + ip
+  })
+}
diff --git a/src/views/system/area/area.data.ts b/src/views/system/area/area.data.ts
new file mode 100644
index 00000000..008e8a41
--- /dev/null
+++ b/src/views/system/area/area.data.ts
@@ -0,0 +1,23 @@
+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)
diff --git a/src/views/system/area/index.vue b/src/views/system/area/index.vue
new file mode 100644
index 00000000..e5a6a682
--- /dev/null
+++ b/src/views/system/area/index.vue
@@ -0,0 +1,126 @@
+<template>
+  <div class="app-container">
+    <doc-alert title="地区 & IP" url="https://doc.iocoder.cn/area-and-ip/" />
+    <!-- 操作工具栏 -->
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <XButton preIcon="fa:search" type="primary" @click="handleAdd" title="IP 查询" />
+      </el-col>
+    </el-row>
+    <!-- 列表 -->
+    <el-table
+      v-if="refreshTable"
+      v-loading="loading"
+      :data="list"
+      row-key="id"
+      :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+    >
+      <el-table-column label="编号" prop="id" />
+      <el-table-column label="名字" prop="name" />
+    </el-table>
+    <!-- <XTable ref="xGrid" @register="registerTable" show-overflow /> -->
+
+    <!-- 对话框(添加 / 修改) -->
+    <el-dialog title="IP 查询" v-model="open" width="500px" append-to-body>
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="IP" prop="ip">
+          <el-input v-model="form.ip" placeholder="请输入 IP 地址" />
+        </el-form-item>
+        <el-form-item label="地址" prop="result">
+          <el-input v-model="form.result" readonly placeholder="展示查询 IP 结果" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button type="primary" @click="submitForm(formRef)">查 询</el-button>
+        <el-button @click="cancel(formRef)">取 消</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup name="Area">
+import * as areaApi from '@/api/system/area'
+import type { FormInstance } from 'element-plus'
+// import { allSchemas } from './area.data'
+// import { getAreaByIp, getAreaTree } from '@/api/system/area'
+// 遮罩层
+const loading = ref(true)
+// 地区列表
+const list = ref([])
+// 弹出层标题
+// const title = ref('')
+// 是否显示弹出层
+const open = ref(false)
+// 重新渲染表格状态
+const refreshTable = ref(true)
+// 表单参数
+const form = ref({
+  ip: undefined,
+  result: undefined
+})
+const formRef = ref<FormInstance>()
+// 表单校验
+const rules = ref({
+  ip: [{ required: true, message: 'IP 地址不能为控', trigger: 'blur' }]
+})
+const message = useMessage() // 消息弹窗
+
+// const treeConfig = {
+//   transform: true,
+//   rowField: 'id',
+//   parentField: 'id'
+//   // expandAll: true
+// }
+
+// const [registerTable, { reload }] = useXTable({
+//   allSchemas: allSchemas,
+//   treeConfig: treeConfig,
+//   getListApi: areaApi.getAreaTree
+// })
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  const response = await areaApi.getAreaTree()
+  list.value = response.data
+  loading.value = false
+}
+/** 取消按钮 */
+const cancel = (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  formEl.resetFields()
+  open.value = false
+  reset()
+}
+/** 表单重置 */
+const reset = async () => {
+  form.value = {
+    ip: undefined,
+    result: undefined
+  }
+  // await reload()
+}
+/** 新增按钮操作 */
+const handleAdd = () => {
+  open.value = true
+  reset()
+}
+/** 提交按钮 */
+const submitForm = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  await formEl.validate(async (valid, fields) => {
+    if (valid) {
+      console.log('submit!')
+      const response = await areaApi.getAreaByIp(form.value.ip)
+      message.success('查询成功')
+      form.value.result = response.data
+    } else {
+      console.log('error submit!', fields)
+    }
+  })
+}
+
+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 02/86] =?UTF-8?q?=E9=87=87=E7=94=A8ep=E9=87=8D=E5=86=99dep?=
 =?UTF-8?q?t=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 1846ba150ebbb98fa8e6db3b0439a5dcb7a93f20 Mon Sep 17 00:00:00 2001
From: admin <>
Date: Sun, 19 Mar 2023 21:44:13 +0800
Subject: [PATCH 03/86] =?UTF-8?q?=E9=87=8D=E6=96=B0dept=E6=A8=A1=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 04/86] =?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 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 05/86] =?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 06/86] =?UTF-8?q?fix(=E6=95=B0=E6=8D=AE=E6=BA=90=E9=85=8D?=
 =?UTF-8?q?=E7=BD=AE):=20=E9=A1=B5=E9=9D=A2api=E6=9C=AA=E5=90=8C=E6=AD=A5?=
 =?UTF-8?q?=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 07/86] =?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 08/86] =?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 09/86] =?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 10/86] =?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 11/86] =?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 12/86] =?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 13/86] =?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 bf2b4ced8c9303eabd22c87d505820b5b60e566c Mon Sep 17 00:00:00 2001
From: puhui999 <puhui999@163.com>
Date: Wed, 22 Mar 2023 16:02:04 +0800
Subject: [PATCH 14/86] =?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 15/86] =?UTF-8?q?[=E7=B3=BB=E7=BB=9F=E7=AE=A1=E7=90=86=20-?=
 =?UTF-8?q?>=20=E5=B2=97=E4=BD=8D=E7=AE=A1=E7=90=86]=20=E4=BD=BF=E7=94=A8?=
 =?UTF-8?q?=20Element=20Plus=20=E5=8E=9F=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/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 16/86] =?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 17/86] =?UTF-8?q?update:=20=E4=BF=AE=E5=A4=8Dvalue-format?=
 =?UTF-8?q?=E5=B1=9E=E6=80=A7=E5=A4=B1=E6=95=88=E7=9A=84=E9=97=AE=E9=A2=98?=
 =?UTF-8?q?=EF=BC=8C=E5=BA=94=E5=85=A8=E9=83=A8=E9=83=BD=E7=94=A8=E5=A4=A7?=
 =?UTF-8?q?=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 18/86] 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 19/86] =?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 20/86] =?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 21/86] =?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 22/86] =?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 23/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 24/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 42128709aa88dfb0578cb8a4e405201b04ae75f2 Mon Sep 17 00:00:00 2001
From: puhui999 <puhui999@163.com>
Date: Wed, 22 Mar 2023 23:53:39 +0800
Subject: [PATCH 25/86] =?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 26/86] =?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 27/86] =?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 28/86] =?UTF-8?q?update:=20!!!=E8=AF=B7=E4=BF=9D=E7=95=99?=
 =?UTF-8?q?=E8=BF=99=E4=B8=AA=E5=B1=9E=E6=80=A7=EF=BC=8C=E9=98=B2=E6=AD=A2?=
 =?UTF-8?q?tsconfig.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 29/86] 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 30/86] 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 31/86] =?UTF-8?q?fix:Descriptions=E5=8E=BB=E6=8E=89mb-10px?=
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 32/86] =?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 33/86] =?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 34/86] 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 35/86] 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 36/86] 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 37/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 38/86] =?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 39/86] =?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 40/86] =?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 41/86] =?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 42/86] =?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 43/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 44/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 45/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 46/86] =?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 47/86] =?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 23b23f920011e0b890972f3fcb5c506a228ad730 Mon Sep 17 00:00:00 2001
From: gexinzhineng/gxzn27 <1348660141@qq.com>
Date: Fri, 24 Mar 2023 17:05:24 +0800
Subject: [PATCH 48/86] =?UTF-8?q?xTable=E6=9F=A5=E8=AF=A2=E6=A1=86?=
 =?UTF-8?q?=E5=89=8D=E9=97=B4=E9=9A=94=EF=BC=8C=E9=80=80=E5=87=BA=E7=B3=BB?=
 =?UTF-8?q?=E7=BB=9F=E6=8C=89=E9=92=AE=E7=82=B9=E5=87=BB=E6=9C=89=E6=97=B6?=
 =?UTF-8?q?=E4=BC=9A=E6=97=A0=E6=B3=95=E6=88=90=E5=8A=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/components/XTable/src/XTable.vue            | 2 +-
 src/layout/components/UserInfo/src/UserInfo.vue | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/components/XTable/src/XTable.vue b/src/components/XTable/src/XTable.vue
index 55c7b129..fac292d4 100644
--- a/src/components/XTable/src/XTable.vue
+++ b/src/components/XTable/src/XTable.vue
@@ -128,7 +128,7 @@ const getColumnsConfig = (options: XTableProps) => {
     proxyForm = true
     options.formConfig = {
       enabled: true,
-      titleWidth: 180,
+      titleWidth: 110,
       titleAlign: 'right',
       items: allSchemas.searchSchema
     }
diff --git a/src/layout/components/UserInfo/src/UserInfo.vue b/src/layout/components/UserInfo/src/UserInfo.vue
index 73e048ab..7ccadb57 100644
--- a/src/layout/components/UserInfo/src/UserInfo.vue
+++ b/src/layout/components/UserInfo/src/UserInfo.vue
@@ -66,9 +66,9 @@ const toDocument = () => {
           <Icon icon="ep:menu" />
           <div @click="toDocument">{{ t('common.document') }}</div>
         </ElDropdownItem>
-        <ElDropdownItem divided>
+        <ElDropdownItem divided @click="loginOut">
           <Icon icon="ep:switch-button" />
-          <div @click="loginOut">{{ t('common.loginOut') }}</div>
+          <div>{{ t('common.loginOut') }}</div>
         </ElDropdownItem>
       </ElDropdownMenu>
     </template>

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 49/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 50/86] =?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 51/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 52/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 53/86] =?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 54/86] =?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 55/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 56/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 7463eea243bdfaf379dc48816ef6268717de3fa4 Mon Sep 17 00:00:00 2001
From: YunaiV <zhijiantianya@gmail.com>
Date: Sat, 25 Mar 2023 16:22:43 +0800
Subject: [PATCH 57/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 58/86] =?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 59/86] =?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 60/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9Aform=20?=
 =?UTF-8?q?=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 61/86] =?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 62/86] =?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 63/86] =?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 64/86] =?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 65/86] =?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 66/86] =?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 67/86] =?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 9c869dee5f2e582bab8ed23287865fd183e6e69b Mon Sep 17 00:00:00 2001
From: YunaiV <zhijiantianya@gmail.com>
Date: Sun, 26 Mar 2023 21:16:16 +0800
Subject: [PATCH 68/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 69/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 70/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 71/86] =?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 72/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 73/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 74/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 6ce9ab875c659ef669cb5fbd4dc006b3d6d47b0f Mon Sep 17 00:00:00 2001
From: fessor <352475718@qq.com>
Date: Mon, 27 Mar 2023 01:44:23 +0000
Subject: [PATCH 75/86] =?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 76/86] =?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 77/86] =?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 78/86] =?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 79/86] =?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 80/86] =?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 81/86] =?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 82/86] =?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 83/86] =?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 84/86] =?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 85/86] =?UTF-8?q?Vue3=20=E9=87=8D=E6=9E=84=EF=BC=9AREVIEW?=
 =?UTF-8?q?=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 86/86] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20sr?=
 =?UTF-8?q?c/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)