applyAfter.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
  1. <template>
  2. <div class="apply-after-container">
  3. <div class="page-header">
  4. <div class="page-inner header-inner">
  5. <div class="header-left">
  6. <div class="header-icon">
  7. <el-icon>
  8. <Clock />
  9. </el-icon>
  10. </div>
  11. <div class="header-title">申请售后</div>
  12. </div>
  13. <div class="header-center">
  14. <div class="header-item">
  15. <span class="label">订单编号</span>
  16. <span class="value">{{ orderInfo.orderNo }}</span>
  17. </div>
  18. <div class="header-item">
  19. <span class="label">下单时间</span>
  20. <span class="value">{{ orderInfo.orderTime }}</span>
  21. </div>
  22. </div>
  23. <!-- <div class="header-right">
  24. <el-button @click="handleViewEvaluation">查看评价</el-button>
  25. <el-button @click="handleBuyAgain">再次购买</el-button>
  26. </div> -->
  27. </div>
  28. </div>
  29. <div class="content">
  30. <div class="page-inner">
  31. <div class="panel">
  32. <div class="panel-title">选择服务类型:</div>
  33. <div class="service-type">
  34. <el-radio-group v-model="form.serviceType" size="large">
  35. <el-radio-button label="1">退换货</el-radio-button>
  36. <el-radio-button label="3">维修</el-radio-button>
  37. </el-radio-group>
  38. <div class="service-tip">如商品出现质量问题,退货价格按照销售价格退货!</div>
  39. </div>
  40. <div class="section-title">提交数量</div>
  41. <div class="summary-bar" v-if="topBtnFlag">
  42. <div class="summary-left">
  43. <span>商品总数:{{ summary.totalKinds }}个</span>
  44. <span>本单提交商品总数量:{{ summary.totalQty }}个</span>
  45. <span>退货总金额:¥ {{ summary.totalAmount }}</span>
  46. </div>
  47. <el-icon class="summary-close" @click="topBtnFlag = false">
  48. <Close />
  49. </el-icon>
  50. </div>
  51. <div class="product-table">
  52. <div class="table-header">
  53. <div class="col col-product">商品信息</div>
  54. <div class="col col-price">单价</div>
  55. <div class="col col-sold">销售数量</div>
  56. <div class="col col-available">未售后数量</div>
  57. <div class="col col-qty">退货数量</div>
  58. <div class="col col-amount">退货金额</div>
  59. <!-- <div class="col col-op">操作</div> -->
  60. </div>
  61. <div v-if="selectedProducts.length === 0" class="table-empty">
  62. <el-empty description="暂无商品" />
  63. </div>
  64. <div v-for="item in selectedProducts" :key="item.productId" class="table-row">
  65. <div class="col col-product">
  66. <div class="product-info">
  67. <div class="product-image">
  68. <el-image :src="item.productImage" fit="cover">
  69. <template #error>
  70. <div class="image-placeholder"></div>
  71. </template>
  72. </el-image>
  73. </div>
  74. <div class="product-meta">
  75. <div class="name">{{ item.name }}</div>
  76. <div class="spec">{{ item.spec }}</div>
  77. </div>
  78. </div>
  79. </div>
  80. <div class="col col-price">¥{{ formatMoney(item.unitPrice) }}</div>
  81. <div class="col col-sold">{{ item.soldQty }}</div>
  82. <div class="col col-available">{{ item.availableQty }}</div>
  83. <div class="col col-qty">
  84. <el-input-number
  85. v-model="item.returnQuantity"
  86. :min="item.soldQty"
  87. :max="item.availableQty"
  88. controls-position="right"
  89. :controls="false"
  90. disabled
  91. @change="handleQtyChange"
  92. />
  93. </div>
  94. <div class="col col-amount">¥{{ formatMoney(item.returnQuantity * item.unitPrice) }}</div>
  95. <!-- <div class="col col-op">
  96. <el-button type="danger" link @click="handleRemoveProduct(item.productId)">删除</el-button>
  97. </div> -->
  98. </div>
  99. </div>
  100. </div>
  101. <div class="panel">
  102. <div class="panel-title">售后服务单信息:</div>
  103. <el-form ref="formRef" :model="form" :rules="rules" label-width="80px" class="after-form">
  104. <el-form-item label="退货原因" prop="returnReasonId">
  105. <el-select v-model="form.returnReasonId" placeholder="请选择" style="width: 200px" @change="handleReturnReasonChange">
  106. <el-option v-for="r in reasonOptions" :key="r.id" :label="r.returnReasonName" :value="r.id" />
  107. </el-select>
  108. </el-form-item>
  109. <el-form-item label="问题描述" prop="problemDescription">
  110. <el-input v-model="form.problemDescription" type="textarea" :rows="5" placeholder="请输入问题描述" />
  111. </el-form-item>
  112. <!-- <el-form-item label="凭证图片">
  113. <el-upload v-model:file-list="form.voucherPhotoArray" list-type="picture-card" :action="action" :auto-upload="false" :limit="5">
  114. <el-icon><Plus /></el-icon>
  115. </el-upload>
  116. </el-form-item> -->
  117. <el-form-item label="凭证图片">
  118. <div v-if="form.voucherPhoto && form.voucherPhoto != ''" class="flex">
  119. <div class="upload-bos" v-for="(item, index) in form.voucherPhoto.split(',')" :key="index">
  120. <img class="upload-img" :src="item" />
  121. <div class="upload-box">
  122. <el-icon @click="previewImage(item)" color="#ffffff" size="20" class="mr-[20px] icons"><Search /></el-icon>
  123. <el-icon @click="onDel(index)" color="#ffffff" size="20" class="icons"><Delete /></el-icon>
  124. </div>
  125. </div>
  126. </div>
  127. <el-upload
  128. class="avatar-uploader"
  129. :action="action"
  130. :limit="5"
  131. :show-file-list="false"
  132. :on-success="handleAvatarSuccess"
  133. :before-upload="beforeAvatarUpload"
  134. >
  135. <el-icon class="avatar-uploader-icon">
  136. <Plus />
  137. </el-icon>
  138. </el-upload>
  139. </el-form-item>
  140. </el-form>
  141. </div>
  142. <div class="panel">
  143. <div class="panel-title">选择取件方式:</div>
  144. <el-radio-group v-model="form.returnMethod" class="pickup-type">
  145. <el-radio label="1">上门取件</el-radio>
  146. <el-radio label="2">快递配送</el-radio>
  147. </el-radio-group>
  148. <div class="pickup-info" v-if="form.returnMethod === '1'">
  149. <div class="pickup-row">
  150. <span class="k">顾客姓名:</span><span class="v">{{ form.chargebackName }}</span>
  151. </div>
  152. <div class="pickup-row">
  153. <span class="k">手机号:</span><span class="v">{{ form.chargebackPhone }}</span>
  154. </div>
  155. <div class="pickup-row">
  156. <span class="k">取件时间:</span>
  157. <el-date-picker v-model="form.chargebackPickupTime" type="datetime" placeholder="请选择" style="width: 240px" />
  158. </div>
  159. <div class="pickup-row">
  160. <span class="k">取件地址:</span><span class="v">{{ form.chargebackAddress }}</span>
  161. </div>
  162. <div class="pickup-row hint">提交服务单后,售后专员可能与您电话沟通,请保持手机畅通</div>
  163. <div class="pickup-row">
  164. <el-button plain @click="handleChangeAddress">切换地址</el-button>
  165. </div>
  166. </div>
  167. <div class="pickup-info" v-if="form.returnMethod === '2'">
  168. <div class="pickup-row"><span class="k">邮寄地址:</span><span class="v"></span></div>
  169. <div class="pickup-row">
  170. <span class="k">取件地址:</span><span class="v">{{ form.chargebackAddress }}</span>
  171. </div>
  172. <div class="pickup-row">
  173. <span class="k">收件人:</span><span class="v">{{ form.chargebackName }} {{ form.chargebackPhone }}</span>
  174. </div>
  175. <div class="pickup-info">
  176. <div class="pickup-row hint">提交服务单后,售后专员可能与您电话沟通,请保持手机畅通</div>
  177. </div>
  178. </div>
  179. </div>
  180. <div class="footer">
  181. <el-button type="danger" class="submit-btn" @click="handleSubmit">提交</el-button>
  182. </div>
  183. </div>
  184. </div>
  185. <el-dialog v-model="addressDialogVisible" title="切换地址" width="900px" class="address-dialog">
  186. <div v-loading="addressLoading" class="address-list">
  187. <div
  188. v-for="addr in addressList"
  189. :key="addr.id"
  190. :class="['address-item', { active: selectedAddressId === String(addr.id) }]"
  191. @click="handleSelectAddress(addr)"
  192. >
  193. <div class="address-left">
  194. <div class="line">收货人: {{ addr.consignee || addr.receiverName || addr.name || '-' }}</div>
  195. <div class="line">联系方式: {{ addr.phone || addr.mobile || '-' }}</div>
  196. <div class="line">收货地址: {{ formatAddress(addr) }}</div>
  197. </div>
  198. <div class="address-right">
  199. <span v-if="isDefaultAddress(addr)" class="default-tag">默认地址</span>
  200. </div>
  201. </div>
  202. </div>
  203. <template #footer>
  204. <div class="dialog-footer">
  205. <el-button type="primary" @click="handleConfirmAddress">确认</el-button>
  206. <el-button @click="addressDialogVisible = false">取消</el-button>
  207. </div>
  208. </template>
  209. </el-dialog>
  210. <!-- 图片放大 -->
  211. <el-image-viewer
  212. :url-list="previewImageList"
  213. v-if="imageViewer.show"
  214. @close="imageViewer.show = false"
  215. :initial-index="imageViewer.index"
  216. :zoom-rate="1"
  217. />
  218. </div>
  219. </template>
  220. <script setup lang="ts">
  221. import { computed, reactive, ref } from 'vue';
  222. import { ElMessage } from 'element-plus';
  223. import { Clock, Close, Plus } from '@element-plus/icons-vue';
  224. import { useRouter, useRoute } from 'vue-router';
  225. import { getOrderInfo, getOrderProductsWithAvailableQty, getReturnReason } from '@/api/pc/enterprise/order';
  226. import { getEnterpriseInfo, getAddressList } from '@/api/pc/enterprise';
  227. import { getOrderReturnInfo, addOrderReturn } from '@/api/pc/enterprise/orderReturn';
  228. import type { OrderReturn } from '@/api/pc/enterprise/orderReturnTypes';
  229. const action = import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
  230. import type { UploadProps } from 'element-plus';
  231. const topBtnFlag = ref(false);
  232. const router = useRouter();
  233. const route = useRoute();
  234. const loading = ref(false);
  235. const orderId = ref<any>(0);
  236. const addressDialogVisible = ref(false);
  237. const addressLoading = ref(false);
  238. const addressList = ref<any[]>([]);
  239. const selectedAddressId = ref<string>('');
  240. const selectedAddress = ref<any>(null);
  241. const orderInfo = reactive({
  242. orderId: 0,
  243. orderNo: '',
  244. orderTime: '',
  245. freight: '0.00',
  246. totalAmount: '0.00',
  247. address: '',
  248. receiverName: '',
  249. receiverPhone: '',
  250. deliveryTime: '',
  251. purchaseReason: '',
  252. costType: '',
  253. remark: ''
  254. });
  255. type ApplyProduct = {
  256. productId: number;
  257. productNo: string;
  258. productUnit: string;
  259. productImage: string;
  260. productName: string;
  261. orderProductId: number;
  262. name: string;
  263. spec: string;
  264. unitPrice: number;
  265. soldQty: number;
  266. totalAmount: number;
  267. reasonDetail: string;
  268. availableQty: number;
  269. returnQuantity: number;
  270. };
  271. const selectedProducts = ref<ApplyProduct[]>([]);
  272. const formRef = ref();
  273. const form = reactive({
  274. orderId: 0,
  275. orderNo: '',
  276. customerNo: '',
  277. orderAmount: 0,
  278. returnAmount: '',
  279. serviceType: '1',
  280. returnReasonId: '',
  281. returnReason: '',
  282. problemDescription: '',
  283. returnProductNum: 0,
  284. afterSaleAmount: '',
  285. voucherPhotoArray: [],
  286. voucherPhoto: '',
  287. returnMethod: '1',
  288. chargebackPickupTime: '' as any,
  289. chargebackName: '',
  290. chargebackPhone: '',
  291. chargebackAddress: '',
  292. orderReturnItemList: []
  293. });
  294. const rules = {
  295. returnReasonId: [{ required: true, message: '请选择退货原因', trigger: 'change' }],
  296. problemDescription: [{ required: true, message: '请输入问题描述', trigger: 'blur' }]
  297. };
  298. const reasonOptions = ref([]);
  299. const summary = computed(() => {
  300. const list = selectedProducts.value;
  301. const totalKinds = list.length;
  302. const totalQty = list.reduce((sum, p) => sum + (Number(p.returnQuantity) || 0), 0);
  303. const totalAmountNum = list.reduce((sum, p) => sum + (Number(p.returnQuantity) || 0) * (Number(p.unitPrice) || 0), 0);
  304. return {
  305. totalKinds,
  306. totalQty,
  307. totalAmount: formatMoney(totalAmountNum)
  308. };
  309. });
  310. const formatMoney = (val: number | string) => {
  311. const num = Number(val) || 0;
  312. return num.toFixed(2);
  313. };
  314. const handleQtyChange = () => {
  315. // computed 会自动更新
  316. };
  317. const handleRemoveProduct = (id: number) => {
  318. selectedProducts.value = selectedProducts.value.filter((p) => p.productId != id);
  319. };
  320. // 图片放大
  321. const imageViewer = reactive({
  322. show: false,
  323. index: 0
  324. });
  325. const previewImageList = ref<any>([]);
  326. const previewImage = (item: any) => {
  327. console.log(item, '8888');
  328. previewImageList.value = [item];
  329. imageViewer.index = 0;
  330. imageViewer.show = true;
  331. };
  332. // 图片删除
  333. const onDel = (index: any) => {
  334. let originalString: any = form.voucherPhoto;
  335. originalString = originalString.split(',');
  336. originalString.splice(index, 1);
  337. form.voucherPhoto = originalString.join(',');
  338. };
  339. //上传成功
  340. const handleAvatarSuccess = (res: any) => {
  341. if (res.code == 200) {
  342. const url = res.data.url;
  343. if (form.voucherPhoto) {
  344. form.voucherPhoto = form.voucherPhoto + ',' + url;
  345. } else {
  346. form.voucherPhoto = url;
  347. }
  348. } else {
  349. ElMessage({
  350. message: res.msg,
  351. type: 'warning'
  352. });
  353. }
  354. console.log(res);
  355. };
  356. const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
  357. if (rawFile.size / 1024 / 1024 > 2) {
  358. ElMessage.error('不能大于2MB!');
  359. return false;
  360. }
  361. return true;
  362. };
  363. const isDefaultAddress = (addr: any) => {
  364. const v = addr?.isDefault ?? addr?.defaultFlag;
  365. return v === 0 || v === '0' || v === 1 || v === '1' || v === true;
  366. };
  367. const formatAddress = (addr: any) => {
  368. const parts = [addr?.provincialCityCountry].filter(Boolean);
  369. const detail = addr?.detailAddress || addr?.address || addr?.fullAddress || '';
  370. const prefix = parts.join('');
  371. return `${prefix}${detail ? (prefix ? ' ' : '') + detail : ''}`.trim() || '-';
  372. };
  373. const loadAddressList = async () => {
  374. try {
  375. addressLoading.value = true;
  376. const res = await getAddressList();
  377. if (res.code === 200) {
  378. const rows = res.rows || res.data || [];
  379. addressList.value = Array.isArray(rows) ? rows : [];
  380. const defaultAddr = addressList.value.find((a) => isDefaultAddress(a));
  381. const initAddr = selectedAddress.value || defaultAddr || addressList.value[0];
  382. if (initAddr) {
  383. selectedAddress.value = initAddr;
  384. selectedAddressId.value = String(initAddr.id);
  385. }
  386. }
  387. } catch (error) {
  388. console.error('加载地址列表失败:', error);
  389. ElMessage.error('加载地址列表失败');
  390. } finally {
  391. addressLoading.value = false;
  392. }
  393. };
  394. const handleSelectAddress = (addr: any) => {
  395. selectedAddress.value = addr;
  396. selectedAddressId.value = String(addr.id);
  397. };
  398. const handleConfirmAddress = () => {
  399. if (!selectedAddress.value) {
  400. ElMessage.warning('请选择地址');
  401. return;
  402. }
  403. const addr = selectedAddress.value;
  404. form.chargebackName = addr.consignee || addr.receiverName || addr.name || form.chargebackName;
  405. form.chargebackPhone = addr.phone || addr.mobile || form.chargebackPhone;
  406. form.chargebackAddress = formatAddress(addr);
  407. addressDialogVisible.value = false;
  408. };
  409. // 加载退货原因列表
  410. const loadReturnReason = async () => {
  411. try {
  412. const res = await getReturnReason();
  413. if (res.code === 200) {
  414. reasonOptions.value = res.rows || [];
  415. }
  416. } catch (error) {
  417. console.error('加载退货原因列表失败:', error);
  418. }
  419. };
  420. // 加载企业信息
  421. const loadEnterpriseInfo = async () => {
  422. try {
  423. const res = await getEnterpriseInfo();
  424. if (res.code === 200 && res.data) {
  425. const data = res.data;
  426. const salesInfo = data.customerSalesInfoVo || {};
  427. const businessInfo = data.customerBusinessInfoVo || {};
  428. // 辅助函数:处理空值,空字符串也转为 '-'
  429. const formatValue = (value: any) => (value && value !== '' ? value : '-');
  430. form.chargebackName = formatValue(data.customerName);
  431. form.chargebackPhone = formatValue(data.landline);
  432. form.chargebackAddress = formatValue(data.provincialCityCounty) + ' ' + formatValue(data.address);
  433. }
  434. } catch (error) {
  435. console.error('加载企业信息失败:', error);
  436. ElMessage.error('加载企业信息失败');
  437. }
  438. };
  439. const handleChangeAddress = () => {
  440. addressDialogVisible.value = true;
  441. };
  442. const handleViewEvaluation = () => {
  443. ElMessage.info('查看评价');
  444. };
  445. const handleBuyAgain = () => {
  446. ElMessage.info('再次购买');
  447. };
  448. // 加载订单详情
  449. const loadOrderDetail = async () => {
  450. try {
  451. loading.value = true;
  452. const res = await getOrderInfo(orderId.value);
  453. if (res.code === 200 && res.data) {
  454. const order = res.data;
  455. // 映射订单信息
  456. orderInfo.orderId = order.id;
  457. orderInfo.orderNo = order.orderNo;
  458. form.orderId = order.id;
  459. form.orderNo = order.orderNo;
  460. form.orderAmount = order.payableAmount || '0.00';
  461. orderInfo.orderTime = order.orderTime;
  462. orderInfo.freight = order.shippingFee || '0.00';
  463. orderInfo.totalAmount = order.payableAmount || '0.00';
  464. orderInfo.deliveryTime = order.expectedDeliveryTime || '';
  465. orderInfo.purchaseReason = order.purchaseReason || '';
  466. orderInfo.remark = order.remark || '';
  467. form.customerNo = order.customerCode;
  468. // 获取商品信息
  469. const productsRes = await getOrderProductsWithAvailableQty([orderId.value]);
  470. if (productsRes.code === 200 && productsRes.rows) {
  471. selectedProducts.value = productsRes.rows.map((p: any) => {
  472. const soldQty = Number(p.orderQuantity) || 0;
  473. const afterSaleQty = Number(p.afterSaleQuantity) || 0;
  474. // const availableQty = Math.max(soldQty - afterSaleQty, 0);
  475. const availableQty = Math.max(p.availableQty, 0);
  476. const unitPrice = Number(p.orderPrice) || 0;
  477. return {
  478. productId: p.productId,
  479. productNo: p.productNo,
  480. productUnit: p.productUnit || '',
  481. productImage: p.productImage || '',
  482. name: p.productName || '',
  483. spec: `${p.productUnit || ''} ${p.productNo || ''}`.trim(),
  484. unitPrice,
  485. soldQty,
  486. availableQty,
  487. returnQuantity: availableQty > 0 ? 1 : 0,
  488. orderProductId: p.id,
  489. productName: p.productName,
  490. reasonDetail: form.returnReason
  491. } as ApplyProduct;
  492. });
  493. }
  494. }
  495. } catch (error) {
  496. console.error('加载订单详情失败:', error);
  497. ElMessage.error('加载订单详情失败');
  498. } finally {
  499. loading.value = false;
  500. }
  501. };
  502. const handleReturnReasonChange = (value: string) => {
  503. const reason = reasonOptions.value.find((r) => r.id === value);
  504. if (reason) {
  505. form.returnReason = reason.returnReasonName;
  506. }
  507. };
  508. onMounted(() => {
  509. const paramId = route.query.orderId;
  510. // 直接使用字符串,不转换为数字,避免精度丢失
  511. orderId.value = paramId as string;
  512. if (orderId.value) {
  513. loadOrderDetail();
  514. } else {
  515. console.error('订单ID无效,无法加载订单详情');
  516. }
  517. loadReturnReason();
  518. loadEnterpriseInfo();
  519. loadAddressList();
  520. });
  521. const handleSubmit = async () => {
  522. if (selectedProducts.value.length === 0) {
  523. ElMessage.warning('请至少选择一个商品');
  524. return;
  525. }
  526. const hasQty = selectedProducts.value.some((p) => Number(p.returnQuantity) > 0);
  527. if (!hasQty) {
  528. ElMessage.warning('请填写退货数量');
  529. return;
  530. }
  531. const valid = await formRef.value?.validate();
  532. if (!valid) return;
  533. form.returnProductNum = summary.value.totalQty;
  534. form.afterSaleAmount = summary.value.totalAmount;
  535. form.returnAmount = summary.value.totalAmount;
  536. // form.voucherPhoto = (form.voucherPhotoArray || []).map((file: any) => file.url).join(',');
  537. form.orderReturnItemList = selectedProducts.value;
  538. const res = await addOrderReturn(form as any);
  539. if (res.code == 200) {
  540. ElMessage.success('申请成功');
  541. router.push('/order/afterSale');
  542. } else {
  543. ElMessage.error('申请失败');
  544. }
  545. };
  546. </script>
  547. <style scoped lang="scss">
  548. .apply-after-container {
  549. padding: 20px;
  550. background: #fff;
  551. min-height: 100%;
  552. width: 100%;
  553. }
  554. .page-inner {
  555. width: 100%;
  556. max-width: 1200px;
  557. margin: 0 auto;
  558. }
  559. .page-header {
  560. height: 72px;
  561. background: #fff;
  562. border-bottom: 1px solid #eee;
  563. .header-inner {
  564. height: 100%;
  565. display: flex;
  566. align-items: center;
  567. padding: 0 20px;
  568. gap: 20px;
  569. }
  570. .header-left {
  571. display: flex;
  572. align-items: center;
  573. gap: 10px;
  574. .header-icon {
  575. width: 36px;
  576. height: 36px;
  577. border-radius: 50%;
  578. display: flex;
  579. align-items: center;
  580. justify-content: center;
  581. border: 2px solid #e60012;
  582. color: #e60012;
  583. }
  584. .header-title {
  585. color: #e60012;
  586. font-size: 16px;
  587. font-weight: 600;
  588. }
  589. }
  590. .header-center {
  591. flex: 1;
  592. display: flex;
  593. align-items: center;
  594. gap: 50px;
  595. color: #666;
  596. font-size: 13px;
  597. .header-item {
  598. .label {
  599. color: #999;
  600. margin-right: 10px;
  601. }
  602. .value {
  603. color: #333;
  604. }
  605. }
  606. }
  607. .header-right {
  608. display: flex;
  609. gap: 10px;
  610. }
  611. }
  612. .content {
  613. padding: 16px 20px 30px;
  614. }
  615. .panel {
  616. background: #fff;
  617. border: 1px solid #eee;
  618. border-radius: 4px;
  619. padding: 16px 18px;
  620. margin-bottom: 16px;
  621. }
  622. .panel-title {
  623. font-size: 14px;
  624. font-weight: 600;
  625. color: #333;
  626. margin-bottom: 12px;
  627. }
  628. .service-type {
  629. .service-tip {
  630. margin-top: 8px;
  631. font-size: 12px;
  632. color: #999;
  633. }
  634. }
  635. .section-title {
  636. margin-top: 16px;
  637. margin-bottom: 10px;
  638. font-size: 14px;
  639. font-weight: 600;
  640. color: #333;
  641. }
  642. .summary-bar {
  643. background: #eef8ee;
  644. border: 1px solid #d8f0d8;
  645. color: #18a058;
  646. border-radius: 2px;
  647. padding: 10px 12px;
  648. display: flex;
  649. align-items: center;
  650. justify-content: space-between;
  651. margin-bottom: 12px;
  652. .summary-left {
  653. display: flex;
  654. gap: 18px;
  655. font-size: 13px;
  656. }
  657. .summary-close {
  658. cursor: pointer;
  659. color: #18a058;
  660. }
  661. }
  662. .product-table {
  663. border: 1px solid #eee;
  664. .table-header {
  665. display: flex;
  666. background: #fafafa;
  667. border-bottom: 1px solid #eee;
  668. font-size: 12px;
  669. color: #666;
  670. font-weight: 600;
  671. }
  672. .table-row {
  673. display: flex;
  674. border-bottom: 1px solid #f0f0f0;
  675. &:last-child {
  676. border-bottom: none;
  677. }
  678. }
  679. .col {
  680. padding: 12px;
  681. display: flex;
  682. align-items: center;
  683. justify-content: center;
  684. border-right: 1px solid #f0f0f0;
  685. min-height: 88px;
  686. &:last-child {
  687. border-right: none;
  688. }
  689. }
  690. .col-product {
  691. flex: 1;
  692. justify-content: flex-start;
  693. }
  694. .col-price,
  695. .col-sold,
  696. .col-available,
  697. .col-qty,
  698. .col-amount {
  699. width: 110px;
  700. }
  701. .col-op {
  702. width: 80px;
  703. }
  704. .table-empty {
  705. padding: 20px 0;
  706. }
  707. .product-info {
  708. display: flex;
  709. gap: 12px;
  710. align-items: center;
  711. width: 100%;
  712. }
  713. .product-image {
  714. width: 60px;
  715. height: 60px;
  716. background: #f5f5f5;
  717. border-radius: 4px;
  718. overflow: hidden;
  719. flex-shrink: 0;
  720. .el-image {
  721. width: 100%;
  722. height: 100%;
  723. }
  724. .image-placeholder {
  725. width: 100%;
  726. height: 100%;
  727. background: #f5f5f5;
  728. }
  729. }
  730. .product-meta {
  731. .name {
  732. font-size: 13px;
  733. color: #333;
  734. line-height: 1.4;
  735. }
  736. .spec {
  737. margin-top: 4px;
  738. font-size: 12px;
  739. color: #999;
  740. }
  741. }
  742. }
  743. .after-form {
  744. :deep(.el-form-item__label) {
  745. color: #333;
  746. }
  747. }
  748. .pickup-type {
  749. margin-bottom: 12px;
  750. }
  751. .pickup-info {
  752. font-size: 13px;
  753. color: #666;
  754. .pickup-row {
  755. margin-bottom: 10px;
  756. .k {
  757. color: #999;
  758. margin-right: 6px;
  759. }
  760. &.hint {
  761. color: #999;
  762. }
  763. }
  764. }
  765. .footer {
  766. display: flex;
  767. justify-content: center;
  768. padding: 10px 0 20px;
  769. .submit-btn {
  770. width: 120px;
  771. }
  772. }
  773. .address-dialog {
  774. .address-list {
  775. max-height: 520px;
  776. overflow: auto;
  777. }
  778. .address-item {
  779. display: flex;
  780. justify-content: space-between;
  781. gap: 10px;
  782. padding: 14px 16px;
  783. border: 1px solid #e5e5e5;
  784. margin-bottom: 12px;
  785. cursor: pointer;
  786. &:hover {
  787. border-color: #d9d9d9;
  788. }
  789. &.active {
  790. border-color: #e60012;
  791. }
  792. .address-left {
  793. .line {
  794. font-size: 13px;
  795. color: #333;
  796. line-height: 1.8;
  797. }
  798. }
  799. .address-right {
  800. display: flex;
  801. align-items: center;
  802. justify-content: flex-end;
  803. min-width: 80px;
  804. }
  805. .default-tag {
  806. color: #e60012;
  807. font-size: 12px;
  808. }
  809. }
  810. .dialog-footer {
  811. display: flex;
  812. justify-content: flex-end;
  813. gap: 10px;
  814. }
  815. }
  816. .avatar-uploader {
  817. margin: 10px 20px 0 0px;
  818. :deep(.el-upload) {
  819. width: 108px;
  820. height: 108px;
  821. background: #f2f3f5;
  822. border-radius: 2px;
  823. cursor: pointer;
  824. position: relative;
  825. overflow: hidden;
  826. }
  827. }
  828. .upload-bos {
  829. width: 108px;
  830. height: 108px;
  831. border-radius: 2px;
  832. margin: 10px 20px 0 0px;
  833. position: relative;
  834. &:hover {
  835. .upload-box {
  836. width: 100%;
  837. height: 100%;
  838. }
  839. }
  840. .upload-img {
  841. width: 100%;
  842. height: 100%;
  843. }
  844. .upload-box {
  845. width: 0;
  846. height: 0;
  847. overflow: hidden;
  848. background-color: rgba(0, 0, 0, 0.5);
  849. position: absolute;
  850. top: 0;
  851. left: 0;
  852. display: flex;
  853. justify-content: center;
  854. align-items: center;
  855. .icons {
  856. cursor: pointer;
  857. }
  858. }
  859. }
  860. .el-icon.avatar-uploader-icon {
  861. font-size: 28px;
  862. color: #8c939d;
  863. width: 178px;
  864. height: 178px;
  865. text-align: center;
  866. }
  867. </style>