FinishProductIn.vue 22 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010
  1. <!-- 成品入库 - 智能仓储风格 -->
  2. <template>
  3. <div class="stock-requisition-page">
  4. <!-- 背景层 -->
  5. <div class="bg-layer" :style="{ backgroundImage: `url(${bgImg})` }" />
  6. <!-- 顶部标题区域 -->
  7. <div class="header-section">
  8. <button class="logout-btn" @click="goHome">
  9. <i class="fas fa-home" />
  10. <span>主页</span>
  11. </button>
  12. <h1 class="page-title">成品入库</h1>
  13. <!-- 右侧操作按钮 -->
  14. <div class="header-actions">
  15. <button class="action-btn add-btn" @click="openModal">
  16. <i class="fas fa-plus-circle" />
  17. <span>添加成品</span>
  18. </button>
  19. </div>
  20. </div>
  21. <!-- 主内容区域 -->
  22. <main class="main-content">
  23. <!-- 统计信息 -->
  24. <div v-if="products.length > 0" class="stats-section">
  25. <span class="stats-text">当前已添加 {{ products.length }} 条成品记录</span>
  26. </div>
  27. <!-- 卡片网格区域 -->
  28. <div class="card-grid-wrapper">
  29. <!-- 空状态 -->
  30. <div v-if="products.length === 0" class="empty-state">
  31. <i class="fas fa-box-open empty-icon" />
  32. <p>请添加成品</p>
  33. </div>
  34. <!-- 卡片网格 -->
  35. <div v-else class="card-grid">
  36. <div v-for="(product, index) in products" :key="product.code || index" class="inventory-card">
  37. <!-- 卡片序号 -->
  38. <div class="card-index">{{ index + 1 }}</div>
  39. <!-- 图片区域 -->
  40. <div class="card-image">
  41. <div class="image-placeholder">
  42. <i class="fas fa-box" />
  43. </div>
  44. </div>
  45. <!-- 信息区域 -->
  46. <div class="card-info">
  47. <div class="info-row">
  48. <span class="info-label">名称:</span>
  49. <span class="info-value">{{ product.inventoryName || '-' }}</span>
  50. </div>
  51. <div class="info-row">
  52. <span class="info-label">图号:</span>
  53. <span class="info-value">{{ product.inventoryNo || '-' }}</span>
  54. </div>
  55. <div class="info-row location-row">
  56. <i class="fas fa-map-marker-alt location-icon" />
  57. <span class="info-value">{{ product.positionName || '-' }}</span>
  58. </div>
  59. </div>
  60. <!-- 操作区域 -->
  61. <div class="card-action-section">
  62. <button class="delete-btn" @click.stop="openDeleteDialog(index)">
  63. <i class="fas fa-trash" style="color: red !important;" /> 删除
  64. </button>
  65. </div>
  66. </div>
  67. </div>
  68. </div>
  69. </main>
  70. <!-- 底部操作按钮 -->
  71. <div class="bottom-actions">
  72. <button class="submit-btn" :disabled="products.length === 0" @click="handleSubmit">
  73. 提交入库
  74. </button>
  75. </div>
  76. <!-- 新增成品弹窗 -->
  77. <div v-if="isModalOpen" class="tech-modal-overlay" @click.self="closeModal">
  78. <div class="tech-modal">
  79. <!-- <button class="modal-close-btn" @click="closeModal">
  80. <i class="fas fa-times" />
  81. </button> -->
  82. <div class="modal-header">
  83. <h3>新增成品</h3>
  84. <p>请填写成品信息</p>
  85. </div>
  86. <div class="modal-body">
  87. <div class="form-item">
  88. <label>成品名称</label>
  89. <input v-model="newProduct.inventoryName" type="text" placeholder="请输入成品名称" />
  90. </div>
  91. <div class="form-item">
  92. <label>成品图号</label>
  93. <input v-model="newProduct.inventoryNo" type="text" placeholder="请输入成品图号" />
  94. </div>
  95. <div class="form-item">
  96. <label>货位选择</label>
  97. <v-select
  98. v-model="newProduct.positionId" :options="warehouseLocationOptions"
  99. :reduce="item => item.positionId" label="positionName" placeholder="请选择货位" :clearable="true"
  100. :filterable="true" class="dark-select" append-to-body
  101. >
  102. <template #no-options>
  103. <span>无该选项数据</span>
  104. </template>
  105. </v-select>
  106. </div>
  107. </div>
  108. <div class="modal-footer">
  109. <button class="modal-btn cancel" @click="closeModal">取消</button>
  110. <button class="modal-btn confirm" :disabled="!isFormValid" @click="saveProduct">保存</button>
  111. </div>
  112. </div>
  113. </div>
  114. <!-- 确认入库弹窗 -->
  115. <div v-if="showConfirmModal" class="tech-modal-overlay" @click.self="showConfirmModal = false">
  116. <div class="tech-modal success-modal">
  117. <!-- <button class="modal-close-btn" @click="showConfirmModal = false">
  118. <i class="fas fa-times" />
  119. </button> -->
  120. <div class="modal-content-row">
  121. <div class="modal-text">
  122. <h3>确认入库</h3>
  123. <p>您确定要将选中的 <span class="highlight">{{ products.length }}</span> 个成品进行入库操作吗?</p>
  124. </div>
  125. <div class="modal-icon">
  126. <div class="icon-box">
  127. <i class="fas fa-box" />
  128. <i class="fas fa-check-circle check-icon" />
  129. </div>
  130. </div>
  131. </div>
  132. <div class="modal-footer">
  133. <button class="modal-btn cancel" @click="showConfirmModal = false">取消</button>
  134. <button class="modal-btn confirm" @click="executeInbound">确认入库</button>
  135. </div>
  136. </div>
  137. </div>
  138. <!-- 删除成品确认弹窗 - 科技感风格 -->
  139. <div v-if="showDeleteModal" class="tech-modal-overlay" @click.self="showDeleteModal = false">
  140. <div class="tech-modal warning-modal">
  141. <!-- <button class="modal-close-btn" @click="showDeleteModal = false">
  142. <i class="fas fa-times" />
  143. </button> -->
  144. <div class="modal-content-row">
  145. <div class="modal-text">
  146. <h3>确认删除</h3>
  147. <p>你确认要删除掉该成品吗?</p>
  148. </div>
  149. <div class="modal-icon warning">
  150. <div class="icon-box">
  151. <i class="fas fa-trash" />
  152. </div>
  153. </div>
  154. </div>
  155. <div class="modal-footer">
  156. <button class="modal-btn cancel" @click="showDeleteModal = false">取消</button>
  157. <button class="modal-btn confirm danger" @click="confirmDelete">确认删除</button>
  158. </div>
  159. </div>
  160. </div>
  161. <!-- Loading -->
  162. <div v-if="loading" class="loading-overlay">
  163. <div class="loading-dots">
  164. <div class="dot" />
  165. <div class="dot" />
  166. <div class="dot" />
  167. </div>
  168. <span class="loading-text">加载中...</span>
  169. </div>
  170. </div>
  171. </template>
  172. <script setup>
  173. import { ref, reactive, onMounted, computed } from 'vue';
  174. import { useRouter } from 'vue-router';
  175. import { showNotify } from 'vant';
  176. import { generateStockIn, getLocatorList } from '../api/finishProduct';
  177. import vSelect from 'vue-select';
  178. import 'vue-select/dist/vue-select.css';
  179. import { gateController } from '../hardware/GateOperate.js';
  180. import { showErrorDialog } from '../util/errorDialog';
  181. // 图片资源
  182. import bgImg from '../assets/images/bj.png';
  183. const router = useRouter();
  184. // 返回主页
  185. const goHome = () => {
  186. router.push('/home');
  187. };
  188. // 加载状态
  189. const loading = ref(false);
  190. // 仓库货位数据
  191. const warehouseLocationOptions = ref([]);
  192. // 弹窗显示状态
  193. const isModalOpen = ref(false);
  194. const showConfirmModal = ref(false);
  195. const showDeleteModal = ref(false);
  196. const deleteIndex = ref(-1);
  197. // 成品列表
  198. const products = ref([]);
  199. // 新增成品表单
  200. const newProduct = reactive({
  201. inventoryName: '',
  202. inventoryNo: '',
  203. positionId: undefined,
  204. });
  205. // 表单校验
  206. const isFormValid = computed(() => {
  207. return (
  208. newProduct.inventoryName.trim() !== '' &&
  209. newProduct.inventoryNo.trim() !== '' &&
  210. !!newProduct.positionId
  211. );
  212. });
  213. // 打开弹窗
  214. const openModal = () => {
  215. resetForm();
  216. isModalOpen.value = true;
  217. };
  218. // 关闭弹窗
  219. const closeModal = () => {
  220. isModalOpen.value = false;
  221. };
  222. // 重置表单
  223. const resetForm = () => {
  224. newProduct.inventoryName = '';
  225. newProduct.inventoryNo = '';
  226. newProduct.positionId = undefined;
  227. };
  228. // 保存成品
  229. const saveProduct = () => {
  230. if (!isFormValid.value) {
  231. showErrorDialog('请完整填写成品信息');
  232. return;
  233. }
  234. const location = warehouseLocationOptions.value.find(
  235. item => item.positionId === newProduct.positionId,
  236. );
  237. console.log(location);
  238. products.value.push({
  239. inventoryName: newProduct.inventoryName.trim(),
  240. inventoryNo: newProduct.inventoryNo.trim(),
  241. positionId: newProduct.positionId,
  242. positionName: location ? location.positionName + ' / ' + location.warehouseName : '',
  243. });
  244. showNotify({ type: 'success', message: '成品已添加' });
  245. isModalOpen.value = false;
  246. };
  247. // 执行入库
  248. const handleSubmit = () => {
  249. if (products.value.length === 0) {
  250. showErrorDialog('暂无可提交的成品,请添加成品');
  251. return;
  252. }
  253. showConfirmModal.value = true;
  254. };
  255. // 提交入库
  256. const executeInbound = async () => {
  257. const count = products.value.length;
  258. loading.value = true;
  259. const params = products.value.map(item => {
  260. return {
  261. inventoryName: item.inventoryName,
  262. inventoryNo: item.inventoryNo,
  263. positionId: item.positionId,
  264. };
  265. });
  266. try {
  267. const res = await generateStockIn(params);
  268. if (res.errorCode === 0) {
  269. // 调用开门操作
  270. gateController('SHOTOPEN');
  271. showNotify({ type: 'success', message: `已提交 ${count} 条成品入库记录` });
  272. router.push('/home');
  273. } else {
  274. showErrorDialog(res.errorMessage || '添加失败');
  275. }
  276. products.value = [];
  277. showConfirmModal.value = false;
  278. } catch (error) {
  279. console.log(error);
  280. if(error.status !== 401) showErrorDialog('添加成品失败');
  281. } finally {
  282. loading.value = false;
  283. }
  284. };
  285. // 打开删除确认弹窗
  286. const openDeleteDialog = index => {
  287. deleteIndex.value = index;
  288. showDeleteModal.value = true;
  289. };
  290. // 确认删除成品
  291. const confirmDelete = () => {
  292. if (deleteIndex.value >= 0 && deleteIndex.value < products.value.length) {
  293. products.value.splice(deleteIndex.value, 1);
  294. showNotify({ type: 'success', message: '成品已删除' });
  295. }
  296. showDeleteModal.value = false;
  297. deleteIndex.value = -1;
  298. };
  299. const getLocators = async () => {
  300. try {
  301. const res = await getLocatorList();
  302. if (res && res.length > 0) {
  303. warehouseLocationOptions.value = res;
  304. } else {
  305. showErrorDialog('获取货位列表失败');
  306. }
  307. } catch (error) {
  308. console.log(error);
  309. if(error.status !== 401) showErrorDialog('获取货位列表失败');
  310. }
  311. };
  312. onMounted(() => {
  313. getLocators();
  314. });
  315. </script>
  316. <style scoped>
  317. /* ========== 基础样式 ========== */
  318. .stock-requisition-page {
  319. width: 100%;
  320. height: 100vh;
  321. max-height: 100vh;
  322. position: relative;
  323. font-family: 'Microsoft YaHei', sans-serif;
  324. color: #fff;
  325. overflow: hidden;
  326. display: flex;
  327. flex-direction: column;
  328. }
  329. /* 背景层 */
  330. .bg-layer {
  331. position: fixed;
  332. top: 0;
  333. left: 0;
  334. width: 100%;
  335. height: 100%;
  336. background-color: #041c3d;
  337. background-size: cover;
  338. background-position: center;
  339. background-repeat: no-repeat;
  340. z-index: 0;
  341. }
  342. /* 成品入库-操作按钮 - 绿色主题 */
  343. .action-btn {
  344. background: linear-gradient(90deg, #1a6a5a 0%, #0d5a4a 100%);
  345. border-color: #2affcf;
  346. }
  347. .action-btn:hover {
  348. background: linear-gradient(90deg, #2a7a6a 0%, #1d6a5a 100%);
  349. box-shadow: 0 0 20px rgba(42, 255, 207, 0.5);
  350. }
  351. .action-btn i {
  352. color: #2affcf;
  353. }
  354. /* ========== 主内容区域 ========== */
  355. .main-content {
  356. flex: 1;
  357. display: flex;
  358. flex-direction: column;
  359. padding: 20px 30px;
  360. position: relative;
  361. z-index: 10;
  362. min-height: 0;
  363. overflow: hidden;
  364. }
  365. /* 统计信息 */
  366. .stats-section {
  367. background: rgba(9, 61, 140, 0.5);
  368. border: 1px solid #049FD8;
  369. border-radius: 12px;
  370. padding: 15px 20px;
  371. margin-bottom: 15px;
  372. flex-shrink: 0;
  373. }
  374. .stats-text {
  375. color: #7ec8ff;
  376. font-size: 16px;
  377. }
  378. /* ========== 卡片网格区域 ========== */
  379. .card-grid-wrapper {
  380. flex: 1;
  381. display: flex;
  382. flex-direction: column;
  383. min-height: 0;
  384. overflow-y: auto;
  385. overflow-x: hidden;
  386. padding-bottom: 10px;
  387. scrollbar-width: none;
  388. -ms-overflow-style: none;
  389. }
  390. .card-grid-wrapper::-webkit-scrollbar {
  391. display: none;
  392. }
  393. /* 空状态 */
  394. .empty-state {
  395. flex: 1;
  396. display: flex;
  397. flex-direction: column;
  398. align-items: center;
  399. justify-content: center;
  400. color: #7ec8ff;
  401. padding: 60px 0;
  402. }
  403. .empty-icon {
  404. font-size: 80px;
  405. margin-bottom: 20px;
  406. opacity: 0.5;
  407. }
  408. .empty-state p {
  409. font-size: 18px;
  410. margin: 0;
  411. }
  412. /* 卡片网格 - 4列布局 */
  413. .card-grid {
  414. display: grid;
  415. grid-template-columns: repeat(4, 1fr);
  416. gap: 20px;
  417. }
  418. /* ========== 设备卡片样式 ========== */
  419. .inventory-card {
  420. background: rgba(5, 30, 60, 0.8);
  421. border: 2px solid #0d4a8a;
  422. border-radius: 12px;
  423. overflow: hidden;
  424. transition: all 0.3s ease;
  425. position: relative;
  426. }
  427. .inventory-card:hover {
  428. border-color: #1e90ff;
  429. box-shadow: 0 0 20px rgba(30, 144, 255, 0.3);
  430. transform: translateY(-3px);
  431. }
  432. /* 卡片序号 - 左上角 */
  433. .card-index {
  434. position: absolute;
  435. top: 10px;
  436. left: 10px;
  437. width: 36px;
  438. height: 36px;
  439. background: linear-gradient(135deg, #1e90ff 0%, #00bfff 100%);
  440. border-radius: 50%;
  441. display: flex;
  442. align-items: center;
  443. justify-content: center;
  444. font-size: 16px;
  445. font-weight: bold;
  446. color: #fff;
  447. z-index: 2;
  448. box-shadow: 0 2px 8px rgba(0, 191, 255, 0.4);
  449. }
  450. /* 图片区域 */
  451. .card-image {
  452. width: 100%;
  453. aspect-ratio: 3 / 4;
  454. display: flex;
  455. align-items: center;
  456. justify-content: center;
  457. overflow: hidden;
  458. margin: 15px;
  459. margin-bottom: 10px;
  460. border-radius: 8px;
  461. width: calc(100% - 30px);
  462. }
  463. .image-placeholder {
  464. display: flex;
  465. align-items: center;
  466. justify-content: center;
  467. width: 100%;
  468. height: 100%;
  469. background: linear-gradient(135deg, #f0f4f8 0%, #e8ecf0 100%);
  470. color: #fff;
  471. font-size: 48px;
  472. }
  473. /* 信息区域 */
  474. .card-info {
  475. padding: 10px 15px 15px;
  476. }
  477. .info-row {
  478. display: flex;
  479. align-items: baseline;
  480. margin-bottom: 6px;
  481. font-size: 14px;
  482. }
  483. .info-row:last-child {
  484. margin-bottom: 0;
  485. }
  486. .info-label {
  487. color: #7ec8ff;
  488. white-space: nowrap;
  489. margin-right: 5px;
  490. }
  491. .info-value {
  492. color: #fff;
  493. flex: 1;
  494. overflow: hidden;
  495. text-overflow: ellipsis;
  496. white-space: nowrap;
  497. }
  498. /* 位置行样式 */
  499. .location-row {
  500. display: flex;
  501. align-items: center;
  502. margin-top: 4px;
  503. padding-top: 4px;
  504. border-top: 1px solid rgba(30, 144, 255, 0.2);
  505. }
  506. .location-icon {
  507. color: #00bfff;
  508. font-size: 12px;
  509. margin-right: 6px;
  510. }
  511. /* ========== 操作区域 ========== */
  512. .card-action-section {
  513. padding: 12px 15px;
  514. border-top: 1px solid rgba(30, 144, 255, 0.3);
  515. background: rgba(9, 61, 140, 0.3);
  516. min-height: 50px;
  517. display: flex;
  518. align-items: center;
  519. justify-content: center;
  520. }
  521. /* 删除按钮 */
  522. .delete-btn {
  523. padding: 8px 20px;
  524. background: rgba(239, 68, 68, 0.2);
  525. border: 1px solid #ef4444;
  526. border-radius: 6px;
  527. color: #ef4444;
  528. font-size: 13px;
  529. cursor: pointer;
  530. transition: all 0.3s;
  531. display: flex;
  532. align-items: center;
  533. gap: 6px;
  534. }
  535. .delete-btn:hover {
  536. background: rgba(239, 68, 68, 0.4);
  537. box-shadow: 0 0 10px rgba(239, 68, 68, 0.3);
  538. }
  539. /* ========== 底部操作按钮 ========== */
  540. .bottom-actions {
  541. width: 100%;
  542. padding: 15px 30px;
  543. box-sizing: border-box;
  544. background: rgba(4, 28, 61, 0.95);
  545. z-index: 20;
  546. flex-shrink: 0;
  547. }
  548. .submit-btn {
  549. width: 100%;
  550. padding: 18px 0;
  551. background: #4a99e2;
  552. border: none;
  553. border-radius: 12px;
  554. font-size: 24px;
  555. font-weight: bold;
  556. color: #fff;
  557. cursor: pointer;
  558. transition: all 0.3s;
  559. letter-spacing: 8px;
  560. text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
  561. }
  562. .submit-btn:hover:not(:disabled) {
  563. box-shadow: 0 0 30px rgba(74, 153, 226, 0.6);
  564. transform: translateY(-2px);
  565. }
  566. .submit-btn:disabled {
  567. opacity: 0.5;
  568. cursor: not-allowed;
  569. }
  570. /* ========== 科技感弹窗样式 ========== */
  571. .tech-modal-overlay {
  572. position: fixed;
  573. top: 0;
  574. left: 0;
  575. width: 100%;
  576. height: 100%;
  577. background: rgba(4, 28, 61, 0.9);
  578. display: flex;
  579. align-items: center;
  580. justify-content: center;
  581. z-index: 2000;
  582. }
  583. .tech-modal {
  584. background: linear-gradient(135deg, #0a3d6d 0%, #041c3d 100%);
  585. border: 1px solid #049FD8;
  586. border-radius: 16px;
  587. width: 90%;
  588. max-width: 500px;
  589. position: relative;
  590. box-shadow: 0 0 40px rgba(4, 159, 216, 0.3);
  591. }
  592. .modal-close-btn {
  593. position: absolute;
  594. top: 15px;
  595. right: 15px;
  596. width: 36px;
  597. height: 36px;
  598. background: rgba(255, 255, 255, 0.1);
  599. border: none;
  600. border-radius: 50%;
  601. color: #7ec8ff;
  602. font-size: 18px;
  603. cursor: pointer;
  604. transition: all 0.3s;
  605. display: flex;
  606. align-items: center;
  607. justify-content: center;
  608. }
  609. .modal-close-btn:hover {
  610. background: rgba(255, 255, 255, 0.2);
  611. color: #fff;
  612. }
  613. .modal-header {
  614. padding: 30px 30px 20px;
  615. }
  616. .modal-header h3 {
  617. font-size: 22px;
  618. font-weight: bold;
  619. color: #fff;
  620. margin: 0 0 8px 0;
  621. }
  622. .modal-header p {
  623. font-size: 14px;
  624. color: #7ec8ff;
  625. margin: 0;
  626. }
  627. .modal-body {
  628. padding: 0 30px 20px;
  629. }
  630. .form-item {
  631. margin-bottom: 20px;
  632. }
  633. .form-item:last-child {
  634. margin-bottom: 0;
  635. }
  636. .form-item label {
  637. display: block;
  638. font-size: 14px;
  639. color: #7ec8ff;
  640. margin-bottom: 8px;
  641. font-weight: 500;
  642. }
  643. .form-item input {
  644. width: 100%;
  645. padding: 12px 15px;
  646. background: rgba(13, 58, 106, 0.8);
  647. border: 1px solid #2a7fff;
  648. border-radius: 8px;
  649. color: #fff;
  650. font-size: 15px;
  651. outline: none;
  652. transition: all 0.3s;
  653. box-sizing: border-box;
  654. }
  655. .form-item input::placeholder {
  656. color: #5a8abf;
  657. }
  658. .form-item input:focus {
  659. border-color: #00bfff;
  660. box-shadow: 0 0 10px rgba(0, 191, 255, 0.3);
  661. }
  662. .modal-footer {
  663. padding: 20px 30px 30px;
  664. display: flex;
  665. gap: 15px;
  666. }
  667. .modal-btn {
  668. flex: 1;
  669. padding: 14px 0;
  670. border-radius: 8px;
  671. font-size: 16px;
  672. font-weight: 600;
  673. cursor: pointer;
  674. transition: all 0.3s;
  675. }
  676. .modal-btn.cancel {
  677. background: transparent;
  678. border: 1px solid #2a7fff;
  679. color: #7ec8ff;
  680. }
  681. .modal-btn.cancel:hover {
  682. background: rgba(42, 127, 255, 0.2);
  683. }
  684. .modal-btn.confirm {
  685. background: linear-gradient(90deg, #1e90ff 0%, #00bfff 100%);
  686. border: none;
  687. color: #fff;
  688. }
  689. .modal-btn.confirm:hover:not(:disabled) {
  690. box-shadow: 0 0 20px rgba(30, 144, 255, 0.5);
  691. }
  692. .modal-btn.confirm:disabled {
  693. opacity: 0.5;
  694. cursor: not-allowed;
  695. }
  696. .modal-btn.confirm.danger {
  697. background: linear-gradient(90deg, #ef4444 0%, #f87171 100%);
  698. }
  699. .modal-btn.confirm.danger:hover {
  700. box-shadow: 0 0 20px rgba(239, 68, 68, 0.5);
  701. }
  702. /* 成功/警告弹窗样式 */
  703. .modal-content-row {
  704. padding: 30px;
  705. display: flex;
  706. align-items: center;
  707. gap: 30px;
  708. }
  709. .modal-text {
  710. flex: 1;
  711. }
  712. .modal-text h3 {
  713. font-size: 22px;
  714. font-weight: bold;
  715. color: #fff;
  716. margin: 0 0 12px 0;
  717. }
  718. .modal-text p {
  719. font-size: 15px;
  720. color: #7ec8ff;
  721. margin: 0;
  722. line-height: 1.6;
  723. }
  724. .modal-text .highlight {
  725. color: #00bfff;
  726. font-weight: bold;
  727. }
  728. .modal-icon {
  729. flex-shrink: 0;
  730. }
  731. .icon-box {
  732. width: 80px;
  733. height: 80px;
  734. background: linear-gradient(135deg, #1e90ff 0%, #00bfff 100%);
  735. border-radius: 12px;
  736. display: flex;
  737. align-items: center;
  738. justify-content: center;
  739. font-size: 36px;
  740. color: #fff;
  741. position: relative;
  742. transform: perspective(100px) rotateY(-10deg);
  743. box-shadow: 0 10px 30px rgba(0, 191, 255, 0.3);
  744. }
  745. .icon-box .check-icon {
  746. position: absolute;
  747. bottom: -5px;
  748. right: -5px;
  749. font-size: 24px;
  750. color: #10b981;
  751. background: #041c3d;
  752. border-radius: 50%;
  753. padding: 2px;
  754. }
  755. .modal-icon.warning .icon-box {
  756. background: linear-gradient(135deg, #ef4444 0%, #f87171 100%);
  757. box-shadow: 0 10px 30px rgba(239, 68, 68, 0.3);
  758. }
  759. /* Vue Select 深色主题 */
  760. :deep(.dark-select .vs__dropdown-toggle) {
  761. background: rgba(13, 58, 106, 0.8);
  762. border: 1px solid #2a7fff;
  763. border-radius: 8px;
  764. padding: 8px 12px;
  765. min-height: 44px;
  766. }
  767. :deep(.dark-select .vs__selected) {
  768. color: #fff;
  769. }
  770. :deep(.dark-select .vs__search) {
  771. color: #fff;
  772. }
  773. :deep(.dark-select .vs__search::placeholder) {
  774. color: #5a8abf;
  775. }
  776. :deep(.dark-select .vs__actions svg) {
  777. fill: #7ec8ff;
  778. }
  779. :deep(.dark-select .vs__dropdown-menu) {
  780. background: rgba(13, 58, 106, 0.95);
  781. border: 1px solid #2a7fff;
  782. border-radius: 8px;
  783. z-index: 99999;
  784. }
  785. :deep(.dark-select .vs__dropdown-option) {
  786. color: #fff;
  787. padding: 10px 15px;
  788. }
  789. :deep(.dark-select .vs__dropdown-option--highlight) {
  790. background: rgba(30, 144, 255, 0.3);
  791. }
  792. /* ========== 响应式 - 横屏 ========== */
  793. @media screen and (orientation: landscape) {
  794. .header-section {
  795. padding: 10px 20px;
  796. }
  797. .page-title {
  798. font-size: 22px;
  799. }
  800. .logout-btn {
  801. padding: 6px 14px;
  802. font-size: 13px;
  803. left: 20px;
  804. }
  805. .header-actions {
  806. right: 20px;
  807. }
  808. .action-btn {
  809. padding: 6px 14px;
  810. font-size: 12px;
  811. }
  812. .main-content {
  813. padding: 20px;
  814. }
  815. .card-grid {
  816. grid-template-columns: repeat(6, 1fr);
  817. gap: 15px;
  818. }
  819. .card-index {
  820. width: 24px;
  821. height: 24px;
  822. font-size: 11px;
  823. top: 6px;
  824. left: 6px;
  825. }
  826. .card-image {
  827. margin: 10px;
  828. margin-bottom: 8px;
  829. width: calc(100% - 20px);
  830. }
  831. .image-placeholder {
  832. font-size: 36px;
  833. }
  834. .card-info {
  835. padding: 6px 10px 8px;
  836. }
  837. .info-row {
  838. font-size: 11px;
  839. margin-bottom: 3px;
  840. }
  841. .card-action-section {
  842. padding: 8px 10px;
  843. min-height: 40px;
  844. }
  845. .delete-btn {
  846. padding: 5px 12px;
  847. font-size: 11px;
  848. }
  849. .bottom-actions {
  850. padding: 10px 20px;
  851. }
  852. .submit-btn {
  853. padding: 12px 0;
  854. font-size: 18px;
  855. letter-spacing: 6px;
  856. border-radius: 8px;
  857. }
  858. :deep(.v-select .vs__dropdown-toggle,
  859. .dark-select .vs__dropdown-toggle,
  860. .filter-select .vs__dropdown-toggle,
  861. .dialog-select .vs__dropdown-toggle) {
  862. padding: 8px 12px !important;
  863. min-height: 48px !important;
  864. }
  865. }
  866. </style>
  867. <style>
  868. /* 全局样式:确保 v-select 下拉菜单在模态框之上 */
  869. .vs__dropdown-menu {
  870. z-index: 99999 !important;
  871. }
  872. /* vue-select 的下拉菜单容器 */
  873. .vs--open .vs__dropdown-menu {
  874. z-index: 99999 !important;
  875. }
  876. /* 大选择器的下拉菜单 */
  877. .large-select .vs__dropdown-menu {
  878. z-index: 99999 !important;
  879. }
  880. </style>