!139 fix: type error

Merge pull request !139 from xingyu/dev
pull/143/MERGE
xingyu 2025-06-12 09:55:43 +00:00 committed by Gitee
commit cac4efc227
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
65 changed files with 655 additions and 228 deletions

View File

@ -10,7 +10,12 @@ import {
setupVbenVxeTable,
useVbenVxeGrid,
} from '@vben/plugins/vxe-table';
import { isFunction, isString } from '@vben/utils';
import {
floatToFixed2,
formatToFractionDigit,
isFunction,
isString,
} from '@vben/utils';
import { Button, Image, Popconfirm, Switch } from 'ant-design-vue';
@ -313,33 +318,13 @@ setupVbenVxeTable({
// add by 星语:数量格式化,例如说:金额
vxeUI.formats.add('formatNumber', {
tableCellFormatMethod({ cellValue }, digits = 2) {
if (cellValue === null || cellValue === undefined) {
return '';
}
if (isString(cellValue)) {
cellValue = Number.parseFloat(cellValue);
}
// 如果非 number则直接返回空串
if (Number.isNaN(cellValue)) {
return '';
}
return cellValue.toFixed(digits);
return formatToFractionDigit(cellValue, digits);
},
});
vxeUI.formats.add('formatAmount2', {
tableCellFormatMethod({ cellValue }) {
if (cellValue === null || cellValue === undefined) {
return '0.00';
}
if (isString(cellValue)) {
cellValue = Number.parseFloat(cellValue);
}
// 如果非 number则直接返回空串
if (Number.isNaN(cellValue)) {
return '0.00';
}
return `${(cellValue / 100).toFixed(2)}`;
return `${floatToFixed2(cellValue)}`;
},
});
},

View File

@ -23,6 +23,11 @@ interface DictTagProps {
const props = defineProps<DictTagProps>();
function isHexColor(color: string) {
const reg = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
return reg.test(color);
}
/** 获取字典标签 */
const dictTag = computed(() => {
//
@ -66,7 +71,16 @@ const dictTag = computed(() => {
</script>
<template>
<Tag v-if="dictTag" :color="dictTag.colorType">
<Tag
v-if="dictTag"
:color="
dictTag.colorType
? dictTag.colorType
: dictTag.cssClass && isHexColor(dictTag.cssClass)
? dictTag.cssClass
: ''
"
>
{{ dictTag.label }}
</Tag>
</template>

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import type { SimpleFlowNode } from '../../consts';
import { ref, watch } from 'vue';
import { nextTick, ref, watch } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
@ -53,8 +53,6 @@ const condition = ref<any>({
},
});
//
const showInput = ref(false);
const conditionRef = ref();
const fieldOptions = useFormFieldsAndStartUser(); //
@ -130,13 +128,24 @@ watch(
currentNode.value = newValue;
},
);
//
const showInput = ref(false);
//
const inputRef = ref<HTMLInputElement | null>(null);
// showInput true
watch(showInput, (value) => {
if (value) {
nextTick(() => {
inputRef.value?.focus();
});
}
});
function clickIcon() {
showInput.value = true;
}
//
function blurEvent() {
//
function changeNodeName() {
showInput.value = false;
currentNode.value.name =
currentNode.value.name ||
@ -153,10 +162,12 @@ defineExpose({ open }); // 提供 open 方法,用于打开弹窗
<template #title>
<div class="flex items-center">
<Input
ref="inputRef"
v-if="showInput"
type="text"
class="mr-2 w-48"
@blur="blurEvent()"
@blur="changeNodeName()"
@press-enter="changeNodeName()"
v-model:value="currentNode.name"
:placeholder="currentNode.name"
/>

View File

@ -75,9 +75,8 @@ const [Drawer, drawerApi] = useVbenDrawer({
const currentNode = useWatchNode(props);
//
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(
BpmNodeTypeEnum.COPY_TASK_NODE,
);
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
useNodeName(BpmNodeTypeEnum.COPY_TASK_NODE);
// Tab
const activeTabName = ref('user');
@ -213,9 +212,11 @@ defineExpose({ showCopyTaskNodeConfig }); // 暴露方法给父组件
<div class="config-header">
<Input
v-if="showInput"
ref="inputRef"
type="text"
class="config-editable-input"
@blur="blurEvent()"
@blur="changeNodeName()"
@press-enter="changeNodeName()"
v-model:value="nodeName"
:placeholder="nodeName"
/>

View File

@ -45,9 +45,8 @@ const props = defineProps({
//
const currentNode = useWatchNode(props);
//
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(
BpmNodeTypeEnum.DELAY_TIMER_NODE,
);
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
useNodeName(BpmNodeTypeEnum.DELAY_TIMER_NODE);
//
const formRef = ref(); // Ref
@ -158,9 +157,11 @@ defineExpose({ openDrawer }); // 暴露方法给父组件
<div class="flex items-center">
<Input
v-if="showInput"
ref="inputRef"
type="text"
class="mr-2 w-48"
@blur="blurEvent()"
@blur="changeNodeName()"
@press-enter="changeNodeName()"
v-model:value="nodeName"
:placeholder="nodeName"
/>

View File

@ -25,7 +25,7 @@ import {
Tooltip,
} from 'ant-design-vue';
import { BpmModelFormType } from '#/utils/constants';
import { BpmModelFormType } from '#/utils';
import {
COMPARISON_OPERATORS,

View File

@ -41,9 +41,8 @@ const processNodeTree = inject<Ref<SimpleFlowNode>>('processNodeTree');
/** 当前节点 */
const currentNode = useWatchNode(props);
/** 节点名称 */
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(
BpmNodeTypeEnum.ROUTER_BRANCH_NODE,
);
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
useNodeName(BpmNodeTypeEnum.ROUTER_BRANCH_NODE);
const routerGroups = ref<RouterSetting[]>([]);
const nodeOptions = ref<any[]>([]);
const conditionRef = ref<any[]>([]);
@ -205,10 +204,12 @@ defineExpose({ openDrawer }); // 暴露方法给父组件
<template #title>
<div class="flex items-center">
<Input
ref="inputRef"
v-if="showInput"
type="text"
class="mr-2 w-48"
@blur="blurEvent()"
@blur="changeNodeName()"
@press-enter="changeNodeName()"
v-model:value="nodeName"
:placeholder="nodeName"
/>

View File

@ -52,9 +52,8 @@ const deptOptions = inject<Ref<SystemDeptApi.Dept[]>>('deptList');
//
const currentNode = useWatchNode(props);
//
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(
BpmNodeTypeEnum.START_USER_NODE,
);
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
useNodeName(BpmNodeTypeEnum.START_USER_NODE);
// Tab
const activeTabName = ref('user');
@ -145,12 +144,13 @@ defineExpose({ showStartUserNodeConfig });
<Drawer>
<template #title>
<div class="config-header">
<!-- TODO v-mountedFocus 自动聚集 需要迁移一下 -->
<Input
ref="inputRef"
v-if="showInput"
type="text"
class="config-editable-input"
@blur="blurEvent()"
@blur="changeNodeName()"
@press-enter="changeNodeName()"
v-model:value="nodeName"
:placeholder="nodeName"
/>

View File

@ -72,9 +72,8 @@ const [Drawer, drawerApi] = useVbenDrawer({
//
const currentNode = useWatchNode(props);
//
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(
BpmNodeTypeEnum.TRIGGER_NODE,
);
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
useNodeName(BpmNodeTypeEnum.TRIGGER_NODE);
//
const formRef = ref(); // Ref
@ -388,10 +387,12 @@ onMounted(() => {
<template #title>
<div class="config-header">
<Input
ref="inputRef"
v-if="showInput"
type="text"
class="config-editable-input"
@blur="blurEvent()"
@blur="changeNodeName()"
@press-enter="changeNodeName()"
v-model:value="nodeName"
:placeholder="nodeName"
/>

View File

@ -114,9 +114,8 @@ const [Drawer, drawerApi] = useVbenDrawer({
});
//
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(
BpmNodeTypeEnum.USER_TASK_NODE,
);
const { nodeName, showInput, clickIcon, changeNodeName, inputRef } =
useNodeName(BpmNodeTypeEnum.USER_TASK_NODE);
// Tab
const activeTabName = ref('user');
@ -586,9 +585,11 @@ onMounted(() => {
<div class="config-header">
<Input
v-if="showInput"
ref="inputRef"
type="text"
class="config-editable-input"
@blur="blurEvent()"
@blur="changeNodeName()"
@press-enter="changeNodeName()"
v-model:value="nodeName"
:placeholder="nodeName"
/>

View File

@ -32,7 +32,7 @@ const readonly = inject<Boolean>('readonly');
//
const currentNode = useWatchNode(props);
//
const { showInput, blurEvent, clickTitle } = useNodeName2(
const { showInput, changeNodeName, clickTitle, inputRef } = useNodeName2(
currentNode,
BpmNodeTypeEnum.COPY_TASK_NODE,
);
@ -67,11 +67,13 @@ function deleteNode() {
<span class="iconfont icon-copy"></span>
</div>
<Input
ref="inputRef"
v-if="!readonly && showInput"
type="text"
class="editable-title-input"
@blur="blurEvent()"
v-model="currentNode.name"
@blur="changeNodeName()"
@press-enter="changeNodeName()"
v-model:value="currentNode.name"
:placeholder="currentNode.name"
/>
<div v-else class="node-title" @click="clickTitle">

View File

@ -30,7 +30,7 @@ const readonly = inject<Boolean>('readonly');
//
const currentNode = useWatchNode(props);
//
const { showInput, blurEvent, clickTitle } = useNodeName2(
const { showInput, changeNodeName, clickTitle, inputRef } = useNodeName2(
currentNode,
BpmNodeTypeEnum.DELAY_TIMER_NODE,
);
@ -64,11 +64,13 @@ function deleteNode() {
<span class="iconfont icon-delay"></span>
</div>
<Input
ref="inputRef"
v-if="!readonly && showInput"
type="text"
class="editable-title-input"
@blur="blurEvent()"
v-model="currentNode.name"
@blur="changeNodeName()"
@press-enter="changeNodeName()"
v-model:value="currentNode.name"
:placeholder="currentNode.name"
/>
<div v-else class="node-title" @click="clickTitle">

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import type { SimpleFlowNode } from '../../consts';
import { getCurrentInstance, inject, ref, watch } from 'vue';
import { getCurrentInstance, inject, nextTick, ref, watch } from 'vue';
import { IconifyIcon } from '@vben/icons';
import { cloneDeep, buildShortUUID as generateUUID } from '@vben/utils';
@ -51,11 +51,30 @@ watch(
currentNode.value = newValue;
},
);
//
const inputRefs = ref<HTMLInputElement[]>([]);
//
const showInputs = ref<boolean[]>([]);
//
function blurEvent(index: number) {
//
watch(
showInputs,
(newValues) => {
// true ,
newValues.forEach((value, index) => {
if (value) {
// false true ,
nextTick(() => {
inputRefs.value[index]?.focus();
});
}
});
},
{ deep: true },
);
//
function changeNodeName(index: number) {
showInputs.value[index] = false;
const conditionNode = currentNode.value.conditionNodes?.at(
index,
@ -188,10 +207,16 @@ function recursiveFindParentNode(
<div class="branch-node-title-container">
<div v-if="!readonly && showInputs[index]">
<Input
:ref="
(el) => {
inputRefs[index] = el as HTMLInputElement;
}
"
type="text"
class="editable-title-input"
@blur="blurEvent(index)"
v-model="item.name"
@blur="changeNodeName(index)"
@press-enter="changeNodeName(index)"
v-model:value="item.name"
/>
</div>
<div v-else class="branch-title" @click="clickEvent(index)">

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import type { SimpleFlowNode } from '../../consts';
import { getCurrentInstance, inject, ref, watch } from 'vue';
import { getCurrentInstance, inject, nextTick, ref, watch } from 'vue';
import { IconifyIcon } from '@vben/icons';
import { cloneDeep, buildShortUUID as generateUUID } from '@vben/utils';
@ -57,10 +57,28 @@ watch(
currentNode.value = newValue;
},
);
//
const inputRefs = ref<HTMLInputElement[]>([]);
//
const showInputs = ref<boolean[]>([]);
//
function blurEvent(index: number) {
//
watch(
showInputs,
(newValues) => {
// true ,
newValues.forEach((value, index) => {
if (value) {
// false true ,
nextTick(() => {
inputRefs.value[index]?.focus();
});
}
});
},
{ deep: true },
);
//
function changeNodeName(index: number) {
showInputs.value[index] = false;
const conditionNode = currentNode.value.conditionNodes?.at(
index,
@ -192,10 +210,16 @@ function recursiveFindParentNode(
<div class="branch-node-title-container">
<div v-if="!readonly && showInputs[index]">
<Input
:ref="
(el) => {
inputRefs[index] = el as HTMLInputElement;
}
"
type="text"
class="editable-title-input"
@blur="blurEvent(index)"
v-model="item.name"
@blur="changeNodeName(index)"
@press-enter="changeNodeName(index)"
v-model:value="item.name"
/>
</div>
<div v-else class="branch-title" @click="clickEvent(index)">

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import type { SimpleFlowNode } from '../../consts';
import { inject, ref, watch } from 'vue';
import { inject, nextTick, ref, watch } from 'vue';
import { IconifyIcon } from '@vben/icons';
import { buildShortUUID as generateUUID } from '@vben/utils';
@ -46,10 +46,28 @@ watch(
},
);
//
const inputRefs = ref<HTMLInputElement[]>([]);
//
const showInputs = ref<boolean[]>([]);
//
function blurEvent(index: number) {
//
watch(
showInputs,
(newValues) => {
// ,
newValues.forEach((value, index) => {
if (value) {
// false true ,
nextTick(() => {
inputRefs.value[index]?.focus();
});
}
});
},
{ deep: true },
);
//
function changeNodeName(index: number) {
showInputs.value[index] = false;
const conditionNode = currentNode.value.conditionNodes?.at(
index,
@ -150,10 +168,16 @@ function recursiveFindParentNode(
<div class="branch-node-title-container">
<div v-if="showInputs[index]">
<Input
:ref="
(el) => {
inputRefs[index] = el as HTMLInputElement;
}
"
type="text"
class="input-max-width editable-title-input"
@blur="blurEvent(index)"
v-model="item.name"
@blur="changeNodeName(index)"
@press-enter="changeNodeName(index)"
v-model:value="item.name"
/>
</div>
<div v-else class="branch-title" @click="clickEvent(index)">

View File

@ -33,7 +33,7 @@ const readonly = inject<Boolean>('readonly');
//
const currentNode = useWatchNode(props);
//
const { showInput, blurEvent, clickTitle } = useNodeName2(
const { showInput, changeNodeName, clickTitle, inputRef } = useNodeName2(
currentNode,
BpmNodeTypeEnum.ROUTER_BRANCH_NODE,
);
@ -67,11 +67,13 @@ function deleteNode() {
<span class="iconfont icon-router"></span>
</div>
<Input
ref="inputRef"
v-if="!readonly && showInput"
type="text"
class="editable-title-input"
@blur="blurEvent()"
v-model="currentNode.name"
@blur="changeNodeName()"
@press-enter="changeNodeName()"
v-model:value="currentNode.name"
:placeholder="currentNode.name"
/>
<div v-else class="node-title" @click="clickTitle">

View File

@ -37,7 +37,7 @@ const tasks = inject<Ref<any[]>>('tasks', ref([]));
//
const currentNode = useWatchNode(props);
//
const { showInput, blurEvent, clickTitle } = useNodeName2(
const { showInput, changeNodeName, clickTitle, inputRef } = useNodeName2(
currentNode,
BpmNodeTypeEnum.START_USER_NODE,
);
@ -81,10 +81,12 @@ function nodeClick() {
<span class="iconfont icon-start-user"></span>
</div>
<Input
ref="inputRef"
v-if="!readonly && showInput"
type="text"
class="editable-title-input"
@blur="blurEvent()"
@blur="changeNodeName()"
@press-enter="changeNodeName()"
v-model:value="currentNode.name"
:placeholder="currentNode.name"
/>

View File

@ -35,7 +35,7 @@ const readonly = inject<Boolean>('readonly');
//
const currentNode = useWatchNode(props);
//
const { showInput, blurEvent, clickTitle } = useNodeName2(
const { showInput, changeNodeName, clickTitle, inputRef } = useNodeName2(
currentNode,
BpmNodeTypeEnum.TRIGGER_NODE,
);
@ -69,11 +69,13 @@ function deleteNode() {
<span class="iconfont icon-trigger"></span>
</div>
<Input
ref="inputRef"
v-if="!readonly && showInput"
type="text"
class="editable-title-input"
@blur="blurEvent()"
v-model="currentNode.name"
@blur="changeNodeName()"
@press-enter="changeNodeName()"
v-model:value="currentNode.name"
:placeholder="currentNode.name"
/>
<div v-else class="node-title" @click="clickTitle">

View File

@ -36,7 +36,7 @@ const tasks = inject<Ref<any[]>>('tasks', ref([]));
//
const currentNode = useWatchNode(props);
//
const { showInput, blurEvent, clickTitle } = useNodeName2(
const { showInput, changeNodeName, clickTitle, inputRef } = useNodeName2(
currentNode,
BpmNodeTypeEnum.USER_TASK_NODE,
);
@ -87,11 +87,13 @@ function findReturnTaskNodes(
</span>
</div>
<Input
ref="inputRef"
v-if="!readonly && showInput"
type="text"
class="editable-title-input"
@blur="blurEvent()"
v-model="currentNode.name"
@blur="changeNodeName()"
@press-enter="changeNodeName()"
v-model:value="currentNode.name"
:placeholder="currentNode.name"
/>
<div v-else class="node-title" @click="clickTitle">

View File

@ -12,7 +12,7 @@ import type { SystemPostApi } from '#/api/system/post';
import type { SystemRoleApi } from '#/api/system/role';
import type { SystemUserApi } from '#/api/system/user';
import { inject, ref, toRaw, unref, watch } from 'vue';
import { inject, nextTick, ref, toRaw, unref, watch } from 'vue';
import {
BpmNodeTypeEnum,
@ -622,21 +622,33 @@ export function useNodeName(nodeType: BpmNodeTypeEnum) {
const nodeName = ref<string>();
// 节点名称输入框
const showInput = ref(false);
// 输入框的引用
const inputRef = ref<HTMLInputElement | null>(null);
// 点击节点名称编辑图标
function clickIcon() {
showInput.value = true;
}
// 节点名称输入框失去焦点
function blurEvent() {
// 修改节点名称
function changeNodeName() {
showInput.value = false;
nodeName.value =
nodeName.value || (NODE_DEFAULT_NAME.get(nodeType) as string);
}
// 监听 showInput 的变化,当变为 true 时自动聚焦
watch(showInput, (value) => {
if (value) {
nextTick(() => {
inputRef.value?.focus();
});
}
});
return {
nodeName,
showInput,
inputRef,
clickIcon,
blurEvent,
changeNodeName,
};
}
@ -646,11 +658,24 @@ export function useNodeName2(
) {
// 显示节点名称输入框
const showInput = ref(false);
// 节点名称输入框失去焦点
function blurEvent() {
// 输入框的引用
const inputRef = ref<HTMLInputElement | null>(null);
// 监听 showInput 的变化,当变为 true 时自动聚焦
watch(showInput, (value) => {
if (value) {
nextTick(() => {
inputRef.value?.focus();
});
}
});
// 修改节点名称
function changeNodeName() {
showInput.value = false;
node.value.name =
node.value.name || (NODE_DEFAULT_NAME.get(nodeType) as string);
console.warn('node.value.name===>', node.value.name);
}
// 点击节点标题进行输入
function clickTitle() {
@ -658,8 +683,9 @@ export function useNodeName2(
}
return {
showInput,
inputRef,
clickTitle,
blurEvent,
changeNodeName,
};
}

View File

@ -43,28 +43,27 @@ const { hasAccessByCodes } = useAccess();
function isIfShow(action: ActionItem): boolean {
const ifShow = action.ifShow;
let isIfShow = true;
if (isBoolean(ifShow)) {
isIfShow = ifShow;
}
if (isFunction(ifShow)) {
isIfShow = ifShow(action);
}
if (isIfShow) {
isIfShow =
hasAccessByCodes(action.auth || []) || (action.auth || []).length === 0;
}
return isIfShow;
}
const getActions = computed(() => {
return (toRaw(props.actions) || [])
.filter((action) => {
return (
(hasAccessByCodes(action.auth || []) ||
(action.auth || []).length === 0) &&
isIfShow(action)
);
const actions = toRaw(props.actions) || [];
return actions
.filter((action: ActionItem) => {
return isIfShow(action);
})
.map((action) => {
.map((action: ActionItem) => {
const { popConfirm } = action;
return {
type: action.type || 'link',
@ -78,24 +77,21 @@ const getActions = computed(() => {
});
const getDropdownList = computed((): any[] => {
return (toRaw(props.dropDownActions) || [])
.filter((action) => {
return (
(hasAccessByCodes(action.auth || []) ||
(action.auth || []).length === 0) &&
isIfShow(action)
);
const dropDownActions = toRaw(props.dropDownActions) || [];
return dropDownActions
.filter((action: ActionItem) => {
return isIfShow(action);
})
.map((action, index) => {
.map((action: ActionItem, index: number) => {
const { label, popConfirm } = action;
delete action.icon;
return {
...action,
...popConfirm,
onConfirm: popConfirm?.confirm,
onCancel: popConfirm?.cancel,
text: label,
divider:
index < props.dropDownActions.length - 1 ? props.divider : false,
divider: index < dropDownActions.length - 1 ? props.divider : false,
};
});
});

View File

@ -86,6 +86,18 @@ const routes: RouteRecordRaw[] = [
keepAlive: true,
},
},
{
path: 'manager/definition',
component: () => import('#/views/bpm/model/definition/index.vue'),
name: 'BpmProcessDefinition',
meta: {
title: '流程定义',
activePath: '/bpm/manager/model',
icon: 'carbon:flow-modeler',
hideInMenu: true,
keepAlive: true,
},
},
],
},
];

View File

@ -1,7 +1,6 @@
export * from './constants';
export * from './dict';
export * from './download';
export * from './formatNumber';
export * from './formCreate';
export * from './rangePickerProps';
export * from './routerHelper';

View File

@ -0,0 +1,65 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmProcessDefinitionApi } from '#/api/bpm/definition';
import { DICT_TYPE } from '#/utils';
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions<BpmProcessDefinitionApi.ProcessDefinitionVO>['columns'] {
return [
{
field: 'id',
title: '定义编号',
minWidth: 250,
},
{
field: 'name',
title: '流程名称',
minWidth: 150,
},
{
field: 'icon',
title: '流程图标',
minWidth: 100,
slots: { default: 'icon' },
},
{
field: 'startUsers',
title: '可见范围',
minWidth: 100,
slots: { default: 'startUsers' },
},
{
field: 'modelType',
title: '流程类型',
minWidth: 120,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.BPM_MODEL_TYPE },
},
},
{
field: 'formType',
title: '表单信息',
minWidth: 150,
slots: { default: 'formInfo' },
},
{
field: 'version',
title: '流程版本',
minWidth: 80,
slots: { default: 'version' },
},
{
field: 'deploymentTime',
title: '部署时间',
minWidth: 180,
formatter: 'formatDateTime',
},
{
title: '操作',
width: 120,
fixed: 'right',
slots: { default: 'actions' },
},
];
}

View File

@ -0,0 +1,156 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, Tag, Tooltip } from 'ant-design-vue';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getProcessDefinitionPage } from '#/api/bpm/definition';
import { BpmModelFormType } from '#/utils';
// FormCreate
import FormCreateDetail from '../../form/modules/detail.vue';
import { useGridColumns } from './data';
defineOptions({ name: 'BpmProcessDefinition' });
const [FormCreateDetailModal, formCreateDetailModalApi] = useVbenModal({
connectedComponent: FormCreateDetail,
destroyOnClose: true,
});
/** 查看表单详情 */
function handleFormDetail(row: any) {
if (row.formType === BpmModelFormType.NORMAL) {
const data = {
id: row.formId,
};
formCreateDetailModalApi.setData(data).open();
} else {
// TODO
console.warn('业务表单待实现', row);
}
}
const router = useRouter();
/** 恢复流程模型 */
async function openModelForm(id?: number) {
await router.push({
name: 'BpmModelUpdate',
params: { id, type: 'definition' },
});
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
const route = useRoute();
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: useGridColumns(),
height: 'auto',
keepSource: true,
pagerConfig: {
enabled: true,
},
proxyConfig: {
ajax: {
query: async ({ page }) => {
const params = {
pageNo: page?.currentPage,
pageSize: page?.pageSize,
key: route.query.key,
};
return await getProcessDefinitionPage(params);
},
},
},
rowConfig: {
keyField: 'id',
},
toolbarConfig: {
refresh: { code: 'query' },
},
} as VxeTableGridOptions,
});
/** 初始化 */
onMounted(() => {
onRefresh();
});
</script>
<template>
<Page auto-content-height>
<template #doc>
<DocAlert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
</template>
<Grid table-title="">
<template #icon="{ row }">
<Image
v-if="row.icon"
:src="row.icon"
:width="24"
:height="24"
class="rounded"
/>
<span v-else> </span>
</template>
<template #startUsers="{ row }">
<template v-if="!row.startUsers?.length"></template>
<template v-else-if="row.startUsers.length === 1">
{{ row.startUsers[0].nickname }}
</template>
<template v-else>
<Tooltip
placement="top"
:title="row.startUsers.map((user: any) => user.nickname).join(',')"
>
{{ row.startUsers[0].nickname }}
{{ row.startUsers.length }} 人可见
</Tooltip>
</template>
</template>
<template #formInfo="{ row }">
<Button
v-if="row.formType === BpmModelFormType.NORMAL"
type="link"
@click="handleFormDetail(row)"
>
<span>{{ row.formName }}</span>
</Button>
<Button
v-else-if="row.formType === BpmModelFormType.CUSTOM"
type="link"
@click="handleFormDetail(row)"
>
<span>{{ row.formCustomCreatePath }}</span>
</Button>
<span v-else></span>
</template>
<template #version="{ row }">
<Tag>v{{ row.version }}</Tag>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: '恢复',
type: 'link',
auth: ['bpm:model:update'],
onClick: openModelForm.bind(null, row.id),
},
]"
/>
</template>
</Grid>
<FormCreateDetailModal />
</Page>
</template>

View File

@ -26,8 +26,11 @@ import {
HttpRequestSetting,
parseFormFields,
} from '#/components/simple-process-design';
import { ProcessVariableEnum } from '#/utils';
import { BpmAutoApproveType, BpmModelFormType } from '#/utils/constants';
import {
BpmAutoApproveType,
BpmModelFormType,
ProcessVariableEnum,
} from '#/utils';
const modelData = defineModel<any>();

View File

@ -178,13 +178,13 @@ async function handleCategorySortSubmit() {
<template #overlay>
<Menu @click="(e) => handleCommand(e.key as string)">
<Menu.Item key="handleCategoryAdd">
<div class="flex items-center">
<div class="flex items-center gap-1">
<IconifyIcon icon="lucide:plus" />
新建分类
</div>
</Menu.Item>
<Menu.Item key="handleCategorySort">
<div class="flex items-center">
<div class="flex items-center gap-1">
<IconifyIcon icon="lucide:align-start-vertical" />
分类排序
</div>

View File

@ -4,7 +4,7 @@ import type { BpmModelApi, ModelCategoryInfo } from '#/api/bpm/model';
import { computed, ref, watchEffect } from 'vue';
import { useRouter } from 'vue-router';
import { confirm, useVbenModal } from '@vben/common-ui';
import { confirm, EllipsisText, useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { useUserStore } from '@vben/stores';
import { cloneDeep, formatDateTime, isEqual } from '@vben/utils';
@ -33,10 +33,12 @@ import {
} from '#/api/bpm/model';
import { DictTag } from '#/components/dict-tag';
import { $t } from '#/locales';
import { DICT_TYPE } from '#/utils';
import { BpmModelFormType, DICT_TYPE } from '#/utils';
//
import CategoryRenameForm from '../../category/modules/rename-form.vue';
// FormCreate
import FormCreateDetail from '../../form/modules/detail.vue';
const props = defineProps<{
categoryInfo: ModelCategoryInfo;
@ -45,12 +47,18 @@ const props = defineProps<{
const emit = defineEmits(['success']);
//
/** 重命名分类对话框 */
const [CategoryRenameModal, categoryRenameModalApi] = useVbenModal({
connectedComponent: CategoryRenameForm,
destroyOnClose: true,
});
/** 流程表单详情对话框 */
const [FormCreateDetailModal, formCreateDetailModalApi] = useVbenModal({
connectedComponent: FormCreateDetail,
destroyOnClose: true,
});
const router = useRouter();
// Id
const userStore = useUserStore();
@ -73,8 +81,7 @@ const columns = [
dataIndex: 'name',
key: 'name',
align: 'left' as const,
ellipsis: true,
width: 230,
width: 250,
},
{
title: '可见范围',
@ -193,8 +200,15 @@ async function handleDeleteCategory() {
/** 处理表单详情点击 */
function handleFormDetail(row: any) {
// TODO
console.warn('待实现', row);
if (row.formType === BpmModelFormType.NORMAL) {
const data = {
id: row.formId,
};
formCreateDetailModalApi.setData(data).open();
} else {
// TODO
console.warn('业务表单待实现', row);
}
}
/** 判断是否是流程管理员 */
@ -243,7 +257,7 @@ function handleModelCommand(command: string, row: any) {
break;
}
case 'handleDefinitionList': {
console.warn('历史待实现', row);
handleDefinitionList(row);
break;
}
case 'handleDelete': {
@ -317,6 +331,16 @@ function handleDelete(row: any) {
});
}
/** 跳转到指定流程定义列表 */
function handleDefinitionList(row: any) {
router.push({
name: 'BpmProcessDefinition',
query: {
key: row.key,
},
});
}
/** 更新 modelList 模型列表 */
const updateModelList = useDebounceFn(() => {
const newModelList = props.categoryInfo.modelList;
@ -486,7 +510,9 @@ const handleRenameSuccess = () => {
class="mr-2.5 h-9 w-9 rounded"
alt="图标"
/>
{{ record.name }}
<EllipsisText :max-width="160" :tooltip-when-ellipsis="true">
{{ record.name }}
</EllipsisText>
</div>
</template>
@ -543,7 +569,7 @@ const handleRenameSuccess = () => {
<template v-else-if="column.key === 'formType'">
<!-- TODO BpmModelFormType.NORMAL -->
<Button
v-if="record.formType === 10"
v-if="record.formType === BpmModelFormType.NORMAL"
type="link"
@click="handleFormDetail(record)"
>
@ -551,7 +577,7 @@ const handleRenameSuccess = () => {
</Button>
<!-- TODO BpmModelFormType.CUSTOM -->
<Button
v-else-if="record.formType === 20"
v-else-if="record.formType === BpmModelFormType.CUSTOM"
type="link"
@click="handleFormDetail(record)"
>
@ -604,7 +630,6 @@ const handleRenameSuccess = () => {
</Button>
<Dropdown placement="bottomRight" arrow>
<Button type="link" size="small" class="px-1">更多</Button>
<!-- TODO 待实现 -->
<template #overlay>
<Menu
@click="
@ -613,12 +638,14 @@ const handleRenameSuccess = () => {
>
<Menu.Item key="handleCopy"> 复制 </Menu.Item>
<Menu.Item key="handleDefinitionList"> 历史 </Menu.Item>
<!-- TODO 待实现报表
<Menu.Item
key="handleReport"
:disabled="!isManagerUser(record)"
>
报表
</Menu.Item>
</Menu.Item> -->
<Menu.Item
key="handleChangeState"
v-if="record.processDefinition"
@ -657,6 +684,8 @@ const handleRenameSuccess = () => {
<!-- 重命名分类弹窗 -->
<CategoryRenameModal @success="handleRenameSuccess" />
<!-- 流程表单详情对话框 -->
<FormCreateDetailModal />
</div>
</template>

View File

@ -1,10 +1,11 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { erpPriceMultiply } from '@vben/utils';
import { getBusinessStatusTypeSimpleList } from '#/api/crm/business/status';
import { getCustomerSimpleList } from '#/api/crm/customer';
import { getSimpleUserList } from '#/api/system/user';
import { erpPriceMultiply } from '#/utils';
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
@ -167,7 +168,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
{
field: 'totalPrice',
title: '商机金额(元)',
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
field: 'dealTime',

View File

@ -1,9 +1,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { DescriptionItemSchema } from '#/components/description';
import { formatDateTime } from '@vben/utils';
import { erpPriceInputFormatter } from '#/utils';
import { erpPriceInputFormatter, formatDateTime } from '@vben/utils';
/** 详情页的字段 */
export function useDetailSchema(): DescriptionItemSchema[] {
@ -97,7 +95,7 @@ export function useDetailListColumns(): VxeTableGridOptions['columns'] {
{
field: 'totalPrice',
title: '商机金额(元)',
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
field: 'dealTime',

View File

@ -4,6 +4,7 @@ import type { CrmBusinessApi } from '#/api/crm/business';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { erpPriceMultiply } from '@vben/utils';
import { message } from 'ant-design-vue';
@ -15,7 +16,6 @@ import {
} from '#/api/crm/business';
import { BizTypeEnum } from '#/api/crm/permission';
import { $t } from '#/locales';
import { erpPriceMultiply } from '#/utils';
import { ProductEditTable } from '#/views/crm/product';
import { useFormSchema } from '../data';

View File

@ -1,13 +1,14 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { erpPriceMultiply, floatToFixed2 } from '@vben/utils';
import { z } from '#/adapter/form';
import { getSimpleBusinessList } from '#/api/crm/business';
import { getSimpleContactList } from '#/api/crm/contact';
import { getCustomerSimpleList } from '#/api/crm/customer';
import { getSimpleUserList } from '#/api/system/user';
import { erpPriceMultiply, floatToFixed2 } from '#/utils';
import { DICT_TYPE } from '#/utils/dict';
import { DICT_TYPE } from '#/utils';
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
@ -242,7 +243,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
title: '合同金额(元)',
field: 'totalPrice',
minWidth: 150,
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
title: '下单时间',
@ -277,7 +278,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
title: '已回款金额(元)',
field: 'totalReceivablePrice',
minWidth: 150,
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
title: '未回款金额(元)',

View File

@ -3,11 +3,14 @@ import type { DescriptionItemSchema } from '#/components/description';
import { h } from 'vue';
import { formatDateTime } from '@vben/utils';
import {
erpPriceInputFormatter,
floatToFixed2,
formatDateTime,
} from '@vben/utils';
import { DictTag } from '#/components/dict-tag';
import { DICT_TYPE, erpPriceInputFormatter, floatToFixed2 } from '#/utils';
import { DICT_TYPE } from '#/utils';
/** 详情头部的配置 */
export function useDetailSchema(): DescriptionItemSchema[] {
return [
@ -120,7 +123,7 @@ export function useDetailListColumns(): VxeTableGridOptions['columns'] {
title: '合同金额(元)',
field: 'totalPrice',
minWidth: 150,
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
title: '合同开始时间',
@ -138,7 +141,7 @@ export function useDetailListColumns(): VxeTableGridOptions['columns'] {
title: '已回款金额(元)',
field: 'totalReceivablePrice',
minWidth: 150,
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
title: '未回款金额(元)',

View File

@ -4,6 +4,7 @@ import type { CrmContractApi } from '#/api/crm/contract';
import { computed, ref } from 'vue';
import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { erpPriceMultiply } from '@vben/utils';
import { message } from 'ant-design-vue';
@ -14,7 +15,6 @@ import {
} from '#/api/crm/contract';
import { BizTypeEnum } from '#/api/crm/permission';
import { $t } from '#/locales';
import { erpPriceMultiply } from '#/utils';
import { ProductEditTable } from '#/views/crm/product';
import { useFormSchema } from '../data';

View File

@ -1,7 +1,7 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { DICT_TYPE, getDictOptions } from '#/utils';
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {

View File

@ -140,7 +140,7 @@ async function handleQuit() {
message.warning('你不是团队成员!');
return;
}
await deleteSelfPermission(userPermission.id);
await deleteSelfPermission(userPermission.id as number);
message.success('退出团队成员成功!');
emits('quitTeam');
}

View File

@ -134,7 +134,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
{
field: 'price',
title: '价格(元)',
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
field: 'description',
@ -203,7 +203,7 @@ export function useProductEditTableColumns(): VxeTableGridOptions['columns'] {
field: 'productPrice',
title: '价格(元)',
minWidth: 100,
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
field: 'sellingPrice',
@ -221,7 +221,7 @@ export function useProductEditTableColumns(): VxeTableGridOptions['columns'] {
field: 'totalPrice',
title: '合计',
minWidth: 100,
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
title: '操作',

View File

@ -3,8 +3,10 @@ import type { DescriptionItemSchema } from '#/components/description';
import { h } from 'vue';
import { erpPriceInputFormatter } from '@vben/utils';
import { DictTag } from '#/components/dict-tag';
import { DICT_TYPE, erpPriceInputFormatter } from '#/utils';
import { DICT_TYPE } from '#/utils';
/** 详情页的字段 */
export function useDetailSchema(): DescriptionItemSchema[] {
@ -94,12 +96,12 @@ export function useDetailListColumns(
{
field: 'productPrice',
title: '产品价格(元)',
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
field: 'businessPrice',
title: '商机价格(元)',
formatter: 'formatNumber',
formatter: 'formatAmount2',
visible: showBussinePrice,
},
{
@ -110,7 +112,7 @@ export function useDetailListColumns(
{
field: 'totalPrice',
title: '合计金额(元)',
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
];
}

View File

@ -4,11 +4,12 @@ import type { CrmProductApi } from '#/api/crm/product';
import { ref } from 'vue';
import { erpPriceInputFormatter } from '@vben/utils';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getBusiness } from '#/api/crm/business';
import { getContract } from '#/api/crm/contract';
import { BizTypeEnum } from '#/api/crm/permission';
import { erpPriceInputFormatter } from '#/utils';
import { useDetailListColumns } from './detail-data';

View File

@ -5,12 +5,13 @@ import type { CrmProductApi } from '#/api/crm/product';
import { nextTick, onMounted, ref, watch } from 'vue';
import { erpPriceMultiply } from '@vben/utils';
import { InputNumber, Select } from 'ant-design-vue';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { BizTypeEnum } from '#/api/crm/permission';
import { getProductSimpleList } from '#/api/crm/product';
import { erpPriceMultiply } from '#/utils';
import { useProductEditTableColumns } from '../data';

View File

@ -5,7 +5,7 @@ import { getContractSimpleList } from '#/api/crm/contract';
import { getCustomerSimpleList } from '#/api/crm/customer';
import { getReceivablePlanSimpleList } from '#/api/crm/receivable/plan';
import { getSimpleUserList } from '#/api/system/user';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { DICT_TYPE, getDictOptions } from '#/utils';
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
@ -199,7 +199,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
title: '回款金额(元)',
field: 'price',
minWidth: 150,
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
title: '回款方式',
@ -219,7 +219,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
title: '合同金额(元)',
field: 'contract.totalPrice',
minWidth: 150,
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
title: '负责人',

View File

@ -3,10 +3,10 @@ import type { DescriptionItemSchema } from '#/components/description';
import { h } from 'vue';
import { formatDateTime } from '@vben/utils';
import { erpPriceInputFormatter, formatDateTime } from '@vben/utils';
import { DictTag } from '#/components/dict-tag';
import { DICT_TYPE, erpPriceInputFormatter } from '#/utils';
import { DICT_TYPE } from '#/utils';
/** 详情页的字段 */
export function useDetailSchema(): DescriptionItemSchema[] {
@ -108,7 +108,7 @@ export function useDetailListColumns(): VxeTableGridOptions['columns'] {
title: '回款金额(元)',
field: 'price',
minWidth: 150,
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
title: '回款方式',

View File

@ -1,8 +1,10 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { floatToFixed2 } from '@vben/utils';
import { getCustomerSimpleList } from '#/api/crm/customer';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { DICT_TYPE, getDictOptions } from '#/utils';
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
@ -141,7 +143,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
title: '计划回款金额(元)',
field: 'price',
minWidth: 160,
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
title: '计划回款日期',
@ -183,7 +185,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
title: '实际回款金额(元)',
field: 'receivable.price',
minWidth: 160,
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
title: '实际回款日期',
@ -197,9 +199,9 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
minWidth: 160,
formatter: ({ row }) => {
if (row.receivable) {
return row.price - row.receivable.price;
return floatToFixed2(row.price - row.receivable.price);
}
return row.price;
return floatToFixed2(row.price);
},
},
{

View File

@ -3,10 +3,10 @@ import type { DescriptionItemSchema } from '#/components/description';
import { h } from 'vue';
import { formatDateTime } from '@vben/utils';
import { erpPriceInputFormatter, formatDateTime } from '@vben/utils';
import { DictTag } from '#/components/dict-tag';
import { DICT_TYPE, erpPriceInputFormatter } from '#/utils';
import { DICT_TYPE } from '#/utils';
/** 详情页的字段 */
export function useDetailSchema(): DescriptionItemSchema[] {
@ -101,7 +101,7 @@ export function useDetailListColumns(): VxeTableGridOptions['columns'] {
title: '计划回款(元)',
field: 'price',
minWidth: 150,
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
title: '计划回款日期',

View File

@ -8,6 +8,7 @@ import { useRouter } from 'vue-router';
import { confirm, DocAlert, Page } from '@vben/common-ui';
import {
downloadFileFromBlobPart,
fenToYuan,
handleTree,
treeToString,
} from '@vben/utils';
@ -24,7 +25,7 @@ import {
updateStatus,
} from '#/api/mall/product/spu';
import { $t } from '#/locales';
import { fenToYuan, ProductSpuStatusEnum } from '#/utils';
import { ProductSpuStatusEnum } from '#/utils';
import { useGridColumns, useGridFormSchema } from './data';

View File

@ -5,6 +5,7 @@ import type { MallOrderApi } from '#/api/mall/trade/order';
import { h, onMounted, ref } from 'vue';
import { Page, prompt } from '@vben/common-ui';
import { fenToYuan } from '@vben/utils';
import { Card, Input, message } from 'ant-design-vue';
@ -15,7 +16,7 @@ import {
getOrderSummary,
} from '#/api/mall/trade/order';
import { SummaryCard } from '#/components/summary-card';
import { DeliveryTypeEnum, fenToYuan, TradeOrderStatusEnum } from '#/utils';
import { DeliveryTypeEnum, TradeOrderStatusEnum } from '#/utils';
import { useGridColumns, useGridFormSchema } from './data';

View File

@ -6,6 +6,7 @@ import { h } from 'vue';
import { useRouter } from 'vue-router';
import { DocAlert, Page, prompt, useVbenModal } from '@vben/common-ui';
import { fenToYuan } from '@vben/utils';
import { Image, List, Tag, Textarea } from 'ant-design-vue';
@ -13,12 +14,7 @@ import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getOrderPage, updateOrderRemark } from '#/api/mall/trade/order';
import { DictTag } from '#/components/dict-tag';
import { $t } from '#/locales';
import {
DeliveryTypeEnum,
DICT_TYPE,
fenToYuan,
TradeOrderStatusEnum,
} from '#/utils';
import { DeliveryTypeEnum, DICT_TYPE, TradeOrderStatusEnum } from '#/utils';
import { useGridColumns, useGridFormSchema } from './data';
import DeleveryForm from './modules/delevery-form.vue';

View File

@ -2,10 +2,11 @@
import type { MemberUserApi } from '#/api/member/user';
import type { PayWalletApi } from '#/api/pay/wallet/balance';
import { fenToYuan } from '@vben/utils';
import { Card } from 'ant-design-vue';
import { useDescription } from '#/components/description';
import { fenToYuan } from '#/utils';
withDefaults(
defineProps<{

View File

@ -3,6 +3,8 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { h } from 'vue';
import { convertToInteger, formatToFraction } from '@vben/utils';
import { Tag } from 'ant-design-vue';
import { z } from '#/adapter/form';
@ -12,9 +14,7 @@ import { getSimpleTagList } from '#/api/member/tag';
import { getAreaTree } from '#/api/system/area';
import {
CommonStatusEnum,
convertToInteger,
DICT_TYPE,
formatToFraction,
getDictOptions,
getRangePickerDefaultProps,
} from '#/utils';

View File

@ -4,6 +4,7 @@ import type { MemberUserApi } from '#/api/member/user';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { formatToFraction } from '@vben/utils';
import { message } from 'ant-design-vue';
@ -11,7 +12,6 @@ import { useVbenForm } from '#/adapter/form';
import { getUser, updateUser } from '#/api/member/user';
import { getWallet } from '#/api/pay/wallet/balance';
import { $t } from '#/locales';
import { formatToFraction } from '#/utils';
import { useBalanceFormSchema } from '../data';

View File

@ -2,8 +2,7 @@ import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { PayAppApi } from '#/api/pay/app';
import { CommonStatusEnum } from '#/utils/constants';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
export function useGridFormSchema(): VbenFormSchema[] {
return [

View File

@ -10,7 +10,7 @@ import { message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { changeAppStatus, deleteApp, getAppPage } from '#/api/pay/app';
import { $t } from '#/locales';
import { CommonStatusEnum, PayChannelEnum } from '#/utils/constants';
import { CommonStatusEnum, PayChannelEnum } from '#/utils';
import { useGridColumns, useGridFormSchema } from './data';
import appFrom from './modules/app-form.vue';

View File

@ -3,7 +3,7 @@ import type { VbenFormSchema } from '#/adapter/form';
import { h } from 'vue';
import { InputUpload } from '#/components/upload';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { DICT_TYPE, getDictOptions } from '#/utils';
export function channelSchema(formType: string): VbenFormSchema[] {
if (formType.includes('alipay_')) {

View File

@ -6,7 +6,7 @@ import { useRoute, useRouter } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
import { useTabs } from '@vben/hooks';
import { formatDate } from '@vben/utils';
import { fenToYuan, formatDate } from '@vben/utils';
import {
Button,
@ -19,7 +19,6 @@ import {
import { getOrder, submitOrder } from '#/api/pay/order';
import {
fenToYuan,
PayChannelEnum,
PayDisplayModeEnum,
PayOrderStatusEnum,

View File

@ -50,12 +50,12 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
{
field: 'price',
title: '支付价格',
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
field: 'refundPrice',
title: '退款金额',
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
field: 'createTime',

View File

@ -78,7 +78,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
{
field: 'price',
title: '提现金额',
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
field: 'userName',

View File

@ -3,6 +3,7 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { DemoWithdrawApi } from '#/api/pay/demo/withdraw';
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
import { floatToFixed2 } from '@vben/utils';
import { message, Tag } from 'ant-design-vue';
@ -109,7 +110,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
<Tag v-else-if="row.type === 3">钱包余额</Tag>
</template>
<template #price="{ row }">
<span>{{ (row.price / 100.0).toFixed(2) }}</span>
<span>{{ floatToFixed2(row.price) }}</span>
</template>
<template #status="{ row }">
<Tag v-if="row.status === 0 && !row.payTransferId" type="warning">

View File

@ -78,17 +78,17 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
{
title: '支付金额',
field: 'price',
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
title: '退款金额',
field: 'refundPrice',
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
title: '手续金额',
field: 'channelFeePrice',
formatter: 'formatNumber',
formatter: 'formatAmount2',
},
{
title: '订单号',

View File

@ -4,13 +4,13 @@ import type { PayOrderApi } from '#/api/pay/order';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { formatDateTime } from '@vben/utils';
import { floatToFixed2, formatDateTime } from '@vben/utils';
import { Descriptions, Divider, Tag } from 'ant-design-vue';
import { getOrder } from '#/api/pay/order';
import { DictTag } from '#/components/dict-tag';
import { DICT_TYPE } from '#/utils/dict';
import { DICT_TYPE } from '#/utils';
const detailData = ref<PayOrderApi.Order>();
@ -63,16 +63,16 @@ const [Modal, modalApi] = useVbenModal({
</Descriptions.Item>
<Descriptions.Item label="支付金额">
<Tag color="green" size="small">
{{ (detailData?.price || 0 / 100.0).toFixed(2) }}
{{ floatToFixed2(detailData?.price) }}
</Tag>
</Descriptions.Item>
<Descriptions.Item label="手续费">
<Tag color="orange" size="small">
{{ (detailData?.channelFeePrice || 0 / 100.0).toFixed(2) }}
{{ floatToFixed2(detailData?.channelFeePrice) }}
</Tag>
</Descriptions.Item>
<Descriptions.Item label="手续费比例">
{{ (detailData?.channelFeeRate || 0 / 100.0).toFixed(2) }}%
{{ floatToFixed2(detailData?.channelFeeRate) }}%
</Descriptions.Item>
<Descriptions.Item label="支付时间">
{{ formatDateTime(detailData?.successTime) }}
@ -115,7 +115,7 @@ const [Modal, modalApi] = useVbenModal({
</Descriptions.Item>
<Descriptions.Item label="退款金额" :span="2">
<Tag size="small" color="red">
{{ (detailData?.refundPrice || 0 / 100.0).toFixed(2) }}
{{ floatToFixed2(detailData?.refundPrice) }}
</Tag>
</Descriptions.Item>
<Descriptions.Item label="通知 URL">

View File

@ -4,7 +4,7 @@ import type { DescriptionItemSchema } from '#/components/description';
import { h } from 'vue';
import { formatDateTime } from '@vben/utils';
import { floatToFixed2, formatDateTime } from '@vben/utils';
import { Tag } from 'ant-design-vue';
@ -120,7 +120,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
{
field: 'price',
title: '转账金额',
formatter: ({ cellValue }) => `${(cellValue / 100).toFixed(2)}`,
formatter: 'formatAmount2',
},
{
field: 'status',
@ -217,7 +217,7 @@ export function useDetailSchema(): DescriptionItemSchema[] {
content: (data) => {
return h(Tag, {
color: 'blue',
content: `${(data?.price / 100).toFixed(2)}`,
content: `${floatToFixed2(data?.price)}`,
});
},
},

View File

@ -4,6 +4,7 @@ import type { WalletRechargePackageApi } from '#/api/pay/wallet/rechargePackage'
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { fenToYuan, yuanToFen } from '@vben/utils';
import { message } from 'ant-design-vue';
@ -14,7 +15,6 @@ import {
updatePackage,
} from '#/api/pay/wallet/rechargePackage';
import { $t } from '#/locales';
import { fenToYuan, yuanToFen } from '#/utils';
import { useFormSchema } from '../data';

View File

@ -76,6 +76,15 @@ export function useTypeGridFormSchema(): VbenFormSchema[] {
clearable: true,
},
},
{
fieldName: 'type',
label: '字典类型',
component: 'Input',
componentProps: {
placeholder: '请输入字典类型',
clearable: true,
},
},
{
fieldName: 'status',
label: '状态',

View File

@ -1,4 +1,6 @@
<script lang="ts" setup>
import type { Recordable } from '@vben/types';
import type { SystemDeptApi } from '#/api/system/dept';
import type { SystemRoleApi } from '#/api/system/role';
@ -13,6 +15,7 @@ import { useVbenForm } from '#/adapter/form';
import { getMenuList } from '#/api/system/menu';
import { assignRoleMenu, getRoleMenuList } from '#/api/system/permission';
import { $t } from '#/locales';
import { SystemMenuTypeEnum } from '#/utils';
import { useAssignMenuFormSchema } from '../data';
@ -121,6 +124,18 @@ function getAllNodeIds(nodes: any[], ids: number[] = []): number[] {
});
return ids;
}
function getNodeClass(node: Recordable<any>) {
const classes: string[] = [];
if (node.value?.type === SystemMenuTypeEnum.BUTTON) {
classes.push('inline-flex');
if (node.index % 3 >= 1) {
classes.push('!pl-0');
}
}
return classes.join(' ');
}
</script>
<template>
@ -134,9 +149,11 @@ function getAllNodeIds(nodes: any[], ids: number[] = []): number[] {
multiple
bordered
:expanded="expandedKeys"
:get-node-class="getNodeClass"
v-bind="slotProps"
value-field="id"
label-field="name"
icon-field="meta.icon"
/>
</template>
</Form>

View File

@ -1,12 +1,25 @@
// TODO @xingyu感觉 formatToFraction 可以整合起来;【优先级:低】
import { isEmpty, isString, isUndefined } from './inference';
/**
*
* @param num
* @param digit
*/
export function formatToFractionDigit(
num: number | string | undefined,
digit: number = 2,
): string {
if (isUndefined(num)) return '0.00';
const parsedNumber = isString(num) ? Number.parseFloat(num) : num;
return (parsedNumber / 100).toFixed(digit);
}
/**
*
* @param num
*/
export function formatToFraction(num: number | string | undefined): string {
if (num === undefined) return '0.00';
const parsedNumber = typeof num === 'string' ? Number.parseFloat(num) : num;
return (parsedNumber / 100).toFixed(2);
return formatToFractionDigit(num, 2);
}
/**
@ -17,9 +30,7 @@ export function formatToFraction(num: number | string | undefined): string {
*/
export function floatToFixed2(num: number | string | undefined): string {
let str = '0.00';
if (num === undefined) {
return str;
}
if (isUndefined(num)) return str;
const f = formatToFraction(num);
const decimalPart = f.toString().split('.')[1];
const len = decimalPart ? decimalPart.length : 0;
@ -45,8 +56,8 @@ export function floatToFixed2(num: number | string | undefined): string {
* @param num
*/
export function convertToInteger(num: number | string | undefined): number {
if (num === undefined) return 0;
const parsedNumber = typeof num === 'string' ? Number.parseFloat(num) : num;
if (isUndefined(num)) return 0;
const parsedNumber = isString(num) ? Number.parseFloat(num) : num;
return Math.round(parsedNumber * 100);
}
@ -125,7 +136,6 @@ export function erpCountInputFormatter(num: number | string | undefined) {
return erpNumberFormatter(num, ERP_COUNT_DIGIT);
}
// noinspection JSCommentMatchesSignature
/**
* ERP
*
@ -148,7 +158,6 @@ export function erpPriceInputFormatter(num: number | string | undefined) {
return erpNumberFormatter(num, ERP_PRICE_DIGIT);
}
// noinspection JSCommentMatchesSignature
/**
* ERP
*
@ -167,9 +176,7 @@ export function erpPriceTableColumnFormatter(cellValue: any) {
* @return undefined
*/
export function erpPriceMultiply(price: number, count: number) {
if (price === null || count === null) {
return undefined;
}
if (isEmpty(price) || isEmpty(count)) return undefined;
return Number.parseFloat((price * count).toFixed(ERP_PRICE_DIGIT));
}

View File

@ -3,6 +3,7 @@ export * from './date';
export * from './diff';
export * from './dom';
export * from './download';
export * from './formatNumber';
export * from './inference';
export * from './letter';
export * from './merge';