parent
0dcc807b34
commit
28c328d191
@ -1,3 +0,0 @@ |
||||
import LocaleDropdown from './src/LocaleDropdown.vue' |
||||
|
||||
export { LocaleDropdown } |
||||
@ -1,50 +0,0 @@ |
||||
<script lang="ts" name="LocaleDropdown" setup> |
||||
import { useLocaleStore } from '@/store/modules/locale' |
||||
import { useLocale } from '@/hooks/web/useLocale' |
||||
import { propTypes } from '@/utils/propTypes' |
||||
import { useDesign } from '@/hooks/web/useDesign' |
||||
|
||||
const { getPrefixCls } = useDesign() |
||||
|
||||
const prefixCls = getPrefixCls('locale-dropdown') |
||||
|
||||
defineProps({ |
||||
color: propTypes.string.def('') |
||||
}) |
||||
|
||||
const localeStore = useLocaleStore() |
||||
|
||||
const langMap = computed(() => localeStore.getLocaleMap) |
||||
|
||||
const currentLang = computed(() => localeStore.getCurrentLocale) |
||||
|
||||
const setLang = (lang: LocaleType) => { |
||||
if (lang === unref(currentLang).lang) return |
||||
// 需要重新加载页面让整个语言多初始化 |
||||
window.location.reload() |
||||
localeStore.setCurrentLocale({ |
||||
lang |
||||
}) |
||||
const { changeLocale } = useLocale() |
||||
changeLocale(lang) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<ElDropdown :class="prefixCls" trigger="click" @command="setLang"> |
||||
<Icon |
||||
:class="$attrs.class" |
||||
:color="color" |
||||
:size="18" |
||||
class="cursor-pointer" |
||||
icon="ion:language-sharp" |
||||
/> |
||||
<template #dropdown> |
||||
<ElDropdownMenu> |
||||
<ElDropdownItem v-for="item in langMap" :key="item.lang" :command="item.lang"> |
||||
{{ item.name }} |
||||
</ElDropdownItem> |
||||
</ElDropdownMenu> |
||||
</template> |
||||
</ElDropdown> |
||||
</template> |
||||
@ -1,3 +0,0 @@ |
||||
import SizeDropdown from './src/SizeDropdown.vue' |
||||
|
||||
export { SizeDropdown } |
||||
@ -1,38 +0,0 @@ |
||||
<script lang="ts" name="SizeDropdown" setup> |
||||
import { useAppStore } from '@/store/modules/app' |
||||
|
||||
import { propTypes } from '@/utils/propTypes' |
||||
import { useDesign } from '@/hooks/web/useDesign' |
||||
import { ElementPlusSize } from '@/types/elementPlus' |
||||
|
||||
const { getPrefixCls } = useDesign() |
||||
|
||||
const prefixCls = getPrefixCls('size-dropdown') |
||||
|
||||
defineProps({ |
||||
color: propTypes.string.def('') |
||||
}) |
||||
|
||||
const { t } = useI18n() |
||||
|
||||
const appStore = useAppStore() |
||||
|
||||
const sizeMap = computed(() => appStore.sizeMap) |
||||
|
||||
const setCurrentSize = (size: ElementPlusSize) => { |
||||
appStore.setCurrentSize(size) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<ElDropdown :class="prefixCls" trigger="click" @command="setCurrentSize"> |
||||
<Icon :color="color" :size="18" class="cursor-pointer" icon="mdi:format-size" /> |
||||
<template #dropdown> |
||||
<ElDropdownMenu> |
||||
<ElDropdownItem v-for="item in sizeMap" :key="item" :command="item"> |
||||
{{ t(`size.${item}`) }} |
||||
</ElDropdownItem> |
||||
</ElDropdownMenu> |
||||
</template> |
||||
</ElDropdown> |
||||
</template> |
||||
@ -0,0 +1,106 @@ |
||||
<template> |
||||
<!-- 搜索工作栏 --> |
||||
<el-form :model="queryParams" ref="queryFormRef" inline label-width="0"> |
||||
<el-form-item> |
||||
<el-input |
||||
v-model="queryParams.name" |
||||
placeholder="请输入部门名称" |
||||
clearable |
||||
class="!w-240px" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item> |
||||
<el-button @click="handleQuery"> 搜索</el-button> |
||||
<el-button @click="resetQuery"> 重置</el-button> |
||||
<el-button type="primary" plain @click="openForm('create')"> 新增 </el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
|
||||
<!-- 列表 --> |
||||
<el-table v-loading="loading" :data="list" row-key="id" default-expand-all border> |
||||
<el-table-column prop="name" label="部门名称" /> |
||||
<el-table-column prop="leader" label="负责人" width="120" /> |
||||
<el-table-column prop="sort" label="排序" width="200" /> |
||||
<el-table-column prop="status" label="状态" width="100" /> |
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180" /> |
||||
<el-table-column label="操作" align="center" class-name="fixed-width" width="160"> |
||||
<template #default="scope"> |
||||
<el-button link type="primary" @click="openForm('update', scope.row.id)"> 修改 </el-button> |
||||
<el-button link type="danger" @click="handleDelete(scope.row.id)"> 删除 </el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
|
||||
<!-- 表单弹窗:添加/修改 --> |
||||
<DeptForm ref="formRef" @success="getList" /> |
||||
</template> |
||||
<script setup lang="ts" name="SystemDept"> |
||||
import { handleTree } from '@/utils/tree' |
||||
// import * as DeptApi from '@/api/system/dept' |
||||
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({ |
||||
name: undefined, |
||||
pageNo: 1, |
||||
pageSize: 20 |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
|
||||
/** 查询部门列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
// const data = await DeptApi.getDeptPage(queryParams) |
||||
const data = [ |
||||
{ name: '职能部', id: 10001, leader: '张三', status: 1 }, |
||||
{ name: '财务部', id: 10002, leader: '李四', parentId: 10001, status: 1 } |
||||
] |
||||
list.value = handleTree(data) |
||||
} finally { |
||||
loading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 搜索按钮操作 */ |
||||
const handleQuery = () => { |
||||
queryParams.pageNo = 1 |
||||
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 { |
||||
console.log(id) |
||||
// 删除的二次确认 |
||||
await message.delConfirm() |
||||
// 发起删除 |
||||
// await DeptApi.deleteDept(id) |
||||
message.success(t('common.delSuccess')) |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch {} |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(async () => { |
||||
await getList() |
||||
}) |
||||
</script> |
||||
@ -0,0 +1,46 @@ |
||||
<template> |
||||
<div> |
||||
<el-table v-loading="loading" :data="tableList" border> |
||||
<el-table-column type="index" width="50" /> |
||||
<el-table-column |
||||
v-for="col in columns" |
||||
:prop="col.prop" |
||||
:key="col.prop" |
||||
:label="col.label" |
||||
:width="col.width" |
||||
/> |
||||
</el-table> |
||||
<Pagination |
||||
v-model:limit="pageSize" |
||||
v-model:page="currentPage" |
||||
:total="total" |
||||
@pagination="getList" |
||||
/> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup name="RoleEmployee"> |
||||
const loading = ref(false) |
||||
const tableList = ref([]) |
||||
const total = ref(0) |
||||
const pageSize = ref(20) |
||||
const currentPage = ref(1) |
||||
|
||||
const columns = ref([ |
||||
{ prop: 'userName', label: '姓名' }, |
||||
{ prop: '', label: '手机号' }, |
||||
{ prop: '', label: '部门' }, |
||||
{ prop: '', label: '角色', width: '300px' }, |
||||
{ prop: '', label: '性别' } |
||||
]) |
||||
|
||||
function getList() { |
||||
tableList.value = [{ userName: '测试', phone: '18899998888' }] |
||||
} |
||||
|
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
|
||||
<style lang="scss" scoped></style> |
||||
@ -0,0 +1,85 @@ |
||||
<template> |
||||
<div> |
||||
<el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="0px"> |
||||
<el-form-item> |
||||
<el-tree |
||||
ref="treeRef" |
||||
:data="menuOptions" |
||||
:props="defaultProps" |
||||
empty-text="加载中,请稍候" |
||||
node-key="id" |
||||
show-checkbox |
||||
style="width: 100%" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item> |
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">保存权限</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
</div> |
||||
</template> |
||||
<script lang="ts" name="RoleAssignMenuForm" setup> |
||||
import { defaultProps } from '@/utils/tree' |
||||
// import * as MenuApi from '@/api/system/menu' |
||||
// import * as PermissionApi from '@/api/system/permission' |
||||
|
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息弹窗 |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
||||
const formData = reactive({ |
||||
id: 0, |
||||
name: '', |
||||
menuIds: [] |
||||
}) |
||||
const formRef = ref() // 表单 Ref |
||||
const menuOptions = ref<any[]>([]) // 菜单树形结构 |
||||
const treeRef = ref() // 菜单树组件 Ref |
||||
|
||||
/** 提交表单 */ |
||||
const submitForm = async () => { |
||||
// 校验表单 |
||||
if (!formRef.value) return |
||||
const valid = await formRef.value.validate() |
||||
if (!valid) return |
||||
// 提交请求 |
||||
formLoading.value = true |
||||
try { |
||||
// const data = { |
||||
// roleId: formData.id, |
||||
// menuIds: [ |
||||
// ...(treeRef.value.getCheckedKeys(false) as unknown as Array<number>), // 获得当前选中节点 |
||||
// ...(treeRef.value.getHalfCheckedKeys() as unknown as Array<number>) // 获得半选中的父节点 |
||||
// ] |
||||
// } |
||||
// await PermissionApi.assignRoleMenu(data) |
||||
message.success(t('common.updateSuccess')) |
||||
dialogVisible.value = false |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
|
||||
onMounted(() => { |
||||
menuOptions.value = [ |
||||
{ |
||||
name: '统计报表', |
||||
id: '10001', |
||||
children: [ |
||||
{ name: '首页', id: '20001' }, |
||||
{ name: 'XX统计', id: '20002' } |
||||
] |
||||
}, |
||||
{ |
||||
name: '线索管理', |
||||
id: '10002', |
||||
children: [ |
||||
{ name: '线索库', id: '20002' }, |
||||
{ name: '成交管理', id: '20003' } |
||||
] |
||||
} |
||||
] |
||||
}) |
||||
</script> |
||||
<style lang="scss" scoped></style> |
||||
@ -0,0 +1,167 @@ |
||||
<template> |
||||
<el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="80px"> |
||||
<el-form-item label="权限范围"> |
||||
<el-select v-model="formData.dataScope"> |
||||
<el-option |
||||
v-for="item in dataScopeOptions" |
||||
:key="item.value" |
||||
:label="item.label" |
||||
:value="item.value" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
<el-form-item |
||||
v-if="formData.dataScope === SystemDataScopeEnum.DEPT_CUSTOM" |
||||
label="权限范围" |
||||
style="display: flex" |
||||
> |
||||
<template #header> |
||||
全选/全不选: |
||||
<el-switch |
||||
v-model="treeNodeAll" |
||||
active-text="是" |
||||
inactive-text="否" |
||||
inline-prompt |
||||
@change="handleCheckedTreeNodeAll()" |
||||
/> |
||||
全部展开/折叠: |
||||
<el-switch |
||||
v-model="deptExpand" |
||||
active-text="展开" |
||||
inactive-text="折叠" |
||||
inline-prompt |
||||
@change="handleCheckedTreeExpand" |
||||
/> |
||||
父子联动(选中父节点,自动选择子节点): |
||||
<el-switch v-model="checkStrictly" active-text="是" inactive-text="否" inline-prompt /> |
||||
</template> |
||||
<el-tree |
||||
ref="treeRef" |
||||
:check-strictly="!checkStrictly" |
||||
:data="deptOptions" |
||||
:props="defaultProps" |
||||
default-expand-all |
||||
empty-text="加载中,请稍后" |
||||
node-key="id" |
||||
show-checkbox |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label-width="0"> |
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">保存权限</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
</template> |
||||
<script lang="ts" name="RoleDataPermissionForm" setup> |
||||
// import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
import { defaultProps, handleTree } from '@/utils/tree' |
||||
import { SystemDataScopeEnum } from '@/utils/constants' |
||||
|
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息弹窗 |
||||
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
||||
const formData = reactive({ |
||||
id: 0, |
||||
name: '', |
||||
dataScope: undefined, |
||||
dataScopeDeptIds: [] |
||||
}) |
||||
const formRef = ref() // 表单 Ref |
||||
const deptOptions = ref<any[]>([]) // 部门树形结构 |
||||
const deptExpand = ref(false) // 展开/折叠 |
||||
const treeRef = ref() // 菜单树组件 Ref |
||||
const treeNodeAll = ref(false) // 全选/全不选 |
||||
const checkStrictly = ref(true) // 是否严格模式,即父子不关联 |
||||
|
||||
// const dataScopeOptions = getIntDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE) |
||||
const dataScopeOptions = [ |
||||
{ label: '全部数据权限', value: 1 }, |
||||
{ label: '指定部门数据权限', value: 2 }, |
||||
{ label: '部门数据权限', value: 3 }, |
||||
{ label: '部门及以下数据权限', value: 4 }, |
||||
{ label: '仅本人数据权限', value: 5 } |
||||
] |
||||
|
||||
/** 提交表单 */ |
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |
||||
const submitForm = async () => { |
||||
formLoading.value = true |
||||
try { |
||||
// await PermissionApi.assignRoleDataScope(data) |
||||
message.success(t('common.updateSuccess')) |
||||
// 发送操作成功的事件 |
||||
emit('success') |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 全选/全不选 */ |
||||
const handleCheckedTreeNodeAll = () => { |
||||
treeRef.value.setCheckedNodes(treeNodeAll.value ? deptOptions.value : []) |
||||
} |
||||
|
||||
/** 展开/折叠全部 */ |
||||
const handleCheckedTreeExpand = () => { |
||||
const nodes = treeRef.value?.store.nodesMap |
||||
for (let node in nodes) { |
||||
if (nodes[node].expanded === deptExpand.value) { |
||||
continue |
||||
} |
||||
nodes[node].expanded = deptExpand.value |
||||
} |
||||
} |
||||
|
||||
deptOptions.value = handleTree([ |
||||
{ |
||||
id: 100, |
||||
name: '芋道源码', |
||||
parentId: 0 |
||||
}, |
||||
{ |
||||
id: 101, |
||||
name: '深圳总公司', |
||||
parentId: 100 |
||||
}, |
||||
{ |
||||
id: 103, |
||||
name: '研发部门', |
||||
parentId: 101 |
||||
}, |
||||
{ |
||||
id: 108, |
||||
name: '市场部门', |
||||
parentId: 102 |
||||
}, |
||||
{ |
||||
id: 102, |
||||
name: '长沙分公司', |
||||
parentId: 100 |
||||
}, |
||||
{ |
||||
id: 104, |
||||
name: '市场部门', |
||||
parentId: 101 |
||||
}, |
||||
{ |
||||
id: 109, |
||||
name: '财务部门', |
||||
parentId: 102 |
||||
}, |
||||
{ |
||||
id: 105, |
||||
name: '测试部门', |
||||
parentId: 101 |
||||
}, |
||||
{ |
||||
id: 106, |
||||
name: '财务部门', |
||||
parentId: 101 |
||||
}, |
||||
{ |
||||
id: 107, |
||||
name: '运维部门', |
||||
parentId: 101 |
||||
} |
||||
]) |
||||
</script> |
||||
@ -0,0 +1,124 @@ |
||||
<template> |
||||
<div class="flex"> |
||||
<el-card shadow="always" :body-style="{ padding: '10px' }"> |
||||
<div class="flex justify-between items-center" style="width: 300px"> |
||||
<div class="text-16px font-bold">角色列表</div> |
||||
<el-button type="primary" style="padding: 0px" text @click="openForm('create')"> |
||||
新增 |
||||
</el-button> |
||||
</div> |
||||
<div class="border-top-1px mt-10px pt-10px"> |
||||
<div |
||||
class="flex justify-between items-center pl-10px pr-10px cursor-pointer pt-5px pb-5px" |
||||
v-for="(item, index) in list" |
||||
:key="index" |
||||
:class="{ actived: libraryIndex == index }" |
||||
@click="libraryIndex = index" |
||||
> |
||||
<div class="flex-1 text-14px">{{ item.name }}</div> |
||||
<div class="ml-10px"> |
||||
<el-button |
||||
type="primary" |
||||
style="padding: 0px" |
||||
text |
||||
@click="openForm('update', item.id)" |
||||
> |
||||
修改 |
||||
</el-button> |
||||
<el-button |
||||
type="primary" |
||||
class="ml-10px" |
||||
style="padding: 0px" |
||||
text |
||||
@click="handleDelete(index)" |
||||
> |
||||
删除 |
||||
</el-button> |
||||
</div> |
||||
</div> |
||||
<Pagination |
||||
v-model:limit="queryParams.pageSize" |
||||
v-model:page="queryParams.pageNo" |
||||
:total="total" |
||||
@pagination="getList" |
||||
/> |
||||
</div> |
||||
</el-card> |
||||
<el-card class="ml-20px" style="flex: 1" shadow="always" :body-style="{ padding: '10px' }"> |
||||
<el-tabs v-model="roleOperateIndex" type="card"> |
||||
<el-tab-pane label="角色用户" :name="1"> |
||||
<RoleEmployee /> |
||||
</el-tab-pane> |
||||
<el-tab-pane label="菜单权限" :name="2"> |
||||
<RoleAssignMenuForm ref="assignMenuFormRef" @success="getList" /> |
||||
</el-tab-pane> |
||||
<el-tab-pane label="数据权限" :name="3"> |
||||
<RoleDataPermissionForm ref="dataPermissionFormRef" @success="getList" /> |
||||
</el-tab-pane> |
||||
</el-tabs> |
||||
</el-card> |
||||
<!-- 表单弹窗:添加/修改 --> |
||||
<RoleForm ref="formRef" @success="getList" /> |
||||
</div> |
||||
</template> |
||||
<script lang="ts" name="SystemRole" setup> |
||||
import RoleForm from './RoleForm.vue' |
||||
import RoleEmployee from './Comp/RoleEmployee.vue' |
||||
import RoleAssignMenuForm from './RoleAssignMenuForm.vue' |
||||
import RoleDataPermissionForm from './RoleDataPermissionForm.vue' |
||||
|
||||
const message = useMessage() // 消息弹窗 |
||||
const { t } = useI18n() // 国际化 |
||||
|
||||
const total = ref(0) // 列表的总页数 |
||||
const list = ref([]) // 列表的数据 |
||||
|
||||
const libraryIndex = ref(0) |
||||
const queryParams = reactive({ |
||||
pageNo: 1, |
||||
pageSize: 10 |
||||
}) |
||||
|
||||
/** 查询角色列表 */ |
||||
const getList = async () => { |
||||
// const data = await RoleApi.getRolePage(queryParams) |
||||
list.value = [{ id: 1, name: '管理员' }] |
||||
total.value = 0 |
||||
} |
||||
|
||||
/** 添加/修改操作 */ |
||||
const formRef = ref() |
||||
const openForm = (type: string, id?: number) => { |
||||
formRef.value.open(type, id) |
||||
} |
||||
|
||||
/** 数据权限操作 */ |
||||
const dataPermissionFormRef = ref() |
||||
|
||||
/** 菜单权限操作 */ |
||||
const assignMenuFormRef = ref() |
||||
|
||||
/** 删除按钮操作 */ |
||||
const handleDelete = async (id: number) => { |
||||
try { |
||||
console.log(id) |
||||
|
||||
// 删除的二次确认 |
||||
await message.delConfirm() |
||||
// 发起删除 |
||||
// await RoleApi.deleteRole(id) |
||||
message.success(t('common.delSuccess')) |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch {} |
||||
} |
||||
|
||||
const roleOperateIndex = ref(1) |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
|
||||
<style lang="scss" scoped></style> |
||||
@ -0,0 +1,184 @@ |
||||
<template> |
||||
<el-row :gutter="20"> |
||||
<!-- 左侧部门树 --> |
||||
<el-col :span="4" :xs="24"> |
||||
<DeptTree @node-click="handleDeptNodeClick" /> |
||||
</el-col> |
||||
<el-col :span="20" :xs="24"> |
||||
<!-- 搜索 --> |
||||
<el-form :model="queryParams" ref="queryFormRef" inline label-width="68px"> |
||||
<el-form-item label="用户名称" prop="username"> |
||||
<el-input |
||||
v-model="queryParams.username" |
||||
placeholder="请输入用户名称" |
||||
clearable |
||||
@keyup.enter="handleQuery" |
||||
class="!w-240px" |
||||
/> |
||||
</el-form-item> |
||||
<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> |
||||
<el-button @click="handleQuery">搜索</el-button> |
||||
<el-button @click="resetQuery">重置</el-button> |
||||
<el-button type="primary" plain @click="openForm('create')"> 新增 </el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
<el-table v-loading="loading" :data="list"> |
||||
<el-table-column label="用户编号" key="id" prop="id" /> |
||||
<el-table-column label="用户名称" prop="username" /> |
||||
<el-table-column label="用户昵称" prop="nickname" /> |
||||
<el-table-column label="部门" key="deptName" prop="dept.name" /> |
||||
<el-table-column label="手机号码" prop="mobile" width="120" /> |
||||
<el-table-column label="状态" key="status"> |
||||
<template #default="scope"> |
||||
<el-switch |
||||
v-model="scope.row.status" |
||||
:active-value="0" |
||||
:inactive-value="1" |
||||
@change="handleStatusChange(scope.row)" |
||||
/> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="创建时间" prop="createTime" width="180" /> |
||||
<el-table-column label="操作" width="260"> |
||||
<template #default="scope"> |
||||
<el-button type="primary" link @click="openForm('update', scope.row.id)"> |
||||
修改 |
||||
</el-button> |
||||
<el-button type="primary" link @click="handleDelete(scope.row.id)"> 删除 </el-button> |
||||
<el-button type="primary" link @click="handleResetPwd(scope.row)"> 重置密码 </el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
<Pagination |
||||
:total="total" |
||||
v-model:page="queryParams.pageNo" |
||||
v-model:limit="queryParams.pageSize" |
||||
@pagination="getList" |
||||
/> |
||||
</el-col> |
||||
</el-row> |
||||
|
||||
<!-- 添加或修改用户对话框 --> |
||||
<UserForm ref="formRef" @success="getList" /> |
||||
</template> |
||||
<script setup lang="ts" name="SystemUser"> |
||||
import { CommonStatusEnum } from '@/utils/constants' |
||||
import * as UserApi from '@/api/system/user' |
||||
import UserForm from './UserForm.vue' |
||||
import DeptTree from './DeptTree.vue' |
||||
const message = useMessage() // 消息弹窗 |
||||
const { t } = useI18n() // 国际化 |
||||
|
||||
const loading = ref(true) // 列表的加载中 |
||||
const total = ref(0) // 列表的总页数 |
||||
const list = ref([]) // 列表的数 |
||||
const queryParams = reactive({ |
||||
pageNo: 1, |
||||
pageSize: 10, |
||||
username: undefined, |
||||
mobile: undefined, |
||||
deptId: undefined |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
|
||||
/** 查询列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
// const data = await UserApi.getUserPage(queryParams) |
||||
const data = { |
||||
list: [{ username: '测试', status: 0 }], |
||||
total: 0 |
||||
} |
||||
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 handleDeptNodeClick = async (row) => { |
||||
queryParams.deptId = row.id |
||||
await getList() |
||||
} |
||||
|
||||
/** 添加/修改操作 */ |
||||
const formRef = ref() |
||||
const openForm = (type: string, id?: number) => { |
||||
formRef.value.open(type, id) |
||||
} |
||||
|
||||
/** 修改用户状态 */ |
||||
const handleStatusChange = async (row: UserApi.UserVO) => { |
||||
try { |
||||
// 修改状态的二次确认 |
||||
const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用' |
||||
await message.confirm('确认要"' + text + '""' + row.username + '"用户吗?') |
||||
// 发起修改状态 |
||||
// await UserApi.updateUserStatus(row.id, row.status) |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch { |
||||
// 取消后,进行恢复按钮 |
||||
row.status = |
||||
row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE |
||||
} |
||||
} |
||||
|
||||
/** 删除按钮操作 */ |
||||
const handleDelete = async (id: number) => { |
||||
try { |
||||
console.log(id) |
||||
|
||||
// 删除的二次确认 |
||||
await message.delConfirm() |
||||
// 发起删除 |
||||
// await UserApi.deleteUser(id) |
||||
message.success(t('common.delSuccess')) |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch {} |
||||
} |
||||
|
||||
/** 重置密码 */ |
||||
const handleResetPwd = async (row: UserApi.UserVO) => { |
||||
try { |
||||
// 重置的二次确认 |
||||
const result = await message.prompt( |
||||
'请输入"' + row.username + '"的新密码', |
||||
t('common.reminder') |
||||
) |
||||
const password = result.value |
||||
// 发起重置 |
||||
// await UserApi.resetUserPwd(row.id, password) |
||||
message.success('修改成功,新密码是:' + password) |
||||
} catch {} |
||||
} |
||||
|
||||
/** 初始化 */ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,171 +0,0 @@ |
||||
<template> |
||||
<ContentWrap> |
||||
<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="primary" link @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="primary" |
||||
link |
||||
@click="handleFormDetail(scope.row)" |
||||
> |
||||
<span>{{ scope.row.formName }}</span> |
||||
</el-button> |
||||
<el-button v-else type="primary" link @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" |
||||
/> |
||||
<el-table-column |
||||
label="定义描述" |
||||
align="center" |
||||
prop="description" |
||||
width="300" |
||||
show-overflow-tooltip |
||||
/> |
||||
<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> |
||||
|
||||
<!-- 弹窗:表单详情 --> |
||||
<Dialog title="表单详情" v-model="formDetailVisible" width="800"> |
||||
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" /> |
||||
</Dialog> |
||||
|
||||
<!-- 弹窗:流程模型图的预览 --> |
||||
<Dialog title="流程图" v-model="bpmnDetailVisible" width="800"> |
||||
<MyProcessViewer |
||||
key="designer" |
||||
v-model="bpmnXML" |
||||
:value="bpmnXML as any" |
||||
v-bind="bpmnControlForm" |
||||
:prefix="bpmnControlForm.prefix" |
||||
/> |
||||
</Dialog> |
||||
</template> |
||||
|
||||
<script setup lang="ts" name="BpmProcessDefinition"> |
||||
import { DICT_TYPE } from '@/utils/dict' |
||||
import { dateFormatter } from '@/utils/formatTime' |
||||
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package' |
||||
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 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: [], |
||||
option: {} |
||||
}) |
||||
const handleFormDetail = async (row) => { |
||||
if (row.formType == 10) { |
||||
// 设置表单 |
||||
setConfAndFields2(formDetailPreview, row.formConf, row.formFields) |
||||
// 弹窗打开 |
||||
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) => { |
||||
bpmnXML.value = await DefinitionApi.getProcessDefinitionBpmnXML(row.id) |
||||
bpmnDetailVisible.value = true |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,117 +0,0 @@ |
||||
<template> |
||||
<ContentWrap> |
||||
<!-- 表单设计器 --> |
||||
<FcDesigner ref="designer" height="780px"> |
||||
<template #handle> |
||||
<el-button round size="small" type="primary" @click="handleSave"> |
||||
<Icon class="mr-5px" icon="ep:plus" /> |
||||
保存 |
||||
</el-button> |
||||
</template> |
||||
</FcDesigner> |
||||
</ContentWrap> |
||||
|
||||
<!-- 表单保存的弹窗 --> |
||||
<Dialog v-model="dialogVisible" title="保存表单" 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" placeholder="请输入备注" type="textarea" /> |
||||
</el-form-item> |
||||
</el-form> |
||||
<template #footer> |
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> |
||||
<el-button @click="dialogVisible = false">取 消</el-button> |
||||
</template> |
||||
</Dialog> |
||||
</template> |
||||
<script lang="ts" name="BpmFormEditor" setup> |
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
import { CommonStatusEnum } from '@/utils/constants' |
||||
import * as FormApi from '@/api/bpm/form' |
||||
import FcDesigner from '@form-create/designer' |
||||
import { encodeConf, encodeFields, setConfAndFields } from '@/utils/formCreate' |
||||
import { useTagsViewStore } from '@/store/modules/tagsView' |
||||
|
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息 |
||||
const { push, currentRoute } = useRouter() // 路由 |
||||
const { query } = useRoute() // 路由信息 |
||||
const { delView } = useTagsViewStore() // 视图操作 |
||||
|
||||
const designer = ref() // 表单设计器 |
||||
const dialogVisible = 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 = () => { |
||||
dialogVisible.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')) |
||||
} |
||||
dialogVisible.value = false |
||||
close() |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
/** 关闭按钮 */ |
||||
const close = () => { |
||||
delView(unref(currentRoute)) |
||||
push('/bpm/manager/form') |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
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> |
||||
@ -1,191 +0,0 @@ |
||||
<template> |
||||
<ContentWrap> |
||||
<!-- 搜索工作栏 --> |
||||
<el-form |
||||
ref="queryFormRef" |
||||
:inline="true" |
||||
:model="queryParams" |
||||
class="-mb-15px" |
||||
label-width="68px" |
||||
> |
||||
<el-form-item label="表单名" prop="name"> |
||||
<el-input |
||||
v-model="queryParams.name" |
||||
class="!w-240px" |
||||
clearable |
||||
placeholder="请输入表单名" |
||||
@keyup.enter="handleQuery" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item> |
||||
<el-button @click="handleQuery"> |
||||
<Icon class="mr-5px" icon="ep:search" /> |
||||
搜索 |
||||
</el-button> |
||||
<el-button @click="resetQuery"> |
||||
<Icon class="mr-5px" icon="ep:refresh" /> |
||||
重置 |
||||
</el-button> |
||||
<el-button v-hasPermi="['bpm:form:create']" plain type="primary" @click="openForm"> |
||||
<Icon class="mr-5px" icon="ep:plus" /> |
||||
新增 |
||||
</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
</ContentWrap> |
||||
|
||||
<!-- 列表 --> |
||||
<ContentWrap> |
||||
<el-table v-loading="loading" :data="list"> |
||||
<el-table-column align="center" label="编号" prop="id" /> |
||||
<el-table-column align="center" label="表单名" prop="name" /> |
||||
<el-table-column align="center" label="状态" prop="status"> |
||||
<template #default="scope"> |
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column align="center" label="备注" prop="remark" /> |
||||
<el-table-column |
||||
:formatter="dateFormatter" |
||||
align="center" |
||||
label="创建时间" |
||||
prop="createTime" |
||||
/> |
||||
<el-table-column align="center" label="操作"> |
||||
<template #default="scope"> |
||||
<el-button |
||||
v-hasPermi="['bpm:form:update']" |
||||
link |
||||
type="primary" |
||||
@click="openForm(scope.row.id)" |
||||
> |
||||
编辑 |
||||
</el-button> |
||||
<el-button v-hasPermi="['bpm:form:query']" link @click="openDetail(scope.row.id)"> |
||||
详情 |
||||
</el-button> |
||||
<el-button |
||||
v-hasPermi="['bpm:form:delete']" |
||||
link |
||||
type="danger" |
||||
@click="handleDelete(scope.row.id)" |
||||
> |
||||
删除 |
||||
</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
<!-- 分页 --> |
||||
<Pagination |
||||
v-model:limit="queryParams.pageSize" |
||||
v-model:page="queryParams.pageNo" |
||||
:total="total" |
||||
@pagination="getList" |
||||
/> |
||||
</ContentWrap> |
||||
|
||||
<!-- 表单详情的弹窗 --> |
||||
<Dialog v-model="detailVisible" title="表单详情" width="800"> |
||||
<form-create :option="detailData.option" :rule="detailData.rule" /> |
||||
</Dialog> |
||||
</template> |
||||
|
||||
<script lang="ts" name="BpmForm" setup> |
||||
import { DICT_TYPE } from '@/utils/dict' |
||||
import { dateFormatter } from '@/utils/formatTime' |
||||
import * as FormApi from '@/api/bpm/form' |
||||
import { setConfAndFields2 } from '@/utils/formCreate' |
||||
|
||||
const message = useMessage() // 消息弹窗 |
||||
const { t } = useI18n() // 国际化 |
||||
const { currentRoute, push } = useRouter() // 路由 |
||||
|
||||
const loading = ref(true) // 列表的加载中 |
||||
const total = ref(0) // 列表的总页数 |
||||
const list = ref([]) // 列表的数据 |
||||
const queryParams = reactive({ |
||||
pageNo: 1, |
||||
pageSize: 10, |
||||
name: null |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
|
||||
/** 查询列表 */ |
||||
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 handleQuery = () => { |
||||
queryParams.pageNo = 1 |
||||
getList() |
||||
} |
||||
|
||||
/** 重置按钮操作 */ |
||||
const resetQuery = () => { |
||||
queryFormRef.value.resetFields() |
||||
handleQuery() |
||||
} |
||||
|
||||
/** 添加/修改操作 */ |
||||
const openForm = (id?: number) => { |
||||
const toRouter: { name: string; query?: { id: number } } = { |
||||
name: 'BpmFormEditor' |
||||
} |
||||
// 表单新建的时候id传的是event需要排除 |
||||
if (typeof id === 'number') { |
||||
toRouter.query = { |
||||
id |
||||
} |
||||
} |
||||
push(toRouter) |
||||
} |
||||
|
||||
/** 删除按钮操作 */ |
||||
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 openDetail = async (rowId: number) => { |
||||
// 设置表单 |
||||
const data = await FormApi.getForm(rowId) |
||||
setConfAndFields2(detailData, data.conf, data.fields) |
||||
// 弹窗打开 |
||||
detailVisible.value = true |
||||
} |
||||
/**表单保存返回后重新加载列表 */ |
||||
watch( |
||||
() => currentRoute.value, |
||||
() => { |
||||
getList() |
||||
}, |
||||
{ |
||||
immediate: true |
||||
} |
||||
) |
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,130 +0,0 @@ |
||||
<template> |
||||
<Dialog v-model="dialogVisible" :title="dialogTitle"> |
||||
<el-form |
||||
ref="formRef" |
||||
v-loading="formLoading" |
||||
:model="formData" |
||||
:rules="formRules" |
||||
label-width="100px" |
||||
> |
||||
<el-form-item label="组名" prop="name"> |
||||
<el-input v-model="formData.name" placeholder="请输入组名" /> |
||||
</el-form-item> |
||||
<el-form-item label="描述"> |
||||
<el-input v-model="formData.description" placeholder="请输入描述" type="textarea" /> |
||||
</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> |
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> |
||||
<el-button @click="dialogVisible = false">取 消</el-button> |
||||
</template> |
||||
</Dialog> |
||||
</template> |
||||
<script lang="ts" name="UserGroupForm" setup> |
||||
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 dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const dialogTitle = 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<any[]>([]) // 用户列表 |
||||
|
||||
/** 打开弹窗 */ |
||||
const open = async (type: string, id?: number) => { |
||||
dialogVisible.value = true |
||||
dialogTitle.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')) |
||||
} |
||||
dialogVisible.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> |
||||
@ -1,186 +0,0 @@ |
||||
<template> |
||||
<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="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" |
||||
plain |
||||
@click="openForm('create')" |
||||
v-hasPermi="['bpm:user-group:create']" |
||||
> |
||||
<Icon icon="ep:plus" 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="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" |
||||
/> |
||||
<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" name="BpmUserGroup"> |
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
import { dateFormatter } from '@/utils/formatTime' |
||||
import * as UserGroupApi from '@/api/bpm/userGroup' |
||||
import * as UserApi from '@/api/system/user' |
||||
import UserGroupForm from './UserGroupForm.vue' |
||||
import { UserVO } from '@/api/system/user' |
||||
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, |
||||
status: null, |
||||
createTime: [] |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
const userList = ref<UserVO[]>([]) // 用户列表 |
||||
|
||||
/** 查询列表 */ |
||||
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 |
||||
} |
||||
} |
||||
|
||||
/** 搜索按钮操作 */ |
||||
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 handleDelete = async (id: number) => { |
||||
try { |
||||
// 删除的二次确认 |
||||
await message.delConfirm() |
||||
// 发起删除 |
||||
await UserGroupApi.deleteUserGroup(id) |
||||
message.success(t('common.delSuccess')) |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch {} |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(async () => { |
||||
await getList() |
||||
// 加载用户列表 |
||||
userList.value = await UserApi.getSimpleUserList() |
||||
}) |
||||
</script> |
||||
@ -1,228 +0,0 @@ |
||||
<template> |
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="600"> |
||||
<el-form |
||||
ref="formRef" |
||||
v-loading="formLoading" |
||||
:model="formData" |
||||
:rules="formRules" |
||||
label-width="110px" |
||||
> |
||||
<el-form-item label="流程标识" prop="key"> |
||||
<el-input |
||||
v-model="formData.key" |
||||
:disabled="!!formData.id" |
||||
placeholder="请输入流标标识" |
||||
style="width: 330px" |
||||
/> |
||||
<el-tooltip |
||||
v-if="!formData.id" |
||||
class="item" |
||||
content="新建后,流程标识不可修改!" |
||||
effect="light" |
||||
placement="top" |
||||
> |
||||
<i class="el-icon-question" style="padding-left: 5px"></i> |
||||
</el-tooltip> |
||||
<el-tooltip v-else class="item" content="流程标识不可修改!" effect="light" placement="top"> |
||||
<i class="el-icon-question" style="padding-left: 5px"></i> |
||||
</el-tooltip> |
||||
</el-form-item> |
||||
<el-form-item label="流程名称" prop="name"> |
||||
<el-input |
||||
v-model="formData.name" |
||||
:disabled="!!formData.id" |
||||
clearable |
||||
placeholder="请输入流程名称" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item v-if="formData.id" label="流程分类" prop="category"> |
||||
<el-select |
||||
v-model="formData.category" |
||||
clearable |
||||
placeholder="请选择流程分类" |
||||
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 v-model="formData.description" clearable type="textarea" /> |
||||
</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 formList" |
||||
: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" |
||||
content="自定义表单的提交路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/create" |
||||
effect="light" |
||||
placement="top" |
||||
> |
||||
<i class="el-icon-question" style="padding-left: 5px"></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" |
||||
content="自定义表单的查看路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/view" |
||||
effect="light" |
||||
placement="top" |
||||
> |
||||
<i class="el-icon-question" style="padding-left: 5px"></i> |
||||
</el-tooltip> |
||||
</el-form-item> |
||||
</div> |
||||
</el-form> |
||||
<template #footer> |
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> |
||||
<el-button @click="dialogVisible = false">取 消</el-button> |
||||
</template> |
||||
</Dialog> |
||||
</template> |
||||
<script lang="ts" name="ModelForm" setup> |
||||
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() // 消息弹窗 |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const dialogTitle = 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 formList = ref([]) // 流程表单的下拉框的数据 |
||||
|
||||
/** 打开弹窗 */ |
||||
const open = async (type: string, id?: number) => { |
||||
dialogVisible.value = true |
||||
dialogTitle.value = t('action.' + type) |
||||
formType.value = type |
||||
resetForm() |
||||
// 修改时,设置数据 |
||||
if (id) { |
||||
formLoading.value = true |
||||
try { |
||||
formData.value = await ModelApi.getModel(id) |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
// 获得流程表单的下拉框的数据 |
||||
formList.value = await FormApi.getSimpleFormList() |
||||
} |
||||
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')) |
||||
} |
||||
dialogVisible.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> |
||||
@ -1,138 +0,0 @@ |
||||
<template> |
||||
<Dialog v-model="dialogVisible" title="导入流程" width="400"> |
||||
<div> |
||||
<el-upload |
||||
ref="uploadRef" |
||||
v-model:file-list="fileList" |
||||
:action="importUrl" |
||||
:auto-upload="false" |
||||
:data="formData" |
||||
:disabled="formLoading" |
||||
:headers="uploadHeaders" |
||||
:limit="1" |
||||
:on-error="submitFormError" |
||||
:on-exceed="handleExceed" |
||||
:on-success="submitFormSuccess" |
||||
accept=".bpmn, .xml" |
||||
drag |
||||
name="bpmnFile" |
||||
> |
||||
<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" clearable placeholder="请输入流程名称" /> |
||||
</el-form-item> |
||||
<el-form-item label="流程描述" prop="description"> |
||||
<el-input v-model="formData.description" clearable type="textarea" /> |
||||
</el-form-item> |
||||
</el-form> |
||||
</div> |
||||
</template> |
||||
</el-upload> |
||||
</div> |
||||
<template #footer> |
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> |
||||
<el-button @click="dialogVisible = false">取 消</el-button> |
||||
</template> |
||||
</Dialog> |
||||
</template> |
||||
<script lang="ts" name="ModelImportForm" setup> |
||||
import { getAccessToken, getTenantId } from '@/utils/auth' |
||||
|
||||
const message = useMessage() // 消息弹窗 |
||||
|
||||
const dialogVisible = 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 open = async () => { |
||||
dialogVisible.value = true |
||||
resetForm() |
||||
} |
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
||||
|
||||
/** 提交表单 */ |
||||
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() |
||||
} |
||||
formLoading.value = true |
||||
uploadRef.value!.submit() |
||||
} |
||||
|
||||
/** 文件上传成功 */ |
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |
||||
const submitFormSuccess = async (response: any) => { |
||||
if (response.code !== 0) { |
||||
message.error(response.msg) |
||||
formLoading.value = false |
||||
return |
||||
} |
||||
// 提示成功 |
||||
message.success('导入流程成功!请点击【设计流程】按钮,进行编辑保存后,才可以进行【发布流程】') |
||||
// 发送操作成功的事件 |
||||
emit('success') |
||||
} |
||||
|
||||
/** 上传错误提示 */ |
||||
const submitFormError = (): void => { |
||||
message.error('导入流程失败,请您重新上传!') |
||||
formLoading.value = false |
||||
} |
||||
|
||||
/** 重置表单 */ |
||||
const resetForm = () => { |
||||
// 重置上传状态和文件 |
||||
formLoading.value = false |
||||
uploadRef.value?.clearFiles() |
||||
// 重置表单 |
||||
formData.value = { |
||||
key: '', |
||||
name: '', |
||||
description: '' |
||||
} |
||||
formRef.value?.resetFields() |
||||
} |
||||
|
||||
/** 文件数超出提示 */ |
||||
const handleExceed = (): void => { |
||||
message.error('最多只能上传一个文件!') |
||||
} |
||||
</script> |
||||
@ -1,103 +0,0 @@ |
||||
<template> |
||||
<ContentWrap> |
||||
<!-- 流程设计器,负责绘制流程等 --> |
||||
<MyProcessDesigner |
||||
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" |
||||
/> |
||||
<!-- 流程属性器,负责编辑每个流程节点的属性 --> |
||||
<MyProcessPenal |
||||
key="penal" |
||||
:bpmnModeler="modeler as any" |
||||
:prefix="controlForm.prefix" |
||||
class="process-panel" |
||||
:model="model" |
||||
/> |
||||
</ContentWrap> |
||||
</template> |
||||
|
||||
<script setup lang="ts" name="BpmModelEditor"> |
||||
import { MyProcessDesigner, MyProcessPenal } from '@/components/bpmnProcessDesigner/package' |
||||
// 自定义元素选中时的弹出菜单(修改 默认任务 为 用户任务) |
||||
import CustomContentPadProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/content-pad' |
||||
// 自定义左侧菜单(修改 默认任务 为 用户任务) |
||||
import CustomPaletteProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/palette' |
||||
import * as ModelApi from '@/api/bpm/model' |
||||
|
||||
const router = useRouter() // 路由 |
||||
const { query } = useRoute() // 路由的查询 |
||||
const message = useMessage() // 国际化 |
||||
|
||||
const xmlString = ref(undefined) // BPMN XML |
||||
const modeler = ref(null) // BPMN Modeler |
||||
const controlForm = ref({ |
||||
simulation: true, |
||||
labelEditing: false, |
||||
labelVisible: false, |
||||
prefix: 'flowable', |
||||
headerButtonSize: 'mini', |
||||
additionalModel: [CustomContentPadProvider, CustomPaletteProvider] |
||||
}) |
||||
const model = ref<ModelApi.ModelVO>() // 流程模型的信息 |
||||
|
||||
/** 初始化 modeler */ |
||||
const initModeler = (item) => { |
||||
setTimeout(() => { |
||||
modeler.value = item |
||||
}, 10) |
||||
} |
||||
|
||||
/** 添加/修改模型 */ |
||||
const save = async (bpmnXml) => { |
||||
const data = { |
||||
...model.value, |
||||
bpmnXml: bpmnXml // bpmnXml 只是初始化流程图,后续修改无法通过它获得 |
||||
} as unknown as ModelApi.ModelVO |
||||
// 提交 |
||||
if (data.id) { |
||||
await ModelApi.updateModel(data) |
||||
message.success('修改成功') |
||||
} else { |
||||
await ModelApi.createModel(data) |
||||
message.success('新增成功') |
||||
} |
||||
// 跳转回去 |
||||
close() |
||||
} |
||||
|
||||
/** 关闭按钮 */ |
||||
const close = () => { |
||||
router.push({ path: '/bpm/manager/model' }) |
||||
} |
||||
|
||||
/** 初始化 */ |
||||
onMounted(async () => { |
||||
const modelId = query.modelId as unknown as number |
||||
if (!modelId) { |
||||
message.error('缺少模型 modelId 编号') |
||||
return |
||||
} |
||||
// 查询模型 |
||||
const data = await ModelApi.getModel(modelId) |
||||
xmlString.value = data.bpmnXml |
||||
model.value = { |
||||
...data, |
||||
bpmnXml: undefined // 清空 bpmnXml 属性 |
||||
} |
||||
}) |
||||
</script> |
||||
<style lang="scss"> |
||||
.process-panel__container { |
||||
position: absolute; |
||||
right: 60px; |
||||
top: 90px; |
||||
} |
||||
</style> |
||||
@ -1,401 +0,0 @@ |
||||
<template> |
||||
<ContentWrap> |
||||
<!-- 搜索工作栏 --> |
||||
<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" |
||||
/> |
||||
</el-form-item> |
||||
<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="category"> |
||||
<el-select |
||||
v-model="queryParams.category" |
||||
placeholder="请选择流程分类" |
||||
clearable |
||||
class="!w-240px" |
||||
> |
||||
<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="openImportForm" 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" width="200" /> |
||||
<el-table-column label="流程名称" align="center" prop="name" width="200"> |
||||
<template #default="scope"> |
||||
<el-button type="primary" link @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="primary" |
||||
link |
||||
@click="handleFormDetail(scope.row)" |
||||
> |
||||
<span>{{ scope.row.formName }}</span> |
||||
</el-button> |
||||
<el-button |
||||
v-else-if="scope.row.formType === 20" |
||||
type="primary" |
||||
link |
||||
@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" |
||||
width="180" |
||||
: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="85" |
||||
> |
||||
<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" width="240" fixed="right"> |
||||
<template #default="scope"> |
||||
<el-button |
||||
link |
||||
type="primary" |
||||
@click="openForm('update', scope.row.id)" |
||||
v-hasPermi="['bpm:model:update']" |
||||
> |
||||
修改流程 |
||||
</el-button> |
||||
<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:model: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" /> |
||||
|
||||
<!-- 表单弹窗:导入流程 --> |
||||
<ModelImportForm ref="importFormRef" @success="getList" /> |
||||
|
||||
<!-- 弹窗:表单详情 --> |
||||
<Dialog title="表单详情" v-model="formDetailVisible" width="800"> |
||||
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" /> |
||||
</Dialog> |
||||
|
||||
<!-- 弹窗:流程模型图的预览 --> |
||||
<Dialog title="流程图" v-model="bpmnDetailVisible" width="800"> |
||||
<MyProcessViewer |
||||
key="designer" |
||||
v-model="bpmnXML" |
||||
:value="bpmnXML as any" |
||||
v-bind="bpmnControlForm" |
||||
:prefix="bpmnControlForm.prefix" |
||||
/> |
||||
</Dialog> |
||||
</template> |
||||
|
||||
<script setup lang="ts" name="BpmModel"> |
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
import { dateFormatter, formatDate } from '@/utils/formatTime' |
||||
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package' |
||||
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() // 国际化 |
||||
const { push } = useRouter() // 路由 |
||||
|
||||
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 queryFormRef = ref() // 搜索的表单 |
||||
|
||||
/** 查询列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
const data = await ModelApi.getModelPage(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 formRef = ref() |
||||
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 { |
||||
// 删除的二次确认 |
||||
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: 'BpmModelEditor', |
||||
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: 'BpmProcessDefinition', |
||||
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(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,86 +0,0 @@ |
||||
<template> |
||||
<el-form |
||||
ref="formRef" |
||||
:model="formData" |
||||
:rules="formRules" |
||||
label-width="80px" |
||||
v-loading="formLoading" |
||||
> |
||||
<el-form-item label="请假类型" prop="type"> |
||||
<el-select v-model="formData.type" placeholder="请选择请假类型" clearable> |
||||
<el-option |
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_OA_LEAVE_TYPE)" |
||||
:key="dict.value" |
||||
:label="dict.label" |
||||
:value="dict.value" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
<el-form-item label="开始时间" prop="startTime"> |
||||
<el-date-picker |
||||
clearable |
||||
v-model="formData.startTime" |
||||
type="datetime" |
||||
value-format="x" |
||||
placeholder="请选择开始时间" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="结束时间" prop="endTime"> |
||||
<el-date-picker |
||||
clearable |
||||
v-model="formData.endTime" |
||||
type="datetime" |
||||
value-format="x" |
||||
placeholder="请选择结束时间" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="原因" prop="reason"> |
||||
<el-input v-model="formData.reason" type="textarea" placeholder="请输请假原因" /> |
||||
</el-form-item> |
||||
<el-form-item> |
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
</template> |
||||
<script setup name="BpmOALeaveCreate" lang="ts"> |
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
import * as LeaveApi from '@/api/bpm/leave' |
||||
import { useTagsViewStore } from '@/store/modules/tagsView' |
||||
const message = useMessage() // 消息弹窗 |
||||
const { delView } = useTagsViewStore() // 视图操作 |
||||
const { currentRoute } = useRouter() // 路由 |
||||
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
||||
const formData = ref({ |
||||
type: undefined, |
||||
reason: undefined, |
||||
startTime: undefined, |
||||
endTime: undefined |
||||
}) |
||||
const formRules = reactive({ |
||||
type: [{ required: true, message: '请假类型不能为空', trigger: 'blur' }], |
||||
reason: [{ required: true, message: '请假原因不能为空', trigger: 'change' }], |
||||
startTime: [{ required: true, message: '请假开始时间不能为空', trigger: 'change' }], |
||||
endTime: [{ required: true, message: '请假结束时间不能为空', trigger: 'change' }] |
||||
}) |
||||
const formRef = ref() // 表单 Ref |
||||
|
||||
/** 提交表单 */ |
||||
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 LeaveApi.LeaveVO |
||||
await LeaveApi.createLeave(data) |
||||
message.success('发起成功') |
||||
// 关闭当前 Tab |
||||
delView(unref(currentRoute)) |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
</script> |
||||
@ -1,49 +0,0 @@ |
||||
<template> |
||||
<ContentWrap> |
||||
<el-descriptions :column="1" border> |
||||
<el-descriptions-item label="请假类型"> |
||||
<dict-tag :type="DICT_TYPE.BPM_OA_LEAVE_TYPE" :value="detailData.type" /> |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="开始时间"> |
||||
{{ formatDate(detailData.startTime, 'YYYY-MM-DD') }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="结束时间"> |
||||
{{ formatDate(detailData.endTime, 'YYYY-MM-DD') }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="原因"> |
||||
{{ detailData.reason }} |
||||
</el-descriptions-item> |
||||
</el-descriptions> |
||||
</ContentWrap> |
||||
</template> |
||||
<script lang="ts" name="BpmOALeaveDetail" setup> |
||||
import { DICT_TYPE } from '@/utils/dict' |
||||
import { formatDate } from '@/utils/formatTime' |
||||
import { propTypes } from '@/utils/propTypes' |
||||
import * as LeaveApi from '@/api/bpm/leave' |
||||
|
||||
const { query } = useRoute() // 查询参数 |
||||
|
||||
const props = defineProps({ |
||||
id: propTypes.number.def(undefined) |
||||
}) |
||||
const detailLoading = ref(false) // 表单的加载中 |
||||
const detailData = ref<any>({}) // 详情数据 |
||||
const queryId = query.id as unknown as number // 从 URL 传递过来的 id 编号 |
||||
|
||||
/** 获得数据 */ |
||||
const getInfo = async () => { |
||||
detailLoading.value = true |
||||
try { |
||||
detailData.value = await LeaveApi.getLeave(props.id || queryId) |
||||
} finally { |
||||
detailLoading.value = false |
||||
} |
||||
} |
||||
defineExpose({ open: getInfo }) // 提供 open 方法,用于打开弹窗 |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
getInfo() |
||||
}) |
||||
</script> |
||||
@ -1,232 +0,0 @@ |
||||
<template> |
||||
<ContentWrap> |
||||
<!-- 搜索工作栏 --> |
||||
<el-form |
||||
class="-mb-15px" |
||||
:model="queryParams" |
||||
ref="queryFormRef" |
||||
:inline="true" |
||||
label-width="68px" |
||||
> |
||||
<el-form-item label="请假类型" prop="type"> |
||||
<el-select |
||||
v-model="queryParams.type" |
||||
placeholder="请选择请假类型" |
||||
clearable |
||||
class="!w-240px" |
||||
> |
||||
<el-option |
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_OA_LEAVE_TYPE)" |
||||
: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 label="结果" prop="result"> |
||||
<el-select v-model="queryParams.result" placeholder="请选择结果" clearable class="!w-240px"> |
||||
<el-option |
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT)" |
||||
:key="dict.value" |
||||
:label="dict.label" |
||||
:value="dict.value" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
<el-form-item label="原因" prop="reason"> |
||||
<el-input |
||||
v-model="queryParams.reason" |
||||
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" plain @click="handleCreate()"> |
||||
<Icon icon="ep:plus" 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="result"> |
||||
<template #default="scope"> |
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="开始时间" |
||||
align="center" |
||||
prop="startTime" |
||||
width="180" |
||||
:formatter="dateFormatter" |
||||
/> |
||||
<el-table-column |
||||
label="结束时间" |
||||
align="center" |
||||
prop="endTime" |
||||
width="180" |
||||
:formatter="dateFormatter" |
||||
/> |
||||
<el-table-column label="请假类型" align="center" prop="type"> |
||||
<template #default="scope"> |
||||
<dict-tag :type="DICT_TYPE.BPM_OA_LEAVE_TYPE" :value="scope.row.type" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="原因" align="center" prop="reason" /> |
||||
<el-table-column |
||||
label="申请时间" |
||||
align="center" |
||||
prop="createTime" |
||||
width="180" |
||||
:formatter="dateFormatter" |
||||
/> |
||||
<el-table-column label="操作" align="center" width="200"> |
||||
<template #default="scope"> |
||||
<el-button |
||||
link |
||||
type="primary" |
||||
@click="handleDetail(scope.row)" |
||||
v-hasPermi="['bpm:oa-leave:query']" |
||||
> |
||||
详情 |
||||
</el-button> |
||||
<el-button |
||||
link |
||||
type="primary" |
||||
@click="handleProcessDetail(scope.row)" |
||||
v-hasPermi="['bpm:oa-leave:query']" |
||||
> |
||||
进度 |
||||
</el-button> |
||||
<el-button |
||||
link |
||||
type="danger" |
||||
@click="cancelLeave(scope.row)" |
||||
v-hasPermi="['bpm:oa-leave:create']" |
||||
v-if="scope.row.result === 1" |
||||
> |
||||
取消 |
||||
</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" name="BpmOALeave"> |
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
import { dateFormatter } from '@/utils/formatTime' |
||||
import * as LeaveApi from '@/api/bpm/leave' |
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance' |
||||
const message = useMessage() // 消息弹窗 |
||||
const router = useRouter() // 路由 |
||||
const { t } = useI18n() // 国际化 |
||||
|
||||
const loading = ref(true) // 列表的加载中 |
||||
const total = ref(0) // 列表的总页数 |
||||
const list = ref([]) // 列表的数据 |
||||
const queryParams = reactive({ |
||||
pageNo: 1, |
||||
pageSize: 10, |
||||
type: undefined, |
||||
result: undefined, |
||||
reason: undefined, |
||||
createTime: [] |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
|
||||
/** 查询列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
const data = await LeaveApi.getLeavePage(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 handleCreate = () => { |
||||
router.push({ name: 'OALeaveCreate' }) |
||||
} |
||||
|
||||
/** 详情操作 */ |
||||
const handleDetail = (row: LeaveApi.LeaveVO) => { |
||||
router.push({ |
||||
name: 'OALeaveDetail', |
||||
query: { |
||||
id: row.id |
||||
} |
||||
}) |
||||
} |
||||
|
||||
/** 取消请假操作 */ |
||||
const cancelLeave = async (row) => { |
||||
// 二次确认 |
||||
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', { |
||||
confirmButtonText: t('common.ok'), |
||||
cancelButtonText: t('common.cancel'), |
||||
inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格 |
||||
inputErrorMessage: '取消原因不能为空' |
||||
}) |
||||
// 发起取消 |
||||
await ProcessInstanceApi.cancelProcessInstance(row.id, value) |
||||
message.success('取消成功') |
||||
// 刷新列表 |
||||
await getList() |
||||
} |
||||
|
||||
/** 审批进度 */ |
||||
const handleProcessDetail = (row) => { |
||||
router.push({ |
||||
name: 'BpmProcessInstanceDetail', |
||||
query: { |
||||
id: row.processInstanceId |
||||
} |
||||
}) |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,130 +0,0 @@ |
||||
<template> |
||||
<!-- 第一步,通过流程定义的列表,选择对应的流程 --> |
||||
<ContentWrap v-if="!selectProcessInstance"> |
||||
<el-table v-loading="loading" :data="list"> |
||||
<el-table-column label="流程名称" align="center" prop="name" /> |
||||
<el-table-column label="流程分类" align="center" prop="category"> |
||||
<template #default="scope"> |
||||
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="流程版本" align="center" prop="version"> |
||||
<template #default="scope"> |
||||
<el-tag>v{{ scope.row.version }}</el-tag> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="流程描述" align="center" prop="description" /> |
||||
<el-table-column label="操作" align="center"> |
||||
<template #default="scope"> |
||||
<el-button link type="primary" @click="handleSelect(scope.row)"> |
||||
<Icon icon="ep:plus" /> 选择 |
||||
</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
</ContentWrap> |
||||
|
||||
<!-- 第二步,填写表单,进行流程的提交 --> |
||||
<ContentWrap v-else> |
||||
<el-card class="box-card"> |
||||
<div class="clearfix"> |
||||
<span class="el-icon-document">申请信息【{{ selectProcessInstance.name }}】</span> |
||||
<el-button style="float: right" type="primary" @click="selectProcessInstance = undefined"> |
||||
<Icon icon="ep:delete" /> 选择其它流程 |
||||
</el-button> |
||||
</div> |
||||
<el-col :span="16" :offset="6" style="margin-top: 20px"> |
||||
<form-create |
||||
:rule="detailForm.rule" |
||||
v-model:api="fApi" |
||||
:option="detailForm.option" |
||||
@submit="submitForm" |
||||
/> |
||||
</el-col> |
||||
</el-card> |
||||
<!-- 流程图预览 --> |
||||
<ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML as any" /> |
||||
</ContentWrap> |
||||
</template> |
||||
<script setup lang="ts" name="BpmProcessInstanceCreate"> |
||||
import { DICT_TYPE } from '@/utils/dict' |
||||
import * as DefinitionApi from '@/api/bpm/definition' |
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance' |
||||
import { setConfAndFields2 } from '@/utils/formCreate' |
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config' |
||||
import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue' |
||||
const router = useRouter() // 路由 |
||||
const message = useMessage() // 消息 |
||||
|
||||
// ========== 列表相关 ========== |
||||
const loading = ref(true) // 列表的加载中 |
||||
const list = ref([]) // 列表的数据 |
||||
const queryParams = reactive({ |
||||
suspensionState: 1 |
||||
}) |
||||
|
||||
/** 查询列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
list.value = await DefinitionApi.getProcessDefinitionList(queryParams) |
||||
} finally { |
||||
loading.value = false |
||||
} |
||||
} |
||||
|
||||
// ========== 表单相关 ========== |
||||
const bpmnXML = ref(null) // BPMN 数据 |
||||
const fApi = ref<ApiAttrs>() |
||||
const detailForm = ref({ |
||||
// 流程表单详情 |
||||
rule: [], |
||||
option: {} |
||||
}) |
||||
const selectProcessInstance = ref() // 选择的流程实例 |
||||
|
||||
/** 处理选择流程的按钮操作 **/ |
||||
const handleSelect = async (row) => { |
||||
// 设置选择的流程 |
||||
selectProcessInstance.value = row |
||||
|
||||
// 情况一:流程表单 |
||||
if (row.formType == 10) { |
||||
// 设置表单 |
||||
setConfAndFields2(detailForm, row.formConf, row.formFields) |
||||
// 加载流程图 |
||||
bpmnXML.value = await DefinitionApi.getProcessDefinitionBpmnXML(row.id) |
||||
// 情况二:业务表单 |
||||
} else if (row.formCustomCreatePath) { |
||||
await router.push({ |
||||
path: row.formCustomCreatePath |
||||
}) |
||||
// 这里暂时无需加载流程图,因为跳出到另外个 Tab; |
||||
} |
||||
} |
||||
|
||||
/** 提交按钮 */ |
||||
const submitForm = async (formData) => { |
||||
if (!fApi.value || !selectProcessInstance.value) { |
||||
return |
||||
} |
||||
// 提交请求 |
||||
fApi.value.btn.loading(true) |
||||
try { |
||||
await ProcessInstanceApi.createProcessInstance({ |
||||
processDefinitionId: selectProcessInstance.value.id, |
||||
variables: formData |
||||
}) |
||||
// 提示 |
||||
message.success('发起流程成功') |
||||
router.go(-1) |
||||
} finally { |
||||
fApi.value.btn.loading(false) |
||||
} |
||||
} |
||||
|
||||
/** 初始化 */ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,55 +0,0 @@ |
||||
<template> |
||||
<el-card v-loading="loading" class="box-card"> |
||||
<template #header> |
||||
<span class="el-icon-picture-outline">流程图</span> |
||||
</template> |
||||
<MyProcessViewer |
||||
key="designer" |
||||
:activityData="activityList" |
||||
:prefix="bpmnControlForm.prefix" |
||||
:processInstanceData="processInstance" |
||||
:taskData="tasks" |
||||
:value="bpmnXml" |
||||
v-bind="bpmnControlForm" |
||||
/> |
||||
</el-card> |
||||
</template> |
||||
<script lang="ts" name="BpmProcessInstanceBpmnViewer" setup> |
||||
import { propTypes } from '@/utils/propTypes' |
||||
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package' |
||||
import * as ActivityApi from '@/api/bpm/activity' |
||||
|
||||
const props = defineProps({ |
||||
loading: propTypes.bool, // 是否加载中 |
||||
id: propTypes.string, // 流程实例的编号 |
||||
processInstance: propTypes.any, // 流程实例的信息 |
||||
tasks: propTypes.array, // 流程任务的数组 |
||||
bpmnXml: propTypes.string // BPMN XML |
||||
}) |
||||
|
||||
const bpmnControlForm = ref({ |
||||
prefix: 'flowable' |
||||
}) |
||||
const activityList = ref([]) // 任务列表 |
||||
// const bpmnXML = computed(() => { // TODO 芋艿:不晓得为啊哈不能这么搞 |
||||
// if (!props.processInstance || !props.processInstance.processDefinition) { |
||||
// return |
||||
// } |
||||
// return DefinitionApi.getProcessDefinitionBpmnXML(props.processInstance.processDefinition.id) |
||||
// }) |
||||
|
||||
/** 初始化 */ |
||||
onMounted(async () => { |
||||
if (props.id) { |
||||
activityList.value = await ActivityApi.getActivityList({ |
||||
processInstanceId: props.id |
||||
}) |
||||
} |
||||
}) |
||||
</script> |
||||
<style> |
||||
.box-card { |
||||
width: 100%; |
||||
margin-bottom: 20px; |
||||
} |
||||
</style> |
||||
@ -1,89 +0,0 @@ |
||||
<template> |
||||
<el-card v-loading="loading" class="box-card"> |
||||
<template #header> |
||||
<span class="el-icon-picture-outline">审批记录</span> |
||||
</template> |
||||
<el-col :offset="4" :span="16"> |
||||
<div class="block"> |
||||
<el-timeline> |
||||
<el-timeline-item |
||||
v-for="(item, index) in tasks" |
||||
:key="index" |
||||
:icon="getTimelineItemIcon(item)" |
||||
:type="getTimelineItemType(item)" |
||||
> |
||||
<p style="font-weight: 700">任务:{{ item.name }}</p> |
||||
<el-card :body-style="{ padding: '10px' }"> |
||||
<label v-if="item.assigneeUser" style="font-weight: normal; margin-right: 30px"> |
||||
审批人:{{ item.assigneeUser.nickname }} |
||||
<el-tag size="small" type="info">{{ item.assigneeUser.deptName }}</el-tag> |
||||
</label> |
||||
<label v-if="item.createTime" style="font-weight: normal">创建时间:</label> |
||||
<label style="color: #8a909c; font-weight: normal"> |
||||
{{ formatDate(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"> |
||||
{{ formatDate(item?.endTime) }} |
||||
</label> |
||||
<label v-if="item.durationInMillis" style="margin-left: 30px; font-weight: normal"> |
||||
耗时: |
||||
</label> |
||||
<label v-if="item.durationInMillis" style="color: #8a909c; font-weight: normal"> |
||||
{{ formatPast2(item?.durationInMillis) }} |
||||
</label> |
||||
<p v-if="item.reason"> |
||||
<el-tag :type="getTimelineItemType(item)">{{ item.reason }}</el-tag> |
||||
</p> |
||||
</el-card> |
||||
</el-timeline-item> |
||||
</el-timeline> |
||||
</div> |
||||
</el-col> |
||||
</el-card> |
||||
</template> |
||||
<script lang="ts" name="BpmProcessInstanceTaskList" setup> |
||||
import { formatDate, formatPast2 } from '@/utils/formatTime' |
||||
import { propTypes } from '@/utils/propTypes' |
||||
|
||||
defineProps({ |
||||
loading: propTypes.bool, // 是否加载中 |
||||
tasks: propTypes.array // 流程任务的数组 |
||||
}) |
||||
|
||||
/** 获得任务对应的 icon */ |
||||
const getTimelineItemIcon = (item) => { |
||||
if (item.result === 1) { |
||||
return 'el-icon-time' |
||||
} |
||||
if (item.result === 2) { |
||||
return 'el-icon-check' |
||||
} |
||||
if (item.result === 3) { |
||||
return 'el-icon-close' |
||||
} |
||||
if (item.result === 4) { |
||||
return 'el-icon-remove-outline' |
||||
} |
||||
return '' |
||||
} |
||||
|
||||
/** 获得任务对应的颜色 */ |
||||
const getTimelineItemType = (item) => { |
||||
if (item.result === 1) { |
||||
return 'primary' |
||||
} |
||||
if (item.result === 2) { |
||||
return 'success' |
||||
} |
||||
if (item.result === 3) { |
||||
return 'danger' |
||||
} |
||||
if (item.result === 4) { |
||||
return 'info' |
||||
} |
||||
return '' |
||||
} |
||||
</script> |
||||
@ -1,81 +0,0 @@ |
||||
<template> |
||||
<Dialog v-model="dialogVisible" title="转派审批人" width="500"> |
||||
<el-form |
||||
ref="formRef" |
||||
v-loading="formLoading" |
||||
:model="formData" |
||||
:rules="formRules" |
||||
label-width="110px" |
||||
> |
||||
<el-form-item label="新审批人" prop="assigneeUserId"> |
||||
<el-select v-model="formData.assigneeUserId" clearable style="width: 100%"> |
||||
<el-option |
||||
v-for="item in userList" |
||||
:key="item.id" |
||||
:label="item.nickname" |
||||
:value="item.id" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
</el-form> |
||||
<template #footer> |
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> |
||||
<el-button @click="dialogVisible = false">取 消</el-button> |
||||
</template> |
||||
</Dialog> |
||||
</template> |
||||
<script lang="ts" name="BpmTaskUpdateAssigneeForm" setup> |
||||
import * as TaskApi from '@/api/bpm/task' |
||||
import * as UserApi from '@/api/system/user' |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const formLoading = ref(false) // 表单的加载中 |
||||
const formData = ref({ |
||||
id: '', |
||||
assigneeUserId: undefined |
||||
}) |
||||
const formRules = ref({ |
||||
assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }] |
||||
}) |
||||
|
||||
const formRef = ref() // 表单 Ref |
||||
const userList = ref<any[]>([]) // 用户列表 |
||||
|
||||
/** 打开弹窗 */ |
||||
const open = async (id: string) => { |
||||
dialogVisible.value = true |
||||
resetForm() |
||||
formData.value.id = id |
||||
// 获得用户列表 |
||||
userList.value = await UserApi.getSimpleUserList() |
||||
} |
||||
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗 |
||||
|
||||
/** 提交表单 */ |
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |
||||
const submitForm = async () => { |
||||
// 校验表单 |
||||
if (!formRef) return |
||||
const valid = await formRef.value.validate() |
||||
if (!valid) return |
||||
// 提交请求 |
||||
formLoading.value = true |
||||
try { |
||||
await TaskApi.updateTaskAssignee(formData.value) |
||||
dialogVisible.value = false |
||||
// 发送操作成功的事件 |
||||
emit('success') |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 重置表单 */ |
||||
const resetForm = () => { |
||||
formData.value = { |
||||
id: '', |
||||
assigneeUserId: undefined |
||||
} |
||||
formRef.value?.resetFields() |
||||
} |
||||
</script> |
||||
@ -1,279 +0,0 @@ |
||||
<template> |
||||
<ContentWrap> |
||||
<!-- 审批信息 --> |
||||
<el-card |
||||
v-for="(item, index) in runningTasks" |
||||
:key="index" |
||||
v-loading="processInstanceLoading" |
||||
class="box-card" |
||||
> |
||||
<template #header> |
||||
<span class="el-icon-picture-outline">审批任务【{{ item.name }}】</span> |
||||
</template> |
||||
<el-col :offset="6" :span="16"> |
||||
<el-form |
||||
:ref="'form' + index" |
||||
:model="auditForms[index]" |
||||
:rules="auditRule" |
||||
label-width="100px" |
||||
> |
||||
<el-form-item v-if="processInstance && processInstance.name" label="流程名"> |
||||
{{ processInstance.name }} |
||||
</el-form-item> |
||||
<el-form-item v-if="processInstance && processInstance.startUser" label="流程发起人"> |
||||
{{ processInstance.startUser.nickname }} |
||||
<el-tag size="small" type="info">{{ processInstance.startUser.deptName }}</el-tag> |
||||
</el-form-item> |
||||
<el-form-item label="审批建议" prop="reason"> |
||||
<el-input |
||||
v-model="auditForms[index].reason" |
||||
placeholder="请输入审批建议" |
||||
type="textarea" |
||||
/> |
||||
</el-form-item> |
||||
</el-form> |
||||
<div style="margin-left: 10%; margin-bottom: 20px; font-size: 14px"> |
||||
<el-button type="success" @click="handleAudit(item, true)"> |
||||
<Icon icon="ep:select" /> |
||||
通过 |
||||
</el-button> |
||||
<el-button type="danger" @click="handleAudit(item, false)"> |
||||
<Icon icon="ep:close" /> |
||||
不通过 |
||||
</el-button> |
||||
<el-button type="primary" @click="openTaskUpdateAssigneeForm(item.id)"> |
||||
<Icon icon="ep:edit" /> |
||||
转办 |
||||
</el-button> |
||||
<el-button type="primary" @click="handleDelegate(item)"> |
||||
<Icon icon="ep:position" /> |
||||
委派 |
||||
</el-button> |
||||
<el-button type="warning" @click="handleBack(item)"> |
||||
<Icon icon="ep:back" /> |
||||
回退 |
||||
</el-button> |
||||
</div> |
||||
</el-col> |
||||
</el-card> |
||||
|
||||
<!-- 申请信息 --> |
||||
<el-card v-loading="processInstanceLoading" class="box-card"> |
||||
<template #header> |
||||
<span class="el-icon-document">申请信息【{{ processInstance.name }}】</span> |
||||
</template> |
||||
<!-- 情况一:流程表单 --> |
||||
<el-col v-if="processInstance?.processDefinition?.formType === 10" :offset="6" :span="16"> |
||||
<form-create |
||||
ref="fApi" |
||||
v-model="detailForm.value" |
||||
:option="detailForm.option" |
||||
:rule="detailForm.rule" |
||||
/> |
||||
</el-col> |
||||
<!-- 情况二:业务表单 --> |
||||
<div v-if="processInstance?.processDefinition?.formType === 20"> |
||||
<BusinessFormComponent :id="processInstance.businessKey" /> |
||||
</div> |
||||
</el-card> |
||||
|
||||
<!-- 审批记录 --> |
||||
<ProcessInstanceTaskList :loading="tasksLoad" :tasks="tasks" /> |
||||
|
||||
<!-- 高亮流程图 --> |
||||
<ProcessInstanceBpmnViewer |
||||
:id="`${id}`" |
||||
:bpmn-xml="bpmnXML" |
||||
:loading="processInstanceLoading" |
||||
:process-instance="processInstance" |
||||
:tasks="tasks" |
||||
/> |
||||
|
||||
<!-- 弹窗:转派审批人 --> |
||||
<TaskUpdateAssigneeForm ref="taskUpdateAssigneeFormRef" @success="getDetail" /> |
||||
</ContentWrap> |
||||
</template> |
||||
<script lang="ts" name="BpmProcessInstanceDetail" setup> |
||||
import { useUserStore } from '@/store/modules/user' |
||||
import { setConfAndFields2 } from '@/utils/formCreate' |
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config' |
||||
import * as DefinitionApi from '@/api/bpm/definition' |
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance' |
||||
import * as TaskApi from '@/api/bpm/task' |
||||
import TaskUpdateAssigneeForm from './TaskUpdateAssigneeForm.vue' |
||||
import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue' |
||||
import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue' |
||||
import { registerComponent } from '@/utils/routerHelper' |
||||
|
||||
const { query } = useRoute() // 查询参数 |
||||
const message = useMessage() // 消息弹窗 |
||||
const { proxy } = getCurrentInstance() as any |
||||
|
||||
const userId = useUserStore().getUser.id // 当前登录的编号 |
||||
const id = query.id as unknown as number // 流程实例的编号 |
||||
const processInstanceLoading = ref(false) // 流程实例的加载中 |
||||
const processInstance = ref<any>({}) // 流程实例 |
||||
const bpmnXML = ref('') // BPMN XML |
||||
const tasksLoad = ref(true) // 任务的加载中 |
||||
const tasks = ref<any[]>([]) // 任务列表 |
||||
// ========== 审批信息 ========== |
||||
const runningTasks = ref<any[]>([]) // 运行中的任务 |
||||
const auditForms = ref<any[]>([]) // 审批任务的表单 |
||||
const auditRule = reactive({ |
||||
reason: [{ required: true, message: '审批建议不能为空', trigger: 'blur' }] |
||||
}) |
||||
// ========== 申请信息 ========== |
||||
const fApi = ref<ApiAttrs>() // |
||||
const detailForm = ref({ |
||||
// 流程表单详情 |
||||
rule: [], |
||||
option: {}, |
||||
value: {} |
||||
}) |
||||
|
||||
/** 处理审批通过和不通过的操作 */ |
||||
const handleAudit = async (task, pass) => { |
||||
// 1.1 获得对应表单 |
||||
const index = runningTasks.value.indexOf(task) |
||||
const auditFormRef = proxy.$refs['form' + index][0] |
||||
// 1.2 校验表单 |
||||
const elForm = unref(auditFormRef) |
||||
if (!elForm) return |
||||
const valid = await elForm.validate() |
||||
if (!valid) return |
||||
|
||||
// 2.1 提交审批 |
||||
const data = { |
||||
id: task.id, |
||||
reason: auditForms.value[index].reason |
||||
} |
||||
if (pass) { |
||||
await TaskApi.approveTask(data) |
||||
message.success('审批通过成功') |
||||
} else { |
||||
await TaskApi.rejectTask(data) |
||||
message.success('审批不通过成功') |
||||
} |
||||
// 2.2 加载最新数据 |
||||
getDetail() |
||||
} |
||||
|
||||
/** 转派审批人 */ |
||||
const taskUpdateAssigneeFormRef = ref() |
||||
const openTaskUpdateAssigneeForm = (id: string) => { |
||||
taskUpdateAssigneeFormRef.value.open(id) |
||||
} |
||||
|
||||
/** 处理审批退回的操作 */ |
||||
const handleDelegate = async (task) => { |
||||
message.error('暂不支持【委派】功能,可以使用【转派】替代!') |
||||
console.log(task) |
||||
} |
||||
|
||||
/** 处理审批退回的操作 */ |
||||
const handleBack = async (task) => { |
||||
message.error('暂不支持【退回】功能!') |
||||
console.log(task) |
||||
} |
||||
|
||||
/** 获得详情 */ |
||||
const getDetail = () => { |
||||
// 1. 获得流程实例相关 |
||||
getProcessInstance() |
||||
// 2. 获得流程任务列表(审批记录) |
||||
getTaskList() |
||||
} |
||||
|
||||
/** 加载流程实例 */ |
||||
const BusinessFormComponent = ref(null) // 异步组件 |
||||
const getProcessInstance = async () => { |
||||
try { |
||||
processInstanceLoading.value = true |
||||
const data = await ProcessInstanceApi.getProcessInstance(id) |
||||
if (!data) { |
||||
message.error('查询不到流程信息!') |
||||
return |
||||
} |
||||
processInstance.value = data |
||||
|
||||
// 设置表单信息 |
||||
const processDefinition = data.processDefinition |
||||
if (processDefinition.formType === 10) { |
||||
setConfAndFields2( |
||||
detailForm, |
||||
processDefinition.formConf, |
||||
processDefinition.formFields, |
||||
data.formVariables |
||||
) |
||||
nextTick().then(() => { |
||||
fApi.value?.fapi?.btn.show(false) |
||||
fApi.value?.fapi?.resetBtn.show(false) |
||||
fApi.value?.fapi?.disabled(true) |
||||
}) |
||||
} else { |
||||
BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath) |
||||
} |
||||
|
||||
// 加载流程图 |
||||
bpmnXML.value = await DefinitionApi.getProcessDefinitionBpmnXML(processDefinition.id as number) |
||||
} finally { |
||||
processInstanceLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 加载任务列表 */ |
||||
const getTaskList = async () => { |
||||
try { |
||||
// 获得未取消的任务 |
||||
tasksLoad.value = true |
||||
const data = await TaskApi.getTaskListByProcessInstanceId(id) |
||||
tasks.value = [] |
||||
// 1.1 移除已取消的审批 |
||||
data.forEach((task) => { |
||||
if (task.result !== 4) { |
||||
tasks.value.push(task) |
||||
} |
||||
}) |
||||
// 1.2 排序,将未完成的排在前面,已完成的排在后面; |
||||
tasks.value.sort((a, b) => { |
||||
// 有已完成的情况,按照完成时间倒序 |
||||
if (a.endTime && b.endTime) { |
||||
return b.endTime - a.endTime |
||||
} else if (a.endTime) { |
||||
return 1 |
||||
} else if (b.endTime) { |
||||
return -1 |
||||
// 都是未完成,按照创建时间倒序 |
||||
} else { |
||||
return b.createTime - a.createTime |
||||
} |
||||
}) |
||||
|
||||
// 获得需要自己审批的任务 |
||||
runningTasks.value = [] |
||||
auditForms.value = [] |
||||
tasks.value.forEach((task) => { |
||||
// 2.1 只有待处理才需要 |
||||
if (task.result !== 1) { |
||||
return |
||||
} |
||||
// 2.2 自己不是处理人 |
||||
if (!task.assigneeUser || task.assigneeUser.id !== userId) { |
||||
return |
||||
} |
||||
// 2.3 添加到处理任务 |
||||
runningTasks.value.push({ ...task }) |
||||
auditForms.value.push({ |
||||
reason: '' |
||||
}) |
||||
}) |
||||
} finally { |
||||
tasksLoad.value = false |
||||
} |
||||
} |
||||
|
||||
/** 初始化 */ |
||||
onMounted(() => { |
||||
getDetail() |
||||
}) |
||||
</script> |
||||
@ -1,247 +0,0 @@ |
||||
<template> |
||||
<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="processDefinitionId"> |
||||
<el-input |
||||
v-model="queryParams.processDefinitionId" |
||||
placeholder="请输入流程定义的编号" |
||||
clearable |
||||
@keyup.enter="handleQuery" |
||||
class="!w-240px" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="流程分类" prop="category"> |
||||
<el-select |
||||
v-model="queryParams.category" |
||||
placeholder="请选择流程分类" |
||||
clearable |
||||
class="!w-240px" |
||||
> |
||||
<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="status"> |
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px"> |
||||
<el-option |
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)" |
||||
:key="dict.value" |
||||
:label="dict.label" |
||||
:value="dict.value" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
<el-form-item label="结果" prop="result"> |
||||
<el-select v-model="queryParams.result" placeholder="请选择结果" clearable class="!w-240px"> |
||||
<el-option |
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT)" |
||||
: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" |
||||
plain |
||||
v-hasPermi="['bpm:process-instance:query']" |
||||
@click="handleCreate" |
||||
> |
||||
<Icon icon="ep:plus" 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" width="300px" /> |
||||
<el-table-column label="流程名称" align="center" prop="name" /> |
||||
<el-table-column label="流程分类" align="center" prop="category"> |
||||
<template #default="scope"> |
||||
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="当前审批任务" align="center" prop="tasks"> |
||||
<template #default="scope"> |
||||
<el-button type="primary" v-for="task in scope.row.tasks" :key="task.id" link> |
||||
<span>{{ task.name }}</span> |
||||
</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="状态" prop="status"> |
||||
<template #default="scope"> |
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="结果" prop="result"> |
||||
<template #default="scope"> |
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_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" |
||||
prop="endTime" |
||||
width="180" |
||||
:formatter="dateFormatter" |
||||
/> |
||||
<el-table-column label="操作" align="center"> |
||||
<template #default="scope"> |
||||
<el-button |
||||
link |
||||
type="primary" |
||||
v-hasPermi="['bpm:process-instance:cancel']" |
||||
@click="handleDetail(scope.row)" |
||||
> |
||||
详情 |
||||
</el-button> |
||||
<el-button |
||||
link |
||||
type="primary" |
||||
v-if="scope.row.result === 1" |
||||
v-hasPermi="['bpm:process-instance:query']" |
||||
@click="handleCancel(scope.row)" |
||||
> |
||||
取消 |
||||
</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" name="BpmProcessInstance"> |
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
import { dateFormatter } from '@/utils/formatTime' |
||||
import { ElMessageBox } from 'element-plus' |
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance' |
||||
const router = useRouter() // 路由 |
||||
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: '', |
||||
processDefinitionId: undefined, |
||||
category: undefined, |
||||
status: undefined, |
||||
result: undefined, |
||||
createTime: [] |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
|
||||
/** 查询列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
const data = await ProcessInstanceApi.getMyProcessInstancePage(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 handleCreate = () => { |
||||
router.push({ |
||||
name: 'BpmProcessInstanceCreate' |
||||
}) |
||||
} |
||||
|
||||
/** 查看详情 */ |
||||
const handleDetail = (row) => { |
||||
router.push({ |
||||
name: 'BpmProcessInstanceDetail', |
||||
query: { |
||||
id: row.id |
||||
} |
||||
}) |
||||
} |
||||
|
||||
/** 取消按钮操作 */ |
||||
const handleCancel = async (row) => { |
||||
// 二次确认 |
||||
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', { |
||||
confirmButtonText: t('common.ok'), |
||||
cancelButtonText: t('common.cancel'), |
||||
inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格 |
||||
inputErrorMessage: '取消原因不能为空' |
||||
}) |
||||
// 发起取消 |
||||
await ProcessInstanceApi.cancelProcessInstance(row.id, value) |
||||
message.success('取消成功') |
||||
// 刷新列表 |
||||
await getList() |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,49 +0,0 @@ |
||||
<template> |
||||
<Dialog v-model="dialogVisible" :max-height="500" :scroll="true" title="详情"> |
||||
<el-descriptions :column="1" border> |
||||
<el-descriptions-item label="任务编号" min-width="120"> |
||||
{{ detailData.id }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="任务名称"> |
||||
{{ detailData.name }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="所属流程"> |
||||
{{ detailData.processInstance.name }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="流程发起人"> |
||||
{{ detailData.processInstance.startUserNickname }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="状态"> |
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="detailData.result" /> |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="原因"> |
||||
{{ detailData.reason }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="创建时间"> |
||||
{{ formatDate(detailData.createTime) }} |
||||
</el-descriptions-item> |
||||
</el-descriptions> |
||||
</Dialog> |
||||
</template> |
||||
<script lang="ts" name="BpmTaskDetail" setup> |
||||
import { DICT_TYPE } from '@/utils/dict' |
||||
import { formatDate } from '@/utils/formatTime' |
||||
import * as TaskApi from '@/api/bpm/task' |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const detailLoading = ref(false) // 表单的加载中 |
||||
const detailData = ref() // 详情数据 |
||||
|
||||
/** 打开弹窗 */ |
||||
const open = async (data: TaskApi.TaskVO) => { |
||||
dialogVisible.value = true |
||||
// 设置数据 |
||||
detailLoading.value = true |
||||
try { |
||||
detailData.value = data |
||||
} finally { |
||||
detailLoading.value = false |
||||
} |
||||
} |
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
||||
</script> |
||||
@ -1,146 +0,0 @@ |
||||
<template> |
||||
<ContentWrap> |
||||
<!-- 搜索工作栏 --> |
||||
<el-form |
||||
ref="queryFormRef" |
||||
:inline="true" |
||||
:model="queryParams" |
||||
class="-mb-15px" |
||||
label-width="68px" |
||||
> |
||||
<el-form-item label="任务名称" prop="name"> |
||||
<el-input |
||||
v-model="queryParams.name" |
||||
class="!w-240px" |
||||
clearable |
||||
placeholder="请输入任务名称" |
||||
@keyup.enter="handleQuery" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="创建时间" prop="createTime"> |
||||
<el-date-picker |
||||
v-model="queryParams.createTime" |
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
||||
class="!w-240px" |
||||
end-placeholder="结束日期" |
||||
start-placeholder="开始日期" |
||||
type="daterange" |
||||
value-format="YYYY-MM-DD HH:mm:ss" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item> |
||||
<el-button @click="handleQuery"> |
||||
<Icon class="mr-5px" icon="ep:search" /> |
||||
搜索 |
||||
</el-button> |
||||
<el-button @click="resetQuery"> |
||||
<Icon class="mr-5px" icon="ep:refresh" /> |
||||
重置 |
||||
</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
</ContentWrap> |
||||
|
||||
<!-- 列表 --> |
||||
<ContentWrap> |
||||
<el-table v-loading="loading" :data="list"> |
||||
<el-table-column align="center" label="任务编号" prop="id" width="300px" /> |
||||
<el-table-column align="center" label="任务名称" prop="name" /> |
||||
<el-table-column align="center" label="所属流程" prop="processInstance.name" /> |
||||
<el-table-column align="center" label="流程发起人" prop="processInstance.startUserNickname" /> |
||||
<el-table-column align="center" label="状态" prop="result"> |
||||
<template #default="scope"> |
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column align="center" label="原因" prop="reason" /> |
||||
<el-table-column |
||||
:formatter="dateFormatter" |
||||
align="center" |
||||
label="创建时间" |
||||
prop="createTime" |
||||
width="180" |
||||
/> |
||||
<el-table-column align="center" label="操作"> |
||||
<template #default="scope"> |
||||
<el-button link type="primary" @click="openDetail(scope.row)">详情</el-button> |
||||
<el-button link type="primary" @click="handleAudit(scope.row)">流程</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
<!-- 分页 --> |
||||
<Pagination |
||||
v-model:limit="queryParams.pageSize" |
||||
v-model:page="queryParams.pageNo" |
||||
:total="total" |
||||
@pagination="getList" |
||||
/> |
||||
</ContentWrap> |
||||
|
||||
<!-- 表单弹窗:详情 --> |
||||
<TaskDetail ref="detailRef" @success="getList" /> |
||||
</template> |
||||
<script lang="ts" name="BpmTodoTask" setup> |
||||
import { DICT_TYPE } from '@/utils/dict' |
||||
import { dateFormatter } from '@/utils/formatTime' |
||||
import * as TaskApi from '@/api/bpm/task' |
||||
import TaskDetail from './TaskDetail.vue' |
||||
|
||||
const { push } = useRouter() // 路由 |
||||
|
||||
const loading = ref(true) // 列表的加载中 |
||||
const total = ref(0) // 列表的总页数 |
||||
const list = ref([]) // 列表的数据 |
||||
const queryParams = reactive({ |
||||
pageNo: 1, |
||||
pageSize: 10, |
||||
name: '', |
||||
createTime: [] |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
|
||||
/** 查询任务列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
const data = await TaskApi.getDoneTaskPage(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 = (row: TaskApi.TaskVO) => { |
||||
detailRef.value.open(row) |
||||
} |
||||
|
||||
/** 处理审批按钮 */ |
||||
const handleAudit = (row) => { |
||||
push({ |
||||
name: 'BpmProcessInstanceDetail', |
||||
query: { |
||||
id: row.processInstance.id |
||||
} |
||||
}) |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,135 +0,0 @@ |
||||
<template> |
||||
<ContentWrap> |
||||
<!-- 搜索工作栏 --> |
||||
<el-form |
||||
ref="queryFormRef" |
||||
:inline="true" |
||||
:model="queryParams" |
||||
class="-mb-15px" |
||||
label-width="68px" |
||||
> |
||||
<el-form-item label="任务名称" prop="name"> |
||||
<el-input |
||||
v-model="queryParams.name" |
||||
class="!w-240px" |
||||
clearable |
||||
placeholder="请输入任务名称" |
||||
@keyup.enter="handleQuery" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="创建时间" prop="createTime"> |
||||
<el-date-picker |
||||
v-model="queryParams.createTime" |
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
||||
class="!w-240px" |
||||
end-placeholder="结束日期" |
||||
start-placeholder="开始日期" |
||||
type="daterange" |
||||
value-format="YYYY-MM-DD HH:mm:ss" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item> |
||||
<el-button @click="handleQuery"> |
||||
<Icon class="mr-5px" icon="ep:search" /> |
||||
搜索 |
||||
</el-button> |
||||
<el-button @click="resetQuery"> |
||||
<Icon class="mr-5px" icon="ep:refresh" /> |
||||
重置 |
||||
</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
</ContentWrap> |
||||
|
||||
<!-- 列表 --> |
||||
<ContentWrap> |
||||
<el-table v-loading="loading" :data="list"> |
||||
<el-table-column align="center" label="任务编号" prop="id" width="300px" /> |
||||
<el-table-column align="center" label="任务名称" prop="name" /> |
||||
<el-table-column align="center" label="所属流程" prop="processInstance.name" /> |
||||
<el-table-column align="center" label="流程发起人" prop="processInstance.startUserNickname" /> |
||||
<el-table-column |
||||
:formatter="dateFormatter" |
||||
align="center" |
||||
label="创建时间" |
||||
prop="createTime" |
||||
width="180" |
||||
/> |
||||
<el-table-column label="任务状态" prop="suspensionState"> |
||||
<template #default="scope"> |
||||
<el-tag v-if="scope.row.suspensionState === 1" type="success">激活</el-tag> |
||||
<el-tag v-if="scope.row.suspensionState === 2" type="warning">挂起</el-tag> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column align="center" label="操作"> |
||||
<template #default="scope"> |
||||
<el-button link type="primary" @click="handleAudit(scope.row)">审批进度</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
<!-- 分页 --> |
||||
<Pagination |
||||
v-model:limit="queryParams.pageSize" |
||||
v-model:page="queryParams.pageNo" |
||||
:total="total" |
||||
@pagination="getList" |
||||
/> |
||||
</ContentWrap> |
||||
</template> |
||||
|
||||
<script lang="ts" name="BpmDoneTask" setup> |
||||
import { dateFormatter } from '@/utils/formatTime' |
||||
import * as TaskApi from '@/api/bpm/task' |
||||
|
||||
const { push } = useRouter() // 路由 |
||||
|
||||
const loading = ref(true) // 列表的加载中 |
||||
const total = ref(0) // 列表的总页数 |
||||
const list = ref([]) // 列表的数据 |
||||
const queryParams = reactive({ |
||||
pageNo: 1, |
||||
pageSize: 10, |
||||
name: '', |
||||
createTime: [] |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
|
||||
/** 查询任务列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
const data = await TaskApi.getTodoTaskPage(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 handleAudit = (row) => { |
||||
push({ |
||||
name: 'BpmProcessInstanceDetail', |
||||
query: { |
||||
id: row.processInstance.id |
||||
} |
||||
}) |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,248 +0,0 @@ |
||||
<template> |
||||
<Dialog v-model="dialogVisible" title="修改任务规则" 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" disabled placeholder="请输入流标标识" /> |
||||
</el-form-item> |
||||
<el-form-item label="任务标识" prop="taskDefinitionKey"> |
||||
<el-input v-model="formData.taskDefinitionKey" disabled placeholder="请输入任务标识" /> |
||||
</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" clearable multiple 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 |
||||
v-if="formData.type === 20 || formData.type === 21" |
||||
label="指定部门" |
||||
prop="deptIds" |
||||
span="24" |
||||
> |
||||
<el-tree-select |
||||
ref="treeRef" |
||||
v-model="formData.deptIds" |
||||
:data="deptTreeOptions" |
||||
:props="defaultProps" |
||||
empty-text="加载中,请稍后" |
||||
multiple |
||||
node-key="id" |
||||
show-checkbox |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item v-if="formData.type === 22" label="指定岗位" prop="postIds" span="24"> |
||||
<el-select v-model="formData.postIds" clearable multiple 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 |
||||
v-if="formData.type === 30 || formData.type === 31 || formData.type === 32" |
||||
label="指定用户" |
||||
prop="userIds" |
||||
span="24" |
||||
> |
||||
<el-select v-model="formData.userIds" clearable multiple 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 v-if="formData.type === 40" label="指定用户组" prop="userGroupIds"> |
||||
<el-select v-model="formData.userGroupIds" clearable multiple 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 v-if="formData.type === 50" label="指定脚本" prop="scripts"> |
||||
<el-select v-model="formData.scripts" clearable multiple 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 :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> |
||||
<el-button @click="dialogVisible = false">取 消</el-button> |
||||
</template> |
||||
</Dialog> |
||||
</template> |
||||
<script lang="ts" name="BpmTaskAssignRuleForm" setup> |
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
import { defaultProps, handleTree } 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 dialogVisible = 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) |
||||
} |
||||
// 打开弹窗 |
||||
dialogVisible.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')) |
||||
} |
||||
dialogVisible.value = false |
||||
// 发送操作成功的事件 |
||||
emit('success') |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 重置表单 */ |
||||
const resetForm = () => { |
||||
formRef.value?.resetFields() |
||||
} |
||||
</script> |
||||
@ -1,133 +0,0 @@ |
||||
<template> |
||||
<ContentWrap> |
||||
<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> |
||||
</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="BpmTaskAssignRule"> |
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
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' |
||||
import TaskAssignRuleForm from './TaskAssignRuleForm.vue' |
||||
const { query } = useRoute() // 查询参数 |
||||
|
||||
const loading = ref(true) // 列表的加载中 |
||||
const list = ref([]) // 列表的数据 |
||||
const queryParams = reactive({ |
||||
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) { |
||||
if (roleOption.id === option) { |
||||
return roleOption.name |
||||
} |
||||
} |
||||
} else if (type === 20 || type === 21) { |
||||
for (const deptOption of deptOptions.value) { |
||||
if (deptOption.id === option) { |
||||
return deptOption.name |
||||
} |
||||
} |
||||
} else if (type === 22) { |
||||
for (const postOption of postOptions.value) { |
||||
if (postOption.id === option) { |
||||
return postOption.name |
||||
} |
||||
} |
||||
} else if (type === 30 || type === 31 || type === 32) { |
||||
for (const userOption of userOptions.value) { |
||||
if (userOption.id === option) { |
||||
return userOption.nickname |
||||
} |
||||
} |
||||
} else if (type === 40) { |
||||
for (const userGroupOption of userGroupOptions.value) { |
||||
if (userGroupOption.id === option) { |
||||
return userGroupOption.name |
||||
} |
||||
} |
||||
} else if (type === 50) { |
||||
option = option + '' // 转换成 string |
||||
for (const dictData of taskAssignScriptDictDatas) { |
||||
if (dictData.value === option) { |
||||
return dictData.label |
||||
} |
||||
} |
||||
} |
||||
return '未知(' + option + ')' |
||||
} |
||||
|
||||
/** 添加/修改操作 */ |
||||
const formRef = ref() |
||||
const openForm = (row: TaskAssignRuleApi.TaskAssignVO) => { |
||||
formRef.value.open(queryParams.modelId, row) |
||||
} |
||||
|
||||
/** 初始化 */ |
||||
onMounted(async () => { |
||||
await getList() |
||||
// 获得角色列表 |
||||
roleOptions.value = await RoleApi.getSimpleRoleList() |
||||
// 获得部门列表 |
||||
deptOptions.value = await DeptApi.getSimpleDeptList() |
||||
// 获得岗位列表 |
||||
postOptions.value = await PostApi.getSimplePostList() |
||||
// 获得用户列表 |
||||
userOptions.value = await UserApi.getSimpleUserList() |
||||
// 获得用户组列表 |
||||
userGroupOptions.value = await UserGroupApi.getSimpleUserGroupList() |
||||
}) |
||||
</script> |
||||
@ -1,65 +0,0 @@ |
||||
<template> |
||||
<Dialog v-model="dialogVisible" :max-height="500" :scroll="true" title="详情" width="800"> |
||||
<el-descriptions :column="1" border> |
||||
<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" /> |
||||
</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 }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="请求参数"> |
||||
{{ detailData.requestParams }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="请求时间"> |
||||
{{ formatDate(detailData.beginTime) }} ~ {{ formatDate(detailData.endTime) }} |
||||
</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" |
||||
>失败 | {{ detailData.resultCode }} | {{ detailData.resultMsg }} |
||||
</div> |
||||
</el-descriptions-item> |
||||
</el-descriptions> |
||||
</Dialog> |
||||
</template> |
||||
|
||||
<script lang="ts" name="ApiAccessLogDetail" setup> |
||||
import { DICT_TYPE } from '@/utils/dict' |
||||
import { formatDate } from '@/utils/formatTime' |
||||
import * as ApiAccessLog from '@/api/infra/apiAccessLog' |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const detailLoading = ref(false) // 表单地加载中 |
||||
const detailData = ref() // 详情数据 |
||||
|
||||
/** 打开弹窗 */ |
||||
const open = async (data: ApiAccessLog.ApiAccessLogVO) => { |
||||
dialogVisible.value = true |
||||
// 设置数据 |
||||
detailLoading.value = true |
||||
try { |
||||
detailData.value = data |
||||
} finally { |
||||
detailLoading.value = false |
||||
} |
||||
} |
||||
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
||||
</script> |
||||
@ -1,216 +0,0 @@ |
||||
<template> |
||||
<doc-alert title="系统日志" url="https://doc.iocoder.cn/system-log/" /> |
||||
|
||||
<ContentWrap> |
||||
<!-- 搜索工作栏 --> |
||||
<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" |
||||
/> |
||||
</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 getIntDictOptions(DICT_TYPE.USER_TYPE)" |
||||
:key="dict.value" |
||||
:label="dict.label" |
||||
:value="dict.value" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
<el-form-item label="应用名" prop="applicationName"> |
||||
<el-input |
||||
v-model="queryParams.applicationName" |
||||
placeholder="请输入应用名" |
||||
clearable |
||||
@keyup.enter="handleQuery" |
||||
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> |
||||
</ContentWrap> |
||||
|
||||
<!-- 列表 --> |
||||
<ContentWrap> |
||||
<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" 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"> |
||||
<span>{{ formatDate(scope.row.beginTime) }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="执行时长" align="center" prop="duration" width="180"> |
||||
<template #default="scope"> {{ scope.row.duration }} ms </template> |
||||
</el-table-column> |
||||
<el-table-column label="操作结果" align="center" prop="status"> |
||||
<template #default="scope"> |
||||
{{ scope.row.resultCode === 0 ? '成功' : '失败(' + scope.row.resultMsg + ')' }} |
||||
</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" |
||||
/> |
||||
</ContentWrap> |
||||
|
||||
<!-- 表单弹窗:详情 --> |
||||
<ApiAccessLogDetail ref="detailRef" /> |
||||
</template> |
||||
<script setup lang="ts" name="InfraApiAccessLog"> |
||||
import { DICT_TYPE, getIntDictOptions } 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 message = useMessage() // 消息弹窗 |
||||
|
||||
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 queryFormRef = ref() // 搜索的表单 |
||||
const exportLoading = ref(false) // 导出的加载中 |
||||
|
||||
/** 查询列表 */ |
||||
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> |
||||
@ -1,79 +0,0 @@ |
||||
<template> |
||||
<Dialog v-model="dialogVisible" :max-height="500" :scroll="true" title="详情" width="800"> |
||||
<el-descriptions :column="1" border> |
||||
<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" /> |
||||
</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 }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="请求参数"> |
||||
{{ detailData.requestParams }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="异常时间"> |
||||
{{ formatDate(detailData.exceptionTime) }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="异常名"> |
||||
{{ detailData.exceptionName }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item v-if="detailData.exceptionStackTrace" label="异常堆栈"> |
||||
<el-input |
||||
v-model="detailData.exceptionStackTrace" |
||||
:autosize="{ maxRows: 20 }" |
||||
:readonly="true" |
||||
type="textarea" |
||||
/> |
||||
</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 v-if="detailData.processUserId" label="处理人"> |
||||
{{ detailData.processUserId }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item v-if="detailData.processTime" label="处理时间"> |
||||
{{ formatDate(detailData.processTime) }} |
||||
</el-descriptions-item> |
||||
</el-descriptions> |
||||
</Dialog> |
||||
</template> |
||||
<script lang="ts" name="ApiErrorLogDetail" setup> |
||||
import { DICT_TYPE } from '@/utils/dict' |
||||
import { formatDate } from '@/utils/formatTime' |
||||
import * as ApiErrorLog from '@/api/infra/apiErrorLog' |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const detailLoading = ref(false) // 表单的加载中 |
||||
const detailData = ref() // 详情数据 |
||||
|
||||
/** 打开弹窗 */ |
||||
const open = async (data: ApiErrorLog.ApiErrorLogVO) => { |
||||
dialogVisible.value = true |
||||
// 设置数据 |
||||
detailLoading.value = true |
||||
try { |
||||
detailData.value = data |
||||
} finally { |
||||
detailLoading.value = false |
||||
} |
||||
} |
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
||||
</script> |
||||
@ -1,249 +0,0 @@ |
||||
<template> |
||||
<doc-alert title="系统日志" url="https://doc.iocoder.cn/system-log/" /> |
||||
|
||||
<ContentWrap> |
||||
<!-- 搜索工作栏 --> |
||||
<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" |
||||
/> |
||||
</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 getIntDictOptions(DICT_TYPE.USER_TYPE)" |
||||
:key="dict.value" |
||||
:label="dict.label" |
||||
:value="dict.value" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
<el-form-item label="应用名" prop="applicationName"> |
||||
<el-input |
||||
v-model="queryParams.applicationName" |
||||
placeholder="请输入应用名" |
||||
clearable |
||||
@keyup.enter="handleQuery" |
||||
class="!w-240px" |
||||
/> |
||||
</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" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="处理状态" prop="processStatus"> |
||||
<el-select |
||||
v-model="queryParams.processStatus" |
||||
placeholder="请选择处理状态" |
||||
clearable |
||||
class="!w-240px" |
||||
> |
||||
<el-option |
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS)" |
||||
:key="dict.value" |
||||
:label="dict.label" |
||||
:value="dict.value" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
<el-form-item> |
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> |
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> |
||||
<el-button |
||||
type="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> |
||||
</ContentWrap> |
||||
|
||||
<!-- 列表 --> |
||||
<ContentWrap> |
||||
<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" 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 |
||||
:type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS" |
||||
:value="scope.row.processStatus" |
||||
/> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="操作" align="center" width="200"> |
||||
<template #default="scope"> |
||||
<el-button |
||||
link |
||||
type="primary" |
||||
@click="openDetail(scope.row)" |
||||
v-hasPermi="['infra:api-error-log:query']" |
||||
> |
||||
详细 |
||||
</el-button> |
||||
<el-button |
||||
link |
||||
type="primary" |
||||
v-if="scope.row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT" |
||||
@click="handleProcess(scope.row.id, InfraApiErrorLogProcessStatusEnum.DONE)" |
||||
v-hasPermi="['infra:api-error-log:update-status']" |
||||
> |
||||
已处理 |
||||
</el-button> |
||||
<el-button |
||||
link |
||||
type="primary" |
||||
v-if="scope.row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT" |
||||
@click="handleProcess(scope.row.id, InfraApiErrorLogProcessStatusEnum.IGNORE)" |
||||
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" |
||||
/> |
||||
</ContentWrap> |
||||
|
||||
<!-- 表单弹窗:详情 --> |
||||
<ApiErrorLogDetail ref="detailRef" /> |
||||
</template> |
||||
|
||||
<script setup lang="ts" name="InfraApiErrorLog"> |
||||
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 './ApiErrorLogDetail.vue' |
||||
import { InfraApiErrorLogProcessStatusEnum } from '@/utils/constants' |
||||
const message = useMessage() // 消息弹窗 |
||||
|
||||
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 queryFormRef = ref() // 搜索的表单 |
||||
const exportLoading = ref(false) // 导出的加载中 |
||||
|
||||
/** 查询列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
const data = await ApiErrorLogApi.getApiErrorLogPage(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: ApiErrorLogApi.ApiErrorLogVO) => { |
||||
detailRef.value.open(data) |
||||
} |
||||
|
||||
/** 处理已处理 / 已忽略的操作 **/ |
||||
const handleProcess = async (id: number, processStatus: number) => { |
||||
try { |
||||
// 操作的二次确认 |
||||
const type = processStatus === InfraApiErrorLogProcessStatusEnum.DONE ? '已处理' : '已忽略' |
||||
await message.confirm('确认标记为' + type + '?') |
||||
// 执行操作 |
||||
await ApiErrorLogApi.updateApiErrorLogPage(id, processStatus) |
||||
await message.success(type) |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch {} |
||||
} |
||||
|
||||
/** 导出按钮操作 */ |
||||
const handleExport = async () => { |
||||
try { |
||||
// 导出的二次确认 |
||||
await message.exportConfirm() |
||||
// 发起导出 |
||||
exportLoading.value = true |
||||
const data = await ApiErrorLogApi.exportApiErrorLog(queryParams) |
||||
download.excel(data, '异常日志.xls') |
||||
} catch { |
||||
} finally { |
||||
exportLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,141 +0,0 @@ |
||||
<template> |
||||
<ContentWrap> |
||||
<el-row> |
||||
<el-col> |
||||
<div class="mb-2 float-right"> |
||||
<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="danger" @click="showTemplate">生成组件</el-button> |
||||
</div> |
||||
</el-col> |
||||
<!-- 表单设计器 --> |
||||
<el-col> |
||||
<FcDesigner ref="designer" height="780px" /> |
||||
</el-col> |
||||
</el-row> |
||||
</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> |
||||
<pre><code class="hljs" v-html="highlightedCode(formData)"></code></pre> |
||||
</div> |
||||
</el-scrollbar> |
||||
</div> |
||||
</Dialog> |
||||
</template> |
||||
<script setup lang="ts" name="InfraBuild"> |
||||
import FcDesigner from '@form-create/designer' |
||||
import { useClipboard } from '@vueuse/core' |
||||
import { isString } from '@/utils/is' |
||||
|
||||
import hljs from 'highlight.js' // 导入代码高亮文件 |
||||
import 'highlight.js/styles/github.css' // 导入代码高亮样式 |
||||
import xml from 'highlight.js/lib/languages/java' |
||||
import json from 'highlight.js/lib/languages/json' |
||||
|
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息 |
||||
|
||||
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 |
||||
} |
||||
|
||||
/** 生成 JSON */ |
||||
const showJson = () => { |
||||
openModel('生成 JSON') |
||||
formType.value = 0 |
||||
formData.value = designer.value.getRule() |
||||
} |
||||
|
||||
/** 生成 Options */ |
||||
const showOption = () => { |
||||
openModel('生成 Options') |
||||
formType.value = 1 |
||||
formData.value = designer.value.getOption() |
||||
} |
||||
|
||||
/** 生成组件 */ |
||||
const showTemplate = () => { |
||||
openModel('生成组件') |
||||
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 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')) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 代码高亮 |
||||
*/ |
||||
const highlightedCode = (code) => { |
||||
// 处理语言和代码 |
||||
let language = 'json' |
||||
if (formType.value === 2) { |
||||
language = 'xml' |
||||
} |
||||
if (!isString(code)) { |
||||
code = JSON.stringify(code) |
||||
} |
||||
// 高亮 |
||||
const result = hljs.highlight(language, code, true) |
||||
return result.value || ' ' |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(async () => { |
||||
// 注册代码高亮的各种语言 |
||||
hljs.registerLanguage('xml', xml) |
||||
hljs.registerLanguage('json', json) |
||||
}) |
||||
</script> |
||||
@ -1,81 +0,0 @@ |
||||
<template> |
||||
<ContentWrap v-loading="formLoading"> |
||||
<el-tabs v-model="activeName"> |
||||
<el-tab-pane label="基本信息" name="basicInfo"> |
||||
<basic-info-form ref="basicInfoRef" :table="formData.table" /> |
||||
</el-tab-pane> |
||||
<el-tab-pane label="字段信息" name="colum"> |
||||
<colum-info-form ref="columInfoRef" :columns="formData.columns" /> |
||||
</el-tab-pane> |
||||
<el-tab-pane label="生成信息" name="generateInfo"> |
||||
<generate-info-form ref="generateInfoRef" :table="formData.table" /> |
||||
</el-tab-pane> |
||||
</el-tabs> |
||||
<el-form> |
||||
<el-form-item style="float: right"> |
||||
<el-button :loading="formLoading" type="primary" @click="submitForm">保存</el-button> |
||||
<el-button @click="close">返回</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
</ContentWrap> |
||||
</template> |
||||
<script lang="ts" name="InfraCodegenEditTable" setup> |
||||
import { useTagsViewStore } from '@/store/modules/tagsView' |
||||
import { BasicInfoForm, ColumInfoForm, GenerateInfoForm } from './components' |
||||
import * as CodegenApi from '@/api/infra/codegen' |
||||
|
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息弹窗 |
||||
const { push, currentRoute } = useRouter() // 路由 |
||||
const { query } = useRoute() // 查询参数 |
||||
const { delView } = useTagsViewStore() // 视图操作 |
||||
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
||||
const activeName = ref('colum') // Tag 激活的窗口 |
||||
const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>() |
||||
const columInfoRef = ref<ComponentRef<typeof ColumInfoForm>>() |
||||
const generateInfoRef = ref<ComponentRef<typeof GenerateInfoForm>>() |
||||
const formData = ref<CodegenApi.CodegenUpdateReqVO>({ |
||||
table: {}, |
||||
columns: [] |
||||
}) |
||||
|
||||
/** 获得详情 */ |
||||
const getDetail = async () => { |
||||
const id = query.id as unknown as number |
||||
if (!id) { |
||||
return |
||||
} |
||||
formLoading.value = true |
||||
try { |
||||
formData.value = await CodegenApi.getCodegenTable(id) |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 提交按钮 */ |
||||
const submitForm = async () => { |
||||
// 参数校验 |
||||
if (!unref(formData)) return |
||||
await unref(basicInfoRef)?.validate() |
||||
await unref(generateInfoRef)?.validate() |
||||
try { |
||||
// 提交请求 |
||||
await CodegenApi.updateCodegenTable(formData.value) |
||||
message.success(t('common.updateSuccess')) |
||||
close() |
||||
} catch {} |
||||
} |
||||
|
||||
/** 关闭按钮 */ |
||||
const close = () => { |
||||
delView(unref(currentRoute)) |
||||
push('/infra/codegen') |
||||
} |
||||
|
||||
/** 初始化 */ |
||||
onMounted(() => { |
||||
getDetail() |
||||
}) |
||||
</script> |
||||
@ -1,149 +0,0 @@ |
||||
<template> |
||||
<Dialog v-model="dialogVisible" title="导入表" width="800px"> |
||||
<!-- 搜索栏 --> |
||||
<el-form ref="queryFormRef" :inline="true" :model="queryParams" label-width="68px"> |
||||
<el-form-item label="数据源" prop="dataSourceConfigId"> |
||||
<el-select |
||||
v-model="queryParams.dataSourceConfigId" |
||||
class="!w-240px" |
||||
placeholder="请选择数据源" |
||||
> |
||||
<el-option |
||||
v-for="config in dataSourceConfigList" |
||||
:key="config.id" |
||||
:label="config.name" |
||||
:value="config.id" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
<el-form-item label="表名称" prop="name"> |
||||
<el-input |
||||
v-model="queryParams.name" |
||||
class="!w-240px" |
||||
clearable |
||||
placeholder="请输入表名称" |
||||
@keyup.enter="getList" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="表描述" prop="comment"> |
||||
<el-input |
||||
v-model="queryParams.comment" |
||||
class="!w-240px" |
||||
clearable |
||||
placeholder="请输入表描述" |
||||
@keyup.enter="getList" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item> |
||||
<el-button @click="getList"> |
||||
<Icon class="mr-5px" icon="ep:search" /> |
||||
搜索 |
||||
</el-button> |
||||
<el-button @click="resetQuery"> |
||||
<Icon class="mr-5px" icon="ep:refresh" /> |
||||
重置 |
||||
</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
<!-- 列表 --> |
||||
<el-row> |
||||
<el-table |
||||
ref="tableRef" |
||||
v-loading="dbTableLoading" |
||||
:data="dbTableList" |
||||
height="260px" |
||||
@row-click="handleRowClick" |
||||
@selection-change="handleSelectionChange" |
||||
> |
||||
<el-table-column type="selection" width="55" /> |
||||
<el-table-column :show-overflow-tooltip="true" label="表名称" prop="name" /> |
||||
<el-table-column :show-overflow-tooltip="true" label="表描述" prop="comment" /> |
||||
</el-table> |
||||
</el-row> |
||||
<!-- 操作 --> |
||||
<template #footer> |
||||
<el-button :disabled="tableList.length === 0" type="primary" @click="handleImportTable"> |
||||
导入 |
||||
</el-button> |
||||
<el-button @click="close">关闭</el-button> |
||||
</template> |
||||
</Dialog> |
||||
</template> |
||||
<script lang="ts" name="InfraCodegenImportTable" setup> |
||||
import * as CodegenApi from '@/api/infra/codegen' |
||||
import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig' |
||||
import { ElTable } from 'element-plus' |
||||
|
||||
const message = useMessage() // 消息弹窗 |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const dbTableLoading = ref(true) // 数据源的加载中 |
||||
const dbTableList = ref<CodegenApi.DatabaseTableVO[]>([]) // 表的列表 |
||||
const queryParams = reactive({ |
||||
name: undefined, |
||||
comment: undefined, |
||||
dataSourceConfigId: 0 |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
const dataSourceConfigList = ref<DataSourceConfigApi.DataSourceConfigVO[]>([]) // 数据源列表 |
||||
|
||||
/** 查询表数据 */ |
||||
const getList = async () => { |
||||
dbTableLoading.value = true |
||||
try { |
||||
dbTableList.value = await CodegenApi.getSchemaTableList(queryParams) |
||||
} finally { |
||||
dbTableLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 重置操作 */ |
||||
const resetQuery = async () => { |
||||
queryParams.name = undefined |
||||
queryParams.comment = undefined |
||||
queryParams.dataSourceConfigId = dataSourceConfigList.value[0].id as number |
||||
await getList() |
||||
} |
||||
|
||||
/** 打开弹窗 */ |
||||
const open = async () => { |
||||
// 加载数据源的列表 |
||||
dataSourceConfigList.value = await DataSourceConfigApi.getDataSourceConfigList() |
||||
queryParams.dataSourceConfigId = dataSourceConfigList.value[0].id as number |
||||
dialogVisible.value = true |
||||
// 加载表的列表 |
||||
await getList() |
||||
} |
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
||||
|
||||
/** 关闭弹窗 */ |
||||
const close = () => { |
||||
dialogVisible.value = false |
||||
tableList.value = [] |
||||
} |
||||
|
||||
const tableRef = ref<typeof ElTable>() // 表格的 Ref |
||||
const tableList = ref<string[]>([]) // 选中的表名 |
||||
|
||||
/** 处理某一行的点击 */ |
||||
const handleRowClick = (row) => { |
||||
unref(tableRef)?.toggleRowSelection(row) |
||||
} |
||||
|
||||
/** 多选框选中数据 */ |
||||
const handleSelectionChange = (selection) => { |
||||
tableList.value = selection.map((item) => item.name) |
||||
} |
||||
|
||||
/** 导入按钮操作 */ |
||||
const handleImportTable = async () => { |
||||
await CodegenApi.createCodegenList({ |
||||
dataSourceConfigId: queryParams.dataSourceConfigId, |
||||
tableNames: tableList.value |
||||
}) |
||||
message.success('导入成功') |
||||
emit('success') |
||||
close() |
||||
} |
||||
const emit = defineEmits(['success']) |
||||
</script> |
||||
@ -1,220 +0,0 @@ |
||||
<template> |
||||
<Dialog |
||||
v-model="dialogVisible" |
||||
align-center |
||||
class="app-infra-codegen-preview-container" |
||||
title="代码预览" |
||||
width="80%" |
||||
> |
||||
<div class="flex"> |
||||
<!-- 代码目录树 --> |
||||
<el-card |
||||
v-loading="loading" |
||||
:gutter="12" |
||||
class="w-1/3" |
||||
element-loading-text="生成文件目录中..." |
||||
shadow="hover" |
||||
> |
||||
<el-scrollbar height="calc(100vh - 88px - 40px)"> |
||||
<el-tree |
||||
ref="treeRef" |
||||
:data="preview.fileTree" |
||||
:expand-on-click-node="false" |
||||
default-expand-all |
||||
highlight-current |
||||
node-key="id" |
||||
@node-click="handleNodeClick" |
||||
/> |
||||
</el-scrollbar> |
||||
</el-card> |
||||
<!-- 代码 --> |
||||
<el-card |
||||
v-loading="loading" |
||||
:gutter="12" |
||||
class="w-2/3 ml-3" |
||||
element-loading-text="加载代码中..." |
||||
shadow="hover" |
||||
> |
||||
<el-tabs v-model="preview.activeName"> |
||||
<el-tab-pane |
||||
v-for="item in previewCodegen" |
||||
:key="item.filePath" |
||||
:label="item.filePath.substring(item.filePath.lastIndexOf('/') + 1)" |
||||
:name="item.filePath" |
||||
> |
||||
<el-button class="float-right" text type="primary" @click="copy(item.code)"> |
||||
{{ t('common.copy') }} |
||||
</el-button> |
||||
<div> |
||||
<pre><code class="hljs" v-html="highlightedCode(item)"></code></pre> |
||||
</div> |
||||
</el-tab-pane> |
||||
</el-tabs> |
||||
</el-card> |
||||
</div> |
||||
</Dialog> |
||||
</template> |
||||
<script lang="ts" name="InfraCodegenPreviewCode" setup> |
||||
import { useClipboard } from '@vueuse/core' |
||||
import { handleTree2 } from '@/utils/tree' |
||||
import * as CodegenApi from '@/api/infra/codegen' |
||||
|
||||
import hljs from 'highlight.js' // 导入代码高亮文件 |
||||
import 'highlight.js/styles/github.css' // 导入代码高亮样式 |
||||
import java from 'highlight.js/lib/languages/java' |
||||
import xml from 'highlight.js/lib/languages/java' |
||||
import javascript from 'highlight.js/lib/languages/javascript' |
||||
import sql from 'highlight.js/lib/languages/sql' |
||||
import typescript from 'highlight.js/lib/languages/typescript' |
||||
|
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息弹窗 |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const loading = ref(false) // 加载中的状态 |
||||
const preview = reactive({ |
||||
fileTree: [], // 文件树 |
||||
activeName: '' // 激活的文件名 |
||||
}) |
||||
const previewCodegen = ref<CodegenApi.CodegenPreviewVO[]>() |
||||
|
||||
/** 点击文件 */ |
||||
const handleNodeClick = async (data, node) => { |
||||
if (node && !node.isLeaf) { |
||||
return false |
||||
} |
||||
preview.activeName = data.id |
||||
} |
||||
|
||||
/** 生成 files 目录 **/ |
||||
interface filesType { |
||||
id: string |
||||
label: string |
||||
parentId: string |
||||
} |
||||
|
||||
/** 打开弹窗 */ |
||||
const open = async (id: number) => { |
||||
dialogVisible.value = true |
||||
try { |
||||
loading.value = true |
||||
// 生成代码 |
||||
const data = await CodegenApi.previewCodegen(id) |
||||
previewCodegen.value = data |
||||
// 处理文件 |
||||
let file = handleFiles(data) |
||||
preview.fileTree = handleTree2(file, 'id', 'parentId', 'children', '/') |
||||
// 点击首个文件 |
||||
preview.activeName = data[0].filePath |
||||
} finally { |
||||
loading.value = false |
||||
} |
||||
} |
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
||||
|
||||
/** 处理文件 */ |
||||
const handleFiles = (datas: CodegenApi.CodegenPreviewVO[]) => { |
||||
let exists = {} // key:file 的 id;value:true |
||||
let files: filesType[] = [] |
||||
// 遍历每个元素 |
||||
for (const data of datas) { |
||||
let paths = data.filePath.split('/') |
||||
let fullPath = '' // 从头开始的路径,用于生成 id |
||||
// 特殊处理 java 文件 |
||||
if (paths[paths.length - 1].indexOf('.java') >= 0) { |
||||
let newPaths: string[] = [] |
||||
for (let i = 0; i < paths.length; i++) { |
||||
let path = paths[i] |
||||
if (path !== 'java') { |
||||
newPaths.push(path) |
||||
continue |
||||
} |
||||
newPaths.push(path) |
||||
// 特殊处理中间的 package,进行合并 |
||||
let tmp = '' |
||||
while (i < paths.length) { |
||||
path = paths[i + 1] |
||||
if ( |
||||
path === 'controller' || |
||||
path === 'convert' || |
||||
path === 'dal' || |
||||
path === 'enums' || |
||||
path === 'service' || |
||||
path === 'vo' || // 下面三个,主要是兜底。可能考虑到有人改了包结构 |
||||
path === 'mysql' || |
||||
path === 'dataobject' |
||||
) { |
||||
break |
||||
} |
||||
tmp = tmp ? tmp + '.' + path : path |
||||
i++ |
||||
} |
||||
if (tmp) { |
||||
newPaths.push(tmp) |
||||
} |
||||
} |
||||
paths = newPaths |
||||
} |
||||
// 遍历每个 path, 拼接成树 |
||||
for (let i = 0; i < paths.length; i++) { |
||||
// 已经添加到 files 中,则跳过 |
||||
let oldFullPath = fullPath |
||||
// 下面的 replaceAll 的原因,是因为上面包处理了,导致和 tabs 不匹配,所以 replaceAll 下 |
||||
fullPath = fullPath.length === 0 ? paths[i] : fullPath.replaceAll('.', '/') + '/' + paths[i] |
||||
if (exists[fullPath]) { |
||||
continue |
||||
} |
||||
// 添加到 files 中 |
||||
exists[fullPath] = true |
||||
files.push({ |
||||
id: fullPath, |
||||
label: paths[i], |
||||
parentId: oldFullPath || '/' // "/" 为根节点 |
||||
}) |
||||
} |
||||
} |
||||
return files |
||||
} |
||||
|
||||
/** 复制 **/ |
||||
const copy = async (text: string) => { |
||||
const { copy, copied, isSupported } = useClipboard({ source: text }) |
||||
if (!isSupported) { |
||||
message.error(t('common.copyError')) |
||||
return |
||||
} |
||||
await copy() |
||||
if (unref(copied)) { |
||||
message.success(t('common.copySuccess')) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 代码高亮 |
||||
*/ |
||||
const highlightedCode = (item) => { |
||||
const language = item.filePath.substring(item.filePath.lastIndexOf('.') + 1) |
||||
const result = hljs.highlight(language, item.code || '', true) |
||||
return result.value || ' ' |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(async () => { |
||||
// 注册代码高亮的各种语言 |
||||
hljs.registerLanguage('java', java) |
||||
hljs.registerLanguage('xml', xml) |
||||
hljs.registerLanguage('html', xml) |
||||
hljs.registerLanguage('vue', xml) |
||||
hljs.registerLanguage('javascript', javascript) |
||||
hljs.registerLanguage('sql', sql) |
||||
hljs.registerLanguage('typescript', typescript) |
||||
}) |
||||
</script> |
||||
<style lang="scss"> |
||||
.app-infra-codegen-preview-container { |
||||
.el-scrollbar .el-scrollbar__wrap .el-scrollbar__view { |
||||
white-space: nowrap; |
||||
display: inline-block; |
||||
} |
||||
} |
||||
</style> |
||||
@ -1,85 +0,0 @@ |
||||
<template> |
||||
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px"> |
||||
<el-row> |
||||
<el-col :span="12"> |
||||
<el-form-item label="表名称" prop="tableName"> |
||||
<el-input v-model="formData.tableName" placeholder="请输入仓库名称" /> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-form-item label="表描述" prop="tableComment"> |
||||
<el-input v-model="formData.tableComment" placeholder="请输入" /> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-form-item prop="className"> |
||||
<template #label> |
||||
<span> |
||||
实体类名称 |
||||
<el-tooltip |
||||
content="默认去除表名的前缀。如果存在重复,则需要手动添加前缀,避免 MyBatis 报 Alias 重复的问题。" |
||||
placement="top" |
||||
> |
||||
<Icon class="" icon="ep:question-filled" /> |
||||
</el-tooltip> |
||||
</span> |
||||
</template> |
||||
<el-input v-model="formData.className" placeholder="请输入" /> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-form-item label="作者" prop="author"> |
||||
<el-input v-model="formData.author" placeholder="请输入" /> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="24"> |
||||
<el-form-item label="备注" prop="remark"> |
||||
<el-input v-model="formData.remark" :rows="3" type="textarea" /> |
||||
</el-form-item> |
||||
</el-col> |
||||
</el-row> |
||||
</el-form> |
||||
</template> |
||||
<script lang="ts" name="InfraCodegenBasicInfoForm" setup> |
||||
import * as CodegenApi from '@/api/infra/codegen' |
||||
import { PropType } from 'vue' |
||||
|
||||
const props = defineProps({ |
||||
table: { |
||||
type: Object as PropType<Nullable<CodegenApi.CodegenTableVO>>, |
||||
default: () => null |
||||
} |
||||
}) |
||||
|
||||
const formRef = ref() |
||||
const formData = ref({ |
||||
tableName: '', |
||||
tableComment: '', |
||||
className: '', |
||||
author: '', |
||||
remark: '' |
||||
}) |
||||
const rules = reactive({ |
||||
tableName: [required], |
||||
tableComment: [required], |
||||
className: [required], |
||||
author: [required] |
||||
}) |
||||
|
||||
/** 监听 table 属性,复制给 formData 属性 */ |
||||
watch( |
||||
() => props.table, |
||||
(table) => { |
||||
if (!table) return |
||||
formData.value = table |
||||
}, |
||||
{ |
||||
deep: true, |
||||
immediate: true |
||||
} |
||||
) |
||||
|
||||
defineExpose({ |
||||
validate: async () => unref(formRef)?.validate() |
||||
}) |
||||
</script> |
||||
@ -1,151 +0,0 @@ |
||||
<template> |
||||
<el-table ref="dragTable" :data="formData" :max-height="tableHeight" row-key="columnId"> |
||||
<el-table-column |
||||
:show-overflow-tooltip="true" |
||||
label="字段列名" |
||||
min-width="10%" |
||||
prop="columnName" |
||||
/> |
||||
<el-table-column label="字段描述" min-width="10%"> |
||||
<template #default="scope"> |
||||
<el-input v-model="scope.row.columnComment" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
:show-overflow-tooltip="true" |
||||
label="物理类型" |
||||
min-width="10%" |
||||
prop="dataType" |
||||
/> |
||||
<el-table-column label="Java类型" min-width="11%"> |
||||
<template #default="scope"> |
||||
<el-select v-model="scope.row.javaType"> |
||||
<el-option label="Long" value="Long" /> |
||||
<el-option label="String" value="String" /> |
||||
<el-option label="Integer" value="Integer" /> |
||||
<el-option label="Double" value="Double" /> |
||||
<el-option label="BigDecimal" value="BigDecimal" /> |
||||
<el-option label="LocalDateTime" value="LocalDateTime" /> |
||||
<el-option label="Boolean" value="Boolean" /> |
||||
</el-select> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="java属性" min-width="10%"> |
||||
<template #default="scope"> |
||||
<el-input v-model="scope.row.javaField" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="插入" min-width="4%"> |
||||
<template #default="scope"> |
||||
<el-checkbox v-model="scope.row.createOperation" false-label="false" true-label="true" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="编辑" min-width="4%"> |
||||
<template #default="scope"> |
||||
<el-checkbox v-model="scope.row.updateOperation" false-label="false" true-label="true" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="列表" min-width="4%"> |
||||
<template #default="scope"> |
||||
<el-checkbox |
||||
v-model="scope.row.listOperationResult" |
||||
false-label="false" |
||||
true-label="true" |
||||
/> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="查询" min-width="4%"> |
||||
<template #default="scope"> |
||||
<el-checkbox v-model="scope.row.listOperation" false-label="false" true-label="true" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="查询方式" min-width="10%"> |
||||
<template #default="scope"> |
||||
<el-select v-model="scope.row.listOperationCondition"> |
||||
<el-option label="=" value="=" /> |
||||
<el-option label="!=" value="!=" /> |
||||
<el-option label=">" value=">" /> |
||||
<el-option label=">=" value=">=" /> |
||||
<el-option label="<" value="<>" /> |
||||
<el-option label="<=" value="<=" /> |
||||
<el-option label="LIKE" value="LIKE" /> |
||||
<el-option label="BETWEEN" value="BETWEEN" /> |
||||
</el-select> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="允许空" min-width="5%"> |
||||
<template #default="scope"> |
||||
<el-checkbox v-model="scope.row.nullable" false-label="false" true-label="true" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="显示类型" min-width="12%"> |
||||
<template #default="scope"> |
||||
<el-select v-model="scope.row.htmlType"> |
||||
<el-option label="文本框" value="input" /> |
||||
<el-option label="文本域" value="textarea" /> |
||||
<el-option label="下拉框" value="select" /> |
||||
<el-option label="单选框" value="radio" /> |
||||
<el-option label="复选框" value="checkbox" /> |
||||
<el-option label="日期控件" value="datetime" /> |
||||
<el-option label="图片上传" value="imageUpload" /> |
||||
<el-option label="文件上传" value="fileUpload" /> |
||||
<el-option label="富文本控件" value="editor" /> |
||||
</el-select> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="字典类型" min-width="12%"> |
||||
<template #default="scope"> |
||||
<el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择"> |
||||
<el-option |
||||
v-for="dict in dictOptions" |
||||
:key="dict.id" |
||||
:label="dict.name" |
||||
:value="dict.type" |
||||
/> |
||||
</el-select> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="示例" min-width="10%"> |
||||
<template #default="scope"> |
||||
<el-input v-model="scope.row.example" /> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
</template> |
||||
<script lang="ts" name="InfraCodegenColumInfoForm" setup> |
||||
import { PropType } from 'vue' |
||||
import * as CodegenApi from '@/api/infra/codegen' |
||||
import * as DictDataApi from '@/api/system/dict/dict.type' |
||||
|
||||
const props = defineProps({ |
||||
columns: { |
||||
type: Array as unknown as PropType<CodegenApi.CodegenColumnVO[]>, |
||||
default: () => null |
||||
} |
||||
}) |
||||
|
||||
const formData = ref<CodegenApi.CodegenColumnVO[]>([]) |
||||
const tableHeight = document.documentElement.scrollHeight - 350 + 'px' |
||||
|
||||
/** 查询字典下拉列表 */ |
||||
const dictOptions = ref<DictDataApi.DictTypeVO[]>() |
||||
const getDictOptions = async () => { |
||||
dictOptions.value = await DictDataApi.getSimpleDictTypeList() |
||||
} |
||||
|
||||
watch( |
||||
() => props.columns, |
||||
(columns) => { |
||||
if (!columns) return |
||||
formData.value = columns |
||||
}, |
||||
{ |
||||
deep: true, |
||||
immediate: true |
||||
} |
||||
) |
||||
|
||||
onMounted(async () => { |
||||
await getDictOptions() |
||||
}) |
||||
</script> |
||||
@ -1,389 +0,0 @@ |
||||
<template> |
||||
<el-form ref="formRef" :model="formData" :rules="rules" label-width="150px"> |
||||
<el-row> |
||||
<el-col :span="12"> |
||||
<el-form-item label="生成模板" prop="templateType"> |
||||
<el-select v-model="formData.templateType" @change="tplSelectChange"> |
||||
<el-option |
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE)" |
||||
:key="dict.value" |
||||
:label="dict.label" |
||||
:value="dict.value" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-form-item label="前端类型" prop="frontType"> |
||||
<el-select v-model="formData.frontType"> |
||||
<el-option |
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_FRONT_TYPE)" |
||||
:key="dict.value" |
||||
:label="dict.label" |
||||
:value="dict.value" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
</el-col> |
||||
|
||||
<el-col :span="12"> |
||||
<el-form-item label="生成场景" prop="scene"> |
||||
<el-select v-model="formData.scene"> |
||||
<el-option |
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE)" |
||||
:key="dict.value" |
||||
:label="dict.label" |
||||
:value="dict.value" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-form-item> |
||||
<template #label> |
||||
<span> |
||||
上级菜单 |
||||
<el-tooltip content="分配到指定菜单下,例如 系统管理" placement="top"> |
||||
<Icon icon="ep:question-filled" /> |
||||
</el-tooltip> |
||||
</span> |
||||
</template> |
||||
<el-tree-select |
||||
v-model="formData.parentMenuId" |
||||
:data="menus" |
||||
:props="menuTreeProps" |
||||
check-strictly |
||||
node-key="id" |
||||
placeholder="请选择系统菜单" |
||||
/> |
||||
</el-form-item> |
||||
</el-col> |
||||
|
||||
<!-- <el-col :span="12">--> |
||||
<!-- <el-form-item prop="packageName">--> |
||||
<!-- <span slot="label">--> |
||||
<!-- 生成包路径--> |
||||
<!-- <el-tooltip content="生成在哪个java包下,例如 com.ruoyi.system" placement="top">--> |
||||
<!-- <i class="el-icon-question"></i>--> |
||||
<!-- </el-tooltip>--> |
||||
<!-- </span>--> |
||||
<!-- <el-input v-model="formData.packageName" />--> |
||||
<!-- </el-form-item>--> |
||||
<!-- </el-col>--> |
||||
|
||||
<el-col :span="12"> |
||||
<el-form-item prop="moduleName"> |
||||
<template #label> |
||||
<span> |
||||
模块名 |
||||
<el-tooltip |
||||
content="模块名,即一级目录,例如 system、infra、tool 等等" |
||||
placement="top" |
||||
> |
||||
<Icon icon="ep:question-filled" /> |
||||
</el-tooltip> |
||||
</span> |
||||
</template> |
||||
<el-input v-model="formData.moduleName" /> |
||||
</el-form-item> |
||||
</el-col> |
||||
|
||||
<el-col :span="12"> |
||||
<el-form-item prop="businessName"> |
||||
<template #label> |
||||
<span> |
||||
业务名 |
||||
<el-tooltip |
||||
content="业务名,即二级目录,例如 user、permission、dict 等等" |
||||
placement="top" |
||||
> |
||||
<Icon icon="ep:question-filled" /> |
||||
</el-tooltip> |
||||
</span> |
||||
</template> |
||||
<el-input v-model="formData.businessName" /> |
||||
</el-form-item> |
||||
</el-col> |
||||
|
||||
<!-- <el-col :span="12">--> |
||||
<!-- <el-form-item prop="businessPackage">--> |
||||
<!-- <span slot="label">--> |
||||
<!-- 业务包--> |
||||
<!-- <el-tooltip content="业务包,自定义二级目录。例如说,我们希望将 dictType 和 dictData 归类成 dict 业务" placement="top">--> |
||||
<!-- <i class="el-icon-question"></i>--> |
||||
<!-- </el-tooltip>--> |
||||
<!-- </span>--> |
||||
<!-- <el-input v-model="formData.businessPackage" />--> |
||||
<!-- </el-form-item>--> |
||||
<!-- </el-col>--> |
||||
|
||||
<el-col :span="12"> |
||||
<el-form-item prop="className"> |
||||
<template #label> |
||||
<span> |
||||
类名称 |
||||
<el-tooltip |
||||
content="类名称(首字母大写),例如SysUser、SysMenu、SysDictData 等等" |
||||
placement="top" |
||||
> |
||||
<Icon icon="ep:question-filled" /> |
||||
</el-tooltip> |
||||
</span> |
||||
</template> |
||||
<el-input v-model="formData.className" /> |
||||
</el-form-item> |
||||
</el-col> |
||||
|
||||
<el-col :span="12"> |
||||
<el-form-item prop="classComment"> |
||||
<template #label> |
||||
<span> |
||||
类描述 |
||||
<el-tooltip content="用作类描述,例如 用户" placement="top"> |
||||
<Icon icon="ep:question-filled" /> |
||||
</el-tooltip> |
||||
</span> |
||||
</template> |
||||
<el-input v-model="formData.classComment" /> |
||||
</el-form-item> |
||||
</el-col> |
||||
|
||||
<el-col v-if="formData.genType === '1'" :span="24"> |
||||
<el-form-item prop="genPath"> |
||||
<template #label> |
||||
<span> |
||||
自定义路径 |
||||
<el-tooltip |
||||
content="填写磁盘绝对路径,若不填写,则生成到当前Web项目下" |
||||
placement="top" |
||||
> |
||||
<Icon icon="ep:question-filled" /> |
||||
</el-tooltip> |
||||
</span> |
||||
</template> |
||||
<el-input v-model="formData.genPath"> |
||||
<template #append> |
||||
<el-dropdown> |
||||
<el-button type="primary"> |
||||
最近路径快速选择 |
||||
<i class="el-icon-arrow-down el-icon--right"></i> |
||||
</el-button> |
||||
<template #dropdown> |
||||
<el-dropdown-menu> |
||||
<el-dropdown-item @click="formData.genPath = '/'"> |
||||
恢复默认的生成基础路径 |
||||
</el-dropdown-item> |
||||
</el-dropdown-menu> |
||||
</template> |
||||
</el-dropdown> |
||||
</template> |
||||
</el-input> |
||||
</el-form-item> |
||||
</el-col> |
||||
</el-row> |
||||
|
||||
<el-row v-show="formData.tplCategory === 'tree'"> |
||||
<h4 class="form-header">其他信息</h4> |
||||
<el-col :span="12"> |
||||
<el-form-item> |
||||
<template #label> |
||||
<span> |
||||
树编码字段 |
||||
<el-tooltip content="树显示的编码字段名, 如:dept_id" placement="top"> |
||||
<Icon icon="ep:question-filled" /> |
||||
</el-tooltip> |
||||
</span> |
||||
</template> |
||||
<el-select v-model="formData.treeCode" placeholder="请选择"> |
||||
<el-option |
||||
v-for="(column, index) in formData.columns" |
||||
:key="index" |
||||
:label="column.columnName + ':' + column.columnComment" |
||||
:value="column.columnName" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-form-item> |
||||
<template #label> |
||||
<span> |
||||
树父编码字段 |
||||
<el-tooltip content="树显示的父编码字段名, 如:parent_Id" placement="top"> |
||||
<Icon icon="ep:question-filled" /> |
||||
</el-tooltip> |
||||
</span> |
||||
</template> |
||||
<el-select v-model="formData.treeParentCode" placeholder="请选择"> |
||||
<el-option |
||||
v-for="(column, index) in formData.columns" |
||||
:key="index" |
||||
:label="column.columnName + ':' + column.columnComment" |
||||
:value="column.columnName" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-form-item> |
||||
<template #label> |
||||
<span> |
||||
树名称字段 |
||||
<el-tooltip content="树节点的显示名称字段名, 如:dept_name" placement="top"> |
||||
<Icon icon="ep:question-filled" /> |
||||
</el-tooltip> |
||||
</span> |
||||
</template> |
||||
|
||||
<el-select v-model="formData.treeName" placeholder="请选择"> |
||||
<el-option |
||||
v-for="(column, index) in formData.columns" |
||||
:key="index" |
||||
:label="column.columnName + ':' + column.columnComment" |
||||
:value="column.columnName" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
</el-col> |
||||
</el-row> |
||||
<el-row v-show="formData.tplCategory === 'sub'"> |
||||
<h4 class="form-header">关联信息</h4> |
||||
<el-col :span="12"> |
||||
<el-form-item> |
||||
<template #label> |
||||
<span> |
||||
关联子表的表名 |
||||
<el-tooltip content="关联子表的表名, 如:sys_user" placement="top"> |
||||
<Icon icon="ep:question-filled" /> |
||||
</el-tooltip> |
||||
</span> |
||||
</template> |
||||
<el-select v-model="formData.subTableName" placeholder="请选择" @change="subSelectChange"> |
||||
<el-option |
||||
v-for="(table0, index) in tables" |
||||
:key="index" |
||||
:label="table0.tableName + ':' + table0.tableComment" |
||||
:value="table0.tableName" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-form-item> |
||||
<template #label> |
||||
<span> |
||||
子表关联的外键名 |
||||
<el-tooltip content="子表关联的外键名, 如:user_id" placement="top"> |
||||
<Icon icon="ep:question-filled" /> |
||||
</el-tooltip> |
||||
</span> |
||||
</template> |
||||
<el-select v-model="formData.subTableFkName" placeholder="请选择"> |
||||
<el-option |
||||
v-for="(column, index) in subColumns" |
||||
:key="index" |
||||
:label="column.columnName + ':' + column.columnComment" |
||||
:value="column.columnName" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
</el-col> |
||||
</el-row> |
||||
</el-form> |
||||
</template> |
||||
<script lang="ts" name="InfraCodegenGenerateInfoForm" setup> |
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
import { handleTree } from '@/utils/tree' |
||||
import * as CodegenApi from '@/api/infra/codegen' |
||||
import * as MenuApi from '@/api/system/menu' |
||||
import { PropType } from 'vue' |
||||
|
||||
const message = useMessage() // 消息弹窗 |
||||
const props = defineProps({ |
||||
table: { |
||||
type: Object as PropType<Nullable<CodegenApi.CodegenTableVO>>, |
||||
default: () => null |
||||
} |
||||
}) |
||||
|
||||
const formRef = ref() |
||||
const formData = ref({ |
||||
templateType: null, |
||||
frontType: null, |
||||
scene: null, |
||||
moduleName: '', |
||||
businessName: '', |
||||
className: '', |
||||
classComment: '', |
||||
parentMenuId: null, |
||||
genPath: '', |
||||
treeCode: '', |
||||
treeParentCode: '', |
||||
treeName: '', |
||||
tplCategory: '', |
||||
subTableName: '', |
||||
subTableFkName: '', |
||||
genType: '' |
||||
}) |
||||
|
||||
const rules = reactive({ |
||||
templateType: [required], |
||||
frontType: [required], |
||||
scene: [required], |
||||
moduleName: [required], |
||||
businessName: [required], |
||||
businessPackage: [required], |
||||
className: [required], |
||||
classComment: [required] |
||||
}) |
||||
|
||||
const tables = ref([]) |
||||
const subColumns = ref([]) |
||||
const menus = ref<any[]>([]) |
||||
const menuTreeProps = { |
||||
label: 'name' |
||||
} |
||||
|
||||
/** 选择子表名触发 */ |
||||
const subSelectChange = () => { |
||||
formData.value.subTableFkName = '' |
||||
} |
||||
|
||||
/** 选择生成模板触发 */ |
||||
const tplSelectChange = (value) => { |
||||
if (value !== 1) { |
||||
// TODO 芋艿:暂时不考虑支持树形结构 |
||||
message.error( |
||||
'暂时不考虑支持【树形】和【主子表】的代码生成。原因是:导致 vm 模板过于复杂,不利于胖友二次开发' |
||||
) |
||||
return false |
||||
} |
||||
if (value !== 'sub') { |
||||
formData.value.subTableName = '' |
||||
formData.value.subTableFkName = '' |
||||
} |
||||
} |
||||
|
||||
watch( |
||||
() => props.table, |
||||
(table) => { |
||||
if (!table) return |
||||
formData.value = table as any |
||||
}, |
||||
{ |
||||
deep: true, |
||||
immediate: true |
||||
} |
||||
) |
||||
|
||||
onMounted(async () => { |
||||
try { |
||||
const resp = await MenuApi.getSimpleMenusList() |
||||
menus.value = handleTree(resp) |
||||
} catch {} |
||||
}) |
||||
|
||||
defineExpose({ |
||||
validate: async () => unref(formRef)?.validate() |
||||
}) |
||||
</script> |
||||
@ -1,4 +0,0 @@ |
||||
import BasicInfoForm from './BasicInfoForm.vue' |
||||
import ColumInfoForm from './ColumInfoForm.vue' |
||||
import GenerateInfoForm from './GenerateInfoForm.vue' |
||||
export { BasicInfoForm, ColumInfoForm, GenerateInfoForm } |
||||
@ -1,254 +0,0 @@ |
||||
<template> |
||||
<doc-alert title="代码生成" url="https://doc.iocoder.cn/new-feature/" /> |
||||
<doc-alert title="单元测试" url="https://doc.iocoder.cn/unit-test/" /> |
||||
|
||||
<!-- 搜索 --> |
||||
<ContentWrap> |
||||
<el-form |
||||
ref="queryFormRef" |
||||
:inline="true" |
||||
:model="queryParams" |
||||
class="-mb-15px" |
||||
label-width="68px" |
||||
> |
||||
<el-form-item label="表名称" prop="tableName"> |
||||
<el-input |
||||
v-model="queryParams.tableName" |
||||
class="!w-240px" |
||||
clearable |
||||
placeholder="请输入表名称" |
||||
@keyup.enter="handleQuery" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="表描述" prop="tableComment"> |
||||
<el-input |
||||
v-model="queryParams.tableComment" |
||||
class="!w-240px" |
||||
clearable |
||||
placeholder="请输入表描述" |
||||
@keyup.enter="handleQuery" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="创建时间" prop="createTime"> |
||||
<el-date-picker |
||||
v-model="queryParams.createTime" |
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
||||
class="!w-240px" |
||||
end-placeholder="结束日期" |
||||
start-placeholder="开始日期" |
||||
type="daterange" |
||||
value-format="YYYY-MM-dd HH:mm:ss" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item> |
||||
<el-button @click="handleQuery"> |
||||
<Icon class="mr-5px" icon="ep:search" /> |
||||
搜索 |
||||
</el-button> |
||||
<el-button @click="resetQuery"> |
||||
<Icon class="mr-5px" icon="ep:refresh" /> |
||||
重置 |
||||
</el-button> |
||||
<el-button v-hasPermi="['infra:codegen:create']" type="primary" @click="openImportTable()"> |
||||
<Icon class="mr-5px" icon="ep:zoom-in" /> |
||||
导入 |
||||
</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
</ContentWrap> |
||||
|
||||
<!-- 列表 --> |
||||
<ContentWrap> |
||||
<el-table v-loading="loading" :data="list"> |
||||
<el-table-column align="center" label="数据源"> |
||||
<template #default="scope"> |
||||
{{ |
||||
dataSourceConfigList.find((config) => config.id === scope.row.dataSourceConfigId)?.name |
||||
}} |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column align="center" label="表名称" prop="tableName" width="200" /> |
||||
<el-table-column |
||||
:show-overflow-tooltip="true" |
||||
align="center" |
||||
label="表描述" |
||||
prop="tableComment" |
||||
width="200" |
||||
/> |
||||
<el-table-column align="center" label="实体" prop="className" width="200" /> |
||||
<el-table-column |
||||
:formatter="dateFormatter" |
||||
align="center" |
||||
label="创建时间" |
||||
prop="createTime" |
||||
width="180" |
||||
/> |
||||
<el-table-column |
||||
:formatter="dateFormatter" |
||||
align="center" |
||||
label="更新时间" |
||||
prop="createTime" |
||||
width="180" |
||||
/> |
||||
<el-table-column align="center" fixed="right" label="操作" width="300px"> |
||||
<template #default="scope"> |
||||
<el-button |
||||
v-hasPermi="['infra:codegen:preview']" |
||||
link |
||||
type="primary" |
||||
@click="handlePreview(scope.row)" |
||||
> |
||||
预览 |
||||
</el-button> |
||||
<el-button |
||||
v-hasPermi="['infra:codegen:update']" |
||||
link |
||||
type="primary" |
||||
@click="handleUpdate(scope.row.id)" |
||||
> |
||||
编辑 |
||||
</el-button> |
||||
<el-button |
||||
v-hasPermi="['infra:codegen:delete']" |
||||
link |
||||
type="danger" |
||||
@click="handleDelete(scope.row.id)" |
||||
> |
||||
删除 |
||||
</el-button> |
||||
<el-button |
||||
v-hasPermi="['infra:codegen:update']" |
||||
link |
||||
type="primary" |
||||
@click="handleSyncDB(scope.row)" |
||||
> |
||||
同步 |
||||
</el-button> |
||||
<el-button |
||||
v-hasPermi="['infra:codegen:download']" |
||||
link |
||||
type="primary" |
||||
@click="handleGenTable(scope.row)" |
||||
> |
||||
生成代码 |
||||
</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
<!-- 分页 --> |
||||
<Pagination |
||||
v-model:limit="queryParams.pageSize" |
||||
v-model:page="queryParams.pageNo" |
||||
:total="total" |
||||
@pagination="getList" |
||||
/> |
||||
</ContentWrap> |
||||
|
||||
<!-- 弹窗:导入表 --> |
||||
<ImportTable ref="importRef" @success="getList" /> |
||||
<!-- 弹窗:预览代码 --> |
||||
<PreviewCode ref="previewRef" /> |
||||
</template> |
||||
<script lang="ts" name="InfraCodegen" setup> |
||||
import { dateFormatter } from '@/utils/formatTime' |
||||
import download from '@/utils/download' |
||||
import * as CodegenApi from '@/api/infra/codegen' |
||||
import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig' |
||||
import ImportTable from './ImportTable.vue' |
||||
import PreviewCode from './PreviewCode.vue' |
||||
|
||||
const message = useMessage() // 消息弹窗 |
||||
const { t } = useI18n() // 国际化 |
||||
const { push } = useRouter() // 路由跳转 |
||||
|
||||
const loading = ref(true) // 列表的加载中 |
||||
const total = ref(0) // 列表的总页数 |
||||
const list = ref([]) // 列表的数据 |
||||
const queryParams = reactive({ |
||||
pageNo: 1, |
||||
pageSize: 10, |
||||
tableName: undefined, |
||||
tableComment: undefined, |
||||
createTime: [] |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
const dataSourceConfigList = ref<DataSourceConfigApi.DataSourceConfigVO[]>([]) // 数据源列表 |
||||
|
||||
/** 查询列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
const data = await CodegenApi.getCodegenTablePage(queryParams) |
||||
list.value = data.list |
||||
total.value = data.total |
||||
} finally { |
||||
loading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 搜索按钮操作 */ |
||||
const handleQuery = () => { |
||||
queryParams.pageNo = 1 |
||||
getList() |
||||
} |
||||
|
||||
/** 重置按钮操作 */ |
||||
const resetQuery = () => { |
||||
queryFormRef.value.resetFields() |
||||
handleQuery() |
||||
} |
||||
|
||||
/** 导入操作 */ |
||||
const importRef = ref() |
||||
const openImportTable = () => { |
||||
importRef.value.open() |
||||
} |
||||
|
||||
/** 编辑操作 */ |
||||
const handleUpdate = (id: number) => { |
||||
push('/codegen/edit?id=' + id) |
||||
} |
||||
|
||||
/** 预览操作 */ |
||||
const previewRef = ref() |
||||
const handlePreview = (row: CodegenApi.CodegenTableVO) => { |
||||
previewRef.value.open(row.id) |
||||
} |
||||
|
||||
/** 删除按钮操作 */ |
||||
const handleDelete = async (id: number) => { |
||||
try { |
||||
// 删除的二次确认 |
||||
await message.delConfirm() |
||||
// 发起删除 |
||||
await CodegenApi.deleteCodegenTable(id) |
||||
message.success(t('common.delSuccess')) |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch {} |
||||
} |
||||
|
||||
/** 同步操作 */ |
||||
const handleSyncDB = async (row: CodegenApi.CodegenTableVO) => { |
||||
// 基于 DB 同步 |
||||
const tableName = row.tableName |
||||
try { |
||||
await message.confirm('确认要强制同步' + tableName + '表结构吗?', t('common.reminder')) |
||||
await CodegenApi.syncCodegenFromDB(row.id) |
||||
message.success('同步成功') |
||||
} catch {} |
||||
} |
||||
|
||||
/** 生成代码操作 */ |
||||
const handleGenTable = async (row: CodegenApi.CodegenTableVO) => { |
||||
const res = await CodegenApi.downloadCodegen(row.id) |
||||
download.zip(res, 'codegen-' + row.className + '.zip') |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(async () => { |
||||
await getList() |
||||
// 加载数据源列表 |
||||
dataSourceConfigList.value = await DataSourceConfigApi.getDataSourceConfigList() |
||||
}) |
||||
</script> |
||||
@ -1,129 +0,0 @@ |
||||
<template> |
||||
<Dialog v-model="dialogVisible" :title="dialogTitle"> |
||||
<el-form |
||||
ref="formRef" |
||||
v-loading="formLoading" |
||||
:model="formData" |
||||
:rules="formRules" |
||||
label-width="80px" |
||||
> |
||||
<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 getBoolDictOptions(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" placeholder="请输入内容" type="textarea" /> |
||||
</el-form-item> |
||||
</el-form> |
||||
<template #footer> |
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> |
||||
<el-button @click="dialogVisible = false">取 消</el-button> |
||||
</template> |
||||
</Dialog> |
||||
</template> |
||||
<script lang="ts" name="InfraConfigForm" setup> |
||||
import { DICT_TYPE, getBoolDictOptions } from '@/utils/dict' |
||||
import * as ConfigApi from '@/api/infra/config' |
||||
|
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息弹窗 |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const dialogTitle = 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 open = async (type: string, id?: number) => { |
||||
dialogVisible.value = true |
||||
dialogTitle.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({ 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 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')) |
||||
} |
||||
dialogVisible.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> |
||||
@ -1,225 +0,0 @@ |
||||
<template> |
||||
<doc-alert title="配置中心" url="https://doc.iocoder.cn/config-center/" /> |
||||
|
||||
<!-- 搜索 --> |
||||
<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="key"> |
||||
<el-input |
||||
v-model="queryParams.key" |
||||
placeholder="请输入参数键名" |
||||
clearable |
||||
@keyup.enter="handleQuery" |
||||
class="!w-240px" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="系统内置" prop="type"> |
||||
<el-select |
||||
v-model="queryParams.type" |
||||
placeholder="请选择系统内置" |
||||
clearable |
||||
class="!w-240px" |
||||
> |
||||
<el-option |
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_CONFIG_TYPE)" |
||||
: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" |
||||
plain |
||||
@click="openForm('create')" |
||||
v-hasPermi="['infra:config:create']" |
||||
> |
||||
<Icon icon="ep:plus" 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="category" /> |
||||
<el-table-column label="参数名称" align="center" prop="name" :show-overflow-tooltip="true" /> |
||||
<el-table-column label="参数键名" align="center" prop="key" :show-overflow-tooltip="true" /> |
||||
<el-table-column label="参数键值" align="center" prop="value" /> |
||||
<el-table-column label="是否可见" align="center" prop="visible"> |
||||
<template #default="scope"> |
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.visible" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="系统内置" align="center" prop="type"> |
||||
<template #default="scope"> |
||||
<dict-tag :type="DICT_TYPE.INFRA_CONFIG_TYPE" :value="scope.row.type" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" /> |
||||
<el-table-column |
||||
label="创建时间" |
||||
align="center" |
||||
prop="createTime" |
||||
width="180" |
||||
:formatter="dateFormatter" |
||||
/> |
||||
<el-table-column label="操作" align="center"> |
||||
<template #default="scope"> |
||||
<el-button |
||||
link |
||||
type="primary" |
||||
@click="openForm('update', scope.row.id)" |
||||
v-hasPermi="['infra:config:update']" |
||||
> |
||||
编辑 |
||||
</el-button> |
||||
<el-button |
||||
link |
||||
type="danger" |
||||
@click="handleDelete(scope.row.id)" |
||||
v-hasPermi="['infra:config:delete']" |
||||
> |
||||
删除 |
||||
</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
<!-- 分页 --> |
||||
<Pagination |
||||
:total="total" |
||||
v-model:page="queryParams.pageNo" |
||||
v-model:limit="queryParams.pageSize" |
||||
@pagination="getList" |
||||
/> |
||||
</ContentWrap> |
||||
|
||||
<!-- 表单弹窗:添加/修改 --> |
||||
<ConfigForm ref="formRef" @success="getList" /> |
||||
</template> |
||||
<script setup lang="ts" name="InfraConfig"> |
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
import { dateFormatter } from '@/utils/formatTime' |
||||
import download from '@/utils/download' |
||||
import * as ConfigApi from '@/api/infra/config' |
||||
import ConfigForm from './ConfigForm.vue' |
||||
const message = useMessage() // 消息弹窗 |
||||
const { t } = useI18n() // 国际化 |
||||
|
||||
const loading = ref(true) // 列表的加载中 |
||||
const total = ref(0) // 列表的总页数 |
||||
const list = ref([]) // 列表的数据 |
||||
const queryParams = reactive({ |
||||
pageNo: 1, |
||||
pageSize: 10, |
||||
name: undefined, |
||||
key: undefined, |
||||
type: undefined, |
||||
createTime: [] |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
const exportLoading = ref(false) // 导出的加载中 |
||||
|
||||
/** 查询列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
const data = await ConfigApi.getConfigPage(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 formRef = ref() |
||||
const openForm = (type: string, id?: number) => { |
||||
formRef.value.open(type, id) |
||||
} |
||||
|
||||
/** 删除按钮操作 */ |
||||
const handleDelete = async (id: number) => { |
||||
try { |
||||
// 删除的二次确认 |
||||
await message.delConfirm() |
||||
// 发起删除 |
||||
await ConfigApi.deleteConfig(id) |
||||
message.success(t('common.delSuccess')) |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch {} |
||||
} |
||||
|
||||
/** 导出按钮操作 */ |
||||
const handleExport = async () => { |
||||
try { |
||||
// 导出的二次确认 |
||||
await message.exportConfirm() |
||||
// 发起导出 |
||||
exportLoading.value = true |
||||
const data = await ConfigApi.exportConfig(queryParams) |
||||
download.excel(data, '参数配置.xls') |
||||
} catch { |
||||
} finally { |
||||
exportLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,109 +0,0 @@ |
||||
<template> |
||||
<Dialog v-model="dialogVisible" :title="dialogTitle"> |
||||
<el-form |
||||
ref="formRef" |
||||
v-loading="formLoading" |
||||
:model="formData" |
||||
:rules="formRules" |
||||
label-width="100px" |
||||
> |
||||
<el-form-item label="数据源名称" prop="name"> |
||||
<el-input v-model="formData.name" placeholder="请输入参数名称" /> |
||||
</el-form-item> |
||||
<el-form-item label="数据源连接" prop="url"> |
||||
<el-input v-model="formData.url" placeholder="请输入数据源连接" /> |
||||
</el-form-item> |
||||
<el-form-item label="用户名" prop="username"> |
||||
<el-input v-model="formData.username" placeholder="请输入用户名" /> |
||||
</el-form-item> |
||||
<el-form-item label="密码" prop="password"> |
||||
<el-input v-model="formData.password" placeholder="请输入密码" /> |
||||
</el-form-item> |
||||
</el-form> |
||||
<template #footer> |
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> |
||||
<el-button @click="dialogVisible = false">取 消</el-button> |
||||
</template> |
||||
</Dialog> |
||||
</template> |
||||
<script lang="ts" name="InfraDataSourceConfigForm" setup> |
||||
import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig' |
||||
|
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息弹窗 |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const dialogTitle = ref('') // 弹窗的标题 |
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
||||
const formData = ref<DataSourceConfigApi.DataSourceConfigVO>({ |
||||
id: undefined, |
||||
name: '', |
||||
url: '', |
||||
username: '', |
||||
password: '' |
||||
}) |
||||
const formRules = reactive({ |
||||
name: [{ required: true, message: '数据源名称不能为空', trigger: 'blur' }], |
||||
url: [{ required: true, message: '数据源连接不能为空', trigger: 'blur' }], |
||||
username: [{ required: true, message: '用户名不能为空', trigger: 'blur' }], |
||||
password: [{ required: true, message: '密码不能为空', trigger: 'blur' }] |
||||
}) |
||||
const formRef = ref() // 表单 Ref |
||||
|
||||
/** 打开弹窗 */ |
||||
const open = async (type: string, id?: number) => { |
||||
dialogVisible.value = true |
||||
dialogTitle.value = t('action.' + type) |
||||
formType.value = type |
||||
resetForm() |
||||
// 修改时,设置数据 |
||||
if (id) { |
||||
formLoading.value = true |
||||
try { |
||||
formData.value = await DataSourceConfigApi.getDataSourceConfig(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 DataSourceConfigApi.DataSourceConfigVO |
||||
if (formType.value === 'create') { |
||||
await DataSourceConfigApi.createDataSourceConfig(data) |
||||
message.success(t('common.createSuccess')) |
||||
} else { |
||||
await DataSourceConfigApi.updateDataSourceConfig(data) |
||||
message.success(t('common.updateSuccess')) |
||||
} |
||||
dialogVisible.value = false |
||||
// 发送操作成功的事件 |
||||
emit('success') |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 重置表单 */ |
||||
const resetForm = () => { |
||||
formData.value = { |
||||
id: undefined, |
||||
name: '', |
||||
url: '', |
||||
username: '', |
||||
password: '' |
||||
} |
||||
formRef.value?.resetFields() |
||||
} |
||||
</script> |
||||
@ -1,103 +0,0 @@ |
||||
<template> |
||||
<ContentWrap> |
||||
<!-- 搜索工作栏 --> |
||||
<el-form class="-mb-15px" :inline="true"> |
||||
<el-form-item> |
||||
<el-button |
||||
type="primary" |
||||
plain |
||||
@click="openForm('create')" |
||||
v-hasPermi="['infra:data-source-config:create']" |
||||
> |
||||
<Icon icon="ep:plus" 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="name" /> |
||||
<el-table-column label="数据源连接" align="center" prop="url" :show-overflow-tooltip="true" /> |
||||
<el-table-column label="用户名" align="center" prop="username" /> |
||||
<el-table-column |
||||
label="创建时间" |
||||
align="center" |
||||
prop="createTime" |
||||
width="180" |
||||
:formatter="dateFormatter" |
||||
/> |
||||
<el-table-column label="操作" align="center"> |
||||
<template #default="scope"> |
||||
<el-button |
||||
link |
||||
type="primary" |
||||
@click="openForm('update', scope.row.id)" |
||||
v-hasPermi="['infra:data-source-config:update']" |
||||
:disabled="scope.row.id === 0" |
||||
> |
||||
编辑 |
||||
</el-button> |
||||
<el-button |
||||
link |
||||
type="danger" |
||||
@click="handleDelete(scope.row.id)" |
||||
v-hasPermi="['infra:data-source-config:delete']" |
||||
:disabled="scope.row.id === 0" |
||||
> |
||||
删除 |
||||
</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
</ContentWrap> |
||||
|
||||
<!-- 表单弹窗:添加/修改 --> |
||||
<DataSourceConfigForm ref="formRef" @success="getList" /> |
||||
</template> |
||||
<script setup lang="ts" name="InfraDataSourceConfig"> |
||||
import { dateFormatter } from '@/utils/formatTime' |
||||
import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig' |
||||
import DataSourceConfigForm from './DataSourceConfigForm.vue' |
||||
const message = useMessage() // 消息弹窗 |
||||
const { t } = useI18n() // 国际化 |
||||
|
||||
const loading = ref(true) // 列表的加载中 |
||||
const list = ref([]) // 列表的数据 |
||||
|
||||
/** 查询列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
list.value = await DataSourceConfigApi.getDataSourceConfigList() |
||||
} finally { |
||||
loading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 添加/修改操作 */ |
||||
const formRef = ref() |
||||
const openForm = (type: string, id?: number) => { |
||||
formRef.value.open(type, id) |
||||
} |
||||
|
||||
/** 删除按钮操作 */ |
||||
const handleDelete = async (id: number) => { |
||||
try { |
||||
// 删除的二次确认 |
||||
await message.delConfirm() |
||||
// 发起删除 |
||||
await DataSourceConfigApi.deleteDataSourceConfig(id) |
||||
message.success(t('common.delSuccess')) |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch {} |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,57 +0,0 @@ |
||||
<template> |
||||
<doc-alert title="数据库文档" url="https://doc.iocoder.cn/db-doc/" /> |
||||
|
||||
<ContentWrap title="数据库文档"> |
||||
<div class="mb-10px"> |
||||
<el-button type="primary" plain @click="handleExport('HTML')"> |
||||
<Icon icon="ep:download" /> 导出 HTML |
||||
</el-button> |
||||
<el-button type="primary" plain @click="handleExport('Word')"> |
||||
<Icon icon="ep:download" /> 导出 Word |
||||
</el-button> |
||||
<el-button type="primary" plain @click="handleExport('Markdown')"> |
||||
<Icon icon="ep:download" /> 导出 Markdown |
||||
</el-button> |
||||
</div> |
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" /> |
||||
</ContentWrap> |
||||
</template> |
||||
<script setup lang="ts" name="InfraDBDoc"> |
||||
import download from '@/utils/download' |
||||
import * as DbDocApi from '@/api/infra/dbDoc' |
||||
|
||||
const loading = ref(true) // 是否加载中 |
||||
const src = ref('') // HTML 的地址 |
||||
|
||||
/** 页面加载 */ |
||||
const init = async () => { |
||||
try { |
||||
const data = await DbDocApi.exportHtml() |
||||
const blob = new Blob([data], { type: 'text/html' }) |
||||
src.value = window.URL.createObjectURL(blob) |
||||
} finally { |
||||
loading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 处理导出 */ |
||||
const handleExport = async (type: string) => { |
||||
if (type === 'HTML') { |
||||
const res = await DbDocApi.exportHtml() |
||||
download.html(res, '数据库文档.html') |
||||
} |
||||
if (type === 'Word') { |
||||
const res = await DbDocApi.exportWord() |
||||
download.word(res, '数据库文档.doc') |
||||
} |
||||
if (type === 'Markdown') { |
||||
const res = await DbDocApi.exportMarkdown() |
||||
download.markdown(res, '数据库文档.md') |
||||
} |
||||
} |
||||
|
||||
/** 初始化 */ |
||||
onMounted(async () => { |
||||
await init() |
||||
}) |
||||
</script> |
||||
@ -1,26 +0,0 @@ |
||||
<template> |
||||
<doc-alert title="数据库 MyBatis" url="https://doc.iocoder.cn/mybatis/" /> |
||||
<doc-alert title="多数据源(读写分离)" url="https://doc.iocoder.cn/dynamic-datasource/" /> |
||||
|
||||
<ContentWrap> |
||||
<IFrame v-if="!loading" :src="url" /> |
||||
</ContentWrap> |
||||
</template> |
||||
<script setup lang="ts" name="InfraDruid"> |
||||
import * as ConfigApi from '@/api/infra/config' |
||||
|
||||
const loading = ref(true) // 是否加载中 |
||||
const url = ref(import.meta.env.VITE_BASE_URL + '/druid/index.html') |
||||
|
||||
/** 初始化 */ |
||||
onMounted(async () => { |
||||
try { |
||||
const data = await ConfigApi.getConfigKey('url.druid') |
||||
if (data && data.length > 0) { |
||||
url.value = data |
||||
} |
||||
} finally { |
||||
loading.value = false |
||||
} |
||||
}) |
||||
</script> |
||||
@ -1,102 +0,0 @@ |
||||
<template> |
||||
<Dialog v-model="dialogVisible" title="上传文件"> |
||||
<el-upload |
||||
ref="uploadRef" |
||||
v-model:file-list="fileList" |
||||
:action="url" |
||||
:auto-upload="false" |
||||
:data="data" |
||||
:disabled="formLoading" |
||||
:headers="uploadHeaders" |
||||
:limit="1" |
||||
:on-change="handleFileChange" |
||||
:on-error="submitFormError" |
||||
:on-exceed="handleExceed" |
||||
:on-success="submitFormSuccess" |
||||
accept=".jpg, .png, .gif" |
||||
drag |
||||
> |
||||
<i class="el-icon-upload"></i> |
||||
<div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em></div> |
||||
<template #tip> |
||||
<div class="el-upload__tip" style="color: red"> |
||||
提示:仅允许导入 jpg、png、gif 格式文件! |
||||
</div> |
||||
</template> |
||||
</el-upload> |
||||
<template #footer> |
||||
<el-button :disabled="formLoading" type="primary" @click="submitFileForm">确 定</el-button> |
||||
<el-button @click="dialogVisible = false">取 消</el-button> |
||||
</template> |
||||
</Dialog> |
||||
</template> |
||||
<script lang="ts" name="InfraFileForm" setup> |
||||
import { getAccessToken, getTenantId } from '@/utils/auth' |
||||
|
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息弹窗 |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const formLoading = ref(false) // 表单的加载中 |
||||
const url = import.meta.env.VITE_UPLOAD_URL |
||||
const uploadHeaders = ref() // 上传 Header 头 |
||||
const fileList = ref([]) // 文件列表 |
||||
const data = ref({ path: '' }) |
||||
const uploadRef = ref() |
||||
|
||||
/** 打开弹窗 */ |
||||
const open = async () => { |
||||
dialogVisible.value = true |
||||
resetForm() |
||||
} |
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
||||
|
||||
/** 处理上传的文件发生变化 */ |
||||
const handleFileChange = (file) => { |
||||
data.value.path = file.name |
||||
} |
||||
|
||||
/** 提交表单 */ |
||||
const submitFileForm = () => { |
||||
if (fileList.value.length == 0) { |
||||
message.error('请上传文件') |
||||
return |
||||
} |
||||
// 提交请求 |
||||
uploadHeaders.value = { |
||||
Authorization: 'Bearer ' + getAccessToken(), |
||||
'tenant-id': getTenantId() |
||||
} |
||||
unref(uploadRef)?.submit() |
||||
} |
||||
|
||||
/** 文件上传成功处理 */ |
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |
||||
const submitFormSuccess = () => { |
||||
// 清理 |
||||
dialogVisible.value = false |
||||
formLoading.value = false |
||||
unref(uploadRef)?.clearFiles() |
||||
// 提示成功,并刷新 |
||||
message.success(t('common.createSuccess')) |
||||
emit('success') |
||||
} |
||||
|
||||
/** 上传错误提示 */ |
||||
const submitFormError = (): void => { |
||||
message.error('上传失败,请您重新上传!') |
||||
formLoading.value = false |
||||
} |
||||
|
||||
/** 重置表单 */ |
||||
const resetForm = () => { |
||||
// 重置上传状态和文件 |
||||
formLoading.value = false |
||||
uploadRef.value?.clearFiles() |
||||
} |
||||
|
||||
/** 文件数超出提示 */ |
||||
const handleExceed = (): void => { |
||||
message.error('最多只能上传一个文件!') |
||||
} |
||||
</script> |
||||
@ -1,161 +0,0 @@ |
||||
<template> |
||||
<doc-alert title="上传下载" url="https://doc.iocoder.cn/file/" /> |
||||
<!-- 搜索 --> |
||||
<ContentWrap> |
||||
<el-form |
||||
class="-mb-15px" |
||||
:model="queryParams" |
||||
ref="queryFormRef" |
||||
:inline="true" |
||||
label-width="68px" |
||||
> |
||||
<el-form-item label="文件路径" prop="path"> |
||||
<el-input |
||||
v-model="queryParams.path" |
||||
placeholder="请输入文件路径" |
||||
clearable |
||||
@keyup.enter="handleQuery" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="文件类型" prop="type" width="80"> |
||||
<el-input |
||||
v-model="queryParams.type" |
||||
placeholder="请输入文件类型" |
||||
clearable |
||||
@keyup.enter="handleQuery" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="创建时间" prop="createTime"> |
||||
<el-date-picker |
||||
v-model="queryParams.createTime" |
||||
value-format="YYYY-MM-DD HH:mm:ss" |
||||
type="daterange" |
||||
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" plain @click="openForm"> |
||||
<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="name" :show-overflow-tooltip="true" /> |
||||
<el-table-column label="文件路径" align="center" prop="path" :show-overflow-tooltip="true" /> |
||||
<el-table-column label="URL" align="center" prop="url" :show-overflow-tooltip="true" /> |
||||
<el-table-column |
||||
label="文件大小" |
||||
align="center" |
||||
prop="size" |
||||
width="120" |
||||
:formatter="fileSizeFormatter" |
||||
/> |
||||
<el-table-column label="文件类型" align="center" prop="type" width="180px" /> |
||||
<el-table-column |
||||
label="上传时间" |
||||
align="center" |
||||
prop="createTime" |
||||
width="180" |
||||
:formatter="dateFormatter" |
||||
/> |
||||
<el-table-column label="操作" align="center"> |
||||
<template #default="scope"> |
||||
<el-button |
||||
link |
||||
type="danger" |
||||
@click="handleDelete(scope.row.id)" |
||||
v-hasPermi="['infra:config:delete']" |
||||
> |
||||
删除 |
||||
</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
<!-- 分页 --> |
||||
<Pagination |
||||
:total="total" |
||||
v-model:page="queryParams.pageNo" |
||||
v-model:limit="queryParams.pageSize" |
||||
@pagination="getList" |
||||
/> |
||||
</ContentWrap> |
||||
|
||||
<!-- 表单弹窗:添加/修改 --> |
||||
<FileForm ref="formRef" @success="getList" /> |
||||
</template> |
||||
<script setup lang="ts" name="InfraFile"> |
||||
import { fileSizeFormatter } from '@/utils' |
||||
import { dateFormatter } from '@/utils/formatTime' |
||||
import * as FileApi from '@/api/infra/file' |
||||
import FileForm from './FileForm.vue' |
||||
const message = useMessage() // 消息弹窗 |
||||
const { t } = useI18n() // 国际化 |
||||
|
||||
const loading = ref(true) // 列表的加载中 |
||||
const total = ref(0) // 列表的总页数 |
||||
const list = ref([]) // 列表的数据 |
||||
const queryParams = reactive({ |
||||
pageNo: 1, |
||||
pageSize: 10, |
||||
name: undefined, |
||||
type: undefined, |
||||
createTime: [] |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
|
||||
/** 查询列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
const data = await FileApi.getFilePage(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 formRef = ref() |
||||
const openForm = () => { |
||||
formRef.value.open() |
||||
} |
||||
|
||||
/** 删除按钮操作 */ |
||||
const handleDelete = async (id: number) => { |
||||
try { |
||||
// 删除的二次确认 |
||||
await message.delConfirm() |
||||
// 发起删除 |
||||
await FileApi.deleteFile(id) |
||||
message.success(t('common.delSuccess')) |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch {} |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,193 +0,0 @@ |
||||
<template> |
||||
<Dialog v-model="dialogVisible" :title="dialogTitle"> |
||||
<el-form |
||||
ref="formRef" |
||||
v-loading="formLoading" |
||||
:model="formData" |
||||
:rules="formRules" |
||||
label-width="120px" |
||||
> |
||||
<el-form-item label="配置名" prop="name"> |
||||
<el-input v-model="formData.name" placeholder="请输入配置名" /> |
||||
</el-form-item> |
||||
<el-form-item label="备注" prop="remark"> |
||||
<el-input v-model="formData.remark" placeholder="请输入备注" /> |
||||
</el-form-item> |
||||
<el-form-item label="存储器" prop="storage"> |
||||
<el-select |
||||
v-model="formData.storage" |
||||
:disabled="formData.id !== undefined" |
||||
placeholder="请选择存储器" |
||||
> |
||||
<el-option |
||||
v-for="dict in getDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)" |
||||
:key="dict.value" |
||||
:label="dict.label" |
||||
:value="parseInt(dict.value)" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
<!-- DB --> |
||||
<!-- Local / FTP / SFTP --> |
||||
<el-form-item |
||||
v-if="formData.storage >= 10 && formData.storage <= 12" |
||||
label="基础路径" |
||||
prop="config.basePath" |
||||
> |
||||
<el-input v-model="formData.config.basePath" placeholder="请输入基础路径" /> |
||||
</el-form-item> |
||||
<el-form-item |
||||
v-if="formData.storage >= 11 && formData.storage <= 12" |
||||
label="主机地址" |
||||
prop="config.host" |
||||
> |
||||
<el-input v-model="formData.config.host" placeholder="请输入主机地址" /> |
||||
</el-form-item> |
||||
<el-form-item |
||||
v-if="formData.storage >= 11 && formData.storage <= 12" |
||||
label="主机端口" |
||||
prop="config.port" |
||||
> |
||||
<el-input-number v-model="formData.config.port" :min="0" placeholder="请输入主机端口" /> |
||||
</el-form-item> |
||||
<el-form-item |
||||
v-if="formData.storage >= 11 && formData.storage <= 12" |
||||
label="用户名" |
||||
prop="config.username" |
||||
> |
||||
<el-input v-model="formData.config.username" placeholder="请输入密码" /> |
||||
</el-form-item> |
||||
<el-form-item |
||||
v-if="formData.storage >= 11 && formData.storage <= 12" |
||||
label="密码" |
||||
prop="config.password" |
||||
> |
||||
<el-input v-model="formData.config.password" placeholder="请输入密码" /> |
||||
</el-form-item> |
||||
<el-form-item v-if="formData.storage === 11" label="连接模式" prop="config.mode"> |
||||
<el-radio-group v-model="formData.config.mode"> |
||||
<el-radio key="Active" label="Active">主动模式</el-radio> |
||||
<el-radio key="Passive" label="Passive">主动模式</el-radio> |
||||
</el-radio-group> |
||||
</el-form-item> |
||||
<!-- S3 --> |
||||
<el-form-item v-if="formData.storage === 20" label="节点地址" prop="config.endpoint"> |
||||
<el-input v-model="formData.config.endpoint" placeholder="请输入节点地址" /> |
||||
</el-form-item> |
||||
<el-form-item v-if="formData.storage === 20" label="存储 bucket" prop="config.bucket"> |
||||
<el-input v-model="formData.config.bucket" placeholder="请输入 bucket" /> |
||||
</el-form-item> |
||||
<el-form-item v-if="formData.storage === 20" label="accessKey" prop="config.accessKey"> |
||||
<el-input v-model="formData.config.accessKey" placeholder="请输入 accessKey" /> |
||||
</el-form-item> |
||||
<el-form-item v-if="formData.storage === 20" label="accessSecret" prop="config.accessSecret"> |
||||
<el-input v-model="formData.config.accessSecret" placeholder="请输入 accessSecret" /> |
||||
</el-form-item> |
||||
<!-- 通用 --> |
||||
<el-form-item v-if="formData.storage === 20" label="自定义域名"> |
||||
<!-- 无需参数校验,所以去掉 prop --> |
||||
<el-input v-model="formData.config.domain" placeholder="请输入自定义域名" /> |
||||
</el-form-item> |
||||
<el-form-item v-else-if="formData.storage" label="自定义域名" prop="config.domain"> |
||||
<el-input v-model="formData.config.domain" placeholder="请输入自定义域名" /> |
||||
</el-form-item> |
||||
</el-form> |
||||
<template #footer> |
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> |
||||
<el-button @click="dialogVisible = false">取 消</el-button> |
||||
</template> |
||||
</Dialog> |
||||
</template> |
||||
<script lang="ts" name="InfraFileConfigForm" setup> |
||||
import { DICT_TYPE, getDictOptions } from '@/utils/dict' |
||||
import * as FileConfigApi from '@/api/infra/fileConfig' |
||||
|
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息弹窗 |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const dialogTitle = ref('') // 弹窗的标题 |
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
||||
const formData = ref({ |
||||
id: undefined, |
||||
name: '', |
||||
storage: '', |
||||
remark: '', |
||||
config: {} |
||||
}) |
||||
const formRules = reactive({ |
||||
name: [{ required: true, message: '配置名不能为空', trigger: 'blur' }], |
||||
storage: [{ required: true, message: '存储器不能为空', trigger: 'change' }], |
||||
config: { |
||||
basePath: [{ required: true, message: '基础路径不能为空', trigger: 'blur' }], |
||||
host: [{ required: true, message: '主机地址不能为空', trigger: 'blur' }], |
||||
port: [{ required: true, message: '主机端口不能为空', trigger: 'blur' }], |
||||
username: [{ required: true, message: '用户名不能为空', trigger: 'blur' }], |
||||
password: [{ required: true, message: '密码不能为空', trigger: 'blur' }], |
||||
mode: [{ required: true, message: '连接模式不能为空', trigger: 'change' }], |
||||
endpoint: [{ required: true, message: '节点地址不能为空', trigger: 'blur' }], |
||||
bucket: [{ required: true, message: '存储 bucket 不能为空', trigger: 'blur' }], |
||||
accessKey: [{ required: true, message: 'accessKey 不能为空', trigger: 'blur' }], |
||||
accessSecret: [{ required: true, message: 'accessSecret 不能为空', trigger: 'blur' }], |
||||
domain: [{ required: true, message: '自定义域名不能为空', trigger: 'blur' }] |
||||
} |
||||
}) |
||||
const formRef = ref() // 表单 Ref |
||||
|
||||
/** 打开弹窗 */ |
||||
const open = async (type: string, id?: number) => { |
||||
dialogVisible.value = true |
||||
dialogTitle.value = t('action.' + type) |
||||
formType.value = type |
||||
resetForm() |
||||
// 修改时,设置数据 |
||||
if (id) { |
||||
formLoading.value = true |
||||
try { |
||||
formData.value = await FileConfigApi.getFileConfig(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 FileConfigApi.FileConfigVO |
||||
if (formType.value === 'create') { |
||||
await FileConfigApi.createFileConfig(data) |
||||
message.success(t('common.createSuccess')) |
||||
} else { |
||||
await FileConfigApi.updateFileConfig(data) |
||||
message.success(t('common.updateSuccess')) |
||||
} |
||||
dialogVisible.value = false |
||||
// 发送操作成功的事件 |
||||
emit('success') |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 重置表单 */ |
||||
const resetForm = () => { |
||||
formData.value = { |
||||
id: undefined, |
||||
name: '', |
||||
storage: '', |
||||
remark: '', |
||||
config: {} |
||||
} |
||||
formRef.value?.resetFields() |
||||
} |
||||
</script> |
||||
@ -1,214 +0,0 @@ |
||||
<template> |
||||
<doc-alert title="上传下载" url="https://doc.iocoder.cn/file/" /> |
||||
|
||||
<!-- 搜索 --> |
||||
<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="storage"> |
||||
<el-select |
||||
v-model="queryParams.storage" |
||||
placeholder="请选择存储器" |
||||
clearable |
||||
class="!w-240px" |
||||
> |
||||
<el-option |
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)" |
||||
: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" |
||||
plain |
||||
@click="openForm('create')" |
||||
v-hasPermi="['infra:file-config:create']" |
||||
> |
||||
<Icon icon="ep:plus" 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="name" /> |
||||
<el-table-column label="存储器" align="center" prop="storage"> |
||||
<template #default="scope"> |
||||
<dict-tag :type="DICT_TYPE.INFRA_FILE_STORAGE" :value="scope.row.storage" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="备注" align="center" prop="remark" /> |
||||
<el-table-column label="主配置" align="center" prop="primary"> |
||||
<template #default="scope"> |
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="创建时间" |
||||
align="center" |
||||
prop="createTime" |
||||
width="180" |
||||
:formatter="dateFormatter" |
||||
/> |
||||
<el-table-column label="操作" align="center" width="240px"> |
||||
<template #default="scope"> |
||||
<el-button |
||||
link |
||||
type="primary" |
||||
@click="openForm('update', scope.row.id)" |
||||
v-hasPermi="['infra:file-config:update']" |
||||
> |
||||
编辑 |
||||
</el-button> |
||||
<el-button |
||||
link |
||||
type="primary" |
||||
:disabled="scope.row.master" |
||||
@click="handleMaster(scope.row.id)" |
||||
v-hasPermi="['infra:file-config:update']" |
||||
> |
||||
主配置 |
||||
</el-button> |
||||
<el-button link type="primary" @click="handleTest(scope.row.id)"> 测试 </el-button> |
||||
<el-button |
||||
link |
||||
type="danger" |
||||
@click="handleDelete(scope.row.id)" |
||||
v-hasPermi="['infra:config:delete']" |
||||
> |
||||
删除 |
||||
</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
<!-- 分页 --> |
||||
<Pagination |
||||
:total="total" |
||||
v-model:page="queryParams.pageNo" |
||||
v-model:limit="queryParams.pageSize" |
||||
@pagination="getList" |
||||
/> |
||||
</ContentWrap> |
||||
|
||||
<!-- 表单弹窗:添加/修改 --> |
||||
<FileConfigForm ref="formRef" @success="getList" /> |
||||
</template> |
||||
<script setup lang="ts" name="InfraFileConfig"> |
||||
import * as FileConfigApi from '@/api/infra/fileConfig' |
||||
import FileConfigForm from './FileConfigForm.vue' |
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
import { dateFormatter } from '@/utils/formatTime' |
||||
const message = useMessage() // 消息弹窗 |
||||
const { t } = useI18n() // 国际化 |
||||
|
||||
const loading = ref(true) // 列表的加载中 |
||||
const total = ref(0) // 列表的总页数 |
||||
const list = ref([]) // 列表的数据 |
||||
const queryParams = reactive({ |
||||
pageNo: 1, |
||||
pageSize: 10, |
||||
name: undefined, |
||||
storage: undefined, |
||||
createTime: [] |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
|
||||
/** 查询列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
const data = await FileConfigApi.getFileConfigPage(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 formRef = ref() |
||||
const openForm = (type: string, id?: number) => { |
||||
formRef.value.open(type, id) |
||||
} |
||||
|
||||
/** 删除按钮操作 */ |
||||
const handleDelete = async (id: number) => { |
||||
try { |
||||
// 删除的二次确认 |
||||
await message.delConfirm() |
||||
// 发起删除 |
||||
await FileConfigApi.deleteFileConfig(id) |
||||
message.success(t('common.delSuccess')) |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch {} |
||||
} |
||||
|
||||
/** 主配置按钮操作 */ |
||||
const handleMaster = async (id) => { |
||||
try { |
||||
await message.confirm('是否确认修改配置编号为"' + id + '"的数据项为主配置?') |
||||
await FileConfigApi.updateFileConfigMaster(id) |
||||
message.success(t('common.updateSuccess')) |
||||
await getList() |
||||
} catch {} |
||||
} |
||||
|
||||
/** 测试按钮操作 */ |
||||
const handleTest = async (id) => { |
||||
try { |
||||
const response = await FileConfigApi.testFileConfig(id) |
||||
message.alert('测试通过,上传文件成功!访问地址:' + response) |
||||
} catch {} |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,71 +0,0 @@ |
||||
<template> |
||||
<Dialog v-model="dialogVisible" title="任务详细" width="700px"> |
||||
<el-descriptions :column="1" border> |
||||
<el-descriptions-item label="任务编号" min-width="60"> |
||||
{{ detailData.id }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="任务名称"> |
||||
{{ detailData.name }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="任务名称"> |
||||
<dict-tag :type="DICT_TYPE.INFRA_JOB_STATUS" :value="detailData.status" /> |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="处理器的名字"> |
||||
{{ detailData.handlerName }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="处理器的参数"> |
||||
{{ detailData.handlerParam }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="Cron 表达式"> |
||||
{{ detailData.cronExpression }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="重试次数"> |
||||
{{ detailData.retryCount }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="重试间隔"> |
||||
{{ detailData.retryInterval + ' 毫秒' }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="监控超时时间"> |
||||
{{ detailData.monitorTimeout > 0 ? detailData.monitorTimeout + ' 毫秒' : '未开启' }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="后续执行时间"> |
||||
<el-timeline> |
||||
<el-timeline-item |
||||
v-for="(nextTime, index) in nextTimes" |
||||
:key="index" |
||||
:timestamp="formatDate(nextTime)" |
||||
> |
||||
第 {{ index + 1 }} 次 |
||||
</el-timeline-item> |
||||
</el-timeline> |
||||
</el-descriptions-item> |
||||
</el-descriptions> |
||||
</Dialog> |
||||
</template> |
||||
<script lang="ts" name="InfraJobDetail" setup> |
||||
import { DICT_TYPE } from '@/utils/dict' |
||||
import { formatDate } from '@/utils/formatTime' |
||||
import * as JobApi from '@/api/infra/job' |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const detailLoading = ref(false) // 表单的加载中 |
||||
const detailData = ref({}) // 详情数据 |
||||
const nextTimes = ref([]) // 下一轮执行时间的数组 |
||||
|
||||
/** 打开弹窗 */ |
||||
const open = async (id: number) => { |
||||
dialogVisible.value = true |
||||
// 查看,设置数据 |
||||
if (id) { |
||||
detailLoading.value = true |
||||
try { |
||||
detailData.value = await JobApi.getJob(id) |
||||
// 获取下一次执行时间 |
||||
nextTimes.value = await JobApi.getJobNextTimes(id) |
||||
} finally { |
||||
detailLoading.value = false |
||||
} |
||||
} |
||||
} |
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
||||
</script> |
||||
@ -1,128 +0,0 @@ |
||||
<template> |
||||
<Dialog :title="dialogTitle" v-model="dialogVisible"> |
||||
<el-form |
||||
ref="formRef" |
||||
:model="formData" |
||||
:rules="formRules" |
||||
label-width="120px" |
||||
v-loading="formLoading" |
||||
> |
||||
<el-form-item label="任务名称" prop="name"> |
||||
<el-input v-model="formData.name" placeholder="请输入任务名称" /> |
||||
</el-form-item> |
||||
<el-form-item label="处理器的名字" prop="handlerName"> |
||||
<el-input |
||||
:readonly="formData.id !== undefined" |
||||
v-model="formData.handlerName" |
||||
placeholder="请输入处理器的名字" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="处理器的参数" prop="handlerParam"> |
||||
<el-input v-model="formData.handlerParam" placeholder="请输入处理器的参数" /> |
||||
</el-form-item> |
||||
<el-form-item label="CRON 表达式" prop="cronExpression"> |
||||
<crontab v-model="formData.cronExpression" /> |
||||
</el-form-item> |
||||
<el-form-item label="重试次数" prop="retryCount"> |
||||
<el-input |
||||
v-model="formData.retryCount" |
||||
placeholder="请输入重试次数。设置为 0 时,不进行重试" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="重试间隔" prop="retryInterval"> |
||||
<el-input |
||||
v-model="formData.retryInterval" |
||||
placeholder="请输入重试间隔,单位:毫秒。设置为 0 时,无需间隔" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="监控超时时间" prop="monitorTimeout"> |
||||
<el-input v-model="formData.monitorTimeout" placeholder="请输入监控超时时间,单位:毫秒" /> |
||||
</el-form-item> |
||||
</el-form> |
||||
<template #footer> |
||||
<el-button type="primary" @click="submitForm" :loading="formLoading">确 定</el-button> |
||||
<el-button @click="dialogVisible = false">取 消</el-button> |
||||
</template> |
||||
</Dialog> |
||||
</template> |
||||
<script setup lang="ts" name="JobForm"> |
||||
import * as JobApi from '@/api/infra/job' |
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息弹窗 |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const dialogTitle = ref('') // 弹窗的标题 |
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
||||
const formData = ref({ |
||||
id: undefined, |
||||
name: '', |
||||
handlerName: '', |
||||
handlerParam: '', |
||||
cronExpression: '' |
||||
}) |
||||
const formRules = reactive({ |
||||
name: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }], |
||||
handlerName: [{ required: true, message: '处理器的名字不能为空', trigger: 'blur' }], |
||||
cronExpression: [{ required: true, message: 'CRON 表达式不能为空', trigger: 'blur' }], |
||||
retryCount: [{ required: true, message: '重试次数不能为空', trigger: 'blur' }], |
||||
retryInterval: [{ required: true, message: '重试间隔不能为空', trigger: 'blur' }] |
||||
}) |
||||
const formRef = ref() // 表单 Ref |
||||
|
||||
/** 打开弹窗 */ |
||||
const open = async (type: string, id?: number) => { |
||||
dialogVisible.value = true |
||||
dialogTitle.value = t('action.' + type) |
||||
formType.value = type |
||||
resetForm() |
||||
// 修改时,设置数据 |
||||
if (id) { |
||||
formLoading.value = true |
||||
try { |
||||
formData.value = await JobApi.getJob(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 JobApi.JobVO |
||||
if (formType.value === 'create') { |
||||
await JobApi.createJob(data) |
||||
message.success(t('common.createSuccess')) |
||||
} else { |
||||
await JobApi.updateJob(data) |
||||
message.success(t('common.updateSuccess')) |
||||
} |
||||
dialogVisible.value = false |
||||
// 发送操作成功的事件 |
||||
emit('success') |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 重置表单 */ |
||||
const resetForm = () => { |
||||
formData.value = { |
||||
id: undefined, |
||||
name: '', |
||||
handlerName: '', |
||||
handlerParam: '', |
||||
cronExpression: '' |
||||
} |
||||
formRef.value?.resetFields() |
||||
} |
||||
</script> |
||||
@ -1,304 +0,0 @@ |
||||
<template> |
||||
<doc-alert title="定时任务" url="https://doc.iocoder.cn/job/" /> |
||||
<doc-alert title="异步任务" url="https://doc.iocoder.cn/async-task/" /> |
||||
<doc-alert title="消息队列" url="https://doc.iocoder.cn/message-queue/" /> |
||||
|
||||
<ContentWrap> |
||||
<!-- 搜索工作栏 --> |
||||
<el-form |
||||
class="-mb-15px" |
||||
:model="queryParams" |
||||
ref="queryFormRef" |
||||
:inline="true" |
||||
label-width="100px" |
||||
> |
||||
<el-form-item label="任务名称" prop="name"> |
||||
<el-input |
||||
v-model="queryParams.name" |
||||
placeholder="请输入任务名称" |
||||
clearable |
||||
@keyup.enter="handleQuery" |
||||
class="!w-240px" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="任务状态" prop="status"> |
||||
<el-select |
||||
v-model="queryParams.status" |
||||
placeholder="请选择任务状态" |
||||
clearable |
||||
class="!w-240px" |
||||
> |
||||
<el-option |
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_JOB_STATUS)" |
||||
:key="dict.value" |
||||
:label="dict.label" |
||||
:value="dict.value" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
<el-form-item label="处理器的名字" prop="handlerName"> |
||||
<el-input |
||||
v-model="queryParams.handlerName" |
||||
placeholder="请输入处理器的名字" |
||||
clearable |
||||
@keyup.enter="handleQuery" |
||||
class="!w-240px" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item> |
||||
<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="['infra:job:create']" |
||||
> |
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增 |
||||
</el-button> |
||||
<el-button |
||||
type="success" |
||||
plain |
||||
@click="handleExport" |
||||
:loading="exportLoading" |
||||
v-hasPermi="['infra:job:export']" |
||||
> |
||||
<Icon icon="ep:download" class="mr-5px" /> 导出 |
||||
</el-button> |
||||
<el-button type="info" plain @click="handleJobLog" v-hasPermi="['infra:job:query']"> |
||||
<Icon icon="ep:zoom-in" class="mr-5px" /> 执行日志 |
||||
</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
</ContentWrap> |
||||
|
||||
<!-- 列表 --> |
||||
<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="status"> |
||||
<template #default="scope"> |
||||
<dict-tag :type="DICT_TYPE.INFRA_JOB_STATUS" :value="scope.row.status" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="处理器的名字" align="center" prop="handlerName" /> |
||||
<el-table-column label="处理器的参数" align="center" prop="handlerParam" /> |
||||
<el-table-column label="CRON 表达式" align="center" prop="cronExpression" /> |
||||
<el-table-column label="操作" align="center" width="200"> |
||||
<template #default="scope"> |
||||
<el-button |
||||
type="primary" |
||||
link |
||||
@click="openForm('update', scope.row.id)" |
||||
v-hasPermi="['infra:job:update']" |
||||
> |
||||
修改 |
||||
</el-button> |
||||
<el-button |
||||
type="primary" |
||||
link |
||||
@click="handleChangeStatus(scope.row)" |
||||
v-hasPermi="['infra:job:update']" |
||||
> |
||||
{{ scope.row.status === InfraJobStatusEnum.STOP ? '开启' : '暂停' }} |
||||
</el-button> |
||||
<el-button |
||||
type="danger" |
||||
link |
||||
@click="handleDelete(scope.row)" |
||||
v-hasPermi="['infra:job:delete']" |
||||
> |
||||
删除 |
||||
</el-button> |
||||
<el-dropdown |
||||
@command="(command) => handleCommand(command, scope.row)" |
||||
v-hasPermi="['infra:job:trigger', 'infra:job:query']" |
||||
> |
||||
<el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button> |
||||
<template #dropdown> |
||||
<el-dropdown-menu> |
||||
<el-dropdown-item command="handleRun" v-if="checkPermi(['infra:job:trigger'])"> |
||||
执行一次 |
||||
</el-dropdown-item> |
||||
<el-dropdown-item command="openDetail" v-if="checkPermi(['infra:job:query'])"> |
||||
任务详细 |
||||
</el-dropdown-item> |
||||
<el-dropdown-item command="handleJobLog" v-if="checkPermi(['infra:job:query'])"> |
||||
调度日志 |
||||
</el-dropdown-item> |
||||
</el-dropdown-menu> |
||||
</template> |
||||
</el-dropdown> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
<!-- 分页组件 --> |
||||
<Pagination |
||||
:total="total" |
||||
v-model:page="queryParams.pageNo" |
||||
v-model:limit="queryParams.pageSize" |
||||
@pagination="getList" |
||||
/> |
||||
</ContentWrap> |
||||
|
||||
<!-- 表单弹窗:添加/修改 --> |
||||
<JobForm ref="formRef" @success="getList" /> |
||||
<!-- 表单弹窗:查看 --> |
||||
<JobDetail ref="detailRef" /> |
||||
</template> |
||||
<script setup lang="ts" name="InfraJob"> |
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
import { checkPermi } from '@/utils/permission' |
||||
import JobForm from './JobForm.vue' |
||||
import JobDetail from './JobDetail.vue' |
||||
import download from '@/utils/download' |
||||
import * as JobApi from '@/api/infra/job' |
||||
import { InfraJobStatusEnum } from '@/utils/constants' |
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息弹窗 |
||||
const { push } = useRouter() // 路由 |
||||
|
||||
const loading = ref(true) // 列表的加载中 |
||||
const total = ref(0) // 列表的总页数 |
||||
const list = ref([]) // 列表的数据 |
||||
const queryParams = reactive({ |
||||
pageNo: 1, |
||||
pageSize: 10, |
||||
name: undefined, |
||||
status: undefined, |
||||
handlerName: undefined |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
const exportLoading = ref(false) // 导出的加载中 |
||||
|
||||
/** 查询列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
const data = await JobApi.getJobPage(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 JobApi.exportJob(queryParams) |
||||
download.excel(data, '定时任务.xls') |
||||
} catch { |
||||
} finally { |
||||
exportLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 添加/修改操作 */ |
||||
const formRef = ref() |
||||
const openForm = (type: string, id?: number) => { |
||||
formRef.value.open(type, id) |
||||
} |
||||
|
||||
/** 修改状态操作 */ |
||||
const handleChangeStatus = async (row: JobApi.JobVO) => { |
||||
try { |
||||
// 修改状态的二次确认 |
||||
const text = row.status === InfraJobStatusEnum.STOP ? '开启' : '关闭' |
||||
await message.confirm( |
||||
'确认要' + text + '定时任务编号为"' + row.id + '"的数据项?', |
||||
t('common.reminder') |
||||
) |
||||
const status = |
||||
row.status === InfraJobStatusEnum.STOP ? InfraJobStatusEnum.NORMAL : InfraJobStatusEnum.STOP |
||||
await JobApi.updateJobStatus(row.id, status) |
||||
message.success(text + '成功') |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch { |
||||
// 取消后,进行恢复按钮 |
||||
row.status = |
||||
row.status === InfraJobStatusEnum.NORMAL ? InfraJobStatusEnum.STOP : InfraJobStatusEnum.NORMAL |
||||
} |
||||
} |
||||
|
||||
/** 删除按钮操作 */ |
||||
const handleDelete = async (id: number) => { |
||||
try { |
||||
// 删除的二次确认 |
||||
await message.delConfirm() |
||||
// 发起删除 |
||||
await JobApi.deleteJob(id) |
||||
message.success(t('common.delSuccess')) |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch {} |
||||
} |
||||
|
||||
/** '更多'操作按钮 */ |
||||
const handleCommand = (command, row) => { |
||||
switch (command) { |
||||
case 'handleRun': |
||||
handleRun(row) |
||||
break |
||||
case 'openDetail': |
||||
openDetail(row.id) |
||||
break |
||||
case 'handleJobLog': |
||||
handleJobLog(row?.id) |
||||
break |
||||
default: |
||||
break |
||||
} |
||||
} |
||||
|
||||
/** 执行一次 */ |
||||
const handleRun = async (row: JobApi.JobVO) => { |
||||
try { |
||||
// 二次确认 |
||||
await message.confirm('确认要立即执行一次' + row.name + '?', t('common.reminder')) |
||||
// 提交执行 |
||||
await JobApi.runJob(row.id) |
||||
message.success('执行成功') |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch {} |
||||
} |
||||
|
||||
/** 查看操作 */ |
||||
const detailRef = ref() |
||||
const openDetail = (id: number) => { |
||||
detailRef.value.open(id) |
||||
} |
||||
|
||||
/** 跳转执行日志 */ |
||||
const handleJobLog = (id: number) => { |
||||
if (id > 0) { |
||||
push('/job/job-log?id=' + id) |
||||
} else { |
||||
push('/job/job-log') |
||||
} |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,57 +0,0 @@ |
||||
<template> |
||||
<Dialog v-model="dialogVisible" title="任务详细" width="700px"> |
||||
<el-descriptions :column="1" border> |
||||
<el-descriptions-item label="日志编号" min-width="60"> |
||||
{{ detailData.id }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="任务编号"> |
||||
{{ detailData.jobId }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="处理器的名字"> |
||||
{{ detailData.handlerName }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="处理器的参数"> |
||||
{{ detailData.handlerParam }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="第几次执行"> |
||||
{{ detailData.executeIndex }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="执行时间"> |
||||
{{ formatDate(detailData.beginTime) + ' ~ ' + formatDate(detailData.endTime) }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="执行时长"> |
||||
{{ detailData.duration + ' 毫秒' }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="任务状态"> |
||||
<dict-tag :type="DICT_TYPE.INFRA_JOB_LOG_STATUS" :value="detailData.status" /> |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="执行结果"> |
||||
{{ detailData.duration + ' result' }} |
||||
</el-descriptions-item> |
||||
</el-descriptions> |
||||
</Dialog> |
||||
</template> |
||||
<script lang="ts" name="JobLogDetail" setup> |
||||
import { DICT_TYPE } from '@/utils/dict' |
||||
import { formatDate } from '@/utils/formatTime' |
||||
import * as JobLogApi from '@/api/infra/jobLog' |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const detailLoading = ref(false) // 表单的加载中 |
||||
const detailData = ref({}) // 详情数据 |
||||
|
||||
/** 打开弹窗 */ |
||||
const open = async (id: number) => { |
||||
dialogVisible.value = true |
||||
// 查看,设置数据 |
||||
if (id) { |
||||
detailLoading.value = true |
||||
try { |
||||
detailData.value = await JobLogApi.getJobLog(id) |
||||
} finally { |
||||
detailLoading.value = false |
||||
} |
||||
} |
||||
} |
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
||||
</script> |
||||
@ -1,197 +0,0 @@ |
||||
<template> |
||||
<doc-alert title="定时任务" url="https://doc.iocoder.cn/job/" /> |
||||
<doc-alert title="异步任务" url="https://doc.iocoder.cn/async-task/" /> |
||||
<doc-alert title="消息队列" url="https://doc.iocoder.cn/message-queue/" /> |
||||
|
||||
<ContentWrap> |
||||
<!-- 搜索工作栏 --> |
||||
<el-form |
||||
class="-mb-15px" |
||||
:model="queryParams" |
||||
ref="queryFormRef" |
||||
:inline="true" |
||||
label-width="120px" |
||||
> |
||||
<el-form-item label="处理器的名字" prop="handlerName"> |
||||
<el-input |
||||
v-model="queryParams.handlerName" |
||||
placeholder="请输入处理器的名字" |
||||
clearable |
||||
@keyup.enter="handleQuery" |
||||
class="!w-240px" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="开始执行时间" prop="beginTime"> |
||||
<el-date-picker |
||||
v-model="queryParams.beginTime" |
||||
type="date" |
||||
value-format="YYYY-MM-DD HH:mm:ss" |
||||
placeholder="选择开始执行时间" |
||||
clearable |
||||
class="!w-240px" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="结束执行时间" prop="endTime"> |
||||
<el-date-picker |
||||
v-model="queryParams.endTime" |
||||
type="date" |
||||
value-format="YYYY-MM-DD HH:mm:ss" |
||||
placeholder="选择结束执行时间" |
||||
clearable |
||||
:default-time="new Date('1 23:59:59')" |
||||
class="!w-240px" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="任务状态" prop="status"> |
||||
<el-select |
||||
v-model="queryParams.status" |
||||
placeholder="请选择任务状态" |
||||
clearable |
||||
class="!w-240px" |
||||
> |
||||
<el-option |
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_JOB_LOG_STATUS)" |
||||
:key="dict.value" |
||||
:label="dict.label" |
||||
:value="dict.value" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
<el-form-item> |
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> |
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> |
||||
<el-button |
||||
type="success" |
||||
plain |
||||
@click="handleExport" |
||||
:loading="exportLoading" |
||||
v-hasPermi="['infra:job: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="jobId" /> |
||||
<el-table-column label="处理器的名字" align="center" prop="handlerName" /> |
||||
<el-table-column label="处理器的参数" align="center" prop="handlerParam" /> |
||||
<el-table-column label="第几次执行" align="center" prop="executeIndex" /> |
||||
<el-table-column label="执行时间" align="center" width="170s"> |
||||
<template #default="scope"> |
||||
<span>{{ formatDate(scope.row.beginTime) + ' ~ ' + formatDate(scope.row.endTime) }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="执行时长" align="center" prop="duration"> |
||||
<template #default="scope"> |
||||
<span>{{ scope.row.duration + ' 毫秒' }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="任务状态" align="center" prop="status"> |
||||
<template #default="scope"> |
||||
<dict-tag :type="DICT_TYPE.INFRA_JOB_LOG_STATUS" :value="scope.row.status" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="操作" align="center"> |
||||
<template #default="scope"> |
||||
<el-button |
||||
type="primary" |
||||
link |
||||
@click="openDetail(scope.row.id)" |
||||
v-hasPermi="['infra:job: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> |
||||
|
||||
<!-- 表单弹窗:查看 --> |
||||
<JobLogDetail ref="detailRef" /> |
||||
</template> |
||||
<script setup lang="ts" name="InfraJobLog"> |
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
import { formatDate } from '@/utils/formatTime' |
||||
import download from '@/utils/download' |
||||
import JobLogDetail from './JobLogDetail.vue' |
||||
import * as JobLogApi from '@/api/infra/jobLog' |
||||
const message = useMessage() // 消息弹窗 |
||||
const { query } = useRoute() // 查询参数 |
||||
|
||||
const loading = ref(true) // 列表的加载中 |
||||
const total = ref(0) // 列表的总页数 |
||||
const list = ref([]) // 列表的数据 |
||||
const queryParams = reactive({ |
||||
pageNo: 1, |
||||
pageSize: 10, |
||||
jobId: query.id, |
||||
handlerName: undefined, |
||||
beginTime: undefined, |
||||
endTime: undefined, |
||||
status: undefined |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
const exportLoading = ref(false) // 导出的加载中 |
||||
|
||||
/** 查询列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
const data = await JobLogApi.getJobLogPage(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 = (rowId?: number) => { |
||||
detailRef.value.open(rowId) |
||||
} |
||||
|
||||
/** 导出按钮操作 */ |
||||
const handleExport = async () => { |
||||
try { |
||||
// 导出的二次确认 |
||||
await message.exportConfirm() |
||||
// 发起导出 |
||||
exportLoading.value = true |
||||
const data = await JobLogApi.exportJobLog(queryParams) |
||||
download.excel(data, '定时任务执行日志.xls') |
||||
} catch { |
||||
} finally { |
||||
exportLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,268 +0,0 @@ |
||||
<template> |
||||
<doc-alert title="Redis 缓存" url="https://doc.iocoder.cn/redis-cache/" /> |
||||
<doc-alert title="本地缓存" url="https://doc.iocoder.cn/local-cache/" /> |
||||
<el-scrollbar height="calc(100vh - 88px - 40px - 50px)"> |
||||
<el-row> |
||||
<!-- 基本信息 --> |
||||
<el-col :span="24" class="card-box" shadow="hover"> |
||||
<el-card> |
||||
<el-descriptions title="基本信息" :column="6" border> |
||||
<el-descriptions-item label="Redis版本 :"> |
||||
{{ cache?.info?.redis_version }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="运行模式 :"> |
||||
{{ cache?.info?.redis_mode == 'standalone' ? '单机' : '集群' }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="端口 :"> |
||||
{{ cache?.info?.tcp_port }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="客户端数 :"> |
||||
{{ cache?.info?.connected_clients }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="运行时间(天) :"> |
||||
{{ cache?.info?.uptime_in_days }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="使用内存 :"> |
||||
{{ cache?.info?.used_memory_human }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="使用CPU :"> |
||||
{{ cache?.info ? parseFloat(cache?.info?.used_cpu_user_children).toFixed(2) : '' }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="内存配置 :"> |
||||
{{ cache?.info?.maxmemory_human }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="AOF是否开启 :"> |
||||
{{ cache?.info?.aof_enabled == '0' ? '否' : '是' }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="RDB是否成功 :"> |
||||
{{ cache?.info?.rdb_last_bgsave_status }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="Key数量 :"> |
||||
{{ cache?.dbSize }} |
||||
</el-descriptions-item> |
||||
<el-descriptions-item label="网络入口/出口 :"> |
||||
{{ cache?.info?.instantaneous_input_kbps }}kps/ |
||||
{{ cache?.info?.instantaneous_output_kbps }}kps |
||||
</el-descriptions-item> |
||||
</el-descriptions> |
||||
</el-card> |
||||
</el-col> |
||||
<!-- 命令统计 --> |
||||
<el-col :span="12" class="mt-3"> |
||||
<el-card :gutter="12" shadow="hover"> |
||||
<Echart :options="commandStatsRefChika" :height="420" /> |
||||
</el-card> |
||||
</el-col> |
||||
<!-- 内存使用量统计 --> |
||||
<el-col :span="12" class="mt-3"> |
||||
<el-card class="ml-3" :gutter="12" shadow="hover"> |
||||
<Echart :options="usedmemoryEchartChika" :height="420" /> |
||||
</el-card> |
||||
</el-col> |
||||
</el-row> |
||||
</el-scrollbar> |
||||
</template> |
||||
<script setup lang="ts"> |
||||
import * as RedisApi from '@/api/infra/redis' |
||||
import { RedisMonitorInfoVO } from '@/api/infra/redis/types' |
||||
const cache = ref<RedisMonitorInfoVO>() |
||||
|
||||
// 基本信息 |
||||
const readRedisInfo = async () => { |
||||
const data = await RedisApi.getCache() |
||||
cache.value = data |
||||
} |
||||
|
||||
// 内存使用情况 |
||||
const usedmemoryEchartChika = reactive<any>({ |
||||
title: { |
||||
// 仪表盘标题。 |
||||
text: '内存使用情况', |
||||
left: 'center', |
||||
show: true, // 是否显示标题,默认 true。 |
||||
offsetCenter: [0, '20%'], //相对于仪表盘中心的偏移位置,数组第一项是水平方向的偏移,第二项是垂直方向的偏移。可以是绝对的数值,也可以是相对于仪表盘半径的百分比。 |
||||
color: 'yellow', // 文字的颜色,默认 #333。 |
||||
fontSize: 20 // 文字的字体大小,默认 15。 |
||||
}, |
||||
toolbox: { |
||||
show: false, |
||||
feature: { |
||||
restore: { show: true }, |
||||
saveAsImage: { show: true } |
||||
} |
||||
}, |
||||
series: [ |
||||
{ |
||||
name: '峰值', |
||||
type: 'gauge', |
||||
min: 0, |
||||
max: 50, |
||||
splitNumber: 10, |
||||
//这是指针的颜色 |
||||
color: '#F5C74E', |
||||
radius: '85%', |
||||
center: ['50%', '50%'], |
||||
startAngle: 225, |
||||
endAngle: -45, |
||||
axisLine: { |
||||
// 坐标轴线 |
||||
lineStyle: { |
||||
// 属性lineStyle控制线条样式 |
||||
color: [ |
||||
[0.2, '#7FFF00'], |
||||
[0.8, '#00FFFF'], |
||||
[1, '#FF0000'] |
||||
], |
||||
//width: 6 外框的大小(环的宽度) |
||||
width: 10 |
||||
} |
||||
}, |
||||
axisTick: { |
||||
// 坐标轴小标记 |
||||
//里面的线长是5(短线) |
||||
length: 5, // 属性length控制线长 |
||||
lineStyle: { |
||||
// 属性lineStyle控制线条样式 |
||||
color: '#76D9D7' |
||||
} |
||||
}, |
||||
splitLine: { |
||||
// 分隔线 |
||||
length: 20, // 属性length控制线长 |
||||
lineStyle: { |
||||
// 属性lineStyle(详见lineStyle)控制线条样式 |
||||
color: '#76D9D7' |
||||
} |
||||
}, |
||||
axisLabel: { |
||||
color: '#76D9D7', |
||||
distance: 15, |
||||
fontSize: 15 |
||||
}, |
||||
pointer: { |
||||
// 指针的大小 |
||||
width: 7, |
||||
show: true |
||||
}, |
||||
detail: { |
||||
textStyle: { |
||||
fontWeight: 'normal', |
||||
// 里面文字下的数值大小(50) |
||||
fontSize: 15, |
||||
color: '#FFFFFF' |
||||
}, |
||||
valueAnimation: true |
||||
}, |
||||
progress: { |
||||
show: true |
||||
} |
||||
} |
||||
] |
||||
}) |
||||
|
||||
// 指令使用情况 |
||||
const commandStatsRefChika = reactive({ |
||||
title: { |
||||
text: '命令统计', |
||||
left: 'center' |
||||
}, |
||||
tooltip: { |
||||
trigger: 'item', |
||||
formatter: '{a} <br/>{b} : {c} ({d}%)' |
||||
}, |
||||
legend: { |
||||
type: 'scroll', |
||||
orient: 'vertical', |
||||
right: 30, |
||||
top: 10, |
||||
bottom: 20, |
||||
data: [] as any[], |
||||
textStyle: { |
||||
color: '#a1a1a1' |
||||
} |
||||
}, |
||||
series: [ |
||||
{ |
||||
name: '命令', |
||||
type: 'pie', |
||||
radius: [20, 120], |
||||
center: ['40%', '60%'], |
||||
data: [] as any[], |
||||
roseType: 'radius', |
||||
label: { |
||||
show: true |
||||
}, |
||||
emphasis: { |
||||
label: { |
||||
show: true |
||||
}, |
||||
itemStyle: { |
||||
shadowBlur: 10, |
||||
shadowOffsetX: 0, |
||||
shadowColor: 'rgba(0, 0, 0, 0.5)' |
||||
} |
||||
} |
||||
} |
||||
] |
||||
}) |
||||
|
||||
/** 加载数据 */ |
||||
const getSummary = () => { |
||||
// 初始化命令图表 |
||||
initCommandStatsChart() |
||||
usedMemoryInstance() |
||||
} |
||||
|
||||
/** 命令使用情况 */ |
||||
const initCommandStatsChart = async () => { |
||||
usedmemoryEchartChika.series[0].data = [] |
||||
// 发起请求 |
||||
try { |
||||
const data = await RedisApi.getCache() |
||||
cache.value = data |
||||
// 处理数据 |
||||
const commandStats = [] as any[] |
||||
const nameList = [] as string[] |
||||
data.commandStats.forEach((row) => { |
||||
commandStats.push({ |
||||
name: row.command, |
||||
value: row.calls |
||||
}) |
||||
nameList.push(row.command) |
||||
}) |
||||
commandStatsRefChika.legend.data = nameList |
||||
commandStatsRefChika.series[0].data = commandStats |
||||
} catch {} |
||||
} |
||||
const usedMemoryInstance = async () => { |
||||
try { |
||||
const data = await RedisApi.getCache() |
||||
cache.value = data |
||||
// 仪表盘详情,用于显示数据。 |
||||
usedmemoryEchartChika.series[0].detail = { |
||||
show: true, // 是否显示详情,默认 true。 |
||||
offsetCenter: [0, '50%'], // 相对于仪表盘中心的偏移位置,数组第一项是水平方向的偏移,第二项是垂直方向的偏移。可以是绝对的数值,也可以是相对于仪表盘半径的百分比。 |
||||
color: 'auto', // 文字的颜色,默认 auto。 |
||||
fontSize: 30, // 文字的字体大小,默认 15。 |
||||
formatter: cache.value!.info.used_memory_human // 格式化函数或者字符串 |
||||
} |
||||
|
||||
usedmemoryEchartChika.series[0].data[0] = { |
||||
value: cache.value!.info.used_memory_human, |
||||
name: '内存消耗' |
||||
} |
||||
console.log(cache.value!.info) |
||||
usedmemoryEchartChika.tooltip = { |
||||
formatter: '{b} <br/>{a} : ' + cache.value!.info.used_memory_human |
||||
} |
||||
} catch {} |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
// 读取 redis 信息 |
||||
readRedisInfo() |
||||
// 加载数据 |
||||
getSummary() |
||||
}) |
||||
</script> |
||||
@ -1,25 +0,0 @@ |
||||
<template> |
||||
<doc-alert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" /> |
||||
|
||||
<ContentWrap> |
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" /> |
||||
</ContentWrap> |
||||
</template> |
||||
<script setup lang="ts" name="InfraAdminServer"> |
||||
import * as ConfigApi from '@/api/infra/config' |
||||
|
||||
const loading = ref(true) // 是否加载中 |
||||
const src = ref(import.meta.env.VITE_BASE_URL + '/admin/applications') |
||||
|
||||
/** 初始化 */ |
||||
onMounted(async () => { |
||||
try { |
||||
const data = await ConfigApi.getConfigKey('url.spring-boot-admin') |
||||
if (data && data.length > 0) { |
||||
src.value = data |
||||
} |
||||
} finally { |
||||
loading.value = false |
||||
} |
||||
}) |
||||
</script> |
||||
@ -1,25 +0,0 @@ |
||||
<template> |
||||
<doc-alert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" /> |
||||
|
||||
<ContentWrap> |
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" /> |
||||
</ContentWrap> |
||||
</template> |
||||
<script setup lang="ts" name="InfraSkyWalking"> |
||||
import * as ConfigApi from '@/api/infra/config' |
||||
|
||||
const loading = ref(true) // 是否加载中 |
||||
const src = ref('http://skywalking.shop.iocoder.cn') |
||||
|
||||
/** 初始化 */ |
||||
onMounted(async () => { |
||||
try { |
||||
const data = await ConfigApi.getConfigKey('url.skywalking') |
||||
if (data && data.length > 0) { |
||||
src.value = data |
||||
} |
||||
} finally { |
||||
loading.value = false |
||||
} |
||||
}) |
||||
</script> |
||||
@ -1,26 +0,0 @@ |
||||
<template> |
||||
<doc-alert title="接口文档" url="https://doc.iocoder.cn/api-doc/" /> |
||||
|
||||
<ContentWrap> |
||||
<IFrame :src="src" /> |
||||
</ContentWrap> |
||||
</template> |
||||
<script setup lang="ts" name="InfraSwagger"> |
||||
import * as ConfigApi from '@/api/infra/config' |
||||
|
||||
const loading = ref(true) // 是否加载中 |
||||
const src = ref(import.meta.env.VITE_BASE_URL + '/doc.html') // Knife4j UI |
||||
// const src = ref(import.meta.env.VITE_BASE_URL + '/swagger-ui') // Swagger UI |
||||
|
||||
/** 初始化 */ |
||||
onMounted(async () => { |
||||
try { |
||||
const data = await ConfigApi.getConfigKey('url.swagger') |
||||
if (data && data.length > 0) { |
||||
src.value = data |
||||
} |
||||
} finally { |
||||
loading.value = false |
||||
} |
||||
}) |
||||
</script> |
||||
@ -1,4 +0,0 @@ |
||||
<template> |
||||
<div>index</div> |
||||
</template> |
||||
<script setup lang="ts" name="TestDome"></script> |
||||
@ -1,116 +0,0 @@ |
||||
<template> |
||||
<div class="flex"> |
||||
<el-card :gutter="12" class="w-1/2" shadow="always"> |
||||
<template #header> |
||||
<div class="card-header"> |
||||
<span>连接</span> |
||||
</div> |
||||
</template> |
||||
<div class="flex items-center"> |
||||
<span class="text-lg font-medium mr-4"> 连接状态: </span> |
||||
<el-tag :color="getTagColor">{{ status }}</el-tag> |
||||
</div> |
||||
<hr class="my-4" /> |
||||
|
||||
<div class="flex"> |
||||
<el-input v-model="server" disabled> |
||||
<template #prepend> 服务地址</template> |
||||
</el-input> |
||||
<el-button :type="getIsOpen ? 'danger' : 'primary'" @click="toggle"> |
||||
{{ getIsOpen ? '关闭连接' : '开启连接' }} |
||||
</el-button> |
||||
</div> |
||||
<p class="text-lg font-medium mt-4">设置</p> |
||||
<hr class="my-4" /> |
||||
<el-input |
||||
v-model="sendValue" |
||||
:autosize="{ minRows: 2, maxRows: 4 }" |
||||
:disabled="!getIsOpen" |
||||
clearable |
||||
type="textarea" |
||||
/> |
||||
<el-button :disabled="!getIsOpen" block class="mt-4" type="primary" @click="handlerSend"> |
||||
发送 |
||||
</el-button> |
||||
</el-card> |
||||
<el-card :gutter="12" class="w-1/2" shadow="always"> |
||||
<template #header> |
||||
<div class="card-header"> |
||||
<span>消息记录</span> |
||||
</div> |
||||
</template> |
||||
<div class="max-h-80 overflow-auto"> |
||||
<ul> |
||||
<li v-for="item in getList" :key="item.time" class="mt-2"> |
||||
<div class="flex items-center"> |
||||
<span class="mr-2 text-primary font-medium">收到消息:</span> |
||||
<span>{{ formatDate(item.time) }}</span> |
||||
</div> |
||||
<div> |
||||
{{ item.res }} |
||||
</div> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
</el-card> |
||||
</div> |
||||
</template> |
||||
<script lang="ts" name="InfraWebSocket" setup> |
||||
import { formatDate } from '@/utils/formatTime' |
||||
import { useUserStore } from '@/store/modules/user' |
||||
import { useWebSocket } from '@vueuse/core' |
||||
|
||||
const userStore = useUserStore() |
||||
|
||||
const sendValue = ref('') |
||||
|
||||
const server = ref( |
||||
(import.meta.env.VITE_BASE_URL + '/websocket/message').replace('http', 'ws') + |
||||
'?userId=' + |
||||
userStore.getUser.id |
||||
) |
||||
|
||||
const state = reactive({ |
||||
recordList: [] as { id: number; time: number; res: string }[] |
||||
}) |
||||
|
||||
const { status, data, send, close, open } = useWebSocket(server.value, { |
||||
autoReconnect: false, |
||||
heartbeat: true |
||||
}) |
||||
|
||||
watchEffect(() => { |
||||
if (data.value) { |
||||
try { |
||||
const res = JSON.parse(data.value) |
||||
state.recordList.push(res) |
||||
} catch (error) { |
||||
state.recordList.push({ |
||||
res: data.value, |
||||
id: Math.ceil(Math.random() * 1000), |
||||
time: new Date().getTime() |
||||
}) |
||||
} |
||||
} |
||||
}) |
||||
|
||||
const getIsOpen = computed(() => status.value === 'OPEN') |
||||
const getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red')) |
||||
|
||||
const getList = computed(() => { |
||||
return [...state.recordList].reverse() |
||||
}) |
||||
|
||||
function handlerSend() { |
||||
send(sendValue.value) |
||||
sendValue.value = '' |
||||
} |
||||
|
||||
function toggle() { |
||||
if (getIsOpen.value) { |
||||
close() |
||||
} else { |
||||
open() |
||||
} |
||||
} |
||||
</script> |
||||
@ -1,120 +0,0 @@ |
||||
<template> |
||||
<Dialog :title="dialogTitle" v-model="dialogVisible"> |
||||
<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="picUrl"> |
||||
<UploadImg v-model="formData.picUrl" :limit="1" :is-show-tip="false" /> |
||||
</el-form-item> |
||||
<el-form-item label="品牌排序" prop="sort"> |
||||
<el-input-number v-model="formData.sort" controls-position="right" :min="0" /> |
||||
</el-form-item> |
||||
<el-form-item label="品牌状态" prop="status"> |
||||
<el-radio-group v-model="formData.status"> |
||||
<el-radio |
||||
v-for="dict in 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="品牌描述"> |
||||
<el-input v-model="formData.description" type="textarea" placeholder="请输入品牌描述" /> |
||||
</el-form-item> |
||||
</el-form> |
||||
<template #footer> |
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> |
||||
<el-button @click="dialogVisible = false">取 消</el-button> |
||||
</template> |
||||
</Dialog> |
||||
</template> |
||||
<script setup lang="ts" name="ProductBrandForm"> |
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
import { CommonStatusEnum } from '@/utils/constants' |
||||
import * as ProductBrandApi from '@/api/mall/product/brand' |
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息弹窗 |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const dialogTitle = ref('') // 弹窗的标题 |
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
||||
const formData = ref({ |
||||
id: undefined, |
||||
name: '', |
||||
picUrl: '', |
||||
status: CommonStatusEnum.ENABLE, |
||||
description: '' |
||||
}) |
||||
const formRules = reactive({ |
||||
name: [{ required: true, message: '品牌名称不能为空', trigger: 'blur' }], |
||||
picUrl: [{ required: true, message: '品牌图片不能为空', trigger: 'blur' }], |
||||
sort: [{ required: true, message: '品牌排序不能为空', trigger: 'blur' }] |
||||
}) |
||||
const formRef = ref() // 表单 Ref |
||||
|
||||
/** 打开弹窗 */ |
||||
const open = async (type: string, id?: number) => { |
||||
dialogVisible.value = true |
||||
dialogTitle.value = t('action.' + type) |
||||
formType.value = type |
||||
resetForm() |
||||
// 修改时,设置数据 |
||||
if (id) { |
||||
formLoading.value = true |
||||
try { |
||||
formData.value = await ProductBrandApi.getBrand(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 ProductBrandApi.BrandVO |
||||
if (formType.value === 'create') { |
||||
await ProductBrandApi.createBrand(data) |
||||
message.success(t('common.createSuccess')) |
||||
} else { |
||||
await ProductBrandApi.updateBrand(data) |
||||
message.success(t('common.updateSuccess')) |
||||
} |
||||
dialogVisible.value = false |
||||
// 发送操作成功的事件 |
||||
emit('success') |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 重置表单 */ |
||||
const resetForm = () => { |
||||
formData.value = { |
||||
id: undefined, |
||||
name: '', |
||||
picUrl: '', |
||||
status: CommonStatusEnum.ENABLE, |
||||
description: '' |
||||
} |
||||
formRef.value?.resetFields() |
||||
} |
||||
</script> |
||||
@ -1,177 +0,0 @@ |
||||
<template> |
||||
<!-- 搜索工作栏 --> |
||||
<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="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" |
||||
plain |
||||
@click="openForm('create')" |
||||
v-hasPermi="['product:brand:create']" |
||||
> |
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增 |
||||
</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
</ContentWrap> |
||||
|
||||
<!-- 列表 --> |
||||
<ContentWrap> |
||||
<el-table v-loading="loading" :data="list" row-key="id" default-expand-all> |
||||
<el-table-column label="品牌名称" prop="name" sortable /> |
||||
<el-table-column label="品牌图片" align="center" prop="picUrl"> |
||||
<template #default="scope"> |
||||
<img v-if="scope.row.picUrl" :src="scope.row.picUrl" alt="品牌图片" class="h-100px" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="品牌排序" align="center" prop="sort" /> |
||||
<el-table-column label="开启状态" align="center" prop="status"> |
||||
<template #default="scope"> |
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="创建时间" |
||||
align="center" |
||||
prop="createTime" |
||||
width="180" |
||||
:formatter="dateFormatter" |
||||
/> |
||||
<el-table-column label="操作" align="center"> |
||||
<template #default="scope"> |
||||
<el-button |
||||
link |
||||
type="primary" |
||||
@click="openForm('update', scope.row.id)" |
||||
v-hasPermi="['product:brand:update']" |
||||
> |
||||
编辑 |
||||
</el-button> |
||||
<el-button |
||||
link |
||||
type="danger" |
||||
@click="handleDelete(scope.row.id)" |
||||
v-hasPermi="['product:brand: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> |
||||
|
||||
<!-- 表单弹窗:添加/修改 --> |
||||
<BrandForm ref="formRef" @success="getList" /> |
||||
</template> |
||||
<script setup lang="ts" name="ProductBrand"> |
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
import { dateFormatter } from '@/utils/formatTime' |
||||
import * as ProductBrandApi from '@/api/mall/product/brand' |
||||
import BrandForm from './BrandForm.vue' |
||||
const message = useMessage() // 消息弹窗 |
||||
const { t } = useI18n() // 国际化 |
||||
|
||||
const loading = ref(true) // 列表的加载中 |
||||
const total = ref(0) // 列表的总页数 |
||||
const list = ref<any[]>([]) // 列表的数据 |
||||
const queryParams = reactive({ |
||||
pageNo: 1, |
||||
pageSize: 10, |
||||
name: undefined, |
||||
status: undefined, |
||||
createTime: [] |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
|
||||
/** 查询列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
const data = await ProductBrandApi.getBrandParam(queryParams) |
||||
list.value = data.list |
||||
total.value = data.total |
||||
} finally { |
||||
loading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 搜索按钮操作 */ |
||||
const handleQuery = () => { |
||||
getList() |
||||
} |
||||
|
||||
/** 重置按钮操作 */ |
||||
const resetQuery = () => { |
||||
queryFormRef.value.resetFields() |
||||
handleQuery() |
||||
} |
||||
|
||||
/** 添加/修改操作 */ |
||||
const formRef = ref() |
||||
const openForm = (type: string, id?: number) => { |
||||
formRef.value.open(type, id) |
||||
} |
||||
|
||||
/** 删除按钮操作 */ |
||||
const handleDelete = async (id: number) => { |
||||
try { |
||||
// 删除的二次确认 |
||||
await message.delConfirm() |
||||
// 发起删除 |
||||
await ProductBrandApi.deleteBrand(id) |
||||
message.success(t('common.delSuccess')) |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch {} |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,138 +0,0 @@ |
||||
<template> |
||||
<Dialog :title="dialogTitle" v-model="dialogVisible"> |
||||
<el-form |
||||
ref="formRef" |
||||
:model="formData" |
||||
:rules="formRules" |
||||
label-width="120px" |
||||
v-loading="formLoading" |
||||
> |
||||
<el-form-item label="上级分类" prop="parentId"> |
||||
<el-select v-model="formData.parentId" placeholder="请选择上级分类"> |
||||
<el-option :key="0" label="顶级分类" :value="0" /> |
||||
<el-option |
||||
v-for="item in categoryList" |
||||
:key="item.id" |
||||
:label="item.name" |
||||
:value="item.id" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
<el-form-item label="分类名称" prop="name"> |
||||
<el-input v-model="formData.name" placeholder="请输入分类名称" /> |
||||
</el-form-item> |
||||
<el-form-item label="移动端分类图" prop="picUrl"> |
||||
<UploadImg v-model="formData.picUrl" :limit="1" :is-show-tip="false" /> |
||||
<div style="font-size: 10px" class="pl-10px">推荐 180x180 图片分辨率</div> |
||||
</el-form-item> |
||||
<el-form-item label="PC 端分类图" prop="bigPicUrl"> |
||||
<UploadImg v-model="formData.bigPicUrl" :limit="1" :is-show-tip="false" /> |
||||
<div style="font-size: 10px" class="pl-10px">推荐 468x340 图片分辨率</div> |
||||
</el-form-item> |
||||
<el-form-item label="分类排序" prop="sort"> |
||||
<el-input-number v-model="formData.sort" controls-position="right" :min="0" /> |
||||
</el-form-item> |
||||
<el-form-item label="开启状态" prop="status"> |
||||
<el-radio-group v-model="formData.status"> |
||||
<el-radio |
||||
v-for="dict in 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> |
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> |
||||
<el-button @click="dialogVisible = false">取 消</el-button> |
||||
</template> |
||||
</Dialog> |
||||
</template> |
||||
<script setup lang="ts" name="ProductCategory"> |
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
import { CommonStatusEnum } from '@/utils/constants' |
||||
import * as ProductCategoryApi from '@/api/mall/product/category' |
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息弹窗 |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const dialogTitle = ref('') // 弹窗的标题 |
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
||||
const formData = ref({ |
||||
id: undefined, |
||||
name: '', |
||||
picUrl: '', |
||||
bigPicUrl: '', |
||||
status: CommonStatusEnum.ENABLE |
||||
}) |
||||
const formRules = reactive({ |
||||
parentId: [{ required: true, message: '请选择上级分类', trigger: 'blur' }], |
||||
name: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }], |
||||
picUrl: [{ required: true, message: '分类图片不能为空', trigger: 'blur' }], |
||||
sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }], |
||||
status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }] |
||||
}) |
||||
const formRef = ref() // 表单 Ref |
||||
const categoryList = ref<any[]>([]) // 分类树 |
||||
|
||||
/** 打开弹窗 */ |
||||
const open = async (type: string, id?: number) => { |
||||
dialogVisible.value = true |
||||
dialogTitle.value = t('action.' + type) |
||||
formType.value = type |
||||
resetForm() |
||||
// 修改时,设置数据 |
||||
if (id) { |
||||
formLoading.value = true |
||||
try { |
||||
formData.value = await ProductCategoryApi.getCategory(id) |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
// 获得分类树 |
||||
categoryList.value = await ProductCategoryApi.getCategoryList({ parentId: 0 }) |
||||
} |
||||
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 ProductCategoryApi.CategoryVO |
||||
if (formType.value === 'create') { |
||||
await ProductCategoryApi.createCategory(data) |
||||
message.success(t('common.createSuccess')) |
||||
} else { |
||||
await ProductCategoryApi.updateCategory(data) |
||||
message.success(t('common.updateSuccess')) |
||||
} |
||||
dialogVisible.value = false |
||||
// 发送操作成功的事件 |
||||
emit('success') |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 重置表单 */ |
||||
const resetForm = () => { |
||||
formData.value = { |
||||
id: undefined, |
||||
name: '', |
||||
picUrl: '', |
||||
bigPicUrl: '', |
||||
status: CommonStatusEnum.ENABLE |
||||
} |
||||
formRef.value?.resetFields() |
||||
} |
||||
</script> |
||||
@ -1,144 +0,0 @@ |
||||
<template> |
||||
<!-- 搜索工作栏 --> |
||||
<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> |
||||
<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="['product:category:create']" |
||||
> |
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增 |
||||
</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
</ContentWrap> |
||||
|
||||
<!-- 列表 --> |
||||
<ContentWrap> |
||||
<el-table v-loading="loading" :data="list" row-key="id" default-expand-all> |
||||
<el-table-column label="分类名称" prop="name" sortable /> |
||||
<el-table-column label="移动端分类图" align="center" prop="picUrl"> |
||||
<template #default="scope"> |
||||
<img v-if="scope.row.picUrl" :src="scope.row.picUrl" alt="移动端分类图" class="h-100px" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column label="分类排序" align="center" prop="sort" /> |
||||
<el-table-column label="开启状态" align="center" prop="status"> |
||||
<template #default="scope"> |
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="创建时间" |
||||
align="center" |
||||
prop="createTime" |
||||
width="180" |
||||
:formatter="dateFormatter" |
||||
/> |
||||
<el-table-column label="操作" align="center"> |
||||
<template #default="scope"> |
||||
<el-button |
||||
link |
||||
type="primary" |
||||
@click="openForm('update', scope.row.id)" |
||||
v-hasPermi="['product:category:update']" |
||||
> |
||||
编辑 |
||||
</el-button> |
||||
<el-button |
||||
link |
||||
type="danger" |
||||
@click="handleDelete(scope.row.id)" |
||||
v-hasPermi="['product:category:delete']" |
||||
> |
||||
删除 |
||||
</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
</ContentWrap> |
||||
|
||||
<!-- 表单弹窗:添加/修改 --> |
||||
<CategoryForm ref="formRef" @success="getList" /> |
||||
</template> |
||||
<script setup lang="ts" name="ProductCategory"> |
||||
import { DICT_TYPE } from '@/utils/dict' |
||||
import { handleTree } from '@/utils/tree' |
||||
import { dateFormatter } from '@/utils/formatTime' |
||||
import * as ProductCategoryApi from '@/api/mall/product/category' |
||||
import CategoryForm from './CategoryForm.vue' |
||||
const message = useMessage() // 消息弹窗 |
||||
const { t } = useI18n() // 国际化 |
||||
|
||||
const loading = ref(true) // 列表的加载中 |
||||
const list = ref<any[]>([]) // 列表的数据 |
||||
const queryParams = reactive({ |
||||
name: undefined |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
|
||||
/** 查询列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
const data = await ProductCategoryApi.getCategoryList(queryParams) |
||||
list.value = handleTree(data, 'id', 'parentId') |
||||
} finally { |
||||
loading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 搜索按钮操作 */ |
||||
const handleQuery = () => { |
||||
getList() |
||||
} |
||||
|
||||
/** 重置按钮操作 */ |
||||
const resetQuery = () => { |
||||
queryFormRef.value.resetFields() |
||||
handleQuery() |
||||
} |
||||
|
||||
/** 添加/修改操作 */ |
||||
const formRef = ref() |
||||
const openForm = (type: string, id?: number) => { |
||||
formRef.value.open(type, id) |
||||
} |
||||
|
||||
/** 删除按钮操作 */ |
||||
const handleDelete = async (id: number) => { |
||||
try { |
||||
// 删除的二次确认 |
||||
await message.delConfirm() |
||||
// 发起删除 |
||||
await ProductCategoryApi.deleteCategory(id) |
||||
message.success(t('common.delSuccess')) |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch {} |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,168 +0,0 @@ |
||||
<template> |
||||
<!-- 搜索工作栏 --> |
||||
<ContentWrap> |
||||
<el-form |
||||
ref="queryFormRef" |
||||
:inline="true" |
||||
:model="queryParams" |
||||
class="-mb-15px" |
||||
label-width="68px" |
||||
> |
||||
<el-form-item label="名称" prop="name"> |
||||
<el-input |
||||
v-model="queryParams.name" |
||||
class="!w-240px" |
||||
clearable |
||||
placeholder="请输入名称" |
||||
@keyup.enter="handleQuery" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="创建时间" prop="createTime"> |
||||
<el-date-picker |
||||
v-model="queryParams.createTime" |
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
||||
class="!w-240px" |
||||
end-placeholder="结束日期" |
||||
start-placeholder="开始日期" |
||||
type="daterange" |
||||
value-format="YYYY-MM-DD HH:mm:ss" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item> |
||||
<el-button @click="handleQuery"> |
||||
<Icon class="mr-5px" icon="ep:search" /> |
||||
搜索 |
||||
</el-button> |
||||
<el-button @click="resetQuery"> |
||||
<Icon class="mr-5px" icon="ep:refresh" /> |
||||
重置 |
||||
</el-button> |
||||
<el-button |
||||
v-hasPermi="['product:property:create']" |
||||
plain |
||||
type="primary" |
||||
@click="openForm('create')" |
||||
> |
||||
<Icon class="mr-5px" icon="ep:plus" /> |
||||
新增 |
||||
</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
</ContentWrap> |
||||
|
||||
<!-- 列表 --> |
||||
<ContentWrap> |
||||
<el-table v-loading="loading" :data="list"> |
||||
<el-table-column align="center" label="编号" prop="id" /> |
||||
<el-table-column align="center" label="名称" prop="name" /> |
||||
<el-table-column :show-overflow-tooltip="true" align="center" label="备注" prop="remark" /> |
||||
<el-table-column |
||||
:formatter="dateFormatter" |
||||
align="center" |
||||
label="创建时间" |
||||
prop="createTime" |
||||
width="180" |
||||
/> |
||||
<el-table-column align="center" label="操作"> |
||||
<template #default="scope"> |
||||
<el-button |
||||
v-hasPermi="['product:property:update']" |
||||
link |
||||
type="primary" |
||||
@click="openForm('update', scope.row.id)" |
||||
> |
||||
编辑 |
||||
</el-button> |
||||
<el-button link type="primary"> |
||||
<router-link :to="'/property/value/' + scope.row.id">属性值</router-link> |
||||
</el-button> |
||||
<el-button |
||||
v-hasPermi="['product:property:delete']" |
||||
link |
||||
type="danger" |
||||
@click="handleDelete(scope.row.id)" |
||||
> |
||||
删除 |
||||
</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
<!-- 分页 --> |
||||
<Pagination |
||||
v-model:limit="queryParams.pageSize" |
||||
v-model:page="queryParams.pageNo" |
||||
:total="total" |
||||
@pagination="getList" |
||||
/> |
||||
</ContentWrap> |
||||
|
||||
<!-- 表单弹窗:添加/修改 --> |
||||
<PropertyForm ref="formRef" @success="getList" /> |
||||
</template> |
||||
<script lang="ts" name="ProductProperty" setup> |
||||
import { dateFormatter } from '@/utils/formatTime' |
||||
import * as PropertyApi from '@/api/mall/product/property' |
||||
import PropertyForm from './PropertyForm.vue' |
||||
|
||||
const message = useMessage() // 消息弹窗 |
||||
const { t } = useI18n() // 国际化 |
||||
|
||||
const loading = ref(true) // 列表的加载中 |
||||
const total = ref(0) // 列表的总页数 |
||||
const list = ref([]) // 列表的数据 |
||||
const queryParams = reactive({ |
||||
pageNo: 1, |
||||
pageSize: 10, |
||||
name: undefined, |
||||
createTime: [] |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
|
||||
/** 查询列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
const data = await PropertyApi.getPropertyPage(queryParams) |
||||
list.value = data.list |
||||
total.value = data.total |
||||
} finally { |
||||
loading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 搜索按钮操作 */ |
||||
const handleQuery = () => { |
||||
queryParams.pageNo = 1 |
||||
getList() |
||||
} |
||||
|
||||
/** 重置按钮操作 */ |
||||
const resetQuery = () => { |
||||
queryFormRef.value.resetFields() |
||||
handleQuery() |
||||
} |
||||
|
||||
/** 添加/修改操作 */ |
||||
const formRef = ref() |
||||
const openForm = (type: string, id?: number) => { |
||||
formRef.value.open(type, id) |
||||
} |
||||
|
||||
/** 删除按钮操作 */ |
||||
const handleDelete = async (id: number) => { |
||||
try { |
||||
// 删除的二次确认 |
||||
await message.delConfirm() |
||||
// 发起删除 |
||||
await PropertyApi.deleteProperty(id) |
||||
message.success(t('common.delSuccess')) |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch {} |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(() => { |
||||
getList() |
||||
}) |
||||
</script> |
||||
@ -1,103 +0,0 @@ |
||||
<template> |
||||
<Dialog v-model="dialogVisible" :title="dialogTitle"> |
||||
<el-form |
||||
ref="formRef" |
||||
v-loading="formLoading" |
||||
:model="formData" |
||||
:rules="formRules" |
||||
label-width="80px" |
||||
> |
||||
<el-form-item label="属性编号" prop="category"> |
||||
<el-input v-model="formData.propertyId" disabled="" /> |
||||
</el-form-item> |
||||
<el-form-item label="名称" prop="name"> |
||||
<el-input v-model="formData.name" placeholder="请输入名称" /> |
||||
</el-form-item> |
||||
<el-form-item label="备注" prop="remark"> |
||||
<el-input v-model="formData.remark" placeholder="请输入内容" type="textarea" /> |
||||
</el-form-item> |
||||
</el-form> |
||||
<template #footer> |
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> |
||||
<el-button @click="dialogVisible = false">取 消</el-button> |
||||
</template> |
||||
</Dialog> |
||||
</template> |
||||
<script lang="ts" name="ProductPropertyValueForm" setup> |
||||
import * as PropertyApi from '@/api/mall/product/property' |
||||
|
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息弹窗 |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const dialogTitle = ref('') // 弹窗的标题 |
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
||||
const formData = ref({ |
||||
id: undefined, |
||||
propertyId: undefined, |
||||
name: '', |
||||
remark: '' |
||||
}) |
||||
const formRules = reactive({ |
||||
propertyId: [{ required: true, message: '属性不能为空', trigger: 'blur' }], |
||||
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }] |
||||
}) |
||||
const formRef = ref() // 表单 Ref |
||||
|
||||
/** 打开弹窗 */ |
||||
const open = async (type: string, propertyId: number, id?: number) => { |
||||
dialogVisible.value = true |
||||
dialogTitle.value = t('action.' + type) |
||||
formType.value = type |
||||
resetForm() |
||||
formData.value.propertyId = propertyId |
||||
// 修改时,设置数据 |
||||
if (id) { |
||||
formLoading.value = true |
||||
try { |
||||
formData.value = await PropertyApi.getPropertyValue(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 PropertyApi.PropertyValueVO |
||||
if (formType.value === 'create') { |
||||
await PropertyApi.createPropertyValue(data) |
||||
message.success(t('common.createSuccess')) |
||||
} else { |
||||
await PropertyApi.updatePropertyValue(data) |
||||
message.success(t('common.updateSuccess')) |
||||
} |
||||
dialogVisible.value = false |
||||
// 发送操作成功的事件 |
||||
emit('success') |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 重置表单 */ |
||||
const resetForm = () => { |
||||
formData.value = { |
||||
id: undefined, |
||||
propertyId: undefined, |
||||
name: '', |
||||
remark: '' |
||||
} |
||||
formRef.value?.resetFields() |
||||
} |
||||
</script> |
||||
@ -1,160 +0,0 @@ |
||||
<template> |
||||
<!-- 搜索工作栏 --> |
||||
<ContentWrap> |
||||
<el-form |
||||
class="-mb-15px" |
||||
:model="queryParams" |
||||
ref="queryFormRef" |
||||
:inline="true" |
||||
label-width="68px" |
||||
> |
||||
<el-form-item label="属性项" prop="propertyId"> |
||||
<el-select v-model="queryParams.propertyId" class="!w-240px"> |
||||
<el-option |
||||
v-for="item in propertyOptions" |
||||
:key="item.id" |
||||
:label="item.name" |
||||
:value="item.id" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
<el-form-item label="名称" prop="name"> |
||||
<el-input |
||||
v-model="queryParams.name" |
||||
placeholder="请输入名称" |
||||
clearable |
||||
@keyup.enter="handleQuery" |
||||
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 |
||||
plain |
||||
type="primary" |
||||
@click="openForm('create')" |
||||
v-hasPermi="['product:property:create']" |
||||
> |
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增 |
||||
</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
</ContentWrap> |
||||
|
||||
<!-- 列表 --> |
||||
<ContentWrap> |
||||
<el-table v-loading="loading" :data="list"> |
||||
<el-table-column label="编号" align="center" prop="id" /> |
||||
<el-table-column label="名称" align="center" prop="name" :show-overflow-tooltip="true" /> |
||||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" /> |
||||
<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="openForm('update', scope.row.id)" |
||||
v-hasPermi="['product:property:update']" |
||||
> |
||||
编辑 |
||||
</el-button> |
||||
<el-button |
||||
link |
||||
type="danger" |
||||
@click="handleDelete(scope.row.id)" |
||||
v-hasPermi="['product:property: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> |
||||
|
||||
<!-- 表单弹窗:添加/修改 --> |
||||
<ValueForm ref="formRef" @success="getList" /> |
||||
</template> |
||||
<script setup lang="ts" name="ProductPropertyValue"> |
||||
import { dateFormatter } from '@/utils/formatTime' |
||||
import * as PropertyApi from '@/api/mall/product/property' |
||||
import ValueForm from './ValueForm.vue' |
||||
const message = useMessage() // 消息弹窗 |
||||
const { t } = useI18n() // 国际化 |
||||
const { params } = useRoute() // 查询参数 |
||||
|
||||
const loading = ref(true) // 列表的加载中 |
||||
const total = ref(0) // 列表的总页数 |
||||
const list = ref([]) // 列表的数据 |
||||
const queryParams = reactive({ |
||||
pageNo: 1, |
||||
pageSize: 10, |
||||
propertyId: Number(params.propertyId), |
||||
name: undefined |
||||
}) |
||||
const queryFormRef = ref() // 搜索的表单 |
||||
const propertyOptions = ref([]) // 属性项的列表 |
||||
|
||||
/** 查询列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
const data = await PropertyApi.getPropertyValuePage(queryParams) |
||||
list.value = data.list |
||||
total.value = data.total |
||||
} finally { |
||||
loading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 搜索按钮操作 */ |
||||
const handleQuery = () => { |
||||
queryParams.pageNo = 1 |
||||
getList() |
||||
} |
||||
|
||||
/** 重置按钮操作 */ |
||||
const resetQuery = () => { |
||||
queryFormRef.value.resetFields() |
||||
handleQuery() |
||||
} |
||||
|
||||
/** 添加/修改操作 */ |
||||
const formRef = ref() |
||||
const openForm = (type: string, id?: number) => { |
||||
formRef.value.open(type, queryParams.propertyId, id) |
||||
} |
||||
|
||||
/** 删除按钮操作 */ |
||||
const handleDelete = async (id: number) => { |
||||
try { |
||||
// 删除的二次确认 |
||||
await message.delConfirm() |
||||
// 发起删除 |
||||
await PropertyApi.deleteProperty(id) |
||||
message.success(t('common.delSuccess')) |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch {} |
||||
} |
||||
|
||||
/** 初始化 **/ |
||||
onMounted(async () => { |
||||
await getList() |
||||
// 属性项下拉框数据 |
||||
propertyOptions.value = await PropertyApi.getPropertyList({}) |
||||
}) |
||||
</script> |
||||
@ -1,178 +0,0 @@ |
||||
<template> |
||||
<ContentWrap v-loading="formLoading"> |
||||
<el-tabs v-model="activeName"> |
||||
<el-tab-pane label="商品信息" name="basicInfo"> |
||||
<BasicInfoForm |
||||
ref="basicInfoRef" |
||||
v-model:activeName="activeName" |
||||
:propFormData="formData" |
||||
/> |
||||
</el-tab-pane> |
||||
<el-tab-pane label="商品详情" name="description"> |
||||
<DescriptionForm |
||||
ref="descriptionRef" |
||||
v-model:activeName="activeName" |
||||
:propFormData="formData" |
||||
/> |
||||
</el-tab-pane> |
||||
<el-tab-pane label="其他设置" name="otherSettings"> |
||||
<OtherSettingsForm |
||||
ref="otherSettingsRef" |
||||
v-model:activeName="activeName" |
||||
:propFormData="formData" |
||||
/> |
||||
</el-tab-pane> |
||||
</el-tabs> |
||||
<el-form> |
||||
<el-form-item style="float: right"> |
||||
<el-button :loading="formLoading" type="primary" @click="submitForm">保存</el-button> |
||||
<el-button @click="close">返回</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
</ContentWrap> |
||||
</template> |
||||
<script lang="ts" name="ProductSpuForm" setup> |
||||
import { cloneDeep } from 'lodash-es' |
||||
import { useTagsViewStore } from '@/store/modules/tagsView' |
||||
import { BasicInfoForm, DescriptionForm, OtherSettingsForm } from './components' |
||||
// 业务api |
||||
import * as ProductSpuApi from '@/api/mall/product/spu' |
||||
import { convertToInteger, formatToFraction } from '@/utils' |
||||
|
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息弹窗 |
||||
const { push, currentRoute } = useRouter() // 路由 |
||||
const { params } = useRoute() // 查询参数 |
||||
const { delView } = useTagsViewStore() // 视图操作 |
||||
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
||||
const activeName = ref('basicInfo') // Tag 激活的窗口 |
||||
const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>() // 商品信息Ref |
||||
const descriptionRef = ref<ComponentRef<typeof DescriptionForm>>() // 商品详情Ref |
||||
const otherSettingsRef = ref<ComponentRef<typeof OtherSettingsForm>>() // 其他设置Ref |
||||
// spu 表单数据 |
||||
const formData = ref<ProductSpuApi.SpuType>({ |
||||
name: '', // 商品名称 |
||||
categoryId: null, // 商品分类 |
||||
keyword: '', // 关键字 |
||||
unit: null, // 单位 |
||||
picUrl: '', // 商品封面图 |
||||
sliderPicUrls: [], // 商品轮播图 |
||||
introduction: '', // 商品简介 |
||||
deliveryTemplateId: 1, // 运费模版 |
||||
brandId: null, // 商品品牌 |
||||
specType: false, // 商品规格 |
||||
subCommissionType: false, // 分销类型 |
||||
skus: [ |
||||
{ |
||||
price: 0, // 商品价格 |
||||
marketPrice: 0, // 市场价 |
||||
costPrice: 0, // 成本价 |
||||
barCode: '', // 商品条码 |
||||
picUrl: '', // 图片地址 |
||||
stock: 0, // 库存 |
||||
weight: 0, // 商品重量 |
||||
volume: 0, // 商品体积 |
||||
subCommissionFirstPrice: 0, // 一级分销的佣金 |
||||
subCommissionSecondPrice: 0 // 二级分销的佣金 |
||||
} |
||||
], |
||||
description: '', // 商品详情 |
||||
sort: 0, // 商品排序 |
||||
giveIntegral: 0, // 赠送积分 |
||||
virtualSalesCount: 0, // 虚拟销量 |
||||
recommendHot: false, // 是否热卖 |
||||
recommendBenefit: false, // 是否优惠 |
||||
recommendBest: false, // 是否精品 |
||||
recommendNew: false, // 是否新品 |
||||
recommendGood: false // 是否优品 |
||||
}) |
||||
|
||||
/** 获得详情 */ |
||||
const getDetail = async () => { |
||||
const id = params.spuId as number |
||||
if (id) { |
||||
formLoading.value = true |
||||
try { |
||||
const res = (await ProductSpuApi.getSpu(id)) as ProductSpuApi.SpuType |
||||
res.skus.forEach((item) => { |
||||
// 回显价格分转元 |
||||
item.price = formatToFraction(item.price) |
||||
item.marketPrice = formatToFraction(item.marketPrice) |
||||
item.costPrice = formatToFraction(item.costPrice) |
||||
item.subCommissionFirstPrice = formatToFraction(item.subCommissionFirstPrice) |
||||
item.subCommissionSecondPrice = formatToFraction(item.subCommissionSecondPrice) |
||||
}) |
||||
formData.value = res |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** 提交按钮 */ |
||||
const submitForm = async () => { |
||||
// 提交请求 |
||||
formLoading.value = true |
||||
// 三个表单逐一校验,如果有一个表单校验不通过则切换到对应表单,如果有两个及以上的情况则切换到最前面的一个并弹出提示消息 |
||||
// 校验各表单 |
||||
try { |
||||
await unref(basicInfoRef)?.validate() |
||||
await unref(descriptionRef)?.validate() |
||||
await unref(otherSettingsRef)?.validate() |
||||
const deepCopyFormData = cloneDeep(unref(formData.value)) // 深拷贝一份 fix:这样最终 server 端不满足,不需要恢复, |
||||
// TODO 兜底处理 sku 空数据 |
||||
formData.value.skus.forEach((sku) => { |
||||
// 因为是空数据这里判断一下商品条码是否为空就行 |
||||
if (sku.barCode === '') { |
||||
const index = deepCopyFormData.skus.findIndex( |
||||
(item) => JSON.stringify(item.properties) === JSON.stringify(sku.properties) |
||||
) |
||||
// 删除这条 sku |
||||
deepCopyFormData.skus.splice(index, 1) |
||||
} |
||||
}) |
||||
deepCopyFormData.skus.forEach((item) => { |
||||
// 给sku name赋值 |
||||
item.name = deepCopyFormData.name |
||||
// sku相关价格元转分 |
||||
item.price = convertToInteger(item.price) |
||||
item.marketPrice = convertToInteger(item.marketPrice) |
||||
item.costPrice = convertToInteger(item.costPrice) |
||||
item.subCommissionFirstPrice = convertToInteger(item.subCommissionFirstPrice) |
||||
item.subCommissionSecondPrice = convertToInteger(item.subCommissionSecondPrice) |
||||
}) |
||||
// 处理轮播图列表 |
||||
const newSliderPicUrls = [] |
||||
deepCopyFormData.sliderPicUrls.forEach((item) => { |
||||
// 如果是前端选的图 |
||||
typeof item === 'object' ? newSliderPicUrls.push(item.url) : newSliderPicUrls.push(item) |
||||
}) |
||||
deepCopyFormData.sliderPicUrls = newSliderPicUrls |
||||
// 校验都通过后提交表单 |
||||
const data = deepCopyFormData as ProductSpuApi.SpuType |
||||
const id = params.spuId as number |
||||
if (!id) { |
||||
await ProductSpuApi.createSpu(data) |
||||
message.success(t('common.createSuccess')) |
||||
} else { |
||||
await ProductSpuApi.updateSpu(data) |
||||
message.success(t('common.updateSuccess')) |
||||
} |
||||
close() |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 关闭按钮 */ |
||||
const close = () => { |
||||
delView(unref(currentRoute)) |
||||
push('/product/product-spu') |
||||
} |
||||
|
||||
/** 初始化 */ |
||||
onMounted(async () => { |
||||
await getDetail() |
||||
}) |
||||
</script> |
||||
@ -1,274 +0,0 @@ |
||||
<template> |
||||
<el-form ref="productSpuBasicInfoRef" :model="formData" :rules="rules" label-width="120px"> |
||||
<el-row> |
||||
<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"> |
||||
<!-- TODO @puhui999:只能选根节点 --> |
||||
<el-form-item label="商品分类" prop="categoryId"> |
||||
<el-tree-select |
||||
v-model="formData.categoryId" |
||||
:data="categoryList" |
||||
:props="defaultProps" |
||||
check-strictly |
||||
class="w-1/1" |
||||
node-key="id" |
||||
placeholder="请选择商品分类" |
||||
/> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-form-item label="商品关键字" prop="keyword"> |
||||
<el-input v-model="formData.keyword" placeholder="请输入商品关键字" /> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-form-item label="单位" prop="unit"> |
||||
<el-select v-model="formData.unit" class="w-1/1" placeholder="请选择单位"> |
||||
<el-option |
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRODUCT_UNIT)" |
||||
:key="dict.value" |
||||
:label="dict.label" |
||||
:value="dict.value" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-form-item label="商品简介" prop="introduction"> |
||||
<el-input |
||||
v-model="formData.introduction" |
||||
:rows="3" |
||||
placeholder="请输入商品简介" |
||||
type="textarea" |
||||
/> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-form-item label="商品封面图" prop="picUrl"> |
||||
<UploadImg v-model="formData.picUrl" height="80px" /> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="24"> |
||||
<el-form-item label="商品轮播图" prop="sliderPicUrls"> |
||||
<UploadImgs v-model:modelValue="formData.sliderPicUrls" /> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-form-item label="运费模板" prop="deliveryTemplateId"> |
||||
<el-select v-model="formData.deliveryTemplateId" placeholder="请选择"> |
||||
<el-option v-for="item in []" :key="item.id" :label="item.name" :value="item.id" /> |
||||
</el-select> |
||||
<el-button class="ml-20px">运费模板</el-button> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-form-item label="品牌" prop="brandId"> |
||||
<el-select v-model="formData.brandId" placeholder="请选择"> |
||||
<el-option |
||||
v-for="item in brandList" |
||||
:key="item.id" |
||||
:label="item.name" |
||||
:value="item.id" |
||||
/> |
||||
</el-select> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-form-item label="商品规格" props="specType"> |
||||
<el-radio-group v-model="formData.specType" @change="onChangeSpec"> |
||||
<el-radio :label="false" class="radio">单规格</el-radio> |
||||
<el-radio :label="true">多规格</el-radio> |
||||
</el-radio-group> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="12"> |
||||
<el-form-item label="分销类型" props="subCommissionType"> |
||||
<el-radio-group v-model="formData.subCommissionType" @change="changeSubCommissionType"> |
||||
<el-radio :label="false">默认设置</el-radio> |
||||
<el-radio :label="true" class="radio">自行设置</el-radio> |
||||
</el-radio-group> |
||||
</el-form-item> |
||||
</el-col> |
||||
<!-- 多规格添加--> |
||||
<el-col :span="24"> |
||||
<el-form-item v-if="formData.specType" label="商品属性"> |
||||
<el-button class="mr-15px mb-10px" @click="attributesAddFormRef.open">添加规格</el-button> |
||||
<ProductAttributes :propertyList="propertyList" @success="generateSkus" /> |
||||
</el-form-item> |
||||
<template v-if="formData.specType && propertyList.length > 0"> |
||||
<el-form-item label="批量设置"> |
||||
<SkuList :is-batch="true" :prop-form-data="formData" :propertyList="propertyList" /> |
||||
</el-form-item> |
||||
<el-form-item label="属性列表"> |
||||
<SkuList ref="skuListRef" :prop-form-data="formData" :propertyList="propertyList" /> |
||||
</el-form-item> |
||||
</template> |
||||
<el-form-item v-if="!formData.specType"> |
||||
<SkuList :prop-form-data="formData" :propertyList="propertyList" /> |
||||
</el-form-item> |
||||
</el-col> |
||||
</el-row> |
||||
</el-form> |
||||
<ProductAttributesAddForm ref="attributesAddFormRef" :propertyList="propertyList" /> |
||||
</template> |
||||
<script lang="ts" name="ProductSpuBasicInfoForm" setup> |
||||
import { PropType } from 'vue' |
||||
import { copyValueToTarget } from '@/utils' |
||||
import { propTypes } from '@/utils/propTypes' |
||||
import { defaultProps, handleTree } from '@/utils/tree' |
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
import type { SpuType } from '@/api/mall/product/spu' |
||||
import { UploadImg, UploadImgs } from '@/components/UploadFile' |
||||
import { ProductAttributes, ProductAttributesAddForm, SkuList } from './index' |
||||
import * as ProductCategoryApi from '@/api/mall/product/category' |
||||
import { getSimpleBrandList } from '@/api/mall/product/brand' |
||||
|
||||
const message = useMessage() // 消息弹窗 |
||||
|
||||
const props = defineProps({ |
||||
propFormData: { |
||||
type: Object as PropType<SpuType>, |
||||
default: () => {} |
||||
}, |
||||
activeName: propTypes.string.def('') |
||||
}) |
||||
const attributesAddFormRef = ref() // 添加商品属性表单 |
||||
const productSpuBasicInfoRef = ref() // 表单 Ref |
||||
const propertyList = ref([]) // 商品属性列表 |
||||
const skuListRef = ref() // 商品属性列表Ref |
||||
/** 调用 SkuList generateTableData 方法*/ |
||||
const generateSkus = (propertyList) => { |
||||
skuListRef.value.generateTableData(propertyList) |
||||
} |
||||
const formData = reactive<SpuType>({ |
||||
name: '', // 商品名称 |
||||
categoryId: null, // 商品分类 |
||||
keyword: '', // 关键字 |
||||
unit: '', // 单位 |
||||
picUrl: '', // 商品封面图 |
||||
sliderPicUrls: [], // 商品轮播图 |
||||
introduction: '', // 商品简介 |
||||
deliveryTemplateId: 1, // 运费模版 |
||||
brandId: null, // 商品品牌 |
||||
specType: false, // 商品规格 |
||||
subCommissionType: false, // 分销类型 |
||||
skus: [] |
||||
}) |
||||
const rules = reactive({ |
||||
name: [required], |
||||
categoryId: [required], |
||||
keyword: [required], |
||||
unit: [required], |
||||
introduction: [required], |
||||
picUrl: [required], |
||||
sliderPicUrls: [required], |
||||
// deliveryTemplateId: [required], |
||||
brandId: [required], |
||||
specType: [required], |
||||
subCommissionType: [required] |
||||
}) |
||||
|
||||
/** |
||||
* 将传进来的值赋值给 formData |
||||
*/ |
||||
watch( |
||||
() => props.propFormData, |
||||
(data) => { |
||||
if (!data) { |
||||
return |
||||
} |
||||
copyValueToTarget(formData, data) |
||||
formData.sliderPicUrls = data['sliderPicUrls'].map((item) => ({ |
||||
url: item |
||||
})) |
||||
// TODO @puhui999:if return,减少嵌套层级 |
||||
// 只有是多规格才处理 |
||||
if (formData.specType) { |
||||
// 直接拿返回的 skus 属性逆向生成出 propertyList |
||||
const properties = [] |
||||
formData.skus.forEach((sku) => { |
||||
sku.properties.forEach(({ propertyId, propertyName, valueId, valueName }) => { |
||||
// 添加属性 |
||||
if (!properties.some((item) => item.id === propertyId)) { |
||||
properties.push({ id: propertyId, name: propertyName, values: [] }) |
||||
} |
||||
// 添加属性值 |
||||
const index = properties.findIndex((item) => item.id === propertyId) |
||||
if (!properties[index].values.some((value) => value.id === valueId)) { |
||||
properties[index].values.push({ id: valueId, name: valueName }) |
||||
} |
||||
}) |
||||
}) |
||||
propertyList.value = properties |
||||
} |
||||
}, |
||||
{ |
||||
immediate: true |
||||
} |
||||
) |
||||
|
||||
/** |
||||
* 表单校验 |
||||
*/ |
||||
const emit = defineEmits(['update:activeName']) |
||||
const validate = async () => { |
||||
// 校验表单 |
||||
if (!productSpuBasicInfoRef) return |
||||
return await unref(productSpuBasicInfoRef).validate((valid) => { |
||||
if (!valid) { |
||||
message.warning('商品信息未完善!!') |
||||
emit('update:activeName', 'basicInfo') |
||||
// 目的截断之后的校验 |
||||
throw new Error('商品信息未完善!!') |
||||
} else { |
||||
// 校验通过更新数据 |
||||
Object.assign(props.propFormData, formData) |
||||
} |
||||
}) |
||||
} |
||||
defineExpose({ validate }) |
||||
|
||||
/** 分销类型 */ |
||||
const changeSubCommissionType = () => { |
||||
// 默认为零,类型切换后也要重置为零 |
||||
for (const item of formData.skus) { |
||||
item.subCommissionFirstPrice = 0 |
||||
item.subCommissionSecondPrice = 0 |
||||
} |
||||
} |
||||
|
||||
/** 选择规格 */ |
||||
const onChangeSpec = () => { |
||||
// 重置商品属性列表 |
||||
propertyList.value = [] |
||||
// 重置sku列表 |
||||
formData.skus = [ |
||||
{ |
||||
price: 0, |
||||
marketPrice: 0, |
||||
costPrice: 0, |
||||
barCode: '', |
||||
picUrl: '', |
||||
stock: 0, |
||||
weight: 0, |
||||
volume: 0, |
||||
subCommissionFirstPrice: 0, |
||||
subCommissionSecondPrice: 0 |
||||
} |
||||
] |
||||
} |
||||
|
||||
const categoryList = ref([]) // 分类树 |
||||
const brandList = ref([]) // 精简商品品牌列表 |
||||
onMounted(async () => { |
||||
// 获得分类树 |
||||
const data = await ProductCategoryApi.getCategoryList({}) |
||||
categoryList.value = handleTree(data, 'id', 'parentId') |
||||
// 获取商品品牌列表 |
||||
brandList.value = await getSimpleBrandList() |
||||
}) |
||||
</script> |
||||
@ -1,83 +0,0 @@ |
||||
<template> |
||||
<el-form ref="descriptionFormRef" :model="formData" :rules="rules" label-width="120px"> |
||||
<!--富文本编辑器组件--> |
||||
<el-form-item label="商品详情" prop="description"> |
||||
<Editor v-model:modelValue="formData.description" /> |
||||
</el-form-item> |
||||
</el-form> |
||||
</template> |
||||
<script lang="ts" name="DescriptionForm" setup> |
||||
import type { SpuType } from '@/api/mall/product/spu' |
||||
import { Editor } from '@/components/Editor' |
||||
import { PropType } from 'vue' |
||||
import { propTypes } from '@/utils/propTypes' |
||||
import { copyValueToTarget } from '@/utils' |
||||
|
||||
const message = useMessage() // 消息弹窗 |
||||
const props = defineProps({ |
||||
propFormData: { |
||||
type: Object as PropType<SpuType>, |
||||
default: () => {} |
||||
}, |
||||
activeName: propTypes.string.def('') |
||||
}) |
||||
const descriptionFormRef = ref() // 表单Ref |
||||
const formData = ref<SpuType>({ |
||||
description: '' // 商品详情 |
||||
}) |
||||
// 表单规则 |
||||
const rules = reactive({ |
||||
description: [required] |
||||
}) |
||||
/** |
||||
* 富文本编辑器如果输入过再清空会有残留,需再重置一次 |
||||
*/ |
||||
watch( |
||||
() => formData.value.description, |
||||
(newValue) => { |
||||
if ('<p><br></p>' === newValue) { |
||||
formData.value.description = '' |
||||
} |
||||
}, |
||||
{ |
||||
deep: true, |
||||
immediate: true |
||||
} |
||||
) |
||||
/** |
||||
* 将传进来的值赋值给formData |
||||
*/ |
||||
watch( |
||||
() => props.propFormData, |
||||
(data) => { |
||||
if (!data) return |
||||
// fix:三个表单组件监听赋值必须使用 copyValueToTarget 使用 formData.value = data 会监听非常多次 |
||||
copyValueToTarget(formData.value, data) |
||||
}, |
||||
{ |
||||
// fix: 去掉深度监听只有对象引用发生改变的时候才执行,解决改一动多的问题 |
||||
immediate: true |
||||
} |
||||
) |
||||
|
||||
/** |
||||
* 表单校验 |
||||
*/ |
||||
const emit = defineEmits(['update:activeName']) |
||||
const validate = async () => { |
||||
// 校验表单 |
||||
if (!descriptionFormRef) return |
||||
return await unref(descriptionFormRef).validate((valid) => { |
||||
if (!valid) { |
||||
message.warning('商品详情为完善!!') |
||||
emit('update:activeName', 'description') |
||||
// 目的截断之后的校验 |
||||
throw new Error('商品详情为完善!!') |
||||
} else { |
||||
// 校验通过更新数据 |
||||
Object.assign(props.propFormData, formData.value) |
||||
} |
||||
}) |
||||
} |
||||
defineExpose({ validate }) |
||||
</script> |
||||
@ -1,145 +0,0 @@ |
||||
<template> |
||||
<el-form ref="otherSettingsFormRef" :model="formData" :rules="rules" label-width="120px"> |
||||
<el-row> |
||||
<el-col :span="24"> |
||||
<el-row :gutter="20"> |
||||
<el-col :span="8"> |
||||
<el-form-item label="商品排序" prop="sort"> |
||||
<el-input-number v-model="formData.sort" :min="0" /> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="8"> |
||||
<el-form-item label="赠送积分" prop="giveIntegral"> |
||||
<el-input-number v-model="formData.giveIntegral" :min="0" /> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="8"> |
||||
<el-form-item label="虚拟销量" prop="virtualSalesCount"> |
||||
<el-input-number |
||||
v-model="formData.virtualSalesCount" |
||||
:min="0" |
||||
placeholder="请输入虚拟销量" |
||||
/> |
||||
</el-form-item> |
||||
</el-col> |
||||
</el-row> |
||||
</el-col> |
||||
<el-col :span="24"> |
||||
<el-form-item label="商品推荐"> |
||||
<el-checkbox-group v-model="checkboxGroup" @change="onChangeGroup"> |
||||
<el-checkbox v-for="(item, index) in recommendOptions" :key="index" :label="item.value"> |
||||
{{ item.name }} |
||||
</el-checkbox> |
||||
</el-checkbox-group> |
||||
</el-form-item> |
||||
</el-col> |
||||
<el-col :span="24"> |
||||
<!-- TODO tag展示暂时不考虑排序 --> |
||||
<el-form-item label="活动优先级"> |
||||
<el-tag>默认</el-tag> |
||||
<el-tag class="ml-2" type="success">秒杀</el-tag> |
||||
<el-tag class="ml-2" type="info">砍价</el-tag> |
||||
<el-tag class="ml-2" type="warning">拼团</el-tag> |
||||
</el-form-item> |
||||
</el-col> |
||||
<!-- TODO @puhui999:等优惠劵 ok 在搞 --> |
||||
<el-col :span="24"> |
||||
<el-form-item label="赠送优惠劵"> |
||||
<el-button>选择优惠券</el-button> |
||||
</el-form-item> |
||||
</el-col> |
||||
</el-row> |
||||
</el-form> |
||||
</template> |
||||
<script lang="ts" name="OtherSettingsForm" setup> |
||||
import type { SpuType } from '@/api/mall/product/spu' |
||||
import { PropType } from 'vue' |
||||
import { propTypes } from '@/utils/propTypes' |
||||
import { copyValueToTarget } from '@/utils' |
||||
|
||||
const message = useMessage() // 消息弹窗 |
||||
|
||||
const props = defineProps({ |
||||
propFormData: { |
||||
type: Object as PropType<SpuType>, |
||||
default: () => {} |
||||
}, |
||||
activeName: propTypes.string.def('') |
||||
}) |
||||
|
||||
const otherSettingsFormRef = ref() // 表单Ref |
||||
// 表单数据 |
||||
const formData = ref<SpuType>({ |
||||
sort: 1, // 商品排序 |
||||
giveIntegral: 1, // 赠送积分 |
||||
virtualSalesCount: 1, // 虚拟销量 |
||||
recommendHot: false, // 是否热卖 |
||||
recommendBenefit: false, // 是否优惠 |
||||
recommendBest: false, // 是否精品 |
||||
recommendNew: false, // 是否新品 |
||||
recommendGood: false // 是否优品 |
||||
}) |
||||
// 表单规则 |
||||
const rules = reactive({ |
||||
sort: [required], |
||||
giveIntegral: [required], |
||||
virtualSalesCount: [required] |
||||
}) |
||||
const recommendOptions = [ |
||||
{ name: '是否热卖', value: 'recommendHot' }, |
||||
{ name: '是否优惠', value: 'recommendBenefit' }, |
||||
{ name: '是否精品', value: 'recommendBest' }, |
||||
{ name: '是否新品', value: 'recommendNew' }, |
||||
{ name: '是否优品', value: 'recommendGood' } |
||||
] // 商品推荐选项 |
||||
const checkboxGroup = ref<string[]>([]) // 选中的推荐选项 |
||||
|
||||
/** 选择商品后赋值 */ |
||||
const onChangeGroup = () => { |
||||
recommendOptions.forEach(({ value }) => { |
||||
formData.value[value] = checkboxGroup.value.includes(value) |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* 将传进来的值赋值给formData |
||||
*/ |
||||
watch( |
||||
() => props.propFormData, |
||||
(data) => { |
||||
if (!data) { |
||||
return |
||||
} |
||||
copyValueToTarget(formData.value, data) |
||||
recommendOptions.forEach(({ value }) => { |
||||
if (formData.value[value] && !checkboxGroup.value.includes(value)) { |
||||
checkboxGroup.value.push(value) |
||||
} |
||||
}) |
||||
}, |
||||
{ |
||||
immediate: true |
||||
} |
||||
) |
||||
|
||||
/** |
||||
* 表单校验 |
||||
*/ |
||||
const emit = defineEmits(['update:activeName']) |
||||
const validate = async () => { |
||||
// 校验表单 |
||||
if (!otherSettingsFormRef) return |
||||
return await unref(otherSettingsFormRef).validate((valid) => { |
||||
if (!valid) { |
||||
message.warning('商品其他设置未完善!!') |
||||
emit('update:activeName', 'otherSettings') |
||||
// 目的截断之后的校验 |
||||
throw new Error('商品其他设置未完善!!') |
||||
} else { |
||||
// 校验通过更新数据 |
||||
Object.assign(props.propFormData, formData.value) |
||||
} |
||||
}) |
||||
} |
||||
defineExpose({ validate }) |
||||
</script> |
||||
@ -1,117 +0,0 @@ |
||||
<template> |
||||
<el-col v-for="(item, index) in attributeList" :key="index"> |
||||
<div> |
||||
<el-text class="mx-1">属性名:</el-text> |
||||
<el-tag class="mx-1" closable type="success" @close="handleCloseProperty(index)" |
||||
>{{ item.name }} |
||||
</el-tag> |
||||
</div> |
||||
<div> |
||||
<el-text class="mx-1">属性值:</el-text> |
||||
<el-tag |
||||
v-for="(value, valueIndex) in item.values" |
||||
:key="value.id" |
||||
class="mx-1" |
||||
closable |
||||
@close="handleCloseValue(index, valueIndex)" |
||||
> |
||||
{{ value.name }} |
||||
</el-tag> |
||||
<el-input |
||||
v-show="inputVisible(index)" |
||||
:id="`input${index}`" |
||||
:ref="setInputRef" |
||||
v-model="inputValue" |
||||
class="!w-20" |
||||
size="small" |
||||
@blur="handleInputConfirm(index, item.id)" |
||||
@keyup.enter="handleInputConfirm(index, item.id)" |
||||
/> |
||||
<el-button |
||||
v-show="!inputVisible(index)" |
||||
class="button-new-tag ml-1" |
||||
size="small" |
||||
@click="showInput(index)" |
||||
> |
||||
+ 添加 |
||||
</el-button> |
||||
</div> |
||||
<el-divider class="my-10px" /> |
||||
</el-col> |
||||
</template> |
||||
|
||||
<script lang="ts" name="ProductAttributes" setup> |
||||
import { ElInput } from 'element-plus' |
||||
import * as PropertyApi from '@/api/mall/product/property' |
||||
|
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息弹窗 |
||||
const inputValue = ref('') // 输入框值 |
||||
const attributeIndex = ref<number | null>(null) // 获取焦点时记录当前属性项的index |
||||
// 输入框显隐控制 |
||||
const inputVisible = computed(() => (index) => { |
||||
if (attributeIndex.value === null) return false |
||||
if (attributeIndex.value === index) return true |
||||
}) |
||||
const inputRef = ref([]) //标签输入框Ref |
||||
/** 解决 ref 在 v-for 中的获取问题*/ |
||||
const setInputRef = (el) => { |
||||
if (el === null || typeof el === 'undefined') return |
||||
// 如果不存在id相同的元素才添加 |
||||
if (!inputRef.value.some((item) => item.input?.attributes.id === el.input?.attributes.id)) { |
||||
inputRef.value.push(el) |
||||
} |
||||
} |
||||
const attributeList = ref([]) // 商品属性列表 |
||||
const props = defineProps({ |
||||
propertyList: { |
||||
type: Array, |
||||
default: () => {} |
||||
} |
||||
}) |
||||
|
||||
watch( |
||||
() => props.propertyList, |
||||
(data) => { |
||||
if (!data) return |
||||
attributeList.value = data |
||||
}, |
||||
{ |
||||
deep: true, |
||||
immediate: true |
||||
} |
||||
) |
||||
|
||||
/** 删除属性值*/ |
||||
const handleCloseValue = (index, valueIndex) => { |
||||
attributeList.value[index].values?.splice(valueIndex, 1) |
||||
} |
||||
/** 删除属性*/ |
||||
const handleCloseProperty = (index) => { |
||||
attributeList.value?.splice(index, 1) |
||||
} |
||||
/** 显示输入框并获取焦点 */ |
||||
const showInput = async (index) => { |
||||
attributeIndex.value = index |
||||
inputRef.value[index].focus() |
||||
} |
||||
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |
||||
|
||||
/** 输入框失去焦点或点击回车时触发 */ |
||||
const handleInputConfirm = async (index, propertyId) => { |
||||
if (inputValue.value) { |
||||
// 保存属性值 |
||||
try { |
||||
const id = await PropertyApi.createPropertyValue({ propertyId, name: inputValue.value }) |
||||
attributeList.value[index].values.push({ id, name: inputValue.value }) |
||||
message.success(t('common.createSuccess')) |
||||
emit('success', attributeList.value) |
||||
} catch { |
||||
message.error('添加失败,请重试') // TODO 缺少国际化 |
||||
} |
||||
} |
||||
attributeIndex.value = null |
||||
inputValue.value = '' |
||||
} |
||||
</script> |
||||
@ -1,98 +0,0 @@ |
||||
<template> |
||||
<Dialog v-model="dialogVisible" :title="dialogTitle"> |
||||
<el-form |
||||
ref="formRef" |
||||
v-loading="formLoading" |
||||
: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> |
||||
<template #footer> |
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> |
||||
<el-button @click="dialogVisible = false">取 消</el-button> |
||||
</template> |
||||
</Dialog> |
||||
</template> |
||||
<script lang="ts" name="ProductPropertyForm" setup> |
||||
import * as PropertyApi from '@/api/mall/product/property' |
||||
|
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息弹窗 |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const dialogTitle = ref('添加商品属性') // 弹窗的标题 |
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
||||
const formData = ref({ |
||||
name: '' |
||||
}) |
||||
const formRules = reactive({ |
||||
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }] |
||||
}) |
||||
const formRef = ref() // 表单 Ref |
||||
const attributeList = ref([]) // 商品属性列表 |
||||
const props = defineProps({ |
||||
propertyList: { |
||||
type: Array, |
||||
default: () => {} |
||||
} |
||||
}) |
||||
|
||||
watch( |
||||
() => props.propertyList, |
||||
(data) => { |
||||
if (!data) return |
||||
attributeList.value = data |
||||
}, |
||||
{ |
||||
deep: true, |
||||
immediate: true |
||||
} |
||||
) |
||||
/** 打开弹窗 */ |
||||
const open = async () => { |
||||
dialogVisible.value = true |
||||
resetForm() |
||||
} |
||||
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 PropertyApi.PropertyVO |
||||
// 检查属性是否已存在,如果有则返回属性和其下属性值 |
||||
const res = await PropertyApi.getPropertyListAndValue({ name: data.name }) |
||||
if (res.length === 0) { |
||||
const propertyId = await PropertyApi.createProperty(data) |
||||
attributeList.value.push({ id: propertyId, ...formData.value, values: [] }) |
||||
} else { |
||||
if (res[0].values === null) { |
||||
res[0].values = [] |
||||
} |
||||
attributeList.value.push(res[0]) // 因为只用一个 |
||||
} |
||||
message.success(t('common.createSuccess')) |
||||
dialogVisible.value = false |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 重置表单 */ |
||||
const resetForm = () => { |
||||
formData.value = { |
||||
name: '', |
||||
remark: '' |
||||
} |
||||
formRef.value?.resetFields() |
||||
} |
||||
</script> |
||||
@ -1,317 +0,0 @@ |
||||
<template> |
||||
<el-table |
||||
:data="isBatch ? skuList : formData.skus" |
||||
border |
||||
class="tabNumWidth" |
||||
max-height="500" |
||||
size="small" |
||||
> |
||||
<el-table-column align="center" fixed="left" label="图片" min-width="100"> |
||||
<template #default="{ row }"> |
||||
<UploadImg v-model="row.picUrl" height="80px" width="100%" /> |
||||
</template> |
||||
</el-table-column> |
||||
<template v-if="formData.specType && !isBatch"> |
||||
<!-- 根据商品属性动态添加 --> |
||||
<el-table-column |
||||
v-for="(item, index) in tableHeaders" |
||||
:key="index" |
||||
:label="item.label" |
||||
align="center" |
||||
min-width="120" |
||||
> |
||||
<template #default="{ row }"> |
||||
<!-- TODO puhui999:展示成蓝色,有点区分度哈 --> |
||||
{{ row.properties[index]?.valueName }} |
||||
</template> |
||||
</el-table-column> |
||||
</template> |
||||
<el-table-column align="center" label="商品条码" min-width="168"> |
||||
<template #default="{ row }"> |
||||
<el-input v-model="row.barCode" class="w-100%" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column align="center" label="销售价(元)" min-width="168"> |
||||
<template #default="{ row }"> |
||||
<el-input-number v-model="row.price" :min="0" :precision="2" :step="0.1" class="w-100%" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column align="center" label="市场价(元)" min-width="168"> |
||||
<template #default="{ row }"> |
||||
<el-input-number |
||||
v-model="row.marketPrice" |
||||
:min="0" |
||||
:precision="2" |
||||
:step="0.1" |
||||
class="w-100%" |
||||
/> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column align="center" label="成本价(元)" min-width="168"> |
||||
<template #default="{ row }"> |
||||
<el-input-number |
||||
v-model="row.costPrice" |
||||
:min="0" |
||||
:precision="2" |
||||
:step="0.1" |
||||
class="w-100%" |
||||
/> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column align="center" label="库存" min-width="168"> |
||||
<template #default="{ row }"> |
||||
<el-input-number v-model="row.stock" :min="0" class="w-100%" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column align="center" label="重量(kg)" min-width="168"> |
||||
<template #default="{ row }"> |
||||
<el-input-number v-model="row.weight" :min="0" :precision="2" :step="0.1" class="w-100%" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column align="center" label="体积(m^3)" min-width="168"> |
||||
<template #default="{ row }"> |
||||
<el-input-number v-model="row.volume" :min="0" :precision="2" :step="0.1" class="w-100%" /> |
||||
</template> |
||||
</el-table-column> |
||||
<template v-if="formData.subCommissionType"> |
||||
<el-table-column align="center" label="一级返佣(元)" min-width="168"> |
||||
<template #default="{ row }"> |
||||
<el-input-number |
||||
v-model="row.subCommissionFirstPrice" |
||||
:min="0" |
||||
:precision="2" |
||||
:step="0.1" |
||||
class="w-100%" |
||||
/> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column align="center" label="二级返佣(元)" min-width="168"> |
||||
<template #default="{ row }"> |
||||
<el-input-number |
||||
v-model="row.subCommissionSecondPrice" |
||||
:min="0" |
||||
:precision="2" |
||||
:step="0.1" |
||||
class="w-100%" |
||||
/> |
||||
</template> |
||||
</el-table-column> |
||||
</template> |
||||
<el-table-column v-if="formData.specType" align="center" fixed="right" label="操作" width="80"> |
||||
<template #default="{ row }"> |
||||
<el-button v-if="isBatch" link size="small" type="primary" @click="batchAdd"> |
||||
批量添加 |
||||
</el-button> |
||||
<el-button v-else link size="small" type="primary" @click="deleteSku(row)">删除</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
</template> |
||||
<script lang="ts" name="SkuList" setup> |
||||
import { PropType } from 'vue' |
||||
import { copyValueToTarget } from '@/utils' |
||||
import { propTypes } from '@/utils/propTypes' |
||||
import { UploadImg } from '@/components/UploadFile' |
||||
import type { Property, SkuType, SpuType } from '@/api/mall/product/spu' |
||||
|
||||
const props = defineProps({ |
||||
propFormData: { |
||||
type: Object as PropType<SpuType>, |
||||
default: () => {} |
||||
}, |
||||
propertyList: { |
||||
type: Array, |
||||
default: () => [] |
||||
}, |
||||
isBatch: propTypes.bool.def(false) // 是否作为批量操作组件 |
||||
}) |
||||
const formData = ref<SpuType>() // 表单数据 |
||||
const skuList = ref<SkuType[]>([ |
||||
{ |
||||
price: 0, // 商品价格 |
||||
marketPrice: 0, // 市场价 |
||||
costPrice: 0, // 成本价 |
||||
barCode: '', // 商品条码 |
||||
picUrl: '', // 图片地址 |
||||
stock: 0, // 库存 |
||||
weight: 0, // 商品重量 |
||||
volume: 0, // 商品体积 |
||||
subCommissionFirstPrice: 0, // 一级分销的佣金 |
||||
subCommissionSecondPrice: 0 // 二级分销的佣金 |
||||
} |
||||
]) // 批量添加时的临时数据 |
||||
// TODO @puhui999:保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。 |
||||
|
||||
/** 批量添加 */ |
||||
const batchAdd = () => { |
||||
formData.value.skus.forEach((item) => { |
||||
copyValueToTarget(item, skuList.value[0]) |
||||
}) |
||||
} |
||||
|
||||
/** 删除 sku */ |
||||
const deleteSku = (row) => { |
||||
const index = formData.value.skus.findIndex( |
||||
// 直接把列表转成字符串比较 |
||||
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties) |
||||
) |
||||
formData.value.skus.splice(index, 1) |
||||
} |
||||
const tableHeaders = ref<{ prop: string; label: string }[]>([]) // 多属性表头 |
||||
|
||||
/** |
||||
* 将传进来的值赋值给 skuList |
||||
*/ |
||||
watch( |
||||
() => props.propFormData, |
||||
(data) => { |
||||
if (!data) return |
||||
formData.value = data |
||||
}, |
||||
{ |
||||
deep: true, |
||||
immediate: true |
||||
} |
||||
) |
||||
|
||||
/** 生成表数据 */ |
||||
const generateTableData = (propertyList: any[]) => { |
||||
// 构建数据结构 |
||||
const propertyValues = propertyList.map((item) => |
||||
item.values.map((v) => ({ |
||||
propertyId: item.id, |
||||
propertyName: item.name, |
||||
valueId: v.id, |
||||
valueName: v.name |
||||
})) |
||||
) |
||||
// TODO @puhui:是不是 buildSkuList,这样容易理解一点哈。item 改成 sku |
||||
const buildList = build(propertyValues) |
||||
// 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表 |
||||
if (!validateData(propertyList)) { |
||||
// 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表 |
||||
formData.value!.skus = [] |
||||
} |
||||
for (const item of buildList) { |
||||
const row = { |
||||
properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个 property 对象 |
||||
price: 0, |
||||
marketPrice: 0, |
||||
costPrice: 0, |
||||
barCode: '', |
||||
picUrl: '', |
||||
stock: 0, |
||||
weight: 0, |
||||
volume: 0, |
||||
subCommissionFirstPrice: 0, |
||||
subCommissionSecondPrice: 0 |
||||
} |
||||
// 如果存在属性相同的 sku 则不做处理 |
||||
const index = formData.value!.skus.findIndex( |
||||
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties) |
||||
) |
||||
if (index !== -1) { |
||||
continue |
||||
} |
||||
formData.value.skus.push(row) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 生成 skus 前置校验 |
||||
*/ |
||||
const validateData = (propertyList: any[]) => { |
||||
const skuPropertyIds = [] |
||||
formData.value.skus.forEach((sku) => |
||||
sku.properties |
||||
?.map((property) => property.propertyId) |
||||
.forEach((propertyId) => { |
||||
if (skuPropertyIds.indexOf(propertyId) === -1) { |
||||
skuPropertyIds.push(propertyId) |
||||
} |
||||
}) |
||||
) |
||||
const propertyIds = propertyList.map((item) => item.id) |
||||
return skuPropertyIds.length === propertyIds.length |
||||
} |
||||
|
||||
/** 构建所有排列组合 */ |
||||
const build = (propertyValuesList: Property[][]) => { |
||||
if (propertyValuesList.length === 0) { |
||||
return [] |
||||
} else if (propertyValuesList.length === 1) { |
||||
return propertyValuesList[0] |
||||
} else { |
||||
const result: Property[][] = [] |
||||
const rest = build(propertyValuesList.slice(1)) |
||||
for (let i = 0; i < propertyValuesList[0].length; i++) { |
||||
for (let j = 0; j < rest.length; j++) { |
||||
// 第一次不是数组结构,后面的都是数组结构 |
||||
if (Array.isArray(rest[j])) { |
||||
result.push([propertyValuesList[0][i], ...rest[j]]) |
||||
} else { |
||||
result.push([propertyValuesList[0][i], rest[j]]) |
||||
} |
||||
} |
||||
} |
||||
return result |
||||
} |
||||
} |
||||
|
||||
/** 监听属性列表,生成相关参数和表头 */ |
||||
watch( |
||||
() => props.propertyList, |
||||
(propertyList) => { |
||||
// 如果不是多规格则结束 |
||||
if (!formData.value.specType) { |
||||
return |
||||
} |
||||
// 如果当前组件作为批量添加数据使用,则重置表数据 |
||||
if (props.isBatch) { |
||||
skuList.value = [ |
||||
{ |
||||
price: 0, |
||||
marketPrice: 0, |
||||
costPrice: 0, |
||||
barCode: '', |
||||
picUrl: '', |
||||
stock: 0, |
||||
weight: 0, |
||||
volume: 0, |
||||
subCommissionFirstPrice: 0, |
||||
subCommissionSecondPrice: 0 |
||||
} |
||||
] |
||||
} |
||||
|
||||
// 判断代理对象是否为空 |
||||
if (JSON.stringify(propertyList) === '[]') { |
||||
return |
||||
} |
||||
// 重置表头 |
||||
tableHeaders.value = [] |
||||
// 生成表头 |
||||
propertyList.forEach((item, index) => { |
||||
// name加属性项index区分属性值 |
||||
tableHeaders.value.push({ prop: `name${index}`, label: item.name }) |
||||
}) |
||||
|
||||
// 如果回显的 sku 属性和添加的属性一致则不处理 |
||||
if (validateData(propertyList)) { |
||||
return |
||||
} |
||||
// 添加新属性没有属性值也不做处理 |
||||
if (propertyList.some((item) => item.values.length === 0)) { |
||||
return |
||||
} |
||||
// 生成 table 数据,即 sku 列表 |
||||
generateTableData(propertyList) |
||||
}, |
||||
{ |
||||
deep: true, |
||||
immediate: true |
||||
} |
||||
) |
||||
// 暴露出生成 sku 方法,给添加属性成功时调用 |
||||
defineExpose({ generateTableData }) |
||||
</script> |
||||
@ -1,15 +0,0 @@ |
||||
import BasicInfoForm from './BasicInfoForm.vue' |
||||
import DescriptionForm from './DescriptionForm.vue' |
||||
import OtherSettingsForm from './OtherSettingsForm.vue' |
||||
import ProductAttributes from './ProductAttributes.vue' |
||||
import ProductAttributesAddForm from './ProductAttributesAddForm.vue' |
||||
import SkuList from './SkuList.vue' |
||||
|
||||
export { |
||||
BasicInfoForm, |
||||
DescriptionForm, |
||||
OtherSettingsForm, |
||||
ProductAttributes, |
||||
ProductAttributesAddForm, |
||||
SkuList |
||||
} |
||||
@ -1,422 +0,0 @@ |
||||
<template> |
||||
<!-- 搜索工作栏 --> |
||||
<ContentWrap> |
||||
<el-form |
||||
ref="queryFormRef" |
||||
:inline="true" |
||||
:model="queryParams" |
||||
class="-mb-15px" |
||||
label-width="68px" |
||||
> |
||||
<!-- TODO @puhui999:品牌应该是数据下拉哈 --> |
||||
<el-form-item label="品牌名称" prop="name"> |
||||
<el-input |
||||
v-model="queryParams.name" |
||||
class="!w-240px" |
||||
clearable |
||||
placeholder="请输入品牌名称" |
||||
@keyup.enter="handleQuery" |
||||
/> |
||||
</el-form-item> |
||||
<!-- TODO 分类只能选择二级分类目前还没做,还是先以联调通顺为主 --> |
||||
<!-- TODO puhui999:我们要不改成支持选择一级。如果选择一级,后端要递归查询下子分类,然后去 in? --> |
||||
<el-form-item label="商品分类" prop="categoryId"> |
||||
<el-tree-select |
||||
v-model="queryParams.categoryId" |
||||
:data="categoryList" |
||||
:props="defaultProps" |
||||
check-strictly |
||||
class="w-1/1" |
||||
node-key="id" |
||||
placeholder="请选择商品分类" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="创建时间" prop="createTime"> |
||||
<el-date-picker |
||||
v-model="queryParams.createTime" |
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" |
||||
class="!w-240px" |
||||
end-placeholder="结束日期" |
||||
start-placeholder="开始日期" |
||||
type="daterange" |
||||
value-format="YYYY-MM-DD HH:mm:ss" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item> |
||||
<el-button @click="handleQuery"> |
||||
<Icon class="mr-5px" icon="ep:search" /> |
||||
搜索 |
||||
</el-button> |
||||
<el-button @click="resetQuery"> |
||||
<Icon class="mr-5px" icon="ep:refresh" /> |
||||
重置 |
||||
</el-button> |
||||
<el-button v-hasPermi="['product:spu:create']" plain type="primary" @click="openForm"> |
||||
<Icon class="mr-5px" icon="ep:plus" /> |
||||
新增 |
||||
</el-button> |
||||
<el-button |
||||
v-hasPermi="['product:spu:export']" |
||||
:loading="exportLoading" |
||||
plain |
||||
type="success" |
||||
@click="handleExport" |
||||
> |
||||
<Icon class="mr-5px" icon="ep:download" /> |
||||
导出 |
||||
</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
</ContentWrap> |
||||
|
||||
<!-- 列表 --> |
||||
<ContentWrap> |
||||
<el-tabs v-model="queryParams.tabType" @tab-click="handleTabClick"> |
||||
<el-tab-pane |
||||
v-for="item in tabsData" |
||||
:key="item.type" |
||||
:label="item.name + '(' + item.count + ')'" |
||||
:name="item.type" |
||||
/> |
||||
</el-tabs> |
||||
<el-table v-loading="loading" :data="list"> |
||||
<!-- TODO puhui:这几个属性哈,一行三个 |
||||
商品分类:服装鞋包/箱包 |
||||
商品市场价格:100.00 |
||||
成本价:0.00 |
||||
收藏:5 |
||||
虚拟销量:999 --> |
||||
<el-table-column type="expand" width="30"> |
||||
<template #default="{ row }"> |
||||
<el-form class="demo-table-expand" inline label-position="left"> |
||||
<el-form-item label="市场价:"> |
||||
<span>{{ formatToFraction(row.marketPrice) }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="成本价:"> |
||||
<span>{{ formatToFraction(row.costPrice) }}</span> |
||||
</el-form-item> |
||||
<el-form-item label="虚拟销量:"> |
||||
<span>{{ row.virtualSalesCount }}</span> |
||||
</el-form-item> |
||||
</el-form> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column key="id" align="center" label="商品编号" prop="id" /> |
||||
<el-table-column label="商品图" min-width="80"> |
||||
<template #default="{ row }"> |
||||
<el-image :src="row.picUrl" @click="imagePreview(row.picUrl)" class="w-30px h-30px" /> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" /> |
||||
<el-table-column align="center" label="商品售价" min-width="90" prop="price"> |
||||
<template #default="{ row }"> |
||||
{{ formatToFraction(row.price) }} |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column align="center" label="销量" min-width="90" prop="salesCount" /> |
||||
<el-table-column align="center" label="库存" min-width="90" prop="stock" /> |
||||
<el-table-column align="center" label="排序" min-width="70" prop="sort" /> |
||||
<el-table-column |
||||
:formatter="dateFormatter" |
||||
align="center" |
||||
label="创建时间" |
||||
prop="createTime" |
||||
width="180" |
||||
/> |
||||
<el-table-column align="center" label="状态" min-width="80"> |
||||
<template #default="{ row }"> |
||||
<template v-if="row.status >= 0"> |
||||
<el-switch |
||||
v-model="row.status" |
||||
:active-value="1" |
||||
:inactive-value="0" |
||||
active-text="上架" |
||||
inactive-text="下架" |
||||
inline-prompt |
||||
@change="changeStatus(row)" |
||||
/> |
||||
</template> |
||||
<template v-else> |
||||
<el-tag type="info">回收站</el-tag> |
||||
</template> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column align="center" fixed="right" label="操作" min-width="200"> |
||||
<template #default="{ row }"> |
||||
<!-- TODO @puhui999:【详情】,可以后面点做哈 --> |
||||
<el-button v-hasPermi="['product:spu:update']" link type="primary" @click="openDetail"> |
||||
详情 |
||||
</el-button> |
||||
<template v-if="queryParams.tabType === 4"> |
||||
<el-button |
||||
v-hasPermi="['product:spu:delete']" |
||||
link |
||||
type="danger" |
||||
@click="handleDelete(row.id)" |
||||
> |
||||
删除 |
||||
</el-button> |
||||
<el-button |
||||
v-hasPermi="['product:spu:update']" |
||||
link |
||||
type="primary" |
||||
@click="changeStatus(row, ProductSpuStatusEnum.DISABLE.status)" |
||||
> |
||||
恢复到仓库 |
||||
</el-button> |
||||
</template> |
||||
<template v-else> |
||||
<!-- 只有不是上架和回收站的商品可以编辑 --> |
||||
<el-button |
||||
v-if="queryParams.tabType !== 0" |
||||
v-hasPermi="['product:spu:update']" |
||||
link |
||||
type="primary" |
||||
@click="openForm(row.id)" |
||||
> |
||||
修改 |
||||
</el-button> |
||||
<el-button |
||||
v-hasPermi="['product:spu:update']" |
||||
link |
||||
type="primary" |
||||
@click="changeStatus(row, ProductSpuStatusEnum.RECYCLE.status)" |
||||
> |
||||
加入回收站 |
||||
</el-button> |
||||
</template> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
<!-- 分页 --> |
||||
<Pagination |
||||
v-model:limit="queryParams.pageSize" |
||||
v-model:page="queryParams.pageNo" |
||||
:total="total" |
||||
@pagination="getList" |
||||
/> |
||||
</ContentWrap> |
||||
</template> |
||||
<script lang="ts" name="ProductSpu" setup> |
||||
import { TabsPaneContext } from 'element-plus' |
||||
import { cloneDeep } from 'lodash-es' |
||||
import { createImageViewer } from '@/components/ImageViewer' |
||||
import { dateFormatter } from '@/utils/formatTime' |
||||
import { defaultProps, handleTree } from '@/utils/tree' |
||||
import { ProductSpuStatusEnum } from '@/utils/constants' |
||||
import { formatToFraction } from '@/utils' |
||||
import download from '@/utils/download' |
||||
import * as ProductSpuApi from '@/api/mall/product/spu' |
||||
import * as ProductCategoryApi from '@/api/mall/product/category' |
||||
|
||||
const message = useMessage() // 消息弹窗 |
||||
const { t } = useI18n() // 国际化 |
||||
const { currentRoute, push } = useRouter() // 路由跳转 |
||||
|
||||
const loading = ref(false) // 列表的加载中 |
||||
const exportLoading = ref(false) // 导出的加载中 |
||||
const total = ref(0) // 列表的总页数 |
||||
const list = ref<any[]>([]) // 列表的数据 |
||||
// tabs 数据 |
||||
const tabsData = ref([ |
||||
{ |
||||
count: 0, |
||||
name: '出售中商品', |
||||
type: 0 |
||||
}, |
||||
{ |
||||
count: 0, |
||||
name: '仓库中商品', |
||||
type: 1 |
||||
}, |
||||
{ |
||||
count: 0, |
||||
name: '已经售空商品', |
||||
type: 2 |
||||
}, |
||||
{ |
||||
count: 0, |
||||
name: '警戒库存', |
||||
type: 3 |
||||
}, |
||||
{ |
||||
count: 0, |
||||
name: '商品回收站', |
||||
type: 4 |
||||
} |
||||
]) |
||||
|
||||
/** 获得每个 Tab 的数量 */ |
||||
const getTabsCount = async () => { |
||||
const res = await ProductSpuApi.getTabsCount() |
||||
for (let objName in res) { |
||||
tabsData.value[Number(objName)].count = res[objName] |
||||
} |
||||
} |
||||
const queryParams = ref({ |
||||
pageNo: 1, |
||||
pageSize: 10, |
||||
tabType: 0 |
||||
}) // 查询参数 |
||||
const queryFormRef = ref() // 搜索的表单Ref |
||||
|
||||
const handleTabClick = (tab: TabsPaneContext) => { |
||||
queryParams.value.tabType = tab.paneName |
||||
getList() |
||||
} |
||||
|
||||
/** 查询列表 */ |
||||
const getList = async () => { |
||||
loading.value = true |
||||
try { |
||||
const data = await ProductSpuApi.getSpuPage(queryParams.value) |
||||
list.value = data.list |
||||
total.value = data.total |
||||
} finally { |
||||
loading.value = false |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 更改 SPU 状态 |
||||
* |
||||
* @param row |
||||
* @param status 更改前的值 |
||||
*/ |
||||
const changeStatus = async (row, status?: number) => { |
||||
const deepCopyValue = cloneDeep(unref(row)) |
||||
if (typeof status !== 'undefined') deepCopyValue.status = status |
||||
try { |
||||
let text = '' |
||||
switch (deepCopyValue.status) { |
||||
case ProductSpuStatusEnum.DISABLE.status: |
||||
text = ProductSpuStatusEnum.DISABLE.name |
||||
break |
||||
case ProductSpuStatusEnum.ENABLE.status: |
||||
text = ProductSpuStatusEnum.ENABLE.name |
||||
break |
||||
case ProductSpuStatusEnum.RECYCLE.status: |
||||
text = `加入${ProductSpuStatusEnum.RECYCLE.name}` |
||||
break |
||||
} |
||||
await message.confirm( |
||||
deepCopyValue.status === -1 |
||||
? `确认要将[${row.name}]${text}吗?` |
||||
: row.status === -1 // 再判断一次原对象是否等于-1,例: 把回收站中的商品恢复到仓库中,事件触发时原对象status为-1 深拷贝对象status被赋值为0 |
||||
? `确认要将[${row.name}]恢复到仓库吗?` |
||||
: `确认要${text}[${row.name}]吗?` |
||||
) |
||||
await ProductSpuApi.updateStatus({ id: deepCopyValue.id, status: deepCopyValue.status }) |
||||
message.success('更新状态成功') |
||||
// 刷新 tabs 数据 |
||||
await getTabsCount() |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch { |
||||
// 取消更改状态时回显数据 |
||||
row.status = |
||||
row.status === ProductSpuStatusEnum.DISABLE.status |
||||
? ProductSpuStatusEnum.ENABLE.status |
||||
: ProductSpuStatusEnum.DISABLE.status |
||||
} |
||||
} |
||||
|
||||
/** 删除按钮操作 */ |
||||
const handleDelete = async (id: number) => { |
||||
try { |
||||
// 删除的二次确认 |
||||
await message.delConfirm() |
||||
// 发起删除 |
||||
await ProductSpuApi.deleteSpu(id) |
||||
message.success(t('common.delSuccess')) |
||||
// 刷新tabs数据 |
||||
await getTabsCount() |
||||
// 刷新列表 |
||||
await getList() |
||||
} catch {} |
||||
} |
||||
|
||||
/** 商品图预览 */ |
||||
const imagePreview = (imgUrl: string) => { |
||||
createImageViewer({ |
||||
urlList: [imgUrl] |
||||
}) |
||||
} |
||||
|
||||
/** 搜索按钮操作 */ |
||||
const handleQuery = () => { |
||||
getList() |
||||
} |
||||
|
||||
/** 重置按钮操作 */ |
||||
const resetQuery = () => { |
||||
queryFormRef.value.resetFields() |
||||
handleQuery() |
||||
} |
||||
|
||||
/** |
||||
* 新增或修改 |
||||
* |
||||
* @param id 商品 SPU 编号 |
||||
*/ |
||||
const openForm = (id?: number) => { |
||||
// 修改 |
||||
if (typeof id === 'number') { |
||||
push('/product/productSpuEdit/' + id) |
||||
return |
||||
} |
||||
// 新增 |
||||
push('/product/productSpuAdd') |
||||
} |
||||
|
||||
/** |
||||
* 查看商品详情 |
||||
*/ |
||||
const openDetail = () => { |
||||
message.alert('查看详情未完善!!!') |
||||
} |
||||
|
||||
/** 导出按钮操作 */ |
||||
const handleExport = async () => { |
||||
try { |
||||
// 导出的二次确认 |
||||
await message.exportConfirm() |
||||
// 发起导出 |
||||
exportLoading.value = true |
||||
const data = await ProductSpuApi.exportSpu(queryParams) |
||||
download.excel(data, '商品列表.xls') |
||||
} catch { |
||||
} finally { |
||||
exportLoading.value = false |
||||
} |
||||
} |
||||
|
||||
// 监听路由变化更新列表 TODO @puhui999:这个是必须加的么?fix: 因为编辑表单是以路由的方式打开,保存表单后列表不会刷新 |
||||
watch( |
||||
() => currentRoute.value, |
||||
() => { |
||||
getList() |
||||
} |
||||
) |
||||
|
||||
const categoryList = ref() // 分类树 |
||||
/** 初始化 **/ |
||||
onMounted(async () => { |
||||
await getTabsCount() |
||||
await getList() |
||||
// 获得分类树 |
||||
const data = await ProductCategoryApi.getCategoryList({}) |
||||
categoryList.value = handleTree(data, 'id', 'parentId') |
||||
}) |
||||
</script> |
||||
<style lang="scss" scoped> |
||||
.demo-table-expand { |
||||
padding-left: 42px; |
||||
|
||||
:deep(.el-form-item__label) { |
||||
width: 82px; |
||||
font-weight: bold; |
||||
color: #99a9bf; |
||||
} |
||||
} |
||||
</style> |
||||
@ -1,124 +0,0 @@ |
||||
<template> |
||||
<Dialog :title="dialogTitle" v-model="dialogVisible"> |
||||
<el-form |
||||
ref="formRef" |
||||
:model="formData" |
||||
:rules="formRules" |
||||
label-width="120px" |
||||
v-loading="formLoading" |
||||
> |
||||
<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="快递公司 logo" prop="logo"> |
||||
<UploadImg v-model="formData.logo" :limit="1" :is-show-tip="false" /> |
||||
<div style="font-size: 10px" class="pl-10px">推荐 180x180 图片分辨率</div> |
||||
</el-form-item> |
||||
<el-form-item label="分类排序" prop="sort"> |
||||
<el-input-number v-model="formData.sort" controls-position="right" :min="0" /> |
||||
</el-form-item> |
||||
<el-form-item label="开启状态" prop="status"> |
||||
<el-radio-group v-model="formData.status"> |
||||
<el-radio |
||||
v-for="dict in 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> |
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> |
||||
<el-button @click="dialogVisible = false">取 消</el-button> |
||||
</template> |
||||
</Dialog> |
||||
</template> |
||||
<script setup lang="ts" name="ExpressForm"> |
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' |
||||
import { CommonStatusEnum } from '@/utils/constants' |
||||
import * as DeliveryExpressApi from '@/api/mall/trade/delivery/express' |
||||
const { t } = useI18n() // 国际化 |
||||
const message = useMessage() // 消息弹窗 |
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||
const dialogTitle = ref('') // 弹窗的标题 |
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
||||
const formData = ref({ |
||||
id: undefined, |
||||
code: '', |
||||
name: '', |
||||
logo: '', |
||||
sort: 0, |
||||
status: CommonStatusEnum.ENABLE |
||||
}) |
||||
const formRules = reactive({ |
||||
code: [{ required: true, message: '快递编码不能为空', trigger: 'blur' }], |
||||
name: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }], |
||||
logo: [{ required: true, message: '分类图片不能为空', trigger: 'blur' }], |
||||
sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }], |
||||
status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }] |
||||
}) |
||||
const formRef = ref() // 表单 Ref |
||||
|
||||
/** 打开弹窗 */ |
||||
const open = async (type: string, id?: number) => { |
||||
dialogVisible.value = true |
||||
dialogTitle.value = t('action.' + type) |
||||
formType.value = type |
||||
resetForm() |
||||
// 修改时,设置数据 |
||||
if (id) { |
||||
formLoading.value = true |
||||
try { |
||||
formData.value = await DeliveryExpressApi.getDeliveryExpress(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 DeliveryExpressApi.DeliveryExpressVO |
||||
if (formType.value === 'create') { |
||||
await DeliveryExpressApi.createDeliveryExpress(data) |
||||
message.success(t('common.createSuccess')) |
||||
} else { |
||||
await DeliveryExpressApi.updateDeliveryExpress(data) |
||||
message.success(t('common.updateSuccess')) |
||||
} |
||||
dialogVisible.value = false |
||||
// 发送操作成功的事件 |
||||
emit('success') |
||||
} finally { |
||||
formLoading.value = false |
||||
} |
||||
} |
||||
|
||||
/** 重置表单 */ |
||||
const resetForm = () => { |
||||
formData.value = { |
||||
id: undefined, |
||||
name: '', |
||||
picUrl: '', |
||||
bigPicUrl: '', |
||||
status: CommonStatusEnum.ENABLE |
||||
} |
||||
formRef.value?.resetFields() |
||||
} |
||||
</script> |
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue