Sfoglia il codice sorgente

feat:完成业务场景管理

chauncey 1 anno fa
parent
commit
5ba8da6dd5

+ 55 - 0
src/api/sys-config/business-scene.ts

@@ -0,0 +1,55 @@
+import { http } from '@/utils/http/axios';
+import {
+  SceneListInfo,
+  AddSceneDataParam,
+  EditSceneDataParam,
+  SortSceneDataParam,
+} from '@/types/business-scene/type.ts';
+/**
+ * @description: 获取业务场景列表
+ */
+export function getSceneList() {
+  return http.request<SceneListInfo[]>({
+    url: '/scene/querySceneList',
+    method: 'get',
+  });
+}
+/**
+ * @description: 添加业务场景数据
+ */
+export function addSceneData(params: AddSceneDataParam) {
+  return http.request({
+    url: '/scene/saveScene',
+    method: 'post',
+    params,
+  });
+}
+/**
+ * @description: 删除业务场景数据
+ */
+export function deleteSceneData(sceneId: number) {
+  return http.request({
+    url: `/scene/deleteScene?sceneId=${sceneId}`,
+    method: 'post',
+  });
+}
+/**
+ * @description: 编辑业务场景数据
+ */
+export function editSceneData(params: EditSceneDataParam) {
+  return http.request({
+    url: '/scene/updateScene',
+    method: 'post',
+    params,
+  });
+}
+/**
+ * @description: 更新业务场景数据顺序
+ */
+export function sortSceneData(params: SortSceneDataParam) {
+  return http.request({
+    url: '/scene/updateSceneSort',
+    method: 'post',
+    params,
+  });
+}

+ 1 - 1
src/api/template/template.ts

@@ -85,7 +85,7 @@ export function getSceneModuleList(data: { pageNumber: number; pageSize: number
 }
 // 查询所有场景模板,全量接口
 export function getAllSceneModuleList() {
-  return http.request<Response<Records>>({
+  return http.request<Records[]>({
     url: '/viewTemplate/queryViewTemplateList',
     method: 'post',
   });

+ 27 - 15
src/components/Table/src/components/TableActionIcons.vue

@@ -1,16 +1,13 @@
 <template>
   <div class="flex items-center justify-center">
     <el-space :size="space">
-      <div v-for="item in props.actionIcons" :key="item.label" @click="item.onClick">
+      <div v-for="item in props.actionIcons" :key="item.label" @click="handleClick(item)"
+        :class="[item.disabled ? 'table__action--disabled' : 'table__action--enabled']">
         <el-tooltip :content="item.label" effect="light">
           <el-icon v-if="props.style === 'icon'" :color="props.color" :size="props.size">
             <component :is="item.icon" />
           </el-icon>
-          <img
-            v-if="props.style === 'img'"
-            :src="item.icon"
-            :style="{ width: `${props.size}px` }"
-          />
+          <img v-if="props.style === 'img'" :src="item.icon" :style="{ width: `${props.size}px` }" />
         </el-tooltip>
       </div>
     </el-space>
@@ -18,15 +15,30 @@
 </template>
 
 <script setup lang="ts">
-  import { ActionItem } from '@/components/Table';
+import { ActionItem } from '@/components/Table';
 
-  const props = defineProps<{
-    space: number;
-    size: number;
-    color: string;
-    style: 'img' | 'icon';
-    actionIcons: ActionItem[];
-  }>();
+const props = defineProps<{
+  space: number;
+  size: number;
+  color: string;
+  style: 'img' | 'icon';
+  actionIcons: ActionItem[];
+  class: string
+}>();
+const handleClick = (item) => {
+  // 如果按钮禁用,不触发任何事件
+  if (item.disabled) return;
+  item.onClick()
+}
 </script>
 
-<style scoped></style>
+<style scoped>
+.table__action--enabled {
+  cursor: pointer;
+}
+
+.table__action--disabled {
+  cursor: not-allowed;
+  opacity: 0.5;
+}
+</style>

+ 28 - 0
src/types/business-scene/type.ts

@@ -0,0 +1,28 @@
+export interface TemplateListInfo {
+  id: number;
+  name: string;
+}
+interface BasicSceneType {
+  name: string;
+  remark: string;
+  isDisabled: number; //0-启用 1-禁用
+}
+export interface SceneListInfo extends BasicSceneType {
+  createdAt?: string;
+  id?: number;
+  orderNum?: number;
+  viewTemplateList: TemplateListInfo[];
+}
+export interface AddSceneDataParam extends BasicSceneType {
+  viewTemplateIdList: number[];
+}
+export interface EditSceneDataParam extends AddSceneDataParam {
+  id: number;
+}
+export interface SortSceneItemParam {
+  sceneId: number | undefined;
+  orderNum: number | undefined;
+}
+export interface SortSceneDataParam {
+  idAndOrderDTOList: SortSceneItemParam[];
+}

+ 180 - 0
src/views/system-config/business-scene/PageBusinessScene.vue

@@ -0,0 +1,180 @@
+<template>
+    <div class="business-scene">
+        <header class="header">
+            <el-button class="header__button" type="primary" :icon="Plus"
+                @click="openDrawer('添加业务场景')">添加业务场景</el-button>
+        </header>
+        <main class="main">
+            <BasicTable :columns="columns" :action-column="actionColumn" :data-source="tableData" :tableSetting="{
+                size: false,
+                redo: false,
+                fullscreen: false,
+                striped: false,
+                setting: false,
+            }" />
+        </main>
+    </div>
+    <InfoDrawer :title="drawerTitle" :data="tableInfo" @close-drawer="drawerVisiable = false"
+        @fetch-table="getTableData()" v-if="drawerVisiable" />
+</template>
+
+<script lang="ts" setup>
+import { onMounted, ref, h, reactive } from 'vue';
+import { ElTag } from 'element-plus';
+import { Plus } from '@element-plus/icons-vue'
+import { BasicTable } from '@/components/Table';
+import { useTableHook } from './useTableHook.ts';
+import { SceneListInfo } from '@/types/business-scene/type.ts'
+import { BasicColumn, TableActionIcons } from '@/components/Table';
+import InfoDrawer from './components/InfoDrawer.vue';
+import { openMessageBox, getMessage } from './components/MessageBox.ts'
+import Up from '@/assets/icons/up.png';
+import Down from '@/assets/icons/down.png';
+import Edit from '@/assets/icons/edit.png';
+import Delete from '@/assets/icons/delete.png';
+const { orderNums, tableData, getTableData, deleteTableData, sortTableData } = useTableHook()
+const drawerVisiable = ref(false);
+const drawerTitle = ref('添加业务场景')
+const openDrawer = (title: string) => {
+    drawerVisiable.value = true;
+    drawerTitle.value = title;
+    tableInfo.value = { name: '', viewTemplateList: [], remark: '', isDisabled: 0 }
+}
+const columns: BasicColumn[] = [
+    {
+        label: '场景名称',
+        prop: 'name',
+        width: 150
+    },
+    {
+        label: '关联模板',
+        prop: 'viewTemplateList',
+        minWidth: 300,
+        render(record) {
+            const template = record.row.viewTemplateList.map(item => item.name);
+            const tags = template.map((item) => h(ElTag, { type: 'info' }, item));
+            return h(
+                'div',
+                {
+                    class: 'div__container--tag',
+                },
+                tags,
+            );
+        },
+    },
+    {
+        label: '状态',
+        prop: 'isDisabled',
+        width: 140,
+        align: 'center',
+        render(record) {
+            const status = record.row.isDisabled;
+            return h(
+                ElTag,
+                { type: status === 0 ? 'success' : 'danger' },
+                status === 0 ? '启用' : '停用',
+            );
+        },
+    },
+    {
+        label: '备注',
+        prop: 'remark',
+        width: 160
+    },
+    {
+        label: '创建时间',
+        prop: 'createdAt',
+        width: 180
+    },
+];
+const tableInfo = ref<SceneListInfo>();
+const actionColumn: BasicColumn = reactive({
+    width: 240,
+    title: '操作',
+    prop: 'action',
+    fixed: 'right',
+    render(record) {
+        return h(TableActionIcons as any, {
+            space: 20,
+            style: 'img',
+            size: 16,
+            actionIcons: [
+                {
+                    label: '编辑',
+                    icon: Edit,
+                    onClick: () => {
+                        openDrawer('修改业务场景');
+                        tableInfo.value = record.row;
+                    },
+                },
+                {
+                    label: '删除',
+                    icon: Delete,
+                    onClick: () => {
+                        openMessageBox(undefined, undefined, () => deleteTableData(record.row.id))
+                    },
+                },
+                {
+                    label: '升序',
+                    icon: Up,
+                    disabled: record.row.orderNum === orderNums.value[0],
+                    onClick: () => {
+                        sortTableData(record.row.id, 'increase')
+                        getMessage('success', '升序成功');
+                    },
+                },
+                {
+                    label: '降序',
+                    icon: Down,
+                    disabled: record.row.orderNum === orderNums.value[1],
+                    onClick: () => {
+                        sortTableData(record.row.id, 'decrease')
+                        getMessage('success', '降序成功');
+                    },
+                },
+            ],
+            class: 'table__action--enabled',
+        });
+    },
+});
+onMounted(() => {
+    getTableData();
+})
+</script>
+
+<style lang="scss" scoped>
+.business-scene {
+    display: flex;
+    flex-direction: column;
+    background: #ffffff;
+    box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.12);
+    border-radius: 6px;
+    width: 100%;
+    height: calc(100vh - 64px - 12px);
+    padding: 24px 44px 0 21px;
+}
+
+.header {
+    display: flex;
+    justify-content: space-between;
+    width: inherit;
+    height: 32px;
+}
+
+.main {
+    position: relative;
+    margin-top: 16px;
+    width: inherit;
+    flex: 1;
+}
+
+:deep(.div__container--tag) {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 4px;
+}
+
+:deep(.div__container--tag)::-webkit-scrollbar {
+    height: 6px;
+}
+</style>

+ 152 - 0
src/views/system-config/business-scene/components/InfoDrawer.vue

@@ -0,0 +1,152 @@
+<template>
+    <main>
+        <el-drawer v-model="visiable" :title="props.title" @close="emits('closeDrawer')">
+            <section class="drawer__form">
+                <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" label-width="auto">
+                    <el-form-item label="场景名称" prop="name">
+                        <el-input v-model="ruleForm.name" maxlength="100" placeholder="请输入场景名称" type="text" />
+                    </el-form-item>
+                    <el-form-item label="应用模板" prop="viewTemplateList">
+                        <el-select v-model="ruleForm.viewTemplateList" multiple placeholder="请选择应用模板"
+                            class="select__custom--multiple" value-key="id">
+                            <el-option v-for="item in options" :key="item.id" :label="item.name" :value="item" />
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item label="备注" prop="remark">
+                        <el-input v-model="ruleForm.remark" maxlength="100" placeholder="请输入备注" type="textarea"
+                            :autosize="{ minRows: 3, maxRows: 6 }" />
+                    </el-form-item>
+                    <el-form-item label="状态" prop="isDisabled">
+                        <el-switch v-model="ruleForm.isDisabled" :active-value=0 :inactive-value=1 />
+                    </el-form-item>
+                </el-form>
+            </section>
+            <footer class="drawer__btn">
+                <el-button @click="resetForm()">重置</el-button>
+                <el-button type="primary" @click="submitForm()">提交</el-button>
+            </footer>
+        </el-drawer>
+    </main>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, onMounted } from 'vue';
+import { SceneListInfo, TemplateListInfo } from '@/types/business-scene/type.ts'
+import type { FormInstance, FormRules } from 'element-plus'
+import { getAllSceneModuleList } from '@/api/template/template'
+import { addSceneData, editSceneData } from '@/api/sys-config/business-scene'
+import { AddSceneDataParam, EditSceneDataParam } from '@/types/business-scene/type'
+import { debounce } from 'lodash-es';
+import { getMessage } from './MessageBox'
+const visiable = ref(true)
+const props = defineProps<{
+    title: string;
+    data: SceneListInfo | undefined
+}>()
+const emits = defineEmits(['closeDrawer', 'fetchTable'])
+const ruleForm = reactive({
+    name: '',
+    viewTemplateList: [] as TemplateListInfo[],
+    remark: '',
+    isDisabled: 0
+})
+const options = ref<TemplateListInfo[]>([])
+const ruleFormRef = ref<FormInstance>();
+const rules = reactive<FormRules<typeof ruleForm>>({
+    name: [{ required: true, message: '场景名称不能为空', trigger: 'change' }],
+    viewTemplateList: [{ required: true, message: '应用模板不能为空', trigger: 'change' }],
+})
+const debounceSubmit = debounce((params) => {
+    addSceneData(params)
+        .then(() => {
+            emits('fetchTable')
+            emits('closeDrawer')
+            getMessage("success", '添加业务场景数据成功!')
+        })
+}, 500);
+const debounceEdit = debounce((params) => {
+    editSceneData(params)
+        .then(() => {
+            emits('fetchTable')
+            emits('closeDrawer')
+            getMessage("success", '修改业务场景数据成功!')
+        })
+}, 500);
+const submitForm = () => {
+    if (!ruleFormRef.value) return;
+    ruleFormRef.value.validate((valid) => {
+        if (!valid) return
+        const addParams: AddSceneDataParam = {
+            name: ruleForm.name,
+            viewTemplateIdList: ruleForm.viewTemplateList.map(item => item.id),
+            remark: ruleForm.remark,
+            isDisabled: ruleForm.isDisabled
+        }
+        if (props.title !== '添加业务场景') {
+            const editParams: EditSceneDataParam = {
+                id: props.data?.id!,
+                ...addParams
+            }
+            debounceEdit(editParams);
+            return;
+        }
+        debounceSubmit(addParams);
+    });
+}
+const resetForm = () => {
+    if (!ruleFormRef.value) return;
+    if (props.title !== '添加业务场景') {
+        initRuleForm(props.data)
+        return;
+    }
+    ruleFormRef.value.resetFields();
+}
+const initRuleForm = (data) => {
+    ruleForm.name = data.name;
+    ruleForm.viewTemplateList = data.viewTemplateList;
+    ruleForm.remark = data.remark;
+    ruleForm.isDisabled = data.isDisabled;
+}
+onMounted(async () => {
+    const res = await getAllSceneModuleList();
+    options.value = res.map(item => ({
+        id: item.id,
+        name: item.name
+    }));
+    initRuleForm(props.data)
+})
+</script>
+
+<style lang="scss" scoped>
+:deep(.el-drawer__body) {
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+    height: 100%;
+}
+
+.el-form {
+    display: flex;
+    flex-direction: column;
+    gap: 20px;
+}
+
+.drawer__form {
+    height: calc(100% - 32px);
+    overflow-y: auto;
+}
+
+.drawer__btn {
+    display: flex;
+    justify-content: center;
+    height: 32px;
+}
+
+.select__custom--multiple {
+    :deep(.el-select__selection) {
+        min-height: 25px;
+        max-height: 120px;
+        overflow-y: auto;
+    }
+}
+</style>

+ 35 - 0
src/views/system-config/business-scene/components/MessageBox.ts

@@ -0,0 +1,35 @@
+import './style.scss';
+import { ElMessage, ElMessageBox } from 'element-plus';
+export const getMessage = (type: 'success' | 'warning' | 'info' | 'error', message: string) => {
+  return ElMessage({
+    type,
+    message,
+  });
+};
+export const openMessageBox = (
+  title?: string | undefined,
+  message?: string | undefined,
+  fn?: () => void,
+) => {
+  ElMessageBox.confirm(
+    message ? message : '请确认是否删除该数据?',
+    title ? title : '业务场景删除?',
+    {
+      confirmButtonText: '确认',
+      cancelButtonText: '取消',
+      customClass: 'elMessageBox__custom--warning',
+    },
+  )
+    .then(async () => {
+      if (!fn) {
+        getMessage('success', '删除成功');
+        return;
+      } else {
+        try {
+          await fn();
+          getMessage('success', '删除成功');
+        } catch {}
+      }
+    })
+    .catch(() => {});
+};

+ 21 - 0
src/views/system-config/business-scene/components/style.scss

@@ -0,0 +1,21 @@
+.elMessageBox__custom--warning {
+  border-radius: 8px;
+  .el-message-box__title {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+  }
+  .el-message-box__title::before {
+    content: '';
+    display: block;
+    width: 24px;
+    height: 24px;
+    background-image: url('@/assets/images/deleteTip.png');
+    background-size: cover;
+    background-position: center;
+    background-repeat: no-repeat;
+  }
+  .el-message-box__container {
+    margin-left: 5px;
+  }
+}

+ 58 - 0
src/views/system-config/business-scene/useTableHook.ts

@@ -0,0 +1,58 @@
+import { computed, ref } from 'vue';
+import { SceneListInfo, SortSceneItemParam, SortSceneDataParam } from '@/types/business-scene/type';
+import { getSceneList, deleteSceneData, sortSceneData } from '@/api/sys-config/business-scene';
+export const useTableHook = () => {
+  const tableData = ref<SceneListInfo[]>([]);
+  const getTableData = async () => {
+    tableData.value = await getSceneList();
+  };
+  const deleteTableData = async (id: number) => {
+    await deleteSceneData(id);
+    await getTableData();
+  };
+  const orderNums = computed(() => {
+    const length = tableData.value.length;
+    const firstOrderNum = length > 0 ? tableData.value[0].orderNum : null;
+    const lastOrderNum = length > 0 ? tableData.value[length - 1].orderNum : null;
+    return [firstOrderNum, lastOrderNum];
+  });
+  const getSwapParam = (id: number, sortType: 'increase' | 'decrease') => {
+    const index = tableData.value.findIndex((item) => item.id === id);
+    if (index === -1) return;
+    let idAndOrderDTOList: SortSceneItemParam[] = [];
+    const currentOrder = tableData.value[index].orderNum; //当前的顺序
+    if (sortType === 'increase') {
+      /**
+       * @description prevId -- 上一个的ID prevOrder -- 上一个的oderNum
+       */
+      const prevData = tableData.value[index - 1];
+      const prevId = prevData.id;
+      const prevOrder = prevData.orderNum;
+      idAndOrderDTOList = [
+        { sceneId: prevId, orderNum: currentOrder },
+        { sceneId: id, orderNum: prevOrder },
+      ];
+    } else {
+      /**
+       * @description nextId -- 下一个的ID nextOrder -- 下一个的oderNum
+       */
+      const nextData = tableData.value[index + 1];
+      const nextId = nextData.id;
+      const nextOrder = nextData.orderNum;
+      idAndOrderDTOList = [
+        { sceneId: nextId, orderNum: currentOrder },
+        { sceneId: id, orderNum: nextOrder },
+      ];
+    }
+    const sortSceneDataParam: SortSceneDataParam = {
+      idAndOrderDTOList,
+    };
+    return sortSceneDataParam;
+  };
+  const sortTableData = async (id: number, sortType: 'increase' | 'decrease') => {
+    const idAndOrderDTOList = getSwapParam(id, sortType);
+    await sortSceneData(idAndOrderDTOList!);
+    await getTableData();
+  };
+  return { orderNums, tableData, getTableData, deleteTableData, sortTableData };
+};