银川
1.商机管理详情页面新增合同逻辑修改
2.商机状态枚举值修改
| | |
| | | statusTypeName?: string; |
| | | statusId: number; |
| | | statusName?: string; |
| | | endStatus: number; |
| | | endRemark: string; |
| | | dealTime: Date; |
| | | totalProductPrice: number; |
| | |
| | | /** 商机更新状态请求 */ |
| | | export interface BusinessUpdateStatusReqVO { |
| | | id: number; |
| | | statusId: number | undefined; |
| | | endStatus: number | undefined; |
| | | statusId: number; |
| | | } |
| | | } |
| | | |
| | |
| | | id?: number; |
| | | name: string; |
| | | percent?: number; |
| | | endStatus?: number; |
| | | key?: string; |
| | | sort?: number; |
| | | } |
| | | } |
| | | |
| | | /** 默认商机状态 */ |
| | | export const DEFAULT_STATUSES = [ |
| | | { |
| | | endStatus: 1, |
| | | key: '结束', |
| | | name: '赢单', |
| | | percent: 100, |
| | | }, |
| | | { |
| | | endStatus: 2, |
| | | key: '结束', |
| | | name: '输单', |
| | | percent: 0, |
| | | }, |
| | | { |
| | | endStatus: 3, |
| | | key: '结束', |
| | | name: '无效', |
| | | percent: 0, |
| | | }, |
| | | ] satisfies CrmBusinessStatusApi.BusinessStatusType[]; |
| | | |
| | | /** 查询商机状态组列表 */ |
| | | export function getBusinessStatusPage(params: PageParam) { |
| | |
| | | title: '商机阶段', |
| | | fixed: 'right', |
| | | width: 120, |
| | | slots: { default: 'statusName' }, |
| | | }, |
| | | { |
| | | title: '操作', |
| | |
| | | import { erpPriceInputFormatter, formatDateTime } from '@vben/utils'; |
| | | |
| | | import { |
| | | DEFAULT_STATUSES, |
| | | getBusinessStatusSimpleList, |
| | | } from '#/api/crm/business/status'; |
| | | |
| | |
| | | }, |
| | | { |
| | | fieldName: 'statusId', |
| | | label: '商机状态', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'endStatus', |
| | | label: '商机状态', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'status', |
| | | label: '商机阶段', |
| | | component: 'Select', |
| | | dependencies: { |
| | |
| | | const statusList = await getBusinessStatusSimpleList( |
| | | formData.value?.statusTypeId ?? 0, |
| | | ); |
| | | const statusOptions = statusList.map((item) => ({ |
| | | const options = statusList.map((item) => ({ |
| | | label: `${item.name}(赢单率:${item.percent}%)`, |
| | | value: item.id, |
| | | })); |
| | | const options = DEFAULT_STATUSES.map((item) => ({ |
| | | label: `${item.name}(赢单率:${item.percent}%)`, |
| | | value: item.endStatus, |
| | | })); |
| | | statusOptions.push(...options); |
| | | return { |
| | | options: statusOptions, |
| | | options, |
| | | }; |
| | | }, |
| | | }, |
| | |
| | | <ContractDetailsList |
| | | :biz-id="businessId" |
| | | :biz-type="BizTypeEnum.CRM_BUSINESS" |
| | | :customer-id="business.customerId" |
| | | :customer-name="business.customerName" |
| | | :business-name="business.name" |
| | | /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane tab="操作日志" key="6" :force-render="true"> |
| | |
| | | // 提交表单 |
| | | const data = (await formApi.getValues()) as CrmBusinessApi.Business; |
| | | try { |
| | | if (!data.status) { |
| | | if (!data.statusId) { |
| | | return; |
| | | } |
| | | await updateBusinessStatus({ |
| | | id: data.id, |
| | | statusId: data.status > 0 ? data.status : undefined, |
| | | endStatus: data.status < 0 ? -data.status : undefined, |
| | | statusId: data.statusId, |
| | | }); |
| | | // 关闭并提示 |
| | | await modalApi.close(); |
| | |
| | | if (!data || !data.id) { |
| | | return; |
| | | } |
| | | data.status = data.endStatus === null ? data.statusId : -data.endStatus; |
| | | formData.value = data; |
| | | modalApi.lock(); |
| | | try { |
| | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart } from '@vben/utils'; |
| | | |
| | | import { Button, message, Tabs } from 'ant-design-vue'; |
| | | import { Button, message, Tabs, Tag } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | |
| | | |
| | | const { push } = useRouter(); |
| | | const sceneType = ref('1'); |
| | | |
| | | const STATUS_COLORS: Record<string, string> = { |
| | | 拜访: 'default', |
| | | 初步接触: 'processing', |
| | | 需求确认: 'cyan', |
| | | 方案报价: 'orange', |
| | | 商务谈判: 'volcano', |
| | | 合同签订: 'success', |
| | | 赢单: 'success', |
| | | 输单: 'error', |
| | | 无效: 'default', |
| | | }; |
| | | |
| | | function getStatusColor(name: string): string { |
| | | return STATUS_COLORS[name] || 'blue'; |
| | | } |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | |
| | | {{ row.customerName }} |
| | | </Button> |
| | | </template> |
| | | <template #statusName="{ row }"> |
| | | <Tag v-if="row.statusName" :color="getStatusColor(row.statusName)">{{ row.statusName }}</Tag> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | |
| | | export function useFormColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { |
| | | field: 'endStatus', |
| | | title: '阶段', |
| | | minWidth: 100, |
| | | slots: { default: 'endStatus' }, |
| | | type: 'seq', |
| | | title: '序号', |
| | | width: 60, |
| | | }, |
| | | { |
| | | field: 'name', |
| | |
| | | import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | createBusinessStatus, |
| | | DEFAULT_STATUSES, |
| | | getBusinessStatus, |
| | | updateBusinessStatus, |
| | | } from '#/api/crm/business/status'; |
| | |
| | | try { |
| | | if (formData.value?.statuses && formData.value.statuses.length > 0) { |
| | | data.statuses = formData.value.statuses; |
| | | data.statuses.splice(-3, 3); |
| | | } |
| | | await (formData.value?.id |
| | | ? updateBusinessStatus(data) |
| | |
| | | // 设置到 values |
| | | await formApi.setValues(formData.value as any); |
| | | await gridApi.grid.reloadData( |
| | | (formData.value!.statuses = |
| | | formData.value?.statuses?.concat(DEFAULT_STATUSES)) as any, |
| | | (formData.value!.statuses) as any, |
| | | ); |
| | | } finally { |
| | | modalApi.unlock(); |
| | |
| | | |
| | | /** 添加状态 */ |
| | | async function handleAddStatus() { |
| | | formData.value!.statuses!.splice(-3, 0, { |
| | | formData.value!.statuses!.push({ |
| | | name: '', |
| | | percent: undefined, |
| | | }); |
| | |
| | | mode: 'cell', |
| | | }, |
| | | columns: useFormColumns(), |
| | | data: formData.value?.statuses?.concat(DEFAULT_STATUSES), |
| | | data: formData.value?.statuses, |
| | | border: true, |
| | | showOverflow: true, |
| | | autoResize: true, |
| | |
| | | <Form class="mx-4"> |
| | | <template #statuses> |
| | | <Grid class="w-full"> |
| | | <template #endStatus="{ row, rowIndex }"> |
| | | <span> |
| | | {{ row.endStatus ? '结束' : `阶段${rowIndex + 1}` }} |
| | | </span> |
| | | </template> |
| | | <template #name="{ row }"> |
| | | <Input |
| | | v-if="!row.endStatus" |
| | | v-model:value="row.name" |
| | | placeholder="请输入状态名" |
| | | /> |
| | | <span v-else>{{ row.name }}</span> |
| | | </template> |
| | | <template #percent="{ row }"> |
| | | <InputNumber |
| | | v-if="!row.endStatus" |
| | | v-model:value="row.percent" |
| | | :min="0" |
| | | :max="100" |
| | | :precision="2" |
| | | placeholder="请输入赢单率" |
| | | /> |
| | | <span v-else>{{ row.percent }}</span> |
| | | </template> |
| | | <template #actions="{ row, rowIndex }"> |
| | | <TableAction |
| | |
| | | { |
| | | label: $t('ui.actionTitle.create'), |
| | | type: 'link', |
| | | ifShow: () => !row.endStatus, |
| | | onClick: handleAddStatus, |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | ifShow: () => !row.endStatus, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.name]), |
| | | confirm: deleteStatusArea.bind(null, row, rowIndex), |
| | |
| | | const props = defineProps<{ |
| | | bizId: number; // 业务编号 |
| | | bizType: number; // 业务类型 |
| | | customerId?: number; // 客户编号(商机/联系人详情传入) |
| | | customerName?: string; // 客户名称(商机/联系人详情传入) |
| | | businessName?: string; // 商机名称(商机详情传入) |
| | | }>(); |
| | | |
| | | const { push } = useRouter(); |
| | |
| | | ? { |
| | | customerId: props.bizId, |
| | | } |
| | | : { businessId: props.bizId }, |
| | | : { |
| | | businessId: props.bizId, |
| | | customerId: props.customerId, |
| | | customerName: props.customerName, |
| | | businessName: props.businessName, |
| | | }, |
| | | ) |
| | | .open(); |
| | | } |
| | |
| | | customerId: props.bizId, |
| | | ...formValues, |
| | | }); |
| | | } else if (props.bizType === BizTypeEnum.CRM_CONTACT) { |
| | | } else if (props.bizType === BizTypeEnum.CRM_BUSINESS) { |
| | | return await getContractPageByBusiness({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | |
| | | componentProps: { |
| | | placeholder: '请选择客户', |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['customerId'], |
| | | disabled: (values) => !!values.customerId, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'orderDate', |
| | |
| | | placeholder: '请选择商机', |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['customerId'], |
| | | disabled: (values) => !values.customerId, |
| | | triggerFields: ['customerId', 'businessId'], |
| | | disabled: (values) => !!values.businessId || !values.customerId, |
| | | async componentProps(values) { |
| | | if (values.businessId && values.businessName) { |
| | | return { |
| | | options: [{ label: values.businessName, value: values.businessId }], |
| | | placeholder: '请选择商机', |
| | | }; |
| | | } |
| | | if (!values.customerId) { |
| | | return { |
| | | options: [], |
| | |
| | | // 加载数据 |
| | | const data = modalApi.getData<CrmContractApi.Contract>(); |
| | | if (!data || !data.id) { |
| | | // 新增时,回显从商机/客户详情传入的数据 |
| | | if (data) { |
| | | await formApi.setValues(data); |
| | | } |
| | | return; |
| | | } |
| | | modalApi.lock(); |