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.

1162 lines
36 KiB

7 months ago
6 months ago
6 months ago
6 months ago
3 months ago
3 months ago
6 months ago
6 months ago
3 months ago
3 months ago
3 months ago
3 months ago
6 months ago
6 months ago
6 months ago
7 months ago
7 months ago
7 months ago
7 months ago
6 months ago
3 months ago
6 months ago
6 months ago
7 months ago
8 months ago
6 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
3 months ago
7 months ago
7 months ago
7 months ago
6 months ago
3 months ago
6 months ago
6 months ago
6 months ago
3 months ago
3 months ago
3 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
3 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
3 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
7 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
7 months ago
6 months ago
7 months ago
7 months ago
6 months ago
7 months ago
6 months ago
7 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
  1. <script setup>
  2. import { onMounted, reactive, ref, watch, computed, nextTick } from "vue";
  3. import { ElIcon, ElMessage } from "element-plus";
  4. import moment from "moment";
  5. import request from "@/util/http.js"
  6. import Cookies from 'js-cookie';
  7. import { useAdminStore } from "@/store/index.js";
  8. import { storeToRefs } from "pinia";
  9. import { WarnTriangleFilled } from "@element-plus/icons-vue";
  10. import dayjs from "dayjs";
  11. // 国际化
  12. import { useI18n } from 'vue-i18n'
  13. const { t } = useI18n()
  14. const adminStore = useAdminStore();
  15. const { adminData, menuTree } = storeToRefs(adminStore);
  16. // 精网号去空格
  17. const trimJwCode = () => {
  18. if (addConsume.value.jwcode) {
  19. // 去除所有空格,并尝试转换为整数
  20. const trimmed = addConsume.value.jwcode.toString().replace(/\s/g, '')
  21. const numeric = Number(trimmed)
  22. // 如果转换为数字成功,保存为数字,否则提示错误
  23. if (!isNaN(numeric)) {
  24. addConsume.value.jwcode = numeric
  25. } else {
  26. ElMessage.error(t('elmessage.limitDigitJwcode'))
  27. }
  28. }
  29. }
  30. //提交禁止重复点击
  31. const addDisabled = ref(false)
  32. // 通过精网号查询用户(客户)信息 表单
  33. const user = ref({
  34. jwcode: null,
  35. name: "",
  36. market: "",
  37. red: null,
  38. historySumGold: null,
  39. historyPermanentGold: null,
  40. historyFreeGold: null,
  41. historyTaskGold: null,
  42. rechargeNum: null,
  43. consumeNum: null,
  44. firstRecharge: "",
  45. nowPermanentGold: null,
  46. nowFreeJune: null,
  47. nowTaskGold: null,
  48. nowFreeDecember: null,
  49. nowFreeGold: null,
  50. nowSumGold: null
  51. })
  52. // 这是添加消费信息的表单(金币)
  53. const addConsume = ref({
  54. // jwcode 是数字
  55. jwcode: null, //精网号
  56. goodsName: "",// 商品名称
  57. price: null, // 原价
  58. sumGold: null, // 消费金币总数
  59. freeGold: null, // 免费金币
  60. permanentGold: null, // 永久金币
  61. taskGold: null, // 任务金币
  62. remark: "",//备注
  63. adminId: null,// 当前管理员id
  64. adminName: adminData.value.adminName,
  65. redMoney: 1 // 是否使用红包:1-使用,0-不使用
  66. })
  67. const Ref = ref(null)
  68. const rules = reactive({
  69. jwcode: [
  70. { required: true, message: t('elmessage.checkJwcode'), trigger: "blur" },
  71. ],
  72. goodsName: [{ required: true, message: t('elmessage.checkGoodsName'), trigger: "change" }],
  73. sumGold: [
  74. { required: true, message: t('elmessage.noEmptySumGold'), trigger: "blur" },
  75. {
  76. validator: (rule, value, callback) => {
  77. // 如果使用了红包,且红包总额抵扣完了原价,允许为0
  78. if (addConsume.value.redMoney === 1) {
  79. const price = Number(addConsume.value.price || 0)
  80. const redAmount = totalRedAmount.value
  81. if (redAmount >= price) {
  82. // 如果红包抵扣完了,允许0
  83. if (parseFloat(value) === 0 || value === 0) {
  84. callback();
  85. return;
  86. }
  87. }
  88. }
  89. // 允许0开头的小数(如0.1)但不允许单独的0
  90. const isValid = /^(0\.\d{1,2})|([1-9]\d*(\.\d{1,2})?)$/.test(value);
  91. if (!isValid) {
  92. callback(new Error(t('elmessage.limitPositiveNumber')));
  93. } else {
  94. callback();
  95. }
  96. },
  97. trigger: "blur"
  98. }
  99. ]
  100. });
  101. // 查询商品的表单
  102. const goods = ref([])
  103. // 输入验证函数
  104. function validateInput() {
  105. const sumGold = parseFloat(addConsume.value.sumGold);
  106. trimJwCode();
  107. if (user.value.jwcode == null) {
  108. ElMessage.warning(t('elmessage.checkUserInfo'));
  109. addConsume.value.sumGold = null;
  110. user.value = {};
  111. return false;
  112. }
  113. /*
  114. // 验证金币数值
  115. if (user.value.jwcode && (isNaN(sumGold) || sumGold <= 0)) {
  116. ElMessage.warning("消费金币总数必须是大于0的数字");
  117. // 将sumGold设置为null
  118. addConsume.value.sumGold = null;
  119. return false;
  120. }
  121. */
  122. // sumGold 补充0(比如.1 为0.1)
  123. if (addConsume.value.sumGold && addConsume.value.sumGold.toString().startsWith('.')) {
  124. addConsume.value.sumGold = '0' + addConsume.value.sumGold;
  125. // ElMessage.info('已自动补充前导0');
  126. }
  127. // 验证金币不能为负数
  128. if (sumGold < 0) {
  129. ElMessage.warning(t('elmessage.limitNegativeNumber'));
  130. addConsume.value.sumGold = null;
  131. return false;
  132. }
  133. // 小数位数限制 2位,整数位数限制 6位
  134. if (addConsume.value.sumGold) {
  135. const sumGoldStr = addConsume.value.sumGold.toString();
  136. // 检查整数部分长度
  137. if (sumGoldStr.includes('.')) {
  138. const integerPart = sumGoldStr.split('.')[0];
  139. if (integerPart.length > 6) {
  140. // 截断整数部分到6位并提示
  141. const truncatedInteger = integerPart.slice(0, 6);
  142. addConsume.value.sumGold = parseFloat(truncatedInteger);
  143. ElMessage.info(t('elmessage.limitSix'));
  144. return; // 直接返回,不再处理小数部分
  145. }
  146. } else {
  147. // 纯整数情况
  148. if (sumGoldStr.length > 6) {
  149. addConsume.value.sumGold = parseFloat(sumGoldStr.slice(0, 6));
  150. ElMessage.info(t('elmessage.limitSix'));
  151. return;
  152. }
  153. }
  154. // 处理小数部分
  155. if (sumGoldStr.includes('.')) {
  156. const decimalPart = sumGoldStr.split('.')[1];
  157. if (decimalPart.length > 2) {
  158. // 截断到两位小数并提示
  159. const truncatedValue = parseFloat(sumGoldStr.slice(0, sumGoldStr.indexOf('.') + 3));
  160. addConsume.value.sumGold = truncatedValue;
  161. ElMessage.info(t('elmessage.limitTwoDecimal'));
  162. }
  163. }
  164. }
  165. // 验证金币总和
  166. const totalAvailableGold = (user.value.nowSumGold)
  167. // 校验消耗金币数是否超过可用金币数
  168. if (user.value.jwcode && sumGold > totalAvailableGold) {
  169. if (addConsume.value.redMoney === 1) {
  170. ElMessage.error(t('elmessage.limitRedAmount'));
  171. // 如果红包额不足,清空已选红包
  172. selectedReds.value = []
  173. } else {
  174. ElMessage.error(t('elmessage.limitExceeded'));
  175. }
  176. // 将sumGold设置为null
  177. addConsume.value.sumGold = null;
  178. return false;
  179. }
  180. return true;
  181. }
  182. // 仅在鼠标离开(blur)时校验:消耗金币数 + 红包抵扣金额 ≥ 原价
  183. function validateRedLimit() {
  184. const sumGold = parseFloat(addConsume.value.sumGold);
  185. const price = Number(addConsume.value.price || 0);
  186. // 优先使用选中的红包总额,如果没有选中(可能是旧逻辑),则使用user.red
  187. const redAmount = (addConsume.value.redMoney == 1 && selectedReds.value.length > 0)
  188. ? totalRedAmount.value
  189. : (addConsume.value.redMoney == 1 ? Number(user.value.red || 0) : 0);
  190. if (!isNaN(price) && price > 0) {
  191. // 如果红包足额,允许sumGold为0
  192. const isCovered = redAmount >= price;
  193. if (isNaN(sumGold) || (sumGold <= 0 && !isCovered)) {
  194. ElMessage.error(t('elmessage.noEmptySumGold'));
  195. return false;
  196. }
  197. // 如果勾选使用红包,才需要校验消耗金币数 + 红包抵扣金额 ≥ 原价
  198. if (addConsume.value.redMoney == 1) {
  199. if (sumGold + redAmount < price) {
  200. ElMessage.error(t('elmessage.limitRedAmount'));
  201. return false;
  202. }
  203. }
  204. }
  205. return true;
  206. }
  207. // 消耗金币计算函数
  208. function calculateCoins(sumGold) {
  209. console.log("消耗金币计算函数:计算金币", sumGold);
  210. const parsedSumGold = parseFloat(sumGold);
  211. if (isNaN(parsedSumGold) || parsedSumGold <= 0 || !user.value.jwcode) {
  212. return { free: 0, permanent: 0, task: 0 };
  213. }
  214. const { nowFreeGold, nowPermanentGold, nowTaskGold } = user.value;
  215. let remaining = parsedSumGold;
  216. let freeUsed = 0, permanentUsed = 0, taskUsed = 0;
  217. // 先消耗免费金币
  218. if (remaining > 0 && nowFreeGold > 0) {
  219. freeUsed = Math.min(parseFloat(nowFreeGold.toFixed(4)), remaining);
  220. remaining = parseFloat((remaining - freeUsed).toFixed(4));
  221. }
  222. // 再消耗永久金币
  223. if (remaining > 0 && nowPermanentGold > 0) {
  224. permanentUsed = Math.min(parseFloat(nowPermanentGold.toFixed(4)), remaining);
  225. remaining = parseFloat((remaining - permanentUsed).toFixed(4));
  226. }
  227. // 最后消耗任务金币
  228. if (remaining > 0 && nowTaskGold > 0) {
  229. const availableTaskGold = parseFloat(nowTaskGold.toFixed(4));
  230. taskUsed = Math.min(availableTaskGold, remaining);
  231. remaining = parseFloat((remaining - taskUsed).toFixed(4));
  232. }
  233. // 更新金币值
  234. addConsume.value.freeGold = freeUsed;
  235. addConsume.value.permanentGold = permanentUsed;
  236. addConsume.value.taskGold = taskUsed;
  237. return { free: freeUsed, permanent: permanentUsed, task: taskUsed };
  238. }
  239. // 用来写的 cookie 的 key
  240. const WriteCookies = ref(null)
  241. // 用来写的 cookie 的 value
  242. const WriteCookiesVale = ref({})
  243. // 用来读的 cookie 的 key
  244. const ReadCookies = ref(null)
  245. // 用来读的 cookie 的 value
  246. const ReadCookiesValue = ref({})
  247. const orderStatus = ref('')
  248. // 这是添加消费信息的接口
  249. const add = async function () {
  250. try {
  251. // 验证输入数据 再验证一次
  252. if (!validateInput()) {
  253. return;
  254. }
  255. // 计算金币使用情况
  256. calculateCoins(addConsume.value.sumGold);
  257. console.log("addConsume.value", addConsume.value)
  258. addDisabled.value = true
  259. // 发送POST请求
  260. const result = await request({
  261. url: "/consume/add",
  262. data: {
  263. jwcode: addConsume.value.jwcode,
  264. adminId: adminData.value.id,
  265. price: Number(addConsume.value.price || 0) * 100,
  266. sumGold: addConsume.value.sumGold * 100,
  267. freeGold: addConsume.value.freeGold * 100,
  268. taskGold: addConsume.value.taskGold * 100,
  269. permanentGold: addConsume.value.permanentGold * 100,
  270. goodsName: addConsume.value.goodsName?.value || addConsume.value.goodsName,
  271. remark: addConsume.value.remark,
  272. adminName: adminData.value.adminName,
  273. redMoney: Number(addConsume.value.redMoney), // 1-使用红包,0-不使用红包
  274. redIds: addConsume.value.redMoney === 1 ? selectedReds.value.map(item => item.id) : [] // 选中的红包ID数组
  275. }
  276. })
  277. addDisabled.value = false
  278. console.log("add请求", result);
  279. // 处理响应
  280. handleResponse(result);
  281. // 重置表单
  282. resetForm();
  283. } catch (error) {
  284. console.error("请求失败", error);
  285. ElMessage.error(t('elmessage.addFailed'));
  286. }
  287. };
  288. // 响应处理函数
  289. function handleResponse(result) {
  290. console.log("响应结果", result)
  291. if (result.code === 200) {
  292. const goodsNameStr = addConsume.value.goodsName?.value || addConsume.value.goodsName
  293. WriteCookies.value = `coinConsume-${addConsume.value.jwcode}-${goodsNameStr}`
  294. //value 为消耗时间
  295. console.log("goodsNameStr add", goodsNameStr)
  296. WriteCookiesVale.value = JSON.stringify({
  297. payTime: moment(result.data.createTime).format("YYYY-MM-DD HH:mm:ss"),
  298. orderCode: result.data.orderCoder,
  299. });
  300. Cookies.set(WriteCookies.value, WriteCookiesVale.value, {expires: 1, path: '/'});
  301. ElMessage.success(t('elmessage.addSuccess'));
  302. console.log("请求成功", result);
  303. } else {
  304. ElMessage.error(result.msg || t('elmessage.addFailedUnknown'));
  305. }
  306. }
  307. // 重置表单函数
  308. function resetForm() {
  309. // 清空表单数据
  310. Ref.value.resetFields();
  311. addConsume.value = {
  312. jwcode: null,
  313. goodsName: "",
  314. sumGold: null,
  315. freeGold: null,
  316. permanentGold: null,
  317. taskGold: null,
  318. remark: "",
  319. adminId: adminData.value.id,
  320. adminName: adminData.value.adminName,
  321. redMoney: 1 // 默认使用红包
  322. }
  323. // 清空红包相关数据
  324. selectedReds.value = []
  325. redList.value = []
  326. console.log("重置表单")
  327. user.value = {
  328. jwcode: null,
  329. name: "",
  330. market: "",
  331. historySumGold: null,
  332. historyPermanentGold: null,
  333. historyFreeGold: null,
  334. historyTaskGold: null,
  335. rechargeNum: null,
  336. consumeNum: null,
  337. firstRecharge: "",
  338. nowPermanentGold: null,
  339. nowFreeJune: null,
  340. nowTaskGold: null,
  341. nowFreeDecember: null,
  342. nowFreeGold: null,
  343. nowSumGold: null
  344. }
  345. if (Ref.value) {
  346. nextTick(() => {
  347. Ref.value.clearValidate()
  348. })
  349. }
  350. }
  351. // 充值对话框显示状态
  352. const ConsumeDialogVisible = ref(false);
  353. // 关闭对话框
  354. const ConsumeDialogVisiblehandleClose = () => {
  355. ConsumeDialogVisible.value = false;
  356. // 重置表单数据
  357. resetForm()
  358. user.value = {}
  359. };
  360. ``
  361. // 确认使用cookie继续充值
  362. const ConsumeDialogVisibleContinue = () => {
  363. ConsumeDialogVisible.value = false;
  364. add();
  365. };
  366. const ConsumeDialogVisibleCancel = () => {
  367. ConsumeDialogVisible.value = false
  368. resetForm()
  369. user.value = {}
  370. };
  371. // 第一次弹窗
  372. // 充值对话框显示状态
  373. const FirstConsumeDialogVisible = ref(false);
  374. // 关闭对话框
  375. const FirstConsumeDialogVisiblehandleClose = () => {
  376. FirstConsumeDialogVisible.value = false;
  377. // 重置表单数据
  378. resetForm()
  379. user.value = {}
  380. };
  381. // 第一次消耗
  382. const FirstConsumeDialogVisibleContinue = () => {
  383. FirstConsumeDialogVisible.value = false;
  384. add();
  385. };
  386. const FirstConsumeDialogVisibleCancel = () => {
  387. FirstConsumeDialogVisible.value = false
  388. resetForm()
  389. user.value = {}
  390. };
  391. // 实际执行充值操作
  392. // const proceedWithConsume = () => {
  393. // ElMessageBox.confirm('确认购买?')
  394. // .then(() => {
  395. // add();
  396. // console.log('添加成功');
  397. // })
  398. // .catch(() => {
  399. // console.log('取消添加');
  400. // });
  401. // };
  402. const historyOrder = ref({})
  403. // 查询订单号对应的状态
  404. const selectByOrderCode = async function (data) {
  405. try {
  406. const result = await request({
  407. url: '/recharge/selectByOrderCode',
  408. data: data,
  409. headers: {
  410. 'Content-Type': 'text/plain;charset=utf-8'
  411. },
  412. })
  413. if(result.code == 200){
  414. historyOrder.value = result.data
  415. }
  416. } catch (error) {
  417. console.log('请求失败', error)
  418. // 在这里可以处理错误逻辑,比如显示错误提示等
  419. }
  420. }
  421. // 添加前验证
  422. const addBefore = () => {
  423. Ref.value.validate(async (valid) => {
  424. // 验证cookie
  425. if (!valid) {
  426. ElMessage({
  427. type: 'error',
  428. message: t('elmessage.checkInputContent')
  429. });
  430. return;
  431. }
  432. if (!validateInput() || !validateRedLimit()) {
  433. return;
  434. }
  435. ReadCookies.value = `coinConsume-${addConsume.value.jwcode}-${addConsume.value.goodsName.value}`
  436. console.log('ReadCookies',ReadCookies.value);
  437. // 获取cookie
  438. const cookieValue = Cookies.get(ReadCookies.value);
  439. if (cookieValue) {
  440. // 解析为对象
  441. const rechargeInfo = JSON.parse(cookieValue);
  442. console.log('提交时间:', rechargeInfo.payTime);
  443. console.log('订单号:', rechargeInfo.orderCode);
  444. ReadCookiesValue.value = {
  445. ...rechargeInfo,
  446. }
  447. }else{
  448. ReadCookiesValue.value = {}
  449. }
  450. console.log('ReadCookies',ReadCookiesValue.value);
  451. if (Object.keys(ReadCookiesValue.value).length > 0) {
  452. await selectByOrderCode(ReadCookiesValue.value.orderCode)
  453. if(historyOrder.value.type == 1 ){
  454. if(historyOrder.value.auditStatus == 1 ){
  455. if(historyOrder.value.isRefund == 0){
  456. orderStatus.value = '正常'
  457. }else if(historyOrder.value.isRefund == 1){
  458. orderStatus.value = '已退款'
  459. }
  460. }
  461. }
  462. ConsumeDialogVisible.value = true;
  463. } else {
  464. FirstConsumeDialogVisible.value = true;
  465. }
  466. });
  467. };
  468. // 查询客户信息(通过精网号)
  469. const getUser = async function (jwcode) {
  470. try {
  471. // 验证精网号
  472. if (!jwcode) {
  473. ElMessage.warning(t('elmessage.noEmptyJwcode'));
  474. return;
  475. }
  476. // 验证精网号是否为数字
  477. if (!/^\d{1,9}$/.test(jwcode)) {
  478. ElMessage.warning(t('elmessage.limitJwcodeNine'));
  479. resetForm()
  480. return;
  481. }
  482. // 发送POST请求
  483. const result = await request({
  484. url: "/user/selectUser",
  485. data: { jwcode }
  486. });
  487. console.log("请求成功", result);
  488. const { code, data } = result;
  489. const region = data && data.market;
  490. if (data && Object.keys(data).length > 0) {
  491. // 查询成功后,重置表单(保留精网号),防止上一个用户的数据干扰
  492. const currentJwcode = addConsume.value.jwcode;
  493. resetForm();
  494. addConsume.value.jwcode = currentJwcode;
  495. // 处理用户数据
  496. user.value = {
  497. ...data,
  498. // 统一处理所有黄金数值,除以100
  499. nowPermanentGold: data.nowPermanentGold,
  500. nowFreeGold: data.nowFreeGold,
  501. nowSumGold: data.nowSumGold,
  502. nowTaskGold: data.nowTaskGold,
  503. nowFreeJune: data.nowFreeJune,
  504. nowFreeDecember: data.nowFreeDecember,
  505. historySumGold: data.historySumGold,
  506. historyPermanentGold: data.historyPermanentGold,
  507. historyFreeGold: data.historyFreeGold,
  508. historyTaskGold: data.historyTaskGold
  509. };
  510. if (code === 0 && region) {
  511. ElMessage.info({
  512. message: t('elmessage.userRegionHint', { region }),
  513. customClass: 'custom-blue-message'
  514. });
  515. return;
  516. }
  517. if (code === 200) {
  518. ElMessage.success(t('elmessage.searchSuccess'));
  519. // 如果当前开启了红包模式,获取红包列表
  520. if (addConsume.value.redMoney === 1) {
  521. getRedList()
  522. }
  523. // 验证输入
  524. validateInput()
  525. } else {
  526. ElMessage.warning(result.msg || t('elmessage.checkQueryParams'));
  527. }
  528. } else {
  529. ElMessage.warning(t('elmessage.noUser'));
  530. user.value.jwcode = null
  531. addConsume.value.jwcode = null
  532. // resetForm(); // 重置表单
  533. }
  534. } catch (error) {
  535. console.error("请求失败", error);
  536. ElMessage.error(t('elmessage.jwcodeError'));
  537. resetForm(); // 重置表单
  538. }
  539. };
  540. // 获取商品信息(三楼接口)
  541. const getGoods = async function () {
  542. try {
  543. // 发送POST请求
  544. const result = await request({
  545. // url: "/product", //
  546. // url: "http://39.101.133.168:8828/live_mall/api/product/all",
  547. url: "https://api.homilychart.com/live_mall/api/product/all",
  548. });
  549. // 将响应结果存储到响应式数据中
  550. console.log("请求成功", result);
  551. goods.value = result.data.map(item => ({
  552. id: item.id,
  553. label: item.name,
  554. value: item.name,
  555. price: item.price
  556. }));
  557. } catch (error) {
  558. console.log("请求失败", error);
  559. // 在这里可以处理错误逻辑,比如显示错误提示等
  560. }
  561. };
  562. /*
  563. ====================监听=================================
  564. */
  565. // 监听消费总金额变化,自动计算三类金币
  566. watch(
  567. () => addConsume.value.sumGold,
  568. (newValue) => {
  569. const parsedNewValue = parseFloat(newValue);
  570. if (!isNaN(parsedNewValue) && parsedNewValue >= 0) {
  571. const { free, permanent, task } = calculateCoins(parsedNewValue);
  572. addConsume.value.freeGold = free;
  573. addConsume.value.permanentGold = permanent;
  574. addConsume.value.taskGold = task;
  575. } else {
  576. addConsume.value.freeGold = null;
  577. addConsume.value.permanentGold = null;
  578. addConsume.value.taskGold = null;
  579. }
  580. }
  581. );
  582. // 监听商品选择,自动展示原价
  583. const handleGoodsChange = (val) => {
  584. // val 现在是整个对象
  585. if (val) {
  586. addConsume.value.price = Number(val.price || 0) || null;
  587. } else {
  588. addConsume.value.price = null;
  589. }
  590. }
  591. // 监听商品选择,自动展示原价
  592. // watch(
  593. // () => addConsume.value.goodsName,
  594. // (newGoods) => {
  595. // if (newGoods && typeof newGoods === 'object') {
  596. // addConsume.value.price = Number(newGoods.price || 0) || null;
  597. // } else {
  598. // addConsume.value.price = null;
  599. // }
  600. // }
  601. // );
  602. /*
  603. ====================红包逻辑=================================
  604. */
  605. // 红包列表
  606. const redList = ref([])
  607. // 选中的红包
  608. const selectedReds = ref([])
  609. // 获取红包列表
  610. const getRedList = async () => {
  611. // 必须先有查询到的用户信息
  612. if (!user.value.jwcode) return
  613. try {
  614. const result = await request({
  615. url: "/Temporary/RedList",
  616. data: { jwcode: user.value.jwcode }
  617. })
  618. console.log("红包列表", result)
  619. if (result.code === 200) {
  620. redList.value = result.data || []
  621. }
  622. } catch (error) {
  623. console.error("获取红包列表失败", error)
  624. }
  625. }
  626. // 监听是否使用红包
  627. watch(() => addConsume.value.redMoney, (val) => {
  628. // 切换模式时,清除sumGold的验证状态,防止之前的错误提示残留
  629. if (Ref.value) {
  630. Ref.value.clearValidate('sumGold')
  631. }
  632. if (val === 1) {
  633. if (!user.value.jwcode) {
  634. if (addConsume.value.jwcode) {
  635. ElMessage.warning(t('elmessage.checkUserInfo'))
  636. }
  637. redList.value = []
  638. selectedReds.value = []
  639. addConsume.value.sumGold = null
  640. return
  641. }
  642. selectedReds.value = []
  643. getRedList()
  644. // 初始消耗金币等于原价(未选红包时)
  645. if (addConsume.value.price) {
  646. addConsume.value.sumGold = addConsume.value.price
  647. } else {
  648. addConsume.value.sumGold = null
  649. }
  650. } else {
  651. selectedReds.value = []
  652. addConsume.value.sumGold = null
  653. }
  654. })
  655. // 计算已选红包总额
  656. const totalRedAmount = computed(() => {
  657. return selectedReds.value.reduce((sum, item) => {
  658. // 根据实际接口返回,使用 discount 字段
  659. const amount = Number(item.discount || 0)
  660. return sum + amount
  661. }, 0)
  662. })
  663. // 监听选中红包变化,计算剩余金币
  664. watch(selectedReds, () => {
  665. if (addConsume.value.redMoney === 1) {
  666. const price = Number(addConsume.value.price || 0)
  667. const redTotal = totalRedAmount.value
  668. // 剩余需要消耗的金币
  669. let remaining = price - redTotal
  670. if (remaining < 0) remaining = 0
  671. addConsume.value.sumGold = remaining
  672. }
  673. }, { deep: true })
  674. // 判断红包选项是否禁用
  675. const isRedOptionDisabled = (item) => {
  676. // 如果已经选中,不禁用(允许取消)
  677. if (selectedReds.value.some(r => r.id === item.id)) return false
  678. const price = Number(addConsume.value.price || 0)
  679. // 如果当前总额已经大于等于原价,则不能再选新的
  680. if (totalRedAmount.value >= price) return true
  681. return false
  682. }
  683. // 监听原价变化(比如换了商品),如果在红包模式下,需要重新计算
  684. watch(() => addConsume.value.price, (newPrice) => {
  685. if (addConsume.value.redMoney === 1) {
  686. const redTotal = totalRedAmount.value
  687. let remaining = (newPrice || 0) - redTotal
  688. if (remaining < 0) remaining = 0
  689. addConsume.value.sumGold = remaining
  690. }
  691. })
  692. /*
  693. ====================挂载=================================
  694. */
  695. // 挂载
  696. onMounted(async function () {
  697. await getGoods()
  698. console.log('adminData', adminData.value)
  699. })
  700. </script>
  701. <template>
  702. <div class="father1">
  703. <div class="left">
  704. <el-form :model="addConsume" ref="Ref" :rules="rules" style="min-width: 600px;" class="add-form"
  705. label-width="auto" label-position="right">
  706. <el-form-item prop="jwcode" :label="t('common_add.jwcode')" style="margin-top: 50px">
  707. <el-input v-model="addConsume.jwcode" style="width: 200px;" @keyup.enter="getUser(addConsume.jwcode)" />
  708. <el-button type="primary" @click="getUser(addConsume.jwcode)" style="margin-left: 20px">
  709. {{ t('common.search') }}
  710. </el-button>
  711. </el-form-item>
  712. <el-form-item prop="goodsName" :label="t('common_add.goodsName')">
  713. <el-select v-model="addConsume.goodsName" :placeholder="t('common_add.goodsNamePlaceholder')"
  714. style="width: 200px" clearable filterable @change="handleGoodsChange" value-key="id">
  715. <el-option v-for="(item, index) in goods" :key="item.id" :label="item.label" :value="item" />
  716. </el-select>
  717. </el-form-item>
  718. <el-form-item prop="price" :label="t('common_add.price')">
  719. <el-input v-model="addConsume.price" style="width: 120px" disabled />
  720. </el-form-item>
  721. <!-- <el-form-item prop="sumGold" :label="t('common_add.consumeTotalGold')">
  722. <el-input v-model="addConsume.sumGold" style="width: 120px"
  723. @blur="validateRedLimit()" />
  724. </el-form-item> -->
  725. <el-form-item prop="sumGold" :label="t('common_add.consumeTotalGold')">
  726. <div style="display: flex; align-items: flex-start;">
  727. <el-input v-model="addConsume.sumGold" style="width: 120px; flex-shrink: 0;" @blur="validateRedLimit()"
  728. :disabled="addConsume.redMoney === 1" />
  729. <div style="display: flex; flex-wrap: wrap; margin-left: 10px;">
  730. <el-radio v-model="addConsume.redMoney" :label="1">{{ t('common_add.使用红包') }}</el-radio>
  731. <el-radio v-model="addConsume.redMoney" :label="0">{{ t('common_add.不使用红包') }}</el-radio>
  732. </div>
  733. </div>
  734. </el-form-item>
  735. <el-form-item v-if="addConsume.redMoney === 1" :label="t('common_add.选择红包')">
  736. <el-select
  737. v-model="selectedReds"
  738. multiple
  739. :placeholder="t('common_add.请选择红包')"
  740. style="width: 200px"
  741. value-key="id"
  742. collapse-tags
  743. >
  744. <el-option
  745. v-for="item in redList"
  746. :key="item.id"
  747. :label="`${item.title} (${item.discount})`"
  748. :value="item"
  749. :disabled="isRedOptionDisabled(item)"
  750. />
  751. </el-select>
  752. <span style="margin-left: 10px; color: #666;">
  753. {{ t('common_add.已抵扣') }}: {{ totalRedAmount }}
  754. </span>
  755. </el-form-item>
  756. <!-- 三类金币自动计算禁用状态不可编辑 -->
  757. <el-form-item prop="permanentGold" :label="t('common_add.permanentGold')">
  758. <el-input v-model="addConsume.permanentGold" disabled style="width: 120px">
  759. <template #default="scope">{{ scope.row.permanentGold }}</template>
  760. </el-input>
  761. <p style="margin-right: 0px">&nbsp;&nbsp;{{ $t('common.个') }}</p>
  762. </el-form-item>
  763. <el-form-item prop="freeCoin" :label="t('common_add.freeGold')">
  764. <el-input disabled v-model="addConsume.freeGold" style="width: 120px" />
  765. <p style="margin-right: 0px">&nbsp;&nbsp;{{ $t('common.个') }}</p>
  766. </el-form-item>
  767. <el-form-item prop="taskGold" :label="t('common_add.taskGold')">
  768. <el-input disabled v-model="addConsume.taskGold" style="width: 120px" />
  769. <p style="margin-right: 20px">&nbsp;&nbsp;{{ $t('common.个') }}</p>
  770. </el-form-item>
  771. <el-form-item prop="remark" :label="t('common_add.remark')">
  772. <el-input v-model="addConsume.remark" style="width: 250px" :rows="4" maxlength="100" show-word-limit
  773. type="textarea" />
  774. </el-form-item>
  775. <el-button type="success" @click="resetForm()" style="margin-left: 200px;margin-top:10px">{{ t('common.reset')
  776. }}</el-button>
  777. <el-button type="primary" :disabled="addDisabled" @click="addBefore" style="margin-top:10px">{{
  778. t('common.submit')
  779. }}</el-button>
  780. </el-form>
  781. </div>
  782. <div class="right">
  783. <!-- 客户信息栏 -->
  784. <el-card v-if="user.jwcode" style="width: 800px; float: right" class="customer-info">
  785. <el-form :model="user" label-width="auto" style="max-width: 1200px" label-position="left">
  786. <el-text size="large" style="margin-left: 20px">{{ $t('common_add_user.customerInfo') }}</el-text>
  787. <!-- 第一行姓名 + 历史金币 -->
  788. <el-row style="margin-top: 20px">
  789. <el-col :span="9">
  790. <el-form-item label-width="120px" :label="$t('common_add_user.name')">
  791. <p>{{ user.name }}</p>
  792. </el-form-item>
  793. </el-col>
  794. <el-col :span="14">
  795. <el-form-item :label="$t('common_add_user.currentGoldCoinTotal')" style="width: 500px">
  796. <span style="color: #2fa1ff; margin-right: 5px" v-if="user.nowSumGold !== undefined">{{
  797. user.nowSumGold
  798. }}</span>
  799. </el-form-item>
  800. <!-- 金币详情独立显示 -->
  801. <el-form-item style="margin-top: -23px"> <!-- 负边距减少间距 -->
  802. <span style="color: #b1b1b1; margin-left: 0px" v-if="user.nowPermanentGold !== undefined">({{
  803. $t('common_add_user.permanentGold') }}:{{
  804. user.nowPermanentGold
  805. }};
  806. {{ $t('common_add_user.freeGold') }}:{{ user.nowFreeGold }};
  807. {{ $t('common_add_user.taskGold') }}:{{ user.nowTaskGold }})</span>
  808. </el-form-item>
  809. </el-col>
  810. </el-row>
  811. <!-- 第二行精网号 + 当前金币独立行 -->
  812. <el-row>
  813. <el-col :span="9">
  814. <el-form-item label-width="120px" :label="$t('common_add_user.jwcode')">
  815. <p>{{ user.jwcode }}</p>
  816. </el-form-item>
  817. </el-col>
  818. <el-col :span="14">
  819. <el-form-item :label="$t('common_add_user.consumptionTimes')">
  820. <p style="color: #2fa1ff">{{ user.consumeNum }} </p>
  821. </el-form-item>
  822. <el-form-item style="margin-top: -23px"> <!-- 负边距减少间距 -->
  823. <p style="font-size: small; color: #b1b1b1">({{ $t('common_add_user.onlyStatisticsDataAfter20250101')
  824. }})
  825. </p>
  826. </el-form-item>
  827. </el-col>
  828. </el-row>
  829. <!-- 第三行首次充值日期 + 充值次数 -->
  830. <!-- <el-row >
  831. <el-col :span="9">
  832. <el-form-item label="首次充值日期">
  833. <p v-if="user.firstRecharge">
  834. {{ moment(user.firstRecharge).format('YYYY-MM-DD HH:mm:ss') }}
  835. </p>
  836. </el-form-item>
  837. </el-col>
  838. </el-row> -->
  839. <!-- 第四行消费次数 + 所属门店 -->
  840. <el-row>
  841. <el-col :span="9">
  842. <el-form-item :label="$t('common_add_user.store')">
  843. <p>{{ user.market }}</p>
  844. </el-form-item>
  845. </el-col>
  846. <el-col :span="14">
  847. <el-form-item :label="$t('common_add_user.maxReductionAmount')">
  848. <p style="color: #2fa1ff">{{ user.red }} </p>
  849. </el-form-item>
  850. </el-col>
  851. </el-row>
  852. </el-form>
  853. </el-card>
  854. <el-dialog v-model="FirstConsumeDialogVisible" :title="$t('common_add.operationConfirm')"
  855. :before-close="FirstConsumeDialogVisiblehandleClose" :close-on-click-modal="false" width="480px">
  856. <!-- 内容整体居中且收窄 -->
  857. <div class="confirm-body">
  858. <!-- 用户信息 -->
  859. <div>
  860. <div class="field-label">{{ $t('common_add.userInfo') }}</div>
  861. <el-input :model-value="user.jwcode + (user.name ? '' + user.name + '' : '')" disabled />
  862. </div>
  863. <!-- 商品名称 -->
  864. <div class="field">
  865. <div class="field-label">{{ $t('common_add.goodsName') }}</div>
  866. <el-input v-model="addConsume.goodsName.value" disabled />
  867. </div>
  868. <!--金币总数 -->
  869. <div class="field">
  870. <div class="field-label">{{ $t('common_add.totalGold') }}</div>
  871. <el-input v-model="addConsume.sumGold" disabled />
  872. </div>
  873. <!-- 金币详细信息同一行左右排列 -->
  874. <el-row :gutter="20" class="coins-row">
  875. <el-col :span="8">
  876. <div class="field">
  877. <div class="field-label">{{ $t('common_add.permanentGold') }}</div>
  878. <el-input v-model="addConsume.permanentGold" disabled />
  879. </div>
  880. </el-col>
  881. <el-col :span="8">
  882. <div class="field">
  883. <div class="field-label">{{ $t('common_add.freeGold') }}</div>
  884. <el-input v-model="addConsume.freeGold" disabled />
  885. </div>
  886. </el-col>
  887. <el-col :span="8">
  888. <div class="field">
  889. <div class="field-label">{{ $t('common_add.taskGold') }}</div>
  890. <el-input v-model="addConsume.taskGold" disabled />
  891. </div>
  892. </el-col>
  893. </el-row>
  894. <div class="field">
  895. <div class="field-label">{{ $t('common_add.remark') }}</div>
  896. <el-input v-model="addConsume.remark" disabled />
  897. </div>
  898. </div>
  899. <!-- 底部按钮居中 -->
  900. <template #footer>
  901. <div class="dialog-footer-center">
  902. <el-button @click="FirstConsumeDialogVisibleCancel">{{ $t('common.cancel') }}</el-button>
  903. <el-button type="primary" @click="FirstConsumeDialogVisibleContinue">{{ $t('common.confirm') }}</el-button>
  904. </div>
  905. </template>
  906. </el-dialog>
  907. <el-dialog v-model="ConsumeDialogVisible" :title="$t('common_add.operationConfirm')"
  908. :before-close="ConsumeDialogVisiblehandleClose" :close-on-click-modal="false" width="480px">
  909. <!-- 内容整体居中且收窄 -->
  910. <div class="confirm-body">
  911. <!-- 用户信息 -->
  912. <div>
  913. <div class="field-label">{{ $t('common_add.userInfo') }}</div>
  914. <el-input :model-value="user.jwcode + (user.name ? '' + user.name + '' : '')" disabled />
  915. </div>
  916. <!-- 商品名称 -->
  917. <div class="field">
  918. <div class="field-label">{{ $t('common_add.goodsName') }}</div>
  919. <el-input v-model="addConsume.goodsName.value" disabled />
  920. </div>
  921. <!--金币总数 -->
  922. <div class="field">
  923. <div class="field-label">{{ $t('common_add.totalGold') }}</div>
  924. <el-input v-model="addConsume.sumGold" disabled />
  925. </div>
  926. <!-- 金币详细信息同一行左右排列 -->
  927. <el-row :gutter="20" class="coins-row">
  928. <el-col :span="8">
  929. <div class="field">
  930. <div class="field-label">{{ $t('common_add.permanentGold') }}</div>
  931. <el-input v-model="addConsume.permanentGold" disabled />
  932. </div>
  933. </el-col>
  934. <el-col :span="8">
  935. <div class="field">
  936. <div class="field-label">{{ $t('common_add.freeGold') }}</div>
  937. <el-input v-model="addConsume.freeGold" disabled />
  938. </div>
  939. </el-col>
  940. <el-col :span="8">
  941. <div class="field">
  942. <div class="field-label">{{ $t('common_add.taskGold') }}</div>
  943. <el-input v-model="addConsume.taskGold" disabled />
  944. </div>
  945. </el-col>
  946. </el-row>
  947. <!-- 风险提示 -->
  948. <div style="display: flex; align-items: center; margin-top: 20px;">
  949. <el-icon :size="24" color="#FFD700">
  950. <WarnTriangleFilled />
  951. </el-icon>
  952. <p>{{ $t('common_add.promptConsume') }}</p>
  953. </div>
  954. <!-- 记录 + 虚线分隔 -->
  955. <div>
  956. <el-divider border-style="dashed" />
  957. <p>{{ $t('common_add.similarCosumeRecords') }}</p>
  958. · {{ ReadCookiesValue.payTime }} {{ $t('common_add.buy') }} {{ addConsume.goodsName.value }}{{ $t('common_add.orderStatus') }}: {{ orderStatus }}({{
  959. $t('common_add.operator') }}: {{ adminData.adminName }})
  960. </div>
  961. <div style="margin-top: 10px">
  962. <p>{{ $t('common_add.continueOperation') }}</p>
  963. </div>
  964. </div>
  965. <!-- 底部按钮居中 -->
  966. <template #footer>
  967. <div class="dialog-footer-center">
  968. <el-button @click="ConsumeDialogVisibleCancel">{{ $t('common.cancel') }}</el-button>
  969. <el-button type="primary" @click="ConsumeDialogVisibleContinue">{{ $t('common.confirm') }}</el-button>
  970. </div>
  971. </template>
  972. </el-dialog>
  973. </div>
  974. </div>
  975. </template>
  976. <style scoped lang="scss">
  977. p {
  978. margin: 0px;
  979. }
  980. /* 上传图片的格式 */
  981. .avatar-uploader .avatar {
  982. width: 50px;
  983. height: 50px;
  984. display: block;
  985. }
  986. .add-form {
  987. width: 400px;
  988. float: left;
  989. }
  990. /* 标题居中 */
  991. .el-dialog__header {
  992. text-align: center;
  993. }
  994. .confirm-body {
  995. width: 350px;
  996. margin: 0 auto;
  997. }
  998. /* 字段块与标签样式 */
  999. .field {
  1000. margin-bottom: 14px;
  1001. }
  1002. .field-label {
  1003. font-size: 14px;
  1004. color: #606266;
  1005. margin-bottom: 6px;
  1006. }
  1007. /* 金币行紧凑 */
  1008. .coins-row .field {
  1009. margin-bottom: 0;
  1010. }
  1011. /* 底部按钮居中 */
  1012. .dialog-footer-center {
  1013. display: flex;
  1014. justify-content: center;
  1015. gap: 12px;
  1016. }
  1017. .father1 {
  1018. width: 82vw;
  1019. height: 80vh;
  1020. display: flex;
  1021. .left {
  1022. width: 500px;
  1023. float: left;
  1024. display: flex;
  1025. }
  1026. .right {
  1027. flex: 1;
  1028. height: 50vh;
  1029. display: flex;
  1030. align-items: center;
  1031. .customer-info {
  1032. width: 300px;
  1033. margin-left: 20px;
  1034. display: flex;
  1035. justify-content: center;
  1036. align-items: center;
  1037. }
  1038. }
  1039. }
  1040. </style>