feat: 完成采购,视频分析模块

This commit is contained in:
fyy
2025-07-27 17:42:43 +08:00
parent 08b738f0f4
commit 3d7ddf3ed8
45 changed files with 5349 additions and 166 deletions

View File

@@ -0,0 +1,132 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { useVbenForm } from '#/adapter/form';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import {
procurementApplicationUpdate,
procurementApplicationInfo,
} from '#/api/property/assetManage/procurementApplication';
import type { ProcurementApplicationForm } from '#/api/property/assetManage/procurementApplication/model';
const emit = defineEmits<{ reload: [] }>();
const title = computed(() => '采购审批');
// 存储完整的申请数据
const fullApplicationData = ref<ProcurementApplicationForm>({});
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
labelWidth: 80,
componentProps: {
class: 'w-full',
},
},
schema: [
{
label: '审核状态',
fieldName: 'state',
component: 'RadioGroup',
componentProps: {
options: [
{ label: '通过', value: '1' },
{ label: '驳回', value: '2' },
],
},
rules: 'required',
},
{
label: '审核意见',
fieldName: 'auditOpinion',
component: 'Textarea',
componentProps: {
rows: 4,
placeholder: '请输入审核意见',
},
rules: 'required',
},
],
showDefaultActions: false,
wrapperClass: 'grid-cols-1',
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicModal, modalApi] = useVbenModal({
class: 'w-[500px]',
fullscreenButton: false,
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
onOpenChange: async (isOpen) => {
if (!isOpen) {
return null;
}
modalApi.modalLoading(true);
const { id } = modalApi.getData() as { id?: number | string };
if (id) {
// 获取完整的申请数据
const record = await procurementApplicationInfo(id);
fullApplicationData.value = cloneDeep(record);
// 设置表单默认值
await formApi.setValues({
id,
state: '',
auditOpinion: '',
});
}
await markInitialized();
modalApi.modalLoading(false);
},
});
async function handleConfirm() {
try {
modalApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
}
// 获取审核表单数据
const auditData = await formApi.getValues();
// 合并完整数据,只更新审核相关字段
const updateData = {
...fullApplicationData.value,
state: auditData.state,
auditOpinion: auditData.auditOpinion,
};
await procurementApplicationUpdate(updateData);
resetInitialized();
emit('reload');
modalApi.close();
} catch (error) {
console.error(error);
} finally {
modalApi.lock(false);
}
}
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
fullApplicationData.value = {};
}
</script>
<template>
<BasicModal :title="title">
<BasicForm />
</BasicModal>
</template>

View File

@@ -3,48 +3,46 @@ import type { VxeGridProps } from '#/adapter/vxe-table';
import { getDictOptions } from '#/utils/dict';
import { renderDict } from '#/utils/render';
import { suppliersList } from '#/api/property/assetManage/suppliers';
import { depotList } from '#/api/property/assetManage/depot';
import { useUserStore } from '@vben/stores';
const userStore = useUserStore();
export const querySchema: FormSchemaGetter = () => [
{
component: 'Input',
fieldName: 'title',
label: '标题',
},
// {
// component: 'Input',
// fieldName: 'title',
// label: '标题',
// },
{
component: 'Input',
fieldName: 'applicat',
label: '申请人id',
},
{
component: 'Input',
fieldName: 'phone',
label: '申请人手机号',
},
{
component: 'Input',
fieldName: 'supplier',
label: '供应商id',
},
{
component: 'Input',
fieldName: 'capitalId',
label: '资产id',
label: '申请人',
},
// {
// component: 'Input',
// fieldName: 'phone',
// label: '申请人手机号',
// },
// {
// component: 'ApiSelect',
// fieldName: 'supplier',
// label: '供应商',
// componentProps: {
// api: suppliersList({ pageNum: 1, pageSize: 1000 }),
// resultField: 'rows',
// labelField: 'assetTypeName',
// valueField: 'id',
// },
// },
{
component: 'Select',
componentProps: {},
fieldName: 'buyType',
label: '采购方式',
},
{
component: 'Input',
fieldName: 'buyUnitPrice',
label: '采购单价',
},
{
component: 'Input',
fieldName: 'buyAmount',
label: '采购金额',
options: getDictOptions('wy_zccgfs'),
},
{
component: 'Select',
@@ -55,21 +53,6 @@ export const querySchema: FormSchemaGetter = () => [
fieldName: 'state',
label: '状态',
},
{
component: 'DatePicker',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
fieldName: 'applicationTime',
label: '申请时间',
},
{
component: 'Input',
fieldName: 'searchValue',
label: '搜索值',
},
];
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
@@ -77,12 +60,18 @@ export const querySchema: FormSchemaGetter = () => [
export const columns: VxeGridProps['columns'] = [
{ type: 'checkbox', width: 60 },
{
title: '',
title: '序号',
field: 'id',
slots: {
default: ({ rowIndex }) => {
return (rowIndex + 1).toString();
},
},
},
{
title: '标题',
field: 'title',
width: 'auto',
},
{
title: '申请人id',
@@ -96,21 +85,15 @@ export const columns: VxeGridProps['columns'] = [
title: '供应商id',
field: 'supplier',
},
{
title: '资产id',
field: 'capitalId',
},
{
title: '采购方式',
field: 'buyType',
},
{
title: '采购单价',
field: 'buyUnitPrice',
},
{
title: '采购金额',
field: 'buyAmount',
slots: {
default: ({ row }) => {
// 可选从DictEnum中获取 DictEnum.WY_ZCSQSHZT 便于维护
return renderDict(row.buyType, 'wy_zccgfs');
},
},
},
{
title: '状态',
@@ -126,20 +109,12 @@ export const columns: VxeGridProps['columns'] = [
title: '备注',
field: 'remark',
},
{
title: '申请时间',
field: 'applicationTime',
},
{
title: '搜索值',
field: 'searchValue',
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: '操作',
width: 180,
width: 280,
},
];
@@ -160,73 +135,131 @@ export const modalSchema: FormSchemaGetter = () => [
rules: 'required',
},
{
label: '申请人id',
fieldName: 'applicat',
component: 'Input',
label: '存放仓库',
fieldName: 'depotId',
component: 'ApiSelect',
rules: 'required',
},
{
label: '申请人手机号',
fieldName: 'phone',
component: 'Input',
rules: 'required',
},
{
label: '供应商id',
fieldName: 'supplier',
component: 'Input',
rules: 'required',
},
{
label: '资产id',
fieldName: 'capitalId',
component: 'Input',
rules: 'required',
},
{
label: '采购方式',
fieldName: 'buyType',
component: 'Select',
componentProps: {},
rules: 'selectRequired',
},
{
label: '采购单价',
fieldName: 'buyUnitPrice',
component: 'Input',
},
{
label: '采购金额',
fieldName: 'buyAmount',
component: 'Input',
},
{
label: '状态',
fieldName: 'state',
component: 'Select',
componentProps: {
// 可选从DictEnum中获取 DictEnum.WY_ZCSQSHZT 便于维护
options: getDictOptions('wy_zcsqshzt'),
api: async () => {
const res = await depotList({ pageNum: 1, pageSize: 1000 });
return res;
},
resultField: 'rows',
labelField: 'depotName',
valueField: 'id',
},
},
{
component: 'Input',
fieldName: 'applicatDisplay', // 显示用
label: '申请人',
rules: 'required',
disabled: true,
},
{
component: 'Input',
rules: 'required',
fieldName: 'applicat', // 实际存储ID
dependencies: {
show: () => false,
triggerFields: [''],
},
},
{
component: 'Input',
fieldName: 'phone',
rules: 'required',
label: '申请人手机号',
},
{
component: 'ApiSelect',
rules: 'required',
fieldName: 'supplier',
label: '供应商',
componentProps: {
api: async () => {
const res = await suppliersList({ pageNum: 1, pageSize: 1000 });
return res;
},
resultField: 'rows',
labelField: 'suppliersName',
valueField: 'id',
},
},
{
rules: 'required',
component: 'Select',
componentProps: {
options: getDictOptions('wy_zccgfs'),
},
fieldName: 'buyType',
label: '采购方式',
},
// {
// label: '采购单价',
// fieldName: 'buyUnitPrice',
// component: 'Input',
// },
// {
// label: '采购金额',
// fieldName: 'buyAmount',
// component: 'Input',
// },
// {
// label: '状态',
// fieldName: 'state',
// component: 'Select',
// componentProps: {
// // 可选从DictEnum中获取 DictEnum.WY_ZCSQSHZT 便于维护
// options: getDictOptions('wy_zcsqshzt'),
// },
// },
{
label: '备注',
fieldName: 'remark',
component: 'Input',
},
];
export const detailColumns: VxeGridProps['columns'] = [
{
label: '申请时间',
fieldName: 'applicationTime',
component: 'DatePicker',
componentProps: {
showTime: true,
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
title: '序号',
field: 'id',
width: 80,
slots: {
default: ({ rowIndex }) => {
return (rowIndex + 1).toString();
},
},
},
{
label: '搜索值',
fieldName: 'searchValue',
component: 'Input',
title: '资产名称',
field: 'capitalName',
width: 150,
},
{
title: '规格',
field: 'spec',
width: 120,
},
{
title: '购买数量',
field: 'buyQuantity',
width: 120,
},
{
title: '采购单价',
field: 'buyUnitPrice',
width: 120,
},
{
title: '金额小计',
field: 'buyAmount',
width: 120,
},
{
title: '备注',
field: 'remark',
width: 150,
},
];

View File

@@ -24,6 +24,7 @@ import type { ProcurementApplicationForm } from '#/api/property/assetManage/proc
import { commonDownloadExcel } from '#/utils/file/download';
import procurementApplicationModal from './procurementApplication-modal.vue';
import auditModal from './audit-modal.vue';
import { columns, querySchema } from './data';
const formOptions: VbenFormProps = {
@@ -88,12 +89,29 @@ const [ProcurementApplicationModal, modalApi] = useVbenModal({
connectedComponent: procurementApplicationModal,
});
const [AuditModal, auditModalApi] = useVbenModal({
connectedComponent: auditModal,
});
function handleAdd() {
modalApi.setData({});
modalApi.open();
}
async function handleEdit(row: Required<ProcurementApplicationForm>) {
async function handleAudit(row: Required<ProcurementApplicationForm>) {
// 检查审核状态,如果已审核则不允许再次审核
if (row.state === '1' || row.state === '2') {
Modal.warning({
title: '提示',
content: '该申请已审核,不能重复审核',
});
return;
}
auditModalApi.setData({ id: row.id });
auditModalApi.open();
}
async function handleDetail(row: Required<ProcurementApplicationForm>) {
modalApi.setData({ id: row.id });
modalApi.open();
}
@@ -140,6 +158,13 @@ function handleDownloadExcel() {
>
{{ $t('pages.common.export') }}
</a-button>
<a-button
type="primary"
v-access:code="['domain:procurementApplication:add']"
@click="handleAdd"
>
新增
</a-button>
<a-button
:disabled="!vxeCheckboxChecked(tableApi)"
danger
@@ -149,22 +174,23 @@ function handleDownloadExcel() {
>
{{ $t('pages.common.delete') }}
</a-button>
<a-button
type="primary"
v-access:code="['domain:procurementApplication:add']"
@click="handleAdd"
>
{{ $t('pages.common.add') }}
</a-button>
</Space>
</template>
<template #action="{ row }">
<Space>
<ghost-button
v-access:code="['domain:procurementApplication:edit']"
@click.stop="handleEdit(row)"
v-access:code="['domain:procurementApplication:audit']"
:disabled="row.state === '1' || row.state === '2'"
type="primary"
@click.stop="handleAudit(row)"
>
{{ $t('pages.common.edit') }}
审核
</ghost-button>
<ghost-button
v-access:code="['domain:procurementApplication:detail']"
@click.stop="handleDetail(row)"
>
详情
</ghost-button>
<Popconfirm
:get-popup-container="getVxePopupContainer"
@@ -183,6 +209,8 @@ function handleDownloadExcel() {
</Space>
</template>
</BasicTable>
<!-- Removed ProcurementApplicationModal @reload="tableApi.query()" -->
<AuditModal @reload="tableApi.query()" />
<ProcurementApplicationModal @reload="tableApi.query()" />
</Page>
</template>

View File

@@ -0,0 +1,200 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter/form';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { assetTypeList } from '#/api/property/assetManage/assetType';
import { useUserStore } from '@vben/stores';
const userStore = useUserStore();
const emit = defineEmits<{ reload: [data: any]; editReload: [data: any] }>();
const isUpdate = ref(false);
const isAdd = ref(false);
const isView = ref(false);
const title = computed(() => {
if (isAdd.value) {
return $t('pages.common.add');
} else if (isView.value) {
return '查看';
} else {
return $t('pages.common.edit');
}
});
// 缓存清洁服务数据
const detailIndex = ref<number>(); //传index对应详情的某条数据,对该条数据进行编辑修改
const detailSchema = [
{
label: '资产名称',
fieldName: 'capitalName',
component: 'Input',
rules: 'required',
},
{
label: '资产类型',
fieldName: 'capitalType',
component: 'ApiSelect',
componentProps: {
api: async () => {
const res = await assetTypeList({ pageNum: 1, pageSize: 1000 });
return res;
},
resultField: 'rows',
labelField: 'assetTypeName',
valueField: 'id',
},
rules: 'required',
},
{
label: '规格',
fieldName: 'spec',
component: 'Input',
rules: 'required',
},
{
label: '采购单价',
fieldName: 'buyUnitPrice',
component: 'Input',
rules: 'required',
componentProps: {
onChange: async () => {
const formValues = await formApi.getValues();
const price = Number(formValues.buyUnitPrice) || 0;
const count = Number(formValues.buyQuantity) || 0;
const amount = price * count;
await formApi.setValues({
buyAmount: amount ? amount.toFixed(2) : '',
});
},
},
},
{
label: '采购数量',
fieldName: 'buyQuantity',
component: 'Input',
rules: 'required',
componentProps: {
onChange: async () => {
const formValues = await formApi.getValues();
const price = Number(formValues.buyUnitPrice) || 0;
const count = Number(formValues.buyQuantity) || 0;
const amount = price * count;
await formApi.setValues({
buyAmount: amount ? amount.toFixed(2) : '',
});
},
},
},
{
label: '金额小计',
fieldName: 'buyAmount',
component: 'Input',
componentProps: {
disabled: true,
},
},
{
label: '备注',
fieldName: 'remark',
component: 'Textarea',
},
];
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
labelWidth: 120,
componentProps: {
class: 'w-full',
},
},
schema: detailSchema,
showDefaultActions: false,
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicModal, modalApi] = useVbenModal({
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
onOpenChange: async (isOpen) => {
if (!isOpen) {
return null;
}
console.log(userStore.userInfo);
modalApi.modalLoading(true);
const data = modalApi.getData();
detailIndex.value = modalApi.getData().index;
if (!data || Object.keys(data).length === 0) {
//modalApi.getData()为空时表示添加
isAdd.value = true;
} else if (data.readonly) {
isView.value = true;
} else {
//表示编辑
isUpdate.value = true;
}
// TODO: 获取详情数据
await formApi.setValues(modalApi.getData());
await markInitialized();
modalApi.modalLoading(false);
},
});
async function handleConfirm() {
try {
modalApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
}
let data = cloneDeep(await formApi.getValues());
if (isUpdate.value) {
data.index = detailIndex.value;
emit('editReload', data);
} else if (isAdd.value) {
emit('reload', data);
}
handleClosed();
await markInitialized();
modalApi.close();
} catch (error) {
console.error(error);
} finally {
modalApi.lock(false);
}
}
async function handleClosed() {
isAdd.value = false;
isView.value = false;
isUpdate.value = false;
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicModal :title="title">
<BasicForm> </BasicForm>
</BasicModal>
</template>
<style scoped>
/* 使用 :deep() 穿透 scoped 样式,影响子组件 */
:deep(.ant-input[disabled]),
:deep(.ant-input-number-disabled .ant-input-number-input),
:deep(.ant-select-disabled .ant-select-selection-item) {
/* 设置一个更深的颜色,可以自己调整 */
color: rgba(0, 0, 0, 0.65) !important;
/* 有些浏览器需要这个来覆盖默认颜色 */
-webkit-text-fill-color: rgba(0, 0, 0, 0.65) !important;
}
</style>

View File

@@ -1,36 +1,44 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Button } from 'ant-design-vue';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { useVbenForm } from '#/adapter/form';
import { procurementApplicationInfo } from '#/api/property/assetManage/procurementApplication';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import { modalSchema } from './data';
import procurementDetailModal from './procurement-detail-modal.vue';
import { detailColumns } from './data';
import {
procurementApplicationAdd,
procurementApplicationInfo,
procurementApplicationUpdate,
} from '#/api/property/assetManage/procurementApplication';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { modalSchema } from './data';
import { useUserStore } from '@vben/stores';
const userStore = useUserStore();
const emit = defineEmits<{ reload: [] }>();
const detailData = ref<any[]>([]); //采购资产详情
const isUpdate = ref(false);
const isDetail = ref(false);
const title = computed(() => {
if (isDetail.value) {
return '采购申请详情';
}
return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
});
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
// 默认占满两列
formItemClass: 'col-span-2',
formItemClass: 'col-span-1',
// 默认label宽度 px
labelWidth: 80,
// 通用配置项 会影响到所有表单项
componentProps: {
class: 'w-full',
disabled: computed(() => isDetail.value),
},
},
schema: modalSchema(),
@@ -47,7 +55,7 @@ const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
const [BasicModal, modalApi] = useVbenModal({
// 在这里更改宽度
class: 'w-[550px]',
class: 'w-[70%]',
fullscreenButton: false,
onBeforeClose,
onClosed: handleClosed,
@@ -57,21 +65,77 @@ const [BasicModal, modalApi] = useVbenModal({
return null;
}
modalApi.modalLoading(true);
const { id } = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id;
// 只有在有id时才设置为详情模式用于详情查看
isDetail.value = !!id;
if (isUpdate.value && id) {
const record = await procurementApplicationInfo(id);
await formApi.setValues(record);
// 在详情模式下,设置资产详情数据到表格
if (isDetail.value && record.capitalInfoVoList) {
detailData.value = record.capitalInfoVoList;
// 更新表格数据
if (tableApi.grid && tableApi.grid.loadData) {
tableApi.grid.loadData(detailData.value);
}
}
} else {
// 新增模式
isDetail.value = false; // 确保新增时不是详情模式
await formApi.setValues({
applicat: userStore.userInfo?.userId, // 存放ID
applicatDisplay: userStore.userInfo?.realName, // 显示姓名
});
}
await markInitialized();
modalApi.modalLoading(false);
},
});
const [ProcurementDetailModal, detailModalApi] = useVbenModal({
connectedComponent: procurementDetailModal,
});
const gridOptions: VxeGridProps = {
checkboxConfig: {
// 高亮
highlight: true,
// 翻页时保留选中状态
reserve: true,
// 点击行选中
// trigger: 'row',
},
// 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新
columns: detailColumns,
data: detailData.value,
// height: 'auto',
keepSource: true,
pagerConfig: {},
rowConfig: {
keyField: 'id',
},
// 表格全局唯一表示 保存列配置需要用到
id: 'domain-procurementApplication-index',
toolbarConfig: {
// 隐藏"刷新/重置"按钮(对应 redo
refresh: false,
zoom: false, // 显示全屏
custom: false, // 隐藏列设置
},
};
const [BasicTable, tableApi] = useVbenVxeGrid({
gridOptions,
});
async function handleConfirm() {
// 如果是详情模式,直接关闭弹窗
if (isDetail.value) {
modalApi.close();
return;
}
try {
modalApi.lock(true);
const { valid } = await formApi.validate();
@@ -82,7 +146,11 @@ async function handleConfirm() {
const data = cloneDeep(await formApi.getValues());
await (isUpdate.value
? procurementApplicationUpdate(data)
: procurementApplicationAdd(data));
: procurementApplicationAdd({
...data,
capitalInfoBolist: detailData.value,
}));
resetInitialized();
emit('reload');
modalApi.close();
@@ -97,10 +165,28 @@ async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
function handleDetailReload(data: any) {
detailData.value.push(data);
}
function handleEditDetailReload() {}
//添加物资
function handleAddDetail() {
detailModalApi.setData({});
detailModalApi.open();
}
</script>
<template>
<BasicModal :title="title">
<BasicForm />
<div class="flex items-center justify-between" v-if="!isDetail">
<div>采购物资</div>
<Button type="primary" @click="handleAddDetail">选择物资</Button>
</div>
<BasicTable />
<ProcurementDetailModal
@reload="handleDetailReload"
@editReload="handleEditDetailReload"
/>
</BasicModal>
</template>