Jelajahi Sumber

更新新的样式

qaz 2 hari lalu
induk
melakukan
66865d1403

+ 1 - 1
src/components/iconSelector/index.vue

@@ -31,7 +31,7 @@
 		>
 			<template #default>
 				<div class="icon-selector-warp">
-					<div class="icon-selector-warp-title">{{ title }}</div>
+					<!-- <div class="icon-selector-warp-title">{{ title }}</div> -->
 					<el-tabs v-model="state.fontIconTabActive" @tab-click="onIconClick">
 						<el-tab-pane lazy label="ali" name="ali">
 							<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />

+ 2 - 1
src/components/iconSelector/list.vue

@@ -44,7 +44,8 @@ const onColClick = (v: unknown | string) => {
 
 <style scoped lang="scss">
 .icon-selector-warp-row {
-	height: 230px;
+	height: 100%;
+	min-height: 0;
 	overflow: hidden;
 	.el-row {
 		padding: 15px;

+ 29 - 5
src/theme/iconSelector.scss

@@ -5,14 +5,29 @@
 	.icon-selector-warp {
 		height: 260px;
 		overflow: hidden;
-		position: relative;
+		display: flex;
+		flex-direction: column;
+		/* 标题与 Tab 表头分行展示,避免 absolute 与 tabs 同一行重叠 */
 		.icon-selector-warp-title {
-			position: absolute;
-			height: 40px;
-			line-height: 40px;
-			left: 15px;
+			flex-shrink: 0;
+			box-sizing: border-box;
+			padding: 0 15px;
+			height: 36px;
+			line-height: 36px;
+			font-size: var(--el-font-size-base);
+			color: var(--el-text-color-primary);
+			overflow: hidden;
+			text-overflow: ellipsis;
+			white-space: nowrap;
+		}
+		.el-tabs {
+			flex: 1;
+			min-height: 0;
+			display: flex;
+			flex-direction: column;
 		}
 		.el-tabs__header {
+			flex-shrink: 0;
 			display: flex;
 			justify-content: flex-end;
 			padding: 0 15px;
@@ -27,5 +42,14 @@
 				}
 			}
 		}
+		.el-tabs__content {
+			flex: 1;
+			min-height: 0;
+			overflow: hidden;
+		}
+		.el-tab-pane {
+			height: 100%;
+			overflow: hidden;
+		}
 	}
 }

+ 92 - 30
src/utils/getStyleSheets.ts

@@ -43,46 +43,108 @@ const getElementPlusIconfont = () => {
 	});
 };
 
-// 初始化获取 css 样式,这里使用 fontawesome 的图标
+/** 外链样式表因 CORS 无法读 cssRules;FA6 的 font-family 也不是单一的 FontAwesome */
+const FA_STYLESHEET_HREF_MARKERS = ['font-awesome', 'fontawesome', 'netdna.bootstrapcdn.com/font-awesome', 'cdnjs.cloudflare.com/ajax/libs/font-awesome', 'use.fontawesome.com'];
+
+const FA_COMPOUND_SKIP = new Set(['fa-solid', 'fa-regular', 'fa-brands']);
+
+function safeReadCssRules(sheet: CSSStyleSheet): CSSRuleList | null {
+	try {
+		return sheet.cssRules || (sheet as CSSStyleSheet & { rules?: CSSRuleList }).rules || null;
+	} catch {
+		return null;
+	}
+}
+
+function fontFamilyLooksLikeFontAwesome(fontFamily: string): boolean {
+	if (!fontFamily) return false;
+	const f = fontFamily.replace(/['"]/g, '').toLowerCase();
+	return f.includes('fontawesome') || f.includes('font awesome');
+}
+
+/** 从选择器中提取图标类名,如 .fa-home::before、.fa-solid.fa-user::before */
+function extractFaIconClassFromSelector(selectorText: string): string | null {
+	if (!selectorText || selectorText.includes(',')) return null;
+	if (!/::before|:before/i.test(selectorText)) return null;
+	const beforePart = selectorText.split(/::before|:before/i)[0];
+	const found = [...beforePart.matchAll(/\.(fa-[a-z0-9-]+)/gi)].map((m) => m[1].toLowerCase());
+	const candidates = found.filter((c) => !FA_COMPOUND_SKIP.has(c));
+	if (candidates.length === 0) return null;
+	return candidates[candidates.length - 1];
+}
+
+async function loadFontAwesomeFallbackList(): Promise<string[]> {
+	const mod: { default?: string[] } | string[] = await import('e-icon-picker/icon/fontawesome/font-awesome.v4.7.0.js');
+	const list = Array.isArray(mod) ? mod : mod.default;
+	if (!Array.isArray(list)) return [];
+	return list
+		.map((entry: string) => {
+			const t = String(entry).trim();
+			const m = t.match(/^fa\s+(fa-[a-z0-9-]+)/i);
+			return m ? m[1] : '';
+		})
+		.filter(Boolean);
+}
+
+// 初始化获取 css 样式,这里使用 fontawesome 的图标(解析失败时用 e-icon-picker 内置列表兜底)
 const getAwesomeIconfont = () => {
-	return new Promise((resolve, reject) => {
-		nextTick(() => {
-			const styles: any = document.styleSheets;
-			let sheetsList = [];
-			let sheetsIconList = [];
-		    // 判断fontFamily是否是本地加载
+	return new Promise<string[]>((resolve, reject) => {
+		nextTick(async () => {
+			const styles = document.styleSheets as unknown as CSSStyleSheet[];
+			const sheetsSet = new Set<CSSStyleSheet>();
+
 			for (let i = 0; i < styles.length; i++) {
-				const rules = styles[i].cssRules || styles[i].rules;
-				if (rules) {
+				const sheet = styles[i];
+				const href = (sheet.href || '').toLowerCase();
+				if (FA_STYLESHEET_HREF_MARKERS.some((m) => href.includes(m))) {
+					sheetsSet.add(sheet);
+					continue;
+				}
+				const rules = safeReadCssRules(sheet);
+				if (!rules) continue;
+				try {
 					for (let j = 0; j < rules.length; j++) {
-						if (rules[j].style && rules[j].style.fontFamily === 'FontAwesome') {
-							sheetsList.push(styles[i])
+						const rule = rules[j] as CSSStyleRule;
+						if (rule.style && fontFamilyLooksLikeFontAwesome(rule.style.fontFamily || '')) {
+							sheetsSet.add(sheet);
+							break;
 						}
 					}
+				} catch {
+					continue;
 				}
 			}
-			for (let i = 0; i < styles.length; i++) {
-				if (styles[i].href && styles[i].href.indexOf('netdna.bootstrapcdn.com') > -1) {
-					sheetsList.push(styles[i]);
-				}
-			}
-			for (let i = 0; i < sheetsList.length; i++) {
-				for (let j = 0; j < sheetsList[i].cssRules.length; j++) {
-					if (
-						sheetsList[i].cssRules[j].selectorText &&
-						sheetsList[i].cssRules[j].selectorText.indexOf('.fa-') === 0 &&
-						sheetsList[i].cssRules[j].selectorText.indexOf(',') === -1
-					) {
-						if (/::before/.test(sheetsList[i].cssRules[j].selectorText)) {
-							sheetsIconList.push(
-								`${sheetsList[i].cssRules[j].selectorText.substring(1, sheetsList[i].cssRules[j].selectorText.length).replace(/\:\:before/gi, '')}`
-							);
-						}
+
+			const seen = new Set<string>();
+			const sheetsIconList: string[] = [];
+			for (const sh of sheetsSet) {
+				const rules = safeReadCssRules(sh);
+				if (!rules) continue;
+				try {
+					for (let j = 0; j < rules.length; j++) {
+						const rule = rules[j] as CSSStyleRule;
+						const iconClass = extractFaIconClassFromSelector(rule.selectorText || '');
+						if (!iconClass || seen.has(iconClass)) continue;
+						seen.add(iconClass);
+						sheetsIconList.push(iconClass);
 					}
+				} catch {
+					continue;
 				}
 			}
-			if (sheetsIconList.length > 0) resolve(sheetsIconList.reverse());
-			else reject('未获取到值,请刷新重试');
+
+			if (sheetsIconList.length > 0) {
+				resolve([...sheetsIconList].reverse());
+				return;
+			}
+
+			const fallback = await loadFontAwesomeFallbackList();
+			if (fallback.length > 0) {
+				resolve(fallback);
+				return;
+			}
+
+			reject('未获取到值,请刷新重试');
 		});
 	});
 };

+ 7 - 0
src/views/system/borrow/borrowSearchConstants.ts

@@ -0,0 +1,7 @@
+/**
+ * 借用列表 URL / 搜索参数与后端、字典的约定(逗号分隔多状态 in 等)
+ */
+/** 看板「已借出」等:已借出 / 部分归还 / 已逾期 / 续借待审批 等需一并筛出的多状态 */
+export const BORROW_OUTSTANDING_STATUS_IN_PARAM = '5,6,8,9'
+/** 仅「已逾期」 */
+export const BORROW_STATUS_OVERDUE_PARAM = '8'

+ 46 - 2
src/views/system/borrow/crud.tsx

@@ -4,10 +4,46 @@ import dayjs from 'dayjs';
 
 const { compute } = useCompute();
 
+/** 列表请求参数处理过程是否打印到控制台(联调后改为 false 即可关) */
+const BORROW_LIST_PAGE_REQUEST_LOG = true;
+
 export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
 	const pageRequest = async (query: UserPageQuery) => {
-		console.log(query);
-		return await api.GetList(query);
+		const q: any = { ...(query as any) };
+		const form = q.form && typeof q.form === 'object' ? { ...q.form } : {};
+		if (BORROW_LIST_PAGE_REQUEST_LOG) {
+			// eslint-disable-next-line no-console
+			console.log('[borrow/list] pageRequest 入参 根上字段与 form', {
+				根: { status: q.status, status__in: q.status__in },
+				form: { ...form },
+			});
+		}
+		/**
+		 * 多状态:列 status__in。fast-crud 可能放在 form 里,也可能展到 query 根上(故 q.form 为空时仍可能有 q.status__in)。
+		 * 列表接口使用 params.status(与后端一致),不单独传 status__in,避免后端忽略。
+		 */
+		const fromInForm =
+			form.status__in != null && String(form.status__in).trim() !== '' ? String(form.status__in).trim() : '';
+		const fromInQuery =
+			q.status__in != null && String(q.status__in).trim() !== '' ? String(q.status__in).trim() : '';
+		const statusInVal = fromInForm || fromInQuery;
+		if (statusInVal) {
+			q.status = statusInVal;
+			delete q.status__in;
+			if (fromInForm) {
+				delete (form as any).status__in;
+			}
+		} else if (form.status != null && String(form.status).trim() !== '') {
+			q.status = String(form.status).trim();
+			delete (form as any).status;
+		}
+		q.form = form;
+		if (BORROW_LIST_PAGE_REQUEST_LOG) {
+			const { form: f, ...rest } = q;
+			// eslint-disable-next-line no-console
+			console.log('[borrow/list] pageRequest → 调用 GetList 参数', { ...rest, form: f });
+		}
+		return await api.GetList(q);
 	};
 
 	const editRequest = async ({ form, row }: EditReq) => {
@@ -465,6 +501,14 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 						component: { placeholder: '请选择当前状态' },
 					}
 				},
+				/* 程序化传入:看板「已借出」等多状态跳转,不在搜索栏展示 */
+				status__in: {
+					title: '状态多选',
+					type: 'text',
+					search: { show: false },
+					column: { show: false },
+					form: { show: false },
+				},
 
 
 

+ 115 - 42
src/views/system/borrow/index.vue

@@ -24,7 +24,7 @@
 	</fs-page>
 </template>
 <script lang="ts" setup name="borrow">
-import { ref, onMounted,onActivated, onBeforeUnmount,nextTick ,provide } from 'vue';
+import { ref, onMounted, onActivated, onBeforeUnmount, nextTick, provide } from 'vue';
 import { useFs } from '@fast-crud/fast-crud';
 import { createCrudOptions } from './crud';
 // import { GetPermission } from './api';
@@ -37,9 +37,65 @@ 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 } from 'vue-router';
+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);
@@ -47,9 +103,18 @@ const showClassroomBorrowDialog = ref(false);
 const showSpecialBorrowDialog = ref(false);
 const showCollectDialog = ref(false);
 const borrowForm = ref({});
-const showSettlementDialog = ref(false);	
+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);
@@ -367,28 +432,10 @@ async function handleRenewDevice(e: CustomEvent) {
 
 let observer: MutationObserver | null = null;
 
-onMounted(() => {
-	// 设置列权限
-	// const newOptions = await handleColumnPermission(GetPermission, crudOptions);
-	//重置crudBinding
-	// resetCrudOptions(newOptions);
-	// 刷新
-	/* crudExpose.doRefresh(); */
-	// window.addEventListener('borrow-add', () => {
-	// 	showBorrowTypeDialog.value = true
-	// });
-	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);
-
-
+function bindBorrowActionbarButton(): boolean {
 	const fsActionbar = document.querySelector('.borrow-page .fs-actionbar');
-	if (!fsActionbar) return;
-	// 创建借用单按钮
+	if (!fsActionbar) return false;
+
 	function insertButton() {
 		if (fsActionbar && !fsActionbar.querySelector('.my-create-btn')) {
 			const button = document.createElement('button');
@@ -401,27 +448,59 @@ onMounted(() => {
 		}
 	}
 
-	// 先尝试插入一次
 	insertButton();
-
-	// 监听 DOM 变化,确保 actionbar 出现时插入按钮
-	// observer = new MutationObserver(() => {
-	// 	insertButton();
-	// });
-	// observer.observe(document.body, { childList: true, subtree: true });
-
+	if (observer) {
+		observer.disconnect();
+		observer = null;
+	}
 	observer = new MutationObserver((_m, obs) => {
 		insertButton();
-		// 插入完毕后停掉 observer
 		obs.disconnect();
 	});
 	observer.observe(fsActionbar, { childList: true });
-	crudExpose.doSearch({
-			form: {
-				status: Number(route.query.status) || ''
+	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) {
@@ -440,11 +519,5 @@ onBeforeUnmount(() => {
 	window.removeEventListener('renew-device', handleRenewDevice);
 });
 
-// onActivated(() => {
-//   crudExpose.doRefresh();
-// });
-
-
-
 </script>
 <style lang="scss" scoped></style>

+ 13 - 3
src/views/system/screenconsole/component/StatusCards.vue

@@ -68,8 +68,12 @@
 import {
   DocumentChecked, Timer, Tools, Warning
 } from '@element-plus/icons-vue'
-import { defineProps, computed } from 'vue'
+import { computed } from 'vue'
 import { useRouter } from 'vue-router'
+import {
+  BORROW_OUTSTANDING_STATUS_IN_PARAM,
+  BORROW_STATUS_OVERDUE_PARAM,
+} from '../../borrow/borrowSearchConstants'
 
 const props = defineProps({
   statusData: {
@@ -96,13 +100,19 @@ function handleClick(item: any) {
       router.push('/borrow/approval')
       break
     case '已借出':
-      router.push('/borrow/list?status=5')
+      router.push({
+        path: '/borrow/list',
+        query: { status: BORROW_OUTSTANDING_STATUS_IN_PARAM },
+      })
       break
     case '待维修':
       router.push('/devicemaintenance')
       break
     case '已逾期':
-      router.push('/borrow/list?status=8')
+      router.push({
+        path: '/borrow/list',
+        query: { status: BORROW_STATUS_OVERDUE_PARAM },
+      })
       break
     default:
       console.warn('未知状态:', item.label)

+ 2 - 6
src/views/system/screenconsole/index.vue

@@ -716,16 +716,12 @@ onMounted(async () => {
 }
 
 .screenconsole-quick-save-btn {
-  background: #ff7c42 !important;
-  border-color: #ff7c42 !important;
+ 
   color: #fff !important;
   box-shadow: 0 2px 6px rgba(255, 124, 66, 0.2);
 }
 
-.screenconsole-quick-save-btn:hover {
-  background: #e55a2e !important;
-  border-color: #e55a2e !important;
-}
+
 
 @media (max-width: 780px) {
   .screenconsole-quick-edit .edit-item {