Pre Merge pull request !161 from xingyu/dev
						commit
						10e17b3834
					
				|  | @ -11,9 +11,9 @@ import { | |||
|   useVbenVxeGrid, | ||||
| } from '@vben/plugins/vxe-table'; | ||||
| import { | ||||
|   erpCountInputFormatter, | ||||
|   erpNumberFormatter, | ||||
|   formatPast2, | ||||
|   formatToFractionDigit, | ||||
|   isFunction, | ||||
|   isString, | ||||
| } from '@vben/utils'; | ||||
|  | @ -333,8 +333,8 @@ setupVbenVxeTable({ | |||
| 
 | ||||
|     // add by 星语:数量格式化,例如说:金额
 | ||||
|     vxeUI.formats.add('formatNumber', { | ||||
|       tableCellFormatMethod({ cellValue }, digits = 2) { | ||||
|         return formatToFractionDigit(cellValue, digits); | ||||
|       tableCellFormatMethod({ cellValue }) { | ||||
|         return erpCountInputFormatter(cellValue); | ||||
|       }, | ||||
|     }); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,12 +1,12 @@ | |||
| <script setup lang="ts"> | ||||
| import type { Rule } from 'ant-design-vue/es/form'; | ||||
| 
 | ||||
| import type { Ref } from 'vue'; | ||||
| import type { ComponentPublicInstance, Ref } from 'vue'; | ||||
| 
 | ||||
| import type { ButtonSetting, SimpleFlowNode } from '../../consts'; | ||||
| import type { UserTaskFormType } from '../../helpers'; | ||||
| 
 | ||||
| import { computed, onMounted, reactive, ref } from 'vue'; | ||||
| import { computed, nextTick, onMounted, reactive, ref } from 'vue'; | ||||
| 
 | ||||
| import { useVbenDrawer } from '@vben/common-ui'; | ||||
| import { IconifyIcon } from '@vben/icons'; | ||||
|  | @ -144,6 +144,7 @@ const { | |||
|   btnDisplayNameEdit, | ||||
|   changeBtnDisplayName, | ||||
|   btnDisplayNameBlurEvent, | ||||
|   setInputRef, | ||||
| } = useButtonsSetting(); | ||||
| 
 | ||||
| const approveType = ref(ApproveType.USER); | ||||
|  | @ -453,9 +454,19 @@ function useButtonsSetting() { | |||
|   const buttonsSetting = ref<ButtonSetting[]>(); | ||||
|   // 操作按钮显示名称可编辑 | ||||
|   const btnDisplayNameEdit = ref<boolean[]>([]); | ||||
|   // 输入框的引用数组 - 内部使用,不暴露出去 | ||||
|   const _btnDisplayNameInputRefs = ref<Array<HTMLInputElement | null>>([]); | ||||
| 
 | ||||
|   const changeBtnDisplayName = (index: number) => { | ||||
|     btnDisplayNameEdit.value[index] = true; | ||||
|     // 输入框自动聚集 | ||||
|     nextTick(() => { | ||||
|       if (_btnDisplayNameInputRefs.value[index]) { | ||||
|         _btnDisplayNameInputRefs.value[index]?.focus(); | ||||
|       } | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   const btnDisplayNameBlurEvent = (index: number) => { | ||||
|     btnDisplayNameEdit.value[index] = false; | ||||
|     const buttonItem = buttonsSetting.value![index]; | ||||
|  | @ -463,11 +474,21 @@ function useButtonsSetting() { | |||
|       buttonItem.displayName = | ||||
|         buttonItem.displayName || OPERATION_BUTTON_NAME.get(buttonItem.id)!; | ||||
|   }; | ||||
| 
 | ||||
|   // 设置 ref 引用的方法 | ||||
|   const setInputRef = ( | ||||
|     el: ComponentPublicInstance | Element | null, | ||||
|     index: number, | ||||
|   ) => { | ||||
|     _btnDisplayNameInputRefs.value[index] = el as HTMLInputElement; | ||||
|   }; | ||||
| 
 | ||||
|   return { | ||||
|     buttonsSetting, | ||||
|     btnDisplayNameEdit, | ||||
|     changeBtnDisplayName, | ||||
|     btnDisplayNameBlurEvent, | ||||
|     setInputRef, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
|  | @ -1123,12 +1144,13 @@ onMounted(() => { | |||
|                 {{ OPERATION_BUTTON_NAME.get(item.id) }} | ||||
|               </Col> | ||||
|               <Col :span="12" class="flex items-center"> | ||||
|                 <!-- TODO  v-mountedFocus 自动聚集需要迁移 --> | ||||
|                 <Input | ||||
|                   v-if="btnDisplayNameEdit[index]" | ||||
|                   :ref="(el) => setInputRef(el, index)" | ||||
|                   type="text" | ||||
|                   class="max-w-32 focus:border-blue-500 focus:shadow-[0_0_0_2px_rgba(24,144,255,0.2)] focus:outline-none" | ||||
|                   @blur="btnDisplayNameBlurEvent(index)" | ||||
|                   @press-enter="btnDisplayNameBlurEvent(index)" | ||||
|                   v-model:value="item.displayName" | ||||
|                   :placeholder="item.displayName" | ||||
|                 /> | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import type { PropType } from 'vue'; | |||
| 
 | ||||
| import type { ActionItem, PopConfirm } from './typing'; | ||||
| 
 | ||||
| import { computed, ref, toRaw, unref, watchEffect } from 'vue'; | ||||
| import { computed, unref } from 'vue'; | ||||
| 
 | ||||
| import { useAccess } from '@vben/access'; | ||||
| import { IconifyIcon } from '@vben/icons'; | ||||
|  | @ -41,14 +41,7 @@ const props = defineProps({ | |||
| 
 | ||||
| const { hasAccessByCodes } = useAccess(); | ||||
| 
 | ||||
| /** 缓存处理后的 actions */ | ||||
| const processedActions = ref<any[]>([]); | ||||
| const processedDropdownActions = ref<any[]>([]); | ||||
| 
 | ||||
| /** 用于比较的字符串化版本 */ | ||||
| const actionsStringField = ref(''); | ||||
| const dropdownActionsStringField = ref(''); | ||||
| 
 | ||||
| /** 检查是否显示 */ | ||||
| function isIfShow(action: ActionItem): boolean { | ||||
|   const ifShow = action.ifShow; | ||||
|   let isIfShow = true; | ||||
|  | @ -65,12 +58,10 @@ function isIfShow(action: ActionItem): boolean { | |||
|   return isIfShow; | ||||
| } | ||||
| 
 | ||||
| /** 处理 actions 的纯函数 */ | ||||
| function processActions(actions: ActionItem[]): any[] { | ||||
|   return actions | ||||
|     .filter((action: ActionItem) => { | ||||
|       return isIfShow(action); | ||||
|     }) | ||||
| /** 处理按钮 actions */ | ||||
| const getActions = computed(() => { | ||||
|   return (props.actions || []) | ||||
|     .filter((action: ActionItem) => isIfShow(action)) | ||||
|     .map((action: ActionItem) => { | ||||
|       const { popConfirm } = action; | ||||
|       return { | ||||
|  | @ -82,17 +73,12 @@ function processActions(actions: ActionItem[]): any[] { | |||
|         enable: !!popConfirm, | ||||
|       }; | ||||
|     }); | ||||
| } | ||||
| }); | ||||
| 
 | ||||
| /** 处理下拉菜单 actions 的纯函数 */ | ||||
| function processDropdownActions( | ||||
|   dropDownActions: ActionItem[], | ||||
|   divider: boolean, | ||||
| ): any[] { | ||||
|   return dropDownActions | ||||
|     .filter((action: ActionItem) => { | ||||
|       return isIfShow(action); | ||||
|     }) | ||||
| /** 处理下拉菜单 actions */ | ||||
| const getDropdownList = computed(() => { | ||||
|   return (props.dropDownActions || []) | ||||
|     .filter((action: ActionItem) => isIfShow(action)) | ||||
|     .map((action: ActionItem, index: number) => { | ||||
|       const { label, popConfirm } = action; | ||||
|       const processedAction = { ...action }; | ||||
|  | @ -103,79 +89,21 @@ function processDropdownActions( | |||
|         onConfirm: popConfirm?.confirm, | ||||
|         onCancel: popConfirm?.cancel, | ||||
|         text: label, | ||||
|         divider: index < dropDownActions.length - 1 ? divider : false, | ||||
|         divider: | ||||
|           index < props.dropDownActions.length - 1 ? props.divider : false, | ||||
|       }; | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| /** 监听 actions 变化并更新缓存 */ | ||||
| watchEffect(() => { | ||||
|   const rawActions = toRaw(props.actions) || []; | ||||
|   const currentStringField = JSON.stringify( | ||||
|     rawActions.map((a) => ({ | ||||
|       ...a, | ||||
|       onClick: undefined, // 排除函数以便比较 | ||||
|       popConfirm: a.popConfirm | ||||
|         ? { ...a.popConfirm, confirm: undefined, cancel: undefined } | ||||
|         : undefined, | ||||
|     })), | ||||
|   ); | ||||
| 
 | ||||
|   if (currentStringField !== actionsStringField.value) { | ||||
|     actionsStringField.value = currentStringField; | ||||
|     processedActions.value = processActions(rawActions); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| /** 监听 dropDownActions 变化并更新缓存 */ | ||||
| watchEffect(() => { | ||||
|   const rawDropDownActions = toRaw(props.dropDownActions) || []; | ||||
|   const currentStringField = JSON.stringify({ | ||||
|     actions: rawDropDownActions.map((a) => ({ | ||||
|       ...a, | ||||
|       onClick: undefined, // 排除函数以便比较 | ||||
|       popConfirm: a.popConfirm | ||||
|         ? { ...a.popConfirm, confirm: undefined, cancel: undefined } | ||||
|         : undefined, | ||||
|     })), | ||||
|     divider: props.divider, | ||||
|   }); | ||||
| 
 | ||||
|   if (currentStringField !== dropdownActionsStringField.value) { | ||||
|     dropdownActionsStringField.value = currentStringField; | ||||
|     processedDropdownActions.value = processDropdownActions( | ||||
|       rawDropDownActions, | ||||
|       props.divider, | ||||
|     ); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| const getActions = computed(() => processedActions.value); | ||||
| 
 | ||||
| const getDropdownList = computed(() => processedDropdownActions.value); | ||||
| 
 | ||||
| /** 缓存 Space 组件的 size 计算结果 */ | ||||
| /** Space 组件的 size */ | ||||
| const spaceSize = computed(() => { | ||||
|   return unref(getActions)?.some((item: ActionItem) => item.type === 'link') | ||||
|     ? 0 | ||||
|     : 8; | ||||
| }); | ||||
| 
 | ||||
| /** 缓存 PopConfirm 属性 */ | ||||
| const popConfirmPropsMap = new Map<string, any>(); | ||||
| 
 | ||||
| /** 获取 PopConfirm 属性 */ | ||||
| function getPopConfirmProps(attrs: PopConfirm) { | ||||
|   const key = JSON.stringify({ | ||||
|     title: attrs.title, | ||||
|     okText: attrs.okText, | ||||
|     cancelText: attrs.cancelText, | ||||
|     disabled: attrs.disabled, | ||||
|   }); | ||||
| 
 | ||||
|   if (popConfirmPropsMap.has(key)) { | ||||
|     return popConfirmPropsMap.get(key); | ||||
|   } | ||||
| 
 | ||||
|   const originAttrs: any = { ...attrs }; | ||||
|   delete originAttrs.icon; | ||||
|   if (attrs.confirm && isFunction(attrs.confirm)) { | ||||
|  | @ -186,61 +114,30 @@ function getPopConfirmProps(attrs: PopConfirm) { | |||
|     originAttrs.onCancel = attrs.cancel; | ||||
|     delete originAttrs.cancel; | ||||
|   } | ||||
| 
 | ||||
|   popConfirmPropsMap.set(key, originAttrs); | ||||
|   return originAttrs; | ||||
| } | ||||
| 
 | ||||
| /** 缓存 Button 属性 */ | ||||
| const buttonPropsMap = new Map<string, any>(); | ||||
| 
 | ||||
| /** 获取 Button 属性 */ | ||||
| function getButtonProps(action: ActionItem) { | ||||
|   const key = JSON.stringify({ | ||||
|     type: action.type, | ||||
|     danger: action.danger || false, | ||||
|     disabled: action.disabled, | ||||
|     loading: action.loading, | ||||
|     size: action.size, | ||||
|   }); | ||||
| 
 | ||||
|   if (buttonPropsMap.has(key)) { | ||||
|     return { ...buttonPropsMap.get(key) }; | ||||
|   } | ||||
| 
 | ||||
|   const res = { | ||||
|   return { | ||||
|     type: action.type || 'link', | ||||
|     danger: action.danger || false, | ||||
|     disabled: action.disabled, | ||||
|     loading: action.loading, | ||||
|     size: action.size, | ||||
|   }; | ||||
| 
 | ||||
|   buttonPropsMap.set(key, res); | ||||
|   return res; | ||||
| } | ||||
| 
 | ||||
| /** 缓存 Tooltip 属性 */ | ||||
| const tooltipPropsMap = new Map<string, any>(); | ||||
| 
 | ||||
| /** 获取 Tooltip 属性 */ | ||||
| function getTooltipProps(tooltip: any | string) { | ||||
|   if (!tooltip) return {}; | ||||
| 
 | ||||
|   const key = typeof tooltip === 'string' ? tooltip : JSON.stringify(tooltip); | ||||
| 
 | ||||
|   if (tooltipPropsMap.has(key)) { | ||||
|     return tooltipPropsMap.get(key); | ||||
|   } | ||||
| 
 | ||||
|   const result = | ||||
|     typeof tooltip === 'string' ? { title: tooltip } : { ...tooltip }; | ||||
| 
 | ||||
|   tooltipPropsMap.set(key, result); | ||||
|   return result; | ||||
|   return typeof tooltip === 'string' ? { title: tooltip } : { ...tooltip }; | ||||
| } | ||||
| 
 | ||||
| /** 处理菜单点击 */ | ||||
| function handleMenuClick(e: any) { | ||||
|   const action = getDropdownList.value[e.key]; | ||||
|   if (action.onClick && isFunction(action.onClick)) { | ||||
|   if (action && action.onClick && isFunction(action.onClick)) { | ||||
|     action.onClick(); | ||||
|   } | ||||
| } | ||||
|  | @ -287,7 +184,7 @@ function getActionKey(action: ActionItem, index: number) { | |||
| 
 | ||||
|     <Dropdown v-if="getDropdownList.length > 0" :trigger="['hover']"> | ||||
|       <slot name="more"> | ||||
|         <Button :type="getDropdownList[0].type"> | ||||
|         <Button :type="getDropdownList[0]?.type"> | ||||
|           <template #icon> | ||||
|             {{ $t('page.action.more') }} | ||||
|             <IconifyIcon icon="lucide:ellipsis-vertical" /> | ||||
|  | @ -339,6 +236,7 @@ function getActionKey(action: ActionItem, index: number) { | |||
|     </Dropdown> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <style lang="scss"> | ||||
| .table-actions { | ||||
|   .ant-btn-link { | ||||
|  |  | |||
|  | @ -5,14 +5,12 @@ | |||
| import { isRef } from 'vue'; | ||||
| 
 | ||||
| // 编码表单 Conf
 | ||||
| export const encodeConf = (designerRef: object) => { | ||||
|   // @ts-ignore designerRef.value is dynamically added by form-create-designer
 | ||||
| export const encodeConf = (designerRef: any) => { | ||||
|   return JSON.stringify(designerRef.value.getOption()); | ||||
| }; | ||||
| 
 | ||||
| // 编码表单 Fields
 | ||||
| export const encodeFields = (designerRef: object) => { | ||||
|   // @ts-ignore designerRef.value is dynamically added by form-create-designer
 | ||||
| export const encodeFields = (designerRef: any) => { | ||||
|   const rule = JSON.parse(designerRef.value.getJson()); | ||||
|   const fields: string[] = []; | ||||
|   rule.forEach((item: unknown) => { | ||||
|  | @ -32,33 +30,29 @@ export const decodeFields = (fields: string[]) => { | |||
| 
 | ||||
| // 设置表单的 Conf 和 Fields,适用 FcDesigner 场景
 | ||||
| export const setConfAndFields = ( | ||||
|   designerRef: object, | ||||
|   designerRef: any, | ||||
|   conf: string, | ||||
|   fields: string | string[], | ||||
| ) => { | ||||
|   // @ts-ignore designerRef.value is dynamically added by form-create-designer
 | ||||
|   designerRef.value.setOption(JSON.parse(conf)); | ||||
|   // @ts-ignore designerRef.value is dynamically added by form-create-designer
 | ||||
|   designerRef.value.setRule(decodeFields(fields)); | ||||
|   // 处理 fields 参数类型,确保传入 decodeFields 的是 string[] 类型
 | ||||
|   const fieldsArray = Array.isArray(fields) ? fields : [fields]; | ||||
|   designerRef.value.setRule(decodeFields(fieldsArray)); | ||||
| }; | ||||
| 
 | ||||
| // 设置表单的 Conf 和 Fields,适用 form-create 场景
 | ||||
| export const setConfAndFields2 = ( | ||||
|   detailPreview: object, | ||||
|   detailPreview: any, | ||||
|   conf: string, | ||||
|   fields: string[], | ||||
|   value?: object, | ||||
|   value?: any, | ||||
| ) => { | ||||
|   if (isRef(detailPreview)) { | ||||
|     // @ts-ignore detailPreview.value is dynamically added by form-create-designer
 | ||||
|     detailPreview = detailPreview.value; | ||||
|   } | ||||
|   // @ts-ignore detailPreview properties are dynamically added by form-create-designer
 | ||||
|   detailPreview.option = JSON.parse(conf); | ||||
|   // @ts-ignore detailPreview properties are dynamically added by form-create-designer
 | ||||
|   detailPreview.rule = decodeFields(fields); | ||||
|   if (value) { | ||||
|     // @ts-ignore detailPreview properties are dynamically added by form-create-designer
 | ||||
|     detailPreview.value = value; | ||||
|   } | ||||
| }; | ||||
|  |  | |||
|  | @ -7,7 +7,6 @@ import { Page, useVbenModal } from '@vben/common-ui'; | |||
| import { IconifyIcon } from '@vben/icons'; | ||||
| import { cloneDeep } from '@vben/utils'; | ||||
| 
 | ||||
| import { refAutoReset } from '@vueuse/core'; | ||||
| import { useSortable } from '@vueuse/integrations/useSortable'; | ||||
| import { Button, Card, Dropdown, Input, Menu, message } from 'ant-design-vue'; | ||||
| 
 | ||||
|  | @ -28,7 +27,7 @@ const [CategoryFormModal, categoryFormModalApi] = useVbenModal({ | |||
|   destroyOnClose: true, | ||||
| }); | ||||
| // 模型列表加载状态 | ||||
| const modelListSpinning = refAutoReset(false, 3000); | ||||
| const modelListSpinning = ref(false); | ||||
| // 保存排序状态 | ||||
| const saveSortLoading = ref(false); | ||||
| // 按照 category 分组的数据 | ||||
|  | @ -148,7 +147,6 @@ async function handleCategorySortSubmit() { | |||
| 
 | ||||
| <template> | ||||
|   <Page auto-content-height> | ||||
|     <!-- TODO @jason:体感整个页面的加载,有点卡顿。先看到分类,大概 1-2 秒后,上箭头变成下,然后看到每个模型。我本地大概 4 个分类,每个分类下 20+ 模型 --> | ||||
|     <!-- 流程分类表单弹窗 --> | ||||
|     <CategoryFormModal @success="getList" /> | ||||
|     <Card | ||||
|  | @ -212,11 +210,12 @@ async function handleCategorySortSubmit() { | |||
|       <!-- 按照分类,展示其所属的模型列表 --> | ||||
|       <div class="px-3" ref="categoryGroupRef"> | ||||
|         <CategoryDraggableModel | ||||
|           v-for="element in categoryGroup" | ||||
|           v-for="(element, index) in categoryGroup" | ||||
|           :class="isCategorySorting ? 'cursor-move' : ''" | ||||
|           :key="element.id" | ||||
|           :category-info="element" | ||||
|           :is-category-sorting="isCategorySorting" | ||||
|           :is-first="index === 0" | ||||
|           @success="getList" | ||||
|         /> | ||||
|       </div> | ||||
|  |  | |||
|  | @ -45,6 +45,7 @@ import { useGridColumns } from './data'; | |||
| const props = defineProps<{ | ||||
|   categoryInfo: ModelCategoryInfo; | ||||
|   isCategorySorting: boolean; | ||||
|   isFirst?: boolean; // 是否为第一个分类 | ||||
| }>(); | ||||
| 
 | ||||
| const emit = defineEmits(['success']); | ||||
|  | @ -68,7 +69,8 @@ const userId = userStore.userInfo?.id; | |||
| const isModelSorting = ref(false); | ||||
| const originalData = ref<BpmModelApi.Model[]>([]); | ||||
| const modelList = ref<BpmModelApi.Model[]>([]); | ||||
| const isExpand = ref(false); | ||||
| // 根据是否为第一个分类, 来设置初始展开状态 | ||||
| const isExpand = ref(!!props.isFirst); | ||||
| 
 | ||||
| const [Grid, gridApi] = useVbenVxeGrid({ | ||||
|   gridOptions: { | ||||
|  | @ -376,9 +378,7 @@ const updateModelList = useDebounceFn(() => { | |||
|   const newModelList = props.categoryInfo.modelList; | ||||
|   if (!isEqual(modelList.value, newModelList)) { | ||||
|     modelList.value = cloneDeep(newModelList); | ||||
|     if (newModelList?.length > 0) { | ||||
|       isExpand.value = true; | ||||
|     } | ||||
|     // 不再自动设置展开状态,除非是第一个分类 | ||||
|     // 关闭排序 | ||||
|     isModelSorting.value = false; | ||||
|     // 重置排序实例 | ||||
|  |  | |||
|  | @ -255,15 +255,12 @@ async function getApprovalDetail(row: { | |||
|  */ | ||||
| function setFieldPermission(field: string, permission: string) { | ||||
|   if (permission === BpmFieldPermissionType.READ) { | ||||
|     // @ts-ignore | ||||
|     fApi.value?.disabled(true, field); | ||||
|   } | ||||
|   if (permission === BpmFieldPermissionType.WRITE) { | ||||
|     // @ts-ignore | ||||
|     fApi.value?.disabled(false, field); | ||||
|   } | ||||
|   if (permission === BpmFieldPermissionType.NONE) { | ||||
|     // @ts-ignore | ||||
|     fApi.value?.hidden(true, field); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -195,17 +195,14 @@ const activityNodes = ref<BpmProcessInstanceApi.ApprovalNodeInfo[]>([]); | |||
|  */ | ||||
| function setFieldPermission(field: string, permission: string) { | ||||
|   if (permission === FieldPermissionType.READ) { | ||||
|     // @ts-ignore | ||||
|     fApi.value?.disabled(true, field); | ||||
|   } | ||||
|   if (permission === FieldPermissionType.WRITE) { | ||||
|     // @ts-ignore | ||||
|     fApi.value?.disabled(false, field); | ||||
|     // 加入可以编辑的字段 | ||||
|     writableFields.push(field); | ||||
|   } | ||||
|   if (permission === FieldPermissionType.NONE) { | ||||
|     // @ts-ignore | ||||
|     fApi.value?.hidden(true, field); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -359,7 +359,6 @@ async function handleAudit(pass: boolean, formRef: FormInstance | undefined) { | |||
|       const formCreateApi = approveFormFApi.value; | ||||
|       if (Object.keys(formCreateApi)?.length > 0) { | ||||
|         await formCreateApi.validate(); | ||||
|         // @ts-ignore | ||||
|         data.variables = approveForm.value.value; | ||||
|       } | ||||
|       await TaskApi.approveTask(data); | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ import type { VbenFormSchema } from '#/adapter/form'; | |||
| import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| 
 | ||||
| import { useUserStore } from '@vben/stores'; | ||||
| import { erpPriceMultiply, floatToFixed2 } from '@vben/utils'; | ||||
| import { erpPriceInputFormatter, erpPriceMultiply } from '@vben/utils'; | ||||
| 
 | ||||
| import { z } from '#/adapter/form'; | ||||
| import { getSimpleBusinessList } from '#/api/crm/business'; | ||||
|  | @ -341,7 +341,9 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { | |||
|       field: 'unpaidPrice', | ||||
|       minWidth: 150, | ||||
|       formatter: ({ row }) => { | ||||
|         return floatToFixed2(row.totalPrice - row.totalReceivablePrice); | ||||
|         return erpPriceInputFormatter( | ||||
|           row.totalPrice - row.totalReceivablePrice, | ||||
|         ); | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|  |  | |||
|  | @ -3,11 +3,7 @@ import type { DescriptionItemSchema } from '#/components/description'; | |||
| 
 | ||||
| import { h } from 'vue'; | ||||
| 
 | ||||
| import { | ||||
|   erpPriceInputFormatter, | ||||
|   floatToFixed2, | ||||
|   formatDateTime, | ||||
| } from '@vben/utils'; | ||||
| import { erpPriceInputFormatter, formatDateTime } from '@vben/utils'; | ||||
| 
 | ||||
| import { DictTag } from '#/components/dict-tag'; | ||||
| import { DICT_TYPE } from '#/utils'; | ||||
|  | @ -148,7 +144,9 @@ export function useDetailListColumns(): VxeTableGridOptions['columns'] { | |||
|       field: 'unpaidPrice', | ||||
|       minWidth: 150, | ||||
|       formatter: ({ row }) => { | ||||
|         return floatToFixed2(row.totalPrice - row.totalReceivablePrice); | ||||
|         return erpPriceInputFormatter( | ||||
|           row.totalPrice - row.totalReceivablePrice, | ||||
|         ); | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|  |  | |||
|  | @ -64,14 +64,14 @@ const [TransferModal, transferModalApi] = useVbenModal({ | |||
|   destroyOnClose: true, | ||||
| }); | ||||
| 
 | ||||
| /** 加载线索详情 */ | ||||
| /** 加载合同详情 */ | ||||
| async function loadContractDetail() { | ||||
|   loading.value = true; | ||||
|   const data = await getContract(contractId.value); | ||||
|   contract.value = data; | ||||
|   // 操作日志 | ||||
|   const logList = await getOperateLogPage({ | ||||
|     bizType: BizTypeEnum.CRM_CLUE, | ||||
|     bizType: BizTypeEnum.CRM_CONTRACT, | ||||
|     bizId: contractId.value, | ||||
|   }); | ||||
|   contractLogList.value = logList.list; | ||||
|  |  | |||
|  | @ -104,6 +104,12 @@ export function useDetailListColumns( | |||
|       formatter: 'formatAmount2', | ||||
|       visible: showBussinePrice, | ||||
|     }, | ||||
|     { | ||||
|       field: 'contractPrice', | ||||
|       title: '合同价格(元)', | ||||
|       formatter: 'formatAmount2', | ||||
|       visible: !showBussinePrice, | ||||
|     }, | ||||
|     { | ||||
|       field: 'count', | ||||
|       title: '数量', | ||||
|  |  | |||
|  | @ -53,7 +53,7 @@ const [FormModal, formModalApi] = useVbenModal({ | |||
|   destroyOnClose: true, | ||||
| }); | ||||
| 
 | ||||
| /** 加载线索详情 */ | ||||
| /** 加载回款详情 */ | ||||
| async function loadReceivableDetail() { | ||||
|   loading.value = true; | ||||
|   const data = await getReceivable(receivableId.value); | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ import type { VbenFormSchema } from '#/adapter/form'; | |||
| import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| 
 | ||||
| import { useUserStore } from '@vben/stores'; | ||||
| import { floatToFixed2 } from '@vben/utils'; | ||||
| import { erpPriceInputFormatter } from '@vben/utils'; | ||||
| 
 | ||||
| import { getContractSimpleList } from '#/api/crm/contract'; | ||||
| import { getCustomerSimpleList } from '#/api/crm/customer'; | ||||
|  | @ -53,6 +53,13 @@ export function useFormSchema(): VbenFormSchema[] { | |||
|               value: item.id, | ||||
|             })), | ||||
|             placeholder: '请选择合同', | ||||
|             onChange: (value: number) => { | ||||
|               const contract = res.find((item) => item.id === value); | ||||
|               if (contract) { | ||||
|                 values.price = | ||||
|                   contract.totalPrice - contract.totalReceivablePrice; | ||||
|               } | ||||
|             }, | ||||
|           }; | ||||
|         }, | ||||
|       }, | ||||
|  | @ -106,6 +113,7 @@ export function useFormSchema(): VbenFormSchema[] { | |||
|         valueFormat: 'x', | ||||
|         format: 'YYYY-MM-DD', | ||||
|       }, | ||||
|       defaultValue: new Date(), | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'remindDays', | ||||
|  | @ -246,9 +254,9 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { | |||
|       minWidth: 160, | ||||
|       formatter: ({ row }) => { | ||||
|         if (row.receivable) { | ||||
|           return floatToFixed2(row.price - row.receivable.price); | ||||
|           return erpPriceInputFormatter(row.price - row.receivable.price); | ||||
|         } | ||||
|         return floatToFixed2(row.price); | ||||
|         return erpPriceInputFormatter(row.price); | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|  |  | |||
|  | @ -53,7 +53,7 @@ const [FormModal, formModalApi] = useVbenModal({ | |||
|   destroyOnClose: true, | ||||
| }); | ||||
| 
 | ||||
| /** 加载线索详情 */ | ||||
| /** 加载回款计划详情 */ | ||||
| async function loadreceivablePlanDetail() { | ||||
|   loading.value = true; | ||||
|   const data = await getReceivablePlan(receivablePlanId.value); | ||||
|  |  | |||
|  | @ -3,7 +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 { erpPriceInputFormatter } from '@vben/utils'; | ||||
| 
 | ||||
| import { message, Tag } from 'ant-design-vue'; | ||||
| 
 | ||||
|  | @ -110,7 +110,7 @@ const [Grid, gridApi] = useVbenVxeGrid({ | |||
|         <Tag v-else-if="row.type === 3">钱包余额</Tag> | ||||
|       </template> | ||||
|       <template #price="{ row }"> | ||||
|         <span>¥{{ floatToFixed2(row.price) }}</span> | ||||
|         <span>¥{{ erpPriceInputFormatter(row.price) }}</span> | ||||
|       </template> | ||||
|       <template #status="{ row }"> | ||||
|         <Tag v-if="row.status === 0 && !row.payTransferId" type="warning"> | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import type { DescriptionItemSchema } from '#/components/description'; | |||
| 
 | ||||
| import { h } from 'vue'; | ||||
| 
 | ||||
| import { floatToFixed2, formatDateTime } from '@vben/utils'; | ||||
| import { erpPriceInputFormatter, formatDateTime } from '@vben/utils'; | ||||
| 
 | ||||
| import { Tag } from 'ant-design-vue'; | ||||
| 
 | ||||
|  | @ -174,17 +174,17 @@ export function useDetailSchema(): DescriptionItemSchema[] { | |||
|     { | ||||
|       field: 'price', | ||||
|       label: '支付金额', | ||||
|       content: (data) => `¥${floatToFixed2(data?.price)}`, | ||||
|       content: (data) => `¥${erpPriceInputFormatter(data?.price)}`, | ||||
|     }, | ||||
|     { | ||||
|       field: 'channelFeePrice', | ||||
|       label: '手续费', | ||||
|       content: (data) => `¥${floatToFixed2(data?.channelFeePrice)}`, | ||||
|       content: (data) => `¥${erpPriceInputFormatter(data?.channelFeePrice)}`, | ||||
|     }, | ||||
|     { | ||||
|       field: 'channelFeeRate', | ||||
|       label: '手续费比例', | ||||
|       content: (data) => `${floatToFixed2(data?.channelFeeRate)}%`, | ||||
|       content: (data) => `${erpPriceInputFormatter(data?.channelFeeRate)}%`, | ||||
|     }, | ||||
|     { | ||||
|       field: 'successTime', | ||||
|  | @ -240,7 +240,7 @@ export function useDetailSchema(): DescriptionItemSchema[] { | |||
|     { | ||||
|       field: 'refundPrice', | ||||
|       label: '退款金额', | ||||
|       content: (data) => `¥${floatToFixed2(data?.refundPrice)}`, | ||||
|       content: (data) => `¥${erpPriceInputFormatter(data?.refundPrice)}`, | ||||
|     }, | ||||
|     { | ||||
|       field: 'notifyUrl', | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import type { DescriptionItemSchema } from '#/components/description'; | |||
| 
 | ||||
| import { h } from 'vue'; | ||||
| 
 | ||||
| import { floatToFixed2, formatDateTime } from '@vben/utils'; | ||||
| import { erpPriceInputFormatter, formatDateTime } from '@vben/utils'; | ||||
| 
 | ||||
| import { Tag } from 'ant-design-vue'; | ||||
| 
 | ||||
|  | @ -217,7 +217,7 @@ export function useDetailSchema(): DescriptionItemSchema[] { | |||
|       content: (data) => { | ||||
|         return h(Tag, { | ||||
|           color: 'blue', | ||||
|           content: `¥${floatToFixed2(data?.price)}`, | ||||
|           content: `¥${erpPriceInputFormatter(data?.price)}`, | ||||
|         }); | ||||
|       }, | ||||
|     }, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 xingyu
						xingyu