| | |
| | | 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) { |
| | |
| | | contractId?: number; // 关联 CRM 合同编号 |
| | | contractNo?: string; // CRM 合同单号 |
| | | needProduction?: number; // 是否需要生产:0-不需要,1-需要 |
| | | productionStatus?: number; // 生产状态:0-未完成,1-已完成 |
| | | productionFinishTime?: Date; // 生产完成时间 |
| | | hasSalesNotice?: boolean; // 是否已生成发货通知单 |
| | | canCreateSalesNotice?: boolean; // 是否可以生成发货通知单 |
| | | items?: SaleOrderItem[]; // 销售订单产品明细列表 |
| | |
| | | let unitListCache: any[] | null = null; |
| | | let unitListPromise: Promise<any[]> | null = null; |
| | | |
| | | /** 将扁平分类列表转为 TreeSelect 所需的树形结构 */ |
| | | function buildCategoryTree(list: any[]): any[] { |
| | | const map = new Map<number, any>(); |
| | | const roots: any[] = []; |
| | | for (const item of list) { |
| | | map.set(item.id, { label: item.name, value: item.id, children: [] }); |
| | | } |
| | | for (const item of list) { |
| | | const node = map.get(item.id)!; |
| | | if (item.parentId && map.has(item.parentId)) { |
| | | map.get(item.parentId)!.children.push(node); |
| | | } else { |
| | | roots.push(node); |
| | | } |
| | | } |
| | | return roots; |
| | | } |
| | | |
| | | /** 获取单位列表(带缓存) */ |
| | | async function getUnitListCached() { |
| | | if (unitListCache) { |
| | |
| | | unitListPromise = null; |
| | | }); |
| | | return unitListPromise; |
| | | } |
| | | |
| | | /** 清除单位列表缓存 */ |
| | | export function clearUnitListCache() { |
| | | unitListCache = null; |
| | | } |
| | | |
| | | /** 新增/修改的表单 */ |
| | |
| | | rules: "required", |
| | | }, |
| | | { |
| | | component: "ApiSelect", |
| | | component: "ApiTreeSelect", |
| | | fieldName: "categoryId", |
| | | label: "物料分类", |
| | | rules: "selectRequired", |
| | | componentProps: { |
| | | placeholder: "请选择物料分类", |
| | | allowClear: true, |
| | | treeDefaultExpandAll: true, |
| | | api: async () => { |
| | | const res = await getItemTypeSimpleList(); |
| | | return res || []; |
| | | return buildCategoryTree(res || []); |
| | | }, |
| | | labelField: "name", |
| | | valueField: "id", |
| | | }, |
| | | }, |
| | | { |
| | |
| | | import { createItem, getItem, updateItem } from '#/api/mdm/item'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | import { useFormSchema, clearUnitListCache } from '../data'; |
| | | |
| | | defineOptions({ name: "ItemForm" }); |
| | | |
| | |
| | | return; |
| | | } |
| | | const data = modalApi.getData<MdmItemApi.Item>(); |
| | | // 清除单位缓存,确保获取最新数据 |
| | | clearUnitListCache(); |
| | | // 设置 schema(formApi 已初始化) |
| | | formApi.setState({ schema: useFormSchema(formApi) }); |
| | | if (data?.id) { |
| | |
| | | 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="[ |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-1/2"> |
| | | <Modal :title="getTitle" class="w-3/4"> |
| | | <Form class="mx-4"> |
| | | <template #product="slotProps"> |
| | | <ProductEditTable |
| | |
| | | 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(); |
| | |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'itemBarCode', |
| | | title: '条码', |
| | | minWidth: 150, |
| | | }, |
| | | { |
| | | field: 'itemUnitName', |
| | | title: '单位', |
| | | minWidth: 80, |
| | |
| | | import type { CrmContractApi } from '#/api/crm/contract'; |
| | | import type { MdmItemApi } from '#/api/mdm/item'; |
| | | |
| | | import { nextTick, onMounted, ref, watch } from 'vue'; |
| | | import { nextTick, ref, watch } from 'vue'; |
| | | |
| | | import { erpPriceMultiply } from '@vben/utils'; |
| | | |
| | | import { InputNumber, Select } from 'ant-design-vue'; |
| | | import { InputNumber } from 'ant-design-vue'; |
| | | |
| | | import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { BizTypeEnum } from '#/api/crm/permission'; |
| | | import { getItemPage } from '#/api/mdm/item'; |
| | | import { MdmItemSelect } from '#/views/basicData/mdm/components'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useProductEditTableColumns } from './data'; |
| | |
| | | } |
| | | |
| | | /** 切换产品时同步基础信息 */ |
| | | function handleProductChange(itemId: any, row: any) { |
| | | const product = productOptions.value.find((p) => p.id === itemId); |
| | | function handleProductChange(product: MdmItemApi.Item | undefined, row: any) { |
| | | if (!product) { |
| | | return; |
| | | } |
| | | row.itemId = itemId; |
| | | row.itemId = product.id; |
| | | row.itemName = product.name; |
| | | row.itemCode = product.code; |
| | | row.itemBarCode = product.barCode; |
| | |
| | | immediate: true, |
| | | }, |
| | | ); |
| | | |
| | | /** 初始化 */ |
| | | const productOptions = ref<MdmItemApi.Item[]>([]); // 物料下拉选项 |
| | | onMounted(async () => { |
| | | const res = await getItemPage({ pageNo: 1, pageSize: 100, status: 0 }); |
| | | productOptions.value = res.list || []; |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Grid class="w-full"> |
| | | <template #itemId="{ row }"> |
| | | <Select |
| | | v-model:value="row.itemId" |
| | | :options="productOptions" |
| | | :field-names="{ label: 'name', value: 'id' }" |
| | | class="w-full" |
| | | <MdmItemSelect |
| | | :model-value="row.itemId" |
| | | placeholder="请选择产品" |
| | | @update:model-value="row.itemId = $event" |
| | | @change="handleProductChange($event, row)" |
| | | /> |
| | | </template> |
| | |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'productionStatus', |
| | | label: '生产状态', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: ['needProduction', 'productionStatus'], |
| | | show: (values) => { |
| | | return formType === 'detail' && values.needProduction === 1; |
| | | }, |
| | | }, |
| | | componentProps: { |
| | | disabled: true, |
| | | }, |
| | | slots: { default: 'productionStatus' }, |
| | | }, |
| | | { |
| | | fieldName: 'productionFinishTime', |
| | | label: '生产完成时间', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: ['productionStatus'], |
| | | show: (values) => { |
| | | return formType === 'detail' && values.productionStatus === 1; |
| | | }, |
| | | }, |
| | | componentProps: { |
| | | disabled: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'orderTime', |
| | | label: '订单时间', |
| | | component: 'DatePicker', |
| | |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'productionStatus', |
| | | label: '生产状态', |
| | | component: 'Select', |
| | | componentProps: { |
| | | options: [ |
| | | { label: '未完成', value: 0 }, |
| | | { label: '已完成', value: 1 }, |
| | | ], |
| | | placeholder: '请选择生产状态', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'returnStatus', |
| | | label: '退货状态', |
| | | component: 'Select', |
| | |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'outStatus', |
| | | title: '出库状态', |
| | | minWidth: 100, |
| | | slots: { default: 'outStatus' }, |
| | | }, |
| | | { |
| | | field: 'totalProductPrice', |
| | | title: '金额合计', |
| | | formatter: 'formatAmount2', |
| | |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'outStatus', |
| | | title: '出库状态', |
| | | minWidth: 100, |
| | | fixed: 'right', |
| | | slots: { default: 'outStatus' }, |
| | | }, |
| | | { |
| | | field: 'productionStatus', |
| | | title: '生产状态', |
| | | minWidth: 100, |
| | | fixed: 'right', |
| | | slots: { default: 'productionStatus' }, |
| | | }, |
| | | { |
| | | field: 'status', |
| | | title: '状态', |
| | | minWidth: 100, |
| | |
| | | <Tag v-else-if="row.outStatus === 2" color="warning">部分出库</Tag> |
| | | <Tag v-else-if="row.outStatus === 3" color="success">全部出库</Tag> |
| | | </template> |
| | | <template #productionStatus="{ row }"> |
| | | <template v-if="row.needProduction !== 1"> |
| | | <Tag color="default">无需生产</Tag> |
| | | </template> |
| | | <template v-else> |
| | | <Tag v-if="row.productionStatus === 0" color="warning">未完成</Tag> |
| | | <Tag v-else-if="row.productionStatus === 1" color="success">已完成</Tag> |
| | | </template> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | |
| | | type: 'link', |
| | | icon: 'ant-design:file-protect-outlined', |
| | | auth: ['mes:wm-sales-notice:create'], |
| | | ifShow: () => row.status === 20 && row.canCreateSalesNotice, |
| | | ifShow: () => row.status === 20 && (row.canCreateSalesNotice || row.productionStatus === 1), |
| | | onClick: handleGenerateNotice.bind(null, row), |
| | | }, |
| | | { |
| | |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | import { message, Tag } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { getAccountSimpleList } from '#/api/erp/finance/account'; |
| | |
| | | :show-confirm-button="formType !== 'detail'" |
| | | > |
| | | <Form class="mx-3"> |
| | | <template #productionStatus="{ modelValue }"> |
| | | <Tag v-if="modelValue === 0" color="warning">未完成</Tag> |
| | | <Tag v-else-if="modelValue === 1" color="success">已完成</Tag> |
| | | </template> |
| | | <template #items> |
| | | <ItemForm |
| | | ref="itemFormRef" |
| | |
| | | |
| | | const isReadOnly = computed(() => props.formType === 'detail'); // 是否只读 |
| | | const title = computed(() => props.kind); // 当前媒体类型标题 |
| | | |
| | | /** 将完整 URL 转为相对路径,确保走前端代理 */ |
| | | function resolveImageUrl(url?: string): string { |
| | | if (!url) return ''; |
| | | try { |
| | | const { pathname } = new URL(url, window.location.origin); |
| | | return pathname; |
| | | } catch { |
| | | return url; |
| | | } |
| | | } |
| | | const loading = ref(false); // SIP/SOP 列表加载中 |
| | | const formOpen = ref(false); // SIP/SOP 表单是否打开 |
| | | const formLoading = ref(false); // SIP/SOP 表单提交中 |
| | |
| | | 添加 {{ title }} |
| | | </Button> |
| | | <Spin :spinning="loading"> |
| | | <div v-if="list.length > 0" class="grid grid-cols-4 gap-3"> |
| | | <div v-if="list.length > 0" class="grid grid-cols-8 gap-1"> |
| | | <Card |
| | | v-for="item in list" |
| | | :key="item.id" |
| | | hoverable |
| | | class="!w-[120px]" |
| | | :body-style="{ padding: '0px' }" |
| | | > |
| | | <Image |
| | | v-if="item.url" |
| | | :src="item.url" |
| | | class="block h-40 w-full object-cover" |
| | | :preview="{ src: item.url }" |
| | | :src="resolveImageUrl(item.url)" |
| | | class="block !h-[90px] !w-[120px] object-cover" |
| | | :preview="{ src: resolveImageUrl(item.url) }" |
| | | /> |
| | | <div |
| | | v-else |
| | | class="flex h-40 w-full items-center justify-center bg-gray-100 text-gray-400" |
| | | class="flex !h-[90px] !w-[120px] items-center justify-center bg-gray-100 text-gray-400" |
| | | > |
| | | 暂无图片 |
| | | </div> |
| | | <div class="p-3"> |
| | | <div class="mb-1 truncate text-sm font-bold">{{ item.title }}</div> |
| | | <div class="p-2"> |
| | | <div class="mb-1 truncate text-xs font-bold">{{ item.title }}</div> |
| | | <div v-if="item.description" class="truncate text-xs text-gray-500"> |
| | | {{ item.description }} |
| | | </div> |
| | |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | label: '配置投料', |
| | | type: 'link', |
| | | ifShow: () => isEditable, |
| | | onClick: handleEdit.bind(null, row), |
| | |
| | | field: 'type', |
| | | title: '检测项类型', |
| | | width: 120, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_INDICATOR_TYPE }, |
| | | }, |
| | | slots: { default: 'type' }, |
| | | }, |
| | | { |
| | | field: 'level', |
| | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart } from '@vben/utils'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | import { message, Tag } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { deleteDefect, exportDefect, getDefectPage } from '#/api/mes/qc/defect'; |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const TAG_COLORS = ['blue', 'green', 'orange', 'purple', 'cyan', 'magenta', 'geekblue', 'volcano'] as const; |
| | | |
| | | const typeDictOptions = getDictOptions(DICT_TYPE.MES_INDICATOR_TYPE, 'number'); |
| | | const typeColorMap = new Map<number, string>(); |
| | | typeDictOptions.forEach((item, index) => { |
| | | typeColorMap.set(item.value as number, TAG_COLORS[index % TAG_COLORS.length]!); |
| | | }); |
| | | |
| | | function getTypeColor(type: number): string { |
| | | return typeColorMap.get(type) || 'default'; |
| | | } |
| | | |
| | | function getTypeLabel(type: number): string { |
| | | const item = typeDictOptions.find((d) => d.value === type); |
| | | return item?.label || String(type); |
| | | } |
| | | |
| | | /** 刷新表格 */ |
| | | function handleRefresh() { |
| | |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #type="{ row }"> |
| | | <Tag v-if="row.type !== undefined && row.type !== null" :color="getTypeColor(row.type)"> |
| | | {{ getTypeLabel(row.type) }} |
| | | </Tag> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | |
| | | field: 'type', |
| | | title: '检测项类型', |
| | | width: 120, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_INDICATOR_TYPE }, |
| | | }, |
| | | slots: { default: 'type' }, |
| | | }, |
| | | { |
| | | field: 'tool', |
| | |
| | | field: 'type', |
| | | title: '检测项类型', |
| | | width: 120, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_INDICATOR_TYPE }, |
| | | }, |
| | | slots: { default: 'type' }, |
| | | }, |
| | | { |
| | | field: 'tool', |
| | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart } from '@vben/utils'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | import { message, Tag } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | |
| | | exportIndicator, |
| | | getIndicatorPage, |
| | | } from '#/api/mes/qc/indicator'; |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const TAG_COLORS = ['blue', 'green', 'orange', 'purple', 'cyan', 'magenta', 'geekblue', 'volcano'] as const; |
| | | |
| | | const typeDictOptions = getDictOptions(DICT_TYPE.MES_INDICATOR_TYPE, 'number'); |
| | | const typeColorMap = new Map<number, string>(); |
| | | typeDictOptions.forEach((item, index) => { |
| | | typeColorMap.set(item.value as number, TAG_COLORS[index % TAG_COLORS.length]!); |
| | | }); |
| | | |
| | | function getTypeColor(type: number): string { |
| | | return typeColorMap.get(type) || 'default'; |
| | | } |
| | | |
| | | function getTypeLabel(type: number): string { |
| | | const item = typeDictOptions.find((d) => d.value === type); |
| | | return item?.label || String(type); |
| | | } |
| | | |
| | | /** 刷新表格 */ |
| | | function handleRefresh() { |
| | |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #type="{ row }"> |
| | | <Tag v-if="row.type !== undefined && row.type !== null" :color="getTypeColor(row.type)"> |
| | | {{ getTypeLabel(row.type) }} |
| | | </Tag> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |