apply.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. <!-- 售后申请 -->
  2. <template>
  3. <s-layout :title="t('order.apply_for_after_sales')">
  4. <!-- 售后商品 -->
  5. <view class="goods-box">
  6. <s-goods-item
  7. :img="state.item.picUrl"
  8. :title="state.item.spuName"
  9. :skuText="state.item.properties?.map((property) => property.valueName).join(' ')"
  10. :price="state.item.price"
  11. :num="state.item.count"
  12. :areaId="state.integralType"
  13. />
  14. </view>
  15. <uni-forms ref="form" v-model="formData" :rules="rules" label-position="top">
  16. <!-- 售后类型 -->
  17. <view class="refund-item">
  18. <view class="item-title ss-m-b-20">{{t('order.after_sales_type')}}</view>
  19. <view class="ss-flex-col">
  20. <radio-group @change="onRefundChange">
  21. <label
  22. class="ss-flex ss-col-center ss-p-y-10"
  23. v-for="(item, index) in state.wayList"
  24. :key="index"
  25. >
  26. <radio
  27. :checked="formData.type === item.value"
  28. color="var(--ui-BG-Main)"
  29. style="transform: scale(0.8)"
  30. :value="item.value"
  31. />
  32. <view class="item-value ss-m-l-8">{{ item.text }}</view>
  33. </label>
  34. </radio-group>
  35. </view>
  36. </view>
  37. <!-- 退款金额 -->
  38. <view class="refund-item ss-flex ss-col-center ss-row-between" @tap="state.showModal = true">
  39. <text class="item-title">{{t('order.refund_amount')}}</text>
  40. <view class="ss-flex refund-cause ss-col-center">
  41. <view class="points-red" v-if="state.integralType == 1"></view>
  42. <view class="points-green" v-if="state.integralType == 3"></view>
  43. <!-- {{ state }} -->
  44. <text class="ss-m-r-20">{{ fen2yuan(state.order.payIntegral) }}</text>
  45. </view>
  46. </view>
  47. <!-- 申请原因 -->
  48. <view class="refund-item ss-flex ss-col-center ss-row-between" @tap="state.showModal = true">
  49. <text class="item-title">{{t('order.application_reason')}}</text>
  50. <view class="ss-flex refund-cause ss-col-center">
  51. <text class="ss-m-r-20" v-if="formData.applyReason">{{ formData.applyReason }}</text>
  52. <text class="ss-m-r-20" v-else>{{t('order.select_reason_for_application')}}</text>
  53. <text class="cicon-forward" style="height: 28rpx"></text>
  54. </view>
  55. </view>
  56. <!-- 留言 -->
  57. <view class="refund-item">
  58. <view class="item-title ss-m-b-20">{{t('order.related_description')}}</view>
  59. <view class="describe-box">
  60. <uni-easyinput
  61. :inputBorder="false"
  62. class="describe-content"
  63. type="textarea"
  64. maxlength="120"
  65. autoHeight
  66. v-model="formData.applyDescription"
  67. :placeholder="t('order.customer_prompt')"
  68. />
  69. <!-- TODO 非繁人:上传的测试 -->
  70. <view class="upload-img">
  71. <s-uploader
  72. v-model:url="formData.applyPicUrls"
  73. fileMediatype="image"
  74. limit="9"
  75. mode="grid"
  76. :imageStyles="{ width: '168rpx', height: '168rpx' }"
  77. />
  78. </view>
  79. </view>
  80. </view>
  81. </uni-forms>
  82. <!-- 底部按钮 -->
  83. <su-fixed bottom placeholder>
  84. <view class="foot-wrap">
  85. <view class="foot_box ss-flex ss-col-center ss-row-between ss-p-x-30">
  86. <!-- <button class="ss-reset-button contcat-btn" @tap="sheep.$router.go('/pages/chat/index')">
  87. 联系客服
  88. </button> -->
  89. <button class="ss-reset-button ui-BG-Main-Gradient sub-btn" @tap="submit">{{t('common.submit')}}</button>
  90. </view>
  91. </view>
  92. </su-fixed>
  93. <!-- 申请原因弹窗 -->
  94. <su-popup :show="state.showModal" round="10" :showClose="true" @close="state.showModal = false">
  95. <view class="modal-box page_box">
  96. <view class="modal-head item-title head_box ss-flex ss-row-center ss-col-center">
  97. {{t('order.application_reason')}}
  98. </view>
  99. <view class="modal-content content_box">
  100. <radio-group @change="onChange">
  101. <label
  102. class="radio ss-flex ss-col-center"
  103. v-for="item in state.reasonList"
  104. :key="item"
  105. >
  106. <view class="ss-flex-1 ss-p-20">{{ item }}</view>
  107. <radio
  108. :value="item"
  109. color="var(--ui-BG-Main)"
  110. :checked="item === state.currentValue"
  111. />
  112. </label>
  113. </radio-group>
  114. </view>
  115. <view class="modal-foot foot_box ss-flex ss-row-center ss-col-center">
  116. <button class="ss-reset-button close-btn ui-BG-Main-Gradient" @tap="onReason">
  117. {{t('common.confirm')}}
  118. </button>
  119. </view>
  120. </view>
  121. </su-popup>
  122. </s-layout>
  123. </template>
  124. <script setup>
  125. import sheep from '@/sheep';
  126. import { onLoad } from '@dcloudio/uni-app';
  127. import { reactive, ref } from 'vue';
  128. import OrderApi from '@/sheep/api/trade/order';
  129. import TradeConfigApi from '@/sheep/api/trade/config';
  130. import { fen2yuan,points2point } from '@/sheep/hooks/useGoods';
  131. import AfterSaleApi from '@/sheep/api/trade/afterSale';
  132. import { t } from '@/locale'
  133. const form = ref(null);
  134. const state = reactive({
  135. orderId: 0, // 订单编号
  136. itemId: 0, // 订单项编号
  137. order: {}, // 订单
  138. item: {}, // 订单项
  139. config: {}, // 交易配置
  140. // 售后类型
  141. wayList: [
  142. // {
  143. // text: t('order.refund_only')
  144. // value: '10',
  145. // },
  146. {
  147. text: t('order.refund_and_return'),
  148. value: '20',
  149. },
  150. ],
  151. reasonList: [], // 可选的申请原因数组
  152. showModal: false, // 是否显示申请原因弹窗
  153. currentValue: '', // 当前选择的售后原因
  154. integralType:0
  155. });
  156. const formData = reactive({
  157. way: '',
  158. applyReason: '',
  159. applyDescription: '',
  160. applyPicUrls: [],
  161. });
  162. const rules = reactive({});
  163. // 提交表单
  164. async function submit() {
  165. if(formData.way == ""){
  166. uni.showToast({
  167. title: t('order.select_after_sales_type'),
  168. icon: 'error',
  169. duration: 1000
  170. })
  171. return false;
  172. }
  173. if(state.currentValue == ""){
  174. uni.showToast({
  175. title: t('order.reason_for_application'),
  176. icon: 'error',
  177. duration: 1000
  178. })
  179. return false;
  180. }
  181. if(formData.applyDescription == ""){
  182. uni.showToast({
  183. title:t('order.please_fill_in_description'),
  184. icon: 'error',
  185. duration: 1000
  186. })
  187. return false;
  188. }
  189. // #ifdef MP
  190. sheep.$platform.useProvider('wechat').subscribeMessage('order_aftersale_change');
  191. // #endif
  192. let data = {
  193. orderItemId: state.itemId,
  194. refundIntegral: state.order.payIntegral,
  195. refundIntegralType:state.integralType,
  196. ...formData,
  197. };
  198. const { code } = await AfterSaleApi.createAfterSale(data);
  199. if (code === 0) {
  200. uni.showToast({
  201. title: t('wallet.application_successful')
  202. });
  203. sheep.$router.go('/pages/order/aftersale/list');
  204. }
  205. }
  206. // 选择售后类型
  207. function onRefundChange(e) {
  208. formData.way = e.detail.value;
  209. // 清理理由
  210. state.reasonList =
  211. formData.way === '10'
  212. ? state.config.afterSaleRefundReasons || []
  213. : state.config.afterSaleReturnReasons || [];
  214. formData.applyReason = '';
  215. state.currentValue = '';
  216. }
  217. // 选择申请原因
  218. function onChange(e) {
  219. state.currentValue = e.detail.value;
  220. }
  221. // 确定
  222. function onReason() {
  223. formData.applyReason = state.currentValue;
  224. state.showModal = false;
  225. }
  226. onLoad(async (options) => {
  227. // 解析参数
  228. if (!options.orderId || !options.itemId) {
  229. sheep.$helper.toast(`缺少订单信息,请检查`);
  230. return;
  231. }
  232. state.orderId = options.orderId;
  233. state.itemId = parseInt(options.itemId);
  234. state.integralType = options.integralType;
  235. // 读取订单信息
  236. const { code, data } = await OrderApi.getOrder(state.orderId);
  237. if (code !== 0) {
  238. return;
  239. }
  240. state.order = data;
  241. state.item = data.items.find((item) => item.id === state.itemId) || {};
  242. // 设置选项
  243. if (state.order.status === 10) {
  244. state.wayList.splice(1, 1);
  245. }
  246. // 读取配置
  247. state.config = (await TradeConfigApi.getTradeConfig()).data;
  248. // 取消仅退款,所以一进入这个页面就默认选中售后退款
  249. formData.way = '20';
  250. formData.type = '20';
  251. state.reasonList = state.config.afterSaleReturnReasons || [];
  252. });
  253. </script>
  254. <style lang="scss" scoped>
  255. .item-title {
  256. font-size: 30rpx;
  257. font-weight: bold;
  258. color: rgba(51, 51, 51, 1);
  259. // margin-bottom: 20rpx;
  260. }
  261. // 售后项目
  262. .refund-item {
  263. background-color: #fff;
  264. border-bottom: 1rpx solid #f5f5f5;
  265. padding: 30rpx;
  266. &:last-child {
  267. border: none;
  268. }
  269. // 留言
  270. .describe-box {
  271. width: 690rpx;
  272. background: rgba(249, 250, 251, 1);
  273. padding: 30rpx;
  274. box-sizing: border-box;
  275. border-radius: 20rpx;
  276. .describe-content {
  277. height: 200rpx;
  278. font-size: 24rpx;
  279. font-weight: 400;
  280. color: #333;
  281. }
  282. }
  283. // 联系方式
  284. .input-box {
  285. height: 84rpx;
  286. background: rgba(249, 250, 251, 1);
  287. border-radius: 20rpx;
  288. }
  289. }
  290. .goods-box {
  291. background: #fff;
  292. padding: 20rpx;
  293. margin-bottom: 20rpx;
  294. }
  295. .foot-wrap {
  296. height: 100rpx;
  297. width: 100%;
  298. }
  299. .foot_box {
  300. height: 100rpx;
  301. background-color: #fff;
  302. .sub-btn {
  303. width: 100%;
  304. line-height: 74rpx;
  305. border-radius: 38rpx;
  306. color: rgba(#fff, 0.9);
  307. font-size: 28rpx;
  308. }
  309. .contcat-btn {
  310. width: 336rpx;
  311. line-height: 74rpx;
  312. background: rgba(238, 238, 238, 1);
  313. border-radius: 38rpx;
  314. font-size: 28rpx;
  315. font-weight: 400;
  316. color: rgba(51, 51, 51, 1);
  317. }
  318. }
  319. .modal-box {
  320. width: 750rpx;
  321. // height: 680rpx;
  322. border-radius: 30rpx 30rpx 0 0;
  323. background: #fff;
  324. .modal-head {
  325. height: 100rpx;
  326. font-size: 30rpx;
  327. }
  328. .modal-content {
  329. font-size: 28rpx;
  330. }
  331. .modal-foot {
  332. .close-btn {
  333. width: 710rpx;
  334. line-height: 80rpx;
  335. border-radius: 40rpx;
  336. color: rgba(#fff, 0.9);
  337. }
  338. }
  339. }
  340. .success-box {
  341. width: 600rpx;
  342. padding: 90rpx 0 64rpx 0;
  343. .cicon-check-round {
  344. font-size: 96rpx;
  345. color: #04b750;
  346. }
  347. .success-title {
  348. font-weight: 500;
  349. color: #333333;
  350. font-size: 32rpx;
  351. }
  352. .success-btn {
  353. width: 492rpx;
  354. height: 70rpx;
  355. background: linear-gradient(90deg, var(--ui-BG-Main-gradient), var(--ui-BG-Main));
  356. border-radius: 35rpx;
  357. }
  358. }
  359. </style>