协同办公优化

This commit is contained in:
z9130 2024-09-24 17:39:51 +08:00
parent 0149f4b10b
commit 3b1197f9ec
30 changed files with 1972 additions and 1179 deletions

View File

@ -1,101 +1,119 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, reactive, onMounted } from 'vue'; import { computed, onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui'; import { useRouter } from 'vue-router';
import { useVxeTable } from '#/hooks/vxeTable';
import { MdiAdd, MdiUpdate, MdiDelete, MdiImport, MdiExport, MdiRadioUnchecked, MdiRadioChecked } from '@vben/icons';
import { getFormSchema, getColumns, PrimaryKey } from './crud.tsx';
import { getMonthStartAndEnd } from '#/utils/time'
import Apis from '#/api'
import dayjs from 'dayjs';
import { message } from "ant-design-vue";
import { Modal } from 'ant-design-vue';
import { useRouter } from 'vue-router' import { Page, useVbenModal } from '@vben/common-ui';
import {
MdiAdd,
MdiDelete,
MdiExport,
MdiRadioChecked,
MdiRadioUnchecked,
MdiUpdate,
} from '@vben/icons';
import { message, Modal } from 'ant-design-vue';
import Apis from '#/api';
import { useVxeTable } from '#/hooks/vxeTable';
import { getMonthStartAndEnd } from '#/utils/time';
import { getColumns, getFormSchema, PrimaryKey } from './crud.tsx';
const router = useRouter(); const router = useRouter();
const checkedValue = ref('all') const checkedValue = ref('all');
const exportSearchParams = ref<any>({ const exportSearchParams = ref<any>({
daterange: getMonthStartAndEnd(), daterange: getMonthStartAndEnd(),
}); });
const searchRef = ref() const searchRef = ref();
let isConfirmLoading = ref(false) const isConfirmLoading = ref(false);
const [_Modal, modalApi] = useVbenModal({ const [_Modal, modalApi] = useVbenModal({
async onConfirm() { async onConfirm() {
isConfirmLoading.value = true isConfirmLoading.value = true;
try { try {
let params = {}; let params = {};
if (checkedValue.value == "daterange") { if (checkedValue.value == 'daterange') {
params = { params = {
startDate: exportSearchParams.value.daterange[0], startDate: exportSearchParams.value.daterange[0],
endDate: exportSearchParams.value.daterange[1], endDate: exportSearchParams.value.daterange[1],
}; };
} }
let res = await Apis.zbgl.post_export({ const res = await Apis.zbgl
params: params, config: { .post_export({
meta: { params,
responseType: 'blob' config: {
} meta: {
} responseType: 'blob',
}).send(); },
message.success("导出成功"); },
modalApi.close() })
.send();
message.success('导出成功');
modalApi.close();
showExportModal.value = false; showExportModal.value = false;
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} finally { } finally {
isConfirmLoading.value = false isConfirmLoading.value = false;
} }
console.info('onConfirm');
console.info("onConfirm");
}, },
}); });
const { xGridRef, triggerProxy, gridProps } = useVxeTable({ ref: 'xGridRef' }); const { xGridRef, triggerProxy, gridProps } = useVxeTable({ ref: 'xGridRef' });
const treeData = ref([]); const treeData = ref([]);
/** Hooks - 表格 */ /** Hooks - 表格 */
const gridOptions = reactive(gridProps({ const gridOptions = reactive(
columns: getColumns(), gridProps({
proxyConfig: { columns: getColumns(),
autoLoad: false, proxyConfig: {
ajax: { autoLoad: false,
query: ({ page }) => { ajax: {
return Apis.contractBaseInfo.get_page({ params: { pageNum: page.currentPage, pageSize: page.pageSize, ...searchRef.value?.formData } }) query: ({ page }) => {
} return Apis.contractBaseInfo.get_page({
params: {
pageNum: page.currentPage,
pageSize: page.pageSize,
...searchRef.value?.formData,
},
});
},
},
}, },
}, pagerConfig: {
pagerConfig: { enabled: true,
enabled: true },
}, toolbarConfig: {
toolbarConfig: { enabled: true,
enabled: true },
}, }),
})); );
function handleEdit(record?: any) { function handleEdit(record?: any) {
if (record && record[PrimaryKey]) { if (record && record[PrimaryKey]) {
router.push("/contract/approval/edit/" + record[PrimaryKey]); router.push(`/contract/approval/edit/${record[PrimaryKey]}`);
} else { } else {
router.push("/contract/approval/edit"); router.push('/contract/approval/edit');
} }
} }
function handleDelete(row) { function handleDelete(row) {
Modal.confirm({ Modal.confirm({
title: '提示', title: '提示',
content: "是否确认删除该条记录?", content: '是否确认删除该条记录?',
okType: 'danger', okType: 'danger',
onOk: async () => { onOk: async () => {
await Apis.contractBaseInfo.post_deletes({ params: { ids: row[PrimaryKey] } }) await Apis.contractBaseInfo.post_deletes({
message.success("删除成功"); params: { ids: row[PrimaryKey] },
triggerProxy("reload"); });
message.success('删除成功');
triggerProxy('reload');
}, },
onCancel() { onCancel() {
console.log('Cancel'); console.log('Cancel');
@ -107,9 +125,9 @@ function handleExport() {
const $grid = xGridRef.value; const $grid = xGridRef.value;
if ($grid) { if ($grid) {
$grid.exportData({ $grid.exportData({
type: "xlsx", type: 'xlsx',
}); });
message.success("导出成功"); message.success('导出成功');
} }
} }
@ -133,66 +151,81 @@ function handleCellClick({ row }) {
} }
onMounted(() => { onMounted(() => {
triggerProxy('reload') triggerProxy('reload');
}) });
const searchForm = ref({ const searchForm = ref({
...getFormSchema(), ...getFormSchema(),
onSearch(context: any) { onSearch(context: any) {
triggerProxy('reload') triggerProxy('reload');
} },
}); });
function toPage() { function toPage() {
window.open("/iframe/meeting/standing-book", "_blank"); window.open('/iframe/meeting/standing-book', '_blank');
} }
function toDetail(row) { function toDetail(row) {
window.open("/iframe/meeting/start/" + row.guid, "_blank"); window.open(`/iframe/meeting/start/${row.guid}`, '_blank');
} }
// //
</script> </script>
<template> <template>
<Page contentClass="h-full flex flex-col"> <Page content-class="h-full flex flex-col">
<fs-search ref="searchRef" v-bind="searchForm"> <fs-search ref="searchRef" v-bind="searchForm">
<template #search_price="{ row }"> <template #search_price="{ row }">
<a-input-number v-model:value="row.budgetSum1" placeholder=""> <a-input-number v-model:value="row.budgetSum1" placeholder="" />
</a-input-number>
<span class="mx-1"></span> <span class="mx-1"></span>
<a-input-number v-model:value="row.budgetSum2" placeholder=""> <a-input-number v-model:value="row.budgetSum2" placeholder="" />
</a-input-number>
</template> </template>
<template #form_time="{ row }"> <template #form_time="{ row }">
<a-date-picker v-model:value="row.startTime" placeholder="" format="YYYY-MM-DD" value-format="YYYY-MM-DD"> <a-date-picker
</a-date-picker> v-model:value="row.startTime"
format="YYYY-MM-DD"
placeholder=""
value-format="YYYY-MM-DD"
/>
<span class="mx-1"></span> <span class="mx-1"></span>
<a-date-picker v-model:value="row.endTime" placeholder="" format="YYYY-MM-DD" value-format="YYYY-MM-DD"> <a-date-picker
</a-date-picker> v-model:value="row.endTime"
format="YYYY-MM-DD"
placeholder=""
value-format="YYYY-MM-DD"
/>
</template> </template>
</fs-search> </fs-search>
<div class="flex-1 min-h-300px"> <div class="min-h-300px flex-1">
<vxe-grid ref="xGridRef" v-bind="gridOptions" @cell-click="handleCellClick"> <vxe-grid
ref="xGridRef"
v-bind="gridOptions"
@cell-click="handleCellClick"
>
<template #toolbar_buttons> <template #toolbar_buttons>
<a-space> <a-space>
<vben-button variant="primary" @click="handleEdit()"> <vben-button variant="primary" @click="handleEdit()">
<MdiAdd class="text-lg mr-0.5" /> <MdiAdd class="mr-0.5 text-lg" />
新增 新增
</vben-button> </vben-button>
<vben-button variant="warning" :disabled="!selectRow || !selectRow[PrimaryKey]" <vben-button
@click="handleEdit(selectRow)"> :disabled="!selectRow || !selectRow[PrimaryKey]"
<MdiUpdate class="text-lg mr-0.5" /> variant="warning"
@click="handleEdit(selectRow)"
>
<MdiUpdate class="mr-0.5 text-lg" />
修改 修改
</vben-button> </vben-button>
<vben-button variant="primary" @click="handleExport()"> <vben-button variant="primary" @click="handleExport()">
<MdiExport class="text-lg mr-0.5" /> <MdiExport class="mr-0.5 text-lg" />
导出 导出
</vben-button> </vben-button>
<vben-button variant="destructive" :disabled="!selectRow || !selectRow[PrimaryKey]" <vben-button
@click="handleDelete(selectRow)"> :disabled="!selectRow || !selectRow[PrimaryKey]"
<MdiDelete class="text-lg mr-0.5" /> variant="destructive"
@click="handleDelete(selectRow)"
>
<MdiDelete class="mr-0.5 text-lg" />
删除 删除
</vben-button> </vben-button>
</a-space> </a-space>
@ -204,7 +237,6 @@ function toDetail(row) {
<MdiRadioUnchecked v-else /> <MdiRadioUnchecked v-else />
</span> </span>
</template> </template>
</vxe-grid> </vxe-grid>
</div> </div>
</Page> </Page>

View File

@ -161,6 +161,9 @@ export default {
get_toDoPage: (data?: QueryOptions) => http.get('/app/ccsq/toDoPage', data), get_toDoPage: (data?: QueryOptions) => http.get('/app/ccsq/toDoPage', data),
/** 协同办公/出差申请 已办 */ /** 协同办公/出差申请 已办 */
get_donePage: (data?: QueryOptions) => http.get('/app/ccsq/donePage', data), get_donePage: (data?: QueryOptions) => http.get('/app/ccsq/donePage', data),
/** 协同办公/出差申请 获取可退回节点信息 */
get_getBackNode: (data?: QueryOptions) =>
http.get('/app/ccsq/getBackNode', data),
/** 协同办公/出差申请 查询流程节点 */ /** 协同办公/出差申请 查询流程节点 */
get_getFlowNodeUserConfig: (data?: QueryOptions) => get_getFlowNodeUserConfig: (data?: QueryOptions) =>
http.get('/app/ccsq/getFlowNodeUserConfig', data), http.get('/app/ccsq/getFlowNodeUserConfig', data),
@ -381,6 +384,12 @@ export default {
/** 合同系统/申报 退回 */ /** 合同系统/申报 退回 */
post_rollback: (data?: BodyOptions) => post_rollback: (data?: BodyOptions) =>
http.post('/app/sbCtrBasePt/rollback', data), http.post('/app/sbCtrBasePt/rollback', data),
/** 合同系统/申报 发起废除 */
post_repeal: (data?: BodyOptions) =>
http.post('/app/sbCtrBasePt/repeal', data),
/** 合同系统/申报 发起流程 */
post_start: (data?: BodyOptions) =>
http.post('/app/sbCtrBasePt/start', data),
}, },
contractBaseInfo: { contractBaseInfo: {
/** 合同系统/立项 合同立项保存 */ /** 合同系统/立项 合同立项保存 */
@ -777,4 +786,12 @@ export default {
/** 合同系统/首页待办/已办 待办 */ /** 合同系统/首页待办/已办 待办 */
get_todo: (data?: QueryOptions) => http.get('/app/home/todo', data), get_todo: (data?: QueryOptions) => http.get('/app/home/todo', data),
}, },
sqConsignPt: {
/** 合同系统/签约授权 签约授权保存 */
post_saveSignMultiEntity: (data?: BodyOptions) =>
http.post('/app/sqConsignPt/saveSignMultiEntity', data),
/** 合同系统/签约授权 签约依据查询 */
get_SigningaAuthorizationSerch: (data?: QueryOptions) =>
http.get('/app/sqConsignPt/SigningaAuthorizationSerch', data),
},
}; };

View File

@ -2,25 +2,23 @@
* *
*/ */
import { preferences } from '@vben/preferences'; import { preferences } from '@vben/preferences';
import { useAccessStore } from '@vben/stores'; import { useAccessStore } from '@vben/stores';
import { createAlova } from 'alova';
import { createServerTokenAuthentication } from 'alova/client';
import fetchAdapter from 'alova/fetch';
import vueHook from 'alova/vue';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { merge } from 'lodash-es';
import { useAuthStore } from '#/store'; import { useAuthStore } from '#/store';
import { transferResponse } from './transferResponse'; import { type BodyOptions, type QueryOptions } from '../global.d';
import { type QueryOptions, type BodyOptions } from '../global.d';
import { merge } from 'lodash-es';
import { createAlova } from 'alova';
import fetchAdapter from 'alova/fetch';
import vueHook from 'alova/vue';
import { ACCESS_TOKEN_FIELD, getBaseURL } from './config'; import { ACCESS_TOKEN_FIELD, getBaseURL } from './config';
import { createServerTokenAuthentication } from 'alova/client'; import { transferResponse } from './transferResponse';
/** 储存过期的token */ /** 储存过期的token */
let expireTokenCache = []; const expireTokenCache = [];
/** 服务端 Token 校验 */ /** 服务端 Token 校验 */
const { onAuthRequired, onResponseRefreshToken } = const { onAuthRequired, onResponseRefreshToken } =
@ -37,10 +35,10 @@ const { onAuthRequired, onResponseRefreshToken } =
} }
const responseClone = response.clone(); const responseClone = response.clone();
let data = await responseClone.json(); const data = await responseClone.json();
// 当服务端返回401时表示token过期 // 当服务端返回401时表示token过期
let isExpired = ['401'].includes(data.code); const isExpired = ['401'].includes(data.code);
if (isExpired) { if (isExpired) {
console.log('AccessToken已过期', data.code); console.log('AccessToken已过期', data.code);
expireTokenCache.push(method.config.headers[ACCESS_TOKEN_FIELD]); expireTokenCache.push(method.config.headers[ACCESS_TOKEN_FIELD]);
@ -93,14 +91,14 @@ export const alovaInstance = createAlova({
// // 统一设置POST的缓存模式 // // 统一设置POST的缓存模式
// GET: { // GET: {
// mode: 'restore', // mode: 'restore',
// expire: 60 * 10 * 1000 // expire: 60 * 10 * 1000,
// }, // },
// POST: { // POST: {
// mode: 'restore', // mode: 'restore',
// expire: 60 * 10 * 1000 // expire: 60 * 10 * 1000
// }, // },
// // 统一设置HEAD请求的缓存模式 // // 统一设置HEAD请求的缓存模式
// HEAD: 60 * 10 * 1000 // HEAD: 60 * 10 * 1000
// }, // },
/** 请求拦截器 */ /** 请求拦截器 */
beforeRequest: onAuthRequired((method) => { beforeRequest: onAuthRequired((method) => {
@ -136,7 +134,7 @@ export const alovaInstance = createAlova({
/** 响应拦截器 */ /** 响应拦截器 */
responded: onResponseRefreshToken(async (response, method) => { responded: onResponseRefreshToken(async (response, method) => {
if (method.meta?.responseType === 'blob') { if (method.meta?.responseType === 'blob') {
let blob = await response.blob(); const blob = await response.blob();
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement('a');
a.href = url; a.href = url;
@ -150,14 +148,14 @@ export const alovaInstance = createAlova({
} }
a.download = fileName; a.download = fileName;
// 触发下载 // 触发下载
document.body.appendChild(a); document.body.append(a);
a.click(); a.click();
// 移除链接 // 移除链接
document.body.removeChild(a); a.remove();
window.URL.revokeObjectURL(url); window.URL.revokeObjectURL(url);
return { return {
blob: blob, blob,
url: url, url,
filename: fileName, filename: fileName,
}; };
} }
@ -197,14 +195,14 @@ export const alovaInstance = createAlova({
}); });
class Http { class Http {
/** private appendParamsToUrl(url: string, params: any): string {
* swagger路径参数pathParams let queryString = '';
* "/api/v1/user/{id}",{ pathParams:{ id :1 },params:{ id:2 } } for (const key in params) {
* /api/v1/user/1?id=2 if (Object.prototype.hasOwnProperty.call(params, key)) {
*/ queryString += `${queryString ? '&' : '?'}${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`;
private replacePathParams(url: string, pathParams?: any): string { }
if (!pathParams) return url; }
return url.replace(/{(\w+)}/g, (_, key) => pathParams[key] || `{${key}}`); return url + queryString;
} }
/** /**
@ -232,11 +230,24 @@ class Http {
return (options as BodyOptions).data !== undefined; return (options as BodyOptions).data !== undefined;
} }
/**
* swagger路径参数pathParams
* "/api/v1/user/{id}",{ pathParams:{ id :1 },params:{ id:2 } }
* /api/v1/user/1?id=2
*/
private replacePathParams(url: string, pathParams?: any): string {
if (!pathParams) return url;
return url.replaceAll(
/\{(\w+)\}/g,
(_, key) => pathParams[key] || `{${key}}`,
);
}
private request( private request(
method: string, method: string,
url: string, url: string,
data?: BodyOptions | QueryOptions, data?: BodyOptions | QueryOptions,
) { ): Promise<any> {
let finalUrl = this.replacePathParams(url, data?.pathParams); let finalUrl = this.replacePathParams(url, data?.pathParams);
const config = this.getConfig(data?.config); const config = this.getConfig(data?.config);
@ -256,7 +267,7 @@ class Http {
}); });
} }
let alovaMethod: 'Get' | 'Post' | 'Delete' | 'Put' = 'Get'; let alovaMethod: 'Delete' | 'Get' | 'Post' | 'Put' = 'Get';
if (method === 'get' || method === 'delete') { if (method === 'get' || method === 'delete') {
if (method === 'get') { if (method === 'get') {
@ -285,20 +296,18 @@ class Http {
} }
} }
private appendParamsToUrl(url: string, params: any): string { delete(url: string, data?: QueryOptions) {
let queryString = ''; return this.request('delete', url, data);
for (const key in params) {
if (Object.prototype.hasOwnProperty.call(params, key)) {
queryString += `${queryString ? '&' : '?'}${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`;
}
}
return url + queryString;
} }
get(url: string, data?: QueryOptions) { get(url: string, data?: QueryOptions) {
return this.request('get', url, data); return this.request('get', url, data);
} }
patch(url: string, data?: BodyOptions) {
return this.request('patch', url, data);
}
post(url: string, data?: BodyOptions) { post(url: string, data?: BodyOptions) {
return this.request('post', url, data); return this.request('post', url, data);
} }
@ -306,14 +315,6 @@ class Http {
put(url: string, data?: BodyOptions) { put(url: string, data?: BodyOptions) {
return this.request('put', url, data); return this.request('put', url, data);
} }
patch(url: string, data?: BodyOptions) {
return this.request('patch', url, data);
}
delete(url: string, data?: QueryOptions) {
return this.request('delete', url, data);
}
} }
export const http = new Http(); export const http = new Http();

View File

@ -1,7 +1,85 @@
<script setup lang="ts">
import type { DictDataType } from '#/utils/dict';
import { computed, onMounted, ref } from 'vue';
import { getDictOpts } from '#/utils/dict';
const props = withDefaults(
defineProps<{
icon?: string;
labelField?: string;
multiple?: boolean;
options: DictDataType[];
selectable?: boolean;
type?: string;
value?: Array<boolean | number | string> | boolean | number | string;
valueField?: string;
}>(),
{
type: '',
options: () => [],
multiple: false,
selectable: false,
labelField: 'label',
valueField: 'value',
value: '',
icon: '',
},
);
const emit = defineEmits(['update:value', 'tag-click']);
const dictData = ref<DictDataType | null>(null);
const selectedValues = ref<Array<number | string>>(
Array.isArray(props.value) ? props.value : [],
);
const dicts = computed(() => {
return props.type ? getDictOpts(props.type) : props.options;
});
const handleClick = (dict: DictDataType) => {
emit('tag-click', dict);
if (props.selectable) {
if (props.multiple) {
const index = selectedValues.value.indexOf(dict.value);
if (index === -1) {
selectedValues.value.push(dict.value);
} else {
selectedValues.value.splice(index, 1);
}
emit('update:value', selectedValues.value);
} else {
emit('update:value', dict.value);
}
}
};
const getTagColor = (dict: DictDataType) => {
return (props.multiple && selectedValues.value.includes(dict.value)) ||
(!props.multiple && props.value == dict.value)
? 'blue'
: '';
};
onMounted(() => {
dictData.value =
(props.options || []).find(
(dict: DictDataType) => dict.value === props.value.toString(),
) || null;
});
</script>
<template> <template>
<template v-if="props.selectable"> <template v-if="props.selectable">
<div v-for="(dict, index) in dicts" :key="index" @click="handleClick(dict)" <div
:class="{ 'cursor-pointer': selectable }"> v-for="(dict, index) in dicts"
:key="index"
:class="{ 'cursor-pointer': selectable }"
@click="handleClick(dict)"
>
<a-tag :color="getTagColor(dict)" :round="!multiple"> <a-tag :color="getTagColor(dict)" :round="!multiple">
{{ dict[labelField] }} {{ dict[labelField] }}
</a-tag> </a-tag>
@ -17,81 +95,12 @@
</template> </template>
</div> </div>
<template v-else> <template v-else>
<!-- <a-tag :color="dictData?.colorType || ''"> <a-tag :color="dictData?.colorType || ''">
{{ (dictData && dictData[labelField]) || value }} {{ (dictData && dictData[labelField]) || value }}
</a-tag> --> </a-tag>
</template> </template>
</template> </template>
<script setup lang="ts">
import { ref, computed, onMounted } from "vue";
import { Tag, Space } from "ant-design-vue";
import { getDictOpts } from "#/utils/dict";
import type { DictDataType } from "#/utils/dict";
const props = withDefaults(
defineProps<{
type?: string;
options: DictDataType[];
value?: string | number | boolean | Array<string | number | boolean>;
icon?: string;
multiple?: boolean;
selectable?: boolean;
labelField?: string;
valueField?: string;
}>(),
{
type: "",
options: () => [],
multiple: false,
selectable: false,
labelField: "label",
valueField: "value",
value: "",
icon: "",
}
);
const emit = defineEmits(["update:value", "tag-click"]);
const dictData = ref<DictDataType | null>(null);
const selectedValues = ref<Array<string | number>>(
Array.isArray(props.value) ? props.value : []
);
const dicts = computed(() => {
return props.type ? getDictOpts(props.type) : props.options;
});
const handleClick = (dict: DictDataType) => {
emit("tag-click", dict);
if (props.selectable) {
if (props.multiple) {
const index = selectedValues.value.indexOf(dict.value);
if (index === -1) {
selectedValues.value.push(dict.value);
} else {
selectedValues.value.splice(index, 1);
}
emit("update:value", selectedValues.value);
} else {
emit("update:value", dict.value);
}
}
};
const getTagColor = (dict: DictDataType) => {
return (props.multiple && selectedValues.value.includes(dict.value)) ||
(!props.multiple && props.value == dict.value)
? "blue"
: "";
};
onMounted(() => {
})
</script>
<style scoped> <style scoped>
.cursor-pointer { .cursor-pointer {
cursor: pointer; cursor: pointer;

View File

@ -1,11 +1,13 @@
import { h } from 'vue'; import { h } from 'vue';
import dayjs from 'dayjs';
import { Button, Tag } from 'ant-design-vue'; import { Button, Tag } from 'ant-design-vue';
import { isArray, isString } from 'lodash-es';
import { DictTag } from '#/components/dict-tag';
// import { Icon } from '@/components/Icon'; // import { Icon } from '@/components/Icon';
import { Tooltip } from 'ant-design-vue' import { Tooltip } from 'ant-design-vue';
import dayjs from 'dayjs';
import { DictTag } from '#/components/dict-tag';
import { getDictOpts } from '#/utils/dict'; import { getDictOpts } from '#/utils/dict';
export const useRender = { export const useRender = {
/** /**
* *
@ -26,7 +28,12 @@ export const useRender = {
* @returns link * @returns link
*/ */
renderLink: (url: string, text?: string) => { renderLink: (url: string, text?: string) => {
if (url) return h(Button, { type: 'link', href: url, target: '_blank' }, () => text || ''); if (url)
return h(
Button,
{ type: 'link', href: url, target: '_blank' },
() => text || '',
);
return ''; return '';
}, },
@ -37,8 +44,7 @@ export const useRender = {
* @returns 1 + 2 * @returns 1 + 2
*/ */
renderText: (text: string, val: string) => { renderText: (text: string, val: string) => {
if (text) return `${text} ${val}`; return text ? `${text} ${val}` : '';
else return '';
}, },
/** /**
* *
@ -47,19 +53,19 @@ export const useRender = {
*/ */
renderMultiLineText: (text: string, params?: any) => { renderMultiLineText: (text: string, params?: any) => {
if (text) { if (text) {
params = params || {} params = params || {};
let classArr: string[] = params.class || []; const classArr: string[] = params.class || [];
if (params?.maxLine && params?.maxLine > 0) { if (params?.maxLine && params?.maxLine > 0) {
classArr.push('line-clamp-' + params?.maxLine) classArr.push(`line-clamp-${params?.maxLine}`);
} }
return h( return h(
Tooltip, Tooltip,
{ trigger: 'hover' }, { trigger: 'hover' },
{ {
trigger: () => h('span', { class: classArr.join(' ') }, text), trigger: () => h('span', { class: classArr.join(' ') }, text),
default: () => text default: () => text,
} },
) );
} }
return ''; return '';
}, },
@ -69,9 +75,8 @@ export const useRender = {
* @param color * @param color
* @returns * @returns
*/ */
renderTag: (text: string | number, color?: string) => { renderTag: (text: number | string, color?: string) => {
if (color) return h(Tag, { color }, () => text); return color ? h(Tag, { color }, () => text) : h(Tag, {}, () => text);
else return h(Tag, {}, () => text);
}, },
/** /**
* *
@ -97,8 +102,9 @@ export const useRender = {
renderDate: (text: string, format?: string) => { renderDate: (text: string, format?: string) => {
if (!text) return ''; if (!text) return '';
if (!format) return dayjs(text).format('YYYY-MM-DD HH:mm:ss'); return format
else return dayjs(text).format(format); ? dayjs(text).format(format)
: dayjs(text).format('YYYY-MM-DD HH:mm:ss');
}, },
/** /**
* *
@ -107,19 +113,20 @@ export const useRender = {
* @returns * @returns
*/ */
renderDict: (text: string, dictType: string, params?: any) => { renderDict: (text: string, dictType: string, params?: any) => {
debugger;
if (!dictType && !params.options) { if (!dictType && !params.options) {
console.warn('请传入字典类型') console.warn('请传入字典类型');
return ''; return '';
} }
let dict: any[] = []; let dict: any[] = [];
if (dictType) { if (dictType) {
dict = getDictOpts(dictType) dict = getDictOpts(dictType);
} }
if (params && params.options) { if (params && params.options) {
dict = params.options; dict = params.options;
} }
return h(DictTag, { options: dict, value: text, ...params }); return h(DictTag, { options: dict, value: text, ...params });
} },
// /** // /**
// * 渲染图标icon // * 渲染图标icon
// * @param text icon // * @param text icon

View File

@ -27,7 +27,7 @@ const routes: RouteRecordRaw[] = [
hideInTab: true, hideInTab: true,
icon: 'lucide:area-chart', icon: 'lucide:area-chart',
title: '立项填报', title: '立项填报',
activePath: '/contract/approval/edit/:id?', activePath: '/supervise/edit/:id?',
}, },
}, },
{ {

View File

@ -1,4 +1,3 @@
/** 数据字典工具类 */ /** 数据字典工具类 */
import { useDictStore } from '#/store/dict'; import { useDictStore } from '#/store/dict';
@ -15,7 +14,7 @@ const dictStore = useDictStore();
export interface DictDataType { export interface DictDataType {
dictTyp?: string; dictTyp?: string;
label: string; label: string;
value: string | number | null; value: null | number | string;
key?: any; key?: any;
colorType?: string; colorType?: string;
cssClass?: string; cssClass?: string;
@ -24,7 +23,7 @@ export interface DictDataType {
export interface DictDataOptions { export interface DictDataOptions {
label?: any; label?: any;
value?: string | number | null; value?: null | number | string;
} }
export function getDictDatas(dictType: string) { export function getDictDatas(dictType: string) {
@ -40,7 +39,10 @@ export function getDictOpts(dictType: string) {
return getDictDatas(dictType); return getDictDatas(dictType);
} }
export function getDictOptions(dictType: string, valueType?: 'string' | 'number' | 'boolean'): any[] { export function getDictOptions(
dictType: string,
valueType?: 'boolean' | 'number' | 'string',
): any[] {
const dictOption: DictDataType[] = []; const dictOption: DictDataType[] = [];
valueType ||= 'string'; valueType ||= 'string';
@ -55,7 +57,7 @@ export function getDictOptions(dictType: string, valueType?: 'string' | 'number'
? `${dict.value}` ? `${dict.value}`
: valueType === 'boolean' : valueType === 'boolean'
? `${dict.value}` === 'true' ? `${dict.value}` === 'true'
: Number.parseInt(`${dict.value}`) : Number.parseInt(`${dict.value}`),
}); });
}); });
} }
@ -68,7 +70,11 @@ export function getDictObj(dictType: string, value: any): DictDataType | null {
const dictOptions: DictDataType[] = getDictDatas(dictType); const dictOptions: DictDataType[] = getDictDatas(dictType);
if (dictOptions) { if (dictOptions) {
if (value) { if (value) {
return dictOptions.find((dict: DictDataType) => dict.value === value.toString()) || null; return (
dictOptions.find(
(dict: DictDataType) => dict.value === value.toString(),
) || null
);
} }
return null; return null;
} else { } else {
@ -79,9 +85,8 @@ export function getDictObj(dictType: string, value: any): DictDataType | null {
/** 获取字典默认数据 */ /** 获取字典默认数据 */
export function getDictDefaultObj(dictType: string): DictDataType | null { export function getDictDefaultObj(dictType: string): DictDataType | null {
const dictOptions: DictDataType[] = getDictDatas(dictType); const dictOptions: DictDataType[] = getDictDatas(dictType);
if (dictOptions) { return dictOptions
return dictOptions.find((dict: DictDataType) => dict.isDefault == '1') || dictOptions[0]; ? dictOptions.find((dict: DictDataType) => dict.isDefault == '1') ||
} else { dictOptions[0]
return null; : null;
}
} }

View File

@ -1,70 +1,69 @@
/** 字典枚举 */ /** 字典枚举 */
export enum DICT_TYPE { export enum DICT_TYPE {
/** 静态枚举-划分标段 */
sectionType = 'section_type',
sectionNum = 'section_num',
commonWhether = 'common_whether',
sysNormalDisable = 'sys_normal_disable',
// 会议类型
meeting_type = 'meeting_type',
meeting_facilities = 'meeting_facilities',
meeting_room = 'meeting_room',
// 订餐
// 主食
canteen_staplefood = 'canteen_staplefood',
/** 就餐方式 */ /** 就餐方式 */
canteen_dineway = 'canteen_dineway', canteen_dineway = 'canteen_dineway',
// 主食
canteen_staplefood = 'canteen_staplefood',
canteenTimeRange = 'canteen_time_range', canteenTimeRange = 'canteen_time_range',
officesupplies_status = 'officesupplies_status',
supervise_task_type = 'supervise_task_type', commonWhether = 'common_whether',
supervise_emergency_level = 'supervise_emergency_level', comprehensiveConfig = 'comprehensive_config',
/** 综合管理-项目管理 */
comprehensiveProject = 'comprehensive_project',
/** 综合管理-项目名称管理 */
comprehensiveProjectName = 'comprehensive_project_name',
// 订餐
/** 合同管理-合同审批 */
contractApproval = 'contract_approval',
/** 合同管理-授权类型 */
contractAuthorizationType = 'contract_authorization_type',
/** 合同管理-签约依据类型 */
contractBasisType = 'contract_basis_type',
/** 合同管理-币种单位 */
contractCurrencyUnit = 'contract_currency_unit',
/** 合同管理-资金流向 */ /** 合同管理-资金流向 */
contractFundFlow = 'contract_fund_flow', contractFundFlow = 'contract_fund_flow',
/** 合同管理-资金渠道 */ /** 合同管理-资金渠道 */
contractFundingSource = 'contract_funding_source', contractFundingSource = 'contract_funding_source',
/** 合同管理-组织形式 */
contractOrganizationForm = 'contract_organization_form',
/** 合同管理-币种单位 */
contractCurrencyUnit = 'contract_currency_unit',
/** 合同管理-合同模块 */ /** 合同管理-合同模块 */
contractModule = 'contract_module', contractModule = 'contract_module',
/** 合同管理-选商方式 */ /** 合同管理-组织形式 */
contractSelectionMethod = 'contract_selection_method', contractOrganizationForm = 'contract_organization_form',
/** 合同管理-合同审批 */
contractApproval = 'contract_approval',
/** 合同管理-履行状态 */ /** 合同管理-履行状态 */
contractPerformanceStatus = 'contract_performance_status', contractPerformanceStatus = 'contract_performance_status',
/** 合同管理-授权类型 */ /** 合同管理-商务计价方式 */
contractAuthorizationType = 'contract_authorization_type', contractPriceStyle = 'contract_price_style',
/** 合同管理-项目类型 */ /** 合同管理-项目类型 */
contractProjectType = 'contract_project_type', contractProjectType = 'contract_project_type',
/** 合同管理-务计价方式 */ /** 合同管理-商方式 */
contractPriceStyle = 'contract_price_style', contractSelectionMethod = 'contract_selection_method',
/** 综合管理-项目管理 */ meeting_facilities = 'meeting_facilities',
comprehensiveProject = 'comprehensive_project',
/** 综合管理-项目名称管理 */ meeting_room = 'meeting_room',
comprehensiveProjectName = 'comprehensive_project_name',
comprehensiveConfig = 'comprehensive_config', // 会议类型
meeting_type = 'meeting_type',
/** 合同管理-签约依据类型 */ officesupplies_status = 'officesupplies_status',
contractBasisType = 'contract_basis_type',
sectionNum = 'section_num',
/** 静态枚举-划分标段 */
sectionType = 'section_type',
supervise_emergency_level = 'supervise_emergency_level',
supervise_task_type = 'supervise_task_type',
sysNormalDisable = 'sys_normal_disable',
} }

View File

@ -1,35 +1,33 @@
export default { export default {
/** 变更原因 */ /** 变更原因 */
cancel_reson: [ cancel_reson: [
{ label: "未填写原因", value: "0" }, { label: '未填写原因', value: '0' },
{ label: "用户取消", value: "1" }, { label: '用户取消', value: '1' },
{ label: "系统取消", value: "2" }, { label: '系统取消', value: '2' },
{ label: "其他原因", value: "3" } { label: '其他原因', value: '3' },
], ],
/** 划分标段 */ /** 划分标段 */
section_type: [ section_type: [
{ label: "是", value: "1" }, { label: '是', value: '1' },
{ label: "否", value: "0" } { label: '否', value: '0' },
], ],
/** 标段数 */ /** 标段数 */
section_num: [ section_num: [
{ label: "1", value: "1" }, { label: '1', value: '1' },
{ label: "2", value: "2" }, , { label: '2', value: '2' },
{ label: "3", value: "3" }, { label: '3', value: '3' },
{ label: "4", value: "4" }, { label: '4', value: '4' },
{ label: "5", value: "5" }, { label: '5', value: '5' },
{ label: "6", value: "6" }, { label: '6', value: '6' },
{ label: "7", value: "7" }, { label: '7', value: '7' },
{ label: "8", value: "8" }, { label: '8', value: '8' },
], ],
/** 常用是否 */ /** 常用是否 */
common_whether: [ common_whether: [
{ label: "是", value: "1" }, { label: '是', value: '1' },
{ label: "否", value: "0" } { label: '否', value: '0' },
] ],
} };

View File

@ -0,0 +1,5 @@
export const PrimaryKey = 'guid';
export function getFormSchema(_params: any = {}) {
return [];
}

View File

@ -1,17 +1,12 @@
<script setup lang="ts"> <script setup lang="tsx">
import { nextTick, onMounted, ref } from 'vue'; import { computed, nextTick, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { MdiUpload } from '@vben/icons'; import { MdiUpload } from '@vben/icons';
import { useUserStore } from '@vben/stores'; import { useUserStore } from '@vben/stores';
import { import { message, Modal, type UploadChangeParam } from 'ant-design-vue';
message,
Modal,
type UploadChangeParam,
type UploadProps,
} from 'ant-design-vue';
import { logger } from 'common-utils'; import { logger } from 'common-utils';
import dayjs, { Dayjs } from 'dayjs'; import dayjs, { Dayjs } from 'dayjs';
@ -35,10 +30,13 @@ const form2Ref = ref();
const isLoading = ref(false); const isLoading = ref(false);
const currData = ref<any>({});
const nodeInfo = ref<any>({});
function calculateDaysBetweenTimestamps(timestamp1, timestamp2) { function calculateDaysBetweenTimestamps(timestamp1, timestamp2) {
const date1 = new Date(timestamp1); const date1 = new Date(timestamp1);
const date2 = new Date(timestamp2); const date2 = new Date(timestamp2);
const timeDifference = Math.abs(date2 - date1); const timeDifference = Math.abs(date2.getTime() - date1.getTime());
const daysDifference = timeDifference / (24 * 60 * 60 * 1000); const daysDifference = timeDifference / (24 * 60 * 60 * 1000);
const roundedDaysDifference = Math.round(daysDifference * 2) / 2; const roundedDaysDifference = Math.round(daysDifference * 2) / 2;
return roundedDaysDifference; return roundedDaysDifference;
@ -70,6 +68,10 @@ function handleUpdateFormattedValue() {
} }
} }
const readOnly = computed(() => {
return id.value && currData.value.status === 'edit';
});
const disabledDate = (current: Dayjs) => { const disabledDate = (current: Dayjs) => {
// Can not select days before today and today // Can not select days before today and today
const form = formRef.value.form; const form = formRef.value.form;
@ -92,10 +94,18 @@ const formBinding = ref({
// disabledDate: (current) => current && current < dayjs().endOf("day"), // disabledDate: (current) => current && current < dayjs().endOf("day"),
format: 'YYYY-MM-DD HH:mm', format: 'YYYY-MM-DD HH:mm',
valueFormat: 'YYYY-MM-DD HH:mm', valueFormat: 'YYYY-MM-DD HH:mm',
onChange: (e) => { onChange: () => {
handleUpdateFormattedValue(); handleUpdateFormattedValue();
}, },
}, },
conditionalRender: {
match(_context) {
return readOnly.value;
},
render({ form }) {
return <span>{form.starttime}</span>;
},
},
rules: [{ required: true, message: '请选择出发时间' }], rules: [{ required: true, message: '请选择出发时间' }],
}, },
endtime: { endtime: {
@ -107,12 +117,20 @@ const formBinding = ref({
allowClear: false, allowClear: false,
showTime: { format: 'HH:mm' }, showTime: { format: 'HH:mm' },
disabledDate, disabledDate,
onChange: (e) => { onChange: () => {
handleUpdateFormattedValue(); handleUpdateFormattedValue();
}, },
format: 'YYYY-MM-DD HH:mm', format: 'YYYY-MM-DD HH:mm',
valueFormat: 'YYYY-MM-DD HH:mm', valueFormat: 'YYYY-MM-DD HH:mm',
}, },
conditionalRender: {
match(_context) {
return readOnly.value;
},
render({ form }) {
return <span>{form.endtime}</span>;
},
},
rules: [{ required: true, message: '请选择结束时间' }], rules: [{ required: true, message: '请选择结束时间' }],
}, },
days: { days: {
@ -123,6 +141,16 @@ const formBinding = ref({
name: 'a-input-number', name: 'a-input-number',
vModel: 'value', vModel: 'value',
min: 0, min: 0,
addonAfter: '天',
placeholder: '请输入出差天数',
},
conditionalRender: {
match(_context) {
return readOnly.value;
},
render({ form }) {
return <span>{form.days} </span>;
},
}, },
}, },
place: { place: {
@ -134,6 +162,14 @@ const formBinding = ref({
vModel: 'value', vModel: 'value',
props: {}, props: {},
}, },
conditionalRender: {
match(_context) {
return readOnly.value;
},
render({ form }) {
return <span>{form.place}</span>;
},
},
rules: [{ required: true, message: '请输入出差地点' }], rules: [{ required: true, message: '请输入出差地点' }],
}, },
reasons: { reasons: {
@ -145,6 +181,14 @@ const formBinding = ref({
vModel: 'value', vModel: 'value',
props: {}, props: {},
}, },
conditionalRender: {
match(_context) {
return readOnly.value;
},
render({ form }) {
return <span>{form.reasons}</span>;
},
},
}, },
phone: { phone: {
title: '联系方式', title: '联系方式',
@ -155,6 +199,14 @@ const formBinding = ref({
vModel: 'value', vModel: 'value',
props: {}, props: {},
}, },
conditionalRender: {
match(_context) {
return readOnly.value;
},
render({ form }) {
return <span>{form.phone}</span>;
},
},
}, },
remarks: { remarks: {
title: '备注', title: '备注',
@ -165,6 +217,14 @@ const formBinding = ref({
vModel: 'value', vModel: 'value',
props: {}, props: {},
}, },
conditionalRender: {
match(_context) {
return readOnly.value;
},
render({ form }) {
return <span>{form.remarks}</span>;
},
},
}, },
}, },
}); });
@ -181,10 +241,6 @@ const form2Binding = ref({
}, },
}); });
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
return false;
};
function handleBack() { function handleBack() {
Modal.confirm({ Modal.confirm({
title: '提示', title: '提示',
@ -208,6 +264,21 @@ const handleChange = (info: UploadChangeParam) => {
}); });
}; };
function handleDelete() {
Modal.confirm({
title: '提示',
content: '是否确认删除该条记录?',
okType: 'danger',
onOk: async () => {
await Apis.ccsq.post_deletes({
params: { ids: id.value },
});
message.success('删除成功');
back();
},
});
}
const chooseUserIds = ref([]); const chooseUserIds = ref([]);
async function handleChooseUserConfirm(e) { async function handleChooseUserConfirm(e) {
@ -215,19 +286,23 @@ async function handleChooseUserConfirm(e) {
isLoading.value = true; isLoading.value = true;
try { try {
// //
// const nodeInfo = await getNodeInfo(nodeId); // const nodeInfo = await getNodeInfo(nodeId);
chooseUserIds.value = e.map((item) => item.ACCOUNT_ID); chooseUserIds.value = e.map((item) => item.ACCOUNT_ID);
if (!nodeInfo.value.variableName) {
throw new Error('出差审批节点异常,请稍候再试');
}
await Apis.ccsq.post_startWorkFlow({ await Apis.ccsq.post_startWorkFlow({
data: { data: {
guid: id.value, guid: id.value,
assigneeList: chooseUserIds.value, variables: {
[nodeInfo.value.variableName]: chooseUserIds.value,
},
}, },
}); });
message.success('提交成功'); message.success('提交成功');
back(); back();
} catch (error) { } catch (error) {
logger.error('合同立项提交失败', error); logger.error('出差提交失败', error);
message.error('提交失败,请稍候再试'); message.error('提交失败,请稍候再试');
} finally { } finally {
isLoading.value = false; isLoading.value = false;
@ -238,27 +313,26 @@ async function handleSave() {
isLoading.value = true; isLoading.value = true;
try { try {
await formRef.value.submit();
const form = formRef.value.form; const form = formRef.value.form;
// await formRef.value.submit()
console.log(formRef.value);
const userStore = useUserStore(); const userStore = useUserStore();
form.applyName = userStore.userInfo.gwmc; form.applyName = (userStore.userInfo as any).gwmc;
form.applyId = userStore.userInfo.userId; form.applyId = userStore.userInfo!.userId;
form.applyTime = dayjs().format('YYYY-MM-DD HH:mm:ss'); form.applyTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
console.log(form2Ref.value.form);
const fileList = form2Ref.value.form.fileList; const fileList = form2Ref.value.form.fileList;
let files: any = []; let files: any = [];
if (fileList && fileList.length > 0) { if (fileList && fileList.length > 0) {
files = await fileUploader.upload(fileList, { source: 'erp' }); files = await fileUploader.upload(fileList, { source: 'erp' });
} }
console.log(files);
if (files) { if (files) {
form.fileUuid = (files.map((item) => item.fileUuid) || []).join(','); form.fileUuid = (files.map((item) => item.fileUuid) || []).join(',');
} }
const data = await Apis.ccsq.post_save({ data: form }); const data = await Apis.ccsq.post_save({ data: form });
id.value = data.guid; id.value = data.guid;
currData.value.status = '已创建';
message.success('保存成功'); message.success('保存成功');
Modal.confirm({ Modal.confirm({
title: '提示', title: '提示',
@ -272,16 +346,25 @@ async function handleSave() {
}); });
} catch (error) { } catch (error) {
message.error('提交失败,请稍候再试'); message.error('提交失败,请稍候再试');
console.log(error); logger.error('出差申请提交失败', error);
} finally { } finally {
isLoading.value = false; isLoading.value = false;
} }
} }
function handleSubmit() { async function handleSubmit() {
await formRef.value.submit();
// isLoading.value = true // isLoading.value = true
//
let tempNodeInfo = await Apis.ccsq.get_getFlowNodeUserConfig({
params: { guid: id.value },
});
nodeInfo.value = tempNodeInfo = tempNodeInfo.rows[0];
chooseUserModalApi.setData({ chooseUserModalApi.setData({
title: '选择审批人', title: `选择${tempNodeInfo.name}(${tempNodeInfo.selectMode})`,
limitMultipleNum: tempNodeInfo.selectMode === '多选' ? 10 : 1,
userIds: chooseUserIds.value || [], userIds: chooseUserIds.value || [],
}); });
chooseUserModalApi.open(); chooseUserModalApi.open();
@ -289,27 +372,21 @@ function handleSubmit() {
onMounted(async () => { onMounted(async () => {
isLoading.value = true; isLoading.value = true;
console.log(id.value);
try { try {
if (id.value) { if (id.value) {
let data = await Apis.ccsq.get_page({ params: { guid: id.value } }); let data = await Apis.ccsq.get_page({ params: { guid: id.value } });
data = data.rows[0]; data = data.rows[0] || {};
currData.value = data;
formRef.value.setFormData(data); formRef.value.setFormData(data);
if (data.fileUuid) { if (data.fileUuid) {
const files = await fileUploader.select(data.fileUuid); const files = await fileUploader.select(data.fileUuid);
console.log(files); form2Ref.value.setFormData({ fileList: files });
form2Ref.value.setFormData(
{
fileList: files,
},
50,
);
} }
} }
} catch (error) { } catch (error) {
console.log(error); logger.error('当前出差申请不存在', error);
Modal.error({ Modal.error({
title: '提示', title: '提示',
content: '当前出差申请不存在', content: '当前出差申请不存在',
@ -337,10 +414,28 @@ onMounted(async () => {
<a-spin :spinning="isLoading"> <a-spin :spinning="isLoading">
<a-space> <a-space>
<vben-button variant="primary" @click="handleSave()">保存</vben-button> <vben-button
<vben-button variant="primary" @click="handleSubmit()"> v-if="!id || ['已创建', '退回'].includes(currData.status)"
variant="primary"
@click="handleSave()"
>
保存
</vben-button>
<vben-button
v-if="!id || ['已创建', '退回'].includes(currData.status)"
:disabled="!id"
variant="primary"
@click="handleSubmit()"
>
提交 提交
</vben-button> </vben-button>
<vben-button
v-if="!isLoading && id && !['待审核'].includes(currData.status)"
variant="destructive"
@click="handleDelete()"
>
删除
</vben-button>
<vben-button variant="secondary" @click="handleBack()"> <vben-button variant="secondary" @click="handleBack()">
返回 返回
</vben-button> </vben-button>
@ -356,13 +451,13 @@ onMounted(async () => {
<template #form_fileList="scope"> <template #form_fileList="scope">
<a-upload <a-upload
v-model:file-list="scope.form.fileList" v-model:file-list="scope.form.fileList"
:before-upload="beforeUpload" :before-upload="() => false"
:max-count="3" :max-count="3"
accept=".pdf" accept=".pdf"
name="file" name="file"
@change="handleChange" @change="handleChange"
> >
<vben-button variant="secondary"> <vben-button v-if="!readOnly" variant="secondary">
<MdiUpload /> <MdiUpload />
点击上传 点击上传
</vben-button> </vben-button>

View File

@ -1,12 +1,20 @@
import type { VxeGridPropTypes } from 'vxe-table'; import type { VxeGridPropTypes } from 'vxe-table';
import { useRender } from '#/hooks/useRender';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useRender } from '#/hooks/useRender';
export const PrimaryKey = 'guid'; export const PrimaryKey = 'guid';
export function getColumns(_params: any = {}): VxeGridPropTypes.Columns { export function getColumns(_params: any = {}): VxeGridPropTypes.Columns {
return [ return [
{ type: 'radio', width: 40, slots: { radio: 'radio_cell' }, align: 'center', fixed: 'left' }, {
type: 'radio',
width: 40,
slots: { radio: 'radio_cell' },
align: 'center',
fixed: 'left',
},
{ {
field: 'applyTime', field: 'applyTime',
title: '申请日期', title: '申请日期',
@ -14,20 +22,32 @@ export function getColumns(_params: any = {}): VxeGridPropTypes.Columns {
fixed: 'left', fixed: 'left',
}, },
{ {
field: 'status', title: '记录状态', width: 120, slots: { field: 'status',
title: '记录状态',
width: 120,
slots: {
default: ({ row }) => { default: ({ row }) => {
const statusMap: any = { const statusMap: any = {
'已创建': 'default', : 'default',
'待审核': 'warning', : 'warning',
'已完成': 'success' : 'success',
退: 'error',
}; };
return useRender.renderTag(row.status, statusMap[row.status] || 'default'); return useRender.renderTag(
} row.status,
} statusMap[row.status] || 'default',
);
},
},
},
{
field: 'base',
title: '出差时间',
width: 200,
slots: { default: 'baseSlot' },
}, },
{ field: 'base', title: '出差时间', width: 200, slots: { default: 'baseSlot' } },
{ field: 'days', title: '出差天数', width: 80 }, { field: 'days', title: '出差天数', width: 80 },
{ field: 'place', title: '出差地点', width: 200, }, { field: 'place', title: '出差地点', width: 200 },
{ field: 'reasons', title: '出差事项', width: 200 }, { field: 'reasons', title: '出差事项', width: 200 },
{ field: 'phone', title: '联系方式', width: 200 }, { field: 'phone', title: '联系方式', width: 200 },
// { field: 'place', title: '出差地点', width: 200, slots: { default: 'ddy' } }, // { field: 'place', title: '出差地点', width: 200, slots: { default: 'ddy' } },

View File

@ -1,46 +1,27 @@
<script setup lang="ts"> <script setup lang="ts">
import { defineComponent, ref, computed, reactive, onMounted } from 'vue'; import { computed, onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui'; import { useRouter } from 'vue-router';
import { useVxeTable } from '#/hooks/vxeTable';
import { import { Page } from '@vben/common-ui';
type CreateCrudOptionsProps,
useColumns,
useFormWrapper,
useFs,
utils,
} from '@fast-crud/fast-crud';
import { import {
MdiAdd, MdiAdd,
MdiUpdate,
MdiDelete, MdiDelete,
MdiExport, MdiExport,
MdiRadioUnchecked,
MdiRadioChecked, MdiRadioChecked,
MdiRadioUnchecked,
MdiUpdate,
} from '@vben/icons'; } from '@vben/icons';
import { getColumns } from './crud.tsx';
import { dict } from '@fast-crud/fast-crud';
import { getMonthStartAndEnd } from '#/utils/time';
import Apis from '#/api';
import dayjs from 'dayjs';
import { message } from 'ant-design-vue';
import { Modal } from 'ant-design-vue';
import { useRouter } from 'vue-router'; import { message, Modal } from 'ant-design-vue';
import Apis from '#/api';
import { useVxeTable } from '#/hooks/vxeTable';
import { getColumns } from './crud.tsx';
const router = useRouter(); const router = useRouter();
const checkedValue = ref('all');
const exportSearchParams = ref<any>({
daterange: getMonthStartAndEnd(),
});
const radioStyle = reactive({
display: 'flex',
height: '30px',
lineHeight: '30px',
});
const searchRef = ref(); const searchRef = ref();
let isConfirmLoading = ref(false);
const { xGridRef, triggerProxy, gridProps } = useVxeTable({ ref: 'xGridRef' }); const { xGridRef, triggerProxy, gridProps } = useVxeTable({ ref: 'xGridRef' });
@ -77,22 +58,23 @@ function handleAdd() {
} }
function handleUpdate(row) { function handleUpdate(row) {
router.push('/bussiness-trip/edit/' + row['guid']); router.push(`/bussiness-trip/edit/${row.guid}`);
} }
function handleDelete(row) { function handleDelete(row) {
if (['待审核'].includes(row.status)) {
message.warning('该记录已提交,不能删除');
return;
}
Modal.confirm({ Modal.confirm({
title: '提示', title: '提示',
content: '是否确认删除该条记录?', content: '是否确认删除该条记录?',
okType: 'danger', okType: 'danger',
onOk: async () => { onOk: async () => {
await Apis.ccsq.post_deletes({ params: { ids: row['guid'] } }); await Apis.ccsq.post_deletes({ params: { ids: row.guid } });
message.success('删除成功'); message.success('删除成功');
triggerProxy('reload'); triggerProxy('reload');
}, },
onCancel() {
console.log('Cancel');
},
}); });
} }
@ -107,13 +89,13 @@ function handleExport() {
} }
/** 选中数据 */ /** 选中数据 */
const selectRow: ant = computed(() => { const selectRow: any = computed(() => {
return xGridRef.value?.getRadioRecord() || null; return xGridRef.value?.getRadioRecord() || null;
}); });
/** 单选框选中事件 */ /** 单选框选中事件 */
const setSelectRow = (row: ant) => { const setSelectRow = (row: any) => {
if (selectRow.value && selectRow.value['guid'] === row['guid']) { if (selectRow.value && selectRow.value.guid === row.guid) {
xGridRef.value?.clearRadioRow(); xGridRef.value?.clearRadioRow();
} else { } else {
xGridRef.value?.setRadioRow(row); xGridRef.value?.setRadioRow(row);
@ -139,29 +121,41 @@ const searchForm = ref({
name: 'a-date-picker', name: 'a-date-picker',
vModel: 'value', vModel: 'value',
allowClear: true, allowClear: true,
props: { format: 'YYYY-MM-DD',
format: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
},
}, },
autoSearchTrigger: 'enter', autoSearchTrigger: 'enter',
show: true, show: true,
}, },
endDate: { endDate: {
title: '截止月份', title: '截止日期',
key: 'endDate', key: 'endDate',
component: { component: {
name: 'a-date-picker', name: 'a-date-picker',
vModel: 'value', vModel: 'value',
allowClear: true, allowClear: true,
props: { format: 'YYYY-MM-DD',
format: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
},
}, },
autoSearchTrigger: 'enter', autoSearchTrigger: 'enter',
show: true, show: true,
}, },
status: {
title: '记录状态',
key: 'status',
component: {
name: 'a-select',
vModel: 'value',
allowClear: true,
class: 'min-w-[180px]',
options: [
{ label: '已创建', value: '已创建' },
{ label: '待审核', value: '待审核' },
{ label: '退回', value: '退回' },
],
},
show: true,
},
}, },
onSearch(_context: any) { onSearch(_context: any) {
triggerProxy('reload'); triggerProxy('reload');
@ -171,31 +165,14 @@ const searchForm = ref({
</script> </script>
<template> <template>
<Page contentClass="h-full flex flex-col"> <Page content-class="h-full flex flex-col">
<SubmitModal <fs-search ref="searchRef" v-bind="searchForm" />
class="w-[600px]"
title="选择送审人"
:loading="isConfirmLoading"
:confirmLoading="isConfirmLoading"
>
<a-radio-group v-model:value="checkedValue">
<a-radio :style="radioStyle" value="all">导出全部</a-radio>
<a-radio :style="radioStyle" value="daterange">
导出时间范围
<div class="ml-2">
<a-range-picker
v-model:value="exportSearchParams.daterange"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
/>
</div>
</a-radio>
</a-radio-group>
</SubmitModal>
<fs-search ref="searchRef" v-bind="searchForm"> </fs-search>
<div class="min-h-300px flex-1"> <div class="min-h-300px flex-1">
<vxe-grid ref="xGridRef" v-bind="gridOptions" @cell-click="handleCellClick"> <vxe-grid
ref="xGridRef"
v-bind="gridOptions"
@cell-click="handleCellClick"
>
<template #toolbar_buttons> <template #toolbar_buttons>
<a-space> <a-space>
<vben-button variant="primary" @click="handleAdd()"> <vben-button variant="primary" @click="handleAdd()">
@ -203,8 +180,8 @@ const searchForm = ref({
新增 新增
</vben-button> </vben-button>
<vben-button <vben-button
:disabled="!selectRow || !selectRow.guid"
variant="warning" variant="warning"
:disabled="!selectRow || !selectRow['guid']"
@click="handleUpdate(selectRow)" @click="handleUpdate(selectRow)"
> >
<MdiUpdate class="mr-0.5 text-lg" /> <MdiUpdate class="mr-0.5 text-lg" />
@ -215,8 +192,8 @@ const searchForm = ref({
导出 导出
</vben-button> </vben-button>
<vben-button <vben-button
:disabled="!selectRow || !selectRow.guid"
variant="destructive" variant="destructive"
:disabled="!selectRow || !selectRow['guid']"
@click="handleDelete(selectRow)" @click="handleDelete(selectRow)"
> >
<MdiDelete class="mr-0.5 text-lg" /> <MdiDelete class="mr-0.5 text-lg" />

View File

@ -1,51 +1,132 @@
import type { VxeGridPropTypes } from 'vxe-table'; import type { VxeGridPropTypes } from 'vxe-table';
import { useRender } from '#/hooks/useRender'; import { useRender } from '#/hooks/useRender';
import dayjs from 'dayjs';
export const PrimaryKey = 'guid'; export const PrimaryKey = 'guid';
export function getColumns(params: any = {}): VxeGridPropTypes.Columns { export function getColumns(params: any = {}): VxeGridPropTypes.Columns {
let columns: VxeGridPropTypes.Columns = [ const columns: VxeGridPropTypes.Columns = [
{ type: 'seq', width: 50, align: 'center', fixed: 'left' }, { type: 'seq', width: 50, align: 'center', fixed: 'left' },
{ {
field: 'applyTime', title: '申请日期', width: 130, field: 'auditTime',
title: '申请日期',
width: 130,
}, },
{ {
field: 'status', title: '记录状态', width: 120, slots: { field: 'status',
title: '记录状态',
width: 120,
slots: {
default: ({ row }) => { default: ({ row }) => {
const statusMap: any = { const statusMap: any = {
'已创建': 'default', : 'default',
'待审核': 'warning', : 'warning',
'已完成': 'success' : 'success',
退: 'error',
}; };
return useRender.renderTag(row.status, statusMap[row.status] || 'default'); return useRender.renderTag(
} row.status,
} statusMap[row.status] || 'default',
);
},
},
}, },
{ {
field: 'applyName', title: '出差人员', minWidth: 200, field: 'taskName',
title: '任务名称',
width: 160,
},
{
field: 'applyName',
title: '出差人员',
minWidth: 200,
},
{
field: 'base',
title: '出差时间',
width: 200,
slots: { default: 'baseSlot' },
}, },
{ field: 'base', title: '出差时间', width: 200, slots: { default: 'baseSlot' } },
{ field: 'days', title: '出差天数', width: 80 }, { field: 'days', title: '出差天数', width: 80 },
{ field: 'place', title: '出差地点', width: 200, }, { field: 'place', title: '出差地点', width: 200 },
{ field: 'reasons', title: '出差事项', width: 200 }, { field: 'reasons', title: '出差事项', width: 200 },
{ field: 'phone', title: '联系方式', width: 200 }, { field: 'phone', title: '联系方式', width: 200 },
{ field: 'remarks', title: '备注', minWidth: 200 }, { field: 'remarks', title: '备注', minWidth: 200 },
] ];
if (params.todoType === 'todo') { if (params.todoType === 'todo') {
columns.push({ columns.push({
field: "operate", field: 'operate',
title: "操作", title: '操作',
width: 110, width: 110,
fixed: "right", fixed: 'right',
slots: { default: "operate" }, slots: { default: 'operate' },
}) });
} }
return columns return columns;
}
export function getDoneColumns(params: any = {}): VxeGridPropTypes.Columns {
const columns: VxeGridPropTypes.Columns = [
{ type: 'seq', width: 50, align: 'center', fixed: 'left' },
{
field: 'applyTime',
title: '申请日期',
width: 130,
},
{
field: 'status',
title: '记录状态',
width: 120,
slots: {
default: ({ row }) => {
const statusMap: any = {
: 'default',
: 'warning',
: 'success',
退: 'error',
};
return useRender.renderTag(
row.status,
statusMap[row.status] || 'default',
);
},
},
},
{
field: 'taskName',
title: '任务名称',
width: 160,
},
{
field: 'applyName',
title: '出差人员',
minWidth: 200,
},
{
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: 'remarks', title: '备注', minWidth: 200 },
{
field: 'operate',
title: '操作',
width: 80,
fixed: 'right',
slots: { default: 'operate' },
},
];
return columns;
} }
export function getFormSchema(_params: any = {}) { export function getFormSchema(_params: any = {}) {
return [] return [];
} }

View File

@ -1,17 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import { defineComponent, ref, reactive, onMounted, nextTick } from 'vue'; import { onMounted, reactive, ref } from 'vue';
import { FsCrud } from '@fast-crud/fast-crud'; import { useRouter } from 'vue-router';
import { type VxeGridProps } from 'vxe-table';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { useVxeTable } from '#/hooks/vxeTable';
import { message } from 'ant-design-vue';
import { logger } from 'common-utils';
import { MdiAdd, MdiUpdate, MdiDelete } from '@vben/icons';
import { dict } from '@fast-crud/fast-crud';
import { getColumns } from './crud';
import Apis from '#/api'; import Apis from '#/api';
import { message, Modal } from 'ant-design-vue'; import { useVxeTable } from '#/hooks/vxeTable';
import chooseUserModal from '#/views/system/user/choose-user-modal.vue'; import chooseUserModal from '#/views/system/user/choose-user-modal.vue';
import { getColumns, getDoneColumns } from './crud';
const router = useRouter();
const [ChooseUserModal, chooseUserModalApi] = useVbenModal({ const [ChooseUserModal, chooseUserModalApi] = useVbenModal({
connectedComponent: chooseUserModal, connectedComponent: chooseUserModal,
}); });
@ -22,9 +25,21 @@ const { xGridRef: xGrid2Ref, triggerProxy: triggerProxy2 } = useVxeTable({
ref: 'xGrid2Ref', ref: 'xGrid2Ref',
}); });
let selectRow = ref({}); const selectRow = ref<any>({});
const treeData = ref([]); const nodeInfo = ref<any>({});
const chooseUserIds = ref([]);
const isLoading = ref(false);
const title = ref('1');
const form2Ref = ref();
const form2Binding = ref({
col: { span: 24 },
columns: {},
});
/** Hooks - 表格 */ /** Hooks - 表格 */
const gridOptions = reactive( const gridOptions = reactive(
@ -40,7 +55,6 @@ const gridOptions = reactive(
params: { params: {
pageNum: page.currentPage, pageNum: page.currentPage,
pageSize: page.pageSize, pageSize: page.pageSize,
...searchParams,
}, },
}); });
}, },
@ -60,7 +74,7 @@ const grid2Options = reactive(
gridProps({ gridProps({
height: '100%', height: '100%',
size: 'mini', size: 'mini',
columns: getColumns({ todoType: 'done' }), columns: getDoneColumns({ todoType: 'done' }),
proxyConfig: { proxyConfig: {
autoLoad: true, autoLoad: true,
ajax: { ajax: {
@ -69,7 +83,6 @@ const grid2Options = reactive(
params: { params: {
pageNum: page.currentPage, pageNum: page.currentPage,
pageSize: page.pageSize, pageSize: page.pageSize,
...searchParams,
}, },
}); });
}, },
@ -84,50 +97,13 @@ const grid2Options = reactive(
}), }),
); );
const assigneeList = ref([]);
onMounted(() => {}); onMounted(() => {});
let searchParams = reactive({}); const isConfirmLoading = ref(false);
const searchForm = ref({
columns: {
name: {
component: {
name: 'a-input',
vModel: 'value',
allowClear: true,
props: {
type: 'text',
showWordLimit: true,
},
},
title: '字典标签',
key: 'type',
autoSearchTrigger: 'enter',
show: true,
},
age: {
component: {
name: 'a-input',
vModel: 'value',
allowClear: true,
},
title: '字典键值',
key: 'value',
autoSearchTrigger: 'enter',
show: true,
},
},
onSearch(context: any) {
searchParams = context.form;
triggerProxy('reload');
},
onReset(context: any) {
searchParams = context.form;
},
});
let isConfirmLoading = ref(false); const [TemporaryModal, temporaryModalApi] = useVbenModal({
const [TemporaryModalApi, temporaryModalApi] = useVbenModal({
onOpenChange(isOpen: boolean) { onOpenChange(isOpen: boolean) {
if (isOpen) { if (isOpen) {
isConfirmLoading.value = false; isConfirmLoading.value = false;
@ -135,24 +111,58 @@ const [TemporaryModalApi, temporaryModalApi] = useVbenModal({
}, },
async onConfirm() { async onConfirm() {
// await // await
handleAudit(selectRow.value, 'rejectConfirm');
}, },
}); });
async function handleAudit(row, type) { async function handleAudit(row, type) {
selectRow.value = row; selectRow.value = row;
//
let tempNodeInfo = await Apis.ccsq.post_getNextNodeUserConfig({
params: { taskId: row.taskId },
});
nodeInfo.value = tempNodeInfo = tempNodeInfo.rows[0];
chooseUserModalApi.setData({
title: `选择${tempNodeInfo.name}(${tempNodeInfo.selectMode})`,
limitMultipleNum: tempNodeInfo.selectMode === '多选' ? 10 : 1,
userIds: chooseUserIds.value || [],
});
if (type === 'access') { if (type === 'access') {
chooseUserModalApi.setData({
title: '选择审批人',
});
chooseUserModalApi.open(); chooseUserModalApi.open();
return; return;
} }
if (type === 'accessConfirm') {
try {
await Apis.ccsq.post_submit({
data: {
guid: selectRow.value.guid,
variables: {
[nodeInfo.value.variableName]: assigneeList.value,
},
comment: '通过',
},
});
message.success('提交成功');
triggerProxy('reload');
triggerProxy2('reload');
} catch (error) {
logger.error('出差审批提交失败', error);
message.error('提交失败,请稍候再试');
} finally {
isLoading.value = false;
}
return;
}
if (type === 'reject') { if (type === 'reject') {
await Apis.ccsq.get_getBackNode({ // let tempBackNodeInfo = await Apis.ccsq.get_getBackNode({
params: { taskId: row.taskId }, // params: { taskId: row.taskId },
}); // });
// tempBackNodeInfo = tempBackNodeInfo.rows[0]
form2Binding.value.columns = {}; form2Binding.value.columns = {};
@ -161,63 +171,57 @@ async function handleAudit(row, type) {
comment: { comment: {
title: '', title: '',
key: 'comment', key: 'comment',
col: { span: 10 }, col: { span: 24 },
colon: false, colon: false,
component: { component: {
name: 'a-time-picker', name: 'a-textarea',
vModel: 'value', vModel: 'value',
format: 'HH:mm', autoSize: { minRows: 4, maxRows: 6 },
valueFormat: 'HH:mm', placeholder: '请输入',
}, },
}, },
}; };
temporaryModalApi.open(); temporaryModalApi.open();
nextTick(() => { isConfirmLoading.value = true;
form2Ref.value.setFormData(submitForm.value); }
if (type === 'rejectConfirm') {
await Apis.ccsq.post_rollback({
params: {
...form2Ref.value.form,
guid: selectRow.value.guid,
backNodeId: 'userTask_first_node',
},
}); });
temporaryModalApi.close();
isConfirmLoading.value = false;
triggerProxy('reload');
triggerProxy2('reload');
} }
} }
async function handleChooseUserConfirm(e) { async function handleChooseUserConfirm(e) {
console.log(e);
chooseUserModalApi.close(); chooseUserModalApi.close();
try { assigneeList.value = e.map((item) => item.ACCOUNT_ID);
await Apis.ccsq.post_submit({ handleAudit(selectRow.value, 'accessConfirm');
data: {
guid: selectRow.value.guid,
assigneeList: e.map((item) => item.ACCOUNT_ID),
comment: '通过',
},
});
message.success('提交成功');
} catch (error) {
console.log(error);
message.error('提交失败,请稍候再试');
} finally {
}
} }
const title = ref('1'); function toPage(row) {
router.push(`/bussiness-trip/edit/${row.guid || row.businessKey}`);
const form2Ref = ref(); }
const form2Binding = ref({
col: { span: 24 },
columns: {},
});
</script> </script>
<template> <template>
<Page contentClass="h-full flex flex-col"> <Page content-class="h-full flex flex-col">
<TemporaryModal <TemporaryModal
class="w-[400px]" :confirm-loading="isConfirmLoading"
:title="title"
contentClass="min-h-0"
:loading="isConfirmLoading" :loading="isConfirmLoading"
:confirmLoading="isConfirmLoading" :title="title"
class="w-[600px]"
content-class="min-h-0"
> >
<fs-form ref="form2Ref" v-bind="form2Binding"> </fs-form> <fs-form ref="form2Ref" v-bind="form2Binding" />
</TemporaryModal> </TemporaryModal>
<ChooseUserModal <ChooseUserModal
@ -226,12 +230,12 @@ const form2Binding = ref({
@confirm="handleChooseUserConfirm" @confirm="handleChooseUserConfirm"
/> />
<a-space direction="vertical" size="small" class="flex h-full flex-col"> <a-space class="flex h-full flex-col" direction="vertical" size="small">
<a-card <a-card
title="待办" :body-style="{ flex: '1' }"
size="small"
class="flex h-full flex-col" class="flex h-full flex-col"
:bodyStyle="{ flex: '1' }" size="small"
title="待办"
> >
<vxe-grid ref="xGridRef" v-bind="gridOptions" class="h-full"> <vxe-grid ref="xGridRef" v-bind="gridOptions" class="h-full">
<template #toolbar_buttons> </template> <template #toolbar_buttons> </template>
@ -244,16 +248,16 @@ const form2Binding = ref({
<template #operate="{ row }"> <template #operate="{ row }">
<a-space> <a-space>
<a-button <a-button
type="text"
size="small" size="small"
type="text"
@click="handleAudit(row, 'access')" @click="handleAudit(row, 'access')"
> >
通过 通过
</a-button> </a-button>
<a-button <a-button
type="text"
danger danger
size="small" size="small"
type="text"
@click="handleAudit(row, 'reject')" @click="handleAudit(row, 'reject')"
> >
拒绝 拒绝
@ -263,10 +267,10 @@ const form2Binding = ref({
</vxe-grid> </vxe-grid>
</a-card> </a-card>
<a-card <a-card
title="已办" :body-style="{ flex: '1' }"
size="small"
class="flex h-full flex-col" class="flex h-full flex-col"
:bodyStyle="{ flex: '1' }" size="small"
title="已办"
> >
<vxe-grid ref="xGrid2Ref" v-bind="grid2Options"> <vxe-grid ref="xGrid2Ref" v-bind="grid2Options">
<template #toolbar_buttons> </template> <template #toolbar_buttons> </template>
@ -274,6 +278,14 @@ const form2Binding = ref({
<p>开始时间{{ row.starttime }}</p> <p>开始时间{{ row.starttime }}</p>
<p>结束时间{{ row.endtime }}</p> <p>结束时间{{ row.endtime }}</p>
</template> </template>
<template #operate="{ row }">
<a-space>
<a-button size="small" type="text" @click="toPage(row)">
查看
</a-button>
</a-space>
</template>
</vxe-grid> </vxe-grid>
</a-card> </a-card>
</a-space> </a-space>

View File

@ -1,78 +1,80 @@
import type { VxeGridPropTypes } from 'vxe-table'; import type { VxeGridPropTypes } from 'vxe-table';
import { useRender } from '#/hooks/useRender';
import dayjs from 'dayjs';
import { DICT_TYPE, getDictObj, getDictOptions } from '#/utils/dict';
import { dict } from '@fast-crud/fast-crud'; import { dict } from '@fast-crud/fast-crud';
import { unitComponentProps } from '#/common/unit';
import { useRender } from '#/hooks/useRender';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
export const PrimaryKey = 'guid'; export const PrimaryKey = 'guid';
export function getColumns(params: any = {}): VxeGridPropTypes.Columns { export function getColumns(params: any = {}): VxeGridPropTypes.Columns {
return [ return [
{ {
type: "radio", type: 'radio',
width: 40, width: 40,
slots: { radio: "radio_cell" }, slots: { radio: 'radio_cell' },
align: "center", align: 'center',
fixed: "left", fixed: 'left',
}, },
{ field: "applyName", title: "申请人", width: 120 }, { field: 'applyName', title: '申请人', width: 120 },
{ field: "officeName", title: "申请物品", minWidth: 150 }, { field: 'officeName', title: '申请物品', minWidth: 150 },
{ {
field: "model", field: 'model',
title: "物品型号", title: '物品型号',
minWidth: 150, minWidth: 150,
}, },
{ {
field: "count", field: 'count',
title: "申请数量", title: '申请数量',
minWidth: 80, minWidth: 80,
slots: { slots: {
default: ({ row }) => { default: ({ row }) => {
return row.count + "个"; return `${row.count}`;
}, },
}, },
}, },
{ field: "remarks", title: "申请原因", width: 200 }, { field: 'remarks', title: '申请原因', width: 200 },
{ {
field: "status", field: 'status',
title: "申请状态", title: '申请状态',
width: 120, width: 120,
slots: { slots: {
default: ({ row }) => { default: ({ row }) => {
return useRender.renderDict(row.status, DICT_TYPE.officesupplies_status, { return useRender.renderDict(
labelField: "extend1", row.status,
}); DICT_TYPE.officesupplies_status,
{
labelField: 'extend1',
},
);
}, },
}, },
}, },
{ {
field: "companyRealCount", field: 'companyRealCount',
title: "实际发放量", title: '实际发放量',
width: 100, width: 100,
showOverflow: true, showOverflow: true,
slots: { slots: {
default: ({ row }) => { default: ({ row }) => {
return useRender.renderText(row.companyRealCount, "个"); return useRender.renderText(row.companyRealCount, '个');
}, },
}, },
}, },
{ field: "time", title: "申请时间", width: 150 }, { field: 'time', title: '申请时间', width: 150 },
] ];
} }
export function getFormSchema(params: any = {}) { export function getFormSchema(params: any = {}) {
const { chooseUserModalApi } = params;
const { chooseUserModalApi } = params const data = getDictOptions(DICT_TYPE.officesupplies_status);
let data = getDictOptions(DICT_TYPE.officesupplies_status);
data.forEach((item) => { data.forEach((item) => {
item.label = item.extend1; item.label = item.extend1;
}); });
return { return {
initialForm: { initialForm: {},
},
columns: { columns: {
status: { status: {
title: '申请状态', title: '申请状态',
@ -83,8 +85,8 @@ export function getFormSchema(params: any = {}) {
class: 'min-w-[180px]', class: 'min-w-[180px]',
allowClear: true, allowClear: true,
dict: dict({ dict: dict({
data: data data,
}) }),
}, },
autoSearchTrigger: 'enter', autoSearchTrigger: 'enter',
show: true, show: true,
@ -126,6 +128,5 @@ export function getFormSchema(params: any = {}) {
show: true, show: true,
}, },
}, },
} };
} }

View File

@ -1,78 +1,173 @@
import type { VxeGridPropTypes } from 'vxe-table'; import type { VxeGridPropTypes } from 'vxe-table';
import { useRender } from '#/hooks/useRender';
import dayjs from 'dayjs';
import { DICT_TYPE, getDictObj, getDictOptions } from '#/utils/dict';
import { dict } from '@fast-crud/fast-crud'; import { dict } from '@fast-crud/fast-crud';
import { unitComponentProps } from '#/common/unit';
import { useRender } from '#/hooks/useRender';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
export const PrimaryKey = 'guid'; export const PrimaryKey = 'guid';
export function getColumns(params: any = {}): VxeGridPropTypes.Columns { export function getColumns(params: any = {}): VxeGridPropTypes.Columns {
return [ return [
{ type: 'seq', width: 50, align: 'center', fixed: 'left' },
{ {
type: "radio", field: 'status',
width: 40, title: '申请状态',
slots: { radio: "radio_cell" },
align: "center",
fixed: "left",
},
{ field: "applyName", title: "申请人", width: 120 },
{ field: "officeName", title: "申请物品", minWidth: 150 },
{
field: "model",
title: "物品型号",
minWidth: 150,
},
{
field: "count",
title: "申请数量",
minWidth: 80,
slots: {
default: ({ row }) => {
return row.count + "个";
},
},
},
{ field: "remarks", title: "申请原因", width: 200 },
{
field: "status",
title: "申请状态",
width: 120, width: 120,
slots: { slots: {
default: ({ row }) => { default: ({ row }) => {
return useRender.renderDict(row.status, DICT_TYPE.officesupplies_status, { return useRender.renderDict(
labelField: "extend1", row.status,
}); DICT_TYPE.officesupplies_status,
{
labelField: 'extend1',
},
);
},
},
},
{ field: 'applyName', title: '申请人', width: 120 },
{ field: 'officeName', title: '申请物品', minWidth: 150 },
{
field: 'model',
title: '物品型号',
minWidth: 150,
},
{
field: 'count',
title: '申请数量',
minWidth: 80,
slots: {
default: ({ row }) => {
return `${row.count}`;
},
},
},
{ field: 'remarks', title: '申请原因', width: 200 },
{
field: 'status',
title: '申请状态',
width: 120,
slots: {
default: ({ row }) => {
return useRender.renderDict(
row.status,
DICT_TYPE.officesupplies_status,
{
labelField: 'extend1',
},
);
}, },
}, },
}, },
{ {
field: "companyRealCount", field: 'companyRealCount',
title: "实际发放量", title: '实际发放量',
width: 100, width: 100,
showOverflow: true, showOverflow: true,
slots: { slots: {
default: ({ row }) => { default: ({ row }) => {
return useRender.renderText(row.companyRealCount, "个"); return useRender.renderText(row.companyRealCount, '个');
}, },
}, },
}, },
{ field: "time", title: "申请时间", width: 150 }, { field: 'remarks', title: '申请原因', minWidth: 150 },
] { field: 'time', title: '申请时间', width: 150 },
{
field: 'num',
title: '审核数量',
width: 200,
fixed: 'right',
slots: { default: 'edit_num' },
},
{
field: 'operate',
title: '操作',
width: 110,
fixed: 'right',
slots: { default: 'operate' },
},
];
}
export function getDoneColumns(params: any = {}): VxeGridPropTypes.Columns {
return [
{ type: 'seq', width: 50, align: 'center', fixed: 'left' },
{
field: 'status',
title: '申请状态',
width: 120,
slots: {
default: ({ row }) => {
return useRender.renderDict(
row.status,
DICT_TYPE.officesupplies_status,
{
labelField: 'extend1',
},
);
},
},
},
{ field: 'applyName', title: '申请人', width: 120 },
{ field: 'officeName', title: '申请物品', minWidth: 150 },
{
field: 'model',
title: '物品型号',
minWidth: 150,
},
{
field: 'count',
title: '申请数量',
minWidth: 80,
slots: {
default: ({ row }) => {
return `${row.count}`;
},
},
},
{ field: 'remarks', title: '申请原因', width: 200 },
{
field: 'status',
title: '申请状态',
width: 120,
slots: {
default: ({ row }) => {
return useRender.renderDict(
row.status,
DICT_TYPE.officesupplies_status,
{
labelField: 'extend1',
},
);
},
},
},
{
field: 'companyRealCount',
title: '实际发放量',
width: 100,
showOverflow: true,
slots: {
default: ({ row }) => {
return useRender.renderText(row.companyRealCount, '个');
},
},
},
{ field: 'time', title: '申请时间', width: 150 },
];
} }
export function getFormSchema(params: any = {}) { export function getFormSchema(params: any = {}) {
const { chooseUserModalApi } = params;
const { chooseUserModalApi } = params const data = getDictOptions(DICT_TYPE.officesupplies_status);
let data = getDictOptions(DICT_TYPE.officesupplies_status);
data.forEach((item) => { data.forEach((item) => {
item.label = item.extend1; item.label = item.extend1;
}); });
return { return {
initialForm: { initialForm: {},
},
columns: { columns: {
status: { status: {
title: '申请状态', title: '申请状态',
@ -83,8 +178,8 @@ export function getFormSchema(params: any = {}) {
class: 'min-w-[180px]', class: 'min-w-[180px]',
allowClear: true, allowClear: true,
dict: dict({ dict: dict({
data: data data,
}) }),
}, },
autoSearchTrigger: 'enter', autoSearchTrigger: 'enter',
show: true, show: true,
@ -126,6 +221,5 @@ export function getFormSchema(params: any = {}) {
show: true, show: true,
}, },
}, },
} };
} }

View File

@ -1,12 +1,251 @@
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import Apis from '#/api';
import { useVxeTable } from '#/hooks/vxeTable';
import { getColumns, getDoneColumns } from './crud.tsx';
const { xGridRef, triggerProxy, gridProps } = useVxeTable({ ref: 'xGridRef' });
const { xGridRef: xGrid2Ref, triggerProxy: triggerProxy2 } = useVxeTable({
ref: 'xGrid2Ref',
});
const title = ref('1');
const form2Ref = ref();
const form2Binding = ref({
col: { span: 24 },
columns: {},
});
const isConfirmLoading = ref(false);
const selectRow = ref<any>({});
const [TemporaryModal, temporaryModalApi] = useVbenModal({
onOpenChange(isOpen: boolean) {
if (isOpen) {
isConfirmLoading.value = false;
}
},
async onConfirm() {
// await
handleAudit(selectRow.value, 'rejectConfirm');
},
});
/** Hooks - 表格 */
const gridOptions = reactive(
gridProps({
height: '100%',
columns: getColumns(),
proxyConfig: {
autoLoad: true,
ajax: {
query: ({ page }) => {
return Apis.officeSuppliesApply.get_page({
params: { pageNum: page.currentPage, pageSize: page.pageSize },
});
},
},
},
pagerConfig: {
enabled: true,
},
toolbarConfig: {
enabled: false,
},
}),
);
/** Hooks - 表格 */
const grid2Options = reactive(
gridProps({
height: '100%',
columns: getDoneColumns(),
proxyConfig: {
autoLoad: true,
ajax: {
query: ({ page }) => {
return Apis.officeSuppliesApply.get_page({
params: {
pageNum: page.currentPage,
pageSize: page.pageSize,
status: 'officeSuppliesAudit',
},
});
},
},
},
pagerConfig: {
enabled: true,
},
toolbarConfig: {
enabled: false,
},
}),
);
async function handleAudit(
row: any,
type: 'access' | 'reject' | 'rejectConfirm',
) {
selectRow.value = row;
let form = {
guid: row.guid,
status: '', // departmentAudit
unitCount: null,
companyApprovalCount: null,
companyRealCount: null,
flag: '',
};
// console.log(row);
// if (row.status === 'start') {
// form.status = 'departmentAudit';
// form.unitCount = row.num;
// }
// if (row.status === 'departmentAudit') {
// form.status = 'officeAudit';
// form.companyApprovalCount = row.num;
// }
// if (row.status === 'officeAudit') {
// form.status = 'officeSuppliesAudit';
// form.companyRealCount = row.num;
// }
// if (row.status === 'officeSuppliesAudit') {
// form.status = 'end';
// }
form.status = row.status;
form.unitCount = row.num;
form.companyApprovalCount = row.num;
form.companyRealCount = row.num;
console.log('审批表单', form);
if (
type === 'access' &&
!form.unitCount &&
!form.companyApprovalCount &&
!form.companyRealCount
) {
message.error('请输入审核或发放数量');
return;
}
form = { ...row, ...form };
if (type === 'reject') {
form.status = 'start';
form2Binding.value.columns = {};
title.value = '审核拒绝';
form2Binding.value.columns = {
comment: {
title: '',
key: 'comment',
col: { span: 24 },
colon: false,
component: {
name: 'a-textarea',
vModel: 'value',
autoSize: { minRows: 4, maxRows: 6 },
placeholder: '请输入',
},
},
};
temporaryModalApi.open();
return;
}
if (type === 'rejectConfirm') {
form.flag = 'rollback';
await Apis.officeSuppliesApply.post_audit({
data: [form, ...form2Ref.value.form],
});
message.success('操作成功');
triggerProxy('reload');
triggerProxy2('reload');
row.num = null;
return;
}
if (type === 'access') {
form.flag = 'submit';
await Apis.officeSuppliesApply.post_audit({ data: [form] });
message.success('操作成功');
triggerProxy('reload');
triggerProxy2('reload');
row.num = null;
}
}
onMounted(() => {});
</script>
<template> <template>
<Page contentClass="h-full flex flex-col"> <Page content-class="h-full flex flex-col">
<a-space direction="vertical" size="small"> <TemporaryModal
<a-card title="待办" size="small"> :confirm-loading="isConfirmLoading"
:loading="isConfirmLoading"
:title="title"
class="w-[600px]"
content-class="min-h-0"
>
<fs-form ref="form2Ref" v-bind="form2Binding" />
</TemporaryModal>
<a-space class="flex h-full flex-col" direction="vertical" size="small">
<a-card
:body-style="{ flex: '1' }"
class="flex h-full flex-col"
size="small"
title="待办"
>
<vxe-grid ref="xGridRef" v-bind="gridOptions"> <vxe-grid ref="xGridRef" v-bind="gridOptions">
<template #toolbar_buttons> </template> <template #toolbar_buttons> </template>
<template #edit_num="{ row }">
<a-input-number
v-model:value="row.num"
:addon-after="row.units || '个'"
:min="1"
placeholder="审核数量"
/>
</template>
<template #operate="{ row }">
<a-space>
<a-button
size="small"
type="text"
@click="handleAudit(row, 'access')"
>
通过
</a-button>
<a-button
danger
size="small"
type="text"
@click="handleAudit(row, 'reject')"
>
拒绝
</a-button>
</a-space>
</template>
</vxe-grid> </vxe-grid>
</a-card> </a-card>
<a-card title="已办" size="small" contentClass=""> <a-card
:body-style="{ flex: '1' }"
class="flex h-full flex-col"
size="small"
title="已办"
>
<vxe-grid ref="xGrid2Ref" v-bind="grid2Options"> <vxe-grid ref="xGrid2Ref" v-bind="grid2Options">
<template #toolbar_buttons> </template> <template #toolbar_buttons> </template>
</vxe-grid> </vxe-grid>
@ -15,60 +254,8 @@
</Page> </Page>
</template> </template>
<script setup lang="ts"> <style scoped>
import { defineComponent, ref, reactive, onMounted } from 'vue'; :deep(.ant-space-item) {
import { type VxeGridProps } from 'vxe-table' flex: 1;
import { Page, useVbenModal } from '@vben/common-ui'; }
import { useVxeTable } from '#/hooks/vxeTable'; </style>
import Apis from '#/api'
import { getColumns } from './crud.tsx';
const { xGridRef, triggerProxy, gridProps } = useVxeTable({ ref: 'xGridRef' });
const { xGridRef:xGrid2Ref, triggerProxy:triggerProxy2 } = useVxeTable({ ref: 'xGrid2Ref' });
/** Hooks - 表格 */
const gridOptions = reactive(gridProps({
columns: getColumns(),
proxyConfig: {
autoLoad: true,
ajax: {
query: ({ page }) => {
return Apis.officeSuppliesApply.get_page({ params: { pageNum: page.currentPage, pageSize: page.pageSize, } })
}
},
},
pagerConfig: {
enabled: true
},
toolbarConfig: {
enabled: false
},
}));
/** Hooks - 表格 */
const grid2Options = reactive(gridProps({
columns: getColumns(),
proxyConfig: {
autoLoad: true,
ajax: {
query: ({ page }) => {
return Apis.ccsq.get_donePage({ params: { pageNum: page.currentPage, pageSize: page.pageSize, } })
}
},
},
pagerConfig: {
enabled: true
},
toolbarConfig: {
enabled: false
},
}));
onMounted(() => {
})
</script>
<style></style>

View File

@ -1,82 +1,79 @@
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref } from "vue"; import type { VxeGridPropTypes, VxeTablePropTypes } from 'vxe-table';
import { useVbenModal } from "@vben/common-ui";
import { message } from "ant-design-vue"; import { reactive, ref } from 'vue';
import { useVxeTable } from "#/hooks/vxeTable";
import type { VxeGridPropTypes, VxeTablePropTypes } from "vxe-table"; import { useVbenModal } from '@vben/common-ui';
import Apis from "#/api"; import { MdiExport } from '@vben/icons';
import { useRender } from "#/hooks/useRender";
import { MdiExport } from "@vben/icons"; import { message, Modal } from 'ant-design-vue';
import { Modal } from "ant-design-vue"; import Big from 'big.js';
import { getUnitData } from "#/common/unit";
import Big from "big.js"; import Apis from '#/api';
import { getUnitData } from '#/common/unit';
import { useRender } from '#/hooks/useRender';
import { useVxeTable } from '#/hooks/vxeTable';
const emit = defineEmits<{ const emit = defineEmits<{
(e: "success"): void; (e: 'confirm', row: any[]): any[];
(e: "rowClick", row: any): any; (e: 'rowClick', row: any): any;
(e: "confirm", row: any[]): any[]; (e: 'success'): void;
}>(); }>();
const { xGridRef, triggerProxy, gridProps } = useVxeTable({ ref: "xGridRef" }); const { xGridRef, triggerProxy, gridProps } = useVxeTable({ ref: 'xGridRef' });
let isConfirmLoading = ref(false); const isConfirmLoading = ref(false);
let unitId = ref(); const unitId = ref();
let treeData = ref<any>([]) const treeData = ref<any>([]);
const data = ref({ const data = ref({
userIds: [], userIds: [],
}); });
const activeKey = ref('1');
const searchRef = ref()
const idToNameMap = ref<any>({});
function getColumns(_params: any = {}): VxeGridPropTypes.Columns { function getColumns(_params: any = {}): VxeGridPropTypes.Columns {
return [ return [
{ type: "seq", width: 50, fixed: "left" }, { type: 'seq', width: 50, fixed: 'left' },
{ {
field: "officeName", field: 'officeName',
title: "办公用名称", title: '办公用名称',
minWidth: 180, minWidth: 180,
slots: { slots: {
default: ({ row }) => { default: ({ row }) => {
if (row.model) { if (row.model) {
return useRender.renderText(row.officeName, "(" + row.model + ")"); return useRender.renderText(row.officeName, `(${row.model})`);
} }
return useRender.renderText(row.officeName, ""); return useRender.renderText(row.officeName, '');
}, },
}, },
}, },
{ {
field: "count", field: 'count',
title: "采购数量", title: '采购数量',
minWidth: 180, minWidth: 180,
slots: { slots: {
default: ({ row }) => { default: ({ row }) => {
return useRender.renderText(row.count, row.units || "个"); return useRender.renderText(row.count, row.units || '个');
}, },
}, },
}, },
{ {
field: "price", field: 'price',
title: "单价", title: '单价',
minWidth: 180, minWidth: 180,
slots: { slots: {
default: ({ row }) => { default: ({ row }) => {
return useRender.renderText(row.price, "元"); return useRender.renderText(row.price, '元');
}, },
}, },
}, },
{ {
field: "priceSum", field: 'priceSum',
title: "预计花费", title: '预计花费',
minWidth: 180, minWidth: 180,
slots: { slots: {
default: ({ row }) => { default: ({ row }) => {
return useRender.renderText(row.priceSum, "元"); return useRender.renderText(row.priceSum, '元');
}, },
}, },
}, },
@ -86,52 +83,57 @@ function getColumns(_params: any = {}): VxeGridPropTypes.Columns {
/** Hooks - 表格 */ /** Hooks - 表格 */
const gridOptions = reactive( const gridOptions = reactive(
gridProps({ gridProps({
height: "500px", height: '500px',
columns: getColumns(), columns: getColumns(),
proxyConfig: { proxyConfig: {
autoLoad: true, autoLoad: true,
ajax: { ajax: {
query: async ({ page }) => { query: async ({ page }) => {
const data = await Apis.officeSuppliesApplySum.get_list({
let data = await Apis.officeSuppliesApplySum.get_list({
params: { params: {
pageNum: page.currentPage, pageSize: page.pageSize, pageNum: page.currentPage,
unitId: unitId.value pageSize: page.pageSize,
} unitId: unitId.value,
}) },
data.rows.forEach((item) => {
item.priceSum = Big(item.price||0).times(item.count||0).toNumber();
}); });
return data data.rows.forEach((item) => {
} item.priceSum = Big(item.price || 0)
} .times(item.count || 0)
.toNumber();
});
return data;
},
},
}, },
showFooter: true, showFooter: true,
pagerConfig: { pagerConfig: {
enabled: false, enabled: false,
}, },
footerMethod: (e) => footerMethod(e), footerMethod: (e) => footerMethod(e),
}) }),
); );
/** /**
* 表格计算规则 * 表格计算规则
* @param param0 * @param param0
*/ */
const footerMethod: VxeTablePropTypes.FooterMethod<any> = ({ columns, data }) => { const footerMethod: VxeTablePropTypes.FooterMethod<any> = ({
columns,
data,
}) => {
const footerData = [ const footerData = [
columns.map((column, _columnIndex) => { columns.map((column, _columnIndex) => {
if (_columnIndex === 0) { if (_columnIndex === 0) {
return "总价"; return '总价';
} }
if (["priceSum"].includes(column.field)) { if (['priceSum'].includes(column.field)) {
return sumNum(data, column.field) + " 元"; return `${sumNum(data, column.field)}`;
} }
// if (['tradeType', 'companyName'].includes(column.field)) { // if (['tradeType', 'companyName'].includes(column.field)) {
// return ''; // return '';
// } // }
return ""; return '';
}), }),
]; ];
return footerData; return footerData;
@ -145,7 +147,7 @@ const footerMethod: VxeTablePropTypes.FooterMethod<any> = ({ columns, data }) =>
const sumNum = (list: any[], field: string) => { const sumNum = (list: any[], field: string) => {
let count = null; let count = null;
list.forEach((item) => { list.forEach((item) => {
if (typeof item[field] == "number") { if (typeof item[field] === 'number') {
if (count == null) { if (count == null) {
count = new Big(0); count = new Big(0);
} }
@ -155,7 +157,6 @@ const sumNum = (list: any[], field: string) => {
return count; return count;
}; };
/** /**
* 办公用品批量选择确认事件 * 办公用品批量选择确认事件
* @param rows * @param rows
@ -163,13 +164,13 @@ const sumNum = (list: any[], field: string) => {
function handleChooseOfficeConfirm(rows) { function handleChooseOfficeConfirm(rows) {
for (const row of rows) { for (const row of rows) {
row.count = 1; row.count = 1;
row.remarks = ""; row.remarks = '';
} }
const $grid = xGridRef.value; const $grid = xGridRef.value;
// //
if ($grid) { if ($grid) {
$grid.remove(); $grid.remove();
$grid.insert(rows).then(({ row }) => { }); $grid.insert(rows).then(({ row }) => {});
} }
} }
@ -177,42 +178,41 @@ function handleExport() {
const $grid = xGridRef.value; const $grid = xGridRef.value;
if ($grid) { if ($grid) {
$grid.exportData({ $grid.exportData({
type: "xlsx", type: 'xlsx',
}); });
message.success("导出成功"); message.success('导出成功');
} }
} }
const [BaseModal, baseModalApi] = useVbenModal({ const [BaseModal, baseModalApi] = useVbenModal({
async onOpenChange(isOpen: boolean) { async onOpenChange(isOpen: boolean) {
if (isOpen) { if (isOpen) {
isConfirmLoading.value = false isConfirmLoading.value = false;
treeData.value = await getUnitData() treeData.value = await getUnitData();
} }
}, },
onConfirm() { onConfirm() {
try { try {
isConfirmLoading.value = true isConfirmLoading.value = true;
const res = xGridRef.value?.getTableData(); const res = xGridRef.value?.getTableData();
const data = res?.fullData || []; const data = res?.fullData || [];
console.log(data); console.log(data);
data.forEach((item) => { data.forEach((item) => {
item.officeName = item.name || ""; item.officeName = item.name || '';
}); });
Modal.confirm({ Modal.confirm({
title: "提示", title: '提示',
content: `是否确认入库以上办公用品?`, content: `是否确认入库以上办公用品?`,
onOk: async () => { onOk: async () => {
try { try {
data.forEach((item) => { data.forEach((item) => {
item.status = "in"; item.status = 'in';
}); });
await Apis.inOrOut.post_saveBatch({ data: data }); await Apis.inOrOut.post_saveBatch({ data });
emit("success"); emit('success');
baseModalApi.close(); baseModalApi.close();
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -220,7 +220,7 @@ const [BaseModal, baseModalApi] = useVbenModal({
}, },
}); });
} finally { } finally {
isConfirmLoading.value = false isConfirmLoading.value = false;
} }
}, },
@ -230,12 +230,16 @@ const [BaseModal, baseModalApi] = useVbenModal({
}); });
</script> </script>
<template> <template>
<BaseModal :title="'办公用品审批通过的采购汇总单'" :showConfirmButton="false" cancelText="关闭"> <BaseModal
<div class="h-full flex flex-col"> :show-confirm-button="false"
cancel-text="关闭"
title="办公用品审批通过的采购汇总单"
>
<div class="flex h-full flex-col">
<VxeGrid ref="xGridRef" v-bind="gridOptions"> <VxeGrid ref="xGridRef" v-bind="gridOptions">
<template #toolbar_buttons> <template #toolbar_buttons>
<vben-button variant="primary" @click="handleExport()"> <vben-button variant="primary" @click="handleExport()">
<MdiExport class="text-lg mr-0.5" /> <MdiExport class="mr-0.5 text-lg" />
导出 导出
</vben-button> </vben-button>
<!-- <a-tree-select v-model:value="unitId" show-search style="width: 200px" <!-- <a-tree-select v-model:value="unitId" show-search style="width: 200px"
@ -255,7 +259,9 @@ const [BaseModal, baseModalApi] = useVbenModal({
<template #supplies_default_slot="scope"> <template #supplies_default_slot="scope">
<div> <div>
{{ scope.row.name ? scope.row.name + "" + scope.row.model + "" : "" }} {{
scope.row.name ? `${scope.row.name}${scope.row.model}` : ''
}}
</div> </div>
</template> </template>
</VxeGrid> </VxeGrid>

View File

@ -1,14 +1,17 @@
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, ref } from "vue"; import { nextTick, ref } from 'vue';
import { useVbenModal } from "@vben/common-ui";
import Apis from "#/api"; import { useVbenModal } from '@vben/common-ui';
import { message } from "ant-design-vue";
import { message } from 'ant-design-vue';
import Apis from '#/api';
const emit = defineEmits<{ const emit = defineEmits<{
(e: "success"): void; (e: 'success'): void;
}>(); }>();
let isConfirmLoading = ref(false); const isConfirmLoading = ref(false);
const data = ref({ const data = ref({
isUpdate: false, isUpdate: false,
@ -18,63 +21,60 @@ const formRef = ref();
const formBinding = ref({ const formBinding = ref({
col: { span: 24 }, col: { span: 24 },
labelCol: { style: { width: "120px" } }, labelCol: { style: { width: '120px' } },
initialForm: {}, initialForm: {},
columns: { columns: {
name: { name: {
title: "用品名称", title: '用品名称',
key: "name", key: 'name',
component: { component: {
name: "a-input", name: 'a-input',
vModel: "value", vModel: 'value',
allowClear: true, allowClear: true,
}, },
rules: [{ required: true, message: "请输入办公用品名称" }], rules: [{ required: true, message: '请输入办公用品名称' }],
}, },
model: { model: {
title: "型号", title: '型号',
key: "model", key: 'model',
component: { component: {
name: "a-input", name: 'a-input',
vModel: "value", vModel: 'value',
allowClear: true, allowClear: true,
}, },
}, },
qualityStandard: { qualityStandard: {
title: "质量标准", title: '质量标准',
key: "qualityStandard", key: 'qualityStandard',
component: { component: {
name: "a-input", name: 'a-input',
vModel: "value", vModel: 'value',
allowClear: true, allowClear: true,
}, },
}, },
price: { price: {
title: "单价", title: '单价',
key: "price", key: 'price',
component: { component: {
name: "a-input-number", name: 'a-input-number',
vModel: "value", vModel: 'value',
allowClear: true, allowClear: true,
min: 0, min: 0,
addonAfter: "元", addonAfter: '元',
placeholder: "请输入办公用品单价", placeholder: '请输入办公用品单价',
}, },
rules: [{ required: true, message: "请输入办公用品单价" }], rules: [{ required: true, message: '请输入办公用品单价' }],
}, },
remarks: { remarks: {
title: "备注", title: '备注',
key: "remarks", key: 'remarks',
component: { component: {
name: "a-textarea", name: 'a-textarea',
vModel: "value", vModel: 'value',
allowClear: true, allowClear: true,
}, },
}, },
}, },
doSubmit(context: any) {
console.log(context);
},
}); });
const [Modal, modalApi] = useVbenModal({ const [Modal, modalApi] = useVbenModal({
@ -90,19 +90,19 @@ const [Modal, modalApi] = useVbenModal({
} }
}, },
async onConfirm() { async onConfirm() {
console.info("onConfirm"); console.info('onConfirm');
try { try {
await formRef.value!.submit(); await formRef.value!.submit();
isConfirmLoading.value = true; isConfirmLoading.value = true;
let form = formRef.value!.form; const form = formRef.value!.form;
await Apis.officeSuppliesList.post_saveBatch({ data: [form] }); await Apis.officeSuppliesList.post_saveBatch({ data: [form] });
modalApi.close(); modalApi.close();
message.success("保存成功"); message.success('保存成功');
emit("success"); emit('success');
} catch (error) { } catch (error) {
console.log(error); console.log(error);
message.error("保存失败"); message.error('保存失败');
} finally { } finally {
isConfirmLoading.value = false; isConfirmLoading.value = false;
} }
@ -114,10 +114,10 @@ const [Modal, modalApi] = useVbenModal({
</script> </script>
<template> <template>
<Modal <Modal
:title="data.isUpdate ? '编辑办公用品' : '新增办公用品'"
:loading="isConfirmLoading"
:confirm-loading="isConfirmLoading" :confirm-loading="isConfirmLoading"
:loading="isConfirmLoading"
:title="data.isUpdate ? '编辑办公用品' : '新增办公用品'"
> >
<fs-form ref="formRef" v-bind="formBinding"> </fs-form> <fs-form ref="formRef" v-bind="formBinding" />
</Modal> </Modal>
</template> </template>

View File

@ -1,101 +1,107 @@
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref } from "vue"; import type { VxeGridPropTypes } from 'vxe-table';
import { useVbenModal } from "@vben/common-ui";
import { message } from "ant-design-vue"; import { reactive, ref } from 'vue';
import { useVxeTable } from "#/hooks/vxeTable";
import type { VxeGridPropTypes } from "vxe-table"; import { useVbenModal } from '@vben/common-ui';
import Apis from "#/api";
import { useRender } from "#/hooks/useRender"; import { Modal } from 'ant-design-vue';
import { MdiAdd } from "@vben/icons";
import { Modal } from "ant-design-vue"; import Apis from '#/api';
import { getUnitData } from "#/common/unit"; import { getUnitData } from '#/common/unit';
import chooseOfficeModal from "../inventory/choose-office-modal.vue"; import { useRender } from '#/hooks/useRender';
import { useVxeTable } from '#/hooks/vxeTable';
import chooseOfficeModal from '../inventory/choose-office-modal.vue';
const emit = defineEmits<{
(e: 'confirm', row: any[]): any[];
(e: 'rowClick', row: any): any;
(e: 'success'): void;
}>();
const [ChooseOfficeModal, chooseOfficeModalApi] = useVbenModal({ const [ChooseOfficeModal, chooseOfficeModalApi] = useVbenModal({
connectedComponent: chooseOfficeModal, connectedComponent: chooseOfficeModal,
}); });
const emit = defineEmits<{ const { xGridRef, triggerProxy, gridProps } = useVxeTable({ ref: 'xGridRef' });
(e: "success"): void;
(e: "rowClick", row: any): any;
(e: "confirm", row: any[]): any[];
}>();
const { xGridRef, triggerProxy, gridProps } = useVxeTable({ ref: "xGridRef" }); const isConfirmLoading = ref(false);
let isConfirmLoading = ref(false); const unitId = ref();
let unitId = ref(); const treeData = ref<any>([]);
let treeData = ref<any>([])
const data = ref({ const data = ref({
userIds: [], userIds: [],
}); });
const activeKey = ref('1'); const activeKey = ref('1');
const searchRef = ref() const searchRef = ref();
const idToNameMap = ref<any>({}); const idToNameMap = ref<any>({});
function getColumns(_params: any = {}): VxeGridPropTypes.Columns { function getColumns(_params: any = {}): VxeGridPropTypes.Columns {
return [ return [
{ type: "seq", width: 50, fixed: "left" }, { type: 'seq', width: 50, fixed: 'left' },
{ field: "applyName", title: "申请人", width: 120 }, { field: 'applyName', title: '申请人', width: 120 },
{ {
field: "applyInfo", field: 'applyInfo',
title: "申请信息", title: '申请信息',
minWidth: 200, minWidth: 200,
slots: { default: "applyInfoSlot" }, slots: { default: 'applyInfoSlot' },
}, },
{ {
field: "count", field: 'count',
title: "申请数量", title: '申请数量',
width: 100, width: 100,
showOverflow: true, showOverflow: true,
slots: { slots: {
default: ({ row }) => { default: ({ row }) => {
return useRender.renderText(row.count, row.units || "个"); return useRender.renderText(row.count, row.units || '个');
}, },
}, },
}, },
{ {
field: "unitCount", field: 'unitCount',
title: "部门审核量", title: '部门审核量',
width: 100, width: 100,
showOverflow: true, showOverflow: true,
slots: { slots: {
default: ({ row }) => { default: ({ row }) => {
return useRender.renderText(row.unitCount, row.units || "个"); return useRender.renderText(row.unitCount, row.units || '个');
}, },
}, },
}, },
{ {
field: "companyApprovalCount", field: 'companyApprovalCount',
title: "办公室审核量", title: '办公室审核量',
width: 100, width: 100,
showOverflow: true, showOverflow: true,
slots: { slots: {
default: ({ row }) => { default: ({ row }) => {
return useRender.renderText(row.companyApprovalCount, row.units || "个"); return useRender.renderText(
row.companyApprovalCount,
row.units || '个',
);
}, },
}, },
}, },
{ field: "remarks", title: "申请原因", minWidth: 150 }, { field: 'remarks', title: '申请原因', minWidth: 150 },
{ field: "time", title: "申请时间", width: 150 }, { field: 'time', title: '申请时间', width: 150 },
{ {
field: "num", field: 'num',
title: "发放数量", title: '发放数量',
width: 150, width: 100,
fixed: "right", fixed: 'right',
slots: { default: "edit_num" }, slots: { default: 'edit_num' },
}, },
{ {
field: "operate", field: 'operate',
title: "操作", title: '操作',
width: 120, width: 120,
fixed: "right", fixed: 'right',
slots: { default: "operate" }, slots: { default: 'operate' },
}, },
]; ];
} }
@ -103,7 +109,7 @@ function getColumns(_params: any = {}): VxeGridPropTypes.Columns {
/** Hooks - 表格 */ /** Hooks - 表格 */
const gridOptions = reactive( const gridOptions = reactive(
gridProps({ gridProps({
height: "500px", height: '500px',
columns: getColumns(), columns: getColumns(),
proxyConfig: { proxyConfig: {
autoLoad: true, autoLoad: true,
@ -111,21 +117,22 @@ const gridOptions = reactive(
query: ({ page }) => { query: ({ page }) => {
return Apis.officeSuppliesApply.get_page({ return Apis.officeSuppliesApply.get_page({
params: { params: {
pageNum: page.currentPage, pageSize: page.pageSize, pageNum: page.currentPage,
unitId: unitId.value pageSize: page.pageSize,
} unitId: unitId.value,
}) },
} });
} },
},
}, },
rowConfig: { rowConfig: {
isCurrent: false, isCurrent: false,
}, },
editConfig: { editConfig: {
trigger: "click", trigger: 'click',
mode: "row", mode: 'row',
}, },
}) }),
); );
/** /**
@ -133,14 +140,7 @@ const gridOptions = reactive(
*/ */
function handleCellClick({ row }) { function handleCellClick({ row }) {
console.log(row); console.log(row);
emit("rowClick", row); emit('rowClick', row);
}
/**
* 打开选择办公用品弹窗
*/
function handleOpenOfficeModal() {
chooseOfficeModalApi.open();
} }
/** /**
@ -150,13 +150,13 @@ function handleOpenOfficeModal() {
function handleChooseOfficeConfirm(rows) { function handleChooseOfficeConfirm(rows) {
for (const row of rows) { for (const row of rows) {
row.count = 1; row.count = 1;
row.remarks = ""; row.remarks = '';
} }
const $grid = xGridRef.value; const $grid = xGridRef.value;
// //
if ($grid) { if ($grid) {
$grid.remove(); $grid.remove();
$grid.insert(rows).then(({ row }) => { }); $grid.insert(rows).then(({ row }) => {});
} }
} }
@ -169,37 +169,35 @@ function handleAudit(row, type) {
} }
} }
let title = ref("选择要入库的办公用品"); const title = ref('选择要入库的办公用品');
const [BaseModal, baseModalApi] = useVbenModal({ const [BaseModal, baseModalApi] = useVbenModal({
async onOpenChange(isOpen: boolean) { async onOpenChange(isOpen: boolean) {
if (isOpen) { if (isOpen) {
isConfirmLoading.value = false isConfirmLoading.value = false;
treeData.value = await getUnitData() treeData.value = await getUnitData();
} }
}, },
onConfirm() { onConfirm() {
try { try {
isConfirmLoading.value = true isConfirmLoading.value = true;
const res = xGridRef.value?.getTableData(); const res = xGridRef.value?.getTableData();
const data = res?.fullData || []; const data = res?.fullData || [];
console.log(data);
data.forEach((item) => { data.forEach((item) => {
item.officeName = item.name || ""; item.officeName = item.name || '';
}); });
Modal.confirm({ Modal.confirm({
title: "提示", title: '提示',
content: `是否确认入库以上办公用品?`, content: `是否确认入库以上办公用品?`,
onOk: async () => { onOk: async () => {
try { try {
data.forEach((item) => { data.forEach((item) => {
item.status = "in"; item.status = 'in';
}); });
await Apis.inOrOut.post_saveBatch({ data: data }); await Apis.inOrOut.post_saveBatch({ data });
emit("success"); emit('success');
baseModalApi.close(); baseModalApi.close();
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -207,7 +205,7 @@ const [BaseModal, baseModalApi] = useVbenModal({
}, },
}); });
} finally { } finally {
isConfirmLoading.value = false isConfirmLoading.value = false;
} }
}, },
@ -218,19 +216,32 @@ const [BaseModal, baseModalApi] = useVbenModal({
</script> </script>
<template> <template>
<BaseModal :title="title"> <BaseModal :title="title">
<ChooseOfficeModal
class="w-[900px] max-w-[75vw]"
@confirm="handleChooseOfficeConfirm"
/>
<ChooseOfficeModal class="w-[900px] max-w-[75vw]" @confirm="handleChooseOfficeConfirm" /> <a-tabs v-model:active-key="activeKey" size="small">
<a-tabs v-model:activeKey="activeKey" size="small">
<a-tab-pane key="1" tab="针对审批分发"> <a-tab-pane key="1" tab="针对审批分发">
<div class="h-full flex flex-col"> <div class="flex h-full flex-col">
<VxeGrid ref="xGridRef" v-bind="gridOptions" @cell-click="handleCellClick"> <VxeGrid
ref="xGridRef"
v-bind="gridOptions"
@cell-click="handleCellClick"
>
<template #toolbar_buttons> <template #toolbar_buttons>
<a-tree-select v-model:value="unitId" show-search style="width: 200px" <a-tree-select
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" placeholder="可筛选单位" allow-clear v-model:value="unitId"
tree-default-expand-all :tree-data="treeData" tree-node-filter-prop="label" :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
@change="triggerProxy('reload')"> :tree-data="treeData"
allow-clear
placeholder="可筛选单位"
show-search
style="width: 200px"
tree-default-expand-all
tree-node-filter-prop="label"
@change="triggerProxy('reload')"
>
<template #title="{ value: val, label }"> <template #title="{ value: val, label }">
<b v-if="val === 'parent 1-1'" style="color: #08c">sss</b> <b v-if="val === 'parent 1-1'" style="color: #08c">sss</b>
<template v-else>{{ label }}</template> <template v-else>{{ label }}</template>
@ -244,21 +255,39 @@ const [BaseModal, baseModalApi] = useVbenModal({
<template #supplies_default_slot="scope"> <template #supplies_default_slot="scope">
<div> <div>
{{ scope.row.name ? scope.row.name + "" + scope.row.model + "" : "" }} {{
scope.row.name
? `${scope.row.name}${scope.row.model}`
: ''
}}
</div> </div>
</template> </template>
<template #edit_num="{ row }"> <template #edit_num="{ row }">
<a-input-number v-model:value="row.count" :min="1" :addon-after="row.units || ''" <a-input-number
placeholder="请选择发放数量" /> v-model:value="row.count"
:addon-after="row.units || '个'"
:min="1"
placeholder="请选择发放数量"
/>
</template> </template>
<template #operate="{ row }"> <template #operate="{ row }">
<a-space> <a-space>
<a-button type="text" size="small" class="text-primary-500" @click="handleAudit(row, 'access')"> <a-button
class="text-primary-500"
size="small"
type="text"
@click="handleAudit(row, 'access')"
>
发放 发放
</a-button> </a-button>
<a-button type="text" size="small" class="text-red-500" @click="handleAudit(row, 'reject')"> <a-button
class="text-red-500"
size="small"
type="text"
@click="handleAudit(row, 'reject')"
>
驳回 驳回
</a-button> </a-button>
</a-space> </a-space>
@ -267,8 +296,5 @@ const [BaseModal, baseModalApi] = useVbenModal({
</div> </div>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
</BaseModal> </BaseModal>
</template> </template>

View File

@ -1,28 +1,20 @@
<script setup lang="ts"> <script setup lang="tsx">
import { nextTick, onMounted, ref } from 'vue'; import { nextTick, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { MdiUpload } from '@vben/icons'; import { MdiUpload } from '@vben/icons';
import { useUserStore } from '@vben/stores';
import { dict } from '@fast-crud/fast-crud'; import { dict } from '@fast-crud/fast-crud';
import { import { message, Modal, type UploadChangeParam } from 'ant-design-vue';
message, import { logger } from 'common-utils';
Modal,
type UploadChangeParam,
type UploadProps,
} from 'ant-design-vue';
import dayjs, { Dayjs } from 'dayjs'; import dayjs, { Dayjs } from 'dayjs';
import Apis from '#/api'; import Apis from '#/api';
import { useVxeTable } from '#/hooks/vxeTable';
import { DICT_TYPE, getDictOptions } from '#/utils/dict'; import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { FileUploader } from '#/utils/file'; import { FileUploader } from '#/utils/file';
import chooseUserModal from '#/views/system/user/choose-user-modal.vue'; import chooseUserModal from '#/views/system/user/choose-user-modal.vue';
const { xGridRef, gridProps, triggerProxy } = useVxeTable({ ref: 'xGridRef' });
const fileUploader = new FileUploader({}); const fileUploader = new FileUploader({});
const [ChooseUserModal, chooseUserModalApi] = useVbenModal({ const [ChooseUserModal, chooseUserModalApi] = useVbenModal({
@ -31,16 +23,12 @@ const [ChooseUserModal, chooseUserModalApi] = useVbenModal({
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const id = route.params.id; const id = ref(route.params.id);
const showHelpTip = ref(false);
const containerRef = ref(); const containerRef = ref();
const formRef = ref(); const formRef = ref();
const form2Ref = ref();
const isLoading = ref(false); const isLoading = ref(false);
function handleUpdateFormattedValue() { function handleUpdateFormattedValue() {
const { starttime, endtime } = formRef.value.form; const { starttime, endtime } = formRef.value.form;
@ -54,6 +42,7 @@ function handleUpdateFormattedValue() {
} }
} }
const selectUsers = ref<any>([]);
const disabledDate = (current: Dayjs) => { const disabledDate = (current: Dayjs) => {
// Can not select days before today and today // Can not select days before today and today
const form = formRef.value.form; const form = formRef.value.form;
@ -80,7 +69,7 @@ const formBinding = ref({
key: 'taskType', key: 'taskType',
col: { span: 12 }, col: { span: 12 },
component: { component: {
name: 'fs-dict-select', name: 'fs-dict-radio',
vModel: 'value', vModel: 'value',
dict: dict({ dict: dict({
data: getDictOptions(DICT_TYPE.supervise_task_type), data: getDictOptions(DICT_TYPE.supervise_task_type),
@ -93,14 +82,13 @@ const formBinding = ref({
key: 'urgentDegree', key: 'urgentDegree',
col: { span: 12 }, col: { span: 12 },
component: { component: {
name: 'fs-dict-select', name: 'fs-dict-radio',
vModel: 'value', vModel: 'value',
dict: dict({ dict: dict({
data: getDictOptions(DICT_TYPE.supervise_emergency_level), data: getDictOptions(DICT_TYPE.supervise_emergency_level),
}), }),
}, },
}, },
starttime: { starttime: {
title: '开始时间', title: '开始时间',
key: 'starttime', key: 'starttime',
@ -113,7 +101,7 @@ const formBinding = ref({
// disabledDate: (current) => current && current < dayjs().endOf("day"), // disabledDate: (current) => current && current < dayjs().endOf("day"),
format: 'YYYY-MM-DD HH:mm', format: 'YYYY-MM-DD HH:mm',
valueFormat: 'YYYY-MM-DD HH:mm', valueFormat: 'YYYY-MM-DD HH:mm',
onChange: (e) => { onChange: () => {
handleUpdateFormattedValue(); handleUpdateFormattedValue();
}, },
}, },
@ -129,7 +117,7 @@ const formBinding = ref({
allowClear: false, allowClear: false,
showTime: { format: 'HH:mm' }, showTime: { format: 'HH:mm' },
disabledDate, disabledDate,
onChange: (e) => { onChange: () => {
handleUpdateFormattedValue(); handleUpdateFormattedValue();
}, },
format: 'YYYY-MM-DD HH:mm', format: 'YYYY-MM-DD HH:mm',
@ -146,7 +134,7 @@ const formBinding = ref({
allowClear: false, allowClear: false,
showTime: { format: 'HH:mm' }, showTime: { format: 'HH:mm' },
disabledDate, disabledDate,
onChange: (e) => { onChange: () => {
handleUpdateFormattedValue(); handleUpdateFormattedValue();
}, },
format: 'YYYY-MM-DD HH:mm', format: 'YYYY-MM-DD HH:mm',
@ -161,6 +149,7 @@ const formBinding = ref({
component: { component: {
name: 'a-textarea', name: 'a-textarea',
vModel: 'value', vModel: 'value',
autoSize: { minRows: 4, maxRows: 6 },
}, },
rules: [{ required: true, message: '请输入任务内容' }], rules: [{ required: true, message: '请输入任务内容' }],
}, },
@ -171,6 +160,7 @@ const formBinding = ref({
component: { component: {
name: 'a-textarea', name: 'a-textarea',
vModel: 'value', vModel: 'value',
autoSize: { minRows: 4, maxRows: 6 },
}, },
rules: [{ required: true, message: '请输入任务进度' }], rules: [{ required: true, message: '请输入任务进度' }],
}, },
@ -178,13 +168,35 @@ const formBinding = ref({
title: '相关附件', title: '相关附件',
key: 'fileList', key: 'fileList',
}, },
people: {
title: '相关执行人',
key: 'people',
component: {
name: 'a-select',
vModel: 'value',
open: false,
mode: 'multiple',
onClick: () => {
chooseUserModalApi.setData({
title: '选择执行人',
limitMultipleNum: 10,
userIds: selectUsers.value.map((row) => row.value) || [],
});
chooseUserModalApi.open();
},
onChange: () => {
// selectUsers
selectUsers.value = formRef.value.form.people.map((item1) => {
const value = item1.split('-')[1];
return selectUsers.value.find((item2) => item2.value === value);
});
},
},
// rules: [{ required: true, message: '' }],
},
}, },
}); });
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
return false;
};
function handleBack() { function handleBack() {
Modal.confirm({ Modal.confirm({
title: '提示', title: '提示',
@ -215,7 +227,7 @@ function handleDelete() {
okType: 'danger', okType: 'danger',
onOk: async () => { onOk: async () => {
await Apis.meeting.post_deletes({ await Apis.meeting.post_deletes({
params: { ids: currData.value.guid }, params: { ids: id.value },
}); });
message.success('删除成功'); message.success('删除成功');
back(); back();
@ -228,53 +240,97 @@ function handleDelete() {
* @param rows * @param rows
*/ */
function handleChooseUserConfirm(rows) { function handleChooseUserConfirm(rows) {
console.log('[ rows ] >', rows); rows.forEach((row) => {
rows.forEach((element) => {}); row.label = row.EMPLOYEE_NAME;
const $grid = xGridRef.value; row.value = row.ACCOUNT_ID;
// });
if ($grid) { formRef.value.setFormData({
$grid.remove(); people: rows.map((row) => `${row.label}-${row.value}`),
$grid.insert(rows).then(({ row }) => {}); });
} else { selectUsers.value = rows;
console.error('xGridRef不存在');
}
} }
async function handleSubmit() { async function handleSave() {
isLoading.value = true; isLoading.value = true;
try { try {
const form = formRef.value.form; await formRef.value.submit();
// await formRef.value.submit()
console.log(formRef.value);
const userStore = useUserStore();
let newForm = {}; const newForm = formRef.value.form;
// //
const fileList = formRef.value.form.fileList; const fileList = formRef.value.form.fileList;
let files: any = []; let files: any = [];
if (fileList && fileList.length > 0) { if (fileList && fileList.length > 0) {
files = await fileUploader.upload(fileList, { source: 'erp' }); files = await fileUploader.upload(fileList, { source: 'erp' });
} }
console.log(files);
if (files) { if (files) {
newForm.fileUuid = (files.map((item) => item.fileUuid) || []).join(','); newForm.fileUuid = (files.map((item) => item.fileUuid) || []).join(',');
} }
newForm = Object.assign({}, formRef.value.form, newForm);
delete newForm.fileList; delete newForm.fileList;
console.log(newForm); const data = await Apis.supervise.post_save({
data: newForm,
});
id.value = data.guid;
await Apis.supervise.post_save({ data: newForm }).then((data) => { message.success('保存成功');
message.success('提交成功'); Modal.confirm({
back(); title: '提示',
content: '保存成功!是否立即提交?',
onOk: () => {
handleSubmit();
},
onCancel: () => {
back();
},
}); });
} catch (error) { } catch (error) {
logger.error('立项保存失败', error);
} finally {
isLoading.value = false;
}
}
async function handleSubmit() {
isLoading.value = true;
try {
await formRef.value.submit();
const newForm: any = formRef.value.form;
if (!newForm.people || newForm.people.length === 0) {
message.error('请先选择相关执行人');
return;
}
await Apis.supervise.post_audit({
params: {
guid: id.value,
},
});
if (newForm.people && newForm.people.length > 0) {
const people = selectUsers.value.map((item) => {
return {
dwbm: item.ORG_ID,
dwmc: item.ORG_NAME,
principalId: item.ACCOUNT_ID,
principal: item.EMPLOYEE_NAME,
};
});
await Apis.feedback.post_save({
params: { guid: newForm.guid },
data: people,
});
}
message.success('提交成功');
back();
} catch (error) {
logger.error('立项提交失败', error);
message.error('提交失败,请稍候再试'); message.error('提交失败,请稍候再试');
console.log(error);
} finally { } finally {
isLoading.value = false; isLoading.value = false;
} }
@ -284,14 +340,11 @@ const currData = ref({});
onMounted(async () => { onMounted(async () => {
isLoading.value = true; isLoading.value = true;
console.log(id);
try { try {
if (id) { if (id.value) {
let data = await Apis.supervise.get_page({ params: { guid: id } }); let data = await Apis.supervise.get_page({ params: { guid: id.value } });
data = data.rows[0]; data = data.rows[0];
console.log(data);
currData.value = data; currData.value = data;
nextTick(() => { nextTick(() => {
formRef.value.setFormData(data); formRef.value.setFormData(data);
@ -299,7 +352,6 @@ onMounted(async () => {
if (data.fileUuid) { if (data.fileUuid) {
const files = await fileUploader.select(data.fileUuid); const files = await fileUploader.select(data.fileUuid);
console.log(files);
nextTick(() => { nextTick(() => {
formRef.value.setFormData({ formRef.value.setFormData({
fileList: files, fileList: files,
@ -308,7 +360,7 @@ onMounted(async () => {
} }
} }
} catch (error) { } catch (error) {
console.log(error); logger.error('当前立项信息不存在', error);
Modal.error({ Modal.error({
title: '提示', title: '提示',
@ -336,6 +388,9 @@ onMounted(async () => {
/> />
<a-spin :spinning="isLoading"> <a-spin :spinning="isLoading">
<a-space> <a-space>
<vben-button variant="primary" @click="handleSave()">
保存
</vben-button>
<vben-button variant="primary" @click="handleSubmit()"> <vben-button variant="primary" @click="handleSubmit()">
提交 提交
</vben-button> </vben-button>
@ -353,7 +408,7 @@ onMounted(async () => {
<template #form_fileList="scope"> <template #form_fileList="scope">
<a-upload <a-upload
v-model:file-list="scope.form.fileList" v-model:file-list="scope.form.fileList"
:before-upload="beforeUpload" :before-upload="() => false"
:max-count="3" :max-count="3"
accept=".pdf,.ppt,.pptx" accept=".pdf,.ppt,.pptx"
name="file" name="file"
@ -373,15 +428,4 @@ onMounted(async () => {
</Page> </Page>
</template> </template>
<style scoped> <style scoped></style>
.sortable-tree-demo .drag-btn {
cursor: move;
font-size: 12px;
text-align: center;
}
.sortable-tree-demo .vxe-body--row.sortable-ghost,
.sortable-tree-demo .vxe-body--row.sortable-chosen {
background-color: #dfecfb;
}
</style>

View File

@ -110,7 +110,7 @@ export function getColumns(params: any = {}): VxeGridPropTypes.Columns {
}, },
}, },
{ field: 'remarks', title: '备注', minWidth: 200 }, { field: 'remarks', title: '备注', minWidth: 200 },
// { title: '操作', width: 120, fixed: 'right', slots: { default: 'operate' } } { title: '操作', width: 80, fixed: 'right', slots: { default: 'operate' } },
]; ];
} }

View File

@ -0,0 +1,82 @@
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import Apis from '#/api';
import { useVxeTable } from '#/hooks/vxeTable';
import { getColumns } from '../../feedback/crud';
const { xGridRef, gridProps, triggerProxy } = useVxeTable({ ref: 'xGridRef' });
const open = ref<boolean>(false);
const title = ref('');
const currData = ref<any>({});
/** Hooks - 表格 */
const gridOptions = reactive(
gridProps({
height: '100%',
columns: getColumns(),
proxyConfig: {
autoLoad: false,
ajax: {
query: async () => {
const data = await Apis.feedback.get_page({
params: {
pageNum: 1,
pageSize: 500,
guid: currData.value.guid,
},
});
return data;
},
},
},
pagerConfig: {
enabled: true,
},
toolbarConfig: {
enabled: true,
},
}),
);
// function handleExport() {
// const $grid = xGridRef.value;
// if ($grid) {
// $grid.exportData({
// type: 'xlsx',
// });
// message.success('');
// }
// }
const openDrawer = async (data) => {
open.value = true;
title.value = data.title;
currData.value = data.record;
triggerProxy('reload');
};
const closeDrawer = () => {
open.value = false;
};
defineExpose({ open: openDrawer, close: closeDrawer });
</script>
<template>
<a-drawer
:open="open"
:title="title"
height="60vh"
placement="bottom"
@close="closeDrawer"
>
<template #extra>
<a-button @click="closeDrawer">关闭</a-button>
</template>
<vxe-grid ref="xGridRef" v-bind="gridOptions">
<template #toolbar_buttons> </template>
</vxe-grid>
</a-drawer>
</template>

View File

@ -18,12 +18,14 @@ import Apis from '#/api';
import { useVxeTable } from '#/hooks/vxeTable'; import { useVxeTable } from '#/hooks/vxeTable';
import { getColumns, getFormSchema } from './crud.tsx'; import { getColumns, getFormSchema } from './crud.tsx';
import DetailDrawer from './detail-drawer/detail-drawer.vue';
const router = useRouter(); const router = useRouter();
const isOneDay = ref(''); const isOneDay = ref('');
const searchRef = ref(); const searchRef = ref();
const detailDrawerRef = ref();
const { xGridRef, triggerProxy, gridProps } = useVxeTable({ ref: 'xGridRef' }); const { xGridRef, triggerProxy, gridProps } = useVxeTable({ ref: 'xGridRef' });
@ -87,6 +89,13 @@ function handleCellClick({ row }) {
setSelectRow(row); setSelectRow(row);
} }
function selectDetail(row) {
detailDrawerRef.value.open({
title: row.taskName,
record: row,
});
}
onMounted(() => { onMounted(() => {
triggerProxy('reload'); triggerProxy('reload');
}); });
@ -122,6 +131,8 @@ function handleDelete(row) {
<template> <template>
<Page content-class="h-full flex flex-col"> <Page content-class="h-full flex flex-col">
<DetailDrawer ref="detailDrawerRef" />
<fs-search ref="searchRef" v-bind="searchForm" /> <fs-search ref="searchRef" v-bind="searchForm" />
<div class="min-h-300px flex-1"> <div class="min-h-300px flex-1">
<vxe-grid <vxe-grid
@ -172,6 +183,18 @@ function handleDelete(row) {
<template #statusSlot="{ row }"> <template #statusSlot="{ row }">
<a-tag>{{ row.status || '待提交' }}</a-tag> <a-tag>{{ row.status || '待提交' }}</a-tag>
</template> </template>
<template #operate="{ row }">
<a-space>
<a-button
class="text-primary"
size="small"
type="text"
@click="selectDetail(row)"
>
查看
</a-button>
</a-space>
</template>
</vxe-grid> </vxe-grid>
</div> </div>
</Page> </Page>

View File

@ -1,24 +1,36 @@
import type { VxeGridPropTypes } from 'vxe-table'; import type { VxeGridPropTypes } from 'vxe-table';
import { useRender } from '#/hooks/useRender';
import dayjs from 'dayjs';
import { DICT_TYPE, getDictObj, getDictOptions } from '#/utils/dict';
import { dict } from '@fast-crud/fast-crud'; import { dict } from '@fast-crud/fast-crud';
import { unitComponentProps } from '#/common/unit';
import { useRender } from '#/hooks/useRender';
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
export const PrimaryKey = 'guid'; export const PrimaryKey = 'guid';
export function getColumns(params: any = {}): VxeGridPropTypes.Columns { export function getColumns(params: any = {}): VxeGridPropTypes.Columns {
return [ return [
{ type: 'radio', width: 40, slots: { radio: 'radio_cell' }, align: 'center', fixed: 'left' }, {
type: 'radio',
width: 40,
slots: { radio: 'radio_cell' },
align: 'center',
fixed: 'left',
},
// { type: 'expand', width: 60, slots: { content: 'expand_content' } }, // { type: 'expand', width: 60, slots: { content: 'expand_content' } },
{ {
field: 'TASK_NAME', title: '任务标题', width: 200, slots: { field: 'TASK_NAME',
default: "task-name-slot" title: '任务标题',
width: 200,
slots: {
default: 'task-name-slot',
}, },
}, },
{ {
field: 'status', title: '任务状态', width: 120, slots: { field: 'status',
default: "statusSlot" title: '任务状态',
width: 120,
slots: {
default: 'statusSlot',
}, },
}, },
{ {
@ -27,22 +39,31 @@ export function getColumns(params: any = {}): VxeGridPropTypes.Columns {
width: 100, width: 100,
slots: { slots: {
default: ({ row }) => { default: ({ row }) => {
return useRender.renderDict(row.taskType, DICT_TYPE.supervise_task_type); return useRender.renderDict(
} row.taskType,
} DICT_TYPE.supervise_task_type,
}, );
{ },
field: 'TASK_CONTENT', title: '任务内容', width: 300, slots: {
default: ({ row }) => {
return useRender.renderMultiLineText(row.TASK_CONTENT, {});
}
}, },
}, },
{ {
field: 'taskProgress', title: '任务进度', minWidth: 200, slots: { field: 'TASK_CONTENT',
title: '任务内容',
width: 300,
slots: {
default: ({ row }) => {
return useRender.renderMultiLineText(row.TASK_CONTENT, {});
},
},
},
{
field: 'taskProgress',
title: '任务进度',
minWidth: 200,
slots: {
default: ({ row }) => { default: ({ row }) => {
return useRender.renderMultiLineText(row.taskProgress, {}); return useRender.renderMultiLineText(row.taskProgress, {});
} },
}, },
}, },
{ {
@ -51,33 +72,42 @@ export function getColumns(params: any = {}): VxeGridPropTypes.Columns {
width: 100, width: 100,
slots: { slots: {
default: ({ row }) => { default: ({ row }) => {
return useRender.renderDict(row.urgentDegree, DICT_TYPE.supervise_emergency_level); return useRender.renderDict(
} row.urgentDegree,
} DICT_TYPE.supervise_emergency_level,
);
},
},
}, },
{ {
field: 'starttime', title: '开始日期', width: 120, field: 'starttime',
title: '开始日期',
width: 120,
slots: { slots: {
default: ({ row }) => { default: ({ row }) => {
return useRender.renderDate(row.starttime, 'YYYY-MM-DD'); return useRender.renderDate(row.starttime, 'YYYY-MM-DD');
} },
} },
}, },
{ {
field: 'endtime', title: '截止日期', width: 120, field: 'endtime',
title: '截止日期',
width: 120,
slots: { slots: {
default: ({ row }) => { default: ({ row }) => {
return useRender.renderDate(row.endtime, 'YYYY-MM-DD'); return useRender.renderDate(row.endtime, 'YYYY-MM-DD');
} },
} },
}, },
{ {
field: 'planFinishTime', title: '预计完成日期', width: 120, field: 'planFinishTime',
title: '预计完成日期',
width: 120,
slots: { slots: {
default: ({ row }) => { default: ({ row }) => {
return useRender.renderDate(row.planFinishTime, 'YYYY-MM-DD'); return useRender.renderDate(row.planFinishTime, 'YYYY-MM-DD');
} },
} },
}, },
{ field: 'remarks', title: '备注', minWidth: 200 }, { field: 'remarks', title: '备注', minWidth: 200 },
// { title: '操作', width: 120, fixed: 'right', slots: { default: 'operate' } } // { title: '操作', width: 120, fixed: 'right', slots: { default: 'operate' } }
@ -86,8 +116,7 @@ export function getColumns(params: any = {}): VxeGridPropTypes.Columns {
export function getFormSchema(_params: any = {}) { export function getFormSchema(_params: any = {}) {
return { return {
initialForm: { initialForm: {},
},
columns: { columns: {
taskName: { taskName: {
title: '任务名称', title: '任务名称',
@ -112,21 +141,21 @@ export function getFormSchema(_params: any = {}) {
data: [ data: [
{ {
value: '1', value: '1',
label: '未开始' label: '未开始',
}, },
{ {
value: '2', value: '2',
label: '进行中' label: '进行中',
}, },
{ {
value: '3', value: '3',
label: '已完成' label: '已完成',
}, },
{ {
value: '4', value: '4',
label: '已超时' label: '已超时',
} },
] ],
}), }),
}, },
autoSearchTrigger: 'enter', autoSearchTrigger: 'enter',
@ -141,7 +170,7 @@ export function getFormSchema(_params: any = {}) {
class: 'min-w-[180px]', class: 'min-w-[180px]',
allowClear: true, allowClear: true,
dict: dict({ dict: dict({
data: getDictOptions(DICT_TYPE.supervise_task_type) data: getDictOptions(DICT_TYPE.supervise_task_type),
}), }),
}, },
autoSearchTrigger: 'enter', autoSearchTrigger: 'enter',
@ -155,6 +184,5 @@ export function getFormSchema(_params: any = {}) {
// show: true, // show: true,
// }, // },
}, },
} };
} }

View File

@ -1,55 +1,55 @@
<script setup lang="ts"> <script setup lang="ts">
import { defineComponent, ref, computed, reactive, onMounted } from 'vue'; import { computed, onMounted, reactive, ref } from 'vue';
import { FsCrud } from '@fast-crud/fast-crud';
import { type VxeGridProps } from 'vxe-table' import { Page } from '@vben/common-ui';
import { Page, useVbenModal } from '@vben/common-ui'; import { MdiExport, MdiRadioChecked, MdiRadioUnchecked } from '@vben/icons';
import { message } from 'ant-design-vue';
import Apis from '#/api';
import { useVxeTable } from '#/hooks/vxeTable'; import { useVxeTable } from '#/hooks/vxeTable';
import { MdiAdd, MdiUpdate, MdiDelete, MdiImport, MdiExport, MdiRadioUnchecked, MdiRadioChecked } from '@vben/icons';
import { getFormSchema, getColumns } from './crud.tsx';
import { dict } from "@fast-crud/fast-crud";
import { getMonthStartAndEnd } from '#/utils/time'
import Apis from '#/api'
import dayjs from 'dayjs';
import { message } from "ant-design-vue";
import { Modal } from 'ant-design-vue';
import { unitComponentProps } from '#/common/unit'
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { useRouter } from 'vue-router' import { getColumns, getFormSchema } from './crud.tsx';
const router = useRouter(); const searchRef = ref();
const searchRef = ref()
const { xGridRef, triggerProxy, gridProps } = useVxeTable({ ref: 'xGridRef' }); const { xGridRef, triggerProxy, gridProps } = useVxeTable({ ref: 'xGridRef' });
/** Hooks - 表格 */ /** Hooks - 表格 */
const gridOptions = reactive(gridProps({ const gridOptions = reactive(
columns: getColumns(), gridProps({
proxyConfig: { columns: getColumns(),
autoLoad: false, proxyConfig: {
ajax: { autoLoad: false,
query: ({ page }) => { ajax: {
let form = searchRef.value?.formData query: ({ page }) => {
return Apis.supervise.get_huizong({ params: { pageNum: page.currentPage, pageSize: page.pageSize, ...form } }) const form = searchRef.value?.formData;
} return Apis.supervise.get_huizong({
params: {
pageNum: page.currentPage,
pageSize: page.pageSize,
...form,
},
});
},
},
}, },
}, pagerConfig: {
pagerConfig: { enabled: true,
enabled: true },
}, toolbarConfig: {
toolbarConfig: { enabled: true,
enabled: true },
}, }),
})); );
function handleExport() { function handleExport() {
const $grid = xGridRef.value; const $grid = xGridRef.value;
if ($grid) { if ($grid) {
$grid.exportData({ $grid.exportData({
type: "xlsx", type: 'xlsx',
}); });
message.success("导出成功"); message.success('导出成功');
} }
} }
@ -60,7 +60,7 @@ const selectRow: any = computed(() => {
/** 单选框选中事件 */ /** 单选框选中事件 */
const setSelectRow = (row: any) => { const setSelectRow = (row: any) => {
if (selectRow.value && selectRow.value['guid'] === row['guid']) { if (selectRow.value && selectRow.value.guid === row.guid) {
xGridRef.value?.clearRadioRow(); xGridRef.value?.clearRadioRow();
} else { } else {
xGridRef.value?.setRadioRow(row); xGridRef.value?.setRadioRow(row);
@ -73,31 +73,30 @@ function handleCellClick({ row }) {
} }
onMounted(() => { onMounted(() => {
triggerProxy('reload') triggerProxy('reload');
}) });
let searchParams = reactive({})
const searchForm = ref({ const searchForm = ref({
...getFormSchema(), ...getFormSchema(),
onSearch(context: any) { onSearch(_context: any) {
console.log(searchRef.value) triggerProxy('reload');
triggerProxy('reload')
},
onReset(context: any) {
searchParams = context.form
}, },
}); });
</script> </script>
<template> <template>
<Page contentClass="h-full flex flex-col"> <Page content-class="h-full flex flex-col">
<fs-search ref="searchRef" v-bind="searchForm"> </fs-search> <fs-search ref="searchRef" v-bind="searchForm" />
<div class="flex-1 min-h-300px"> <div class="min-h-300px flex-1">
<vxe-grid ref="xGridRef" v-bind="gridOptions" @cell-click="handleCellClick"> <vxe-grid
ref="xGridRef"
v-bind="gridOptions"
@cell-click="handleCellClick"
>
<template #toolbar_buttons> <template #toolbar_buttons>
<a-space> <a-space>
<vben-button variant="primary" @click="handleExport()"> <vben-button variant="primary" @click="handleExport()">
<MdiExport class="text-lg mr-0.5" /> <MdiExport class="mr-0.5 text-lg" />
导出 导出
</vben-button> </vben-button>
</a-space> </a-space>
@ -115,7 +114,7 @@ const searchForm = ref({
</template> </template>
<template #statusSlot="{ row }"> <template #statusSlot="{ row }">
<a-tag>{{ row.status || "待提交" }}</a-tag> <a-tag>{{ row.status || '待提交' }}</a-tag>
</template> </template>
</vxe-grid> </vxe-grid>
</div> </div>

View File

@ -1,15 +1,15 @@
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref } from "vue"; import type { VxeGridPropTypes } from 'vxe-table';
import { useVbenModal } from "@vben/common-ui";
import { message } from "ant-design-vue";
import { useVxeTable } from "#/hooks/vxeTable";
import type { VxeGridPropTypes } from "vxe-table";
import Apis from "#/api";
const emit = defineEmits<{ import { reactive, ref } from 'vue';
(e: "rowClick", row: any): any;
(e: "confirm", row: any[]): any[]; import { useVbenModal } from '@vben/common-ui';
}>();
import { message } from 'ant-design-vue';
import { logger } from 'common-utils';
import Apis from '#/api';
import { useVxeTable } from '#/hooks/vxeTable';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -19,68 +19,75 @@ const props = withDefaults(
{ {
multiple: false, multiple: false,
showDepartment: true, showDepartment: true,
} },
); );
const emit = defineEmits<{
(e: 'confirm', row: any[]): any[];
(e: 'rowClick', row: any): any;
}>();
const [messageApi] = message.useMessage(); const [messageApi] = message.useMessage();
const { xGridRef, triggerProxy, gridProps } = useVxeTable({ ref: "xGridRef" }); const { xGridRef, triggerProxy, gridProps } = useVxeTable({ ref: 'xGridRef' });
const searchRef = ref(); const searchRef = ref();
const data = ref({ const data = ref({
userIds: [] title: '',
limitMultipleNum: 10,
userIds: [],
}); });
const formRef = ref();
const treeData = ref([]); const treeData = ref([]);
const treeItemKey = ref([]); const treeItemKey = ref([]);
const isEditMenu = ref(false); const isEditMenu = ref(false);
const checkedId = ref(0); const checkedId = ref(0);
const canMultiple = ref(false);
const checkRecords = ref([]); const limitMultipleNum = ref(10);
const checkRecords = ref<any>([]);
const searchBinding = ref({ const searchBinding = ref({
initialForm: {}, initialForm: {},
columns: { columns: {
name: { name: {
title: "用户姓名", title: '用户姓名',
key: "name", key: 'name',
component: { component: {
name: "a-input", name: 'a-input',
vModel: "value", vModel: 'value',
allowClear: true, allowClear: true,
}, },
show: true, show: true,
}, },
}, },
onSearch(context: any) { onSearch(_context: any) {
triggerProxy("reload"); triggerProxy('reload');
}, },
onReset(context: any) { },
}); });
function getColumns(_params: any = {}): VxeGridPropTypes.Columns { function getColumns(_params: any = {}): VxeGridPropTypes.Columns {
let columns: any[] = [{ type: "seq", width: 50 }]; let columns: any[] = [{ type: 'seq', width: 50 }];
if (props.multiple) { if (props.multiple) {
columns.push({ type: "checkbox", title: "用户ID", width: 150 }); columns.push({ type: 'checkbox', title: '用户ID', width: 150 });
} }
columns = columns.concat([ columns = [
{ field: "EMPLOYEE_NAME", title: "用户姓名", width: 120 }, ...columns,
{ field: "EMPLOYEE_GENDER", title: "性别", width: 80 }, { field: 'EMPLOYEE_NAME', title: '用户姓名', width: 120 },
{ field: "MAIL_ADDRESS", title: "邮箱", width: 150 }, { field: 'EMPLOYEE_GENDER', title: '性别', width: 80 },
{ field: "PHONE_NUM", title: "联系方式", width: 120 }, { field: 'MAIL_ADDRESS', title: '邮箱', width: 150 },
{ field: "ORG_NAME", title: "所属组织", minWidth: 120 }, { field: 'PHONE_NUM', title: '联系方式', width: 120 },
]); { field: 'ORG_NAME', title: '所属组织', minWidth: 120 },
];
return columns; return columns;
} }
/** Hooks - 表格 */ /** Hooks - 表格 */
const gridOptions = reactive( const gridOptions = reactive(
gridProps({ gridProps({
height: "400px", height: '400px',
columns: getColumns(), columns: getColumns(),
pagerConfig: { size: "mini" }, pagerConfig: { size: 'mini' },
proxyConfig: { proxyConfig: {
autoLoad: true, autoLoad: true,
ajax: { ajax: {
@ -101,10 +108,10 @@ const gridOptions = reactive(
}, },
}, },
rowConfig: { rowConfig: {
keyField: "ACCOUNT_ID", keyField: 'ACCOUNT_ID',
}, },
checkboxConfig: { checkboxConfig: {
labelField: "ACCOUNT_ID", labelField: 'ACCOUNT_ID',
// //
showHeader: false, showHeader: false,
highlight: true, highlight: true,
@ -112,14 +119,19 @@ const gridOptions = reactive(
// //
reserve: true, reserve: true,
checkMethod: ({ row }) => { checkMethod: ({ row }) => {
let checkRecordIds = checkRecords.value.map((item) => item.ACCOUNT_ID); const checkRecordIds = checkRecords.value.map(
if (checkRecords.value.length < 10 || checkRecordIds.includes(row.ACCOUNT_ID)) { (item) => item.ACCOUNT_ID,
);
if (
checkRecords.value.length < limitMultipleNum.value ||
checkRecordIds.includes(row.ACCOUNT_ID)
) {
return true; return true;
} }
return false; return false;
}, },
}, },
}) }),
); );
// //
@ -143,7 +155,7 @@ function transTree(list) {
const children = list.filter((data) => data.PARENT_ID === item.ORG_ID); const children = list.filter((data) => data.PARENT_ID === item.ORG_ID);
// return // return
if (!children.length) return; if (children.length === 0) return;
// (item) children // (item) children
item.children = children; item.children = children;
@ -159,37 +171,37 @@ function transferFormData() {
subFilter: [] as any[], subFilter: [] as any[],
}; };
let values = searchRef.value?.formData; const values = searchRef.value?.formData;
if (values.name) { if (values.name) {
form.subFilter.push({ form.subFilter.push({
symbol: "like", symbol: 'like',
singleValue: `%${values.name || ""}%`, singleValue: `%${values.name || ''}%`,
key: "EMPLOYEE_NAME", key: 'EMPLOYEE_NAME',
}); });
} }
if (values.id) { if (values.id) {
form.subFilter.push({ form.subFilter.push({
symbol: "like", symbol: 'like',
singleValue: `%${values.id || ""}%`, singleValue: `%${values.id || ''}%`,
key: "ACCOUNT_ID", key: 'ACCOUNT_ID',
}); });
} }
if (form.subFilter.length == 2) { if (form.subFilter.length === 2) {
form.subFilter[1].logic = "AND"; form.subFilter[1].logic = 'AND';
} }
let newForm: any = {}; const newForm: any = {};
if (treeItemKey.value && treeItemKey.value.length) { if (treeItemKey.value && treeItemKey.value.length > 0) {
newForm.subFilter = [ newForm.subFilter = [
{ {
symbol: "like", symbol: 'like',
singleValue: treeItemKey.value[0], singleValue: treeItemKey.value[0],
key: "ORG_ID", key: 'ORG_ID',
logic: "AND", logic: 'AND',
}, },
]; ];
@ -204,22 +216,22 @@ function transferFormData() {
async function loadDataByDept() { async function loadDataByDept() {
try { try {
let data = await Apis.api.core.orgemplbc.organization.post_paging({ const data = await Apis.api.core.orgemplbc.organization.post_paging({
params: { page: 1, size: 1000 }, params: { page: 1, size: 1000 },
data: { data: {
subFilter: [ subFilter: [
{ {
symbol: "like", symbol: 'like',
singleValue: "0001%", singleValue: '0001%',
key: "ORG_ID", key: 'ORG_ID',
logic: "AND", logic: 'AND',
}, },
], ],
}, },
}); });
treeData.value = transTree(data.rows); treeData.value = transTree(data.rows);
} catch (error) { } catch (error) {
console.log(err); logger.error('loadDataByDept error', error);
} }
} }
@ -227,8 +239,7 @@ async function loadDataByDept() {
* 单元行点击事件 * 单元行点击事件
*/ */
function handleCellClick({ row }) { function handleCellClick({ row }) {
console.log(row); emit('rowClick', {
emit("rowClick", {
...row, ...row,
label: row.EMPLOYEE_NAME, label: row.EMPLOYEE_NAME,
value: row.ACCOUNT_ID, value: row.ACCOUNT_ID,
@ -238,11 +249,10 @@ function handleCellClick({ row }) {
const currTypeData = ref<any>({}); const currTypeData = ref<any>({});
const treeItemTitle = ref(""); const treeItemTitle = ref('');
function handleTreeSelectChange(keys, opts) { function handleTreeSelectChange(keys) {
console.log("keys", keys, opts); if (keys.length > 0) {
if (keys.length) {
const treeItem = keys[0]; const treeItem = keys[0];
treeItemKey.value = keys; treeItemKey.value = keys;
treeItemTitle.value = treeItem.label; treeItemTitle.value = treeItem.label;
@ -250,15 +260,15 @@ function handleTreeSelectChange(keys, opts) {
Object.assign(currTypeData.value, treeItem); Object.assign(currTypeData.value, treeItem);
isEditMenu.value = true; isEditMenu.value = true;
checkedId.value = treeItem.id; checkedId.value = treeItem.id;
triggerProxy("reload"); triggerProxy('reload');
} else { } else {
currTypeData.value = {}; currTypeData.value = {};
checkedId.value = 0; checkedId.value = 0;
isEditMenu.value = false; isEditMenu.value = false;
treeItemKey.value = []; treeItemKey.value = [];
treeItemTitle.value = ""; treeItemTitle.value = '';
// searchParams.type = undefined; // searchParams.type = undefined;
triggerProxy("reload"); triggerProxy('reload');
} }
} }
/** /**
@ -266,14 +276,14 @@ function handleTreeSelectChange(keys, opts) {
* @param e * @param e
*/ */
function handleCheckboxChange(e) { function handleCheckboxChange(e) {
let currRows = xGridRef.value?.getCheckboxRecords() || []; const currRows = xGridRef.value?.getCheckboxRecords() || [];
let otherRows = xGridRef.value?.getCheckboxReserveRecords() || []; const otherRows = xGridRef.value?.getCheckboxReserveRecords() || [];
let allRows = [...currRows, ...otherRows]; const allRows = [...currRows, ...otherRows];
console.log("e>", e);
console.log("allRows", allRows);
if (e.checked) { if (e.checked) {
const existingIds = new Set(checkRecords.value.map((item) => item.ACCOUNT_ID)); const existingIds = new Set(
checkRecords.value.map((item) => item.ACCOUNT_ID),
);
if (!existingIds.has(e.row.ACCOUNT_ID)) { if (!existingIds.has(e.row.ACCOUNT_ID)) {
checkRecords.value = [...checkRecords.value, e.row]; checkRecords.value = [...checkRecords.value, e.row];
@ -282,43 +292,46 @@ function handleCheckboxChange(e) {
handleCloseTag(e.row); handleCloseTag(e.row);
} }
console.log("[ checkRecords.value ] >", checkRecords.value); console.log('[ checkRecords.value ] >', checkRecords.value);
if (allRows.length >= 10) { if (allRows.length >= limitMultipleNum.value) {
messageApi.warning("最多只能选择10条数据"); messageApi.warning(`最多只能选择${limitMultipleNum.value}条数据`);
} }
} }
function handleCloseTag(row) { function handleCloseTag(row) {
checkRecords.value = checkRecords.value.filter( checkRecords.value = checkRecords.value.filter(
(item) => item.ACCOUNT_ID != row.ACCOUNT_ID (item) => item.ACCOUNT_ID != row.ACCOUNT_ID,
); );
xGridRef.value?.setCheckboxRow(row, false); xGridRef.value?.setCheckboxRow(row, false);
} }
let title = ref("选择人员"); const title = ref('选择人员');
const [Modal, modalApi] = useVbenModal({ const [Modal, modalApi] = useVbenModal({
onOpenChange(isOpen: boolean) { onOpenChange(isOpen: boolean) {
if (isOpen) { if (isOpen) {
data.value = modalApi.getData<Record<string, any>>() || {}; data.value = modalApi.getData<Record<string, any>>() || {};
if (data.value.title) { if (data.value.title) {
title.value = data.value.title title.value = data.value.title;
} }
console.log(data.value.userIds) // canMultiple.value = data.value.multiple || false;
let rows: any = [] if (data.value.limitMultipleNum) {
limitMultipleNum.value = data.value.limitMultipleNum;
}
console.log(data.value.userIds);
const rows: any = [];
for (const element of checkRecords.value) { for (const element of checkRecords.value) {
if (data.value.userIds.includes(element.ACCOUNT_ID)) { if (data.value.userIds.includes(element.ACCOUNT_ID)) {
rows.push(element) rows.push(element);
} }
} }
checkRecords.value = rows checkRecords.value = rows;
// //
loadDataByDept(); loadDataByDept();
} }
}, },
onConfirm() { onConfirm() {
console.info("onConfirm"); emit('confirm', checkRecords.value);
emit("confirm", checkRecords.value);
modalApi.close(); modalApi.close();
}, },
onCancel() { onCancel() {
@ -328,11 +341,16 @@ const [Modal, modalApi] = useVbenModal({
</script> </script>
<template> <template>
<Modal :title="title"> <Modal :title="title">
<div v-if="props.multiple" class="flex flex-row py-12px"> <div v-if="props.multiple || canMultiple" class="py-12px flex flex-row">
<span class="block mr-12px">已选择</span> <span class="mr-12px block">已选择</span>
<div class="flex flex-row flex-1"> <div class="flex flex-1 flex-row">
<a-space> <a-space>
<a-tag v-for="(item, index) in checkRecords" :key="index" closable @close="handleCloseTag(item)"> <a-tag
v-for="(item, index) in checkRecords"
:key="index"
closable
@close="handleCloseTag(item)"
>
{{ item.EMPLOYEE_NAME }}-{{ item.ACCOUNT_ID }} {{ item.EMPLOYEE_NAME }}-{{ item.ACCOUNT_ID }}
</a-tag> </a-tag>
</a-space> </a-space>
@ -340,14 +358,24 @@ const [Modal, modalApi] = useVbenModal({
</div> </div>
<a-row class="h-full"> <a-row class="h-full">
<a-col :span="5" class=""> <a-col :span="5" class="">
<a-tree class="draggable-tree" block-node autoExpandParent :tree-data="treeData" @select="handleTreeSelectChange" /> <a-tree
:tree-data="treeData"
auto-expand-parent
block-node
class="draggable-tree"
@select="handleTreeSelectChange"
/>
</a-col> </a-col>
<a-col :span="19" class="flex flex-col"> <a-col :span="19" class="flex flex-col">
<fs-search ref="searchRef" v-bind="searchBinding"> </fs-search> <fs-search ref="searchRef" v-bind="searchBinding" />
<div class="flex-1 min-h-300px"> <div class="min-h-300px flex-1">
<VxeGrid ref="xGridRef" class="h-420px" v-bind="gridOptions" @cell-click="handleCellClick" <VxeGrid
@checkbox-change="handleCheckboxChange"> ref="xGridRef"
</VxeGrid> class="h-420px"
v-bind="gridOptions"
@cell-click="handleCellClick"
@checkbox-change="handleCheckboxChange"
/>
</div> </div>
</a-col> </a-col>
</a-row> </a-row>

View File

@ -164,6 +164,9 @@ export default {
get_toDoPage: (data?: QueryOptions) => http.get('/app/ccsq/toDoPage', data), get_toDoPage: (data?: QueryOptions) => http.get('/app/ccsq/toDoPage', data),
/** 协同办公/出差申请 已办 */ /** 协同办公/出差申请 已办 */
get_donePage: (data?: QueryOptions) => http.get('/app/ccsq/donePage', data), get_donePage: (data?: QueryOptions) => http.get('/app/ccsq/donePage', data),
/** 协同办公/出差申请 获取可退回节点信息 */
get_getBackNode: (data?: QueryOptions) =>
http.get('/app/ccsq/getBackNode', data),
/** 协同办公/出差申请 查询流程节点 */ /** 协同办公/出差申请 查询流程节点 */
get_getFlowNodeUserConfig: (data?: QueryOptions) => get_getFlowNodeUserConfig: (data?: QueryOptions) =>
http.get('/app/ccsq/getFlowNodeUserConfig', data), http.get('/app/ccsq/getFlowNodeUserConfig', data),
@ -384,6 +387,12 @@ export default {
/** 合同系统/申报 退回 */ /** 合同系统/申报 退回 */
post_rollback: (data?: BodyOptions) => post_rollback: (data?: BodyOptions) =>
http.post('/app/sbCtrBasePt/rollback', data), http.post('/app/sbCtrBasePt/rollback', data),
/** 合同系统/申报 发起废除 */
post_repeal: (data?: BodyOptions) =>
http.post('/app/sbCtrBasePt/repeal', data),
/** 合同系统/申报 发起流程 */
post_start: (data?: BodyOptions) =>
http.post('/app/sbCtrBasePt/start', data),
}, },
contractBaseInfo: { contractBaseInfo: {
/** 合同系统/立项 合同立项保存 */ /** 合同系统/立项 合同立项保存 */
@ -780,4 +789,12 @@ export default {
/** 合同系统/首页待办/已办 待办 */ /** 合同系统/首页待办/已办 待办 */
get_todo: (data?: QueryOptions) => http.get('/app/home/todo', data), get_todo: (data?: QueryOptions) => http.get('/app/home/todo', data),
}, },
sqConsignPt: {
/** 合同系统/签约授权 签约授权保存 */
post_saveSignMultiEntity: (data?: BodyOptions) =>
http.post('/app/sqConsignPt/saveSignMultiEntity', data),
/** 合同系统/签约授权 签约依据查询 */
get_SigningaAuthorizationSerch: (data?: QueryOptions) =>
http.get('/app/sqConsignPt/SigningaAuthorizationSerch', data),
},
}; };