Merge branch 'master' of http://47.109.37.87:3000/by2025/admin-vben5
Some checks are pending
Gitea Actions Demo / Explore-Gitea-Actions (push) Waiting to run

This commit is contained in:
FLL
2025-07-23 15:43:32 +08:00
8 changed files with 507 additions and 388 deletions

View File

@@ -5,17 +5,26 @@ import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { useVbenForm } from '#/adapter/form'; import { useVbenForm } from '#/adapter/form';
import { arrangementAdd, arrangementInfo, arrangementUpdate } from '#/api/property/attendanceManagement/arrangement';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { import {
useVbenVxeGrid, arrangementAdd,
type VxeGridProps arrangementInfo,
} from '#/adapter/vxe-table'; arrangementUpdate,
} from '#/api/property/attendanceManagement/arrangement';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import { modalSchema, attendanceColumns } from './data'; import { modalSchema, attendanceColumns } from './data';
import { Radio,DatePicker,Form,Select,FormItem,RadioGroup, message } from 'ant-design-vue'; import {
Radio,
DatePicker,
Form,
Select,
FormItem,
RadioGroup,
message,
} from 'ant-design-vue';
import unitPersonModal from './unit-person-modal.vue'; import unitPersonModal from './unit-person-modal.vue';
import {groupList} from '#/api/property/attendanceManagement/attendanceGroupSettings' import { groupList } from '#/api/property/attendanceManagement/attendanceGroupSettings';
import {getDictOptions} from "#/utils/dict"; import { getDictOptions } from '#/utils/dict';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import type { PersonVO } from './type'; import type { PersonVO } from './type';
import { ref, h } from 'vue'; import { ref, h } from 'vue';
@@ -27,7 +36,9 @@ const title = computed(() => {
}); });
const groupOptions = ref<any[]>([]); const groupOptions = ref<any[]>([]);
const groupMap = ref<Record<string, any>>({}); // 用于快速查找 const groupMap = ref<Record<string, any>>({}); // 用于快速查找
const tableData = reactive<{dept: {unitId: string | number,unitName: string}, users: PersonVO[]}[]>([]); const tableData = reactive<
{ dept: { unitId: string | number; unitName: string }; users: PersonVO[] }[]
>([]);
const columns = [ const columns = [
{ {
@@ -68,22 +79,23 @@ const columns = [
}, },
style: 'margin-right: 4px;', style: 'margin-right: 4px;',
}, },
() => user.userName () => user.userName,
) ),
), ),
}, },
{ {
title: '操作', title: '操作',
key: 'action', key: 'action',
width: 80, width: 80,
customRender: ({ record, index }: { record: any, index: number }) => customRender: ({ record, index }: { record: any; index: number }) =>
h( h(
'a', 'a',
{ {
style: 'color: #ff4d4f; font-size: 18px; margin-left: 8px; cursor: pointer;', style:
'color: #ff4d4f; font-size: 18px; margin-left: 8px; cursor: pointer;',
onClick: () => handleRemoveRow(index), onClick: () => handleRemoveRow(index),
}, },
'移除' '移除',
), ),
}, },
]; ];
@@ -123,7 +135,7 @@ const formModel = reactive<{
// employeeName:undefined,//员工姓名 // employeeName:undefined,//员工姓名
// deptId:undefined,//部门ID // deptId:undefined,//部门ID
// deptName:undefined,//部门名称 // deptName:undefined,//部门名称
]//考勤组人员列表 ], //考勤组人员列表
}); });
const singleDate = ref(''); const singleDate = ref('');
const longDate = ref(''); const longDate = ref('');
@@ -143,7 +155,7 @@ const [BasicForm, formApi] = useVbenForm({
// 通用配置项 会影响到所有表单项 // 通用配置项 会影响到所有表单项
componentProps: { componentProps: {
class: 'w-full', class: 'w-full',
} },
}, },
schema: modalSchema(), schema: modalSchema(),
showDefaultActions: false, showDefaultActions: false,
@@ -195,15 +207,15 @@ async function getGroupList(){
const res = await groupList({ const res = await groupList({
pageSize: 1000000000, pageSize: 1000000000,
pageNum: 1, pageNum: 1,
status:1//0:停用 1:启用 status: 1, //0:停用 1:启用
}); });
groupOptions.value = (res.rows || []).map(item=>({ groupOptions.value = (res.rows || []).map((item) => ({
label: item.groupName, label: item.groupName,
value:item.id value: item.id,
})); }));
// 构建 id 到 group 对象的映射 // 构建 id 到 group 对象的映射
groupMap.value = {}; groupMap.value = {};
(res.rows || []).forEach(item => { (res.rows || []).forEach((item) => {
groupMap.value[item.id] = item; groupMap.value[item.id] = item;
}); });
} }
@@ -230,28 +242,27 @@ async function handleConfirm() {
if (singleDate.value) { if (singleDate.value) {
data.startDate = dayjs(singleDate.value).format('YYYY-MM-DD'); data.startDate = dayjs(singleDate.value).format('YYYY-MM-DD');
} else { } else {
message.error('请选择单个日期') message.error('请选择单个日期');
return; return;
} }
} else if (data.dateType == 2) { } else if (data.dateType == 2) {
if (longDate.value) { if (longDate.value) {
data.startDate = dayjs(longDate.value).format('YYYY-MM-DD'); data.startDate = dayjs(longDate.value).format('YYYY-MM-DD');
} else { } else {
message.error('请选择起始日期') message.error('请选择起始日期');
return; return;
} }
} else if (data.dateType == 3) { } else if (data.dateType == 3) {
if (!rangeDate.value[0]) { if (!rangeDate.value[0]) {
message.error('请选择开始日期') message.error('请选择开始日期');
return; return;
} else if (!rangeDate.value[1]) { } else if (!rangeDate.value[1]) {
message.error('请选择结束日期') message.error('请选择结束日期');
return; return;
} else { } else {
data.startDate = dayjs(rangeDate.value[0]).format('YYYY-MM-DD'); data.startDate = dayjs(rangeDate.value[0]).format('YYYY-MM-DD');
data.endDate = dayjs(rangeDate.value[1]).format('YYYY-MM-DD'); data.endDate = dayjs(rangeDate.value[1]).format('YYYY-MM-DD');
} }
} }
// await (isUpdate.value ? arrangementUpdate(data) : arrangementAdd(data)); // await (isUpdate.value ? arrangementUpdate(data) : arrangementAdd(data));
resetInitialized(); resetInitialized();
@@ -268,41 +279,69 @@ async function handleClosed() {
await formApi.resetForm(); await formApi.resetForm();
resetInitialized(); resetInitialized();
} }
onMounted(() => { onMounted(() => {});
})
</script> </script>
<template> <template>
<BasicModal :title="title"> <BasicModal :title="title">
<!-- 顶部表单区 --> <!-- 顶部表单区 -->
<Form :model="formModel" :rules="rules" ref="formRef" layout="horizontal" class="mb-4" > <Form
:model="formModel"
:rules="rules"
ref="formRef"
layout="horizontal"
class="mb-4"
>
<div class="grid grid-cols-2 gap-x-8 gap-y-2"> <div class="grid grid-cols-2 gap-x-8 gap-y-2">
<FormItem name="groupId" label="请选择考勤组" class="mb-0"> <FormItem name="groupId" label="请选择考勤组" class="mb-0">
<Select v-model:value="formModel.groupId" :options="groupOptions" placeholder="请选择" @change="chooseGroup"/> <Select
v-model:value="formModel.groupId"
:options="groupOptions"
placeholder="请选择"
@change="chooseGroup"
/>
</FormItem> </FormItem>
<FormItem name="attendanceType" label="考勤类型" class="mb-0"> <FormItem name="attendanceType" label="考勤类型" class="mb-0">
<Select :disabled="true" :options="getDictOptions('wy_kqlx')" v-model:value="formModel.attendanceType" placeholder="请选择考勤组" /> <Select
:disabled="true"
:options="getDictOptions('wy_kqlx')"
v-model:value="formModel.attendanceType"
placeholder="请选择考勤组"
/>
</FormItem> </FormItem>
<FormItem label="排班日期" name="dateType" class="col-span-2 mb-0"> <FormItem label="排班日期" name="dateType" class="col-span-2 mb-0">
<RadioGroup v-model:value="formModel.dateType" class="mr-4"> <RadioGroup v-model:value="formModel.dateType" class="mr-4">
<Radio value=1> <Radio value="1">
<span> <span>
单个日期 单个日期
<DatePicker v-model:value="singleDate" @click="handleDateTypeChange(1)"/> <DatePicker
v-model:value="singleDate"
@click="handleDateTypeChange(1)"
/>
</span> </span>
</Radio> </Radio>
<Radio value=2> <Radio value="2">
<span> <span>
从此日起长期有效 从此日起长期有效
<DatePicker v-model:value="longDate" @click="handleDateTypeChange(2)"/> <DatePicker
v-model:value="longDate"
@click="handleDateTypeChange(2)"
/>
</span> </span>
</Radio> </Radio>
<Radio value=3> <Radio value="3">
<span> <span>
在此期间有效 在此期间有效
<DatePicker v-model:value="rangeDate[0]" class="ml-2" @click="handleDateTypeChange(3)"/> <DatePicker
v-model:value="rangeDate[0]"
class="ml-2"
@click="handleDateTypeChange(3)"
/>
<span class="mx-2">~</span> <span class="mx-2">~</span>
<DatePicker v-model:value="rangeDate[1]" @click="handleDateTypeChange(3)"/> <DatePicker
v-model:value="rangeDate[1]"
@click="handleDateTypeChange(3)"
/>
</span> </span>
</Radio> </Radio>
</RadioGroup> </RadioGroup>
@@ -311,9 +350,14 @@ onMounted(() => {
</Form> </Form>
<!-- 考勤组人员表格区 --> <!-- 考勤组人员表格区 -->
<div class="mb-2"> <div class="mb-2">
<div class="flex items-center mb-2"> <div class="mb-2 flex items-center">
<span>考勤组人员已选 <span class="text-red-500">{{ totalSelected }}</span> </span> <span
<a-button type="primary" size="small" class="ml-4" @click="handleAdd">+ 添加人员</a-button> >考勤组人员已选
<span class="text-red-500">{{ totalSelected }}</span> </span
>
<a-button type="primary" size="small" class="ml-4" @click="handleAdd"
>+ 添加人员</a-button
>
</div> </div>
<Table <Table
:columns="columns" :columns="columns"

View File

@@ -1,36 +1,43 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Radio, Button, Calendar, DatePicker } from 'ant-design-vue'; import { Radio, Button, Calendar, DatePicker } from 'ant-design-vue';
import type { RadioChangeEvent,BadgeProps, } from 'ant-design-vue'; import type { RadioChangeEvent, BadgeProps } from 'ant-design-vue';
import { ref } from 'vue'; import { ref } from 'vue';
import { Dayjs } from 'dayjs'; import { Dayjs } from 'dayjs';
import arrangementModal from './arrangement-modal.vue'; import arrangementModal from './arrangement-modal.vue';
import { useVbenModal } from '@vben/common-ui'; import { useVbenModal } from '@vben/common-ui';
import type { PersonVO } from './type'; import type { PersonVO } from './type';
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'changeView',value:boolean):void (e: 'changeView', value: boolean): void;
}>(); }>();
const props = defineProps<{ const props = defineProps<{
viewMode:'calender' | 'schedule' viewMode: 'calender' | 'schedule';
}>(); }>();
const selectedDate = ref(); const selectedDate = ref();
// 切换视图模式 // 切换视图模式
function handleViewModeChange(e: RadioChangeEvent): void { function handleViewModeChange(e: RadioChangeEvent): void {
// 将父组件的isCalenderView变为true // 将父组件的isCalenderView变为true
emit('changeView',e.target.value) emit('changeView', e.target.value);
} }
// 日历模拟数据 // 日历模拟数据
const scheduleData:{ date: string; list: { type: BadgeProps['status']; content: string }[] }[] = [ const scheduleData: {
date: string;
list: { type: BadgeProps['status']; content: string }[];
}[] = [
{ date: '2025-07-08', list: [{ type: 'warning', content: '7月8日事件' }] }, { date: '2025-07-08', list: [{ type: 'warning', content: '7月8日事件' }] },
{ date: '2025-07-06', list: [{ type: 'success', content: '8月8日事件' }] }, { date: '2025-07-06', list: [{ type: 'success', content: '8月8日事件' }] },
// ... // ...
]; ];
const getListData2 = (current: Dayjs): { type: BadgeProps['status']; content: string }[] => { const getListData2 = (
current: Dayjs,
): { type: BadgeProps['status']; content: string }[] => {
const dateStr = current.format('YYYY-MM-DD'); const dateStr = current.format('YYYY-MM-DD');
const found = scheduleData.find(item => item.date === dateStr); const found = scheduleData.find((item) => item.date === dateStr);
return found ? found.list : []; return found ? found.list : [];
}; };
const getListData = (value: Dayjs):{ type: BadgeProps['status']; content: string }[] => { const getListData = (
value: Dayjs,
): { type: BadgeProps['status']; content: string }[] => {
let listData: { type: BadgeProps['status']; content: string }[] | undefined; let listData: { type: BadgeProps['status']; content: string }[] | undefined;
switch (value.date()) { switch (value.date()) {
case 8: case 8:
@@ -77,13 +84,15 @@ function handleAdd() {
modalApi.setData({}); modalApi.setData({});
modalApi.open(); modalApi.open();
} }
</script> </script>
<template> <template>
<div class="h-full flex flex-col"> <div class="flex h-full flex-col">
<div class="flex justify-between"> <div class="flex justify-between">
<div class="flex gap-7"> <div class="flex gap-7">
<Radio.Group v-model:value="props.viewMode" @change="handleViewModeChange"> <Radio.Group
v-model:value="props.viewMode"
@change="handleViewModeChange"
>
<Radio.Button value="calender">日历视图</Radio.Button> <Radio.Button value="calender">日历视图</Radio.Button>
<Radio.Button value="schedule">班表视图</Radio.Button> <Radio.Button value="schedule">班表视图</Radio.Button>
</Radio.Group> </Radio.Group>
@@ -97,18 +106,30 @@ function handleAdd() {
</div> </div>
</div> </div>
<div class="flex-1 p-1"> <div class="flex-1 p-1">
<Calendar v-model:value="selectedDate" :mode="'month'" :headerRender="customHeader"> <Calendar
v-model:value="selectedDate"
:mode="'month'"
:headerRender="customHeader"
>
<template #dateCellRender="{ current }"> <template #dateCellRender="{ current }">
<ul class="events"> <ul class="events">
<li v-for="item in getListData2(current)" :key="item.content"> <li v-for="item in getListData2(current)" :key="item.content">
<span> <span>
<!-- <Badge :status="item.type" :text="item.content" /> --> <!-- <Badge :status="item.type" :text="item.content" /> -->
<span>{{ item.content }}</span> <span>{{ item.content }}</span>
<a style="margin-left: 4px; color: #1890ff; cursor: pointer;">编辑</a> <a style="margin-left: 4px; color: #1890ff; cursor: pointer"
<a style="margin-left: 4px; color: #ff4d4f; cursor: pointer;">删除</a> >编辑</a
>
<a style="margin-left: 4px; color: #ff4d4f; cursor: pointer"
>删除</a
>
</span> </span>
</li> </li>
<a v-if="getListData2(current).length > 0" style="margin-left: 4px; color: #1890ff; cursor: pointer;">详情</a> <a
v-if="getListData2(current).length > 0"
style="margin-left: 4px; color: #1890ff; cursor: pointer"
>详情</a
>
</ul> </ul>
</template> </template>
<template #monthCellRender="{ current }"> <template #monthCellRender="{ current }">

View File

@@ -1,7 +1,6 @@
import type { FormSchemaGetter } from '#/adapter/form'; import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table'; import type { VxeGridProps } from '#/adapter/vxe-table';
export const querySchema: FormSchemaGetter = () => [ export const querySchema: FormSchemaGetter = () => [
{ {
component: 'Input', component: 'Input',
@@ -15,15 +14,13 @@ export const querySchema: FormSchemaGetter = () => [
}, },
{ {
component: 'Select', component: 'Select',
componentProps: { componentProps: {},
},
fieldName: 'scheduleType', fieldName: 'scheduleType',
label: '排班类型1-固定班制2-排班制', label: '排班类型1-固定班制2-排班制',
}, },
{ {
component: 'Select', component: 'Select',
componentProps: { componentProps: {},
},
fieldName: 'dateType', fieldName: 'dateType',
label: '日期类型1-单个日期2-长期有效3-期间有效', label: '日期类型1-单个日期2-长期有效3-期间有效',
}, },
@@ -78,9 +75,9 @@ export const columns: VxeGridProps['columns'] = [
slots: { slots: {
default: ({ rowIndex }) => { default: ({ rowIndex }) => {
return (rowIndex + 1).toString(); return (rowIndex + 1).toString();
}
}, },
width:60 },
width: 60,
}, },
{ {
title: '人员', title: '人员',
@@ -96,12 +93,12 @@ export const columns: VxeGridProps['columns'] = [
{ {
title: '排班名称', title: '排班名称',
field: 'scheduleName', field: 'scheduleName',
minWidth:120 minWidth: 120,
}, },
{ {
title: '考勤组', title: '考勤组',
field: 'groupId', field: 'groupId',
minWidth:120 minWidth: 120,
}, },
{ {
title: '考勤类型', title: '考勤类型',
@@ -111,8 +108,7 @@ export const columns: VxeGridProps['columns'] = [
{ {
title: '考勤时间', title: '考勤时间',
field: 'dateType', field: 'dateType',
minWidth:200 minWidth: 200,
}, },
{ {
field: 'action', field: 'action',
@@ -131,9 +127,9 @@ export const attendanceColumns: VxeGridProps['columns'] = [
slots: { slots: {
default: ({ rowIndex }) => { default: ({ rowIndex }) => {
return (rowIndex + 1).toString(); return (rowIndex + 1).toString();
}
}, },
width:60 },
width: 60,
}, },
{ {
title: '单位', title: '单位',
@@ -148,7 +144,7 @@ export const attendanceColumns: VxeGridProps['columns'] = [
{ {
title: '人员', title: '人员',
field: 'dateType', field: 'dateType',
minWidth:200 minWidth: 200,
}, },
{ {
field: 'action', field: 'action',
@@ -168,14 +164,14 @@ export const unitColumns: VxeGridProps['columns'] = [
slots: { slots: {
default: ({ rowIndex }) => { default: ({ rowIndex }) => {
return (rowIndex + 1).toString(); return (rowIndex + 1).toString();
}
}, },
width:60 },
width: 60,
}, },
{ {
title: '姓名', title: '姓名',
field: 'userName', field: 'userName',
minWidth:120 minWidth: 120,
}, },
{ {
title: '所属单位', title: '所属单位',
@@ -185,8 +181,8 @@ export const unitColumns: VxeGridProps['columns'] = [
{ {
title: '电话', title: '电话',
field: 'phone', field: 'phone',
minWidth:120 minWidth: 120,
} },
]; ];
export const modalSchema: FormSchemaGetter = () => [ export const modalSchema: FormSchemaGetter = () => [
{ {
@@ -212,15 +208,13 @@ export const modalSchema: FormSchemaGetter = () => [
label: '排班类型1-固定班制2-排班制', label: '排班类型1-固定班制2-排班制',
fieldName: 'scheduleType', fieldName: 'scheduleType',
component: 'Select', component: 'Select',
componentProps: { componentProps: {},
},
}, },
{ {
label: '日期类型1-单个日期2-长期有效3-期间有效', label: '日期类型1-单个日期2-长期有效3-期间有效',
fieldName: 'dateType', fieldName: 'dateType',
component: 'Select', component: 'Select',
componentProps: { componentProps: {},
},
}, },
{ {
label: '开始日期', label: '开始日期',

View File

@@ -1,20 +1,27 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import calendarView from './calendarView.vue' import calendarView from './calendarView.vue';
import scheduleView from './scheduleView.vue'; import scheduleView from './scheduleView.vue';
const viewMode = ref<'calender' | 'schedule'>('calender'); const viewMode = ref<'calender' | 'schedule'>('calender');
// const isCalenderView = ref(true); // const isCalenderView = ref(true);
function handleViewModeChange(mode: 'calender' | 'schedule') { function handleViewModeChange(mode: 'calender' | 'schedule') {
viewMode.value = mode viewMode.value = mode;
} }
</script> </script>
<template> <template>
<div class="p-4 h-full"> <div class="h-full p-4">
<div class="p-4 h-full bg-white"> <div class="h-full bg-white p-4">
<calendarView v-show="viewMode === 'calender'" :viewMode="viewMode" @changeView="handleViewModeChange"/> <calendarView
<scheduleView v-show="viewMode === 'schedule'" :viewMode="viewMode" @changeView="handleViewModeChange"/> v-show="viewMode === 'calender'"
:viewMode="viewMode"
@changeView="handleViewModeChange"
/>
<scheduleView
v-show="viewMode === 'schedule'"
:viewMode="viewMode"
@changeView="handleViewModeChange"
/>
</div> </div>
</div> </div>
</template> </template>
<style scoped> <style scoped></style>
</style>

View File

@@ -4,10 +4,7 @@ import type { RadioChangeEvent } from 'ant-design-vue';
import { ref } from 'vue'; import { ref } from 'vue';
import { Dayjs } from 'dayjs'; import { Dayjs } from 'dayjs';
import { columns } from './data'; import { columns } from './data';
import { import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
useVbenVxeGrid,
type VxeGridProps
} from '#/adapter/vxe-table';
import { getVxePopupContainer } from '@vben/utils'; import { getVxePopupContainer } from '@vben/utils';
import { import {
arrangementList, arrangementList,
@@ -19,7 +16,7 @@ import { Page, useVbenModal } from '@vben/common-ui';
import { Popconfirm, Space } from 'ant-design-vue'; import { Popconfirm, Space } from 'ant-design-vue';
const emit = defineEmits<{ (e: 'changeView', value: boolean): void }>(); const emit = defineEmits<{ (e: 'changeView', value: boolean): void }>();
const props = defineProps<{ const props = defineProps<{
viewMode:'calender' | 'schedule' viewMode: 'calender' | 'schedule';
}>(); }>();
const value = ref<Dayjs>(); const value = ref<Dayjs>();
const onPanelChange = (value: Dayjs, mode: string) => { const onPanelChange = (value: Dayjs, mode: string) => {
@@ -28,10 +25,9 @@ const onPanelChange = (value: Dayjs, mode: string) => {
// 切换视图模式 // 切换视图模式
function handleViewModeChange(e: RadioChangeEvent): void { function handleViewModeChange(e: RadioChangeEvent): void {
// 将父组件的isCalenderView变为false // 将父组件的isCalenderView变为false
emit('changeView',e.target.value) emit('changeView', e.target.value);
} }
const gridOptions: VxeGridProps = { const gridOptions: VxeGridProps = {
checkboxConfig: { checkboxConfig: {
// 高亮 // 高亮
@@ -95,7 +91,10 @@ async function handleDelete(row: Required<ArrangementForm>) {
<template> <template>
<div class="h-full"> <div class="h-full">
<div class="flex justify-between"> <div class="flex justify-between">
<Radio.Group v-model:value="props.viewMode" @change="handleViewModeChange"> <Radio.Group
v-model:value="props.viewMode"
@change="handleViewModeChange"
>
<Radio.Button value="calender">日历视图</Radio.Button> <Radio.Button value="calender">日历视图</Radio.Button>
<Radio.Button value="schedule">班表视图</Radio.Button> <Radio.Button value="schedule">班表视图</Radio.Button>
</Radio.Group> </Radio.Group>
@@ -103,10 +102,21 @@ async function handleDelete(row: Required<ArrangementForm>) {
<Button type="primary" @click="handleAdd">新增排班</Button> <Button type="primary" @click="handleAdd">新增排班</Button>
</div> </div>
</div> </div>
<div class="flex p-2 h-full"> <div class="flex h-full p-2">
<div style="padding-top: 48.4px;"> <div style="padding-top: 48.4px">
<div :style="{ width: '300px', border: '1px solid #d9d9d9', borderRadius: '4px' }"> <div
<Calendar v-model:value="value" :fullscreen="false" :mode="'month'" @panelChange="onPanelChange"/> :style="{
width: '300px',
border: '1px solid #d9d9d9',
borderRadius: '4px',
}"
>
<Calendar
v-model:value="value"
:fullscreen="false"
:mode="'month'"
@panelChange="onPanelChange"
/>
</div> </div>
</div> </div>
<div class="flex-1"> <div class="flex-1">
@@ -143,5 +153,4 @@ async function handleDelete(row: Required<ArrangementForm>) {
<ArrangementModal @reload="tableApi.query()" /> <ArrangementModal @reload="tableApi.query()" />
</div> </div>
</template> </template>
<style> <style></style>
</style>

View File

@@ -64,5 +64,4 @@ export interface PersonVO {
* 备注 * 备注
*/ */
remark: string; remark: string;
} }

View File

@@ -1,24 +1,39 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, reactive, onMounted, watch } from 'vue'; import { computed, ref, reactive, onMounted, watch } from 'vue';
import { SyncOutlined } from '@ant-design/icons-vue'; import { SyncOutlined } from '@ant-design/icons-vue';
import { Tree, InputSearch, Skeleton, Empty, Button,Tag } from 'ant-design-vue'; import {
Tree,
InputSearch,
Skeleton,
Empty,
Button,
Tag,
} from 'ant-design-vue';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils'; import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter/form'; import { useVbenForm } from '#/adapter/form';
import { arrangementAdd, arrangementInfo, arrangementUpdate } from '#/api/property/attendanceManagement/arrangement';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { import {
useVbenVxeGrid, arrangementAdd,
type VxeGridProps arrangementInfo,
} from '#/adapter/vxe-table'; arrangementUpdate,
} from '#/api/property/attendanceManagement/arrangement';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
import { modalSchema, unitColumns, unitQuerySchema } from './data'; import { modalSchema, unitColumns, unitQuerySchema } from './data';
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui'; import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { resident_unitList } from '#/api/property/resident/unit'; import { resident_unitList } from '#/api/property/resident/unit';
import { personList } from '#/api/property/resident/person'; import { personList } from '#/api/property/resident/person';
import type { PersonVO } from './type'; import type { PersonVO } from './type';
const emit = defineEmits<{ reload: [tableData: {dept: {unitId: string | number,unitName: string}, users: PersonVO[]}[]] }>(); const emit = defineEmits<{
reload: [
tableData: {
dept: { unitId: string | number; unitName: string };
users: PersonVO[];
}[],
];
}>();
const selectDeptId = ref<string[]>([]); const selectDeptId = ref<string[]>([]);
const isUpdate = ref(false); const isUpdate = ref(false);
const title = computed(() => { const title = computed(() => {
@@ -29,19 +44,20 @@ const searchValue = ref('');
const selectedDeptIds = ref<string[]>([]); const selectedDeptIds = ref<string[]>([]);
const showTreeSkeleton = ref(false); const showTreeSkeleton = ref(false);
//单位树 //单位树
const deptTree = reactive<{ const deptTree = reactive<
{
id: string; id: string;
label: string; label: string;
children: { children: {
id: string; id: string;
label: string; label: string;
}[]; }[];
}[]>([ }[]
>([
{ {
id: '', id: '',
label: '全部单位', label: '全部单位',
children: [ children: [],
],
}, },
]); ]);
@@ -49,9 +65,12 @@ const deptTree = reactive<{
const selectedUsers = ref<PersonVO[]>([]); const selectedUsers = ref<PersonVO[]>([]);
//已选内容,需要展示到父组件 //已选内容,需要展示到父组件
const tableData = reactive<{ const tableData = reactive<
dept: {unitId: string | number,unitName: string}, {
users: PersonVO[]}[]>([]); dept: { unitId: string | number; unitName: string };
users: PersonVO[];
}[]
>([]);
function handleReload() { function handleReload() {
showTreeSkeleton.value = true; showTreeSkeleton.value = true;
setTimeout(() => { setTimeout(() => {
@@ -64,7 +83,7 @@ const filteredDeptTree = computed(() => {
// 递归过滤树 // 递归过滤树
function filter(tree: any[]): any[] { function filter(tree: any[]): any[] {
return tree return tree
.map(node => { .map((node) => {
if (node.label.includes(searchValue.value)) return node; if (node.label.includes(searchValue.value)) return node;
if (node.children) { if (node.children) {
const children = filter(node.children); const children = filter(node.children);
@@ -86,7 +105,7 @@ const [BasicForm, formApi] = useVbenForm({
// 通用配置项 会影响到所有表单项 // 通用配置项 会影响到所有表单项
componentProps: { componentProps: {
class: 'w-full', class: 'w-full',
} },
}, },
schema: modalSchema(), schema: modalSchema(),
showDefaultActions: false, showDefaultActions: false,
@@ -182,30 +201,36 @@ const gridOptions: VxeGridProps = {
}; };
const gridEvents = { const gridEvents = {
checkboxChange: async (params: any) => { checkboxChange: async (params: any) => {
const currentRecords = await params.$grid.getCheckboxRecords()??[]; const currentRecords = (await params.$grid.getCheckboxRecords()) ?? [];
const reserveRecords = await params.$grid.getCheckboxReserveRecords()??[]; const reserveRecords =
(await params.$grid.getCheckboxReserveRecords()) ?? [];
selectedUsers.value = [...reserveRecords, ...currentRecords]; selectedUsers.value = [...reserveRecords, ...currentRecords];
}, },
checkboxAll: async (params: any) => { checkboxAll: async (params: any) => {
const currentRecords = await params.$grid.getCheckboxRecords()??[]; const currentRecords = (await params.$grid.getCheckboxRecords()) ?? [];
const reserveRecords = await params.$grid.getCheckboxReserveRecords()??[]; const reserveRecords =
(await params.$grid.getCheckboxReserveRecords()) ?? [];
selectedUsers.value = [...reserveRecords, ...currentRecords]; selectedUsers.value = [...reserveRecords, ...currentRecords];
}, },
}; };
const [BasicTable, tableApi] = useVbenVxeGrid({ const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions, formOptions,
gridOptions, gridOptions,
gridEvents gridEvents,
}); });
async function handleConfirm() { async function handleConfirm() {
try { try {
for (const user of selectedUsers.value) { for (const user of selectedUsers.value) {
if(tableData.find(item=>item.dept.unitId === user.unitId)){ if (tableData.find((item) => item.dept.unitId === user.unitId)) {
tableData.find(item=>item.dept.unitId===user.unitId)!.users.push(user); tableData
.find((item) => item.dept.unitId === user.unitId)!
.users.push(user);
} else { } else {
tableData.push({ dept:{unitId:user.unitId,unitName:user.unitName}, users: [user] }); tableData.push({
dept: { unitId: user.unitId, unitName: user.unitName },
users: [user],
});
} }
} }
console.log(tableData); console.log(tableData);
@@ -254,13 +279,20 @@ onMounted(() => {
<BasicModal :title="title"> <BasicModal :title="title">
<div class="mb-2 flex"> <div class="mb-2 flex">
<div> <div>
<Skeleton :loading="showTreeSkeleton" :paragraph="{ rows: 8 }" active class="p-[8px]"> <Skeleton
<div class="bg-background z-100 sticky left-0 top-0 p-[8px] flex items-center"> :loading="showTreeSkeleton"
:paragraph="{ rows: 8 }"
active
class="p-[8px]"
>
<div
class="bg-background z-100 sticky left-0 top-0 flex items-center p-[8px]"
>
<InputSearch <InputSearch
v-model:value="searchValue" v-model:value="searchValue"
placeholder="搜索单位" placeholder="搜索单位"
size="small" size="small"
style="flex:1;" style="flex: 1"
> >
<template #enterButton> <template #enterButton>
<Button type="text" @click="getUnitList"> <Button type="text" @click="getUnitList">
@@ -283,26 +315,39 @@ onMounted(() => {
<span v-if="searchValue && label.indexOf(searchValue) > -1"> <span v-if="searchValue && label.indexOf(searchValue) > -1">
{{ label.substring(0, label.indexOf(searchValue)) }} {{ label.substring(0, label.indexOf(searchValue)) }}
<span style="color: #f50">{{ searchValue }}</span> <span style="color: #f50">{{ searchValue }}</span>
{{ label.substring(label.indexOf(searchValue) + searchValue.length) }} {{
label.substring(
label.indexOf(searchValue) + searchValue.length,
)
}}
</span> </span>
<span v-else>{{ label }}</span> <span v-else>{{ label }}</span>
</template> </template>
</Tree> </Tree>
<div v-else class="mt-5"> <div v-else class="mt-5">
<Empty :image="Empty.PRESENTED_IMAGE_SIMPLE" description="无单位数据" /> <Empty
:image="Empty.PRESENTED_IMAGE_SIMPLE"
description="无单位数据"
/>
</div> </div>
</div> </div>
</Skeleton> </Skeleton>
</div> </div>
<div class="flex-1"> <div class="flex-1">
<div class="flex mb-2 overflow-auto" style="max-height: 80px;font-size: 14px;"> <div
class="mb-2 flex overflow-auto"
style="max-height: 80px; font-size: 14px"
>
已选人员 已选人员
<span v-for="user in selectedUsers" :key="user.id" style="margin-right: 8px;"> <span
v-for="user in selectedUsers"
:key="user.id"
style="margin-right: 8px"
>
<Tag>{{ user.userName }}</Tag> <Tag>{{ user.userName }}</Tag>
</span> </span>
</div> </div>
<BasicTable> <BasicTable> </BasicTable>
</BasicTable>
</div> </div>
</div> </div>
</BasicModal> </BasicModal>

View File

@@ -1,18 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Recordable } from '@vben/types';
import { ref } from 'vue';
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui'; import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils'; import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue'; import { Modal, Popconfirm, Space } from 'ant-design-vue';
import dayjs from 'dayjs';
import { import {
useVbenVxeGrid, useVbenVxeGrid,
vxeCheckboxChecked, vxeCheckboxChecked,
type VxeGridProps type VxeGridProps,
} from '#/adapter/vxe-table'; } from '#/adapter/vxe-table';
import { import {
@@ -76,7 +70,7 @@ const gridOptions: VxeGridProps = {
keyField: 'id', keyField: 'id',
}, },
// 表格全局唯一表示 保存列配置需要用到 // 表格全局唯一表示 保存列配置需要用到
id: 'property-costMeterWater-index' id: 'property-costMeterWater-index',
}; };
const [BasicTable, tableApi] = useVbenVxeGrid({ const [BasicTable, tableApi] = useVbenVxeGrid({
@@ -118,9 +112,14 @@ function handleMultiDelete() {
} }
function handleDownloadExcel() { function handleDownloadExcel() {
commonDownloadExcel(costMeterWaterExport, '费用-水电抄数据', tableApi.formApi.form.values, { commonDownloadExcel(
costMeterWaterExport,
'费用-水电抄数据',
tableApi.formApi.form.values,
{
fieldMappingTime: formOptions.fieldMappingTime, fieldMappingTime: formOptions.fieldMappingTime,
}); },
);
} }
</script> </script>
@@ -140,7 +139,8 @@ function handleDownloadExcel() {
danger danger
type="primary" type="primary"
v-access:code="['property:costMeterWater:remove']" v-access:code="['property:costMeterWater:remove']"
@click="handleMultiDelete"> @click="handleMultiDelete"
>
{{ $t('pages.common.delete') }} {{ $t('pages.common.delete') }}
</a-button> </a-button>
<a-button <a-button