| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594 |
- <template>
- <QueryTableModal
- :open="open"
- :title="title"
- :width="width"
- :loading="loading"
- :filters="filters"
- :filter-schema="activeFilterSchema"
- :columns="activeColumns"
- :action-column="actionColumn"
- :data-source="tableData"
- :pagination="pagination"
- :total-pages="totalPages"
- :page-size-options="pageSizeOptions"
- :show-table-setting="false"
- :table-setting="{ size: true, setting: true, redo: false, fullScreen: false }"
- :can-fullscreen="true"
- :switch-options="resolvedModeOptions"
- :active-switch="activeMode"
- wrap-class-name="co-residence-detail-modal"
- @update:open="(value) => emit('update:open', value)"
- @search="handleSearch"
- @reset="handleReset"
- @prev-page="handlePrevPage"
- @next-page="handleNextPage"
- @page-size-change="handlePageSizeChange"
- @page-change="handlePageChange"
- @sort-change="handleSortChange"
- @switch-change="handleModeChange"
- />
- </template>
- <script lang="ts" setup>
- import { computed, nextTick, reactive, ref, watch } from 'vue';
- import QueryTableModal from '@jeesite/case/views/dm/components/QueryTableModal.vue';
- import { useMessage } from '@jeesite/core/hooks/web/useMessage';
- import componentSetting from '@jeesite/core/settings/componentSetting';
- import { useTimeSeriesJump } from '@jeesite/otg/hooks/useTimeSeriesJump';
- import { hotelStayInfoDetail, togetherLiveInfoDetail } from '@jeesite/track/api/trackApi';
- import type { HotelStayInfo, TogetherLiveInfo } from '@jeesite/track/api/types/dto';
- import type { TogetherLiveDetailQuery } from '@jeesite/track/api/types/query';
- type DetailMode = 'hotelStay' | 'togetherLive';
- interface ModeOption {
- label: string;
- value: DetailMode;
- disabled?: boolean;
- }
- interface FilterSchemaItem {
- field: string;
- label: string;
- type: 'input' | 'date' | 'date-range' | 'select' | 'compare-input';
- placeholder?: string;
- width?: number;
- maxWidth?: number;
- startField?: string;
- endField?: string;
- rangePlaceholder?: [string, string];
- valueFormat?: string;
- }
- const props = withDefaults(
- defineProps<{
- open: boolean;
- title?: string;
- width?: number;
- baseQuery?: Partial<TogetherLiveDetailQuery>;
- initialMode?: DetailMode;
- modeOptions?: ReadonlyArray<ModeOption>;
- }>(),
- {
- title: '同住记录',
- width: 1460,
- baseQuery: () => ({}),
- initialMode: 'hotelStay',
- modeOptions: () => [],
- },
- );
- const emit = defineEmits<{
- (e: 'update:open', value: boolean): void;
- }>();
- const { createMessage } = useMessage();
- const { jumping, jumpToBehaviorSpacetime } = useTimeSeriesJump();
- const loading = ref(false);
- const tableData = ref<any[]>([]);
- const activeMode = ref<DetailMode>(props.initialMode);
- const filters = reactive({
- hotelName: '',
- inStartDate: '',
- inEndDate: '',
- outStartDate: '',
- outEndDate: '',
- orderKey: '',
- sort: '' as '' | 'ASC' | 'DESC',
- });
- const pagination = reactive({
- page: 1,
- limit: componentSetting.table.defaultPageSize,
- total: 0,
- });
- const totalPages = computed(() => {
- if (!pagination.total) return 1;
- return Math.max(1, Math.ceil(pagination.total / pagination.limit));
- });
- const pageSizeOptions = componentSetting.table.pageSizeOptions.map((value) => ({
- label: value,
- value: Number(value),
- }));
- const resolvedModeOptions = computed<ReadonlyArray<ModeOption>>(() => {
- if (props.modeOptions.length) return props.modeOptions;
- return [
- { label: '旅馆住宿信息', value: 'hotelStay' },
- { label: '同住服务', value: 'togetherLive' },
- ];
- });
- const commonFilterSchema: ReadonlyArray<FilterSchemaItem> = [
- { field: 'hotelName', label: '旅店名称', type: 'input', placeholder: '请输入旅店名称', width: 220 },
- {
- field: 'checkinRange',
- label: '入住时间',
- type: 'date-range',
- startField: 'inStartDate',
- endField: 'inEndDate',
- width: 280,
- maxWidth: 320,
- rangePlaceholder: ['开始日期', '结束日期'],
- valueFormat: 'YYYY-MM-DD',
- },
- {
- field: 'checkoutRange',
- label: '退房时间',
- type: 'date-range',
- startField: 'outStartDate',
- endField: 'outEndDate',
- width: 280,
- maxWidth: 320,
- rangePlaceholder: ['开始日期', '结束日期'],
- valueFormat: 'YYYY-MM-DD',
- },
- ];
- const hotelStayColumns = [
- { title: '序号', dataIndex: 'id', key: 'id', width: 70, align: 'center' as const },
- { title: '身份证明名称', dataIndex: 'personCertName', key: 'personCertName', width: 140, align: 'center' as const },
- {
- title: '身份证号码',
- dataIndex: 'personCertNo',
- key: 'personCertNo',
- width: 180,
- align: 'center' as const,
- },
- { title: '姓名', dataIndex: 'personName', key: 'personName', width: 120, align: 'center' as const },
- { title: '性别', dataIndex: 'gender', key: 'gender', width: 90, align: 'center' as const },
- { title: '住址省市县区', dataIndex: 'addressCity', key: 'addressCity', width: 180, align: 'center' as const },
- { title: '住址详情', dataIndex: 'addressDetail', key: 'addressDetail', width: 220, align: 'center' as const },
- { title: '旅店名称', dataIndex: 'hotelName', key: 'hotelName', width: 180, align: 'center' as const },
- { title: '旅店详细地址', dataIndex: 'hotelAddress', key: 'hotelAddress', width: 220, align: 'center' as const },
- { title: '入住房号', dataIndex: 'roomNo', key: 'roomNo', width: 120, align: 'center' as const },
- {
- title: '入住时间',
- dataIndex: 'inDateTime',
- key: 'inDateTime',
- width: 180,
- align: 'center' as const,
- sorter: true,
- },
- {
- title: '退房时间',
- dataIndex: 'outDateTime',
- key: 'outDateTime',
- width: 180,
- align: 'center' as const,
- sorter: true,
- },
- ];
- const togetherLiveColumns = [
- { title: '序号', dataIndex: 'id', key: 'id', width: 70, align: 'center' as const },
- { title: '被调查对象', dataIndex: 'queryName', key: 'queryName', width: 120, align: 'center' as const },
- {
- title: '被调查对象证件号',
- dataIndex: 'queryCertNo',
- key: 'queryCertNo',
- width: 180,
- align: 'center' as const,
- },
- { title: '同住人姓名', dataIndex: 'togetherName', key: 'togetherName', width: 120, align: 'center' as const },
- {
- title: '同住人证件号',
- dataIndex: 'togetherCertNo',
- key: 'togetherCertNo',
- width: 180,
- align: 'center' as const,
- },
- {
- title: '同住人证件类型',
- dataIndex: 'togetherCertType',
- key: 'togetherCertType',
- width: 140,
- align: 'center' as const,
- },
- { title: '同住人性别', dataIndex: 'togetherGender', key: 'togetherGender', width: 110, align: 'center' as const },
- { title: '同住人年龄', dataIndex: 'togetherAge', key: 'togetherAge', width: 110, align: 'center' as const },
- {
- title: '同住人民族',
- dataIndex: 'togetherEthnicity',
- key: 'togetherEthnicity',
- width: 120,
- align: 'center' as const,
- },
- {
- title: '同住人籍贯',
- dataIndex: 'togetherOrigin',
- key: 'togetherOrigin',
- width: 160,
- align: 'center' as const,
- },
- { title: '旅店名称', dataIndex: 'hotelName', key: 'hotelName', width: 180, align: 'center' as const },
- { title: '入住房号', dataIndex: 'roomNo', key: 'roomNo', width: 120, align: 'center' as const },
- { title: '同住次数', dataIndex: 'stayCount', key: 'stayCount', width: 110, align: 'center' as const },
- { title: '同住类型', dataIndex: 'coType', key: 'coType', width: 120, align: 'center' as const },
- {
- title: '入住时间',
- dataIndex: 'inDateTime',
- key: 'inDateTime',
- width: 180,
- align: 'center' as const,
- sorter: true,
- },
- {
- title: '退房时间',
- dataIndex: 'outDateTime',
- key: 'outDateTime',
- width: 180,
- align: 'center' as const,
- sorter: true,
- },
- { title: '备注', dataIndex: 'remark', key: 'remark', width: 160, align: 'center' as const },
- ];
- const activeFilterSchema = computed(() => commonFilterSchema);
- const activeColumns = computed(() => (activeMode.value === 'hotelStay' ? hotelStayColumns : togetherLiveColumns));
- const normalizeActionRecordId = (value: unknown) => {
- const text = String(value ?? '').trim();
- if (!text || text === '-' || text === 'null' || text === 'undefined') return undefined;
- return text;
- };
- const resolveJumpPersonName = (record?: HotelStayInfo | TogetherLiveInfo) => {
- const personName = pickFirstValue(
- record?.personName,
- (record as TogetherLiveInfo | undefined)?.queryName,
- props.baseQuery?.personName,
- props.baseQuery?.objName,
- );
- const normalizedName = String(personName ?? '').trim();
- return normalizedName || undefined;
- };
- const buildJumpQuery = (record?: HotelStayInfo | TogetherLiveInfo) => {
- const id = normalizeActionRecordId(record?.id);
- const personName = resolveJumpPersonName(record);
- const togetherName = pickFirstValue(
- (record as TogetherLiveInfo | undefined)?.togetherName,
- props.baseQuery?.liveWithName,
- );
- const keyword = activeMode.value === 'togetherLive' ? String(togetherName ?? '').trim() || undefined : undefined;
- const seriesDateTime = String(record?.inDateTime || record?.checkinTime || '').trim() || undefined;
- return {
- hasLocal: 'N',
- tableName: 'hotel_stay_info',
- tableNameEn: 'hotel_stay_info',
- tableNameCn: '住宿信息',
- id,
- rowId: id,
- personName,
- personNames: personName ? [personName] : undefined,
- keyword,
- seriesDateTime,
- };
- };
- const handleJump = async (record?: HotelStayInfo | TogetherLiveInfo) => {
- await jumpToBehaviorSpacetime(buildJumpQuery(record), {
- onBeforeNavigate: () => {
- emit('update:open', false);
- },
- });
- };
- const actionColumn = computed(() => ({
- title: '操作',
- dataIndex: 'action',
- key: 'action',
- width: 90,
- fixed: 'right',
- align: 'center',
- actions: (record: { raw?: HotelStayInfo | TogetherLiveInfo }) => {
- const rawRecord = record.raw;
- const query = buildJumpQuery(rawRecord);
- return [
- {
- label: '跳转',
- disabled: jumping.value || !query.id,
- onClick: () => void handleJump(rawRecord),
- },
- ];
- },
- }));
- const normalizeList = <T = any,>(resp: any): T[] => {
- if (Array.isArray(resp)) return resp;
- if (Array.isArray(resp?.list)) return resp.list;
- if (Array.isArray(resp?.data)) return resp.data;
- if (Array.isArray(resp?.data?.list)) return resp.data.list;
- if (Array.isArray(resp?.records)) return resp.records;
- if (Array.isArray(resp?.data?.records)) return resp.data.records;
- return [];
- };
- const normalizeTotal = (resp: any, listLen: number) => {
- const total = resp?.total ?? resp?.data?.total ?? resp?.count ?? resp?.data?.count ?? listLen;
- return Number.isFinite(Number(total)) ? Number(total) : listLen;
- };
- const normalizeCurrent = (resp: any, fallback: number) => {
- const current = resp?.current ?? resp?.data?.current ?? fallback;
- return Number.isFinite(Number(current)) && Number(current) > 0 ? Number(current) : fallback;
- };
- const normalizeSize = (resp: any, fallback: number) => {
- const size = resp?.size ?? resp?.data?.size ?? fallback;
- return Number.isFinite(Number(size)) && Number(size) > 0 ? Number(size) : fallback;
- };
- const fallbackValue = (value: string | number | null | undefined) => {
- if (value === 0) return '0';
- if (value === null || value === undefined || value === '') return '-';
- return String(value);
- };
- const pickFirstValue = (...values: Array<string | number | null | undefined>) => {
- for (const value of values) {
- if (value === 0) return value;
- if (value === null || value === undefined) continue;
- if (String(value).trim() === '') continue;
- return value;
- }
- return undefined;
- };
- const formatBoundary = (value?: string, isEnd = false) => {
- if (!value) return undefined;
- return `${value} ${isEnd ? '23:59:59' : '00:00:00'}`;
- };
- const buildQuery = (): TogetherLiveDetailQuery => {
- const hotelName = filters.hotelName || props.baseQuery?.hotelName || undefined;
- const query: TogetherLiveDetailQuery = {
- ...props.baseQuery,
- page: pagination.page,
- limit: pagination.limit,
- hotelName,
- search: hotelName || props.baseQuery?.search || undefined,
- inStartTime: formatBoundary(filters.inStartDate, false) || props.baseQuery?.inStartTime,
- inEndTime: formatBoundary(filters.inEndDate, true) || props.baseQuery?.inEndTime,
- outStartTime: formatBoundary(filters.outStartDate, false) || props.baseQuery?.outStartTime,
- outEndTime: formatBoundary(filters.outEndDate, true) || props.baseQuery?.outEndTime,
- orderKey: filters.orderKey || undefined,
- sort: filters.sort || undefined,
- };
- return query;
- };
- const mapHotelStayRow = (item: HotelStayInfo, index: number) => ({
- rowKey: item.id || `${pagination.page}-${index}-${item.personName || ''}-${item.hotelName || ''}`,
- id: (pagination.page - 1) * pagination.limit + index + 1,
- personCertName: fallbackValue(item.personCertName),
- personCertNo: fallbackValue(item.personCertNo),
- personName: fallbackValue(item.personName),
- gender: fallbackValue(item.gender),
- addressCity: fallbackValue(item.addressCity),
- addressDetail: fallbackValue(item.addressDetail),
- hotelName: fallbackValue(item.hotelName),
- hotelAddress: fallbackValue(item.hotelAddress),
- roomNo: fallbackValue(item.roomNo),
- inDateTime: fallbackValue(item.inDateTime || item.checkinTime),
- outDateTime: fallbackValue(item.outDateTime || item.checkoutTime),
- raw: item,
- });
- const mapTogetherLiveRow = (item: TogetherLiveInfo, index: number) => ({
- rowKey:
- item.id ||
- `${pagination.page}-${index}-${item.queryCertNo || item.personCertNo || ''}-${item.togetherName || ''}`,
- id: (pagination.page - 1) * pagination.limit + index + 1,
- queryName: fallbackValue(
- pickFirstValue(item.queryName, item.personName, props.baseQuery?.objName, props.baseQuery?.personName),
- ),
- queryCertNo: fallbackValue(
- pickFirstValue(item.queryCertNo, item.personCertNo, props.baseQuery?.objIdCard, props.baseQuery?.personCertNo),
- ),
- togetherName: fallbackValue(pickFirstValue(item.togetherName, props.baseQuery?.liveWithName)),
- togetherCertNo: fallbackValue(
- pickFirstValue(item.togetherCertNo, props.baseQuery?.liveWithCertNo, props.baseQuery?.liveWithIdCard),
- ),
- togetherCertType: fallbackValue(item.togetherCertType),
- togetherGender: fallbackValue(item.togetherGender),
- togetherAge: fallbackValue(item.togetherAge),
- togetherEthnicity: fallbackValue(item.togetherEthnicity),
- togetherOrigin: fallbackValue(item.togetherOrigin),
- hotelName: fallbackValue(item.hotelName),
- roomNo: fallbackValue(item.roomNo),
- stayCount: Number.isFinite(Number(item.stayCount)) ? Number(item.stayCount) : fallbackValue(item.stayCount),
- coType: fallbackValue(item.coType),
- inDateTime: fallbackValue(item.inDateTime || item.checkinTime),
- outDateTime: fallbackValue(item.outDateTime || item.checkoutTime),
- remark: fallbackValue(item.remark),
- raw: item,
- });
- const fetchTable = async () => {
- loading.value = true;
- try {
- const resp =
- activeMode.value === 'hotelStay'
- ? await hotelStayInfoDetail(buildQuery())
- : await togetherLiveInfoDetail(buildQuery());
- const list =
- activeMode.value === 'hotelStay' ? normalizeList<HotelStayInfo>(resp) : normalizeList<TogetherLiveInfo>(resp);
- tableData.value =
- activeMode.value === 'hotelStay'
- ? list.map((item, index) => mapHotelStayRow(item as HotelStayInfo, index))
- : list.map((item, index) => mapTogetherLiveRow(item as TogetherLiveInfo, index));
- pagination.total = normalizeTotal(resp, list.length);
- pagination.page = normalizeCurrent(resp, pagination.page);
- pagination.limit = normalizeSize(resp, pagination.limit);
- } catch (error) {
- tableData.value = [];
- pagination.total = 0;
- createMessage.warning('同住详情接口调用失败');
- } finally {
- loading.value = false;
- }
- };
- const syncFiltersFromBaseQuery = () => {
- filters.hotelName = props.baseQuery?.hotelName || '';
- filters.inStartDate = '';
- filters.inEndDate = '';
- filters.outStartDate = '';
- filters.outEndDate = '';
- filters.orderKey = '';
- filters.sort = '';
- };
- const handleSearch = async () => {
- pagination.page = 1;
- await fetchTable();
- };
- const handleReset = async () => {
- syncFiltersFromBaseQuery();
- pagination.page = 1;
- await fetchTable();
- };
- const handlePrevPage = async () => {
- if (loading.value || pagination.page <= 1) return;
- pagination.page -= 1;
- await fetchTable();
- };
- const handleNextPage = async () => {
- if (loading.value || pagination.page >= totalPages.value) return;
- pagination.page += 1;
- await fetchTable();
- };
- const handlePageChange = async (page: number, pageSize: number) => {
- if (loading.value) return;
- if (pageSize !== pagination.limit) {
- await handlePageSizeChange(pageSize);
- return;
- }
- if (page === pagination.page - 1) {
- await handlePrevPage();
- return;
- }
- if (page === pagination.page + 1) {
- await handleNextPage();
- return;
- }
- if (page === pagination.page) return;
- pagination.page = page;
- await fetchTable();
- };
- const handlePageSizeChange = async (size: number) => {
- pagination.limit = Number(size);
- pagination.page = 1;
- await fetchTable();
- };
- const handleSortChange = async (payload: { orderKey: string; sort: 'ASC' | 'DESC' }) => {
- filters.orderKey = payload.orderKey;
- filters.sort = payload.sort;
- pagination.page = 1;
- await fetchTable();
- };
- const handleModeChange = async (value: string | number) => {
- const nextMode = String(value) as DetailMode;
- if (nextMode === activeMode.value) return;
- activeMode.value = nextMode;
- filters.orderKey = '';
- filters.sort = '';
- pagination.page = 1;
- await fetchTable();
- };
- const initAndFetch = async () => {
- activeMode.value = props.initialMode;
- pagination.page = 1;
- syncFiltersFromBaseQuery();
- await fetchTable();
- };
- let initFetchScheduled = false;
- const scheduleInitAndFetch = () => {
- if (!props.open || initFetchScheduled) return;
- initFetchScheduled = true;
- nextTick(async () => {
- initFetchScheduled = false;
- if (!props.open) return;
- await initAndFetch();
- });
- };
- watch(
- () => props.open,
- (open) => {
- if (!open) {
- initFetchScheduled = false;
- return;
- }
- scheduleInitAndFetch();
- },
- );
- watch(
- () => props.baseQuery,
- () => {
- if (!props.open) return;
- scheduleInitAndFetch();
- },
- { deep: true },
- );
- watch(
- () => props.initialMode,
- (mode) => {
- if (!props.open) return;
- activeMode.value = mode;
- scheduleInitAndFetch();
- },
- );
- defineExpose({
- reload: fetchTable,
- });
- </script>
- <style lang="less">
- .co-residence-detail-modal {
- .query-table-search {
- row-gap: 12px;
- }
- }
- </style>
|