| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523 |
- <template>
- <fs-page class="borrow-page">
- <!-- <fs-page> -->
- <fs-crud ref="crudRef" v-bind="crudBinding" />
- <BorrowTypeSelect v-model:visible="showBorrowTypeDialog" @select="onBorrowTypeSelected" />
- <CommonBorrow
- v-if="showCommonBorrowDialog"
- v-model:visible="showCommonBorrowDialog"
- :modelValue="borrowForm"
- @submit="handleBorrowSubmit"
- @refresh="crudExpose.doRefresh()"
- />
- <ClassroomBorrow
- v-if="showClassroomBorrowDialog"
- v-model:visible="showClassroomBorrowDialog"
- :modelValue="borrowForm"
- :lastFormSnapshot="lastFormSnapshot"
- @update:lastFormSnapshot="val => lastFormSnapshot = val"
- @submit="handleBorrowSubmit"
- />
- <SpecialBorrow v-if="showSpecialBorrowDialog" v-model:visible="showSpecialBorrowDialog" :modelValue="borrowForm" @submit="handleBorrowSubmit" />
- <CollectEquipmentDialog v-model:visible="showCollectDialog" :modelValue="borrowForm" @submit="handleCollectSubmit" />
- <SettlementDialog v-model:visible="showSettlementDialog" :modelValue="borrowForm" />
- </fs-page>
- </template>
- <script lang="ts" setup name="borrow">
- import { ref, onMounted, onActivated, onBeforeUnmount, nextTick, provide } from 'vue';
- import { useFs } from '@fast-crud/fast-crud';
- import { createCrudOptions } from './crud';
- // import { GetPermission } from './api';
- // import { handleColumnPermission } from '/@/utils/columnPermission';
- import BorrowTypeSelect from './component/BorrowTypeSelect/index.vue';
- import CommonBorrow from './component/CommonBorrow/index.vue';
- import ClassroomBorrow from './component/ClassroomBorrow/index.vue';
- import SpecialBorrow from './component/SpecialBorrow/index.vue';
- import CollectEquipmentDialog from './component/CollectEquipment/index.vue';
- import SettlementDialog from './component/CollectEquipment/SettlementDialog.vue';
- import * as api from './api';
- import { ElMessage } from 'element-plus';
- import { useRoute, onBeforeRouteUpdate } from 'vue-router';
- import type { LocationQuery } from 'vue-router';
- const route = useRoute();
- function firstQueryString(v: unknown): string {
- if (v == null) return '';
- if (Array.isArray(v)) {
- const x = v[0];
- return x != null ? String(x) : '';
- }
- return String(v);
- }
- const BORROW_LIST_DEBUG = true;
- /** 路由 query → 借用列表搜索表单(与 crud 列 status / status__in 一致;禁止重复 status 键) */
- function buildBorrowListSearchFormFromRoute(q: LocationQuery) {
- const raw = firstQueryString(
- (q as Record<string, unknown>).status__in ?? q.status_in ?? q.status
- ).trim();
- if (BORROW_LIST_DEBUG) {
- // eslint-disable-next-line no-console
- console.log('[borrow/list] 路由 query', { ...q }, '→ 原始 status 链:', raw);
- }
- if (raw === '') {
- const empty = { status: undefined, status__in: undefined };
- if (BORROW_LIST_DEBUG) {
- // eslint-disable-next-line no-console
- console.log('[borrow/list] 无 status 条件 → doSearch form', empty);
- }
- return empty;
- }
- /* 多状态:逗号分隔,对应表单列 status__in */
- if (raw.includes(',')) {
- const f = { status: undefined, status__in: raw };
- if (BORROW_LIST_DEBUG) {
- // eslint-disable-next-line no-console
- console.log('[borrow/list] 多状态(含逗号)→ form.status__in =', f.status__in);
- }
- return f;
- }
- const num = Number(raw);
- if (Number.isFinite(num) && num > 0) {
- const f = { status: num, status__in: undefined };
- if (BORROW_LIST_DEBUG) {
- // eslint-disable-next-line no-console
- console.log('[borrow/list] 单状态(数字)→ form.status =', f.status);
- }
- return f;
- }
- const f = { status: raw, status__in: undefined };
- if (BORROW_LIST_DEBUG) {
- // eslint-disable-next-line no-console
- console.log('[borrow/list] 单状态(非纯数字)→ form.status =', f.status);
- }
- return f;
- }
- const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
- const showBorrowTypeDialog = ref(false);
- const showCommonBorrowDialog = ref(false);
- const showClassroomBorrowDialog = ref(false);
- const showSpecialBorrowDialog = ref(false);
- const showCollectDialog = ref(false);
- const borrowForm = ref({});
- const showSettlementDialog = ref(false);
- /** keep-alive 下首次进入会先后触发 onMounted 与 onActivated,避免重复 doSearch */
- const skipNextActivatedBorrowSearch = ref(false);
- /** 与 onActivated 协调:避免 keep-alive 首次 onMounted + onActivated 连续请求两次 */
- function applyRouteSearchToCrud() {
- skipNextActivatedBorrowSearch.value = true;
- crudExpose.doSearch({
- form: buildBorrowListSearchFormFromRoute(route.query),
- });
- }
- const lastFormSnapshot = ref({});
- // provide('lastFormSnapshot', lastFormSnapshot);
- /** 在响应中递归查找「疑似借用单」主键;expectedBorrowType 为 1 课堂 / 2 特殊 等,与列表性质一致,避免误取 user.id */
- function findBorrowApplicationIdInResponseTree(
- res: any,
- expectedBorrowType: number
- ): string | number | null {
- const s = new WeakSet<object>();
- const want = Number(expectedBorrowType);
- function walk(n: any, depth: number): string | number | null {
- if (n == null || depth > 8) return null;
- if (typeof n !== 'object') return null;
- if (s.has(n as object) && !Array.isArray(n)) return null;
- s.add(n as object);
- if (Array.isArray(n)) {
- for (const item of n) {
- const f = walk(item, depth + 1);
- if (f != null) return f;
- }
- return null;
- }
- const o = n as any;
- if (o.id != null) {
- const hasNo = o.application_no != null && String(o.application_no).trim() !== '';
- const hasBt = o.borrow_type != null;
- const hasPurpose = o.purpose != null && String(o.purpose).trim() !== '';
- const nbt = Number(o.borrow_type);
- if (
- hasNo ||
- (hasBt &&
- hasPurpose &&
- !Number.isNaN(nbt) &&
- nbt === want)
- ) {
- return o.id;
- }
- }
- for (const v of Object.values(n)) {
- if (v && typeof v === 'object') {
- const f = walk(v, depth + 1);
- if (f != null) return f;
- }
- }
- return null;
- }
- return walk(res, 0);
- }
- /** 从「新建借用单」POST 标准返回中尽量解析出新建单 id(兼容 data 为对象/数字/数组及常见字段名) */
- function parseNewBorrowApplicationId(res: any): string | number | null {
- if (!res) return null;
- if (res.id != null) return res.id;
- const d = res.data;
- if (d == null) return null;
- if (typeof d === 'number') return d;
- if (typeof d === 'string' && d.trim() !== '' && !Number.isNaN(Number(d))) {
- return Number(d);
- }
- if (Array.isArray(d) && d.length > 0) {
- const r0 = d[0] as any;
- if (r0?.id != null) return r0.id;
- }
- if (typeof d === 'object') {
- const id =
- d.id ?? d.pk ?? d.application_id ?? d.applicationId ?? d.bid;
- if (id != null) return id;
- if (Array.isArray((d as any).results) && (d as any).results[0]?.id != null) {
- return (d as any).results[0].id;
- }
- const inner = d.data;
- if (inner && typeof inner === 'object' && (inner as any).id != null) return (inner as any).id;
- }
- return null;
- }
- function listResponseRecords(lr: any): any[] {
- const d = lr?.data;
- if (!d) return [];
- if (Array.isArray(d.items)) return d.items;
- if (Array.isArray(d.results)) return d.results;
- if (Array.isArray(d.records)) return d.records;
- if (Array.isArray(d)) return d;
- return [];
- }
- /** 用关键字搜索 + 单号 / 用途 等在列表中匹配新建单;borrowType 与借用性质(1 课堂 2 特殊…)一致 */
- async function searchBorrowIdByList(params: {
- search?: string;
- formPurpose?: string;
- formExpectedStart?: string;
- borrowType: number;
- }): Promise<string | number | null> {
- const { search, formPurpose, formExpectedStart, borrowType } = params;
- const base = {
- page: 1,
- limit: 30,
- borrow_type: Number(borrowType),
- ordering: '-create_datetime',
- } as Record<string, any>;
- try {
- if (search && String(search).trim() !== '') {
- const lr: any = await api.GetList({
- ...base,
- search: String(search).trim(),
- } as any);
- const rows = listResponseRecords(lr);
- if (search) {
- const t = String(search).trim();
- const byNo = rows.find(
- (r: any) => r && String(r.application_no) === t
- );
- if (byNo?.id != null) return byNo.id;
- }
- }
- // 无单号时:取同性质借用最近一页,用「用途」+ 预计开始日(若有)弱匹配
- const lr2: any = await api.GetList(base as any);
- const rows = listResponseRecords(lr2);
- if (!rows.length) return null;
- if (formPurpose) {
- const p = String(formPurpose).trim();
- const m = rows.find((r: any) => r && String(r.purpose) === p);
- if (m?.id != null) return m.id;
- }
- if (formExpectedStart) {
- const day = String(formExpectedStart).slice(0, 10);
- const m = rows.find(
- (r: any) =>
- r &&
- r.expected_start_time &&
- String(r.expected_start_time).slice(0, 10) === day
- );
- if (m?.id != null) return m.id;
- }
- // 新建立即查询时,多并发下非 100% 可靠;与后端约定在 POST 中回 id 可彻底避免
- return rows[0]?.id ?? null;
- } catch {
- return null;
- }
- }
- /**
- * 从 POST 返回 + 表单快照解析新建单主键,供直接打开「领取」(课堂/特殊等,由 borrow_type 区分)
- */
- async function resolveNewBorrowApplicationIdAfterAdd(
- res: any,
- submitted: Record<string, any>
- ): Promise<string | number | null> {
- const borrowType = Number(submitted?.borrow_type);
- let id = parseNewBorrowApplicationId(res);
- if (id != null) return id;
- if (!Number.isNaN(borrowType)) {
- id = findBorrowApplicationIdInResponseTree(res, borrowType);
- if (id != null) return id;
- }
- const d = res?.data;
- const appNoFromRes =
- d && typeof d === 'object' ? d.application_no ?? d.applicationNo : null;
- if (appNoFromRes && String(appNoFromRes).trim() !== '' && !Number.isNaN(borrowType)) {
- id = await searchBorrowIdByList({
- search: String(appNoFromRes).trim(),
- borrowType,
- });
- if (id != null) return id;
- }
- if (submitted?.application_no && !Number.isNaN(borrowType)) {
- id = await searchBorrowIdByList({
- search: String(submitted.application_no),
- borrowType,
- });
- if (id != null) return id;
- }
- return await searchBorrowIdByList({
- formPurpose: submitted?.purpose,
- formExpectedStart: submitted?.expected_start_time,
- borrowType: Number.isNaN(borrowType) ? 1 : borrowType,
- });
- }
- function onBorrowTypeSelected(type: number, mode: 'view' | 'edit' | 'add' |'collect',record?: any) {
- showBorrowTypeDialog.value = false;
- // 必须先同步写入 borrowForm,再打开子弹窗;若在 nextTick 中赋值,子组件挂载时仍拿到上一次的查看/编辑数据,会误拉详情并残留在表单中
- borrowForm.value = { ...(record || {}), borrow_type: type, mode };
- if (type === 1) {
- if (mode === 'view') {
- showCollectDialog.value = true;
- } else {
- showClassroomBorrowDialog.value = true;
- }
- } else if (type === 2) {
- if (mode === 'view') {
- showCollectDialog.value = true;
- } else {
- showSpecialBorrowDialog.value = true;
- }
- } else if (type === 0) {
- if (mode === 'view') {
- showCollectDialog.value = true;
- } else {
- showCommonBorrowDialog.value = true;
- }
- }
- }
- async function handleBorrowSubmit(formData: any) {
- try {
- if (formData.mode === 'edit') {
- await api.UpdateObj(formData);
- ElMessage.success('编辑成功');
- } else {
- const res: any = await api.AddObj(formData);
- ElMessage.success('添加成功');
- // 课堂(1) / 特殊(2) 借用:新增成功后直接打开「领取」弹窗,并进入设备清单
- const addBorrowType = Number(formData.borrow_type);
- if (addBorrowType === 1 || addBorrowType === 2) {
- const newId = await resolveNewBorrowApplicationIdAfterAdd(res, formData);
- if (newId != null) {
- // 先写入 modelValue 再打开弹窗,领取页首帧即 mode=collect,避免先显示「借用单信息」再切页
- borrowForm.value = {
- id: newId,
- borrow_type: addBorrowType,
- mode: 'collect',
- collectInitialTab: 'second',
- };
- showCollectDialog.value = true;
- } else {
- console.warn(
- '[borrow] 新建借用后仍无法确定单主键,请让 POST /application/ 在 data 中返回 id;可暂从列表点击「领取」',
- res
- );
- ElMessage.info('已新建成功,请从下方列表中点击该单的「领取」');
- }
- }
- }
-
- // 刷新列表
- crudRef.value && crudRef.value.doRefresh && crudRef.value.doRefresh();
- crudExpose.doRefresh();
- } catch (e) {
- // 错误处理
- console.error(formData.mode === 'edit' ? '编辑失败' : '提交失败', e);
- ElMessage.error(formData.mode === 'edit' ? '编辑失败' : '提交失败');
- }
- }
- async function handleCollectSubmit(formData: any) {
- try {
- crudExpose.doRefresh();
- } catch (e) {
- console.error('提交失败', e);
- }
- }
- async function handleCollect(e: CustomEvent) {
- const row = e.detail;
- console.log("row:::",row);
- borrowForm.value = { ...row, mode: 'collect' };
- showCollectDialog.value = true;
- console.log("showCollectDialog:::",showCollectDialog.value);
- }
- async function handleReturn(e: CustomEvent) {
- const row = e.detail;
- console.log("row:::",row);
- borrowForm.value = { ...row, mode: 'return' };
- showCollectDialog.value = true;
- console.log("showReturnDialog:::",showCollectDialog.value);
- }
- async function handleView(e: CustomEvent) {
- const row = e.detail;
- onBorrowTypeSelected(row.borrow_type, 'view',row);
- }
- async function handleEdit(e: CustomEvent) {
- const row = e.detail;
- onBorrowTypeSelected(row.borrow_type, 'edit',row);
- }
- async function handleRemove(e: CustomEvent) {
- const row = e.detail;
- onBorrowTypeSelected(row.borrow_type);
- }
- async function handleSettlement(e: CustomEvent) {
- const row = e.detail;
- console.log("row:::",row);
- try {
- // 获取申请详情,避免直接使用行内简略数据
- const res: any = await api.GetApplicationDetail(row.id);
- const detail = res?.data || res; // 兼容后端返回结构
- showSettlementDialog.value = true;
- nextTick(() => {
- borrowForm.value = { ...(detail || {}), mode: 'settlement' };
- });
- } catch (error) {
- console.error('获取结算详情失败:', error);
- ElMessage.error('获取结算详情失败');
- }
- }
- async function handleRenewDevice(e: CustomEvent) {
- const res: any = await api.GetApplicationDetail(e.detail.id);
- if(res.code==2000){
- showSettlementDialog.value = true;
- nextTick(() => {
- borrowForm.value = { ...res.data, mode: 'renew' };
- });
- }else{
- ElMessage.error('获取续借详情失败');
- }
- }
- let observer: MutationObserver | null = null;
- function bindBorrowActionbarButton(): boolean {
- const fsActionbar = document.querySelector('.borrow-page .fs-actionbar');
- if (!fsActionbar) return false;
- function insertButton() {
- if (fsActionbar && !fsActionbar.querySelector('.my-create-btn')) {
- const button = document.createElement('button');
- button.className = 'el-button el-button--primary el-button--default my-create-btn';
- button.innerHTML = '创建借用单';
- button.onclick = () => {
- showBorrowTypeDialog.value = true;
- };
- fsActionbar.append(button);
- }
- }
- insertButton();
- if (observer) {
- observer.disconnect();
- observer = null;
- }
- observer = new MutationObserver((_m, obs) => {
- insertButton();
- obs.disconnect();
- });
- observer.observe(fsActionbar, { childList: true });
- return true;
- }
- onMounted(() => {
- window.addEventListener('borrow-view', handleView);
- window.addEventListener('borrow-edit', handleEdit);
- window.addEventListener('borrow-remove', handleRemove);
- window.addEventListener('borrow-collect', handleCollect);
- window.addEventListener('borrow-return', handleReturn);
- window.addEventListener('borrow-settlement', handleSettlement);
- window.addEventListener('renew-device', handleRenewDevice);
- /* 必须执行:此前若未找到 .fs-actionbar 会提前 return,导致从未 doSearch,URL 带参也不生效 */
- nextTick(() => {
- applyRouteSearchToCrud();
- });
- if (!bindBorrowActionbarButton()) {
- nextTick(() => {
- if (!bindBorrowActionbarButton()) {
- window.setTimeout(() => bindBorrowActionbarButton(), 100);
- }
- });
- }
- });
- /** 同页仅 query 变化时(如 ?status=);勿 watch 全局 fullPath,keep-alive 下会误触发 */
- onBeforeRouteUpdate((to) => {
- nextTick(() => {
- crudExpose.doSearch({
- form: buildBorrowListSearchFormFromRoute(to.query),
- });
- });
- });
- onActivated(() => {
- if (skipNextActivatedBorrowSearch.value) {
- skipNextActivatedBorrowSearch.value = false;
- return;
- }
- crudExpose.doSearch({
- form: buildBorrowListSearchFormFromRoute(route.query),
- });
- });
- onBeforeUnmount(() => {
- if (observer) {
- observer.disconnect();
- observer = null;
- }
- // window.removeEventListener('borrow-add', () => {
- // showBorrowTypeDialog.value = false
- // });
- window.removeEventListener('borrow-view', handleView);
- window.removeEventListener('borrow-edit', handleEdit);
- window.removeEventListener('borrow-remove', handleRemove);
- window.removeEventListener('borrow-collect', handleCollect);
- window.removeEventListener('borrow-return', handleReturn);
- window.removeEventListener('borrow-settlement', handleSettlement);
- window.removeEventListener('renew-device', handleRenewDevice);
- });
- </script>
- <style lang="scss" scoped></style>
|