addOrderStatusLogDrawer.vue 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. <template>
  2. <!-- 订单状态日志抽屉 -->
  3. <el-drawer v-model="drawerVisible" title="变更物流状态" size="38%" :destroy-on-close="true" :close-on-click-modal="true">
  4. <div class="status-log-container">
  5. <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
  6. <el-form-item label="物流单号" prop="logisticNos">
  7. <el-input v-model="form.logisticNos" disabled />
  8. </el-form-item>
  9. <el-form-item label="包裹单号" prop="packageNo">
  10. <el-input v-model="form.packageNo" disabled />
  11. </el-form-item>
  12. <el-form-item label="所在状态" prop="statusName">
  13. <el-select v-model="form.statusName" placeholder="请选择状态" style="width: 100%" @change="handleStatusChange">
  14. <el-option
  15. v-for="item in statusOptions"
  16. :key="item.value"
  17. :label="item.label"
  18. :value="item.value"
  19. :disabled="isStatusDisabled(item.value)"
  20. />
  21. </el-select>
  22. </el-form-item>
  23. <el-form-item label="发货人" prop="shipper">
  24. <el-input v-model="form.shipper" disabled />
  25. </el-form-item>
  26. <el-form-item label="操作时间" prop="operateTime">
  27. <el-date-picker v-model="form.operateTime" type="date" placeholder="请选择操作时间" style="width: 100%" />
  28. </el-form-item>
  29. <el-form-item label="快递员手机号" prop="courierPhone">
  30. <el-input v-model="form.courierPhone" disabled />
  31. </el-form-item>
  32. <el-form-item label="上传图片" prop="images" v-if="form.statusName === '已签收'">
  33. <div class="image-selector-wrapper">
  34. <!-- 修改点 1: 使用 !selectedImages.length 替代 length === 0,并确保样式一致 -->
  35. <div
  36. v-if="!selectedImages || selectedImages.length === 0"
  37. class="upload-box"
  38. @click="showFileSelector = true"
  39. style="
  40. width: 150px;
  41. height: 150px;
  42. border: 2px dashed #d9d9d9;
  43. border-radius: 4px;
  44. display: flex;
  45. align-items: center;
  46. justify-content: center;
  47. cursor: pointer;
  48. transition: all 0.3s;
  49. "
  50. >
  51. <div style="text-align: center; color: #8c939d">
  52. <el-icon :size="40" style="margin-bottom: 8px">
  53. <Plus />
  54. </el-icon>
  55. <div style="font-size: 14px">点击上传</div>
  56. </div>
  57. </div>
  58. <!-- 修改点 2: 列表渲染区域 -->
  59. <div v-else class="selected-images-list">
  60. <div
  61. v-for="(img, index) in selectedImages"
  62. :key="img.url || index"
  63. style="position: relative; display: inline-block; margin: 0 8px 8px 0"
  64. >
  65. <!-- 确保 img.url 存在才渲染,防止报错 -->
  66. <el-image
  67. v-if="img.url"
  68. :src="img.url"
  69. style="width: 150px; height: 150px"
  70. fit="cover"
  71. :preview-src-list="selectedImages.filter((i) => i.url).map((i) => i.url)"
  72. />
  73. <el-button
  74. type="danger"
  75. :icon="Delete"
  76. circle
  77. size="small"
  78. style="position: absolute; top: 5px; right: 5px; z-index: 10"
  79. @click="removeImage(index)"
  80. />
  81. </div>
  82. <!-- 修改点 3: 如果未满 3 张,显示“继续添加”按钮 -->
  83. <div
  84. v-if="selectedImages.length < 3"
  85. class="upload-box-more"
  86. @click="showFileSelector = true"
  87. style="
  88. width: 150px;
  89. height: 150px;
  90. border: 2px dashed #d9d9d9;
  91. border-radius: 4px;
  92. display: inline-flex;
  93. align-items: center;
  94. justify-content: center;
  95. cursor: pointer;
  96. transition: all 0.3s;
  97. vertical-align: top;
  98. background-color: #f5f7fa; /* 加个背景色区分 */
  99. "
  100. >
  101. <el-icon :size="30" color="#8c939d">
  102. <Plus />
  103. </el-icon>
  104. </div>
  105. </div>
  106. </div>
  107. </el-form-item>
  108. <!-- 文件选择器 -->
  109. <FileSelector v-model="showFileSelector" :multiple="true" :allowed-types="[1]" title="选择图片" @confirm="handleImagesSelected" />
  110. </el-form>
  111. </div>
  112. <template #footer>
  113. <div class="drawer-footer">
  114. <el-button @click="closeDrawer">取消</el-button>
  115. <el-button type="primary" @click="handleSubmit" :loading="submitLoading">保存</el-button>
  116. </div>
  117. </template>
  118. </el-drawer>
  119. </template>
  120. <script setup name="AddOrderStatusLogDrawer" lang="ts">
  121. import { ref, reactive } from 'vue';
  122. import { ElMessage } from 'element-plus';
  123. import { Plus, Delete } from '@element-plus/icons-vue';
  124. import { addOrderStatusLog } from '@/api/order/orderStatusLog';
  125. import FileSelector from '@/components/FileSelector/index.vue';
  126. const drawerVisible = ref(false);
  127. const submitLoading = ref(false);
  128. const formRef = ref<any>(null);
  129. const showFileSelector = ref(false);
  130. const selectedImages = ref<any[]>([]);
  131. // 表单数据
  132. const form = reactive({
  133. orderNo: '',
  134. orderId: undefined as string | number | undefined,
  135. customerNo: '',
  136. customerId: undefined as string | number | undefined,
  137. deliverMethod: '0',
  138. statusName: '',
  139. logisticNos: '',
  140. packageNo: '',
  141. shipper: '',
  142. operateTime: '',
  143. courierPhone: '',
  144. images: '',
  145. status: '0'
  146. });
  147. // 状态顺序定义
  148. const statusOrder = ['已下单', '已揽件', '运输中', '派件中', '已签收'];
  149. // 当前订单状态
  150. const currentStatus = ref('');
  151. // 状态选项
  152. const statusOptions = [
  153. { label: '已下单', value: '已下单' },
  154. { label: '已揽件', value: '已揽件' },
  155. { label: '运输中', value: '运输中' },
  156. { label: '派件中', value: '派件中' },
  157. { label: '已签收', value: '已签收' }
  158. ];
  159. const rules = {
  160. statusName: [{ required: true, message: '请选择状态名称', trigger: 'change' }]
  161. };
  162. // 打开抽屉
  163. const openDrawer = (orderData?: any, currentOrderStatus?: string) => {
  164. if (orderData) {
  165. form.orderNo = orderData.orderNo || '';
  166. form.orderId = orderData.orderId || '';
  167. form.shipper = orderData.deliverMan || '';
  168. form.courierPhone = orderData.phone || '';
  169. form.packageNo = orderData.deliverCode || '';
  170. form.logisticNos = orderData.deliverCode || '';
  171. form.customerNo = orderData.customerNo || '';
  172. form.customerId = orderData.customerId;
  173. form.operateTime = orderData.createTime || '';
  174. form.deliverMethod = orderData.deliverMethod || '0';
  175. form.statusName = currentOrderStatus || '';
  176. form.images = orderData.images || '';
  177. selectedImages.value = [];
  178. if (form.images) {
  179. const urls = form.images.split(',');
  180. selectedImages.value = urls.filter((u) => u && u.trim()).map((u) => ({ url: u.trim(), name: 'Image' }));
  181. }
  182. currentStatus.value = currentOrderStatus || '';
  183. }
  184. drawerVisible.value = true;
  185. };
  186. // 关闭抽屉
  187. const closeDrawer = () => {
  188. drawerVisible.value = false;
  189. resetForm();
  190. };
  191. // 判断是否禁用某个状态选项
  192. const isStatusDisabled = (statusValue: string) => {
  193. if (!currentStatus.value) return false;
  194. const currentIndex = statusOrder.indexOf(currentStatus.value);
  195. const targetIndex = statusOrder.indexOf(statusValue);
  196. // 如果目标状态在当前状态之前(索引更小),则禁用
  197. return targetIndex < currentIndex;
  198. };
  199. // 重置表单
  200. const resetForm = () => {
  201. form.statusName = '';
  202. form.logisticNos = '';
  203. form.packageNo = '';
  204. form.orderId = undefined;
  205. form.shipper = '';
  206. form.operateTime = '';
  207. form.courierPhone = '';
  208. form.images = '';
  209. selectedImages.value = [];
  210. showFileSelector.value = false;
  211. currentStatus.value = '';
  212. };
  213. // 处理选择的图片 (优化健壮性)
  214. const handleImagesSelected = (files: any[]) => {
  215. if (!files || files.length === 0) {
  216. showFileSelector.value = false;
  217. return;
  218. }
  219. const remainingSlots = 3 - selectedImages.value.length;
  220. if (remainingSlots <= 0) {
  221. ElMessage.warning('最多上传 3 张图片');
  222. showFileSelector.value = false;
  223. return;
  224. }
  225. const filesToAdd = files.slice(0, remainingSlots);
  226. // 映射为标准格式 { url, name }
  227. const newItems = filesToAdd
  228. .map((f) => ({
  229. url: f.fileUrl || f.url || f.path, // 兼容多种返回字段
  230. name: f.fileName || 'Image'
  231. }))
  232. .filter((item) => item.url); // 过滤掉没有 url 的无效项
  233. if (newItems.length > 0) {
  234. // 重新赋值数组以触发视图更新
  235. selectedImages.value = [...selectedImages.value, ...newItems];
  236. form.images = selectedImages.value.map((img) => img.url).join(',');
  237. }
  238. showFileSelector.value = false;
  239. };
  240. // 移除已选择的图片
  241. const removeImage = (index: number) => {
  242. selectedImages.value.splice(index, 1);
  243. form.images = selectedImages.value.map((img) => img.url).join(',');
  244. };
  245. // 状态变化处理
  246. const handleStatusChange = () => {
  247. // 当状态变化时,如果不是已签收,清空图片
  248. if (form.statusName !== '已签收') {
  249. form.images = '';
  250. selectedImages.value = [];
  251. }
  252. };
  253. const handleSubmit = async () => {
  254. await formRef.value?.validate();
  255. submitLoading.value = true;
  256. try {
  257. await addOrderStatusLog(form);
  258. ElMessage.success('保存成功');
  259. closeDrawer();
  260. } catch (error) {
  261. console.error('保存失败:', error);
  262. ElMessage.error('保存失败');
  263. } finally {
  264. submitLoading.value = false;
  265. }
  266. };
  267. // 暴露方法给父组件
  268. defineExpose({
  269. openDrawer
  270. });
  271. </script>
  272. <style scoped>
  273. .status-log-container {
  274. padding: 20px;
  275. }
  276. .drawer-footer {
  277. display: flex;
  278. justify-content: flex-end;
  279. gap: 12px;
  280. }
  281. </style>