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.

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