This commit is contained in:
z9130 2024-08-06 19:33:04 +08:00
parent 7de10e44c1
commit 9b798ae44d
9 changed files with 806 additions and 103 deletions

View File

@ -95,6 +95,8 @@ export const useUserStore = defineStore(
await redirectFromLogin();
}
console.log(userInfo);
userInfo.userName = userInfo.gwmc;
if (routeStore.isInitAuthRoute) {
window.$notification?.success({
title: $t('page.login.common.loginSuccess'),

View File

@ -22,17 +22,9 @@ declare module 'vue' {
FullScreen: typeof import('./../components/common/full-screen.vue')['default']
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default']
IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
'IconIc:roundPlus': typeof import('~icons/ic/round-plus')['default']
IconIcRoundDelete: typeof import('~icons/ic/round-delete')['default']
IconIcRoundPlus: typeof import('~icons/ic/round-plus')['default']
IconIcRoundRefresh: typeof import('~icons/ic/round-refresh')['default']
IconIcRoundRemove: typeof import('~icons/ic/round-remove')['default']
IconIcRoundSearch: typeof import('~icons/ic/round-search')['default']
IconLocalBanner: typeof import('~icons/local/banner')['default']
IconMdiAccountCheck: typeof import('~icons/mdi/account-check')['default']
IconMdiArchiveCancelOutline: typeof import('~icons/mdi/archive-cancel-outline')['default']
IconMdiArchiveOutline: typeof import('~icons/mdi/archive-outline')['default']
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
@ -45,11 +37,9 @@ declare module 'vue' {
IconMdiChevronUp: typeof import('~icons/mdi/chevron-up')['default']
IconMdiClose: typeof import('~icons/mdi/close')['default']
IconMdiCloudUpload: typeof import('~icons/mdi/cloud-upload')['default']
IconMdiContentSave: typeof import('~icons/mdi/content-save')['default']
IconMdiContentSaveOutline: typeof import('~icons/mdi/content-save-outline')['default']
IconMdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default']
IconMdiDownload: typeof import('~icons/mdi/download')['default']
IconMdiDrag: typeof import('~icons/mdi/drag')['default']
IconMdiDragVariant: typeof import('~icons/mdi/drag-variant')['default']
IconMdiExport: typeof import('~icons/mdi/export')['default']
IconMdiEye: typeof import('~icons/mdi/eye')['default']
@ -58,10 +48,10 @@ declare module 'vue' {
IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
IconMdiNotificationsNone: typeof import('~icons/mdi/notifications-none')['default']
IconMdiPlus: typeof import('~icons/mdi/plus')['default']
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
IconMdiRun: typeof import('~icons/mdi/run')['default']
IconMdiSend: typeof import('~icons/mdi/send')['default']
IconMdiSquareEditOutline: typeof import('~icons/mdi/square-edit-outline')['default']
IconMdiSync: typeof import('~icons/mdi/sync')['default']
IconMdiUndo: typeof import('~icons/mdi/undo')['default']
IconMdiUndoVariant: typeof import('~icons/mdi/undo-variant')['default']
IconUilSearch: typeof import('~icons/uil/search')['default']
@ -80,7 +70,6 @@ declare module 'vue' {
NCheckbox: typeof import('naive-ui')['NCheckbox']
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
NColorPicker: typeof import('naive-ui')['NColorPicker']
NDataTable: typeof import('naive-ui')['NDataTable']
NDatePicker: typeof import('naive-ui')['NDatePicker']
NDescriptions: typeof import('naive-ui')['NDescriptions']
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
@ -89,16 +78,13 @@ declare module 'vue' {
NDrawer: typeof import('naive-ui')['NDrawer']
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
NDropdown: typeof import('naive-ui')['NDropdown']
NDynamicInput: typeof import('naive-ui')['NDynamicInput']
NEllipsis: typeof import('naive-ui')['NEllipsis']
NEmpty: typeof import('naive-ui')['NEmpty']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid']
NGridItem: typeof import('naive-ui')['NGridItem']
NH3: typeof import('naive-ui')['NH3']
NIcon: typeof import('naive-ui')['NIcon']
NIconWrapper: typeof import('naive-ui')['NIconWrapper']
NInput: typeof import('naive-ui')['NInput']
@ -112,7 +98,6 @@ declare module 'vue' {
NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NP: typeof import('naive-ui')['NP']
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
NPopover: typeof import('naive-ui')['NPopover']
NProgress: typeof import('naive-ui')['NProgress']
NRadio: typeof import('naive-ui')['NRadio']

View File

@ -0,0 +1,470 @@
<script setup lang="ts">
import { reactive, ref } from "vue";
import { useModal } from "@/components/Modal";
import type { FormSchema } from "@/components/Form";
import { BasicForm, useForm } from "@/components/Form";
import ChooseMenuModal from "@/views/canteen/menu/modules/ChooseMenuModal.vue";
import MenuCard from "@/views/canteen/menu/modules/MenuCard.vue";
import { DICT_TYPE } from "@/utils/dict";
import { addRecipe, getRecipeList, updateRecipe } from "@/api/office/canteen";
import { getColumns, PrimaryKey } from "./schema";
import dayjs from "dayjs";
import isoWeek from 'dayjs/plugin/isoWeek'
import { usePagination } from 'alova/client';
import { useVxeTable } from "@/hooks/common/vxeTable";
import { SolarDay } from "tyme4ts";
import { paginationProps } from "@/utils/alova";
dayjs.extend(isoWeek);
const { xGridRef, gridProps, triggerProxy } = useVxeTable({ ref: "xGridRef" });
const chooseMenuModalRef = ref();
const emit = defineEmits<{
(e: "register"): void;
(e: "success"): void;
}>();
const [modalRegister, { openModal, closeModal, setModalProps }] = useModal({
style: {
width: "950px",
},
});
const formSchema = ref<FormSchema[]>([
{
field: PrimaryKey,
component: "NInput",
label: "主键",
show: false,
},
{
field: "recipeDate",
component: "NInput",
label: "日期",
show: false,
},
{
field: "recipeId",
component: "NInput",
label: "菜品ID",
show: false,
},
// {
// field: 'stapleFood',
// component: 'DictTag',
// componentProps: {
// type: DICT_TYPE.canteen_staplefood,
// selectable: true
// },
// label: '',
// rules: [{ required: true, message: '' }]
// },
{
field: "eatinRecipe",
component: "NInputTextArea",
label: "堂食菜品",
// defaultValue: '',
componentProps: {
placeholder: "请输入菜品,可用逗号分隔",
},
// rules: [{ required: true, message: '' }],
},
{
field: "eatinOpen",
label: "堂食开放状态",
component: "NRadioGroup",
defaultValue: true,
componentProps: {
options: [
{
label: "开启",
value: true,
},
{
label: "关闭",
value: false,
},
],
},
},
{
field: "deliveryRecipe",
label: "外送菜品",
component: "NInputTextArea",
// defaultValue: '',
componentProps: {
placeholder: "请输入菜品,可用逗号分隔",
},
// rules: [{ required: true, message: '' }],
},
{
field: "deliveryOpen",
label: "外送开放状态",
component: "NRadioGroup",
defaultValue: true,
componentProps: {
options: [
{
label: "开启",
value: true,
},
{
label: "关闭",
value: false,
},
],
},
},
{
field: "remarks",
component: "NInputTextArea",
label: "备注",
defaultValue: "",
componentProps: {
placeholder: "",
},
},
]);
const [register, { setFieldsValue, validate, resetFields, getFieldsValue }] = useForm({
gridProps: { xGap: "24", cols: "1" },
// isFull:true,
inline: true,
labelWidth: 120,
collapsedRows: 200,
// collapsedRows:999,
showAdvancedButton: true,
showActionButtonGroup: false,
schemas: formSchema,
});
function getDatesBetween(startDate, endDate) {
const dates = [];
let currentDate = dayjs(startDate);
while (currentDate.isBefore(endDate) || currentDate.isSame(endDate, 'day')) {
dates.push(currentDate.format('YYYY-MM-DD'));
currentDate = currentDate.add(1, 'day');
}
return dates;
}
const searchParams = reactive({
pageNum: 1,
pageSize: 100
});
/** Hooks - 数据请求 */
const { send, onSuccess } = usePagination(
({ pageNum, pageSize }: any) => getRecipeList({ ...searchParams }),
{ ...paginationProps(), immediate: false }
);
onSuccess((res: any) => {
//
var start = dayjs(weekValue.value[0]).date();
var end = dayjs(weekValue.value[1]).date();
let arr = [];
for (const item of getDatesBetween(weekValue.value[0], weekValue.value[1])) {
let obj = {
recipeDate: item,
week: "",
remarks: "",
};
const solar = SolarDay.fromYmd(
dayjs(item).year(),
dayjs(item).month() + 1,
dayjs(item).date()
);
const holiday = solar.getLegalHoliday();
var datas = dayjs(obj.recipeDate).day();
var week = ["日", "一", "二", "三", "四", "五", "六"];
obj.week = "星期" + week[datas];
obj.eatinOpen = true;
obj.deliveryOpen = true;
if (holiday) {
obj.week = obj.week + "" + holiday.name + "";
}
if (datas == 0 || datas == 6 || holiday) {
obj.eatinOpen = false;
obj.deliveryOpen = false;
}
arr.push(obj);
}
for (let m of arr) {
for (const item of res.data.rows) {
item.recipeDate = dayjs(item.recipeDate).format("YYYY-MM-DD");
if (item.recipeDate === m.recipeDate) {
console.log("日期:", item.recipeDate, item);
if (["0", "1"].includes(item.deliveryOpen)) {
item.deliveryOpen = Boolean(Number(item.deliveryOpen));
}
if (["0", "1"].includes(item.eatinOpen)) {
item.eatinOpen = Boolean(Number(item.eatinOpen));
}
Object.assign(m, item);
// m = item
}
}
}
res.data.rows = arr;
});
/** Hooks - 表格 */
const gridOptions = reactive(
gridProps({
columns: getColumns(),
pagerConfig: {
enabled: false,
},
proxyConfig: {
autoLoad: true,
ajax: {
query: ({ page }) => send({ pageNum: page.currentPage, pageSize: page.pageSize }),
},
},
toolbarConfig: {
enabled: false
},
rowStyle({ row, rowIndex }) {
if ((row && ["星期六", "星期日"].includes(row.week)) || row.week.length > 3) {
return {
backgroundColor: "#fbfbfb",
};
}
},
})
);
interface WeekData {
weekNumber: number;
startDate: string;
endDate: string;
}
function getLastWeeksData(weeks: number = 6): WeekData[] {
const result: WeekData[] = [];
for (let i = weeks; i > 0; i--) {
const weekStart = dayjs().subtract(i, 'week').startOf('isoWeek');
const weekEnd = dayjs().subtract(i, 'week').endOf('isoWeek');
const weekNumber = weekStart.isoWeek();
result.push({
weekNumber,
startDate: weekStart.format('YYYY-MM-DD'),
endDate: weekEnd.format('YYYY-MM-DD')
});
}
return result;
}
function getNextWeeksData(weeks: number = 2): WeekData[] {
const result: WeekData[] = [];
for (let i = 0; i <= weeks; i++) {
const weekStart = dayjs().add(i, 'week').startOf('isoWeek');
const weekEnd = dayjs().add(i, 'week').endOf('isoWeek');
const weekNumber = weekStart.isoWeek();
result.push({
weekNumber,
startDate: weekStart.format('YYYY-MM-DD'),
endDate: weekEnd.format('YYYY-MM-DD'),
value: `${weekStart.format('YYYY-MM-DD')},${weekEnd.format('YYYY-MM-DD')}`,
label: `${weekNumber}周 ( ${weekStart.format('YYYY-MM-DD')}${weekEnd.format('YYYY-MM-DD')} )`
});
}
return result;
}
async function handleSubmit() {
try {
if (!weekNumber) {
window.$message?.error("请选择请选择需要同步的某周");
return
}
if (!currWeek.value) {
window.$message?.error("请选择需要同步至某周");
return
}
let fullData = xGridRef.value?.getTableData().fullData || []
if (fullData.length != 7) {
window.$message?.error("请选择完整的一周");
return
}
setModalProps({ confirmLoading: true });
console.log('[ currWeek.value ] >', currWeek.value)
console.log('[ fullData ] >', fullData)
let week = currWeek.value.split(",")
let arr = getDatesBetween(week[0], week[1])
console.log('[ arr ] >', arr)
let newArr = []
let i = 0;
for (const old of fullData) {
old.recipeDate = arr[i];
old.eatinOpen = old.eatinOpen == true ? 1 : 0;
old.deliveryOpen = old.deliveryOpen == true ? 1 : 0;
i++
newArr.push(old)
}
await Apis.erp.post_recipe_savebatch({ data: newArr });
emit("success");
closeModal();
} finally {
setModalProps({ confirmLoading: false });
}
}
function handleUserRowClick(row) {
console.log(row);
setFieldsValue({ jlry: row.name });
}
function handleMenuItemClick(e) {
console.log(e);
}
const currQtsb = ref({});
const currData = ref<Recordable>({});
const isUpdate = ref(false);
const selectMenus = ref([]);
function handleMenuConfirm(e) {
console.log(e);
selectMenus.value = e;
setFieldsValue({
recipe: e.map((item) => item.dish).join(","),
recipeId: e.map((item) => item.dishId).join(","),
});
}
const weeks = ref<WeekData[]>([])
const weeks2 = ref<WeekData[]>([])
const currWeek = ref()
const weekNumber = ref();
const weekValue = ref<string[]>([]);
function setWeek(week: WeekData) {
weekNumber.value = week.weekNumber
weekValue.value = [week.startDate, week.endDate]
triggerProxy('reload')
}
defineExpose({
/** 打开弹窗 */
open: (data: AnyObject) => {
isUpdate.value = Boolean(data?.isUpdate);
openModal();
weeks.value = getLastWeeksData()
weeks2.value = getNextWeeksData()
// console.log('[ getLastWeeksData ] >', getLastWeeksData())
setTimeout(() => {
setFieldsValue(currData.value);
}, 45);
},
});
</script>
<template>
<div>
<BasicModal :title="'同步指定周的菜谱'" @register="modalRegister" @on-ok="handleSubmit">
<template #default>
<div class="flex flex-row h-600px">
<div>
<span class="mt-2 block">请选择需要同步的某周菜谱右侧可以预览仅支持近6周</span>
<NGrid class="" cols="1" responsive="screen" :x-gap="12" :y-gap="9">
<NGi>
<NCard :segmented="{ content: true }" content-style="padding: 0;" :bordered="false" size="small"
title="">
<div class="flex flex-wrap project-card">
<template v-for="(item, index) in weeks" :key="index">
<n-card size="small" :title="'第' + item.weekNumber + '周'"
class="cursor-pointer project-card-item "
:class="weekNumber == item.weekNumber ? 'bg-blue-200!' : ''" hoverable @click="setWeek(item)">
<div class="flex flex-col justify-center text-gray-500">
<span class="text-lx text-center">{{ item.startDate + ' 至 ' + item.endDate }}</span>
</div>
</n-card>
</template>
</div>
</NCard>
</NGi>
</NGrid>
<span class="mt-6 block">需要同步至</span>
<NSelect v-model:value="currWeek" placeholder="请选择需要同步至的周数" :options="weeks2"></NSelect>
<span class="mt-6 block">选中周所对应菜谱预览</span>
<div class="w-850px h-300px">
<VxeGrid ref="xGridRef" v-bind="gridOptions">
<template #openTsSlot="{ row }">
<div>
<n-checkbox v-model:checked="row.eatinOpen" disabled>
</n-checkbox>
</div>
</template>
<template #openDcSlot="{ row }">
<div>
<n-checkbox v-model:checked="row.deliveryOpen" disabled>
</n-checkbox>
</div>
</template>
</VxeGrid>
</div>
</div>
</div>
</template>
</BasicModal>
<ChooseMenuModal ref="chooseMenuModalRef" @confirm="handleMenuConfirm" @itemClick="handleMenuItemClick">
</ChooseMenuModal>
</div>
</template>
<style scoped>
:deep(.readonly .n-input__textarea-el) {
cursor: pointer !important;
}
.project-card {
margin-right: -6px;
}
.project-card-item {
margin: -1px;
width: 16.6666%;
}
</style>

View File

@ -12,7 +12,7 @@ import dayjs from "dayjs";
import { getMonthStartAndEnd, shortcuts } from "@/utils/time";
import { useVxeTable } from "@/hooks/common/vxeTable";
import { SolarDay } from "tyme4ts";
import RecipeSyncModal from "./RecipeSyncModal.vue";
const { xGridRef, gridProps, triggerProxy } = useVxeTable({ ref: "xGridRef" });
const searchParams = reactive({
@ -145,6 +145,7 @@ const monthArr = computed(() => {
const editModalRef = ref();
const uploadModalRef = ref();
const syncModalRef = ref();
function openEditModal(record?: Recordable) {
console.log(record);
@ -191,6 +192,14 @@ function handleOpenExport() {
exportSearchParams.daterange = getMonthStartAndEnd(searchParams.recipeDate);
}
/**
* 打开同步至某周弹窗
*/
function handleOpenSyncModal() {
syncModalRef.value?.open();
}
const checkedValue = ref<string | null>("daterange");
const exportSearchParams = reactive<any>({
daterange: getMonthStartAndEnd(),
@ -267,7 +276,7 @@ function handleOpenUploadModal() {
:class="{ 'thing-cell-on': searchParams.recipeDate == item }" @click="handleMonthChange(item)">
<template #header>{{
item == dayjs().format("YYYY-MM") ? item + "(当月)" : item
}}</template>
}}</template>
</n-thing>
</n-card>
</n-grid-item>
@ -295,6 +304,14 @@ function handleOpenUploadModal() {
</template>
导出
</NButton>
<NButton type="info" @click="handleOpenSyncModal()">
<template #icon>
<icon-mdi-sync />
</template>
同步指定周的菜谱
</NButton>
<!-- <BasicForm @register="register" @submit="handleSubmit" @reset="handleReset" /> -->
</NSpace>
</template>
@ -349,6 +366,8 @@ function handleOpenUploadModal() {
<RecipeEditModal ref="editModalRef" @success="triggerProxy('reload')">
</RecipeEditModal>
<RecipeSyncModal ref="syncModalRef" @success="triggerProxy('reload')"></RecipeSyncModal>
<RecipeUploadModal ref="uploadModalRef"></RecipeUploadModal>
</div>
</template>

View File

@ -134,6 +134,7 @@ onContractTypeSuccess((res: any) => {
/** Hooks - 表格 */
const gridOptions = reactive(gridProps({
height: "300px",
columns: [
{ type: 'seq', title: '序号', width: 60 },
{ field: 'referenceName', title: '签约依据名称', minWidth: 150 },
@ -350,8 +351,10 @@ function handleBack() {
合同类别
<span v-if="isRequired('ctrType')" class="text-red">*</span>
</template>
<div class="h-80px overflow-y-auto">
<DictTag v-model:value="currData.ctrType" :options="filterContractTypes('-1')" selectable> </DictTag>
<div class="">
<NSelect v-model:value="currData.ctrType" class="w-400px" :options="filterContractTypes('-1')"
placeholder="请选择合同类别" @update:value="() => currData.ctrTwoType = null">
</NSelect>
</div>
</n-descriptions-item>
@ -360,10 +363,10 @@ function handleBack() {
二级类别
<span v-if="isRequired('ctrTwoType')" class="text-red">*</span>
</template>
<div class="h-80px overflow-y-auto">
<DictTag v-model:value="currData.ctrTwoType" :options="filterContractTypes(currData.ctrType)" selectable
<div class="">
<NSelect v-model:value="currData.ctrTwoType" :options="filterContractTypes(contractData.ctrType)"
placeholder="请选择二级类别">
</DictTag>
</NSelect>
</div>
</n-descriptions-item>

View File

@ -19,8 +19,11 @@ import { useTabStore } from "@/store/modules/tab";
import { FileUploader } from "@/utils/file";
import { FileSource } from "@/enums";
import { forEach } from "lodash-es";
const { xGridRef, gridProps, triggerProxy } = useVxeTable({ ref: 'xGridRef' });
const { xGridRef: xGridRef2 } = useVxeTable({ ref: 'xGridRef2' });
const PrimaryKey = "guid"
const message = useMessage();
@ -61,8 +64,28 @@ const fileUploader = new FileUploader({
},
})
/** 必填字段校验 */
const requiredFieldRules: any = {
"projectNum": { required: true, message: '请选择项目' },
"projectProp": { required: true, message: '请选择项目类别' },
"projectNameId": { required: true, message: '请选择项目名称' },
"priceStyleId": { required: true, message: '请选择商务计价方式' },
"fundDitch": { required: true, message: '资金渠道不得为空' },
"organiza": { required: true, message: '组织形式不得为空' },
}
/** 必填字段校验,如果是必填字段则返回对应message */
function isRequired(field: string): string {
let rule = requiredFieldRules[field];
if (rule && rule.required) {
return rule.message || `${field}不可为空`;
}
return ""
}
/** 必填字段校验 */
const requiredFieldRulesByContractBase: any = {
"contractName": { required: true, message: '合同名称不得为空' },
"ctrType": { required: true, message: '合同类别不得为空' },
"ctrTwoType": { required: true, message: '合同二级类别不得为空' },
@ -72,7 +95,7 @@ const requiredFieldRules: any = {
}
/** 必填字段校验,如果是必填字段则返回对应message */
function isRequired(field: string): string {
function isRequiredByContractBase(field: string): string {
let rule = requiredFieldRules[field];
if (rule && rule.required) {
return rule.message || `${field}不可为空`;
@ -128,6 +151,7 @@ onContractTypeSuccess((res: any) => {
/** Hooks - 表格 */
const gridOptions = reactive(gridProps({
height: "300px",
columns: [
{ type: 'seq', title: '序号', width: 60 },
{ field: 'referenceName', title: '签约依据名称', minWidth: 150 },
@ -148,6 +172,26 @@ const gridOptions = reactive(gridProps({
}
}));
/** Hooks - 表格 */
const gridOptions2 = reactive(gridProps({
columns: [
{ type: 'seq', title: '序号', width: 60 },
{ field: 'phaseName', title: '标段名称', width: 150, editRender: { name: 'input' } },
{ field: 'phaseMoney', title: '预算金额(人民币元)', width: 200, editRender: { name: 'input', attrs: { type: 'number' } } },
{ field: 'phaseDesc', title: '范围说明', minWidth: 120, editRender: { name: 'input' } },
],
data: [],
toolbarConfig: {
enabled: false
},
pagerConfig: {
enabled: false
},
editConfig: {
trigger: 'click',
mode: 'row'
},
}));
/**
* 文件下载
@ -174,6 +218,24 @@ async function handleOpenChooseBasisContractModal(status: boolean) {
}
/** 标段选择回调 */
function handleBidChange(e) {
console.log('[ handleBidChange ] >', e)
let arr = new Array<number>(e).fill(0);
let data: any[] = [];
console.log('[ arr ] >', arr)
arr.forEach(element => {
data.push({
phaseName: '',
phaseMoney: '',
phaseDesc: ''
})
});
console.log('[ data ] >', data)
xGridRef2.value?.loadData(data)
}
/**
* 筛选合同列表数据
*
@ -186,18 +248,116 @@ function filterContractTypes(parentId: string) {
}).filter((item) => item.parentId === parentId);
}
/**
* 保存
*/
async function handleSave(type: "submit" | "upload") {
return new Promise((resolve, reject) => {
console.log('[ currData.value ] >', currData.value)
let info = JSON.parse(JSON.stringify(currData.value))
//
info.projectName = getDictObj(DICT_TYPE.comprehensiveProjectName, info.projectNameId)
//
info.priceStyleName = getDictObj(DICT_TYPE.contractPriceStyle, info.priceStyleId)
//
info.choiceTypeName = getDictObj(DICT_TYPE.contractSelectionMethod, info.choiceType)
let form = {
contractBaseInfo: contractData.value,
selectMerchantsBasicInfo: info,
biddingList: xGridRef2.value?.getTableData().fullData
}
if (form.selectMerchantsBasicInfo.isBid) {
let biddingList: any[] = xGridRef2.value?.getTableData().fullData || []
let index = 1
for (const item of biddingList) {
if (!item.phaseName) {
message.error(`标段信息中的标段名称不能为空`)
return
}
if (!item.phaseMoney) {
message.error(`标段信息中的预算金额不能为空`)
return
}
if (!item.phaseDesc) {
message.error(`标段信息中的标段范围说明不能为空`)
return
}
item.phaseSeq = index + ''
index++
}
form.biddingList = biddingList
}
resolve(true)
})
console.log('[ form ] >', form)
return
}
/**
* 提交
*/
async function handleSubmit(type: "submit" | "upload") {
//
dialog.warning({
title: "提示",
content: `提交前需要先保存记录,保存后将自动进行提交,是否确认保存?`,
positiveText: "确认",
negativeText: "取消",
onPositiveClick: async () => {
await handleSave("upload")
},
});
return
console.log('[ currData.value ] >', currData.value)
let form = {
contractBaseInfo: contractData.value,
selectMerchantsBasicInfo: currData.value,
biddingList: biddingList.value
biddingList: xGridRef2.value?.getTableData().fullData
}
if (form.selectMerchantsBasicInfo.isBid) {
let biddingList: any[] = xGridRef2.value?.getTableData().fullData || []
let index = 1
for (const item of biddingList) {
if (!item.phaseName) {
message.error(`标段信息中的标段名称不能为空`)
return
}
if (!item.phaseMoney) {
message.error(`标段信息中的预算金额不能为空`)
return
}
if (!item.phaseDesc) {
message.error(`标段信息中的标段范围说明不能为空`)
return
}
item.phaseSeq = index + ''
index++
}
form.biddingList = biddingList
}
console.log('[ form ] >', form)
return
if (type === "submit") {
//
@ -286,11 +446,11 @@ function handleBack() {
<div>
<n-affix class="z-20 w-full bg-white py-1" :trigger-top="10" position="absolute" :listen-to="() => containerRef">
<n-space>
<!-- <n-button type="primary" size="small" @click="handleSubmit('upload')">
<n-button type="primary" size="small" @click="handleSave('upload')">
<template #icon>
<icon-mdi-content-save-outline />
</template>
保存</n-button> -->
保存</n-button>
<n-button type="primary" size="small" @click="handleSubmit('upload')">
<template #icon>
@ -330,7 +490,7 @@ function handleBack() {
<n-descriptions-item :span="6">
<template #label>
合同名称
<span v-if="isRequired('contractName')" class="text-red">*</span>
<span v-if="isRequiredByContractBase('contractName')" class="text-red">*</span>
</template>
<n-input v-model:value="contractData.contractName" placeholder="请输入"></n-input>
</n-descriptions-item>
@ -338,37 +498,31 @@ function handleBack() {
<n-descriptions-item :span="3">
<template #label>
合同类别
<span v-if="isRequired('ctrType')" class="text-red">*</span>
<span v-if="isRequiredByContractBase('ctrType')" class="text-red">*</span>
</template>
<div class="">
<NSelect v-model:value="contractData.ctrType" :options="filterContractTypes('-1')" placeholder="请选择合同类别"
@update:value="() => contractData.ctrTwoType = null">
<NSelect v-model:value="contractData.ctrType" class="w-400px" :options="filterContractTypes('-1')"
placeholder="请选择合同类别" @update:value="() => contractData.ctrTwoType = null">
</NSelect>
<!-- <DictTag v-model:value="contractData.ctrType" :options="filterContractTypes('-1')" selectable>
</DictTag> -->
</div>
</n-descriptions-item>
<n-descriptions-item :span="3">
<template #label>
二级类别
<span v-if="isRequired('ctrTwoType')" class="text-red">*</span>
<span v-if="isRequiredByContractBase('ctrTwoType')" class="text-red">*</span>
</template>
<div class="">
<NSelect v-model:value="contractData.ctrTwoType" :options="filterContractTypes(contractData.ctrType)"
placeholder="请选择二级类别">
</NSelect>
<!-- <DictTag v-model:value="contractData.ctrTwoType" :options="filterContractTypes(contractData.ctrType)"
selectable placeholder="请选择二级类别">
</DictTag> -->
</div>
</n-descriptions-item>
<n-descriptions-item :span="6">
<template #label>
资金流向
<span v-if="isRequired('fundAllocation')" class="text-red">*</span>
<span v-if="isRequiredByContractBase('fundAllocation')" class="text-red">*</span>
</template>
<DictTag v-model:value="contractData.fundAllocation" :options="getDictOptions(DICT_TYPE.contractFundFlow)"
selectable> </DictTag>
@ -377,7 +531,7 @@ function handleBack() {
<n-descriptions-item :span="6">
<template #label>
资金渠道
<span v-if="isRequired('fundDitch')" class="text-red">*</span>
<span v-if="isRequiredByContractBase('fundDitch')" class="text-red">*</span>
</template>
<DictTag v-model:value="contractData.fundDitch" :options="getDictOptions(DICT_TYPE.contractFundingSource)"
selectable> </DictTag>
@ -386,7 +540,7 @@ function handleBack() {
<n-descriptions-item :span="6">
<template #label>
组织形式
<span v-if="isRequired('organiza')" class="text-red">*</span>
<span v-if="isRequiredByContractBase('organiza')" class="text-red">*</span>
</template>
<div>
<DictTag v-model:value="contractData.organiza"
@ -397,7 +551,7 @@ function handleBack() {
<n-descriptions-item :span="2">
<template #label>
预算金额
<span v-if="isRequired('budgetSum')" class="text-red">*</span>
<span v-if="isRequiredByContractBase('budgetSum')" class="text-red">*</span>
</template>
<div class="flex flex-row">
<n-input-number class="flex-1" v-model:value="contractData.budgetSum"
@ -409,7 +563,7 @@ function handleBack() {
<n-descriptions-item :span="2">
<template #label> 框架协议 </template>
<span v-if="isRequired('frameProtocol')" class="text-red">*</span>
<span v-if="isRequiredByContractBase('frameProtocol')" class="text-red">*</span>
<n-radio-group v-model:value="contractData.frameProtocol" name="radiogroup1">
<n-space>
<n-radio :value="1"> </n-radio>
@ -420,7 +574,7 @@ function handleBack() {
<n-descriptions-item :span="2" label-width="150px">
<template #label> 框架协议下的合同 </template>
<span v-if="isRequired('frameProtocolCtr')" class="text-red">*</span>
<span v-if="isRequiredByContractBase('frameProtocolCtr')" class="text-red">*</span>
<n-radio-group v-model:value="contractData.frameProtocolCtr" name="radiogroup2">
<n-space>
<n-radio :value="1"> </n-radio>
@ -485,10 +639,10 @@ function handleBack() {
<n-descriptions-item :span="3">
<template #label> 选商方式
<span v-if="isRequired('priceStyleId')" class="text-red">*</span>
<span v-if="isRequired('choiceType')" class="text-red">*</span>
</template>
<div class="">
<DictTag v-model:value="currData.priceStyleId"
<DictTag v-model:value="currData.choiceType"
:options="getDictOptions(DICT_TYPE.contractSelectionMethod)" selectable> </DictTag>
</div>
</n-descriptions-item>
@ -523,7 +677,9 @@ function handleBack() {
<div class="mx-12"></div>
<div v-if="currData.isBid" class="flex flex-row mr-6 flex-1 items-center">
<div class="w-80px">标段数</div>
<NSelect class="mr-2" :options="bidList"></NSelect>
<NSelect v-model:value="currData.bidNum" class="mr-2" :options="bidList"
@update:value="handleBidChange">
</NSelect>
</div>
</div>
</n-descriptions-item>
@ -533,9 +689,8 @@ function handleBack() {
<template #label> 标段信息
<span v-if="isRequired('isBid')" class="text-red">*</span>
</template>
<div class="">
<DictTag v-model:value="currData.isBid" :options="getDictOptions(DICT_TYPE.sectionType)" selectable>
</DictTag>
<div class="h-300px">
<vxe-grid ref="xGridRef2" v-bind="gridOptions2"></vxe-grid>
</div>
</n-descriptions-item>
@ -585,21 +740,8 @@ function handleBack() {
</div>
</n-descriptions-item>
</n-descriptions>
</n-spin>
</n-card>
<n-card size="small">
<template #header>
<div class="flex flex-row items-center">
<span>招标文件</span>
</div>
</template>
<n-spin :show="loading || addressLoading">
<n-descriptions label-placement="left" bordered :column="3" label-class="w-100px" size="small">
<n-descriptions-item :span="3">
<template #label> 相关附</template>
<template #label> 招标文件 </template>
<div>
<NUpload ref="uploadAfterRef" directory-dnd multiple :file-list="fileUploader.fileList.value"
:custom-request="fileUploader.customRequest" show-download-button :default-upload="false" :max="5"
@ -609,6 +751,7 @@ function handleBack() {
</NUpload>
</div>
</n-descriptions-item>
</n-descriptions>
</n-spin>
</n-card>

View File

@ -385,7 +385,7 @@ function handleBack() {
<NDescriptionsItem label="承办人">{{ currData.inputPerson }}</NDescriptionsItem>
<NDescriptionsItem label="承办人部门">{{
currData.inputDepartName
}}</NDescriptionsItem>
}}</NDescriptionsItem>
<NDescriptionsItem label="承办时间">{{ currData.inputDate }}</NDescriptionsItem>
</NDescriptions>
</NCard>
@ -533,7 +533,11 @@ function handleBack() {
<span v-if="isRequired('zxh')" class="text-red">*</span>
</template>
<div class="flex flex-row items-center">
<n-input-number v-model:value="currData2.zxh" placeholder="请输入" />个月
<n-input-number v-model:value="currData2.zxh" placeholder="请输入">
<template #suffix>
个月
</template>
</n-input-number>
</div>
</NDescriptionsItem>
@ -543,7 +547,11 @@ function handleBack() {
<span v-if="isRequired('yfklb')" class="text-red">*</span>
</template>
<div class="flex flex-row items-center">
<n-input-number v-model:value="currData2.yfklb" placeholder="请输入" />%
<n-input-number v-model:value="currData2.yfklb" placeholder="请输入">
<template #suffix>
%
</template>
</n-input-number>
</div>
</NDescriptionsItem>
@ -553,7 +561,11 @@ function handleBack() {
<span v-if="isRequired('bzjbl')" class="text-red">*</span>
</template>
<div class="flex flex-row items-center">
<n-input-number v-model:value="currData2.bzjbl" placeholder="请输入" />%
<n-input-number v-model:value="currData2.bzjbl" placeholder="请输入">
<template #suffix>
%
</template>
</n-input-number>
</div>
</NDescriptionsItem>
@ -593,6 +605,18 @@ function handleBack() {
<n-input v-model:value="currData2.cbryj" type="textarea" placeholder="请输入" />
</NDescriptionsItem>
<n-descriptions-item :span="3">
<template #label> 合同相关资料 </template>
<div>
<NUpload ref="uploadRef" directory-dnd multiple :file-list="fileUploader.fileList.value"
:custom-request="fileUploader.customRequest" show-download-button :default-upload="false" :max="5"
@update:file-list="fileUploader.handleFileUpdate" @download="handleDownload"
@finish="fileUploader.handleFileUploadFinish">
<n-button>上传文件</n-button>
</NUpload>
</div>
</n-descriptions-item>
</NDescriptions>
</NCard>
@ -629,29 +653,6 @@ function handleBack() {
</n-spin>
</n-card>
<n-card size="small">
<template #header>
<div class="flex flex-row items-center">
<span>合同相关资料</span>
</div>
</template>
<n-spin :show="loading || addressLoading">
<n-descriptions label-placement="left" bordered :column="3" label-class="w-100px" size="small">
<n-descriptions-item :span="3">
<template #label> 相关附件 </template>
<div>
<NUpload ref="uploadRef" directory-dnd multiple :file-list="fileUploader.fileList.value"
:custom-request="fileUploader.customRequest" show-download-button :default-upload="false" :max="5"
@update:file-list="fileUploader.handleFileUpdate" @download="handleDownload"
@finish="fileUploader.handleFileUploadFinish">
<n-button>上传文件</n-button>
</NUpload>
</div>
</n-descriptions-item>
</n-descriptions>
</n-spin>
</n-card>
</n-space>
</div>
</template>

View File

@ -7,6 +7,10 @@ import LineChart from './modules/line-chart.vue';
import PieChart from './modules/pie-chart.vue';
import ProjectNews from './modules/project-news.vue';
import CreativityBanner from './modules/creativity-banner.vue';
import { useRouterPush } from '@/hooks/common/router';
const { routerPushByKey } = useRouterPush();
const appStore = useAppStore();
@ -16,8 +20,8 @@ const gap = computed(() => (appStore.isMobile ? 0 : 16));
<template>
<NSpace vertical :size="16">
<HeaderBanner />
<CardData />
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<!-- <CardData /> -->
<!-- <NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:14">
<NCard :bordered="false" class="card-wrapper">
<LineChart />
@ -28,16 +32,92 @@ const gap = computed(() => (appStore.isMobile ? 0 : 16));
<PieChart />
</NCard>
</NGi>
</NGrid>
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:14">
<ProjectNews />
</NGi>
<NGi span="24 s:24 m:10">
<CreativityBanner />
</NGrid> -->
<NGrid class="" cols="1" responsive="screen" :x-gap="12" :y-gap="9">
<NGi>
<NCard :segmented="{ content: true }" content-style="padding: 0;" :bordered="false" size="small" title="快捷操作">
<div class="flex flex-wrap project-card">
<n-card size="small" class="cursor-pointer project-card-item" hoverable
@click="routerPushByKey('canteen_orderfood')">
<div class="flex flex-col justify-center text-gray-500">
<span class="text-center">
<n-icon size="30" color="#fab251">
<span class="i-icon-park-outline:cooking" />
</n-icon>
</span>
<span class="text-lx text-center">订餐</span>
</div>
</n-card>
<n-card size="small" class="cursor-pointer project-card-item" hoverable
@click="routerPushByKey('bussiness-trip')">
<div class="flex flex-col justify-center text-gray-500">
<span class="text-center">
<n-icon size="30" color="#68c755">
<span class="i-icon-park-outline:train" />
</n-icon>
</span>
<span class="text-lx text-center">出差查询</span>
</div>
</n-card>
<n-card size="small" class="cursor-pointer project-card-item" hoverable
@click="routerPushByKey('meeting_home')">
<div class="flex flex-col justify-center text-gray-500">
<span class="text-center">
<n-icon size="30" color="#1890ff">
<span class="i-icon-park-outline:online-meeting" />
</n-icon>
</span>
<span class="text-lx text-center">会议查询</span>
</div>
</n-card>
<n-card size="small" class="cursor-pointer project-card-item" hoverable
@click="routerPushByKey('office-supplies_apply')">
<div class="flex flex-col justify-center text-gray-500">
<span class="text-center">
<n-icon size="30" color="#f06b96">
<span class="i-icon-park-outline:editor" />
</n-icon>
</span>
<span class="text-lx text-center">办公用品申请</span>
</div>
</n-card>
<n-card size="small" class="cursor-pointer project-card-item" hoverable @click="routerPushByKey('duty')">
<div class="flex flex-col justify-center text-gray-500">
<span class="text-center">
<n-icon size="30" color="#7238d1">
<span class="i-icon-park-outline:worker" />
</n-icon>
</span>
<span class="text-lx text-center">值班管理</span>
</div>
</n-card>
<n-card size="small" class="cursor-pointer project-card-item" hoverable
@click="routerPushByKey('supervise_list')">
<div class="flex flex-col justify-center text-gray-500">
<span class="text-center">
<n-icon size="30" color="">
<span class="i-icon-park-outline:list-view" />
</n-icon>
</span>
<span class="text-lx text-center">立项查询</span>
</div>
</n-card>
</div>
</NCard>
</NGi>
</NGrid>
</NSpace>
</template>
<style scoped></style>
<style scoped>
.project-card {
margin-right: -6px;
}
.project-card-item {
margin: -1px;
width: 33.333333%;
}
</style>

View File

@ -50,13 +50,13 @@ const statisticData = computed<StatisticData[]>(() => [
<h3 class="text-18px font-semibold">
{{ $t('page.home.greeting', { userName: authStore.userInfo.userName }) }}
</h3>
<p class="text-#999 leading-30px">{{ $t('page.home.weatherDesc') }}</p>
<!-- <p class="text-#999 leading-30px">{{ $t('page.home.weatherDesc') }}</p> -->
</div>
</div>
</NGi>
<NGi span="24 s:24 m:6">
<NSpace :size="24" justify="end">
<NStatistic v-for="item in statisticData" :key="item.id" class="whitespace-nowrap" v-bind="item" />
<!-- <NStatistic v-for="item in statisticData" :key="item.id" class="whitespace-nowrap" v-bind="item" /> -->
</NSpace>
</NGi>
</NGrid>