applyAfter.vue 24 KB

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