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.

927 lines
30 KiB

2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
1 month ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
  1. <script setup>
  2. import { ref, reactive, onMounted, nextTick } from 'vue'
  3. import { useRoute } from 'vue-router'
  4. import { ElMessage, ElMessageBox } from 'element-plus'
  5. import request from '@/util/http.js'
  6. import dayjs from 'dayjs'
  7. import { useI18n } from 'vue-i18n'
  8. import { Moneyfunds, refundOnline, exportFunds } from '@/api/cash/financialAccount.js'
  9. import { useAdminStore } from '@/store/index.js'
  10. import { storeToRefs } from 'pinia'
  11. import _ from 'lodash';
  12. import { normalizePayType } from '@/views/moneyManage/receiveDetail/utils/staticData.js'
  13. const adminStore = useAdminStore()
  14. const { adminData } = storeToRefs(adminStore)
  15. const { t } = useI18n()
  16. const route = useRoute()
  17. const paytypeList = [
  18. t('cash.payMethods.stripe'), // Stripe
  19. t('cash.payMethods.paymentAsia'), // PaymentAsia
  20. t('cash.payMethods.stripe2'), // Stripe2
  21. t('cash.payMethods.ipay88'), // Ipay88
  22. t('cash.payMethods.grabpay'), // Grabpay
  23. t('cash.payMethods.nets'), // Nets
  24. t('cash.payMethods.transfer'), // E-Transfer
  25. t('cash.payMethods.paypal'), // PayPal
  26. t('cash.payMethods.paysolution'), // Paysolution
  27. t('cash.payMethods.bankTransfer'),// 银行转账
  28. t('cash.payMethods.card'), // 刷卡
  29. t('cash.payMethods.cash'), // 现金
  30. t('cash.payMethods.check'), // 支票
  31. ]
  32. const payPlatformOptions = ref([...paytypeList])
  33. const statusOptions = [
  34. { label: t('common_list.received'), value: 4 },
  35. { label: t('common_list.refunded'), value: 6 }
  36. ]
  37. // 地区树
  38. const marketOptions = ref([])
  39. // 查询参数
  40. const queryParams = reactive({
  41. jwcode: '',
  42. markets: [], // 下拉多选
  43. timeRange: [], // [startTime, endTime]
  44. payType: '',
  45. orderCode: '',
  46. statuses: [],
  47. pageNum: 1,
  48. pageSize: 20
  49. })
  50. const total = ref(0)
  51. const tableData = ref([])
  52. const tableRef = ref(null)
  53. const scrollTableTop = () => {
  54. tableRef.value?.setScrollTop?.(0)
  55. }
  56. const loading = ref(false)
  57. // 转换树形结构(参考 coinConsumeDetail.vue)
  58. const transformTree = (nodes) => {
  59. const allChildren = nodes.flatMap(node => node.children || []);
  60. return allChildren.map(child => {
  61. const grandchildren = child.children && child.children.length
  62. ? transformTree([child])
  63. : null;
  64. return {
  65. value: child.id,
  66. label: child.name,
  67. children: grandchildren
  68. };
  69. });
  70. };
  71. // 获取地区数据
  72. const getMarket = async () => {
  73. try {
  74. const result = await request({ url: '/market/selectMarket' });
  75. if (result && result.data) {
  76. marketOptions.value = transformTree(result.data)
  77. }
  78. } catch (error) {
  79. console.error('获取地区失败', error)
  80. }
  81. }
  82. const formatStatuses = (statuses) => {
  83. // 情况1:非数组/空值 → 返回空数组
  84. if (!Array.isArray(statuses)) {
  85. return [];
  86. }
  87. // 情况2:数组中包含 null 或 undefined → 返回空数组
  88. if (statuses.some(item => item === null || item === undefined)) {
  89. return [];
  90. }
  91. // 情况3:正常数组 → 返回原数组
  92. return statuses;
  93. };
  94. // 查询列表
  95. const fetchData = async () => {
  96. loading.value = true
  97. try {
  98. // 构建请求参数
  99. const params = {
  100. pageNum: queryParams.pageNum,
  101. pageSize: queryParams.pageSize,
  102. fundsDTO: {
  103. jwcode: queryParams.jwcode,
  104. localMarket: queryParams.markets,
  105. startTime: queryParams.timeRange?.[0] ? dayjs(queryParams.timeRange[0]).format('YYYY-MM-DD HH:mm:ss') : '',
  106. endTime: queryParams.timeRange?.[1] ? dayjs(queryParams.timeRange[1]).format('YYYY-MM-DD HH:mm:ss') : '',
  107. payType: normalizePayType(queryParams.payType || ''),
  108. orderCode: queryParams.orderCode,
  109. statuses: formatStatuses(queryParams.statuses),
  110. markets: [],
  111. }
  112. }
  113. console.log('查询参数:', params)
  114. const res = await Moneyfunds(params)
  115. if (res.code == 200) {
  116. tableData.value = res.data.list || []
  117. await nextTick()
  118. scrollTableTop()
  119. total.value = res.data.total || 0
  120. loading.value = false
  121. } else {
  122. ElMessage.error(res.msg || t('elmessage.getDataFailed'))
  123. loading.value = false
  124. }
  125. } catch (error) {
  126. console.error(error)
  127. loading.value = false
  128. ElMessage.error(t('elmessage.getDataFailed'))
  129. }
  130. }
  131. const handleSearch = () => {
  132. queryParams.pageNum = 1
  133. fetchData()
  134. }
  135. const handleReset = () => {
  136. queryParams.jwcode = ''
  137. queryParams.markets = []
  138. queryParams.timeRange = null
  139. queryParams.payType = ''
  140. queryParams.orderCode = ''
  141. queryParams.statuses = []
  142. handleSearch()
  143. }
  144. const handlePageSizeChange = (val) => {
  145. queryParams.pageSize = val
  146. fetchData()
  147. }
  148. const handleCurrentChange = (val) => {
  149. queryParams.pageNum = val
  150. fetchData()
  151. }
  152. // 退款操作
  153. const openRefundConfirm = (row) => {
  154. textContent.value = t('common.willRefundOrder') + '?'
  155. refundConfirmDialog.value = true
  156. refundFormData.value = {
  157. ...row,
  158. oldpermanentGold: row.permanentGold,//退款永久金币
  159. oldfreeGold: row.freeGold,//退款免费金币
  160. permanentGold: null,
  161. freeGold: null,
  162. }
  163. console.log(row);
  164. }
  165. const openRefundDialog = () => {
  166. refundDialog.value = true
  167. closeConfirmRefund()
  168. }
  169. const closeConfirmRefund = () => {
  170. refundConfirmDialog.value = false
  171. textContent.value = ''
  172. }
  173. const refundConfirmDialog = ref(false)
  174. const textContent = ref('')
  175. const refundDialog = ref(false)
  176. const refundFormData = ref({})
  177. const resetRefund = () => {
  178. refundFormData.value.refundModel = ''
  179. refundFormData.value.refundReason = ''
  180. refundFormData.value.permanentGold = null
  181. refundFormData.value.freeGold = null
  182. }
  183. const handleRefund = async () => {
  184. try {
  185. if (refundFormData.value.refundModel == 1) {
  186. refundFormData.value.permanentGold = refundFormData.value.oldpermanentGold
  187. refundFormData.value.freeGold = refundFormData.value.oldfreeGold
  188. }
  189. let params = {
  190. jwcode: refundFormData.value.jwcode,
  191. name: refundFormData.value.name,
  192. market: refundFormData.value.marketName,
  193. submitterMarket: adminData.value.markets,
  194. remark: refundFormData.value.remark,
  195. originalOrderId: refundFormData.value.id,
  196. refundReason: refundFormData.value.refundReason,
  197. refundModel: refundFormData.value.refundModel,
  198. orderCode: refundFormData.value.orderCode,
  199. submitterId: adminData.value.id,
  200. submitterMarket: adminData.value.markets,
  201. permanentGold: (refundFormData.value.permanentGold) * 100 || 0,
  202. handlingCharge: refundFormData.value.handlingCharge == null ? null : refundFormData.value.handlingCharge * 100,
  203. freeGold: (refundFormData.value.freeGold) * 100 || 0,
  204. }
  205. console.log('这是退款参数:', params);
  206. const res = await refundOnline(params)
  207. if (res.code == 200) {
  208. refundDialog.value = false
  209. fetchData()
  210. } else {
  211. ElMessage.error(res.msg || '退款失败')
  212. }
  213. } catch (error) {
  214. console.error(error)
  215. }
  216. }
  217. // ==================== 导出相关逻辑 ====================
  218. const exportListVisible = ref(false)
  219. const exportList = ref([])
  220. const exportListLoading = ref(false)
  221. // 导出Excel
  222. const handleExport = async () => {
  223. const formatStatuses = (statuses) => {
  224. // 情况1:非数组/空值 → 返回空数组
  225. if (!Array.isArray(statuses)) {
  226. return [];
  227. }
  228. // 情况2:数组中包含 null 或 undefined → 返回空数组
  229. if (statuses.some(item => item === null || item === undefined)) {
  230. return [];
  231. }
  232. // 情况3:正常数组 → 返回原数组
  233. return statuses;
  234. };
  235. try {
  236. const params = {
  237. pageNum: queryParams.pageNum,
  238. pageSize: queryParams.pageSize,
  239. fundsDTO: {
  240. jwcode: queryParams.jwcode,
  241. localMarket: queryParams.markets,
  242. startTime: queryParams.timeRange?.[0] ? dayjs(queryParams.timeRange[0]).format('YYYY-MM-DD HH:mm:ss') : '',
  243. endTime: queryParams.timeRange?.[1] ? dayjs(queryParams.timeRange[1]).format('YYYY-MM-DD HH:mm:ss') : '',
  244. payType: normalizePayType(queryParams.payType || ''),
  245. orderCode: queryParams.orderCode,
  246. statuses: formatStatuses(queryParams.statuses),
  247. markets: [],
  248. }
  249. }
  250. // TODO: 确认导出接口 URL
  251. const res = await exportFunds(params)
  252. if (res.code == 200) {
  253. console.log('导出参数', params)
  254. ElMessage.success(t('elmessage.exportSuccess'))
  255. }
  256. } catch (error) {
  257. console.error(error)
  258. ElMessage.error(t('elmessage.exportError'))
  259. }
  260. }
  261. // 打开导出列表弹窗
  262. const openExportList = () => {
  263. getExportList()
  264. exportListVisible.value = true
  265. }
  266. // 获取导出列表
  267. const getExportList = async () => {
  268. exportListLoading.value = true
  269. try {
  270. const result = await request({ url: '/export/export' })
  271. if (result.code === 200) {
  272. const filteredData = result.data.filter(item => item.type == 15);
  273. exportList.value = filteredData || []
  274. } else {
  275. ElMessage.error(result.msg || t('elmessage.getExportListError'))
  276. }
  277. } catch (error) {
  278. console.error('获取导出列表出错:', error)
  279. ElMessage.error(t('elmessage.getExportListError'))
  280. } finally {
  281. exportListLoading.value = false
  282. }
  283. }
  284. // 下载导出文件
  285. const downloadExportFile = (item) => {
  286. if (item.state === 2) {
  287. const link = document.createElement('a')
  288. link.href = item.url
  289. link.download = item.fileName
  290. link.click()
  291. } else {
  292. ElMessage.warning(t('elmessage.exportingInProgress'))
  293. }
  294. }
  295. // 根据状态返回对应的标签类型
  296. const getTagType = (state) => {
  297. switch (state) {
  298. case 0: return 'info';
  299. case 1: return 'primary';
  300. case 2: return 'success';
  301. case 3: return 'danger';
  302. default: return 'info';
  303. }
  304. }
  305. // 根据状态返回对应的标签文案
  306. const getTagText = (state) => {
  307. switch (state) {
  308. case 0: return t('elmessage.pendingExecution');
  309. case 1: return t('elmessage.executing');
  310. case 2: return t('elmessage.executed');
  311. case 3: return t('elmessage.errorExecution');
  312. default: return t('elmessage.unknownStatus');
  313. }
  314. }
  315. const throttledsubmitRefund = _.throttle(handleRefund, 5000, {
  316. trailing: false
  317. })
  318. // 递归查找地区ID
  319. // normalizeMarketLabel 标准化地区名称,用于对比匹配
  320. const normalizeMarketLabel = (value) => {
  321. return String(value ?? '')
  322. .trim()
  323. .toLowerCase()
  324. .replace(/[\s_-]+/g, '')
  325. }
  326. // 传入的这两个参数对比,是否有匹配的地区ID
  327. const findValueByLabel = (options, label) => {
  328. // option和label都调用normalizeMarketLabel函数
  329. const normalizedLabel = normalizeMarketLabel(label)
  330. for (const option of options) {
  331. if (normalizeMarketLabel(option.label) === normalizedLabel) {
  332. return option.value
  333. }
  334. if (option.children && option.children.length) {
  335. const found = findValueByLabel(option.children, label)
  336. if (found) return found
  337. }
  338. }
  339. return null
  340. }
  341. onMounted(async () => {
  342. await getMarket()
  343. // 处理从工作台跳转过来的地区参数
  344. // 如果出现URL中的?region=a&region=b 这种重复key,router会解析为['a','b'], 取第一个地区ID
  345. const regionName = Array.isArray(route.query.region) ? route.query.region[0] : route.query.region
  346. if (regionName && marketOptions.value.length) {
  347. const matchedId = findValueByLabel(marketOptions.value, regionName)
  348. if (matchedId) {
  349. // el-cascader 绑定的 markets 是数组
  350. queryParams.markets = [matchedId]
  351. }
  352. }
  353. fetchData()
  354. })
  355. </script>
  356. <template>
  357. <div class="cash-flow-container">
  358. <!-- 搜索区域 -->
  359. <el-card class="search-card">
  360. <div class="search-bar">
  361. <!-- 第一行 -->
  362. <div class="search-row">
  363. <div class="search-item">
  364. <span class="label">{{ t('common.jwcode') }}</span>
  365. <el-input v-model="queryParams.jwcode" :placeholder="t('common.jwcodePlaceholder')" clearable />
  366. </div>
  367. <div class="search-item">
  368. <span class="label">{{ t('common.market') }}</span>
  369. <!-- 下拉多选使用 el-cascader 匹配地区树结构 -->
  370. <el-cascader v-model="queryParams.markets" :options="marketOptions"
  371. :props="{ multiple: true, emitPath: false }" collapse-tags collapse-tags-tooltip
  372. :placeholder="t('common.marketPlaceholder')" clearable style="width: 220px;" />
  373. </div>
  374. <div class="search-item">
  375. <span class="label">{{ t('common.payPlatform1') }}</span>
  376. <el-select v-model="queryParams.payType" :placeholder="t('common.payPlatformPlaceholder1')" clearable>
  377. <el-option v-for="item in payPlatformOptions" :key="item" :label="item" :value="item" />
  378. </el-select>
  379. </div>
  380. <div class="search-item">
  381. <span class="label">{{ t('common.status') }}</span>
  382. <el-select v-model="queryParams.statuses[0]" :placeholder="t('common.statusPlaceholder')" clearable>
  383. <el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
  384. </el-select>
  385. </div>
  386. <div class="search-item">
  387. <span class="label">{{ t('common.orderNo') }}</span>
  388. <el-input v-model="queryParams.orderCode" :placeholder="t('common.orderNoPlaceholder')" clearable />
  389. </div>
  390. <div class="search-item" style="width: auto;">
  391. <span class="label">{{ t('common.payTime2') }}</span>
  392. <el-date-picker v-model="queryParams.timeRange" type="datetimerange" :range-separator="t('common.to')"
  393. :start-placeholder="t('common.startTime')" :end-placeholder="t('common.endTime')"
  394. :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" style="width: 350px;" />
  395. </div>
  396. <div class="search-btn-group">
  397. <el-button type="primary" @click="handleSearch">{{ t('common.search') }}</el-button>
  398. <el-button type="primary" @click="handleExport">{{ t('common.exportExcel') }}</el-button>
  399. <el-button type="primary" @click="openExportList">{{ t('common.viewExportList') }}</el-button>
  400. <el-button type="success" @click="handleReset">{{ t('common.reset') }}</el-button>
  401. </div>
  402. </div>
  403. </div>
  404. </el-card>
  405. <!-- 表格区域 -->
  406. <el-card class="table-card">
  407. <el-table ref="tableRef" :data="tableData" v-loading="loading" style="width: 100%; flex: 1;"
  408. :cell-style="{ textAlign: 'center' }"
  409. :header-cell-style="{ background: '#F3FAFE', color: '#333', textAlign: 'center' }">
  410. <el-table-column type="index" :label="t('common_list.id')" width="60" align="center" fixed="left">
  411. <template #default="scope">
  412. <span>{{ scope.$index + 1 + (queryParams.pageNum - 1) * queryParams.pageSize }}</span>
  413. </template>
  414. </el-table-column>
  415. <el-table-column prop="jwcode" :label="t('common_list.jwcode')" width="120" fixed="left" />
  416. <el-table-column prop="name" :label="t('common_list.name')" width="150" show-overflow-tooltip />
  417. <el-table-column prop="marketName" :label="t('common_list.market')" width="120" show-overflow-tooltip />
  418. <el-table-column prop="orderCode" :label="t('common_list.orderCode')" width="280" show-overflow-tooltip />
  419. <el-table-column prop="paymentAmount" :label="t('common_list.payAmount')" width="150" align="right">
  420. <!-- <template #default="{ row }">
  421. {{ row.paymentAmount }} {{ row.paymentCurrency }}
  422. </template> -->
  423. </el-table-column>
  424. <el-table-column prop="paymentCurrencyName" :label="t('common_list.payCurrency')" width="120"
  425. show-overflow-tooltip />
  426. <el-table-column prop="receivedAmount" :label="t('common_list.receiveAmount')" width="150" align="right">
  427. <!-- <template #default="{ row }">
  428. {{ row.receivedAmount }} {{ row.receivedCurrency }}
  429. </template> -->
  430. </el-table-column>
  431. <el-table-column prop="receivedCurrencyName" :label="t('common_list.receiveCurrency')" width="120"
  432. show-overflow-tooltip />
  433. <el-table-column prop="handlingCharge" :label="t('common_list.fee')" width="100" align="right" />
  434. <el-table-column prop="payType" :label="t('common_list.payModel')" width="120" align="center" />
  435. <el-table-column prop="payTime" :label="t('common_list.payTime2')" width="180" align="center" />
  436. <el-table-column prop="status" :label="t('common_list.status')" width="120" align="center" fixed="right">
  437. <template #default="{ row }">
  438. <div style="display: flex; align-items: center;">
  439. <el-tag :type="row.status === 4 ? 'success' : 'warning'" effect="plain">
  440. {{ row.status === 4 ? t('common_list.received') : t('common_list.refunded') }}
  441. </el-tag>
  442. <el-popover trigger="hover" placement="top" popper-class="refund-popover" width="auto"
  443. v-if="row.status === 6">
  444. <div class="popover-content">
  445. <div class="popover-title">{{ t('common_list.refundDetail') }}</div>
  446. <div class="popover-item">
  447. <span class="label">{{ t('common_list.refundAmount') }}</span>
  448. <span class="value">{{ row.refundAmount || '-' }}</span>
  449. </div>
  450. <div class="popover-item">
  451. <span class="label">{{ t('common_list.refundCurrency') }}</span>
  452. <span class="value">{{ row.refundCurrency || '-' }}</span>
  453. </div>
  454. </div>
  455. <template #reference>
  456. <img @click.stop src="@/assets/SvgIcons/consume.svg"
  457. style="width: 15px; height: 15px; margin-left: 5px; cursor: pointer; display: inline-block;">
  458. </template>
  459. </el-popover>
  460. </div>
  461. </template>
  462. </el-table-column>
  463. <el-table-column :label="t('common_list.operation')" width="100" fixed="right" align="center">
  464. <template #default="{ row }">
  465. <el-button v-if="row.orderCode.slice(0, 4) == 'GOLD' && row.status === 4" type="danger" link size="small"
  466. @click="openRefundConfirm(row)">
  467. {{ t('common_list.refund') }}
  468. </el-button>
  469. </template>
  470. </el-table-column>
  471. </el-table>
  472. <!-- 分页 -->
  473. <div class="pagination-container">
  474. <el-pagination background layout="total, sizes, prev, pager, next, jumper" :total="total"
  475. :current-page="queryParams.pageNum" :page-size="queryParams.pageSize" :page-sizes="[10, 20, 50, 100]"
  476. @size-change="handlePageSizeChange" @current-change="handleCurrentChange" />
  477. </div>
  478. </el-card>
  479. <!-- 导出列表弹窗 -->
  480. <el-dialog v-model="exportListVisible" :title="t('common_export.exportList')" width="80%">
  481. <el-table :data="exportList" style="width: 100% ;height: 60vh;" :loading="exportListLoading">
  482. <el-table-column prop="fileName" :label="t('common_export.fileName')" />
  483. <el-table-column prop="state" :label="t('common_export.status')">
  484. <template #default="scope">
  485. <el-tag :type="getTagType(scope.row.state)" :effect="scope.row.state === 3 ? 'light' : 'plain'">
  486. {{ getTagText(scope.row.state) }}
  487. </el-tag>
  488. </template>
  489. </el-table-column>
  490. <el-table-column prop="createTime" :label="t('common_export.createTime')">
  491. <template #default="scope">
  492. {{ dayjs(scope.row.createTime).format('YYYY-MM-DD HH:mm:ss') }}
  493. </template>
  494. </el-table-column>
  495. <el-table-column :label="t('common_export.operation')">
  496. <template #default="scope">
  497. <el-button type="primary" size="small" @click="downloadExportFile(scope.row)"
  498. :disabled="scope.row.state !== 2">
  499. {{ t('common_export.download') }}
  500. </el-button>
  501. </template>
  502. </el-table-column>
  503. </el-table>
  504. <template #footer>
  505. <div class="dialog-footer">
  506. <el-button text @click="exportListVisible = false">{{ t('common_export.close') }}</el-button>
  507. </div>
  508. </template>
  509. </el-dialog>
  510. <div class="recallDialog" v-show="refundConfirmDialog">
  511. <div class="close">
  512. <button @click="closeConfirmRefund" class="Btn">{{ t('common.close') }}</button>
  513. </div>
  514. <div class="text">
  515. <text class="txt">{{ textContent }}</text>
  516. </div>
  517. <div class="cancle">
  518. <button @click="closeConfirmRefund" class="Btn">{{ t('common.cancel') }}</button>
  519. </div>
  520. <div class="confirm">
  521. <button @click="openRefundDialog" class="Btn">{{ t('common.confirm') }}</button>
  522. </div>
  523. </div>
  524. <el-dialog v-model="refundDialog" :title="t('common_add.refund')" class="refundDialog" overflow draggable
  525. style="width: 40vw;" :before-close="closeRefundForm">
  526. <div style="display: flex;">
  527. <div class="left">
  528. <div class="add-item">
  529. <el-text style="width:4vw;">{{ t('common_add.jwcode') }}</el-text>
  530. <el-input v-model="refundFormData.jwcode" style="width:10vw;" disabled />
  531. </div>
  532. <div class="add-item">
  533. <el-text style="width:4vw;">{{ t('common_add.customerName') }}</el-text>
  534. <el-input v-model="refundFormData.name" style="width:10vw;" disabled />
  535. </div>
  536. <div class="add-item">
  537. <el-text style="width:4vw;">{{ t('common_add.market') }}</el-text>
  538. <el-input v-model="refundFormData.marketName" style="width:10vw;" disabled />
  539. </div>
  540. <div class="add-item">
  541. <el-text style="width:4vw;">{{ t('common_add.activity') }}</el-text>
  542. <el-input v-model="refundFormData.activity" style="width:10vw;" disabled />
  543. </div>
  544. <div class="add-item">
  545. <el-text style="width:4vw;">{{ t('common_add.productName') }}</el-text>
  546. <el-input v-model="refundFormData.goodsName" style="width:10vw;" disabled />
  547. </div>
  548. <div style="display: flex; margin-bottom: 10px;">
  549. <div style=" display: flex; align-items: center;justify-content: center; ">
  550. <span style="color: #999999; white-space: nowrap;">{{ t('common_add.permanentGold')
  551. }}</span>
  552. <el-input style="padding-right: 10px; height: 30px; width: 70px;"
  553. v-model="refundFormData.oldpermanentGold" disabled />
  554. </div>
  555. <div style=" display: flex; align-items: center;justify-content: center; ">
  556. <span style="color: #999999; white-space: nowrap;">{{ t('common_add.freeGold') }}</span>
  557. <el-input style="padding-right: 10px; height: 30px; width: 70px;" v-model="refundFormData.oldfreeGold"
  558. disabled />
  559. </div>
  560. </div>
  561. <div class="add-item">
  562. <el-text style="width:4vw;">{{ t('common_add.payCurrency') }}</el-text>
  563. <el-input v-model="refundFormData.paymentCurrency" style="width:10vw;" disabled />
  564. </div>
  565. <div class="add-item">
  566. <el-text style="width:4vw;">{{ t('common_add.payAmount') }}</el-text>
  567. <el-input v-model="refundFormData.paymentAmount" style="width:10vw;" disabled />
  568. </div>
  569. <div class="add-item">
  570. <el-text style="width:4vw;">{{ t('common_add.payMethod') }}</el-text>
  571. <el-input v-model="refundFormData.payType" style="width:10vw;" disabled />
  572. </div>
  573. <div class="add-item">
  574. <el-text style="width:4vw;">{{ t('common_add.payTime') }}</el-text>
  575. <el-date-picker v-model="refundFormData.payTime" type="datetime" style="width:10vw;" disabled />
  576. </div>
  577. <div class="add-item">
  578. <el-text style="width:4vw;" size="small">{{ t('common_add.transferVoucher') }}</el-text>
  579. <el-form-item :rules="{ required: true, message: t('common_add.uploadPhoto'), trigger: 'change' }">
  580. <el-upload ref="uploadRef" :auto-upload="false" list-type="picture-card" :show-file-list="false">
  581. <template #default>
  582. <img v-if="refundFormData.voucher" :src="refundFormData.voucher"
  583. style="width: 100%; height: 100%; object-fit: cover;">
  584. <el-icon v-else>
  585. <Plus />
  586. </el-icon>
  587. </template>
  588. </el-upload>
  589. </el-form-item>
  590. </div>
  591. <div class="add-item">
  592. <el-text style="width:4vw;">{{ t('common_add.remark') }}</el-text>
  593. <el-input v-model="refundFormData.remark" style="width:10vw;" :rows="2" type="textarea" maxLength="100"
  594. disabled show-word-limit />
  595. </div>
  596. </div>
  597. <div class="right">
  598. <div class="add-item">
  599. <el-text style="width:4vw;">{{ t('common_add.refundModel') }}</el-text>
  600. <el-radio-group v-model="refundFormData.refundModel">
  601. <el-radio value="0">{{ t('common_add.refundModelAll') }}</el-radio>
  602. <el-radio value="1">{{ t('common_add.refundModelPart') }}</el-radio>
  603. </el-radio-group>
  604. </div>
  605. <div v-show="refundFormData.refundModel == '1'" style="display: flex; margin-bottom: 10px;">
  606. <div style=" display: flex; align-items: center;justify-content: center; ">
  607. <span style="color: #999999; white-space: nowrap;">{{ t('common_add.permanentGold')
  608. }}</span>
  609. <el-input style="padding-right: 10px; height: 30px; width: 70px;"
  610. v-model="refundFormData.permanentGold" />
  611. </div>
  612. <div style=" display: flex; align-items: center;justify-content: center; ">
  613. <span style="color: #999999; white-space: nowrap;">{{ t('common_add.freeGold') }}</span>
  614. <el-input style="padding-right: 10px; height: 30px; width: 70px;" v-model="refundFormData.freeGold" />
  615. </div>
  616. </div>
  617. <div class="add-item">
  618. <el-text style="width:4vw;">{{ t('common_add.refundReason') }}</el-text>
  619. <el-input v-model="refundFormData.refundReason" style="width:10vw;" :rows="5" maxlength="150"
  620. show-word-limit type="textarea" />
  621. </div>
  622. <div>{{ t('common_add.tip') }}</div>
  623. <div style="display:flex;justify-content: center;margin-top: 5vh;">
  624. <el-button type="default" @click="resetRefund">{{ t('common.reset') }}</el-button>
  625. <el-button type="primary" @click="throttledsubmitRefund">{{ t('common.submit') }}</el-button>
  626. </div>
  627. </div>
  628. </div>
  629. </el-dialog>
  630. </div>
  631. </template>
  632. <style lang="scss">
  633. .refund-popover {
  634. background-color: #EEF5FE !important;
  635. border: none !important;
  636. padding: 12px !important;
  637. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  638. width: 100px;
  639. min-width: none;
  640. .el-popper__arrow::before {
  641. background-color: #EEF5FE !important;
  642. border-color: #EEF5FE !important;
  643. }
  644. }
  645. </style>
  646. <style scoped lang="scss">
  647. .popover-content {
  648. .popover-title {
  649. color: #409EFF;
  650. font-weight: bold;
  651. font-size: 14px;
  652. margin-bottom: 8px;
  653. }
  654. .popover-item {
  655. display: flex;
  656. font-size: 13px;
  657. color: #606266;
  658. margin-bottom: 4px;
  659. &:last-child {
  660. margin-bottom: 0;
  661. }
  662. .label {
  663. color: #606266;
  664. }
  665. .value {
  666. color: #606266;
  667. margin-left: 4px;
  668. }
  669. }
  670. }
  671. .cash-flow-container {
  672. display: flex;
  673. flex-direction: column;
  674. height: 100%;
  675. }
  676. .search-card {
  677. margin-bottom: 10px;
  678. background: #F3FAFE; // 浅蓝背景
  679. border: none;
  680. :deep(.el-card__body) {
  681. padding: 15px;
  682. }
  683. }
  684. .search-bar {
  685. display: flex;
  686. flex-direction: column;
  687. gap: 15px;
  688. }
  689. .search-row {
  690. display: flex;
  691. flex-wrap: wrap;
  692. gap: 20px;
  693. align-items: center;
  694. }
  695. .search-item {
  696. display: flex;
  697. align-items: center;
  698. .label {
  699. font-size: 15px; // 参考 coinConsumeDetail 的 .text size="large"
  700. color: #000; // 或 #606266
  701. white-space: nowrap;
  702. margin-right: 8px;
  703. min-width: 60px;
  704. text-align: right;
  705. }
  706. .el-input,
  707. .el-select {
  708. width: 200px;
  709. }
  710. }
  711. .search-btn-group {
  712. margin-left: 20px; // 靠右对齐
  713. display: flex;
  714. gap: 10px;
  715. }
  716. .table-card {
  717. background: #E7F4FD;
  718. flex: 1;
  719. border: none;
  720. display: flex;
  721. flex-direction: column;
  722. :deep(.el-card__body) {
  723. padding: 20px;
  724. flex: 1;
  725. display: flex;
  726. flex-direction: column;
  727. overflow: hidden;
  728. }
  729. }
  730. .pagination-container {
  731. margin-top: 15px;
  732. display: flex;
  733. justify-content: flex-start;
  734. }
  735. // 表格样式覆盖 (参考 coinConsumeDetail)
  736. :deep(.el-table__header-wrapper),
  737. :deep(.el-table__body-wrapper),
  738. :deep(.el-table__cell),
  739. :deep(.el-table__body td) {
  740. background-color: #F3FAFE !important;
  741. }
  742. :deep(.el-table__row:hover > .el-table__cell) {
  743. background-color: #E5EBFE !important;
  744. }
  745. .refundDialog {
  746. .left {
  747. width: 50%;
  748. height: 70vh;
  749. min-height: 700px;
  750. padding: 0 2vw;
  751. .add-item {
  752. display: flex;
  753. align-items: center;
  754. margin-bottom: 1vh;
  755. }
  756. .image {
  757. width: 4vw !important;
  758. height: 4vw !important;
  759. }
  760. }
  761. .right {
  762. width: 50%;
  763. height: 50vh;
  764. .add-item {
  765. display: flex;
  766. align-items: center;
  767. margin-bottom: 1vh;
  768. }
  769. }
  770. }
  771. .recallDialog {
  772. //撤回弹窗提示
  773. height: 392px;
  774. width: 700px;
  775. background-image: url('/src/assets/receive-recall.png');
  776. position: fixed; // 固定定位,相对于浏览器窗口
  777. top: 50%; // 距离顶部50%
  778. left: 50%; // 距离左侧50%
  779. transform: translate(-50%, -50%); // 向左、向上平移自身宽高的50%,实现居中
  780. z-index: 1000; // 确保在其他元素上层显示
  781. .close {
  782. position: absolute;
  783. left: 625px;
  784. top: 20px;
  785. height: 38px;
  786. width: 38px;
  787. opacity: 0;
  788. .Btn {
  789. height: 100%;
  790. width: 100%;
  791. border-radius: 10px;
  792. }
  793. }
  794. .text {
  795. position: absolute;
  796. left: 185px;
  797. top: 190px;
  798. height: 67px;
  799. width: 500px;
  800. .txt {
  801. height: 100%;
  802. width: 100%;
  803. color: #001a42;
  804. font-family: "PingFang SC";
  805. font-size: 38px;
  806. font-style: normal;
  807. font-weight: 900;
  808. line-height: normal;
  809. }
  810. }
  811. .cancle {
  812. position: absolute;
  813. left: 185px;
  814. top: 304px;
  815. height: 55px;
  816. width: 150px;
  817. opacity: 0;
  818. .Btn {
  819. height: 100%;
  820. width: 100%;
  821. border-radius: 20px;
  822. }
  823. }
  824. .confirm {
  825. position: absolute;
  826. left: 375px;
  827. top: 304px;
  828. height: 55px;
  829. width: 150px;
  830. opacity: 0;
  831. .Btn {
  832. height: 100%;
  833. width: 100%;
  834. border-radius: 20px;
  835. }
  836. }
  837. }
  838. </style>