调整一些菜单
This commit is contained in:
parent
34de177171
commit
e00ff6a51d
|
@ -2,6 +2,18 @@ import type { RouteMeta } from 'vue-router'
|
|||
import ElegantVueRouter from '@elegant-router/vue/vite'
|
||||
import type { RouteKey } from '@elegant-router/types'
|
||||
|
||||
function generateEditPath(key) {
|
||||
const editSuffix = '_edit';
|
||||
|
||||
if (key.includes(editSuffix)) {
|
||||
const baseKey = key.replace(editSuffix, '');
|
||||
const path = `/${baseKey.replace(/_/g, '/')}/edit/:id?`;
|
||||
return path;
|
||||
}
|
||||
|
||||
return null; // 如果key不包含_edit,返回null或你希望的默认值
|
||||
}
|
||||
|
||||
export function setupElegantRouter() {
|
||||
return ElegantVueRouter({
|
||||
layouts: {
|
||||
|
@ -22,6 +34,7 @@ export function setupElegantRouter() {
|
|||
'document_antd',
|
||||
],
|
||||
},
|
||||
|
||||
routePathTransformer(routeName, routePath) {
|
||||
const key = routeName as RouteKey
|
||||
|
||||
|
@ -33,29 +46,35 @@ export function setupElegantRouter() {
|
|||
return `/login/:module(${moduleReg})?`
|
||||
}
|
||||
|
||||
if (key === 'meeting_edit') {
|
||||
return '/meeting/edit/:id?'
|
||||
|
||||
let newPath = generateEditPath(key)
|
||||
if (newPath) {
|
||||
return newPath
|
||||
}
|
||||
|
||||
if (key === 'contract_approval_edit') {
|
||||
return '/contract/approval/edit/:id?'
|
||||
}
|
||||
// if (key === 'meeting_edit') {
|
||||
// return '/meeting/edit/:id?'
|
||||
// }
|
||||
|
||||
if (key === 'contract_business_edit') {
|
||||
return '/contract/business/edit/:id?'
|
||||
}
|
||||
// if (key === 'contract_approval_edit') {
|
||||
// return '/contract/approval/edit/:id?'
|
||||
// }
|
||||
|
||||
if (key === 'contract_declaration_edit') {
|
||||
return '/contract/declaration/edit/:id?'
|
||||
}
|
||||
// if (key === 'contract_business_edit') {
|
||||
// return '/contract/business/edit/:id?'
|
||||
// }
|
||||
|
||||
if (key === 'contract_archive_edit') {
|
||||
return '/contract/archive/edit/:id?'
|
||||
}
|
||||
// if (key === 'contract_declaration_edit') {
|
||||
// return '/contract/declaration/edit/:id?'
|
||||
// }
|
||||
|
||||
if (key === 'contract_company_edit') {
|
||||
return '/contract/company/edit/:id?'
|
||||
}
|
||||
// if (key === 'contract_archive_edit') {
|
||||
// return '/contract/archive/edit/:id?'
|
||||
// }
|
||||
|
||||
// if (key === 'contract_company_edit') {
|
||||
// return '/contract/company/edit/:id?'
|
||||
// }
|
||||
|
||||
return routePath
|
||||
},
|
||||
|
|
|
@ -77,20 +77,20 @@ export const alovaInstance = createAlova({
|
|||
statesHook: vueHook,
|
||||
requestAdapter: fetchAdapter(),
|
||||
/** 设置缓存状态:不开启 */
|
||||
// cacheFor: null,
|
||||
cacheFor: {
|
||||
// 统一设置POST的缓存模式
|
||||
GET: {
|
||||
mode: 'restore',
|
||||
expire: 60 * 10 * 1000
|
||||
},
|
||||
POST: {
|
||||
mode: 'restore',
|
||||
expire: 60 * 10 * 1000
|
||||
},
|
||||
// 统一设置HEAD请求的缓存模式
|
||||
HEAD: 60 * 10 * 1000
|
||||
},
|
||||
cacheFor: null,
|
||||
// cacheFor: {
|
||||
// // 统一设置POST的缓存模式
|
||||
// GET: {
|
||||
// mode: 'restore',
|
||||
// expire: 60 * 10 * 1000
|
||||
// },
|
||||
// POST: {
|
||||
// mode: 'restore',
|
||||
// expire: 60 * 10 * 1000
|
||||
// },
|
||||
// // 统一设置HEAD请求的缓存模式
|
||||
// HEAD: 60 * 10 * 1000
|
||||
// },
|
||||
/** 请求拦截器 */
|
||||
beforeRequest: onAuthRequired(method => {
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { computed } from 'vue';
|
|||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
import { useTabStore } from '@/store/modules/tab';
|
||||
|
||||
defineOptions({
|
||||
name: 'GlobalContent'
|
||||
|
@ -20,6 +21,7 @@ withDefaults(defineProps<Props>(), {
|
|||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const routeStore = useRouteStore();
|
||||
const tabStore = useTabStore();
|
||||
|
||||
const transitionName = computed(() => (themeStore.page.animate ? themeStore.page.animateMode : ''));
|
||||
</script>
|
||||
|
@ -36,7 +38,7 @@ const transitionName = computed(() => (themeStore.page.animate ? themeStore.page
|
|||
<component
|
||||
:is="Component"
|
||||
v-if="appStore.reloadFlag"
|
||||
:key="route.path"
|
||||
:key="tabStore.getTabIdByRoute(route)"
|
||||
:class="{ 'p-16px': showPadding }"
|
||||
class="flex-grow bg-layout transition-300"
|
||||
/>
|
||||
|
|
|
@ -21,8 +21,9 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
|
|||
"iframe-page": () => import("@/views/_builtin/iframe-page/[url].vue"),
|
||||
login: () => import("@/views/_builtin/login/index.vue"),
|
||||
about: () => import("@/views/about/index.vue"),
|
||||
"bussiness-trip_edit": () => import("@/views/bussiness-trip/edit/index.vue"),
|
||||
"bussiness-trip_edit": () => import("@/views/bussiness-trip/edit/[id].vue"),
|
||||
"bussiness-trip_home": () => import("@/views/bussiness-trip/home/index.vue"),
|
||||
"bussiness-trip_todo": () => import("@/views/bussiness-trip/todo/index.vue"),
|
||||
canteen_collect: () => import("@/views/canteen/collect/index.vue"),
|
||||
canteen_config: () => import("@/views/canteen/config/index.vue"),
|
||||
canteen_menu: () => import("@/views/canteen/menu/index.vue"),
|
||||
|
@ -68,6 +69,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
|
|||
"office-supplies_audit": () => import("@/views/office-supplies/audit/index.vue"),
|
||||
"office-supplies_inventory": () => import("@/views/office-supplies/inventory/index.vue"),
|
||||
"office-supplies_purchase": () => import("@/views/office-supplies/purchase/index.vue"),
|
||||
supervise_edit: () => import("@/views/supervise/edit/[id].vue"),
|
||||
supervise_list: () => import("@/views/supervise/list/index.vue"),
|
||||
supervise_statistics: () => import("@/views/supervise/statistics/index.vue"),
|
||||
system_dict: () => import("@/views/system/dict/index.vue"),
|
||||
|
|
|
@ -60,18 +60,13 @@ export const generatedRoutes: GeneratedRoute[] = [
|
|||
icon: 'icon-park-outline:train',
|
||||
order: 28
|
||||
},
|
||||
redirect: {
|
||||
name: 'bussiness-trip_home'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'bussiness-trip_edit',
|
||||
path: '/bussiness-trip/edit',
|
||||
path: '/bussiness-trip/edit/:id',
|
||||
component: 'view.bussiness-trip_edit',
|
||||
meta: {
|
||||
title: '出差录入',
|
||||
hideInMenu: true,
|
||||
activeMenu: 'bussiness-trip'
|
||||
title: '出差填报'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -79,9 +74,15 @@ export const generatedRoutes: GeneratedRoute[] = [
|
|||
path: '/bussiness-trip/home',
|
||||
component: 'view.bussiness-trip_home',
|
||||
meta: {
|
||||
title: '出差管理',
|
||||
hideInMenu: true,
|
||||
activeMenu: 'bussiness-trip'
|
||||
title: '出差查询'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'bussiness-trip_todo',
|
||||
path: '/bussiness-trip/todo',
|
||||
component: 'view.bussiness-trip_todo',
|
||||
meta: {
|
||||
title: '出差提示'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -692,10 +693,8 @@ export const generatedRoutes: GeneratedRoute[] = [
|
|||
path: '/meeting/edit/:id?',
|
||||
component: 'view.meeting_edit',
|
||||
meta: {
|
||||
title: '会议编辑',
|
||||
hideInMenu: true,
|
||||
activeMenu: 'meeting',
|
||||
order: 999
|
||||
title: '会议填报',
|
||||
order: 20
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -703,9 +702,8 @@ export const generatedRoutes: GeneratedRoute[] = [
|
|||
path: '/meeting/home',
|
||||
component: 'view.meeting_home',
|
||||
meta: {
|
||||
title: '会议管理',
|
||||
hideInMenu: true,
|
||||
activeMenu: 'meeting'
|
||||
title: '会议查询',
|
||||
order: 30
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -852,13 +850,23 @@ export const generatedRoutes: GeneratedRoute[] = [
|
|||
order: 20
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'supervise_edit',
|
||||
path: '/supervise/edit/:id',
|
||||
component: 'view.supervise_edit',
|
||||
meta: {
|
||||
title: '立项填报',
|
||||
order: 10
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'supervise_list',
|
||||
path: '/supervise/list',
|
||||
component: 'view.supervise_list',
|
||||
meta: {
|
||||
title: '督办列表',
|
||||
icon: 'icon-park-outline:list-view'
|
||||
title: '立项查询',
|
||||
icon: 'icon-park-outline:list-view',
|
||||
order: 20
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -867,7 +875,8 @@ export const generatedRoutes: GeneratedRoute[] = [
|
|||
component: 'view.supervise_statistics',
|
||||
meta: {
|
||||
title: '报表汇总',
|
||||
icon: 'icon-park-outline:analysis'
|
||||
icon: 'icon-park-outline:analysis',
|
||||
order:30
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -44,7 +44,7 @@ function transformElegantRouteToVueRoute(
|
|||
function getLayoutName(component: string) {
|
||||
const layout = component.replace(LAYOUT_PREFIX, '');
|
||||
|
||||
if(!layouts[layout]) {
|
||||
if (!layouts[layout]) {
|
||||
throw new Error(`Layout component "${layout}" not found`);
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ function transformElegantRouteToVueRoute(
|
|||
function getViewName(component: string) {
|
||||
const view = component.replace(VIEW_PREFIX, '');
|
||||
|
||||
if(!views[view]) {
|
||||
if (!views[view]) {
|
||||
throw new Error(`View component "${view}" not found`);
|
||||
}
|
||||
|
||||
|
@ -97,7 +97,7 @@ function transformElegantRouteToVueRoute(
|
|||
if (component) {
|
||||
if (isSingleLevelRoute(route)) {
|
||||
const { layout, view } = getSingleLevelRouteComponent(component);
|
||||
|
||||
|
||||
const singleLevelRoute: RouteRecordRaw = {
|
||||
path,
|
||||
component: layouts[layout],
|
||||
|
@ -110,40 +110,40 @@ function transformElegantRouteToVueRoute(
|
|||
} as RouteRecordRaw
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
return [singleLevelRoute];
|
||||
}
|
||||
|
||||
|
||||
if (isLayout(component)) {
|
||||
const layoutName = getLayoutName(component);
|
||||
|
||||
|
||||
vueRoute.component = layouts[layoutName];
|
||||
}
|
||||
|
||||
|
||||
if (isView(component)) {
|
||||
const viewName = getViewName(component);
|
||||
|
||||
|
||||
vueRoute.component = views[viewName];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`Error transforming route "${route.name}": ${error.toString()}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
|
||||
// add redirect to child
|
||||
if (children?.length && !vueRoute.redirect) {
|
||||
vueRoute.redirect = {
|
||||
name: children[0].name
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (children?.length) {
|
||||
const childRoutes = children.flatMap(child => transformElegantRouteToVueRoute(child, layouts, views));
|
||||
|
||||
if(isFirstLevelRoute(route)) {
|
||||
if (isFirstLevelRoute(route)) {
|
||||
vueRoute.children = childRoutes;
|
||||
} else {
|
||||
vueRoutes.push(...childRoutes);
|
||||
|
@ -178,8 +178,9 @@ const routeMap: RouteMap = {
|
|||
"500": "/500",
|
||||
"about": "/about",
|
||||
"bussiness-trip": "/bussiness-trip",
|
||||
"bussiness-trip_edit": "/bussiness-trip/edit",
|
||||
"bussiness-trip_edit": "/bussiness-trip/edit/:id",
|
||||
"bussiness-trip_home": "/bussiness-trip/home",
|
||||
"bussiness-trip_todo": "/bussiness-trip/todo",
|
||||
"canteen": "/canteen",
|
||||
"canteen_collect": "/canteen/collect",
|
||||
"canteen_config": "/canteen/config",
|
||||
|
@ -251,6 +252,7 @@ const routeMap: RouteMap = {
|
|||
"office-supplies_inventory": "/office-supplies/inventory",
|
||||
"office-supplies_purchase": "/office-supplies/purchase",
|
||||
"supervise": "/supervise",
|
||||
"supervise_edit": "/supervise/edit/:id",
|
||||
"supervise_list": "/supervise/list",
|
||||
"supervise_statistics": "/supervise/statistics",
|
||||
"system": "/system",
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
getDefaultHomeTab,
|
||||
getFixedTabIds,
|
||||
getTabByRoute,
|
||||
getTabIdByRoute,
|
||||
isTabInTabs,
|
||||
updateTabByI18nKey,
|
||||
updateTabsByI18nKey
|
||||
|
@ -288,6 +289,8 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
|
|||
setTabLabel,
|
||||
resetTabLabel,
|
||||
isTabRetain,
|
||||
updateTabsByLocale
|
||||
updateTabsByLocale,
|
||||
getTabIdByRoute,
|
||||
cacheTabs
|
||||
};
|
||||
});
|
||||
|
|
|
@ -34,8 +34,9 @@ declare module "@elegant-router/types" {
|
|||
"500": "/500";
|
||||
"about": "/about";
|
||||
"bussiness-trip": "/bussiness-trip";
|
||||
"bussiness-trip_edit": "/bussiness-trip/edit";
|
||||
"bussiness-trip_edit": "/bussiness-trip/edit/:id";
|
||||
"bussiness-trip_home": "/bussiness-trip/home";
|
||||
"bussiness-trip_todo": "/bussiness-trip/todo";
|
||||
"canteen": "/canteen";
|
||||
"canteen_collect": "/canteen/collect";
|
||||
"canteen_config": "/canteen/config";
|
||||
|
@ -107,6 +108,7 @@ declare module "@elegant-router/types" {
|
|||
"office-supplies_inventory": "/office-supplies/inventory";
|
||||
"office-supplies_purchase": "/office-supplies/purchase";
|
||||
"supervise": "/supervise";
|
||||
"supervise_edit": "/supervise/edit/:id";
|
||||
"supervise_list": "/supervise/list";
|
||||
"supervise_statistics": "/supervise/statistics";
|
||||
"system": "/system";
|
||||
|
@ -202,6 +204,7 @@ declare module "@elegant-router/types" {
|
|||
| "about"
|
||||
| "bussiness-trip_edit"
|
||||
| "bussiness-trip_home"
|
||||
| "bussiness-trip_todo"
|
||||
| "canteen_collect"
|
||||
| "canteen_config"
|
||||
| "canteen_menu"
|
||||
|
@ -247,6 +250,7 @@ declare module "@elegant-router/types" {
|
|||
| "office-supplies_audit"
|
||||
| "office-supplies_inventory"
|
||||
| "office-supplies_purchase"
|
||||
| "supervise_edit"
|
||||
| "supervise_list"
|
||||
| "supervise_statistics"
|
||||
| "system_dict"
|
||||
|
@ -417,3 +421,5 @@ declare module "@elegant-router/types" {
|
|||
*/
|
||||
type ElegantRoute = GeneratedRoute | CustomRoute;
|
||||
}
|
||||
e;
|
||||
}
|
||||
|
|
|
@ -73,7 +73,6 @@ export class FileUploader {
|
|||
};
|
||||
|
||||
initiateUpload = () => {
|
||||
debugger
|
||||
this.uploadComplete.value = false;
|
||||
this.uploadInProgress.value = false;
|
||||
|
||||
|
@ -109,11 +108,11 @@ export class FileUploader {
|
|||
let newFileList: any[] = [];
|
||||
for (const file of fileList) {
|
||||
newFileList.push({
|
||||
fileid: file.fileUuid,
|
||||
id: file.fileUuid,
|
||||
batchId: file.fileUuid,
|
||||
name: file.name,
|
||||
name: file.name || file.fileName,
|
||||
status: "finished",
|
||||
url: file.url,
|
||||
url: file.fileUrl,
|
||||
})
|
||||
}
|
||||
this.fileList.value = newFileList;
|
||||
|
|
|
@ -0,0 +1,361 @@
|
|||
<script setup lang="ts">
|
||||
import { reactive, ref, computed, onMounted } from "vue";
|
||||
import { useDialog, useMessage } from "naive-ui";
|
||||
import type { UploadCustomRequestOptions, UploadFileInfo, UploadInst } from "naive-ui";
|
||||
import {
|
||||
getMeetingList,
|
||||
getAddressorList,
|
||||
saveMeeting,
|
||||
} from "@/api/office/meeting";
|
||||
import { useVxeTable } from "@/hooks/common/vxeTable";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useRequest } from "alova/client";
|
||||
import { DICT_TYPE, getDictDefaultObj, getDictObj, getDictOptions } from "@/utils/dict";
|
||||
import { uploadFile, uploadFiles, downloadFile } from "@/api/system/file";
|
||||
import ChooseUserModal from "@/views/user-center/ChooseUserModal.vue";
|
||||
import { router } from "@/router";
|
||||
|
||||
import { useTabStore } from "@/store/modules/tab";
|
||||
|
||||
import { FileUploader } from "@/utils/file";
|
||||
import { FileSource } from "@/enums";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import dayjs from "dayjs";
|
||||
import { useRouterPush } from "@/hooks/common/router";
|
||||
|
||||
const { routerPushByKey, routerPush } = useRouterPush();
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const userInfo = computed(() => userStore.userInfo)
|
||||
|
||||
const { xGridRef, gridProps, triggerProxy } = useVxeTable({ ref: 'xGridRef' });
|
||||
|
||||
const PrimaryKey = "guid"
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
|
||||
const route = useRoute();
|
||||
const id = route.params.id;
|
||||
|
||||
const tabStore = useTabStore();
|
||||
|
||||
const chooseUserModalRef = ref();
|
||||
const spokespersonModalRef = ref();
|
||||
|
||||
const selectField = ref("");
|
||||
|
||||
const uploadRef = ref<UploadInst | null>(null);
|
||||
|
||||
const fileUploader = new FileUploader({
|
||||
uploadRef,
|
||||
data: { source: FileSource.Erp },
|
||||
onSuccess: (fileList: any[]) => {
|
||||
console.log('[ fileList ] >', fileList)
|
||||
handleSubmit('submit')
|
||||
},
|
||||
onError: (err: any) => {
|
||||
console.log('[ err ] >', err)
|
||||
},
|
||||
})
|
||||
|
||||
/** 必填字段校验 */
|
||||
const requiredFieldRules: any = {
|
||||
"timeRanger": { required: true, message: '请选择出差时间' },
|
||||
"days": { required: true, message: '请输入出差天数' },
|
||||
"place": { required: true, message: '请输入出差地点' },
|
||||
}
|
||||
|
||||
/** 必填字段校验,如果是必填字段则返回对应message */
|
||||
function isRequired(field: string): string {
|
||||
let rule = requiredFieldRules[field];
|
||||
if (rule && rule.required) {
|
||||
return rule.message || `${field}不可为空`;
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
/** Hooks 数据请求 - 会议 */
|
||||
const currData = ref<any>({
|
||||
phone: userInfo.value.phone
|
||||
});
|
||||
const { send, onSuccess, loading } = useRequest(Apis.erp.get_ccsq_page({ params: { guid: id } }), {
|
||||
immediate: false,
|
||||
});
|
||||
onSuccess((res: any) => {
|
||||
console.log(res);
|
||||
if (res.data && res.data?.rows && res.data?.rows.length > 0) {
|
||||
currData.value = res.data?.rows[0] || {};
|
||||
|
||||
try {
|
||||
currData.value.timeRanger = [dayjs(currData.value.starttime).format('YYYY-MM-DD hh:mm'), dayjs(currData.value.endtime).format('YYYY-MM-DD hh:mm')];
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
|
||||
// 处理文件
|
||||
if (currData.value?.fileUuid) {
|
||||
Apis.common.get_attachment_list({
|
||||
params: {
|
||||
uuid: currData.value?.fileUuid,
|
||||
},
|
||||
}).then((res: any) => {
|
||||
console.log(res);
|
||||
fileUploader.setFileList(res.rows);
|
||||
console.log(fileUploader.fileList.value)
|
||||
});
|
||||
}
|
||||
|
||||
console.log("当前数据:", currData.value);
|
||||
} else {
|
||||
dialog.error({
|
||||
title: '提示',
|
||||
content: '当出差记录不存在',
|
||||
positiveText: '返回上一层',
|
||||
closable: false,
|
||||
maskClosable: false,
|
||||
onPositiveClick: () => {
|
||||
back()
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 提交
|
||||
*/
|
||||
async function handleSubmit(type: "submit" | "upload") {
|
||||
|
||||
// 参数校验
|
||||
for (const field of Object.keys(requiredFieldRules)) {
|
||||
if (!currData.value[field]) {
|
||||
message.error(isRequired(field));
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// let loading = message.loading("提交中");
|
||||
|
||||
console.log(currData.value);
|
||||
|
||||
try {
|
||||
if (type === "submit") {
|
||||
|
||||
// 提交
|
||||
if (fileUploader.fileList.value && fileUploader.fileList.value.length) {
|
||||
let fileUuids = fileUploader.fileList.value.map((item) => item.batchId);
|
||||
currData.value.fileUuid = fileUuids.join(',')
|
||||
} else {
|
||||
currData.value.fileUuid = ''
|
||||
}
|
||||
|
||||
let values = JSON.parse(JSON.stringify(currData.value))
|
||||
|
||||
values.starttime = values.timeRanger?.[0];
|
||||
values.endtime = values.timeRanger?.[1];
|
||||
values.timeRanger = undefined;
|
||||
|
||||
values.applyName = userStore.userInfo.gwmc;
|
||||
values.applyId = userStore.userInfo.userId;
|
||||
values.applyTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
await Apis.erp.post_ccsq_save({
|
||||
data: values
|
||||
});
|
||||
|
||||
message.success("提交成功");
|
||||
back();
|
||||
} else {
|
||||
fileUploader.initiateUpload();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
// loading.destroy()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面返回并关闭tab
|
||||
*/
|
||||
function back() {
|
||||
router.removeRoute("bussiness-trip_home")
|
||||
tabStore.removeActiveTab();
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (id) {
|
||||
await send();
|
||||
}
|
||||
});
|
||||
|
||||
const containerRef = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
function calculateDaysBetweenTimestamps(timestamp1, timestamp2) {
|
||||
const date1 = new Date(timestamp1);
|
||||
const date2 = new Date(timestamp2);
|
||||
const timeDifference = Math.abs(date2 - date1);
|
||||
const daysDifference = timeDifference / (24 * 60 * 60 * 1000);
|
||||
const roundedDaysDifference = Math.round(daysDifference * 2) / 2;
|
||||
return roundedDaysDifference;
|
||||
}
|
||||
|
||||
function handleUpdateFormattedValue(value) {
|
||||
const startTimestamp = new Date(value[0]).getTime();
|
||||
const endTimestamp = new Date(value[1]).getTime();
|
||||
const daysBetween = calculateDaysBetweenTimestamps(startTimestamp, endTimestamp);
|
||||
currData.value.days = daysBetween;
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
dialog.warning({
|
||||
title: "提示",
|
||||
content: `是否确认返回上一页面?`,
|
||||
positiveText: "确认",
|
||||
negativeText: "取消",
|
||||
onPositiveClick: async () => {
|
||||
back();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="containerRef"
|
||||
class="flex-col-stretch relative gap-16px overflow-hidden bg-white px-4 py-2 lt-sm:overflow-auto">
|
||||
<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')">
|
||||
<template #icon>
|
||||
<icon-mdi-send />
|
||||
</template>
|
||||
提交
|
||||
</n-button>
|
||||
|
||||
<n-button size="small" @click="handleBack()">
|
||||
<template #icon>
|
||||
<icon-mdi-undo-variant />
|
||||
</template>
|
||||
返回
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-affix>
|
||||
</div>
|
||||
|
||||
<n-space vertical class="overflow-auto">
|
||||
<div class="h-20px"></div>
|
||||
|
||||
<n-card size="small">
|
||||
<template #header>
|
||||
<div class="flex flex-row items-center">
|
||||
<span>基本信息</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<n-spin :show="loading">
|
||||
<n-descriptions label-placement="left" bordered :column="6" label-class="w-120px!" size="small">
|
||||
<n-descriptions-item :span="6">
|
||||
<template #label>
|
||||
出差时间
|
||||
<span v-if="isRequired('timeRanger')" class="text-red">*</span>
|
||||
</template>
|
||||
<NDatePicker v-model:formatted-value="currData.timeRanger" type="datetimerange" :close-on-select="true"
|
||||
placeholder="请选择出差时间" format="yyyy-MM-dd HH:mm" valueFormat="yyyy-MM-dd HH:mm"
|
||||
:timePickerProps="{ format: 'hh:mm' }" @update-formatted-value="handleUpdateFormattedValue">
|
||||
</NDatePicker>
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item :span="6">
|
||||
<template #label>
|
||||
天数
|
||||
<span v-if="isRequired('days')" class="text-red">*</span>
|
||||
</template>
|
||||
<div class="">
|
||||
<n-input-number class="flex-1" v-model:value="currData.days" placeholder="请输入出差天数"></n-input-number>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item :span="6">
|
||||
<template #label>
|
||||
出差地点
|
||||
<span v-if="isRequired('place')" class="text-red">*</span>
|
||||
</template>
|
||||
<div class="">
|
||||
<n-input class="flex-1" type="textarea" v-model:value="currData.place" placeholder="请输入出差地点"></n-input>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item :span="6">
|
||||
<template #label>
|
||||
出差事项
|
||||
<span v-if="isRequired('reasons')" class="text-red">*</span>
|
||||
</template>
|
||||
<div class="">
|
||||
<n-input class="flex-1" type="textarea" v-model:value="currData.reasons"
|
||||
placeholder="请输入出差地点"></n-input>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item :span="6">
|
||||
<template #label>
|
||||
联系方式
|
||||
<span v-if="isRequired('phone')" class="text-red">*</span>
|
||||
</template>
|
||||
<div class="">
|
||||
<n-input class="flex-1" v-model:value="currData.phone" placeholder="请输入联系方式"></n-input>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item :span="6">
|
||||
<template #label>
|
||||
备注
|
||||
<span v-if="isRequired('remarks')" class="text-red">*</span>
|
||||
</template>
|
||||
<div class="">
|
||||
<n-input class="flex-1" type="textarea" v-model:value="currData.remarks" placeholder="请输入备注"></n-input>
|
||||
</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">
|
||||
<n-descriptions label-placement="left" bordered :column="3" label-class="w-120px" size="small">
|
||||
<n-descriptions-item :span="3" label="附件">
|
||||
<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" @finish="fileUploader.handleFileUploadFinish">
|
||||
<n-button>上传文件</n-button>
|
||||
</NUpload>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-spin>
|
||||
</n-card>
|
||||
</n-space>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.readonly .n-input__input-el) {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
:deep(.readonly .n-input__textarea-el) {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
</style>
|
|
@ -1,21 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import {reactive, ref} from 'vue'
|
||||
import type {VxeGridProps} from 'vxe-table'
|
||||
import {getColumns, getFormSchema} from "./schema";
|
||||
import {BasicForm, FormSchema, useForm} from "@/components/Form";
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto bg-white px-4 py-2">
|
||||
|
||||
新增
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
|
||||
</style>
|
|
@ -0,0 +1,181 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { useDialog, useMessage } from "naive-ui";
|
||||
import { BasicForm, useForm } from "@/components/Form";
|
||||
import { mergeGridProps } from "@/utils/vxeTable";
|
||||
import DetailModal from "@/components/DetailModal/src/DetailModal.vue";
|
||||
import TripEditModal from "@/views/bussiness-trip/home/TripEditModal.vue";
|
||||
import TripAuditModal from "@/views/bussiness-trip/home/TripAuditModal.vue";
|
||||
|
||||
import { DutyApi } from "@/api/office/duty";
|
||||
import { PrimaryKey, getColumns, getFormSchema } from "./schema";
|
||||
import { getTripList } from "@/api/office/bussiness-trip";
|
||||
|
||||
import { useRouterPush } from "@/hooks/common/router";
|
||||
import { paginationProps } from "@/utils/alova";
|
||||
import { usePagination } from 'alova/client';
|
||||
import { useVxeTable } from '@/hooks/common/vxeTable';
|
||||
|
||||
const { xGridRef, gridProps, triggerProxy } = useVxeTable({ ref: 'xGridRef' });
|
||||
|
||||
const { routerPushByKey } = useRouterPush();
|
||||
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const searchParams = reactive<any>({});
|
||||
|
||||
const detailModalRef = ref(null)
|
||||
const editModalRef = ref(null);
|
||||
const auditModalRef = ref(null);
|
||||
|
||||
const tabKey = ref<'all' | 'todo'>('all')
|
||||
|
||||
/** Hooks - 表单 */
|
||||
const [register, { setFieldsValue, getFieldsValue }] = useForm({
|
||||
gridProps: { cols: "1 s:2 m:3 l:3 xl:4 2xl:4" },
|
||||
labelWidth: 80,
|
||||
schemas: getFormSchema(),
|
||||
});
|
||||
|
||||
|
||||
/** Hooks - 数据请求 查询待办 */
|
||||
const { send, onSuccess } = usePagination(({ pageNum, pageSize }: any) =>
|
||||
Apis.common.post_workflow_querymytodotask({ params: { pageNum, pageSize } }),
|
||||
{ ...paginationProps(), immediate: false }
|
||||
);
|
||||
onSuccess((res) => {
|
||||
console.log(res);
|
||||
});
|
||||
|
||||
/** Hooks - 数据请求 查询待办和已办 */
|
||||
const { send: doneSend, onSuccess: onDoneSuccess } = usePagination(({ pageNum, pageSize }: any) =>
|
||||
Apis.common.post_workflow_gethistorictaskbyuserid({ params: { pageNum, pageSize } }),
|
||||
{ ...paginationProps(), immediate: false }
|
||||
);
|
||||
onDoneSuccess((res) => {
|
||||
console.log(res);
|
||||
});
|
||||
|
||||
|
||||
/** Hooks - 表格 待办 */
|
||||
const gridOptions = reactive(gridProps({
|
||||
columns: getColumns(),
|
||||
proxyConfig: {
|
||||
autoLoad: true,
|
||||
ajax: {
|
||||
query: ({ page }) => send({ pageNum: page.currentPage, pageSize: page.pageSize })
|
||||
},
|
||||
}
|
||||
}));
|
||||
|
||||
/** Hooks - 表格 已办 */
|
||||
const gridOptions2 = reactive(gridProps({
|
||||
columns: getColumns(),
|
||||
proxyConfig: {
|
||||
autoLoad: true,
|
||||
ajax: {
|
||||
query: ({ page }) => doneSend({ pageNum: page.currentPage, pageSize: page.pageSize })
|
||||
},
|
||||
}
|
||||
}));
|
||||
|
||||
function openDetailModal(record?: Recordable, isSelect: boolean = false) {
|
||||
detailModalRef.value?.open({
|
||||
title: "",
|
||||
columns: getColumns(),
|
||||
record: JSON.parse(JSON.stringify(record || {}))
|
||||
});
|
||||
}
|
||||
|
||||
function openEditModal(record?: Recordable, isSelect: boolean = false) {
|
||||
editModalRef.value?.open({
|
||||
isUpdate: Boolean(record && record[PrimaryKey]),
|
||||
isSelect: isSelect,
|
||||
record: JSON.parse(JSON.stringify(record || {}))
|
||||
});
|
||||
}
|
||||
|
||||
function handleDelete(row) {
|
||||
dialog.warning({
|
||||
title: "提示",
|
||||
content: `是否确认删除该条记录?`,
|
||||
positiveText: "确认",
|
||||
negativeText: "取消",
|
||||
onPositiveClick: async () => {
|
||||
x
|
||||
await DutyApi.delete({ ids: row[PrimaryKey] });
|
||||
triggerProxy("reload");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function handleQuery(_values: Recordable) {
|
||||
triggerProxy("reload");
|
||||
}
|
||||
|
||||
function handleReset(_values: Recordable) {
|
||||
triggerProxy("reload");
|
||||
}
|
||||
|
||||
|
||||
/** 选中数据 */
|
||||
const selectRow: Recordable = computed(() => {
|
||||
return xGridRef.value?.getRadioRecord() || null
|
||||
})
|
||||
|
||||
/** 单选框选中事件 */
|
||||
const setSelectRow = (row: Recordable) => {
|
||||
if (selectRow.value && selectRow.value[PrimaryKey] === row[PrimaryKey]) {
|
||||
xGridRef.value?.clearRadioRow()
|
||||
} else {
|
||||
xGridRef.value?.setRadioRow(row)
|
||||
}
|
||||
}
|
||||
|
||||
/** 表格单元格单击事件 */
|
||||
function handleCellClick({ row }) {
|
||||
setSelectRow(row)
|
||||
}
|
||||
|
||||
function handleTabChange(e) {
|
||||
tabKey.value = e;
|
||||
triggerProxy('reload')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-col-stretch gap-4px overflow-hidden bg-white px-4 py-2 lt-sm:overflow-auto">
|
||||
|
||||
<BasicForm @register="register" @submit="handleQuery" @reset="handleReset" />
|
||||
|
||||
<div class="vxebasic-table-container">
|
||||
<VxeGrid ref="xGridRef" v-bind="gridOptions" @cell-click="handleCellClick">
|
||||
<template #radio_cell="{ row, checked }">
|
||||
<span class="text-base" @click.stop="setSelectRow(row)">
|
||||
<icon-mdi-check-circle-outline v-if="checked"></icon-mdi-check-circle-outline>
|
||||
<icon-mdi-checkbox-blank-circle-outline v-else></icon-mdi-checkbox-blank-circle-outline>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #baseSlot="{ row }">
|
||||
<p>开始时间:{{ row.starttime }}</p>
|
||||
<p>结束时间:{{ row.endtime }}</p>
|
||||
</template>
|
||||
|
||||
</VxeGrid>
|
||||
</div>
|
||||
|
||||
<DetailModal :columns="getColumns()" :record="selectRow" ref="detailModalRef">
|
||||
<template #baseSlot="{ row }">
|
||||
<p>开始时间:{{ row.starttime }}</p>
|
||||
<p>结束时间:{{ row.endtime }}</p>
|
||||
</template>
|
||||
</DetailModal>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="scss"></style>
|
|
@ -18,7 +18,7 @@ import { useVxeTable } from '@/hooks/common/vxeTable';
|
|||
|
||||
const { xGridRef, gridProps, triggerProxy } = useVxeTable({ ref: 'xGridRef' });
|
||||
|
||||
const { routerPushByKey } = useRouterPush();
|
||||
const { routerPushByKey, routerPush } = useRouterPush();
|
||||
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
|
@ -44,8 +44,6 @@ onSuccess((res) => {
|
|||
console.log(res);
|
||||
});
|
||||
|
||||
/** Hooks - 数据请求 查询待办和已办 */
|
||||
|
||||
|
||||
/** Hooks - 表格 */
|
||||
const gridOptions = reactive(gridProps({
|
||||
|
@ -67,11 +65,15 @@ function openDetailModal(record?: Recordable, isSelect: boolean = false) {
|
|||
}
|
||||
|
||||
function openEditModal(record?: Recordable, isSelect: boolean = false) {
|
||||
editModalRef.value?.open({
|
||||
isUpdate: Boolean(record && record[PrimaryKey]),
|
||||
isSelect: isSelect,
|
||||
record: JSON.parse(JSON.stringify(record || {}))
|
||||
});
|
||||
if (record && record[PrimaryKey]) {
|
||||
routerPush({
|
||||
path: '/bussiness-trip/edit/' + record[PrimaryKey],
|
||||
})
|
||||
} else {
|
||||
routerPush({
|
||||
path: '/bussiness-trip/edit',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function handleDelete(row) {
|
||||
|
@ -89,12 +91,6 @@ function handleDelete(row) {
|
|||
}
|
||||
|
||||
|
||||
|
||||
function openAuditModal() {
|
||||
auditModalRef.value?.open({
|
||||
});
|
||||
}
|
||||
|
||||
function handleQuery(_values: Recordable) {
|
||||
triggerProxy("reload");
|
||||
}
|
||||
|
@ -128,6 +124,7 @@ function handleCellClick({ row }) {
|
|||
|
||||
<template>
|
||||
<div class="flex-col-stretch gap-4px overflow-hidden bg-white px-4 py-2 lt-sm:overflow-auto">
|
||||
|
||||
<BasicForm @register="register" @submit="handleQuery" @reset="handleReset" />
|
||||
|
||||
<div class="vxebasic-table-container">
|
||||
|
@ -162,15 +159,6 @@ function handleCellClick({ row }) {
|
|||
删除
|
||||
</NButton>
|
||||
|
||||
<n-badge :value="2" :max="15">
|
||||
<NButton type="primary" @click="openAuditModal()">
|
||||
<template #icon>
|
||||
<icon-mdi-account-check />
|
||||
</template>
|
||||
待审批
|
||||
</NButton>
|
||||
</n-badge>
|
||||
|
||||
</NSpace>
|
||||
</template>
|
||||
|
||||
|
@ -202,4 +190,4 @@ function handleCellClick({ row }) {
|
|||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
<style lang="scss"></style>
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { useDialog, useMessage } from "naive-ui";
|
||||
import { BasicForm, useForm } from "@/components/Form";
|
||||
import { mergeGridProps } from "@/utils/vxeTable";
|
||||
import DetailModal from "@/components/DetailModal/src/DetailModal.vue";
|
||||
import TripEditModal from "@/views/bussiness-trip/home/TripEditModal.vue";
|
||||
import TripAuditModal from "@/views/bussiness-trip/home/TripAuditModal.vue";
|
||||
|
||||
import { DutyApi } from "@/api/office/duty";
|
||||
import { PrimaryKey, getColumns, getFormSchema } from "./schema";
|
||||
import { getTripList } from "@/api/office/bussiness-trip";
|
||||
|
||||
import { useRouterPush } from "@/hooks/common/router";
|
||||
import { paginationProps } from "@/utils/alova";
|
||||
import { usePagination } from 'alova/client';
|
||||
import { useVxeTable } from '@/hooks/common/vxeTable';
|
||||
|
||||
const { xGridRef, gridProps, triggerProxy } = useVxeTable({ ref: 'xGridRef' });
|
||||
|
||||
const { routerPushByKey } = useRouterPush();
|
||||
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const searchParams = reactive<any>({});
|
||||
|
||||
const detailModalRef = ref(null)
|
||||
const editModalRef = ref(null);
|
||||
const auditModalRef = ref(null);
|
||||
|
||||
const tabKey = ref<'all' | 'todo'>('all')
|
||||
|
||||
/** Hooks - 数据请求 查询待办 */
|
||||
const { send, onSuccess } = usePagination(({ pageNum, pageSize }: any) =>
|
||||
Apis.common.post_workflow_querymytodotask({ params: { pageNum, pageSize } }),
|
||||
{ ...paginationProps(), immediate: false }
|
||||
);
|
||||
onSuccess((res) => {
|
||||
console.log(res);
|
||||
});
|
||||
|
||||
/** Hooks - 数据请求 查询待办和已办 */
|
||||
const { send: doneSend, onSuccess: onDoneSuccess } = usePagination(({ pageNum, pageSize }: any) =>
|
||||
Apis.common.post_workflow_gethistorictaskbyuserid({ params: { pageNum, pageSize } }),
|
||||
{ ...paginationProps(), immediate: false }
|
||||
);
|
||||
onDoneSuccess((res) => {
|
||||
console.log(res);
|
||||
});
|
||||
|
||||
|
||||
/** Hooks - 表格 待办 */
|
||||
const gridOptions = reactive(gridProps({
|
||||
columns: getColumns(),
|
||||
proxyConfig: {
|
||||
autoLoad: true,
|
||||
ajax: {
|
||||
query: ({ page }) => send({ pageNum: page.currentPage, pageSize: page.pageSize })
|
||||
},
|
||||
},
|
||||
toolbarConfig:{
|
||||
enabled:false
|
||||
}
|
||||
}));
|
||||
|
||||
/** Hooks - 表格 已办 */
|
||||
const gridOptions2 = reactive(gridProps({
|
||||
columns: getColumns(),
|
||||
proxyConfig: {
|
||||
autoLoad: true,
|
||||
ajax: {
|
||||
query: ({ page }) => doneSend({ pageNum: page.currentPage, pageSize: page.pageSize })
|
||||
},
|
||||
},
|
||||
toolbarConfig:{
|
||||
enabled:false
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
function openEditModal(record?: Recordable, isSelect: boolean = false) {
|
||||
editModalRef.value?.open({
|
||||
isUpdate: Boolean(record && record[PrimaryKey]),
|
||||
isSelect: isSelect,
|
||||
record: JSON.parse(JSON.stringify(record || {}))
|
||||
});
|
||||
}
|
||||
|
||||
/** 选中数据 */
|
||||
const selectRow: Recordable = computed(() => {
|
||||
return xGridRef.value?.getRadioRecord() || null
|
||||
})
|
||||
|
||||
/** 单选框选中事件 */
|
||||
const setSelectRow = (row: Recordable) => {
|
||||
if (selectRow.value && selectRow.value[PrimaryKey] === row[PrimaryKey]) {
|
||||
xGridRef.value?.clearRadioRow()
|
||||
} else {
|
||||
xGridRef.value?.setRadioRow(row)
|
||||
}
|
||||
}
|
||||
|
||||
/** 表格单元格单击事件 */
|
||||
function handleCellClick({ row }) {
|
||||
setSelectRow(row)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-col-stretch gap-4px overflow-hidden bg-white px-4 py-2 lt-sm:overflow-auto">
|
||||
<NSpace vertical>
|
||||
|
||||
<NCard size="small" title="待办工作" class="flex lex-col">
|
||||
<div class="vxebasic-table-container">
|
||||
<VxeGrid ref="xGridRef" v-bind="gridOptions" @cell-click="handleCellClick">
|
||||
<template #radio_cell="{ row, checked }">
|
||||
<span class="text-base" @click.stop="setSelectRow(row)">
|
||||
<icon-mdi-check-circle-outline v-if="checked"></icon-mdi-check-circle-outline>
|
||||
<icon-mdi-checkbox-blank-circle-outline v-else></icon-mdi-checkbox-blank-circle-outline>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #baseSlot="{ row }">
|
||||
<p>开始时间:{{ row.starttime }}</p>
|
||||
<p>结束时间:{{ row.endtime }}</p>
|
||||
</template>
|
||||
|
||||
</VxeGrid>
|
||||
</div>
|
||||
</NCard>
|
||||
|
||||
<NCard size="small" title="已办工作" class="flex lex-col">
|
||||
<div class="vxebasic-table-container">
|
||||
<VxeGrid ref="xGridRef" v-bind="gridOptions" @cell-click="handleCellClick">
|
||||
<template #radio_cell="{ row, checked }">
|
||||
<span class="text-base" @click.stop="setSelectRow(row)">
|
||||
<icon-mdi-check-circle-outline v-if="checked"></icon-mdi-check-circle-outline>
|
||||
<icon-mdi-checkbox-blank-circle-outline v-else></icon-mdi-checkbox-blank-circle-outline>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #baseSlot="{ row }">
|
||||
<p>开始时间:{{ row.starttime }}</p>
|
||||
<p>结束时间:{{ row.endtime }}</p>
|
||||
</template>
|
||||
|
||||
</VxeGrid>
|
||||
</div>
|
||||
</NCard>
|
||||
</NSpace>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="scss"></style>
|
|
@ -0,0 +1,39 @@
|
|||
import type { VxeGridPropTypes } from 'vxe-table';
|
||||
import type { FormSchema } from '@/components/Form';
|
||||
import { useRender } from '@/components/Table';
|
||||
|
||||
export const PrimaryKey = 'guid';
|
||||
|
||||
export function getColumns(_params: any = {}): VxeGridPropTypes.Columns {
|
||||
return [
|
||||
{
|
||||
field: 'applyTime',
|
||||
title: '申请日期',
|
||||
width: 130,
|
||||
fixed: 'left',
|
||||
},
|
||||
{ field: 'base', title: '出差时间', width: 200, slots: { default: 'baseSlot' } },
|
||||
{ field: 'days', title: '出差天数', width: 80 },
|
||||
{ field: 'place', title: '出差地点', width: 200, },
|
||||
{ field: 'reasons', title: '出差事项', width: 200 },
|
||||
{ field: 'phone', title: '联系方式', width: 200 },
|
||||
// { field: 'place', title: '出差地点', width: 200, slots: { default: 'ddy' } },
|
||||
{ field: 'remarks', title: '备注', minWidth: 200 },
|
||||
];
|
||||
}
|
||||
|
||||
export function getFormSchema(_params: any = {}): FormSchema[] {
|
||||
return [
|
||||
{
|
||||
field: 'date',
|
||||
component: 'NDatePicker',
|
||||
label: '填报日期',
|
||||
componentProps: {
|
||||
type: 'daterange',
|
||||
closeOnSelect: true,
|
||||
placeholder: '',
|
||||
valueFormat: 'yyyy-MM-dd'
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
|
@ -0,0 +1,361 @@
|
|||
<script setup lang="ts">
|
||||
import { reactive, ref, computed, onMounted } from "vue";
|
||||
import { useDialog, useMessage } from "naive-ui";
|
||||
import type { UploadCustomRequestOptions, UploadFileInfo, UploadInst } from "naive-ui";
|
||||
import {
|
||||
getMeetingList,
|
||||
getAddressorList,
|
||||
saveMeeting,
|
||||
} from "@/api/office/meeting";
|
||||
import { useVxeTable } from "@/hooks/common/vxeTable";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useRequest } from "alova/client";
|
||||
import { DICT_TYPE, getDictDefaultObj, getDictObj, getDictOptions } from "@/utils/dict";
|
||||
import { uploadFile, uploadFiles, downloadFile } from "@/api/system/file";
|
||||
import ChooseUserModal from "@/views/user-center/ChooseUserModal.vue";
|
||||
import { router } from "@/router";
|
||||
|
||||
import { useTabStore } from "@/store/modules/tab";
|
||||
|
||||
import { FileUploader } from "@/utils/file";
|
||||
import { FileSource } from "@/enums";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import dayjs from "dayjs";
|
||||
import { useRouterPush } from "@/hooks/common/router";
|
||||
|
||||
const { routerPushByKey, routerPush } = useRouterPush();
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const userInfo = computed(() => userStore.userInfo)
|
||||
|
||||
const { xGridRef, gridProps, triggerProxy } = useVxeTable({ ref: 'xGridRef' });
|
||||
|
||||
const PrimaryKey = "guid"
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
|
||||
const route = useRoute();
|
||||
const id = route.params.id;
|
||||
|
||||
const tabStore = useTabStore();
|
||||
|
||||
const chooseUserModalRef = ref();
|
||||
const spokespersonModalRef = ref();
|
||||
|
||||
const selectField = ref("");
|
||||
|
||||
const uploadRef = ref<UploadInst | null>(null);
|
||||
|
||||
const fileUploader = new FileUploader({
|
||||
uploadRef,
|
||||
data: { source: FileSource.Erp },
|
||||
onSuccess: (fileList: any[]) => {
|
||||
console.log('[ fileList ] >', fileList)
|
||||
handleSubmit('submit')
|
||||
},
|
||||
onError: (err: any) => {
|
||||
console.log('[ err ] >', err)
|
||||
},
|
||||
})
|
||||
|
||||
/** 必填字段校验 */
|
||||
const requiredFieldRules: any = {
|
||||
"timeRanger": { required: true, message: '请选择出差时间' },
|
||||
"days": { required: true, message: '请输入出差天数' },
|
||||
"place": { required: true, message: '请输入出差地点' },
|
||||
}
|
||||
|
||||
/** 必填字段校验,如果是必填字段则返回对应message */
|
||||
function isRequired(field: string): string {
|
||||
let rule = requiredFieldRules[field];
|
||||
if (rule && rule.required) {
|
||||
return rule.message || `${field}不可为空`;
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
/** Hooks 数据请求 - 会议 */
|
||||
const currData = ref<any>({
|
||||
phone: userInfo.value.phone
|
||||
});
|
||||
const { send, onSuccess, loading } = useRequest(Apis.erp.get_ccsq_page({ params: { guid: id } }), {
|
||||
immediate: false,
|
||||
});
|
||||
onSuccess((res: any) => {
|
||||
console.log(res);
|
||||
if (res.data && res.data?.rows && res.data?.rows.length > 0) {
|
||||
currData.value = res.data?.rows[0] || {};
|
||||
|
||||
try {
|
||||
currData.value.timeRanger = [dayjs(currData.value.starttime).format('YYYY-MM-DD hh:mm'), dayjs(currData.value.endtime).format('YYYY-MM-DD hh:mm')];
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
|
||||
// 处理文件
|
||||
if (currData.value?.fileUuid) {
|
||||
Apis.common.get_attachment_list({
|
||||
params: {
|
||||
uuid: currData.value?.fileUuid,
|
||||
},
|
||||
}).then((res: any) => {
|
||||
console.log(res);
|
||||
fileUploader.setFileList(res.rows);
|
||||
console.log(fileUploader.fileList.value)
|
||||
});
|
||||
}
|
||||
|
||||
console.log("当前数据:", currData.value);
|
||||
} else {
|
||||
dialog.error({
|
||||
title: '提示',
|
||||
content: '当出差记录不存在',
|
||||
positiveText: '返回上一层',
|
||||
closable: false,
|
||||
maskClosable: false,
|
||||
onPositiveClick: () => {
|
||||
back()
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 提交
|
||||
*/
|
||||
async function handleSubmit(type: "submit" | "upload") {
|
||||
|
||||
// 参数校验
|
||||
for (const field of Object.keys(requiredFieldRules)) {
|
||||
if (!currData.value[field]) {
|
||||
message.error(isRequired(field));
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// let loading = message.loading("提交中");
|
||||
|
||||
console.log(currData.value);
|
||||
|
||||
try {
|
||||
if (type === "submit") {
|
||||
|
||||
// 提交
|
||||
if (fileUploader.fileList.value && fileUploader.fileList.value.length) {
|
||||
let fileUuids = fileUploader.fileList.value.map((item) => item.batchId);
|
||||
currData.value.fileUuid = fileUuids.join(',')
|
||||
} else {
|
||||
currData.value.fileUuid = ''
|
||||
}
|
||||
|
||||
let values = JSON.parse(JSON.stringify(currData.value))
|
||||
|
||||
values.starttime = values.timeRanger?.[0];
|
||||
values.endtime = values.timeRanger?.[1];
|
||||
values.timeRanger = undefined;
|
||||
|
||||
values.applyName = userStore.userInfo.gwmc;
|
||||
values.applyId = userStore.userInfo.userId;
|
||||
values.applyTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
await Apis.erp.post_ccsq_save({
|
||||
data: values
|
||||
});
|
||||
|
||||
message.success("提交成功");
|
||||
back();
|
||||
} else {
|
||||
fileUploader.initiateUpload();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
// loading.destroy()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面返回并关闭tab
|
||||
*/
|
||||
function back() {
|
||||
router.removeRoute("bussiness-trip_home")
|
||||
tabStore.removeActiveTab();
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (id) {
|
||||
await send();
|
||||
}
|
||||
});
|
||||
|
||||
const containerRef = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
function calculateDaysBetweenTimestamps(timestamp1, timestamp2) {
|
||||
const date1 = new Date(timestamp1);
|
||||
const date2 = new Date(timestamp2);
|
||||
const timeDifference = Math.abs(date2 - date1);
|
||||
const daysDifference = timeDifference / (24 * 60 * 60 * 1000);
|
||||
const roundedDaysDifference = Math.round(daysDifference * 2) / 2;
|
||||
return roundedDaysDifference;
|
||||
}
|
||||
|
||||
function handleUpdateFormattedValue(value) {
|
||||
const startTimestamp = new Date(value[0]).getTime();
|
||||
const endTimestamp = new Date(value[1]).getTime();
|
||||
const daysBetween = calculateDaysBetweenTimestamps(startTimestamp, endTimestamp);
|
||||
currData.value.days = daysBetween;
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
dialog.warning({
|
||||
title: "提示",
|
||||
content: `是否确认返回上一页面?`,
|
||||
positiveText: "确认",
|
||||
negativeText: "取消",
|
||||
onPositiveClick: async () => {
|
||||
back();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="containerRef"
|
||||
class="flex-col-stretch relative gap-16px overflow-hidden bg-white px-4 py-2 lt-sm:overflow-auto">
|
||||
<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')">
|
||||
<template #icon>
|
||||
<icon-mdi-send />
|
||||
</template>
|
||||
提交
|
||||
</n-button>
|
||||
|
||||
<n-button size="small" @click="handleBack()">
|
||||
<template #icon>
|
||||
<icon-mdi-undo-variant />
|
||||
</template>
|
||||
返回
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-affix>
|
||||
</div>
|
||||
|
||||
<n-space vertical class="overflow-auto">
|
||||
<div class="h-20px"></div>
|
||||
|
||||
<n-card size="small">
|
||||
<template #header>
|
||||
<div class="flex flex-row items-center">
|
||||
<span>基本信息</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<n-spin :show="loading">
|
||||
<n-descriptions label-placement="left" bordered :column="6" label-class="w-120px!" size="small">
|
||||
<n-descriptions-item :span="6">
|
||||
<template #label>
|
||||
出差时间
|
||||
<span v-if="isRequired('timeRanger')" class="text-red">*</span>
|
||||
</template>
|
||||
<NDatePicker v-model:formatted-value="currData.timeRanger" type="datetimerange" :close-on-select="true"
|
||||
placeholder="请选择出差时间" format="yyyy-MM-dd HH:mm" valueFormat="yyyy-MM-dd HH:mm"
|
||||
:timePickerProps="{ format: 'hh:mm' }" @update-formatted-value="handleUpdateFormattedValue">
|
||||
</NDatePicker>
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item :span="6">
|
||||
<template #label>
|
||||
天数
|
||||
<span v-if="isRequired('days')" class="text-red">*</span>
|
||||
</template>
|
||||
<div class="">
|
||||
<n-input-number class="flex-1" v-model:value="currData.days" placeholder="请输入出差天数"></n-input-number>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item :span="6">
|
||||
<template #label>
|
||||
出差地点
|
||||
<span v-if="isRequired('place')" class="text-red">*</span>
|
||||
</template>
|
||||
<div class="">
|
||||
<n-input class="flex-1" type="textarea" v-model:value="currData.place" placeholder="请输入出差地点"></n-input>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item :span="6">
|
||||
<template #label>
|
||||
出差事项
|
||||
<span v-if="isRequired('reasons')" class="text-red">*</span>
|
||||
</template>
|
||||
<div class="">
|
||||
<n-input class="flex-1" type="textarea" v-model:value="currData.reasons"
|
||||
placeholder="请输入出差地点"></n-input>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item :span="6">
|
||||
<template #label>
|
||||
联系方式
|
||||
<span v-if="isRequired('phone')" class="text-red">*</span>
|
||||
</template>
|
||||
<div class="">
|
||||
<n-input class="flex-1" v-model:value="currData.phone" placeholder="请输入联系方式"></n-input>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item :span="6">
|
||||
<template #label>
|
||||
备注
|
||||
<span v-if="isRequired('remarks')" class="text-red">*</span>
|
||||
</template>
|
||||
<div class="">
|
||||
<n-input class="flex-1" type="textarea" v-model:value="currData.remarks" placeholder="请输入备注"></n-input>
|
||||
</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">
|
||||
<n-descriptions label-placement="left" bordered :column="3" label-class="w-120px" size="small">
|
||||
<n-descriptions-item :span="3" label="附件">
|
||||
<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" @finish="fileUploader.handleFileUploadFinish">
|
||||
<n-button>上传文件</n-button>
|
||||
</NUpload>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-spin>
|
||||
</n-card>
|
||||
</n-space>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.readonly .n-input__input-el) {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
:deep(.readonly .n-input__textarea-el) {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
</style>
|
|
@ -2,7 +2,6 @@ import process from 'node:process'
|
|||
import { URL, fileURLToPath } from 'node:url'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import dayjs from 'dayjs'
|
||||
import { VxeUI } from 'vxe-pc-ui'
|
||||
import { setupVitePlugins } from './build/plugins'
|
||||
import { createViteProxy } from './build/config'
|
||||
|
||||
|
|
Loading…
Reference in New Issue