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

View File

@@ -12,6 +12,7 @@ import relativeTime from 'dayjs/plugin/relativeTime';
import {resident_unitInfo, authGroupList} from '#/api/property/resident/unit';
import {renderDict} from "#/utils/render";
import RoomTree from "./components/room-tree.vue";
dayjs.extend(duration);
dayjs.extend(relativeTime);
@@ -39,15 +40,27 @@ async function handleOpenChange(open: boolean) {
authGroupName.value = authGroup.find(item => item.id === authGroupId)?.name;
// 赋值
unitDetail.value = response;
if(response.location){
selectRoomId.value=response.location.split(',');
}
modalApi.modalLoading(false);
}
const selectRoomId = ref<string[]>([]);
</script>
<template>
<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]">
<div class="w-[260px]">
<RoomTree
class="max-h-[calc(100vh-40vh)]"
v-model:select-room-id="selectRoomId"
/>
</div>
<Descriptions class="flex-1" v-if="unitDetail" size="middle" :column="1" bordered :labelStyle="{width:'120px'}">
<DescriptionsItem label="单位编号">
{{ unitDetail.id }}
</DescriptionsItem>
@@ -62,11 +75,11 @@ async function handleOpenChange(open: boolean) {
<DescriptionsItem label="联系人">
{{ unitDetail.contactPerson +'-'+unitDetail.phone}}
</DescriptionsItem>
<DescriptionsItem label="入驻位置" :span="2">
{{ unitDetail.locationDetail }}
</DescriptionsItem>
<DescriptionsItem label="入驻面积(㎡)" :span="2">
{{ unitDetail.area }}
<!-- <DescriptionsItem label="入驻位置" >-->
<!-- {{ unitDetail.locationDetail }}-->
<!-- </DescriptionsItem>-->
<DescriptionsItem label="入驻面积">
<span v-if="unitDetail.area">{{ unitDetail.area }}</span>
</DescriptionsItem>
<DescriptionsItem label="入驻时间">
{{ unitDetail.time }}
@@ -89,5 +102,6 @@ async function handleOpenChange(open: boolean) {
{{ unitDetail.remark ?? '-' }}
</DescriptionsItem>
</Descriptions>
</div>
</BasicModal>
</template>

View File

@@ -3,7 +3,7 @@ import { computed, ref } from 'vue';
import {useVbenModal} from '@vben/common-ui';
import {$t} from '@vben/locales';
import { cloneDeep, handleNode } from '@vben/utils';
import {cloneDeep} from '@vben/utils';
import {useVbenForm} from '#/adapter/form';
import {
@@ -14,7 +14,8 @@ import {
import {defaultFormValueGetter, useBeforeCloseDiff} from '#/utils/popup';
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: [] }>();
@@ -26,7 +27,7 @@ const title = computed(() => {
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
// 默认占满两列
formItemClass: 'col-span-1',
formItemClass: 'col-span-2',
// 默认label宽度 px
labelWidth: 100,
// 通用配置项 会影响到所有表单项
@@ -61,14 +62,14 @@ const [BasicModal, modalApi] = useVbenModal({
const {id} = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id;
await initLocationOptions();
if (isUpdate.value && id) {
const record = await resident_unitInfo(id);
let roomIds = record.location.split(',')
await formApi.setValues({...record,
await formApi.setValues({
...record,
authTime: [record.authBegDate, record.authEndDate],
locations:roomIds
});
checkedRoomId.value = roomIds
}
await markInitialized();
@@ -80,6 +81,10 @@ async function handleConfirm() {
try {
modalApi.lock(true);
const {valid} = await formApi.validate();
if (!checkedRoomId.value.length) {
message.error('请选择入驻位置');
return;
}
if (!valid) {
return;
}
@@ -88,7 +93,7 @@ async function handleConfirm() {
data.authBegDate = data.authTime[0];
data.authEndDate = data.authTime[1];
data.location=data.locations.join(',')
data.location = checkedRoomId.value.join(',')
await (isUpdate.value ? resident_unitUpdate(data) : resident_unitAdd(data));
resetInitialized();
emit('reload');
@@ -99,51 +104,26 @@ async function handleConfirm() {
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() {
await formApi.resetForm();
resetInitialized();
}
const checkedRoomId = ref<string[]>([]);
</script>
<template>
<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>
</template>