index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  1. <template>
  2. <div class="home-container">
  3. <!-- 快捷入口(样式对齐参考稿 quick-section) -->
  4. <div class="quick-section">
  5. <div class="section-title">
  6. <h3>
  7. <i class="fa fa-bolt section-title-bolt" aria-hidden="true"></i>
  8. 快捷入口
  9. </h3>
  10. <button type="button" class="edit-quick-btn" @click="openQuickEdit">
  11. <i class="fa fa-pencil" aria-hidden="true"></i>
  12. 编辑
  13. </button>
  14. </div>
  15. <div class="quick-grid">
  16. <div
  17. v-for="(item, idx) in quickDisplayItems"
  18. :key="`${item.id}-${idx}`"
  19. class="quick-btn"
  20. :class="{ 'highlight-card': item.highlight }"
  21. role="button"
  22. tabindex="0"
  23. @click="onQuickCardClick(item)"
  24. @keydown.enter.prevent="onQuickCardClick(item)"
  25. >
  26. <div v-if="item.highlight" class="hot-badge">
  27. <i class="fa fa-star" aria-hidden="true"></i>
  28. 常用
  29. </div>
  30. <div class="quick-icon">
  31. <i :class="item.iconClass" aria-hidden="true"></i>
  32. </div>
  33. <span>{{ item.title }}</span>
  34. </div>
  35. </div>
  36. </div>
  37. <el-dialog
  38. v-model="quickEditVisible"
  39. title="编辑快捷入口"
  40. width="560px"
  41. destroy-on-close
  42. class="screenconsole-quick-edit"
  43. append-to-body
  44. >
  45. <div class="quick-edit-body">
  46. <div v-for="(_, idx) in quickEditDraft.slots" :key="idx" class="edit-item">
  47. <div class="edit-item-index">{{ idx + 1 }}.</div>
  48. <div class="edit-item-select">
  49. <label>选择功能</label>
  50. <el-select
  51. v-model="quickEditDraft.slots[idx]"
  52. placeholder="请选择"
  53. style="width: 100%"
  54. filterable
  55. popper-class="screenconsole-quick-edit-select-popper"
  56. >
  57. <el-option
  58. v-for="opt in quickMenuOptions"
  59. :key="opt.id"
  60. :label="opt.title"
  61. :value="opt.id"
  62. :disabled="isQuickEntryOptionTakenByOtherSlot(opt.id, idx)"
  63. />
  64. </el-select>
  65. </div>
  66. <div class="edit-item-radio">
  67. <label>标记为常用</label>
  68. <div class="radio-group">
  69. <el-radio v-model="quickEditDraft.highlightIndex" :label="idx">橙色高亮</el-radio>
  70. </div>
  71. </div>
  72. </div>
  73. </div>
  74. <template #footer>
  75. <el-button @click="quickEditVisible = false">取消</el-button>
  76. <el-button
  77. type="primary"
  78. class="screenconsole-quick-save-btn"
  79. :loading="quickSaving"
  80. @click="saveQuickEdit"
  81. >
  82. 保存修改
  83. </el-button>
  84. </template>
  85. </el-dialog>
  86. <!-- 第一行 -->
  87. <el-row :gutter="15" class="home-row">
  88. <el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="6" style="margin-bottom: 10px;">
  89. <StatusCards :statusData="pendingItems" />
  90. </el-col>
  91. <el-col :xs="24" :sm="24" :md="24" :lg="16" :xl="18">
  92. <InfoCards :summaryData="dataSummary" />
  93. </el-col>
  94. </el-row>
  95. <el-row :gutter="15" class="home-row">
  96. <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" style="margin-bottom: 10px;">
  97. <BorrowRankingList/>
  98. </el-col>
  99. <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
  100. <BorrowTrendsChart />
  101. </el-col>
  102. </el-row>
  103. </div>
  104. </template>
  105. <script lang="ts" setup name="screenconsole">
  106. import { reactive, onMounted, ref, watch, computed } from 'vue';
  107. import { useRouter } from 'vue-router';
  108. import { ElMessage } from 'element-plus';
  109. import { storeToRefs } from 'pinia';
  110. import { useI18n } from 'vue-i18n';
  111. import { useRoutesList } from '/@/stores/routesList';
  112. import { filterRoutesFun } from '/@/utils/routeFilter';
  113. import * as api from './api';
  114. import StatusCards from './component/StatusCards.vue';
  115. import InfoCards from './component/InfoCards.vue';
  116. import BorrowTrendsChart from './component/BorrowTrendsChart.vue';
  117. import BorrowRankingList from './component/BorrowRankingList.vue';
  118. import {
  119. QUICK_ENTRY_SLOT_COUNT,
  120. buildQuickMenuOptionsFromRoutes,
  121. loadQuickEntryPrefs,
  122. pickPreferredQuickSlotIds,
  123. saveQuickEntryPrefs,
  124. resolvePathForQuickOption,
  125. type QuickEntryMenuOption,
  126. } from './quickEntry';
  127. import * as workbenchApi from './userWorkbenchShortcutApi';
  128. import {
  129. mapWorkbenchShortcutListToState,
  130. syncQuickSlotsToServer,
  131. buildRecordBySlotFromList,
  132. } from './quickEntryServerAdapter';
  133. import { Session } from '/@/utils/storage';
  134. const router = useRouter();
  135. const { t } = useI18n();
  136. const storesRoutesList = useRoutesList();
  137. const { routesList } = storeToRefs(storesRoutesList);
  138. /** 与侧栏子菜单一致的叶子路由列表(随权限/菜单接口变化) */
  139. const quickMenuOptions = computed(() =>
  140. buildQuickMenuOptionsFromRoutes(filterRoutesFun(routesList.value as RouteItem[]), (k) => t(k))
  141. );
  142. function getQuickOptionById(id: string): QuickEntryMenuOption | undefined {
  143. return quickMenuOptions.value.find((o) => o.id === id);
  144. }
  145. const quickSlotMenuIds = ref<string[]>([]);
  146. const quickHighlightIndex = ref(0);
  147. /** 与 `/api/system/user_workbench_shortcut/` 中每条 id 的对应,用于 PATCH */
  148. const quickServerRecordBySlot = ref(new Map<number, { id: string | number }>());
  149. const quickEntryHydrated = ref(false);
  150. const quickSaving = ref(false);
  151. function applyQuickSlotsFromRoutes() {
  152. const opts = quickMenuOptions.value;
  153. if (!opts.length) return;
  154. const idSet = new Set(opts.map((o) => o.id));
  155. const persisted = loadQuickEntryPrefs();
  156. const seed = quickSlotMenuIds.value.length
  157. ? [...quickSlotMenuIds.value]
  158. : persisted.menuIds.filter((id) => idSet.has(id));
  159. const used = new Set<string>();
  160. const next: string[] = [];
  161. for (const id of seed) {
  162. if (next.length >= QUICK_ENTRY_SLOT_COUNT) break;
  163. if (idSet.has(id) && !used.has(id)) {
  164. next.push(id);
  165. used.add(id);
  166. }
  167. }
  168. if (next.length === 0) {
  169. for (const id of pickPreferredQuickSlotIds(opts)) {
  170. if (next.length >= QUICK_ENTRY_SLOT_COUNT) break;
  171. if (!used.has(id)) {
  172. next.push(id);
  173. used.add(id);
  174. }
  175. }
  176. } else {
  177. while (next.length < QUICK_ENTRY_SLOT_COUNT) {
  178. const fill = opts.find((o) => !used.has(o.id));
  179. if (!fill) break;
  180. next.push(fill.id);
  181. used.add(fill.id);
  182. }
  183. }
  184. const wasEmpty = quickSlotMenuIds.value.length === 0;
  185. quickSlotMenuIds.value = next.slice(0, QUICK_ENTRY_SLOT_COUNT);
  186. if (wasEmpty) {
  187. quickHighlightIndex.value = Math.min(
  188. Math.max(0, persisted.highlightIndex),
  189. QUICK_ENTRY_SLOT_COUNT - 1
  190. );
  191. }
  192. }
  193. /**
  194. * 优先拉取用户工作台快捷入口;无数据或失败时走本地/默认填充(与原先一致)
  195. */
  196. async function hydrateQuickEntry() {
  197. const opts = quickMenuOptions.value;
  198. if (!opts.length) return;
  199. if (quickEntryHydrated.value) {
  200. applyQuickSlotsFromRoutes();
  201. return;
  202. }
  203. try {
  204. const res = await workbenchApi.listUserWorkbenchShortcuts();
  205. if (res.code === 2000) {
  206. const list = workbenchApi.unwrapShortcutListData(res.data);
  207. const mapped = mapWorkbenchShortcutListToState(list, opts);
  208. if (mapped) {
  209. quickSlotMenuIds.value = mapped.menuIds;
  210. quickHighlightIndex.value = mapped.highlightIndex;
  211. quickServerRecordBySlot.value = mapped.recordBySlot;
  212. quickEntryHydrated.value = true;
  213. return;
  214. }
  215. }
  216. } catch (e) {
  217. console.error('load user_workbench_shortcut', e);
  218. }
  219. applyQuickSlotsFromRoutes();
  220. quickEntryHydrated.value = true;
  221. }
  222. watch(quickMenuOptions, () => {
  223. void hydrateQuickEntry();
  224. }, { immediate: true });
  225. /** 与「常用」单选对应的目标菜单 path(id),用 id 判断高亮,避免无权限项被滤掉后下标错位 */
  226. const quickFrequentMenuId = computed(() => {
  227. const ids = quickSlotMenuIds.value;
  228. const h = quickHighlightIndex.value;
  229. if (h < 0 || h >= ids.length) return null;
  230. return ids[h] ?? null;
  231. });
  232. /**
  233. * 仅展示当前 `routesList` 过滤后仍有权访问的项(`quickMenuOptions` 中存在的 path);
  234. * 无权限/菜单已下线的项在列表中不渲染。
  235. */
  236. const quickDisplayItems = computed(() => {
  237. const frequentId = quickFrequentMenuId.value;
  238. return quickSlotMenuIds.value
  239. .map((menuId) => {
  240. const opt = getQuickOptionById(menuId);
  241. if (!opt) return null;
  242. return {
  243. ...opt,
  244. highlight: frequentId !== null && opt.id === frequentId,
  245. };
  246. })
  247. .filter((x): x is QuickEntryMenuOption & { highlight: boolean } => x !== null);
  248. });
  249. const quickEditVisible = ref(false);
  250. const quickEditDraft = reactive({
  251. slots: [] as string[],
  252. highlightIndex: 0,
  253. });
  254. /** 编辑弹窗内:某菜单已被其它格子选中时,当前格子的下拉中禁用该项(避免重复) */
  255. function isQuickEntryOptionTakenByOtherSlot(optionId: string, slotIndex: number): boolean {
  256. return quickEditDraft.slots.some((id, i) => i !== slotIndex && id === optionId);
  257. }
  258. function openQuickEdit() {
  259. const pad = quickSlotMenuIds.value.slice(0, QUICK_ENTRY_SLOT_COUNT);
  260. const used = new Set(pad);
  261. while (pad.length < QUICK_ENTRY_SLOT_COUNT) {
  262. const o = quickMenuOptions.value.find((x) => !used.has(x.id));
  263. if (!o) break;
  264. pad.push(o.id);
  265. used.add(o.id);
  266. }
  267. quickEditDraft.slots = pad;
  268. quickEditDraft.highlightIndex = quickHighlightIndex.value;
  269. quickEditVisible.value = true;
  270. }
  271. async function saveQuickEdit() {
  272. quickSaving.value = true;
  273. try {
  274. quickSlotMenuIds.value = [...quickEditDraft.slots];
  275. quickHighlightIndex.value = quickEditDraft.highlightIndex;
  276. const opts = quickMenuOptions.value;
  277. await syncQuickSlotsToServer(
  278. quickSlotMenuIds.value,
  279. quickHighlightIndex.value,
  280. new Map(quickServerRecordBySlot.value),
  281. opts
  282. );
  283. const res = await workbenchApi.listUserWorkbenchShortcuts();
  284. if (res.code === 2000) {
  285. const list = workbenchApi.unwrapShortcutListData(res.data);
  286. const nextMap = buildRecordBySlotFromList(list, opts);
  287. if (nextMap.size) {
  288. quickServerRecordBySlot.value = nextMap;
  289. }
  290. }
  291. saveQuickEntryPrefs({
  292. menuIds: [...quickSlotMenuIds.value],
  293. highlightIndex: quickHighlightIndex.value,
  294. });
  295. quickEditVisible.value = false;
  296. ElMessage.success('快捷入口已更新');
  297. } catch (e) {
  298. console.error(e);
  299. ElMessage.error('保存失败,请稍后再试');
  300. } finally {
  301. quickSaving.value = false;
  302. }
  303. }
  304. function onQuickCardClick(item: QuickEntryMenuOption & { highlight?: boolean }) {
  305. const path = resolvePathForQuickOption(router, item);
  306. if (!path) {
  307. ElMessage.warning('未找到对应菜单路由,请联系管理员检查菜单配置');
  308. return;
  309. }
  310. router.push(path);
  311. }
  312. const pendingItems = ref({
  313. pending_approval: 0,
  314. pending_return: 0,
  315. pending_overdue: 0,
  316. pending_maintenance: 0,
  317. pending_restock: 0,
  318. pending_warehousing: 0,
  319. pending_inventory_check: 0
  320. })
  321. const dataSummary = ref({
  322. total_devices: 0,
  323. borrowed_devices: 0,
  324. available_devices: 0,
  325. today_borrowed: 0,
  326. today_returned: 0,
  327. total_borrows: 0,
  328. total_borrowers: 0,
  329. })
  330. /* 看板字段:pending_approval 待审批;borrowed 已借出;overdue 已逾期;pending_repair 待维修 */
  331. onMounted(async () => {
  332. try {
  333. const userInfo = Session.get('userInfo');
  334. if (userInfo?.username === '平台管理员') return;
  335. const res = await api.dashboardStatistics();
  336. if (res.code === 2000) {
  337. pendingItems.value.pending_approval = res.data.pending_approval;
  338. pendingItems.value.pending_return = res.data.borrowed;
  339. pendingItems.value.pending_maintenance = res.data.pending_repair;
  340. pendingItems.value.pending_overdue = res.data.overdue;
  341. }
  342. const ress = await api.GetOverviewList();
  343. if (ress.code === 2000) {
  344. dataSummary.value = ress.data;
  345. }
  346. } catch (error) {
  347. console.error('获取数据失败:', error);
  348. }
  349. });
  350. </script>
  351. <style scoped lang="scss">
  352. .home-container {
  353. padding: 20px;
  354. min-height: 100vh;
  355. box-sizing: border-box;
  356. }
  357. .home-row {
  358. margin-bottom: 10px;
  359. // 小屏幕时减少间距
  360. @media (max-width: 768px) {
  361. margin-bottom: 15px;
  362. :deep(.el-col) {
  363. margin-bottom: 15px;
  364. }
  365. }
  366. }
  367. .home-col-table {
  368. display: flex;
  369. flex-wrap: wrap;
  370. gap: 15px;
  371. }
  372. // 响应式断点
  373. @media (max-width: 576px) {
  374. .home-container {
  375. padding: 10px;
  376. }
  377. .home-row {
  378. margin-bottom: 10px;
  379. }
  380. }
  381. @media (min-width: 577px) and (max-width: 768px) {
  382. .home-container {
  383. padding: 15px;
  384. }
  385. }
  386. @media (min-width: 1920px) {
  387. .home-container {
  388. max-width: 1920px;
  389. margin: 0 auto;
  390. }
  391. }
  392. /* ========= 快捷入口(与参考 HTML quick-section 一致) ========= */
  393. .quick-section {
  394. margin-bottom: 24px;
  395. /* 减轻 Windows 下 transform / 半透明层导致的文字发虚 */
  396. -webkit-font-smoothing: antialiased;
  397. -moz-osx-font-smoothing: grayscale;
  398. text-rendering: optimizeLegibility;
  399. }
  400. .section-title {
  401. display: flex;
  402. align-items: center;
  403. justify-content: space-between;
  404. margin-bottom: 14px;
  405. }
  406. .section-title h3 {
  407. margin: 0;
  408. font-size: 20px;
  409. font-weight: 700;
  410. color: #0f172a;
  411. display: flex;
  412. align-items: center;
  413. gap: 6px;
  414. }
  415. .section-title-bolt {
  416. color: #3b82f6;
  417. font-size: 20px;
  418. }
  419. .edit-quick-btn {
  420. background: #f1f5f9;
  421. border-radius: 40px;
  422. padding: 6px 14px;
  423. font-size: 0.8rem;
  424. font-weight: 500;
  425. color: #475569;
  426. cursor: pointer;
  427. transition: all 0.2s;
  428. border: 1px solid #e2e8f0;
  429. display: flex;
  430. align-items: center;
  431. gap: 6px;
  432. font-family: inherit;
  433. line-height: 1.4;
  434. }
  435. .edit-quick-btn:hover {
  436. background: #e6edf5;
  437. color: #2563eb;
  438. border-color: #cbd5e1;
  439. }
  440. .quick-grid {
  441. display: grid;
  442. grid-template-columns: repeat(5, 1fr);
  443. gap: 12px;
  444. }
  445. .quick-btn {
  446. background: white;
  447. border-radius: 14px;
  448. padding: 14px 8px 12px 8px;
  449. text-align: center;
  450. cursor: pointer;
  451. transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
  452. border: 1px solid #eef2f8;
  453. box-shadow: 0 2px 6px rgba(0, 0, 0, 0.02);
  454. position: relative;
  455. -webkit-font-smoothing: antialiased;
  456. -moz-osx-font-smoothing: grayscale;
  457. text-rendering: optimizeLegibility;
  458. }
  459. .quick-btn:hover {
  460. border-color: #cbdff2;
  461. box-shadow: 0 8px 16px -6px rgba(0, 0, 0, 0.12);
  462. background: #fefefe;
  463. }
  464. .quick-icon {
  465. font-size: 2rem;
  466. margin: 0 auto 11px;
  467. display: flex;
  468. align-items: center;
  469. justify-content: center;
  470. background: #eff6ff;
  471. width: 52px;
  472. height: 52px;
  473. border-radius: 50%;
  474. line-height: 1;
  475. color: #2563eb;
  476. transition: all 0.2s;
  477. }
  478. .quick-icon i {
  479. font-size: 2rem;
  480. }
  481. .quick-btn span {
  482. display: block;
  483. font-weight: 600;
  484. font-size: 13px;
  485. margin-top: 0;
  486. color: #1e293b;
  487. line-height: 1.35;
  488. letter-spacing: 0.01em;
  489. }
  490. .quick-btn.highlight-card {
  491. background: #ff7c42;
  492. border-color: #e55a2e;
  493. box-shadow: 0 8px 18px rgba(255, 100, 50, 0.25);
  494. }
  495. .quick-btn.highlight-card .quick-icon {
  496. background: rgba(255, 255, 255, 0.25);
  497. color: white;
  498. }
  499. .quick-btn.highlight-card span {
  500. color: white;
  501. font-weight: 700;
  502. }
  503. .quick-btn.highlight-card:hover {
  504. background: #e55a2e;
  505. box-shadow: 0 10px 20px rgba(229, 90, 46, 0.32);
  506. }
  507. .hot-badge {
  508. position: absolute;
  509. top: 6px;
  510. right: 6px;
  511. /* 不用 backdrop-filter,避免与整卡合成层叠导致字迹发糊 */
  512. background: #ffffff;
  513. color: #ff7c42;
  514. font-size: 11px;
  515. font-weight: 700;
  516. padding: 3px 7px;
  517. border-radius: 40px;
  518. box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
  519. letter-spacing: 0.02em;
  520. z-index: 2;
  521. display: flex;
  522. align-items: center;
  523. gap: 3px;
  524. -webkit-font-smoothing: antialiased;
  525. line-height: 1.2;
  526. }
  527. .hot-badge i {
  528. font-size: 10px;
  529. }
  530. .quick-btn:not(.highlight-card) .quick-icon {
  531. background: #eff6ff;
  532. color: #3b82f6;
  533. }
  534. .quick-btn:not(.highlight-card):hover .quick-icon {
  535. background: #e0edff;
  536. color: #2563eb;
  537. }
  538. @media (max-width: 1100px) {
  539. .quick-grid {
  540. grid-template-columns: repeat(3, 1fr);
  541. gap: 12px;
  542. }
  543. }
  544. @media (max-width: 780px) {
  545. .quick-grid {
  546. grid-template-columns: repeat(2, 1fr);
  547. gap: 10px;
  548. }
  549. .quick-btn {
  550. padding: 12px 6px 10px 6px;
  551. }
  552. .quick-icon {
  553. margin-bottom: 8px;
  554. width: 46px;
  555. height: 46px;
  556. }
  557. .quick-icon i {
  558. font-size: 1.35rem;
  559. }
  560. .quick-btn span {
  561. font-size: 12px;
  562. }
  563. .hot-badge {
  564. font-size: 10px;
  565. padding: 2px 6px;
  566. top: 4px;
  567. right: 4px;
  568. }
  569. }
  570. @media (max-width: 500px) {
  571. .quick-grid {
  572. grid-template-columns: 1fr;
  573. }
  574. }
  575. </style>
  576. <!-- 编辑弹窗挂载到 body,单独块 -->
  577. <style lang="scss">
  578. /* 下拉层挂到 body,与弹窗样式分离 */
  579. .screenconsole-quick-edit-select-popper {
  580. -webkit-font-smoothing: antialiased;
  581. -moz-osx-font-smoothing: grayscale;
  582. .el-select-dropdown__item {
  583. font-size: 13px;
  584. font-weight: 400;
  585. color: #1e293b;
  586. }
  587. .el-select-dropdown__item.is-selected {
  588. font-weight: 500;
  589. color: #2563eb;
  590. }
  591. /* 已被其它格子选中的项:置灰且不可点(与 Element Plus 禁用态一致) */
  592. .el-select-dropdown__item.is-disabled,
  593. .el-select-dropdown__item[aria-disabled='true'] {
  594. color: #a8abb2 !important;
  595. cursor: not-allowed;
  596. background-color: #f5f7fa !important;
  597. }
  598. .el-select-dropdown__item.is-disabled:hover,
  599. .el-select-dropdown__item[aria-disabled='true']:hover {
  600. background-color: #f0f2f5 !important;
  601. }
  602. }
  603. .screenconsole-quick-edit {
  604. -webkit-font-smoothing: antialiased;
  605. -moz-osx-font-smoothing: grayscale;
  606. text-rendering: optimizeLegibility;
  607. .edit-item {
  608. margin-bottom: 24px;
  609. background: #f8fafc;
  610. padding: 16px;
  611. border-radius: 20px;
  612. border: 1px solid #eef2f8;
  613. display: flex;
  614. flex-wrap: wrap;
  615. align-items: stretch;
  616. gap: 16px;
  617. }
  618. .edit-item:last-child {
  619. margin-bottom: 0;
  620. }
  621. .edit-item-index {
  622. width: 36px;
  623. flex-shrink: 0;
  624. font-weight: 700;
  625. font-size: 1.05rem;
  626. color: #1e293b;
  627. line-height: 1.2;
  628. align-self: center;
  629. text-align: right;
  630. padding-right: 4px;
  631. }
  632. .edit-item-select {
  633. flex: 2;
  634. min-width: 160px;
  635. display: flex;
  636. flex-direction: column;
  637. justify-content: flex-start;
  638. }
  639. .edit-item-select label {
  640. font-size: 13px;
  641. font-weight: 600;
  642. color: #334155;
  643. display: block;
  644. margin-bottom: 6px;
  645. letter-spacing: 0.02em;
  646. line-height: 1.4;
  647. }
  648. .edit-item-radio {
  649. flex: 1;
  650. min-width: 120px;
  651. display: flex;
  652. flex-direction: column;
  653. justify-content: center;
  654. gap: 6px;
  655. }
  656. .edit-item-radio > label {
  657. font-size: 13px;
  658. font-weight: 600;
  659. color: #334155;
  660. display: block;
  661. margin-bottom: 0;
  662. letter-spacing: 0.02em;
  663. line-height: 1.4;
  664. }
  665. /* Select 展示区:减轻小字号 + 粗体带来的发糊、发胀 */
  666. .edit-item-select .el-select {
  667. font-size: 13px;
  668. }
  669. .edit-item-select .el-select .el-input__inner {
  670. font-size: 13px;
  671. font-weight: 400;
  672. line-height: 1.5;
  673. color: #1e293b;
  674. -webkit-font-smoothing: antialiased;
  675. }
  676. .edit-item-select .el-select .el-input__wrapper {
  677. font-size: 13px;
  678. }
  679. .edit-item-select .el-select .el-select__placeholder,
  680. .edit-item-select .el-select .el-select__selected-item,
  681. .edit-item-select .el-select .el-select__selection-text {
  682. font-size: 13px !important;
  683. font-weight: 400 !important;
  684. line-height: 1.5 !important;
  685. color: #1e293b !important;
  686. -webkit-font-smoothing: antialiased;
  687. }
  688. .radio-group {
  689. display: flex;
  690. align-items: center;
  691. background: white;
  692. padding: 8px 14px;
  693. border-radius: 40px;
  694. border: 1px solid #e2e8f0;
  695. min-height: 30px;
  696. box-sizing: border-box;
  697. -webkit-font-smoothing: antialiased;
  698. }
  699. .radio-group .el-radio {
  700. margin-right: 0;
  701. height: auto;
  702. display: inline-flex;
  703. align-items: center;
  704. }
  705. .radio-group .el-radio__label {
  706. font-size: 13px;
  707. font-weight: 400;
  708. line-height: 1.5;
  709. color: #334155;
  710. padding-left: 8px;
  711. -webkit-font-smoothing: antialiased;
  712. }
  713. .radio-group .el-radio.is-checked .el-radio__label {
  714. color: #2563eb;
  715. font-weight: 500;
  716. }
  717. }
  718. .screenconsole-quick-save-btn {
  719. color: #fff !important;
  720. box-shadow: 0 2px 6px rgba(255, 124, 66, 0.2);
  721. }
  722. @media (max-width: 780px) {
  723. .screenconsole-quick-edit .edit-item {
  724. flex-direction: column;
  725. align-items: stretch;
  726. }
  727. .screenconsole-quick-edit .edit-item-index {
  728. width: auto;
  729. text-align: left;
  730. align-self: flex-start;
  731. padding-right: 0;
  732. }
  733. .screenconsole-quick-edit .edit-item-radio {
  734. justify-content: flex-start;
  735. }
  736. }
  737. </style>