OrderPicking.vue 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194
  1. <!-- 拣货管理 -->
  2. <template>
  3. <div class="page-container">
  4. <!-- 顶部信息栏 -->
  5. <PageHeader title="拣货管理" />
  6. <!-- 主内容区域 -->
  7. <main class="main-content">
  8. <!-- 页面标题 -->
  9. <!-- <div class="page-title">
  10. <h2>拣货管理</h2>
  11. </div> -->
  12. <!-- 筛选区域 -->
  13. <FilterPanel :default-collapsed="false" :enable-collapse="false" :active-count="getActiveFilterCount()">
  14. <div class="filter-form">
  15. <div class="filter-item">
  16. <label class="filter-label">名称:</label>
  17. <van-field
  18. v-model="filterForm.inventoryName" placeholder="输入名称" class="filter-input"
  19. @keyup.enter="getList"
  20. />
  21. </div>
  22. <div class="filter-item">
  23. <label class="filter-label">编号:</label>
  24. <van-field
  25. v-model="filterForm.inventoryNo" placeholder="输入编号" class="filter-input"
  26. @keyup.enter="getList"
  27. />
  28. </div>
  29. <div class="filter-item">
  30. <label class="filter-label">类型:</label>
  31. <v-select
  32. v-model="filterForm.inventoryType" :options="inventoryTypeList" :reduce="item => item.value"
  33. label="label" placeholder="选择类型" :clearable="true" :filterable="true" class="filter-select"
  34. @update:model-value="getList"
  35. >
  36. <template #no-options>
  37. <span>无该选项数据</span>
  38. </template>
  39. </v-select>
  40. </div>
  41. <div class="filter-item">
  42. <label class="filter-label">仓库:</label>
  43. <v-select
  44. v-model="filterForm.warehouseId" :options="warehouseList" :reduce="item => item.id" label="name"
  45. placeholder="选择仓库" :clearable="true" :filterable="true" class="filter-select"
  46. @update:model-value="getList"
  47. >
  48. <template #no-options>
  49. <span>无该选项数据</span>
  50. </template>
  51. </v-select>
  52. </div>
  53. <div class="filter-item">
  54. <label class="filter-label">货位:</label>
  55. <van-field
  56. v-model="filterForm.positionName" placeholder="输入货位名称" class="filter-input"
  57. @keyup.enter="getList"
  58. />
  59. </div>
  60. <div class="filter-item filter-buttons">
  61. <van-button type="primary" @click="getList">
  62. <i class="fas fa-search mr-1" /> 搜索
  63. </van-button>
  64. <van-button class="ml-2" @click="handleReset">
  65. <i class="fas fa-redo mr-1" /> 重置
  66. </van-button>
  67. </div>
  68. </div>
  69. </FilterPanel>
  70. <!-- 卡片容器 -->
  71. <div class="card-container">
  72. <!-- 全选控制栏 -->
  73. <div v-if="materialList.length > 0" class="select-all-bar">
  74. <van-checkbox :model-value="isAllSelected" @update:model-value="toggleSelectAll">
  75. <span class="select-all-text">全选当前页(已选 {{ selectedIds.length }} 项)</span>
  76. </van-checkbox>
  77. </div>
  78. <!-- 卡片列表区域 -->
  79. <div class="card-list-wrapper">
  80. <!-- 空状态 -->
  81. <van-empty v-if="materialList.length === 0" description="暂无数据" />
  82. <!-- 卡片列表 -->
  83. <div v-else class="card-list">
  84. <div
  85. v-for="(item, index) in materialList" :key="item.id || index"
  86. :class="{ 'selected-card': selectedIds.includes(item.id) }" class="inventory-card"
  87. @click="toggleSelect(item.id)"
  88. >
  89. <div class="card-header">
  90. <div class="card-header-left">
  91. <van-checkbox
  92. :model-value="selectedIds.includes(item.id)" @click.stop
  93. @update:model-value="checked => handleCheckboxChange(checked, item.id)"
  94. />
  95. <div class="custom-avatar ml-3">
  96. <i :class="getInventoryIcon(item.inventoryType)" />
  97. </div>
  98. <div class="ml-4 card-title-info">
  99. <div class="card-name">{{ item.inventoryName }}</div>
  100. <div class="card-subtitle">编号: {{ item.inventoryNo }}</div>
  101. </div>
  102. </div>
  103. <div class="card-header-right">
  104. <div class="card-location">
  105. <i class="fas fa-map-marker-alt mr-2" />
  106. <span>{{ item.positionName || '-' }}</span>
  107. <span class="mx-2">/</span>
  108. <span>{{ item.warehouseName || '-' }}</span>
  109. </div>
  110. </div>
  111. </div>
  112. <!-- 卡片展开内容:配送方式选择(只有选中时显示) -->
  113. <div v-if="selectedIds.includes(item.id)" class="card-delivery-section" @click.stop>
  114. <div class="delivery-options">
  115. <!-- 配送方式选择 -->
  116. <div class="delivery-option-item">
  117. <label class="option-label">配送方式:</label>
  118. <div class="delivery-method-buttons">
  119. <button
  120. :class="['delivery-btn', 'agv-btn', { 'active': item.deliveryMethod === 'AGV_Delivery', 'disabled': item.deliveryType === '人工配送' }]"
  121. :disabled="item.deliveryType === '人工配送'" @click="changeDeliveryMethod(item, 'AGV_Delivery')"
  122. >
  123. <i class="fas fa-robot" /> AGV 配送
  124. </button>
  125. <button
  126. :class="['delivery-btn', 'manual-btn', { 'active': item.deliveryMethod === 'Manual_Delivery', 'disabled': item.deliveryType === '强制AGV配送' }]"
  127. :disabled="item.deliveryType === '强制AGV配送'"
  128. @click="changeDeliveryMethod(item, 'Manual_Delivery')"
  129. >
  130. <i class="fas fa-user" /> 人工配送
  131. </button>
  132. </div>
  133. <span
  134. v-if="item.deliveryType" class="delivery-type-badge"
  135. :class="getDeliveryTypeBadgeClass(item.deliveryType)"
  136. >
  137. {{ item.deliveryType }}
  138. </span>
  139. </div>
  140. <!-- 配送位置选择(仅 AGV 配送时显示) -->
  141. <div v-if="item.deliveryMethod === 'AGV_Delivery'" class="delivery-location-item">
  142. <label class="option-label">配送位置:</label>
  143. <v-select
  144. v-model="item.selectedLocation" :options="locator" placeholder="请选择" :clearable="false"
  145. class="location-select"
  146. >
  147. <template #no-options>
  148. <span>无该选项数据</span>
  149. </template>
  150. </v-select>
  151. </div>
  152. </div>
  153. </div>
  154. </div>
  155. </div>
  156. </div>
  157. <!-- 分页区域(固定底部) -->
  158. <div v-if="materialList.length > 0" class="pagination-wrapper">
  159. <span class="text-gray-600">共 {{ materialList.length }} 条数据</span>
  160. </div>
  161. </div>
  162. </main>
  163. <!-- 底部完成按钮 -->
  164. <div class="bottom-actions">
  165. <van-button
  166. type="primary" size="large" :disabled="selectedIds.length === 0" class="complete-btn"
  167. @click="handleComplete"
  168. >
  169. <i class="fas fa-check-circle mr-2" />
  170. 拣货 ({{ selectedIds.length }})
  171. </van-button>
  172. </div>
  173. <!-- 拣货完成确认弹窗 -->
  174. <van-dialog
  175. v-model:show="completeModalVisible" title="拣货完成" show-cancel-button class-name="large-dialog"
  176. confirm-button-text="确认" cancel-button-text="取消" @confirm="handleCompleteConfirm" @cancel="handleCompleteCancel"
  177. >
  178. <div class="large-dialog-content">
  179. <p>领料申请已完成,配送任务已创建!请确认是否返回主页?</p>
  180. <!-- <p class="dialog-subtitle">如果是请点击【确认】按钮</p> -->
  181. </div>
  182. </van-dialog>
  183. </div>
  184. <Loading v-if="loading" />
  185. </template>
  186. <script setup>
  187. import { useRouter } from 'vue-router';
  188. import { ref, reactive, computed, onMounted } from 'vue';
  189. import { showNotify } from 'vant';
  190. import vSelect from 'vue-select';
  191. import 'vue-select/dist/vue-select.css';
  192. import PageHeader from '../common/PageHeader.vue';
  193. import FilterPanel from '../common/FilterPanel.vue';
  194. import { list, createStockOut } from '../api/stockOut.js';
  195. import { getWarehouseList, queryIdleLocator } from '../api/stock.js';
  196. const router = useRouter();
  197. const warehouseList = ref([]);
  198. // 筛选表单
  199. const filterForm = reactive({
  200. inventoryName: '',
  201. inventoryNo: '',
  202. inventoryType: undefined,
  203. warehouseId: undefined,
  204. positionName: '',
  205. });
  206. const inventoryTypeList = ref([
  207. { value: 'Clamp', label: '工装' },
  208. { value: 'Instrument', label: '设备' },
  209. { value: 'FinishProduct', label: '成品' },
  210. ]);
  211. // 表格加载状态
  212. const loading = ref(false);
  213. // 完成弹窗控制
  214. const completeModalVisible = ref(false);
  215. // 物料列表数据
  216. const materialList = ref([]);
  217. // 分页配置
  218. const pagination = reactive({
  219. start: 1,
  220. lang: 10,
  221. total: 0,
  222. });
  223. // 所选集合
  224. const selectedIds = ref([]);
  225. const selectedRows = ref([]);
  226. // 计算是否全选
  227. const isAllSelected = computed(() => {
  228. if (materialList.value.length === 0) return false;
  229. return materialList.value.every(item => selectedIds.value.includes(item.id));
  230. });
  231. // 可配送位置
  232. const locator = ref([]);
  233. // 计算激活的筛选条件数量
  234. const getActiveFilterCount = () => {
  235. let count = 0;
  236. if (filterForm.inventoryName) count++;
  237. if (filterForm.inventoryNo) count++;
  238. if (filterForm.inventoryType) count++;
  239. if (filterForm.warehouseId) count++;
  240. if (filterForm.positionName) count++;
  241. return count;
  242. };
  243. // 根据 deliveryType 初始化配送方式
  244. const initDeliveryMethod = item => {
  245. if (item.deliveryType === '人工配送') {
  246. // 人工配送:只能人工,不可切换
  247. item.deliveryMethod = 'Manual_Delivery';
  248. item.selectedLocation = '';
  249. } else if (item.deliveryType === '强制AGV配送') {
  250. // 强制AGV配送:只能AGV,不可切换为人工
  251. item.deliveryMethod = 'AGV_Delivery';
  252. if (!item.selectedLocation) {
  253. item.selectedLocation = '';
  254. }
  255. } else if (item.deliveryType === '可选AGV配送') {
  256. // 可选AGV配送:默认人工,可切换为AGV
  257. item.deliveryMethod = item.deliveryMethod || 'Manual_Delivery';
  258. item.selectedLocation = item.selectedLocation || '';
  259. }
  260. };
  261. // 切换选择
  262. const toggleSelect = id => {
  263. const index = selectedIds.value.indexOf(id);
  264. if (index > -1) {
  265. selectedIds.value.splice(index, 1);
  266. // 同时移除 selectedRows 中对应的项
  267. selectedRows.value = selectedRows.value.filter(item => item.id !== id);
  268. } else {
  269. selectedIds.value.push(id);
  270. // 添加到 selectedRows 并初始化配送字段
  271. const item = materialList.value.find(item => item.id === id);
  272. if (item) {
  273. // 初始化配送字段
  274. initDeliveryMethod(item);
  275. selectedRows.value.push(item);
  276. }
  277. }
  278. };
  279. // 处理复选框变化
  280. const handleCheckboxChange = (checked, id) => {
  281. if (checked) {
  282. if (!selectedIds.value.includes(id)) {
  283. selectedIds.value.push(id);
  284. const item = materialList.value.find(item => item.id === id);
  285. if (item) {
  286. // 初始化配送字段
  287. initDeliveryMethod(item);
  288. selectedRows.value.push(item);
  289. }
  290. }
  291. } else {
  292. const index = selectedIds.value.indexOf(id);
  293. if (index > -1) {
  294. selectedIds.value.splice(index, 1);
  295. selectedRows.value = selectedRows.value.filter(item => item.id !== id);
  296. }
  297. }
  298. };
  299. // 全选/取消全选
  300. const toggleSelectAll = checked => {
  301. if (checked) {
  302. // 全选当前页所有项(累加模式)
  303. materialList.value.forEach(item => {
  304. if (!selectedIds.value.includes(item.id)) {
  305. selectedIds.value.push(item.id);
  306. initDeliveryMethod(item);
  307. selectedRows.value.push(item);
  308. }
  309. });
  310. } else {
  311. // 取消全选当前页
  312. const currentPageIds = materialList.value.map(item => item.id);
  313. selectedIds.value = selectedIds.value.filter(id => !currentPageIds.includes(id));
  314. selectedRows.value = selectedRows.value.filter(row => !currentPageIds.includes(row.id));
  315. }
  316. };
  317. // 获取设备类型图标
  318. const getInventoryIcon = type => {
  319. const iconMap = {
  320. '工装': 'fas fa-cube',
  321. '设备': 'fas fa-cogs',
  322. '成品': 'fas fa-box',
  323. };
  324. return iconMap[type] || 'fas fa-cube';
  325. };
  326. // 获取配送类型徽章样式
  327. const getDeliveryTypeBadgeClass = deliveryType => {
  328. const classMap = {
  329. '人工配送': 'badge-manual',
  330. '强制AGV配送': 'badge-agv-force',
  331. '可选AGV配送': 'badge-agv-optional',
  332. };
  333. return classMap[deliveryType] || '';
  334. };
  335. // 重置筛选条件
  336. const handleReset = () => {
  337. filterForm.inventoryName = '';
  338. filterForm.inventoryNo = '';
  339. filterForm.inventoryType = undefined;
  340. filterForm.warehouseId = undefined;
  341. filterForm.positionName = '';
  342. getList();
  343. };
  344. // 加入领料申请
  345. const addToRequisition = record => {
  346. if (!selectedIds.value.includes(record.id)) {
  347. selectedIds.value.push(record.id);
  348. // 初始化配送字段
  349. initDeliveryMethod(record);
  350. selectedRows.value.push(record);
  351. }
  352. };
  353. // 领料申请,直接验证并提交
  354. const handleComplete = async () => {
  355. if (selectedIds.value.length === 0) {
  356. showNotify({ type: 'warning', message: '请至少选择一个物料' });
  357. return;
  358. }
  359. // 验证选择了 AGV 配送的物料是否都选择了配送位置
  360. const agvItems = selectedRows.value.filter(item => item.deliveryMethod === 'AGV_Delivery');
  361. const hasEmptyLocation = agvItems.some(item => !item.selectedLocation);
  362. if (hasEmptyLocation) {
  363. showNotify({ type: 'warning', message: '请为所有 AGV 配送的物料选择配送位置' });
  364. return;
  365. }
  366. const params = [];
  367. selectedRows.value.forEach(item => {
  368. params.push({
  369. stockOutPrepareLineId: item.id,
  370. deliveryMethod: item.deliveryMethod,
  371. positionEndNo: item.selectedLocation.value,
  372. });
  373. });
  374. console.log('提交参数:', params);
  375. await generateCFStockOut(params);
  376. };
  377. // 配送方式变更处理
  378. const changeDeliveryMethod = (item, method) => {
  379. item.deliveryMethod = method;
  380. // 当选择人工配送时,清空配送位置
  381. if (method === 'Manual_Delivery') {
  382. item.selectedLocation = '';
  383. }
  384. console.log('配送方式变更:', item.inventoryName, item.deliveryMethod);
  385. };
  386. // 获取物料列表
  387. const getList = async () => {
  388. loading.value = true;
  389. try {
  390. const params = {
  391. ...filterForm,
  392. userId: JSON.parse(localStorage.getItem('#LoginInfo')).userId,
  393. };
  394. const res = await list(params);
  395. loading.value = false;
  396. if (res.errorCode === 0) {
  397. if (res.datas && res.datas.length > 0) {
  398. const testData = res.datas;
  399. // ========== 测试数据模拟开始 ==========
  400. // const testData = res.datas.map((item, index) => {
  401. // const types = ['人工配送', '强制AGV配送', '可选AGV配送'];
  402. // return {
  403. // ...item,
  404. // // 循环分配不同的配送类型用于测试
  405. // deliveryType: types[index % 3] || item.deliveryType,
  406. // };
  407. // });
  408. // ========== 测试数据模拟结束 ==========
  409. // 保留已选中物料的配送方式设置
  410. materialList.value = testData.map(item => {
  411. // 查找该物料是否在已选中列表中
  412. const selectedItem = selectedRows.value.find(selected => selected.id === item.id);
  413. if (selectedItem) {
  414. // 如果已选中,保留其配送方式和位置设置,并更新 selectedRows 中的引用
  415. const updatedItem = {
  416. ...item,
  417. deliveryMethod: selectedItem.deliveryMethod,
  418. selectedLocation: selectedItem.selectedLocation,
  419. };
  420. // 更新 selectedRows 中的对应项,保持引用同步
  421. const index = selectedRows.value.findIndex(row => row.id === item.id);
  422. if (index !== -1) {
  423. selectedRows.value[index] = updatedItem;
  424. }
  425. return updatedItem;
  426. } else {
  427. // 未选中,初始化为空
  428. return {
  429. ...item,
  430. deliveryMethod: '',
  431. selectedLocation: '',
  432. };
  433. }
  434. });
  435. pagination.total = materialList.value.length;
  436. } else {
  437. materialList.value = [];
  438. }
  439. } else {
  440. showNotify({ type: 'danger', message: res.errorMessage });
  441. }
  442. } catch (error) {
  443. loading.value = false;
  444. console.error('获取物料列表API调用失败:', error);
  445. showNotify({ type: 'danger', message: '获取物料列表API调用失败' });
  446. return;
  447. }
  448. };
  449. // 生成CF出库单
  450. const generateCFStockOut = async params => {
  451. loading.value = true;
  452. try {
  453. const res = await createStockOut(params);
  454. if (res.errorCode === 0) {
  455. showNotify({ type: 'success', message: '领料完成' });
  456. selectedIds.value = [];
  457. selectedRows.value = [];
  458. // 显示完成确认弹窗
  459. completeModalVisible.value = true;
  460. } else {
  461. showNotify({ type: 'danger', message: res.errorMessage });
  462. }
  463. } catch (error) {
  464. console.error('生成CF出库单API调用失败:', error);
  465. showNotify({ type: 'danger', message: '生成CF出库单API调用失败' });
  466. } finally {
  467. loading.value = false;
  468. }
  469. };
  470. // 处理完成确认
  471. const handleCompleteConfirm = () => {
  472. completeModalVisible.value = false;
  473. router.push('/home');
  474. // showToast('领料申请已完成,配送任务已创建!');
  475. };
  476. // 处理完成取消
  477. const handleCompleteCancel = () => {
  478. completeModalVisible.value = false;
  479. // 留在当前页面,刷新列表
  480. getList();
  481. };
  482. // 获取仓库列表
  483. const getWarehouse = async () => {
  484. try {
  485. const res = await getWarehouseList();
  486. if (res.errorCode === 0) {
  487. if (res.datas && res.datas.length > 0) {
  488. warehouseList.value = res.datas;
  489. } else {
  490. warehouseList.value = [];
  491. }
  492. } else {
  493. showNotify({ type: 'danger', message: res.errorMessage });
  494. }
  495. } catch (error) {
  496. console.error('获取仓库列表API调用失败:', error);
  497. showNotify({ type: 'danger', message: '获取仓库列表API调用失败' });
  498. } finally {
  499. loading.value = false;
  500. }
  501. };
  502. // 获取空闲货位
  503. const getIdleLocator = async () => {
  504. try {
  505. const res = await queryIdleLocator();
  506. if (res.errorCode === 0) {
  507. if (res.datas && res.datas.length > 0) {
  508. locator.value = res.datas.map(item => {
  509. return {
  510. label: item.positionName,
  511. value: item.positionNo,
  512. };
  513. });
  514. } else {
  515. locator.value = [];
  516. }
  517. } else {
  518. showNotify({ type: 'warning', message: res.errorMessage });
  519. }
  520. } catch (error) {
  521. console.error('获取空闲货位API调用失败:', error);
  522. showNotify({ type: 'danger', message: '获取空闲货位API调用失败' });
  523. }
  524. };
  525. onMounted(() => {
  526. getWarehouse();
  527. getList();
  528. getIdleLocator();
  529. });
  530. </script>
  531. <style scoped>
  532. /* 页面容器 - 固定高度布局 */
  533. .page-container {
  534. display: flex;
  535. flex-direction: column;
  536. height: 100vh;
  537. background-color: #f9fafb;
  538. overflow: hidden;
  539. }
  540. /* 主内容区 - 可滚动 */
  541. .main-content {
  542. flex: 1;
  543. overflow-y: auto;
  544. padding: 1rem;
  545. display: flex;
  546. flex-direction: column;
  547. }
  548. /* 页面标题 */
  549. .page-title {
  550. margin-bottom: 1.5rem;
  551. }
  552. .page-title h2 {
  553. font-size: 1.5rem;
  554. font-weight: 700;
  555. color: #111827;
  556. margin: 0;
  557. }
  558. /* 卡片容器 */
  559. .card-container {
  560. flex: 1;
  561. background-color: white;
  562. border-radius: 0.5rem;
  563. box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
  564. display: flex;
  565. flex-direction: column;
  566. overflow: hidden;
  567. min-height: 0;
  568. }
  569. /* 卡片列表包装器(可滚动区域) */
  570. .card-list-wrapper {
  571. flex: 1;
  572. overflow-y: auto;
  573. padding: 1.5rem;
  574. min-height: 0;
  575. }
  576. /* 卡片列表(单列布局) */
  577. .card-list {
  578. display: flex;
  579. flex-direction: column;
  580. font-weight: 600;
  581. }
  582. .card-list > * {
  583. margin-bottom: 1rem;
  584. }
  585. .card-list > *:last-child {
  586. margin-bottom: 0;
  587. }
  588. /* 分页包装器(固定底部) */
  589. .pagination-wrapper {
  590. padding: 0.5rem 1.5rem;
  591. border-top: 1px solid #e5e7eb;
  592. background-color: #fafafa;
  593. display: flex;
  594. justify-content: flex-end;
  595. align-items: center;
  596. }
  597. /* 底部操作按钮 */
  598. .bottom-actions {
  599. position: sticky;
  600. bottom: 0;
  601. padding: 0 1rem 1rem 1rem;
  602. background-color: #f9fafb;
  603. display: flex;
  604. justify-content: flex-end;
  605. z-index: 10;
  606. }
  607. /* 筛选表单 */
  608. .filter-form {
  609. display: flex;
  610. flex-wrap: wrap;
  611. align-items: center;
  612. }
  613. .filter-form > * {
  614. margin-right: 1rem;
  615. margin-bottom: 0.4rem;
  616. }
  617. .filter-item {
  618. display: flex;
  619. align-items: center;
  620. }
  621. .filter-item > * {
  622. margin-right: 0.5rem;
  623. }
  624. .filter-item > *:last-child {
  625. margin-right: 0;
  626. }
  627. .filter-label {
  628. font-size: 14px;
  629. font-weight: 600;
  630. white-space: nowrap;
  631. color: #374151;
  632. }
  633. .filter-select {
  634. width: 200px;
  635. position: relative;
  636. z-index: 100;
  637. }
  638. .filter-input {
  639. width: 200px;
  640. }
  641. :deep(.filter-input .van-cell) {
  642. padding: 8px 12px !important;
  643. border: 1px solid #ccc !important;
  644. border-radius: 4px !important;
  645. background-color: white !important;
  646. min-height: 32px !important;
  647. box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05);
  648. }
  649. :deep(.filter-input .van-cell::after) {
  650. display: none !important;
  651. }
  652. :deep(.filter-input .van-field__body) {
  653. border: none !important;
  654. }
  655. :deep(.filter-input .van-field__control) {
  656. font-size: 14px;
  657. }
  658. /* Vue Select 样式 */
  659. :deep(.v-select) {
  660. font-size: 14px;
  661. }
  662. :deep(.v-select .vs__dropdown-toggle) {
  663. border: 1px solid #d1d5db;
  664. border-radius: 4px;
  665. padding: 4px 8px;
  666. min-height: 32px;
  667. background: white;
  668. }
  669. :deep(.v-select .vs__dropdown-menu) {
  670. z-index: 9999 !important;
  671. border: 1px solid #d1d5db;
  672. box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
  673. }
  674. :deep(.v-select .vs__selected) {
  675. margin: 2px;
  676. padding: 0 4px;
  677. }
  678. :deep(.v-select .vs__search) {
  679. padding: 0;
  680. margin: 0;
  681. }
  682. :deep(.v-select .vs__actions) {
  683. padding: 0 4px;
  684. }
  685. .filter-buttons {
  686. display: flex;
  687. }
  688. .filter-buttons > * {
  689. margin-right: 0.5rem;
  690. }
  691. .filter-buttons > *:last-child {
  692. margin-right: 0;
  693. }
  694. /* 库存卡片样式 */
  695. .inventory-card {
  696. cursor: pointer;
  697. transition: all 0.25s ease;
  698. border: 2px solid #e5e7eb;
  699. background-color: #ffffff;
  700. border-radius: 0.5rem;
  701. }
  702. .inventory-card:hover {
  703. border-color: #93c5fd;
  704. box-shadow: 0 4px 12px rgba(59, 130, 246, 0.1);
  705. }
  706. .inventory-card.selected-card {
  707. border-color: #3b82f6 !important;
  708. background-color: #eff6ff;
  709. box-shadow: 0 4px 16px rgba(59, 130, 246, 0.2);
  710. }
  711. /* 卡片标题区域 */
  712. .card-header {
  713. display: flex;
  714. align-items: center;
  715. justify-content: space-between;
  716. width: 100%;
  717. padding: 1rem 1.5rem;
  718. }
  719. .card-header > * {
  720. margin-right: 1.5rem;
  721. }
  722. .card-header > *:last-child {
  723. margin-right: 0;
  724. }
  725. .card-header-left {
  726. display: flex;
  727. align-items: center;
  728. flex: 1;
  729. min-width: 0;
  730. }
  731. .card-header-right {
  732. display: flex;
  733. align-items: center;
  734. }
  735. .card-header-right > * {
  736. margin-left: 1rem;
  737. }
  738. .card-header-right > *:first-child {
  739. margin-left: 0;
  740. }
  741. /* 卡片标题信息 */
  742. .card-title-info {
  743. display: flex;
  744. flex-direction: column;
  745. min-width: 0;
  746. }
  747. .card-title-info > * {
  748. margin-bottom: 0.25rem;
  749. }
  750. .card-title-info > *:last-child {
  751. margin-bottom: 0;
  752. }
  753. .card-name {
  754. font-size: 1.2rem;
  755. font-weight: 600;
  756. color: #111827;
  757. line-height: 1.5;
  758. white-space: nowrap;
  759. overflow: hidden;
  760. text-overflow: ellipsis;
  761. }
  762. .card-subtitle {
  763. font-size: 0.875rem;
  764. color: #6b7280;
  765. line-height: 1.4;
  766. }
  767. /* 卡片位置信息 */
  768. .card-location {
  769. display: flex;
  770. align-items: center;
  771. padding: 0.5rem 1rem;
  772. background-color: #f3f4f6;
  773. border-radius: 0.375rem;
  774. font-size: 0.875rem;
  775. color: #374151;
  776. white-space: nowrap;
  777. }
  778. .card-location i {
  779. color: #3b82f6;
  780. }
  781. /* 自定义头像 */
  782. .custom-avatar {
  783. width: 42px;
  784. height: 42px;
  785. border-radius: 50%;
  786. background-color: #3b82f6;
  787. display: flex;
  788. align-items: center;
  789. justify-content: center;
  790. color: white;
  791. font-size: 20px;
  792. flex-shrink: 0;
  793. }
  794. /* 卡片配送方式选择区域 */
  795. .card-delivery-section {
  796. padding: 1rem 1.5rem;
  797. background-color: #f8fafc;
  798. border-top: 1px solid #e5e7eb;
  799. }
  800. .delivery-options {
  801. display: flex;
  802. flex-direction: row;
  803. align-items: center;
  804. flex-wrap: wrap;
  805. }
  806. .delivery-options > * {
  807. margin-right: 2rem;
  808. margin-bottom: 1rem;
  809. }
  810. .delivery-option-item {
  811. display: flex;
  812. align-items: center;
  813. }
  814. .delivery-option-item > * {
  815. margin-right: 0.75rem;
  816. }
  817. .delivery-option-item > *:last-child {
  818. margin-right: 0;
  819. }
  820. .delivery-location-item {
  821. display: flex;
  822. align-items: center;
  823. }
  824. .delivery-location-item > * {
  825. margin-right: 0.75rem;
  826. }
  827. .delivery-location-item > *:last-child {
  828. margin-right: 0;
  829. }
  830. .option-label {
  831. font-size: 0.875rem;
  832. font-weight: 600;
  833. color: #374151;
  834. min-width: 80px;
  835. margin: 0;
  836. }
  837. /* 配送方式按钮容器 */
  838. .delivery-method-buttons {
  839. display: flex;
  840. border-radius: 4px;
  841. overflow: hidden;
  842. }
  843. /* 配送方式按钮样式 */
  844. .delivery-btn {
  845. display: inline-flex;
  846. align-items: center;
  847. justify-content: center;
  848. padding: 6px 16px;
  849. font-size: 14px;
  850. font-weight: 500;
  851. border: 1px solid #d1d5db;
  852. background-color: white;
  853. color: #374151;
  854. cursor: pointer;
  855. transition: all 0.2s;
  856. height: 32px;
  857. min-width: 110px;
  858. }
  859. .delivery-btn > * {
  860. margin-right: 6px;
  861. }
  862. .delivery-btn > *:last-child {
  863. margin-right: 0;
  864. }
  865. .delivery-btn:first-child {
  866. border-radius: 4px 0 0 4px;
  867. border-right: none;
  868. }
  869. .delivery-btn:last-child {
  870. border-radius: 0 4px 4px 0;
  871. }
  872. .delivery-btn i {
  873. font-size: 14px;
  874. }
  875. /* AGV配送按钮 - 蓝色 */
  876. .delivery-btn.agv-btn.active {
  877. background-color: #3b82f6;
  878. color: white;
  879. border-color: #3b82f6;
  880. }
  881. .delivery-btn.agv-btn:hover:not(.disabled):not(.active) {
  882. background-color: #eff6ff;
  883. border-color: #3b82f6;
  884. color: #3b82f6;
  885. }
  886. /* 人工配送按钮 - 蓝色 */
  887. .delivery-btn.manual-btn.active {
  888. background-color: #3b82f6;
  889. color: white;
  890. border-color: #3b82f6;
  891. }
  892. .delivery-btn.manual-btn:hover:not(.disabled):not(.active) {
  893. background-color: #f3f4f6;
  894. border-color: #9ca3af;
  895. }
  896. /* 禁用状态 */
  897. .delivery-btn.disabled {
  898. opacity: 0.5;
  899. cursor: not-allowed;
  900. }
  901. .delivery-type-badge {
  902. margin-left: 0.75rem;
  903. padding: 0.25rem 0.625rem;
  904. border-radius: 0.375rem;
  905. font-size: 0.8125rem;
  906. font-weight: 500;
  907. white-space: nowrap;
  908. }
  909. /* 人工配送徽章 - 灰色 */
  910. .delivery-type-badge.badge-manual {
  911. background-color: #eff6ff;
  912. color: #4b5563;
  913. }
  914. /* 强制AGV配送徽章 - 蓝色 */
  915. .delivery-type-badge.badge-agv-force {
  916. background-color: #82b0ec;
  917. color: #1e40af;
  918. }
  919. /* 可选AGV配送徽章 - 绿色 */
  920. .delivery-type-badge.badge-agv-optional {
  921. background-color: #adf8d1;
  922. color: #065f46;
  923. }
  924. /* 配送位置选择器样式 */
  925. .location-select {
  926. width: 200px;
  927. }
  928. /* 按钮样式 */
  929. :deep(.van-button--primary) {
  930. background-color: #3b82f6;
  931. border-color: #3b82f6;
  932. }
  933. :deep(.van-button--primary:active) {
  934. background-color: #2563eb;
  935. border-color: #2563eb;
  936. }
  937. .complete-btn {
  938. background-color: #10b981 !important;
  939. border-color: #10b981 !important;
  940. width: auto;
  941. padding: 0 24px;
  942. }
  943. :deep(.complete-btn.van-button--primary) {
  944. background-color: #10b981 !important;
  945. border-color: #10b981 !important;
  946. }
  947. :deep(.complete-btn.van-button--primary:active) {
  948. background-color: #059669 !important;
  949. border-color: #059669 !important;
  950. }
  951. :deep(.van-button--disabled) {
  952. opacity: 0.5;
  953. cursor: not-allowed;
  954. }
  955. :deep(.van-cell) {
  956. border: 1px solid #ddd;
  957. border-radius: 6px;
  958. padding: 6px 8px !important;
  959. }
  960. :deep(.v-select .vs__dropdown-toggle) {
  961. padding: 6px 8px !important;
  962. }
  963. /* 全选控制栏 */
  964. .select-all-bar {
  965. background-color: white;
  966. padding: 1rem 1.5rem;
  967. border-bottom: 1px solid #e5e7eb;
  968. display: flex;
  969. align-items: center;
  970. }
  971. .select-all-text {
  972. font-size: 0.95rem;
  973. color: #374151;
  974. font-weight: 500;
  975. }
  976. /* 大弹窗样式,适合触摸屏 */
  977. :deep(.large-dialog) {
  978. width: 85vw !important;
  979. max-width: 600px !important;
  980. border-radius: 16px !important;
  981. }
  982. :deep(.large-dialog .van-dialog__header) {
  983. padding: 2rem 1.5rem 1rem !important;
  984. font-size: 1.5rem !important;
  985. font-weight: 700 !important;
  986. color: #111827 !important;
  987. }
  988. .large-dialog-content {
  989. padding: 1.5rem 2rem 2.5rem !important;
  990. }
  991. .large-dialog-content p {
  992. margin: 0 0 1rem 0;
  993. font-size: 1.25rem !important;
  994. line-height: 1.8 !important;
  995. color: #374151 !important;
  996. text-align: center;
  997. }
  998. .large-dialog-content p:last-child {
  999. margin-bottom: 0;
  1000. }
  1001. .large-dialog-content .dialog-subtitle {
  1002. font-size: 1.125rem !important;
  1003. color: #6b7280 !important;
  1004. }
  1005. :deep(.large-dialog .van-dialog__footer) {
  1006. padding: 1rem 1.5rem 1.5rem !important;
  1007. display: flex;
  1008. }
  1009. :deep(.large-dialog .van-dialog__footer > *) {
  1010. margin-right: 1rem;
  1011. }
  1012. :deep(.large-dialog .van-dialog__footer > *:last-child) {
  1013. margin-right: 0;
  1014. }
  1015. :deep(.large-dialog .van-dialog__cancel),
  1016. :deep(.large-dialog .van-dialog__confirm) {
  1017. flex: 1;
  1018. height: 56px !important;
  1019. font-size: 1.125rem !important;
  1020. font-weight: 600 !important;
  1021. border-radius: 8px !important;
  1022. border: none !important;
  1023. }
  1024. :deep(.large-dialog .van-dialog__cancel) {
  1025. background-color: #f3f4f6 !important;
  1026. color: #374151 !important;
  1027. }
  1028. :deep(.large-dialog .van-dialog__cancel:active) {
  1029. background-color: #e5e7eb !important;
  1030. }
  1031. :deep(.large-dialog .van-dialog__confirm) {
  1032. background-color: #10b981 !important;
  1033. color: white !important;
  1034. }
  1035. :deep(.large-dialog .van-dialog__confirm:active) {
  1036. background-color: #059669 !important;
  1037. }
  1038. /* 大弹窗蒙层 */
  1039. :deep(.van-overlay) {
  1040. background-color: rgba(0, 0, 0, 0.6) !important;
  1041. }
  1042. </style>