| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216 |
- <!-- 成品出库 - 智能仓储风格 -->
- <template>
- <div class="stock-requisition-page">
- <!-- 背景层 -->
- <div class="bg-layer" :style="{ backgroundImage: `url(${bgImg})` }" />
- <!-- 顶部标题区域 -->
- <div class="header-section">
- <button class="logout-btn" @click="goHome">
- <i class="fas fa-home" />
- <span>主页</span>
- </button>
- <h1 class="page-title">成品出库</h1>
- </div>
- <!-- 主内容区域 -->
- <main class="main-content">
- <!-- 筛选区域 -->
- <div class="filter-section">
- <div class="filter-row">
- <div class="filter-item">
- <label class="filter-label">名称</label>
- <input
- v-model="searchForm.inventoryName" type="text" placeholder="输入名称"
- class="filter-input dark-input" @keyup.enter="handleSearch"
- />
- </div>
- <div class="filter-item">
- <label class="filter-label">图号</label>
- <input
- v-model="searchForm.inventoryDrawNo" type="text" placeholder="输入图号"
- class="filter-input dark-input" @keyup.enter="handleSearch"
- />
- </div>
- <!-- <div class="filter-item">
- <label class="filter-label">仓库</label>
- <v-select
- v-model="searchForm.warehouseId" :options="warehouseList" :reduce="item => item.id" label="name"
- placeholder="选择仓库" :clearable="true" :filterable="true" class="filter-select dark-select"
- @update:model-value="handleSearch"
- >
- <template #no-options>
- <span>无该选项数据</span>
- </template>
- </v-select>
- </div> -->
- <div class="filter-buttons">
- <button class="search-btn" @click="handleSearch">搜索</button>
- <button class="reset-btn" @click="handleReset">重置</button>
- </div>
- </div>
- </div>
- <!-- 全选控制栏 -->
- <div v-if="finishProductList.length > 0" class="select-all-bar">
- <van-checkbox :model-value="isAllSelected" @update:model-value="toggleSelectAll">
- <span class="select-all-text">全选当前页(已选 {{ selectedProducts.length }} 项)</span>
- </van-checkbox>
- </div>
- <!-- 卡片网格区域 -->
- <div ref="cardGridWrapper" class="card-grid-wrapper" @scroll="handleScroll">
- <!-- 空状态 -->
- <div v-if="finishProductList.length === 0 && !loading" class="empty-state">
- <i class="fas fa-box-open empty-icon" />
- <p>暂无成品</p>
- </div>
- <!-- 卡片网格 -->
- <div v-else class="card-grid">
- <div
- v-for="(product, index) in finishProductList" :key="product.id"
- class="inventory-card"
- :class="{ 'selected': selectedProducts.includes(product.id) }"
- @click="toggleSelect(product.id)"
- >
- <!-- 卡片序号 -->
- <div class="card-index">{{ index + 1 }}</div>
- <!-- 图片区域 -->
- <div class="card-image">
- <div class="image-placeholder">
- <i class="fas fa-box" />
- </div>
- </div>
- <!-- 信息区域 -->
- <div class="card-info">
- <div class="info-row">
- <span class="info-label">名称:</span>
- <span class="info-value">{{ product.inventoryName || '-' }}</span>
- </div>
- <div class="info-row">
- <span class="info-label">图号:</span>
- <span class="info-value">{{ product.inventoryDrawNo || '-' }}</span>
- </div>
- <div class="info-row location-row">
- <i class="fas fa-map-marker-alt location-icon" />
- <span class="info-value">{{ product.inventoryPosition || '-' }} / {{ product.inventoryWarehouse || '-' }}</span>
- </div>
- </div>
- <!-- 选中状态指示 -->
- <div v-if="selectedProducts.includes(product.id)" class="selected-indicator">
- <i class="fas fa-check" />
- </div>
- </div>
- </div>
- <!-- 加载更多提示 -->
- <div v-if="loadingMore" class="loading-more">
- <div class="loading-more-spinner" />
- <span>加载中...</span>
- </div>
- <div v-else-if="noMoreData && finishProductList.length > 0" class="no-more-data">
- <span>没有更多数据了</span>
- </div>
- </div>
- </main>
- <!-- 底部操作按钮 -->
- <div class="bottom-actions">
- <button class="submit-btn" :disabled="selectedProducts.length === 0" @click="confirmOutbound">
- 确认出库
- </button>
- </div>
- <!-- 确认出库弹窗 -->
- <div v-if="showConfirmModal" class="tech-modal-overlay" @click.self="showConfirmModal = false">
- <div class="tech-modal success-modal">
- <!-- <button class="modal-close-btn" @click="showConfirmModal = false">
- <i class="fas fa-times" />
- </button> -->
- <div class="modal-content-row">
- <div class="modal-text">
- <h3>确认出库</h3>
- <p>您确定要将选中的 <span class="highlight">{{ selectedProducts.length }}</span> 个成品进行出库操作吗?</p>
- </div>
- <div class="modal-icon">
- <div class="icon-box">
- <i class="fas fa-box" />
- <i class="fas fa-check-circle check-icon" />
- </div>
- </div>
- </div>
- <div class="modal-footer">
- <button class="modal-btn cancel" @click="showConfirmModal = false">取消</button>
- <button class="modal-btn confirm" @click="executeOutbound">确认出库</button>
- </div>
- </div>
- </div>
- <!-- Loading -->
- <div v-if="loading" class="loading-overlay">
- <div class="loading-dots">
- <div class="dot" />
- <div class="dot" />
- <div class="dot" />
- </div>
- <span class="loading-text">加载中...</span>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, computed, onMounted, nextTick } from 'vue';
- import { useRouter } from 'vue-router';
- import { showNotify } from 'vant';
- import { getWarehouseList } from '../api/stock.js';
- import { queryFinishProduct, generateStockOut } from '../api/finishProduct.js';
- import vSelect from 'vue-select';
- import 'vue-select/dist/vue-select.css';
- import { gateController } from '../hardware/GateOperate.js';
- // 图片资源
- import bgImg from '../assets/images/bj.png';
- const router = useRouter();
- // 返回主页
- const goHome = () => {
- router.push('/home');
- };
- // 滚动容器ref
- const cardGridWrapper = ref(null);
- // 加载状态
- const loading = ref(false);
- const loadingMore = ref(false);
- const noMoreData = ref(false);
- // 仓库列表
- const warehouseList = ref([]);
- // 筛选表单
- const searchForm = ref({
- inventoryName: '',
- inventoryDrawNo: '',
- // warehouseId: undefined,
- });
- // 无限滚动分页参数
- const pageSize = 20;
- const currentStart = ref(0);
- const total = ref(0);
- const finishProductList = ref([]);
- // 选中项
- const selectedProducts = ref([]); // 存储选中的 product.id
- // 计算是否全选
- const isAllSelected = computed(() => {
- if (finishProductList.value.length === 0) return false;
- return finishProductList.value.every(item => selectedProducts.value.includes(item.id));
- });
- // 弹窗与提示
- const showConfirmModal = ref(false);
- // 滚动事件处理 - 无限滚动
- const handleScroll = e => {
- const container = e.target;
- const { scrollTop, scrollHeight, clientHeight } = container;
-
- // 距离底部100px时触发加载
- if (scrollHeight - scrollTop - clientHeight < 100) {
- loadMore();
- }
- };
- // 检查容器是否需要继续加载(当内容不足以产生滚动条时自动加载更多)
- const checkAndLoadMore = async () => {
- await nextTick();
- const container = cardGridWrapper.value;
- if (!container) return;
-
- // 如果内容高度不足以产生滚动条,且还有更多数据,继续加载
- if (container.scrollHeight <= container.clientHeight && !noMoreData.value && !loadingMore.value) {
- loadMore();
- }
- };
- // 是否正在执行初次加载(防止重复请求)
- let isInitialLoading = false;
- // 加载更多数据
- const loadMore = async () => {
- // 增加 isInitialLoading 检查,防止初次加载时触发 loadMore
- if (loadingMore.value || noMoreData.value || loading.value || isInitialLoading) return;
-
- loadingMore.value = true;
-
- const params = {
- ...searchForm.value,
- range: {
- start: currentStart.value,
- length: pageSize,
- },
- };
- try {
- const res = await queryFinishProduct(params);
- if (res.errorCode === 0) {
- if (res.datas && res.datas.length > 0) {
- finishProductList.value = [...finishProductList.value, ...res.datas];
- currentStart.value += res.datas.length;
- total.value = res.total;
-
- // 检查是否还有更多数据
- if (finishProductList.value.length >= res.total) {
- noMoreData.value = true;
- } else {
- // 加载完成后检查是否需要继续加载
- checkAndLoadMore();
- }
- } else {
- noMoreData.value = true;
- }
- }
- } catch (error) {
- console.error('加载更多数据失败:', error);
- } finally {
- loadingMore.value = false;
- }
- };
- // 搜索
- const handleSearch = () => {
- getList();
- };
- // 重置
- const handleReset = () => {
- searchForm.value = {
- warehouseId: undefined,
- inventoryName: '',
- inventoryDrawNo: '',
- };
- getList();
- };
- // 卡片勾选
- const toggleSelect = id => {
- const index = selectedProducts.value.indexOf(id);
- if (index > -1) {
- selectedProducts.value.splice(index, 1);
- } else {
- selectedProducts.value.push(id);
- }
- };
- // 处理复选框变化
- const handleCheckboxChange = (checked, id) => {
- if (checked) {
- if (!selectedProducts.value.includes(id)) {
- selectedProducts.value.push(id);
- }
- } else {
- const index = selectedProducts.value.indexOf(id);
- if (index > -1) {
- selectedProducts.value.splice(index, 1);
- }
- }
- };
- // 全选/取消全选
- const toggleSelectAll = checked => {
- if (checked) {
- // 全选当前页所有项(累加模式)
- finishProductList.value.forEach(item => {
- if (!selectedProducts.value.includes(item.id)) {
- selectedProducts.value.push(item.id);
- }
- });
- } else {
- // 取消全选当前页
- const currentPageIds = finishProductList.value.map(item => item.id);
- selectedProducts.value = selectedProducts.value.filter(id => !currentPageIds.includes(id));
- }
- };
- // 打开发起出库确认
- const confirmOutbound = () => {
- if (selectedProducts.value.length === 0) {
- showNotify({ type: 'danger', message: '请选择要确认出库的成品' });
- return;
- }
- showConfirmModal.value = true;
- };
- // 提交中状态(与列表加载分开管理)
- const submitting = ref(false);
- // 执行出库
- const executeOutbound = async () => {
- showConfirmModal.value = false;
- if (selectedProducts.value.length === 0) return;
- submitting.value = true;
- try {
- const res = await generateStockOut(selectedProducts.value);
- if (res.errorCode === 0) {
- // 调用开门操作
- gateController('SHOTOPEN');
- showNotify({ type: 'success', message: '出库成功' });
- selectedProducts.value = [];
- await getList(); // 重新加载列表
- } else {
- showNotify({ type: 'danger', message: res.errorMessage });
- }
- } catch (error) {
- console.log(error);
- showNotify({ type: 'danger', message: '出库失败' });
- } finally {
- submitting.value = false;
- }
- };
- // 初次加载成品列表
- const getList = async () => {
- // 防止重复调用
- if (isInitialLoading) return;
- isInitialLoading = true;
-
- loading.value = true;
- currentStart.value = 0;
- noMoreData.value = false;
- // 立即清空列表,防止数据重复
- finishProductList.value = [];
-
- const params = {
- ...searchForm.value,
- range: {
- start: 0,
- length: pageSize,
- },
- };
-
- try {
- const res = await queryFinishProduct(params);
- if (res.errorCode === 0) {
- if (res.datas && res.datas.length > 0) {
- finishProductList.value = res.datas;
- currentStart.value = res.datas.length;
- total.value = res.total;
-
- if (finishProductList.value.length >= res.total) {
- noMoreData.value = true;
- } else {
- // 初次加载完成后检查是否需要继续加载
- checkAndLoadMore();
- }
- } else {
- finishProductList.value = [];
- total.value = 0;
- noMoreData.value = true;
- }
- } else {
- finishProductList.value = [];
- total.value = 0;
- showNotify({ type: 'danger', message: res.errorMessage });
- }
- } catch (error) {
- console.error('获取成品列表失败:', error);
- showNotify({ type: 'danger', message: '获取成品列表失败' });
- } finally {
- loading.value = false;
- isInitialLoading = false;
- }
- };
- // 获取仓库列表
- const getWarehouse = async () => {
- try {
- const res = await getWarehouseList();
- if (res.errorCode === 0) {
- if (res.datas && res.datas.length > 0) {
- warehouseList.value = res.datas;
- } else {
- warehouseList.value = [];
- }
- } else {
- showNotify({ type: 'danger', message: res.errorMessage });
- }
- } catch (error) {
- console.error('获取仓库列表API调用失败:', error);
- showNotify({ type: 'danger', message: '获取仓库数据失败' });
- } finally {
- loading.value = false;
- }
- };
- onMounted(() => {
- getWarehouse();
- getList();
- });
- </script>
- <style scoped>
- /* ========== 基础样式 ========== */
- .stock-requisition-page {
- width: 100%;
- height: 100vh;
- max-height: 100vh;
- position: relative;
- font-family: 'Microsoft YaHei', sans-serif;
- color: #fff;
- overflow: hidden;
- display: flex;
- flex-direction: column;
- }
- /* 背景层 */
- .bg-layer {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: #041c3d;
- background-size: cover;
- background-position: center;
- background-repeat: no-repeat;
- z-index: 0;
- }
- /* ========== 主内容区域 ========== */
- .main-content {
- flex: 1;
- display: flex;
- flex-direction: column;
- padding: 0 30px;
- position: relative;
- z-index: 10;
- min-height: 0;
- overflow: hidden;
- }
- /* ========== 筛选区域 ========== */
- .filter-section {
- background: rgba(9, 61, 140, 0.5);
- border: 1px solid #049FD8;
- border-radius: 12px;
- padding: 20px;
- margin-bottom: 15px;
- flex-shrink: 0;
- }
- .filter-row {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 20px;
- }
- .filter-item {
- display: flex;
- align-items: center;
- gap: 10px;
- }
- .filter-label {
- font-size: 16px;
- color: #7ec8ff;
- white-space: nowrap;
- font-weight: 500;
- }
- .filter-select {
- width: 200px;
- }
- .filter-input.dark-input {
- width: 200px;
- padding: 10px 15px;
- background: rgba(13, 58, 106, 0.8);
- border: 1px solid #2a7fff;
- border-radius: 6px;
- color: #fff;
- font-size: 14px;
- outline: none;
- transition: all 0.3s;
- }
- .filter-input.dark-input::placeholder {
- color: #7ec8ff;
- opacity: 0.7;
- }
- .filter-input.dark-input:focus {
- border-color: #00bfff;
- box-shadow: 0 0 10px rgba(0, 191, 255, 0.3);
- }
- .filter-buttons {
- display: flex;
- gap: 15px;
- margin-left: auto;
- }
- .search-btn,
- .reset-btn {
- padding: 10px 30px;
- border-radius: 6px;
- font-size: 16px;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.3s;
- border: none;
- }
- .search-btn {
- background: linear-gradient(90deg, #1e90ff 0%, #00bfff 100%);
- color: #fff;
- }
- .search-btn:hover {
- box-shadow: 0 0 15px rgba(30, 144, 255, 0.6);
- transform: translateY(-2px);
- }
- .reset-btn {
- background: rgba(13, 58, 106, 0.8);
- border: 1px solid #2a7fff;
- color: #fff;
- }
- .reset-btn:hover {
- background: rgba(26, 74, 122, 0.8);
- box-shadow: 0 0 10px rgba(30, 144, 255, 0.3);
- }
- /* Vue Select 深色主题 */
- :deep(.dark-select .vs__dropdown-toggle) {
- background: rgba(13, 58, 106, 0.8);
- border: 1px solid #2a7fff;
- border-radius: 6px;
- padding: 6px 10px;
- min-height: 40px;
- }
- :deep(.dark-select .vs__selected) {
- color: #fff;
- }
- :deep(.dark-select .vs__search) {
- color: #fff;
- }
- :deep(.dark-select .vs__search::placeholder) {
- color: #7ec8ff;
- opacity: 0.7;
- }
- :deep(.dark-select .vs__actions svg) {
- fill: #7ec8ff;
- }
- :deep(.dark-select .vs__dropdown-menu) {
- background: rgba(13, 58, 106, 0.95);
- border: 1px solid #2a7fff;
- border-radius: 6px;
- z-index: 9999;
- }
- :deep(.dark-select .vs__dropdown-option) {
- color: #fff;
- padding: 10px 15px;
- }
- :deep(.dark-select .vs__dropdown-option--highlight) {
- background: rgba(30, 144, 255, 0.3);
- }
- :deep(.filter-input .van-cell::after) {
- display: none !important;
- }
- /* ========== 全选控制栏 ========== */
- .select-all-bar {
- background: rgba(9, 61, 140, 0.3);
- border: 1px solid #049FD8;
- border-radius: 8px;
- padding: 10px 20px;
- margin-bottom: 10px;
- display: flex;
- align-items: center;
- flex-shrink: 0;
- }
- .select-all-text {
- font-size: 14px;
- color: #7ec8ff;
- font-weight: 500;
- }
- :deep(.select-all-bar .van-checkbox__label) {
- color: #7ec8ff;
- }
- /* ========== 卡片网格区域 ========== */
- .card-grid-wrapper {
- flex: 1;
- display: flex;
- flex-direction: column;
- min-height: 0;
- overflow-y: auto;
- overflow-x: hidden;
- padding-bottom: 10px;
- scrollbar-width: none;
- -ms-overflow-style: none;
- }
- .card-grid-wrapper::-webkit-scrollbar {
- display: none;
- }
- /* 空状态 */
- .empty-state {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- color: #7ec8ff;
- padding: 60px 0;
- }
- .empty-icon {
- font-size: 80px;
- margin-bottom: 20px;
- opacity: 0.5;
- }
- .empty-state p {
- font-size: 18px;
- margin: 0;
- }
- /* 卡片网格 - 4列布局 */
- .card-grid {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 20px;
- }
- /* ========== 设备卡片样式 ========== */
- .inventory-card {
- background: rgba(5, 30, 60, 0.8);
- border: 2px solid #0d4a8a;
- border-radius: 12px;
- overflow: hidden;
- transition: all 0.3s ease;
- position: relative;
- cursor: pointer;
- }
- .inventory-card:hover {
- border-color: #1e90ff;
- box-shadow: 0 0 20px rgba(30, 144, 255, 0.3);
- transform: translateY(-3px);
- }
- .inventory-card.selected {
- border-color: #00bfff;
- box-shadow: 0 0 25px rgba(0, 191, 255, 0.4);
- }
- /* 卡片序号 - 左上角 */
- .card-index {
- position: absolute;
- top: 10px;
- left: 10px;
- width: 36px;
- height: 36px;
- background: linear-gradient(135deg, #1e90ff 0%, #00bfff 100%);
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 16px;
- font-weight: bold;
- color: #fff;
- z-index: 2;
- box-shadow: 0 2px 8px rgba(0, 191, 255, 0.4);
- }
- /* 图片区域 */
- .card-image {
- width: 100%;
- aspect-ratio: 3 / 4;
- display: flex;
- align-items: center;
- justify-content: center;
- overflow: hidden;
- margin: 15px;
- margin-bottom: 10px;
- border-radius: 8px;
- width: calc(100% - 30px);
- }
- .image-placeholder {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 100%;
- height: 100%;
- background: linear-gradient(135deg, #f0f4f8 0%, #e8ecf0 100%);
- color: #fff;
- font-size: 48px;
- }
- /* 信息区域 */
- .card-info {
- padding: 10px 15px 15px;
- }
- .info-row {
- display: flex;
- align-items: baseline;
- margin-bottom: 6px;
- font-size: 14px;
- }
- .info-row:last-child {
- margin-bottom: 0;
- }
- .info-label {
- color: #7ec8ff;
- white-space: nowrap;
- margin-right: 5px;
- }
- .info-value {
- color: #fff;
- flex: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- /* 位置行样式 */
- .location-row {
- display: flex;
- align-items: center;
- margin-top: 4px;
- padding-top: 4px;
- border-top: 1px solid rgba(30, 144, 255, 0.2);
- }
- .location-icon {
- color: #00bfff;
- font-size: 12px;
- margin-right: 6px;
- }
- /* 选中状态指示 */
- .selected-indicator {
- position: absolute;
- top: 10px;
- right: 10px;
- width: 36px;
- height: 36px;
- background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- color: #fff;
- font-size: 18px;
- z-index: 2;
- box-shadow: 0 2px 8px rgba(16, 185, 129, 0.4);
- }
- /* 加载更多样式 */
- .loading-more {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 10px;
- padding: 20px 0;
- color: #7ec8ff;
- font-size: 14px;
- }
- .loading-more-spinner {
- width: 20px;
- height: 20px;
- border: 2px solid rgba(30, 144, 255, 0.3);
- border-top-color: #00bfff;
- border-radius: 50%;
- animation: spin 1s linear infinite;
- }
- /* 没有更多数据 */
- .no-more-data {
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 20px 0;
- color: #5a8cba;
- font-size: 14px;
- }
- /* 分页信息栏 */
- .pagination-info-bar {
- background: rgba(9, 61, 140, 0.3);
- border: 1px solid #049FD8;
- border-radius: 8px;
- padding: 10px 20px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- flex-shrink: 0;
- margin-top: 10px;
- color: #7ec8ff;
- font-size: 14px;
- }
- .pagination-controls {
- display: flex;
- align-items: center;
- gap: 10px;
- }
- .page-btn {
- width: 32px;
- height: 32px;
- background: rgba(13, 58, 106, 0.8);
- border: 1px solid #2a7fff;
- border-radius: 6px;
- color: #7ec8ff;
- font-size: 14px;
- cursor: pointer;
- transition: all 0.3s;
- }
- .page-btn:hover:not(:disabled) {
- background: rgba(30, 144, 255, 0.3);
- border-color: #00bfff;
- }
- .page-btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- }
- .page-current {
- color: #00bfff;
- font-weight: bold;
- }
- /* ========== 底部操作按钮 ========== */
- .bottom-actions {
- width: 100%;
- padding: 15px 30px;
- box-sizing: border-box;
- background: rgba(4, 28, 61, 0.95);
- z-index: 20;
- flex-shrink: 0;
- }
- .submit-btn {
- width: 100%;
- padding: 18px 0;
- background: #4a99e2;
- border: none;
- border-radius: 12px;
- font-size: 24px;
- font-weight: bold;
- color: #fff;
- cursor: pointer;
- transition: all 0.3s;
- letter-spacing: 8px;
- text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
- }
- .submit-btn:hover:not(:disabled) {
- box-shadow: 0 0 30px rgba(74, 153, 226, 0.6);
- transform: translateY(-2px);
- }
- .submit-btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- }
- /* ========== 科技感弹窗样式 ========== */
- .tech-modal-overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(4, 28, 61, 0.9);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 2000;
- }
- .tech-modal {
- background: linear-gradient(135deg, #0a3d6d 0%, #041c3d 100%);
- border: 1px solid #049FD8;
- border-radius: 16px;
- width: 90%;
- max-width: 500px;
- position: relative;
- box-shadow: 0 0 40px rgba(4, 159, 216, 0.3);
- }
- .modal-close-btn {
- position: absolute;
- top: 15px;
- right: 15px;
- width: 36px;
- height: 36px;
- background: rgba(255, 255, 255, 0.1);
- border: none;
- border-radius: 50%;
- color: #7ec8ff;
- font-size: 18px;
- cursor: pointer;
- transition: all 0.3s;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .modal-close-btn:hover {
- background: rgba(255, 255, 255, 0.2);
- color: #fff;
- }
- .modal-content-row {
- padding: 30px;
- display: flex;
- align-items: center;
- gap: 30px;
- }
- .modal-text {
- flex: 1;
- }
- .modal-text h3 {
- font-size: 22px;
- font-weight: bold;
- color: #fff;
- margin: 0 0 12px 0;
- }
- .modal-text p {
- font-size: 15px;
- color: #7ec8ff;
- margin: 0;
- line-height: 1.6;
- }
- .modal-text .highlight {
- color: #00bfff;
- font-weight: bold;
- }
- .modal-icon {
- flex-shrink: 0;
- }
- .icon-box {
- width: 80px;
- height: 80px;
- background: linear-gradient(135deg, #1e90ff 0%, #00bfff 100%);
- border-radius: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 36px;
- color: #fff;
- position: relative;
- transform: perspective(100px) rotateY(-10deg);
- box-shadow: 0 10px 30px rgba(0, 191, 255, 0.3);
- }
- .icon-box .check-icon {
- position: absolute;
- bottom: -5px;
- right: -5px;
- font-size: 24px;
- color: #10b981;
- background: #041c3d;
- border-radius: 50%;
- padding: 2px;
- }
- .modal-footer {
- padding: 20px 30px 30px;
- display: flex;
- gap: 15px;
- }
- .modal-btn {
- flex: 1;
- padding: 14px 0;
- border-radius: 8px;
- font-size: 16px;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.3s;
- }
- .modal-btn.cancel {
- background: transparent;
- border: 1px solid #2a7fff;
- color: #7ec8ff;
- }
- .modal-btn.cancel:hover {
- background: rgba(42, 127, 255, 0.2);
- }
- .modal-btn.confirm {
- background: linear-gradient(90deg, #1e90ff 0%, #00bfff 100%);
- border: none;
- color: #fff;
- }
- .modal-btn.confirm:hover {
- box-shadow: 0 0 20px rgba(30, 144, 255, 0.5);
- }
- /* ========== 响应式 - 横屏 ========== */
- @media screen and (orientation: landscape) {
- .header-section {
- padding: 10px 20px;
- }
- .page-title {
- font-size: 22px;
- }
- .logout-btn {
- padding: 6px 14px;
- font-size: 13px;
- left: 20px;
- }
- .main-content {
- padding: 0 20px;
- }
- .filter-section {
- padding: 12px 15px;
- margin-bottom: 10px;
- }
- .filter-label {
- font-size: 13px;
- }
- .filter-input.dark-input {
- width: 150px;
- padding: 6px 10px;
- font-size: 12px;
- }
- .filter-select {
- width: 150px;
- }
- .search-btn,
- .reset-btn {
- padding: 6px 20px;
- font-size: 13px;
- }
- .card-grid {
- grid-template-columns: repeat(6, 1fr);
- gap: 15px;
- }
- .card-index {
- width: 24px;
- height: 24px;
- font-size: 11px;
- top: 6px;
- left: 6px;
- }
- .card-image {
- margin: 10px;
- margin-bottom: 8px;
- width: calc(100% - 20px);
- }
- .image-placeholder {
- font-size: 36px;
- }
- .card-info {
- padding: 6px 10px 8px;
- }
- .info-row {
- font-size: 11px;
- margin-bottom: 3px;
- }
- .selected-indicator {
- width: 24px;
- height: 24px;
- font-size: 12px;
- top: 6px;
- right: 6px;
- }
- .bottom-actions {
- padding: 10px 20px;
- }
- .submit-btn {
- padding: 12px 0;
- font-size: 18px;
- letter-spacing: 6px;
- border-radius: 8px;
- }
- }
- </style>
- <style>
- /* 全局样式:确保 v-select 下拉菜单在模态框之上 */
- .vs__dropdown-menu {
- z-index: 99999 !important;
- }
- .v-select .vs__dropdown-menu {
- z-index: 99999 !important;
- }
- /* 确保附加到 body 的下拉菜单在最上层 */
- body > .v-select-menu {
- z-index: 99999 !important;
- }
- body > .vs__dropdown-menu {
- z-index: 99999 !important;
- }
- /* vue-select 的下拉菜单容器 */
- .vs--open .vs__dropdown-menu {
- z-index: 99999 !important;
- }
- </style>
|