feat:入驻单位入驻位置修改

This commit is contained in:
2025-09-12 15:56:54 +08:00
parent 07006ee2ac
commit ebea58cd9f
4 changed files with 249 additions and 115 deletions

View File

@@ -0,0 +1,141 @@
<script setup lang="ts">
import type { PropType } from 'vue';
import { onMounted, ref } from 'vue';
import { SyncOutlined } from '@ant-design/icons-vue';
import { Empty, InputSearch, Skeleton, Tree } from 'ant-design-vue';
import {communityTree} from "#/api/property/community";
import type {CommunityVO} from "#/api/property/community/model";
defineOptions({ inheritAttrs: false });
withDefaults(defineProps<{ showSearch?: boolean }>(), { showSearch: true });
const emit = defineEmits<{
/**
* 点击刷新按钮的事件
*/
reload: [];
/**
* 点击节点的事件
*/
select: [];
}>();
const selectRoomId = defineModel('selectRoomId', {
type: Array as PropType<string[]>,
default: () => [],
});
const checkedRoomId = defineModel('checkedRoomId', {
type: Array as PropType<string[]>,
default: () => [],
});
const searchValue = defineModel('searchValue', {
type: String,
default: '',
});
const checkable = defineModel('checkable', {
type: Boolean,
default: false,
});
/** 房间数据源 */
type RoomTreeArray = CommunityVO[];
const roomTreeArray = ref<RoomTreeArray>([]);
/** 骨架屏加载 */
const showTreeSkeleton = ref<boolean>(true);
async function loadTree() {
showTreeSkeleton.value = true;
searchValue.value = '';
selectRoomId.value = [];
checkedRoomId.value = [];
roomTreeArray.value = await communityTree(4);
showTreeSkeleton.value = false;
}
async function handleReload() {
await loadTree();
emit('reload');
}
function selectStyle(id:string){
if(selectRoomId.value.includes(id)){
return {
backgroundColor: '#e6f4ff',
padding:'0 10px',
borderRadius:'4px',
fontWeight:'bold'
}
}
return {}
}
onMounted(loadTree);
</script>
<template>
<div :class="$attrs.class">
<Skeleton
:loading="showTreeSkeleton"
:paragraph="{ rows: 8 }"
active
class="p-[8px]"
>
<div
class="bg-background flex h-full flex-col overflow-y-auto rounded-lg"
>
<!-- 固定在顶部 必须加上bg-background背景色 否则会产生'穿透'效果 -->
<div
v-if="showSearch"
class="bg-background z-100 sticky left-0 top-0 p-[8px]"
>
<InputSearch
v-model:value="searchValue"
:placeholder="$t('pages.common.search')"
size="small"
>
<template #enterButton>
<a-button @click="handleReload">
<SyncOutlined class="text-primary" />
</a-button>
</template>
</InputSearch>
</div>
<div class="h-full overflow-x-hidden px-[8px]">
<Tree
:checkable="checkable"
:selectable="false"
v-bind="$attrs"
v-if="roomTreeArray.length > 0"
v-model:checked-keys="checkedRoomId"
:class="$attrs.class"
:field-names="{ title: 'label', key: 'id' }"
:tree-data="roomTreeArray"
:virtual="false"
default-expand-all
>
<template #title="{ label,id }">
<span v-if="label.indexOf(searchValue) > -1" :style="selectStyle(id)">
{{ label.substring(0, label.indexOf(searchValue)) }}
<span style="color: #f50">{{ searchValue }}</span>
{{
label.substring(
label.indexOf(searchValue) + searchValue.length,
)
}}
</span>
<span v-else :style="selectStyle(id)">{{ label}}</span>
</template>
</Tree>
<!-- 仅本人数据权限 可以考虑直接不显示 -->
<div v-else class="mt-5">
<Empty
:image="Empty.PRESENTED_IMAGE_SIMPLE"
description="无部门数据"
/>
</div>
</div>
</div>
</Skeleton>
</div>
</template>

View File

@@ -47,12 +47,12 @@ export const columns: VxeGridProps['columns'] = [
return row.id return row.id
} }
}, },
width: 100 width: 180
}, },
{ {
title: '单位名称', title: '单位名称',
field: 'name', field: 'name',
width: 100 minWidth: 180
}, },
{ {
title: '单位类型', title: '单位类型',
@@ -72,7 +72,7 @@ export const columns: VxeGridProps['columns'] = [
{ {
title: '联系电话', title: '联系电话',
field: 'phone', field: 'phone',
width: 100 width: 120
}, },
// { // {
// title: '入驻位置', // title: '入驻位置',
@@ -82,7 +82,7 @@ export const columns: VxeGridProps['columns'] = [
{ {
title: '入驻时间', title: '入驻时间',
field: 'time', field: 'time',
width: 100, width: 180,
}, },
{ {
title: '状态', title: '状态',
@@ -98,19 +98,22 @@ export const columns: VxeGridProps['columns'] = [
{ {
title: '备注', title: '备注',
field: 'remark', field: 'remark',
width: 100, width: 180,
slots: { default: ({ row }) => {
return row.remark || '-'
}, },
}, },
{ {
title: '创建时间', title: '创建时间',
field: 'createTime', field: 'createTime',
width: 100, width: 180,
}, },
{ {
field: 'action', field: 'action',
fixed: 'right', fixed: 'right',
slots: { default: 'action' }, slots: { default: 'action' },
title: '操作', title: '操作',
minWidth: 180, width: 180,
}, },
] ]
@@ -155,13 +158,13 @@ export const modalSchema: FormSchemaGetter = () => [
z.number().int().min(1000000000).max(19999999999, { message: '手机号格式错误' }) z.number().int().min(1000000000).max(19999999999, { message: '手机号格式错误' })
]).transform(val => val.toString()), ]).transform(val => val.toString()),
}, },
{ // {
label: '入驻位置', // label: '入驻位置',
fieldName: 'locations', // fieldName: 'locations',
component: 'TreeSelect', // component: 'TreeSelect',
rules: 'selectRequired', // rules: 'selectRequired',
formItemClass: 'col-span-2' // formItemClass: 'col-span-2'
}, // },
{ {
label: '入驻时间', label: '入驻时间',
fieldName: 'time', fieldName: 'time',
@@ -173,11 +176,6 @@ export const modalSchema: FormSchemaGetter = () => [
}, },
rules: 'required', rules: 'required',
}, },
{
label: '',
fieldName: 'Placeholder',
component: ''
},
{ {
label: '授权期限', label: '授权期限',
fieldName: 'authTime', fieldName: 'authTime',
@@ -188,6 +186,7 @@ export const modalSchema: FormSchemaGetter = () => [
valueFormat: 'YYYY-MM-DD HH:mm:ss', valueFormat: 'YYYY-MM-DD HH:mm:ss',
}, },
rules: 'required', rules: 'required',
formItemClass: 'col-span-1'
}, },
{ {
label: '通行权限组', label: '通行权限组',

View File

@@ -12,6 +12,7 @@ import relativeTime from 'dayjs/plugin/relativeTime';
import {resident_unitInfo, authGroupList} from '#/api/property/resident/unit'; import {resident_unitInfo, authGroupList} from '#/api/property/resident/unit';
import {renderDict} from "#/utils/render"; import {renderDict} from "#/utils/render";
import RoomTree from "./components/room-tree.vue";
dayjs.extend(duration); dayjs.extend(duration);
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
@@ -39,55 +40,68 @@ async function handleOpenChange(open: boolean) {
authGroupName.value = authGroup.find(item => item.id === authGroupId)?.name; authGroupName.value = authGroup.find(item => item.id === authGroupId)?.name;
// 赋值 // 赋值
unitDetail.value = response; unitDetail.value = response;
if(response.location){
selectRoomId.value=response.location.split(',');
}
modalApi.modalLoading(false); modalApi.modalLoading(false);
} }
const selectRoomId = ref<string[]>([]);
</script> </script>
<template> <template>
<BasicModal :footer="false" :fullscreen-button="false" title="入驻单位信息" class="w-[70%]"> <BasicModal :footer="false" :fullscreen-button="false" title="入驻单位信息" class="w-[70%]">
<Descriptions v-if="unitDetail" size="small" :column="2" bordered :labelStyle="{width:'120px'}"> <div class="flex gap-[8px]">
<DescriptionsItem label="单位编号"> <div class="w-[260px]">
{{ unitDetail.id }} <RoomTree
</DescriptionsItem> class="max-h-[calc(100vh-40vh)]"
<DescriptionsItem label="单位名称"> v-model:select-room-id="selectRoomId"
{{ unitDetail.name }}
</DescriptionsItem>
<DescriptionsItem label="单位类型" v-if="unitDetail.type!=null">
<component
:is="renderDict(unitDetail.type,'wy_qylx')"
/> />
</DescriptionsItem> </div>
<DescriptionsItem label="联系人"> <Descriptions class="flex-1" v-if="unitDetail" size="middle" :column="1" bordered :labelStyle="{width:'120px'}">
{{ unitDetail.contactPerson +'-'+unitDetail.phone}} <DescriptionsItem label="单位编号">
</DescriptionsItem> {{ unitDetail.id }}
<DescriptionsItem label="入驻位置" :span="2"> </DescriptionsItem>
{{ unitDetail.locationDetail }} <DescriptionsItem label="单位名称">
</DescriptionsItem> {{ unitDetail.name }}
<DescriptionsItem label="入驻面积(㎡)" :span="2"> </DescriptionsItem>
{{ unitDetail.area }} <DescriptionsItem label="单位类型" v-if="unitDetail.type!=null">
</DescriptionsItem> <component
<DescriptionsItem label="入驻时间"> :is="renderDict(unitDetail.type,'wy_qylx')"
{{ unitDetail.time }} />
</DescriptionsItem> </DescriptionsItem>
<DescriptionsItem label="员工数量"> <DescriptionsItem label="联系人">
{{ unitDetail.number }} {{ unitDetail.contactPerson +'-'+unitDetail.phone}}
</DescriptionsItem> </DescriptionsItem>
<DescriptionsItem label="创建时间"> <!-- <DescriptionsItem label="入驻位置" >-->
{{ unitDetail.createTime ?? '-' }} <!-- {{ unitDetail.locationDetail }}-->
</DescriptionsItem> <!-- </DescriptionsItem>-->
<DescriptionsItem label="状态" v-if="unitDetail.state!=null"> <DescriptionsItem label="入驻面积">
<component <span v-if="unitDetail.area">{{ unitDetail.area }}</span>
:is="renderDict(unitDetail.state,'wy_state')" </DescriptionsItem>
/> <DescriptionsItem label="入驻时间">
</DescriptionsItem> {{ unitDetail.time }}
<DescriptionsItem label="通行权限组"> </DescriptionsItem>
{{ authGroupName }} <DescriptionsItem label="员工数量">
</DescriptionsItem> {{ unitDetail.number }}
<DescriptionsItem label="备注"> </DescriptionsItem>
{{ unitDetail.remark ?? '-' }} <DescriptionsItem label="创建时间">
</DescriptionsItem> {{ unitDetail.createTime ?? '-' }}
</Descriptions> </DescriptionsItem>
<DescriptionsItem label="状态" v-if="unitDetail.state!=null">
<component
:is="renderDict(unitDetail.state,'wy_state')"
/>
</DescriptionsItem>
<DescriptionsItem label="通行权限组">
{{ authGroupName }}
</DescriptionsItem>
<DescriptionsItem label="备注">
{{ unitDetail.remark ?? '-' }}
</DescriptionsItem>
</Descriptions>
</div>
</BasicModal> </BasicModal>
</template> </template>

View File

@@ -1,20 +1,21 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue'; import {computed, ref} from 'vue';
import { useVbenModal } from '@vben/common-ui'; import {useVbenModal} from '@vben/common-ui';
import { $t } from '@vben/locales'; import {$t} from '@vben/locales';
import { cloneDeep, handleNode } from '@vben/utils'; import {cloneDeep} from '@vben/utils';
import { useVbenForm } from '#/adapter/form'; import {useVbenForm} from '#/adapter/form';
import { import {
resident_unitAdd, resident_unitAdd,
resident_unitInfo, resident_unitInfo,
resident_unitUpdate, resident_unitUpdate,
} from '#/api/property/resident/unit'; } from '#/api/property/resident/unit';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup'; import {defaultFormValueGetter, useBeforeCloseDiff} from '#/utils/popup';
import { modalSchema } from './data'; import {modalSchema} from './data';
import { communityTree } from '#/api/property/community'; import RoomTree from "./components/room-tree.vue";
import {message} from "ant-design-vue";
const emit = defineEmits<{ reload: [] }>(); const emit = defineEmits<{ reload: [] }>();
@@ -26,7 +27,7 @@ const title = computed(() => {
const [BasicForm, formApi] = useVbenForm({ const [BasicForm, formApi] = useVbenForm({
commonConfig: { commonConfig: {
// 默认占满两列 // 默认占满两列
formItemClass: 'col-span-1', formItemClass: 'col-span-2',
// 默认label宽度 px // 默认label宽度 px
labelWidth: 100, labelWidth: 100,
// 通用配置项 会影响到所有表单项 // 通用配置项 会影响到所有表单项
@@ -39,7 +40,7 @@ const [BasicForm, formApi] = useVbenForm({
wrapperClass: 'grid-cols-2', wrapperClass: 'grid-cols-2',
}); });
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff( const {onBeforeClose, markInitialized, resetInitialized} = useBeforeCloseDiff(
{ {
initializedGetter: defaultFormValueGetter(formApi), initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi), currentGetter: defaultFormValueGetter(formApi),
@@ -59,16 +60,16 @@ const [BasicModal, modalApi] = useVbenModal({
} }
modalApi.modalLoading(true); modalApi.modalLoading(true);
const { id } = modalApi.getData() as { id?: number | string }; const {id} = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id; isUpdate.value = !!id;
await initLocationOptions();
if (isUpdate.value && id) { if (isUpdate.value && id) {
const record = await resident_unitInfo(id); const record = await resident_unitInfo(id);
let roomIds=record.location.split(',') let roomIds = record.location.split(',')
await formApi.setValues({...record, await formApi.setValues({
authTime:[record.authBegDate,record.authEndDate], ...record,
locations:roomIds authTime: [record.authBegDate, record.authEndDate],
}); });
checkedRoomId.value = roomIds
} }
await markInitialized(); await markInitialized();
@@ -79,7 +80,11 @@ const [BasicModal, modalApi] = useVbenModal({
async function handleConfirm() { async function handleConfirm() {
try { try {
modalApi.lock(true); modalApi.lock(true);
const { valid } = await formApi.validate(); const {valid} = await formApi.validate();
if (!checkedRoomId.value.length) {
message.error('请选择入驻位置');
return;
}
if (!valid) { if (!valid) {
return; return;
} }
@@ -88,7 +93,7 @@ async function handleConfirm() {
data.authBegDate = data.authTime[0]; data.authBegDate = data.authTime[0];
data.authEndDate = data.authTime[1]; data.authEndDate = data.authTime[1];
data.location=data.locations.join(',') data.location = checkedRoomId.value.join(',')
await (isUpdate.value ? resident_unitUpdate(data) : resident_unitAdd(data)); await (isUpdate.value ? resident_unitUpdate(data) : resident_unitAdd(data));
resetInitialized(); resetInitialized();
emit('reload'); emit('reload');
@@ -99,51 +104,26 @@ async function handleConfirm() {
modalApi.lock(false); modalApi.lock(false);
} }
} }
/**
* 入驻位置数据
*/
async function initLocationOptions() {
const locationList = await communityTree(4);
const splitStr = '/';
handleNode(locationList, 'label', splitStr, function (node: any) {
if (node.level != 4) {
node.disabled = true;
}
});
formApi.updateSchema([
{
componentProps: () => ({
class: 'w-full',
fieldNames: {
key: 'id',
label: 'label',
value: 'code',
children: 'children',
},
placeholder: '请选择入驻位置',
showSearch: true,
treeData: locationList,
treeDefaultExpandAll: true,
treeLine: { showLeafIcon: false },
// 筛选的字段
treeNodeFilterProp: 'label',
// 选中后显示在输入框的值
treeNodeLabelProp: 'fullName',
multiple:true
}),
fieldName: 'locations',
},
]);
}
async function handleClosed() { async function handleClosed() {
await formApi.resetForm(); await formApi.resetForm();
resetInitialized(); resetInitialized();
} }
const checkedRoomId = ref<string[]>([]);
</script> </script>
<template> <template>
<BasicModal :title="title"> <BasicModal :title="title">
<BasicForm> </BasicForm> <div class="flex gap-[8px]">
<div class="w-[260px]">
<RoomTree
:checkable="true"
class="max-h-[calc(100vh-45vh)]"
v-model:checked-room-id="checkedRoomId"
/>
</div>
<BasicForm class="flex-1"></BasicForm>
</div>
</BasicModal> </BasicModal>
</template> </template>