You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

707 lines
20 KiB

3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
  1. <script setup>
  2. import {computed, onMounted, reactive, ref, watch} from 'vue'
  3. import {ElMessage, ElMessageBox} from 'element-plus'
  4. import API from '@/util/http.js'
  5. import moment from 'moment'
  6. // import _ from 'lodash'
  7. const addRe = ref({
  8. typeR: '0'
  9. })
  10. //这是获取用户信息的接口
  11. const adminData = ref({})
  12. const getAdminData = async function () {
  13. try {
  14. const result = await API({url: '/admin/userinfo', data: {}})
  15. adminData.value = result
  16. addRefund.value.adminId = adminData.value.id
  17. console.log('请求成功', result)
  18. console.log('用户信息', user.value)
  19. } catch (error) {
  20. console.log('请求失败', error)
  21. }
  22. }
  23. // 精网号去空格
  24. const trimJwCode = () => {
  25. if (addRefund.value.jwcode) {
  26. addRefund.value.jwcode = addRefund.value.jwcode.replace(/\s/g, '');
  27. }
  28. }
  29. // 这是添加退款信息的表单
  30. const addRefund = ref({
  31. jwcode: '',
  32. goodsName: '',
  33. refundType: '',
  34. refundModel: 0,
  35. permanentGold: '',
  36. freeGold: '',
  37. taskGold: '',
  38. sumGold: 0,
  39. remark: '',
  40. adminId: null,
  41. orderCode: ''
  42. })
  43. // 取消按钮
  44. const cancel = function () {
  45. addRefund.value = {
  46. jwcode: '',
  47. goodsName: '',
  48. refundType: '',
  49. refundModel: 0,
  50. permanentGold: '',
  51. freeGold: '',
  52. taskGold: '',
  53. sumGold: 0,
  54. remark: '',
  55. adminId: adminData.value.id,
  56. orderCode: '',
  57. }
  58. addRe.value.typeR = '0'
  59. }
  60. // 这是添加退款信息的接口
  61. const add = async function () {
  62. try {
  63. // 更新 refundModel
  64. addRefund.value.refundModel = parseInt(addRe.value.typeR);
  65. // 对提交的金币数乘以 100
  66. const processedRefund = {
  67. ...addRefund.value,
  68. permanentGold: (Number(addRefund.value.permanentGold) || 0) * 100,
  69. freeGold: (Number(addRefund.value.freeGold) || 0) * 100,
  70. taskGold: (Number(addRefund.value.taskGold) || 0) * 100,
  71. sumGold: (Number(addRefund.value.sumGold) || 0) * 100
  72. }
  73. addDisabled.value = true
  74. // 发送POST请求
  75. const result = await API({
  76. url: '/refund/add',
  77. data: processedRefund
  78. })
  79. addDisabled.value = false
  80. if (result.code === 0) {
  81. ElMessage.error(result.msg)
  82. return
  83. }
  84. console.log('请求成功', result)
  85. ElMessage.success('添加成功')
  86. // 重置表单
  87. cancel()
  88. } catch (error) {
  89. console.log('请求失败', error)
  90. // 在这里可以处理错误逻辑,比如显示错误提示等
  91. }
  92. }
  93. //提交禁止重复点击
  94. const addDisabled = ref(false)
  95. // 提交退款信息前的表单验证
  96. const addBefore = () => {
  97. Ref.value.validate(async (valid) => {
  98. if (valid) {
  99. // try {
  100. // await getUser(addRefund.value.jwcode);
  101. // if (!user.value.jwcode) {
  102. // ElMessage.error('请先验证有效的精网号');
  103. // return;
  104. // }
  105. // } catch (error) {
  106. // ElMessage.error('精网号验证失败');
  107. // return;
  108. // }
  109. ElMessageBox.confirm('确认退款?')
  110. .then(() => {
  111. add()
  112. console.log('退款成功')
  113. })
  114. .catch(() => {
  115. console.log('取消退款')
  116. })
  117. } else {
  118. //提示
  119. ElMessage({
  120. type: 'error',
  121. message: '请检查输入内容'
  122. })
  123. }
  124. })
  125. }
  126. // 表单验证
  127. // 开始时间改变时,重新验证结束时间
  128. const Ref = ref(null)
  129. const startChange = (val) => {
  130. }
  131. const validateJwCode = (rule, value, callback) => {
  132. if (!value) {
  133. callback(new Error('精网号不能为空'));
  134. return;
  135. }
  136. if (/[^0-9]/.test(value)) {
  137. callback(new Error('精网号只能包含数字'));
  138. return;
  139. }
  140. callback();
  141. };
  142. const rules = reactive({
  143. jwcode: [{required: true, validator: validateJwCode, trigger: 'blur'}],
  144. refundType: [{required: true, message: '请选择退款类型', trigger: 'blur'}],
  145. goodsName: [{required: false, message: '请选择退款商品', trigger: 'blur'}],
  146. // 订单号
  147. orderCode: [{required: true, message: '请输入订单号', trigger: 'blur'}],
  148. taskGold: [{required: true, message: '请输入任务金币', trigger: 'blur'}],
  149. freeGold: [{required: true, message: '请输入免费金币', trigger: 'blur'}],
  150. permanentGold: [
  151. {required: true, message: '请输入永久金币', trigger: 'blur'}
  152. ],
  153. sumGold: [
  154. {required: true, message: '请选择付款方式', trigger: 'blur'},
  155. {
  156. validator: (rule, value) => {
  157. if (value === 0) {
  158. return Promise.reject(new Error('总金币不能为0'))
  159. }
  160. return Promise.resolve()
  161. },
  162. trigger: 'blur'
  163. }
  164. ]
  165. })
  166. // 取消按钮
  167. const cancelExceptJwcode = function () {
  168. addRefund.value = {
  169. jwcode: addRefund.value.jwcode, // 保留精网号
  170. goodsName: '',
  171. refundType: '',
  172. refundModel: 0,
  173. permanentGold: '',
  174. freeGold: '',
  175. taskGold: '',
  176. sumGold: 0,
  177. remark: '',
  178. adminId: adminData.value.id
  179. }
  180. addRe.value.typeR = '0'
  181. }
  182. // 查找客户信息的方法
  183. const user = ref({
  184. firstRechargeTime: ''
  185. })
  186. const getUser = async function (jwcode) {
  187. trimJwCode();
  188. // cancelExceptJwcode();
  189. // 验证精网号
  190. if (!jwcode) {
  191. ElMessage.warning('精网号不能为空');
  192. return;
  193. }
  194. // 验证精网号是否为数字
  195. if (!/^\d{1,9}$/.test(jwcode)) {
  196. cancel()
  197. return;
  198. }
  199. try {
  200. // 发送POST请求
  201. const result = await API({
  202. url: '/user/selectUser',
  203. data: {
  204. jwcode: addRefund.value.jwcode
  205. }
  206. })
  207. console.log('请求成功', result)
  208. // 重置订单信息
  209. restGoods()
  210. //在此处错误逻辑的提示做了注释,在后续商品查询接口返回错误时,提示信息会显示在弹窗中
  211. if (result.code === 0) {
  212. ElMessage.error(result.msg);
  213. } else if (result.data === null) {
  214. ElMessage.error("用户不存在");
  215. } else {
  216. // 对传过来的金币数除以 100
  217. const processedData = {
  218. ...result.data,
  219. historySumGold: (Number(result.data.historySumGold) || 0) ,
  220. nowSumGold: (Number(result.data.nowSumGold) || 0) ,
  221. nowPermanentGold: (Number(result.data.nowPermanentGold) || 0) ,
  222. nowFreeGold: (Number(result.data.nowFreeGold) || 0) ,
  223. nowTaskGold: (Number(result.data.nowTaskGold) || 0)
  224. }
  225. user.value = processedData;
  226. console.log("用户信息", user.value);
  227. //ElMessage.success(result.msg);
  228. }
  229. } catch (error) {
  230. console.log("请求失败", error);
  231. ElMessage.error("精网号错误");
  232. }
  233. }
  234. // 退款类型,写死为商品退款
  235. const refundType = ref([{value: '商品退款', label: '商品退款'}]);
  236. // 获取退款类型
  237. // const getRefundTypes = async function () {
  238. // try {
  239. // // 发送请求获取退款类型
  240. // const result = await API({
  241. // url: '/refund/refundType', //这里应该写上一个退款类型的接口
  242. // data: {} })
  243. // console.log('退款类型请求成功', result)
  244. // // 检查返回的数据是否为数组
  245. // if (Array.isArray(result.data)) {
  246. // // 将字符串数组转换为 { value, label } 格式
  247. // refundType.value = result.data.map(item => ({ value: item, label: item }));
  248. // } else {
  249. // console.error('退款类型数据格式错误', result)
  250. // ElMessage.error('退款类型数据格式错误,请联系管理员')
  251. // }
  252. // console.log('退款类型', refundType.value)
  253. // } catch (error) {
  254. // console.log('退款类型请求失败', error)
  255. // }
  256. // }
  257. // 根据精网号查询商品
  258. const goodsName = ref([]) // 存储所有商品信息
  259. const orderCodes = ref([]) // 订单号下拉
  260. const getGoods = async function (jwcode) {
  261. trimJwCode();
  262. if (!/^\d{1,9}$/.test(jwcode)) {
  263. ElMessage.warning('精网号必须为数字且不超过九位');
  264. return;
  265. }
  266. if (!addRefund.value.jwcode) {
  267. goodsName.value = []
  268. orderCodes.value = []
  269. return;
  270. }
  271. try {
  272. const result = await API({
  273. url: '/refund/selectGoods',
  274. data: {jwcode: addRefund.value.jwcode}
  275. })
  276. if (Array.isArray(result.data)) {
  277. // 存储完整商品数据
  278. goodsName.value = result.data.map(item => ({
  279. orderCode: item.orderCode,
  280. goodsName: item.goodsName,
  281. permanentGold: Math.abs(Number(item.permanentGold) || 0) ,
  282. freeGold: Math.abs(Number(item.freeGold) || 0) ,
  283. taskGold: Math.abs(Number(item.taskGold) || 0) ,
  284. }))
  285. // 转换订单号数组 [{label, value}]
  286. orderCodes.value = result.data.map(item => ({
  287. label: item.orderCode,
  288. value: item.orderCode
  289. }))
  290. } else {
  291. ElMessage.error('商品数据格式错误,请联系管理员')
  292. }
  293. } catch (error) {
  294. console.log('请求失败', error)
  295. ElMessage.error('查询商品失败,请检查精网号是否正确')
  296. goodsName.value = []
  297. orderCodes.value = []
  298. }
  299. }
  300. // 根据订单号自动带出商品名
  301. const handleOrderChange = (orderCode) => {
  302. const selected = goodsName.value.find(item => item.orderCode === orderCode)
  303. if (selected) {
  304. addRefund.value.goodsName = selected.goodsName
  305. // 退款方式是“全部退款”时,自动填充金币
  306. if (addRe.value.typeR === '0') {
  307. addRefund.value.permanentGold = selected.permanentGold
  308. addRefund.value.freeGold = selected.freeGold
  309. addRefund.value.taskGold = selected.taskGold
  310. }
  311. // 记录选中商品金币
  312. selectedGoodsGold.value = {
  313. permanentGold: selected.permanentGold,
  314. freeGold: selected.freeGold,
  315. taskGold: selected.taskGold
  316. }
  317. } else {
  318. restGoods()
  319. }
  320. }
  321. // 存储选中商品的金币花费信息
  322. const selectedGoodsGold = ref({
  323. permanentGold: 0,
  324. freeGold: 0,
  325. taskGold: 0
  326. })
  327. // 重置订单信息
  328. const restGoods = () => {
  329. addRefund.value.orderCode = ''
  330. addRefund.value.goodsName = ''
  331. addRefund.value.permanentGold = ''
  332. addRefund.value.freeGold = ''
  333. addRefund.value.taskGold = ''
  334. }
  335. // 处理退款方式切换,当部分退款切换全部退款时,退款金额应自动重新填充
  336. const handleRefundModelChange = () => {
  337. if (addRe.value.typeR === '0') {
  338. // 全部退款时
  339. addRefund.value.permanentGold = selectedGoodsGold.value.permanentGold;
  340. addRefund.value.freeGold = selectedGoodsGold.value.freeGold;
  341. addRefund.value.taskGold = selectedGoodsGold.value.taskGold;
  342. }
  343. }
  344. const handleSelectionChange = (selectedOption) => {
  345. if (selectedOption) {
  346. // 更新商品名称
  347. addRefund.value.goodsName = selectedOption.value;
  348. // 更新金币字段
  349. addRefund.value.permanentGold = selectedOption.permanentGold || 0;
  350. addRefund.value.freeGold = selectedOption.freeGold || 0;
  351. addRefund.value.taskGold = selectedOption.taskGold || 0;
  352. // 记录选中商品的金币花费
  353. selectedGoodsGold.value.permanentGold = Number(selectedOption.permanentGold) || 0;
  354. selectedGoodsGold.value.freeGold = Number(selectedOption.freeGold) || 0;
  355. selectedGoodsGold.value.taskGold = Number(selectedOption.taskGold) || 0;
  356. } else {
  357. // 清空逻辑保持不变
  358. }
  359. console.log('选择的商品', selectedOption);
  360. }
  361. // 统一的输入处理函数
  362. const handleGoldInput = (type, value) => {
  363. // 1. 过滤非法字符(只允许数字和小数点)
  364. let filtered = value.replace(/[^\d.]/g, '');
  365. // 2. 防止多个小数点
  366. const dotCount = (filtered.match(/\./g) || []).length;
  367. if (dotCount > 1) {
  368. // 保留第一个小数点,移除后续的小数点
  369. const parts = filtered.split('.');
  370. filtered = parts[0] + '.' + parts.slice(1).join('');
  371. }
  372. // 3. 限制小数点后两位
  373. if (filtered.includes('.')) {
  374. const [integer, decimal] = filtered.split('.');
  375. if (decimal.length > 2) {
  376. filtered = `${integer}.${decimal.slice(0, 2)}`;
  377. }
  378. }
  379. // 4. 检查是否以小数点开头,如果是,在前面添加0
  380. if (filtered.startsWith('.')) {
  381. filtered = '0' + filtered;
  382. }
  383. // 5. 更新值
  384. addRefund.value[type] = filtered;
  385. // 6. 验证逻辑(这里直接应用修正后的值)
  386. const maxValue = selectedGoodsGold.value[type];
  387. const inputValue = Number(filtered || 0);
  388. if (isNaN(inputValue)) {
  389. return;
  390. }
  391. if (inputValue > maxValue) {
  392. // 修正为最大值并设置回输入框
  393. const correctedValue = maxValue.toFixed(2);
  394. addRefund.value[type] = correctedValue;
  395. ElMessage.warning('所填金额大于该类金币余额');
  396. }
  397. if (inputValue < 0) {
  398. // 修正为0并设置回输入框
  399. addRefund.value[type] = '0';
  400. ElMessage.warning('不能输入负数');
  401. }
  402. }
  403. // 使用示例
  404. const handlePermanentGoldInput = (value) => handleGoldInput('permanentGold', value);
  405. const handleFreeGoldInput = (value) => handleGoldInput('freeGold', value);
  406. const handleTaskGoldInput = (value) => handleGoldInput('taskGold', value);
  407. // 计算总金币
  408. const calculatedRechargeGoods = computed(() => {
  409. const permanentGold = addRefund.value.permanentGold === '' ? 0 : +addRefund.value.permanentGold;
  410. const freeGold = addRefund.value.freeGold === '' ? 0 : +addRefund.value.freeGold;
  411. const taskGold = addRefund.value.taskGold === '' ? 0 : +addRefund.value.taskGold;
  412. return Number((permanentGold + freeGold + taskGold).toFixed(2));
  413. })
  414. watch(calculatedRechargeGoods, (newVal) => {
  415. addRefund.value.sumGold = newVal
  416. console.log('计算的总金币', newVal)
  417. })
  418. // 挂载
  419. onMounted(async function () {
  420. await getAdminData()
  421. // await getRefundTypes()
  422. // await getGoods()
  423. })
  424. </script>
  425. <template>
  426. <div>
  427. <div class="userAndForm">
  428. <el-form :model="addRefund" ref="Ref" :rules="rules" label-width="auto" label-position="right"
  429. style="max-width: 750px" class="form-style">
  430. <el-form-item prop="jwcode" label="精网号">
  431. <el-input v-model="addRefund.jwcode" style="width: 220px" @change="getGoods(addRefund.jwcode)"/>
  432. <el-button type="primary" @click="getUser(addRefund.jwcode)" style="margin-left: 20px">查询
  433. </el-button>
  434. </el-form-item>
  435. <el-form-item prop="refundType" label="退款类型">
  436. <el-select v-model="addRefund.refundType" placeholder="请选择" style="width: 220px">
  437. <el-option v-for="item in refundType" :key="item.value" :label="item.label" :value="item.value"/>
  438. </el-select>
  439. </el-form-item>
  440. <el-form-item prop="orderCode" label="订单号">
  441. <el-select
  442. v-model="addRefund.orderCode"
  443. placeholder="请选择订单号"
  444. style="width: 220px;"
  445. clearable
  446. filterable
  447. :filter-method="handleFilter"
  448. @change="handleOrderChange"
  449. >
  450. <el-option
  451. v-for="(item, index) in orderCodes"
  452. :key="index"
  453. :label="item.label"
  454. :value="item.value"
  455. />
  456. </el-select>
  457. </el-form-item>
  458. <el-form-item prop="goodsName" label="商品名">
  459. <el-input v-model="addRefund.goodsName" style="width: 220px" disabled/>
  460. </el-form-item>
  461. <el-form-item prop="refundModel" label="退款方式:">
  462. <el-radio-group v-model="addRe.typeR" @change="handleRefundModelChange">
  463. <el-radio value="0">全部退款</el-radio>
  464. <el-radio value="1">部分退款</el-radio>
  465. </el-radio-group>
  466. </el-form-item>
  467. <el-form-item prop="permanentGold" label="永久金币">
  468. <el-input v-model="addRefund.permanentGold" style="width: 220px"
  469. :disabled="addRe.typeR === '0' ? true : false" @input="handlePermanentGoldInput($event)"
  470. type="number">
  471. </el-input>&nbsp;&nbsp;
  472. </el-form-item>
  473. <el-form-item prop="freeGold" label="免费金币">
  474. <el-input v-model="addRefund.freeGold" style="float: left; width: 220px"
  475. :disabled="addRe.typeR === '0' ? true : false" @input="handleFreeGoldInput($event)" type="number"/>
  476. &nbsp;&nbsp;
  477. </el-form-item>
  478. <div>
  479. <el-form-item prop="taskGold" label="任务金币">
  480. <el-input v-model="addRefund.taskGold" style="float: left; width: 220px"
  481. :disabled="addRe.typeR === '0' ? true : false" @input="handleTaskGoldInput($event)"
  482. type="number"/>
  483. &nbsp;&nbsp;
  484. </el-form-item>
  485. </div>
  486. <div>
  487. <el-form-item prop="sumGold" label="退款金币总数">
  488. <el-input disabled v-model="addRefund.sumGold" style="width: 220px">
  489. </el-input>
  490. </el-form-item>
  491. </div>
  492. <div>
  493. <el-form-item prop="remark" label="备注">
  494. <el-input v-model="addRefund.remark" style="width: 220px" :rows="3" maxlength="100" show-word-limit
  495. type="textarea"/>
  496. </el-form-item>
  497. </div>
  498. <el-button type="success" @click="cancel()" style="margin-left: 200px">重置</el-button>
  499. <el-button type="primary" :disabled="addDisabled" @click="addBefore"> 提交</el-button>
  500. </el-form>
  501. <!-- 客户信息栏 -->
  502. <el-card v-if="user.jwcode" style="max-width: 800px" class="beautiful">
  503. <el-form :model="user" label-width="auto" style="max-width: 800px" label-position="left">
  504. <el-text size="large" style="margin-left: 20px">客户信息</el-text>
  505. <!-- 第一行姓名 + 历史金币 -->
  506. <el-row style="margin-top: 20px">
  507. <el-col :span="9">
  508. <el-form-item label="姓名">
  509. <p>{{ user.name }}</p>
  510. </el-form-item>
  511. </el-col>
  512. <el-col :span="14">
  513. <el-form-item label="当前金币总数" style="width: 500px">
  514. <span style="color: #2fa1ff; margin-right: 5px" v-if="user.nowSumGold !== undefined">{{
  515. user.nowSumGold
  516. }}</span>
  517. </el-form-item>
  518. <!-- 金币详情独立显示 -->
  519. <el-form-item style="margin-top: -23px"> <!-- 负边距减少间距 -->
  520. <span style="color: #b1b1b1; margin-left: 0px" v-if="user.nowPermanentGold !== undefined">(永久金币:{{
  521. user.nowPermanentGold
  522. }};
  523. 免费金币:{{ user.nowFreeGold }};
  524. 任务金币:{{ user.nowTaskGold }})</span>
  525. </el-form-item>
  526. </el-col>
  527. </el-row>
  528. <!-- 第二行精网号 + 当前金币独立行 -->
  529. <el-row>
  530. <el-col :span="9">
  531. <el-form-item label="精网号">
  532. <p>{{ user.jwcode }}</p>
  533. </el-form-item>
  534. </el-col>
  535. <el-col :span="14">
  536. <el-form-item label="充值次数">
  537. <p style="color: #2fa1ff">{{ user.rechargeNum }}</p>
  538. </el-form-item>
  539. </el-col>
  540. </el-row>
  541. <!-- 第三行首次充值日期 + 充值次数 -->
  542. <el-row>
  543. <el-col :span="9">
  544. <el-form-item label="首次充值日期">
  545. <p v-if="user.firstRecharge">
  546. {{ moment(user.firstRecharge).format('YYYY-MM-DD HH:mm:ss') }}
  547. </p>
  548. </el-form-item>
  549. </el-col>
  550. <el-col :span="14">
  551. <el-form-item label="消费次数">
  552. <p style="color: #2fa1ff">{{ user.consumeNum }}</p>
  553. </el-form-item>
  554. </el-col>
  555. </el-row>
  556. <!-- 第四行消费次数 + 所属门店 -->
  557. <el-row>
  558. <el-col :span="9">
  559. <el-form-item label="所属门店">
  560. <p>{{ user.market }}</p>
  561. </el-form-item>
  562. </el-col>
  563. </el-row>
  564. </el-form>
  565. </el-card>
  566. </div>
  567. </div>
  568. </template>
  569. <style scoped>
  570. p {
  571. margin: 0px;
  572. }
  573. .userAndForm {
  574. width: 90%;
  575. display: flex;
  576. align-items: center;
  577. }
  578. .beautiful {
  579. flex: 1;
  580. float: right;
  581. margin-left: 150px;
  582. }
  583. .form-style {
  584. margin-top: 50px;
  585. max-width: 50%;
  586. float: left;
  587. }
  588. .el-form-item {
  589. margin-left: 50px;
  590. }
  591. </style>
  592. <style>
  593. /* 上传图片的格式 */
  594. .avatar-uploader .avatar {
  595. width: 50px;
  596. height: 50px;
  597. display: block;
  598. }
  599. .avatar-uploader .el-upload {
  600. border: 1px dashed var(--el-border-color);
  601. border-radius: 6px;
  602. cursor: pointer;
  603. position: relative;
  604. overflow: hidden;
  605. transition: var(--el-transition-duration-fast);
  606. }
  607. .avatar-uploader .el-upload:hover {
  608. border-color: var(--el-color-primary);
  609. }
  610. .el-icon.avatar-uploader-icon {
  611. font-size: 28px;
  612. color: #8c939d;
  613. width: 50px;
  614. height: 50px;
  615. text-align: center;
  616. }
  617. .form-style2 {
  618. max-width: 60%;
  619. }
  620. </style>