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.

898 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. localMarket: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. trigger="hover"
  414. placement="top"
  415. popper-class="refund-popover"
  416. width="auto"
  417. v-if="row.status === 6"
  418. >
  419. <div class="popover-content">
  420. <div class="popover-title">{{ t('common_list.refundDetail') }}</div>
  421. <div class="popover-item">
  422. <span class="label">{{ t('common_list.refundAmount') }}</span>
  423. <span class="value">{{ row.refundAmount || '-' }}</span>
  424. </div>
  425. <div class="popover-item">
  426. <span class="label">{{ t('common_list.refundCurrency') }}</span>
  427. <span class="value">{{ row.refundCurrency || '-' }}</span>
  428. </div>
  429. </div>
  430. <template #reference>
  431. <img
  432. @click.stop
  433. src="@/assets/SvgIcons/consume.svg"
  434. style="width: 15px; height: 15px; margin-left: 5px; cursor: pointer; display: inline-block;"
  435. >
  436. </template>
  437. </el-popover>
  438. </div>
  439. </template>
  440. </el-table-column>
  441. <el-table-column :label="t('common_list.operation')" width="100" fixed="right" align="center">
  442. <template #default="{ row }">
  443. <el-button v-if="row.orderCode.slice(0, 4) == 'GOLD' && row.status === 4" type="danger" link size="small"
  444. @click="openRefundConfirm(row)">
  445. {{ t('common_list.refund') }}
  446. </el-button>
  447. </template>
  448. </el-table-column>
  449. </el-table>
  450. <!-- 分页 -->
  451. <div class="pagination-container">
  452. <el-pagination background layout="total, sizes, prev, pager, next, jumper" :total="total"
  453. :current-page="queryParams.pageNum" :page-size="queryParams.pageSize" :page-sizes="[10, 20, 50, 100]"
  454. @size-change="handlePageSizeChange" @current-change="handleCurrentChange" />
  455. </div>
  456. </el-card>
  457. <!-- 导出列表弹窗 -->
  458. <el-dialog v-model="exportListVisible" :title="t('common_export.exportList')" width="80%">
  459. <el-table :data="exportList" style="width: 100% ;height: 60vh;" :loading="exportListLoading">
  460. <el-table-column prop="fileName" :label="t('common_export.fileName')" />
  461. <el-table-column prop="state" :label="t('common_export.status')">
  462. <template #default="scope">
  463. <el-tag :type="getTagType(scope.row.state)" :effect="scope.row.state === 3 ? 'light' : 'plain'">
  464. {{ getTagText(scope.row.state) }}
  465. </el-tag>
  466. </template>
  467. </el-table-column>
  468. <el-table-column prop="createTime" :label="t('common_export.createTime')">
  469. <template #default="scope">
  470. {{ dayjs(scope.row.createTime).format('YYYY-MM-DD HH:mm:ss') }}
  471. </template>
  472. </el-table-column>
  473. <el-table-column :label="t('common_export.operation')">
  474. <template #default="scope">
  475. <el-button type="primary" size="small" @click="downloadExportFile(scope.row)"
  476. :disabled="scope.row.state !== 2">
  477. {{ t('common_export.download') }}
  478. </el-button>
  479. </template>
  480. </el-table-column>
  481. </el-table>
  482. <template #footer>
  483. <div class="dialog-footer">
  484. <el-button text @click="exportListVisible = false">{{ t('common_export.close') }}</el-button>
  485. </div>
  486. </template>
  487. </el-dialog>
  488. <div class="recallDialog" v-show="refundConfirmDialog">
  489. <div class="close">
  490. <button @click="closeConfirmRefund" class="Btn">{{ t('common.close') }}</button>
  491. </div>
  492. <div class="text">
  493. <text class="txt">{{ textContent }}</text>
  494. </div>
  495. <div class="cancle">
  496. <button @click="closeConfirmRefund" class="Btn">{{ t('common.cancel') }}</button>
  497. </div>
  498. <div class="confirm">
  499. <button @click="openRefundDialog" class="Btn">{{ t('common.confirm') }}</button>
  500. </div>
  501. </div>
  502. <el-dialog v-model="refundDialog" :title="t('common_add.refund')" class="refundDialog" overflow draggable
  503. style="width: 40vw;" :before-close="closeRefundForm">
  504. <div style="display: flex;">
  505. <div class="left">
  506. <div class="add-item">
  507. <el-text style="width:4vw;">{{ t('common_add.jwcode') }}</el-text>
  508. <el-input v-model="refundFormData.jwcode" style="width:10vw;" disabled />
  509. </div>
  510. <div class="add-item">
  511. <el-text style="width:4vw;">{{ t('common_add.customerName') }}</el-text>
  512. <el-input v-model="refundFormData.name" style="width:10vw;" disabled />
  513. </div>
  514. <div class="add-item">
  515. <el-text style="width:4vw;">{{ t('common_add.market') }}</el-text>
  516. <el-input v-model="refundFormData.marketName" style="width:10vw;" disabled />
  517. </div>
  518. <div class="add-item">
  519. <el-text style="width:4vw;">{{ t('common_add.activity') }}</el-text>
  520. <el-input v-model="refundFormData.activity" style="width:10vw;" disabled />
  521. </div>
  522. <div class="add-item">
  523. <el-text style="width:4vw;">{{ t('common_add.productName') }}</el-text>
  524. <el-input v-model="refundFormData.goodsName" style="width:10vw;" disabled />
  525. </div>
  526. <div style="display: flex; margin-bottom: 10px;">
  527. <div style=" display: flex; align-items: center;justify-content: center; ">
  528. <span style="color: #999999; white-space: nowrap;">{{ t('common_add.permanentGold')
  529. }}</span>
  530. <el-input style="padding-right: 10px; height: 30px; width: 70px;"
  531. v-model="refundFormData.oldpermanentGold" disabled />
  532. </div>
  533. <div style=" display: flex; align-items: center;justify-content: center; ">
  534. <span style="color: #999999; white-space: nowrap;">{{ t('common_add.freeGold') }}</span>
  535. <el-input style="padding-right: 10px; height: 30px; width: 70px;" v-model="refundFormData.oldfreeGold"
  536. disabled />
  537. </div>
  538. </div>
  539. <div class="add-item">
  540. <el-text style="width:4vw;">{{ t('common_add.payCurrency') }}</el-text>
  541. <el-input v-model="refundFormData.paymentCurrency" style="width:10vw;" disabled />
  542. </div>
  543. <div class="add-item">
  544. <el-text style="width:4vw;">{{ t('common_add.payAmount') }}</el-text>
  545. <el-input v-model="refundFormData.paymentAmount" style="width:10vw;" disabled />
  546. </div>
  547. <div class="add-item">
  548. <el-text style="width:4vw;">{{ t('common_add.payMethod') }}</el-text>
  549. <el-input v-model="refundFormData.payType" style="width:10vw;" disabled />
  550. </div>
  551. <div class="add-item">
  552. <el-text style="width:4vw;">{{ t('common_add.payTime') }}</el-text>
  553. <el-date-picker v-model="refundFormData.payTime" type="datetime" style="width:10vw;" disabled />
  554. </div>
  555. <div class="add-item">
  556. <el-text style="width:4vw;" size="small">{{ t('common_add.transferVoucher') }}</el-text>
  557. <el-form-item :rules="{ required: true, message: t('common_add.uploadPhoto'), trigger: 'change' }">
  558. <el-upload ref="uploadRef" :auto-upload="false" list-type="picture-card" :show-file-list="false">
  559. <template #default>
  560. <img v-if="refundFormData.voucher" :src="refundFormData.voucher"
  561. style="width: 100%; height: 100%; object-fit: cover;">
  562. <el-icon v-else>
  563. <Plus />
  564. </el-icon>
  565. </template>
  566. </el-upload>
  567. </el-form-item>
  568. </div>
  569. <div class="add-item">
  570. <el-text style="width:4vw;">{{ t('common_add.remark') }}</el-text>
  571. <el-input v-model="refundFormData.remark" style="width:10vw;" :rows="2" type="textarea" maxLength="100"
  572. disabled show-word-limit />
  573. </div>
  574. </div>
  575. <div class="right">
  576. <div class="add-item">
  577. <el-text style="width:4vw;">{{ t('common_add.refundModel') }}</el-text>
  578. <el-radio-group v-model="refundFormData.refundModel">
  579. <el-radio value="0">{{ t('common_add.refundModelAll') }}</el-radio>
  580. <el-radio value="1">{{ t('common_add.refundModelPart') }}</el-radio>
  581. </el-radio-group>
  582. </div>
  583. <div v-show="refundFormData.refundModel == '1'" style="display: flex; margin-bottom: 10px;">
  584. <div style=" display: flex; align-items: center;justify-content: center; ">
  585. <span style="color: #999999; white-space: nowrap;">{{ t('common_add.permanentGold')
  586. }}</span>
  587. <el-input style="padding-right: 10px; height: 30px; width: 70px;"
  588. v-model="refundFormData.permanentGold" />
  589. </div>
  590. <div style=" display: flex; align-items: center;justify-content: center; ">
  591. <span style="color: #999999; white-space: nowrap;">{{ t('common_add.freeGold') }}</span>
  592. <el-input style="padding-right: 10px; height: 30px; width: 70px;" v-model="refundFormData.freeGold" />
  593. </div>
  594. </div>
  595. <div class="add-item">
  596. <el-text style="width:4vw;">{{ t('common_add.refundReason') }}</el-text>
  597. <el-input v-model="refundFormData.refundReason" style="width:10vw;" :rows="5" maxlength="150"
  598. show-word-limit type="textarea" />
  599. </div>
  600. <div>{{ t('common_add.tip') }}</div>
  601. <div style="display:flex;justify-content: center;margin-top: 5vh;">
  602. <el-button type="default" @click="resetRefund">{{ t('common.reset') }}</el-button>
  603. <el-button type="primary" @click="throttledsubmitRefund">{{ t('common.submit') }}</el-button>
  604. </div>
  605. </div>
  606. </div>
  607. </el-dialog>
  608. </div>
  609. </template>
  610. <style lang="scss">
  611. .refund-popover {
  612. background-color: #EEF5FE !important;
  613. border: none !important;
  614. padding: 12px !important;
  615. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  616. width: 100px;
  617. min-width: none;
  618. .el-popper__arrow::before {
  619. background-color: #EEF5FE !important;
  620. border-color: #EEF5FE !important;
  621. }
  622. }
  623. </style>
  624. <style scoped lang="scss">
  625. .popover-content {
  626. .popover-title {
  627. color: #409EFF;
  628. font-weight: bold;
  629. font-size: 14px;
  630. margin-bottom: 8px;
  631. }
  632. .popover-item {
  633. display: flex;
  634. font-size: 13px;
  635. color: #606266;
  636. margin-bottom: 4px;
  637. &:last-child {
  638. margin-bottom: 0;
  639. }
  640. .label {
  641. color: #606266;
  642. }
  643. .value {
  644. color: #606266;
  645. margin-left: 4px;
  646. }
  647. }
  648. }
  649. .cash-flow-container {
  650. display: flex;
  651. flex-direction: column;
  652. height: 100%;
  653. }
  654. .search-card {
  655. margin-bottom: 10px;
  656. background: #F3FAFE; // 浅蓝背景
  657. border: none;
  658. :deep(.el-card__body) {
  659. padding: 15px;
  660. }
  661. }
  662. .search-bar {
  663. display: flex;
  664. flex-direction: column;
  665. gap: 15px;
  666. }
  667. .search-row {
  668. display: flex;
  669. flex-wrap: wrap;
  670. gap: 20px;
  671. align-items: center;
  672. }
  673. .search-item {
  674. display: flex;
  675. align-items: center;
  676. .label {
  677. font-size: 15px; // 参考 coinConsumeDetail 的 .text size="large"
  678. color: #000; // 或 #606266
  679. white-space: nowrap;
  680. margin-right: 8px;
  681. min-width: 60px;
  682. text-align: right;
  683. }
  684. .el-input,
  685. .el-select {
  686. width: 200px;
  687. }
  688. }
  689. .search-btn-group {
  690. margin-left: 20px; // 靠右对齐
  691. display: flex;
  692. gap: 10px;
  693. }
  694. .table-card {
  695. background: #E7F4FD;
  696. flex: 1;
  697. border: none;
  698. display: flex;
  699. flex-direction: column;
  700. :deep(.el-card__body) {
  701. padding: 20px;
  702. flex: 1;
  703. display: flex;
  704. flex-direction: column;
  705. overflow: hidden;
  706. }
  707. }
  708. .pagination-container {
  709. margin-top: 15px;
  710. display: flex;
  711. justify-content: flex-start;
  712. }
  713. // 表格样式覆盖 (参考 coinConsumeDetail)
  714. :deep(.el-table__header-wrapper),
  715. :deep(.el-table__body-wrapper),
  716. :deep(.el-table__cell),
  717. :deep(.el-table__body td) {
  718. background-color: #F3FAFE !important;
  719. }
  720. :deep(.el-table__row:hover > .el-table__cell) {
  721. background-color: #E5EBFE !important;
  722. }
  723. .refundDialog {
  724. .left {
  725. width: 50%;
  726. height: 70vh;
  727. min-height: 700px;
  728. padding: 0 2vw;
  729. .add-item {
  730. display: flex;
  731. align-items: center;
  732. margin-bottom: 1vh;
  733. }
  734. .image {
  735. width: 4vw !important;
  736. height: 4vw !important;
  737. }
  738. }
  739. .right {
  740. width: 50%;
  741. height: 50vh;
  742. .add-item {
  743. display: flex;
  744. align-items: center;
  745. margin-bottom: 1vh;
  746. }
  747. }
  748. }
  749. .recallDialog {
  750. //撤回弹窗提示
  751. height: 392px;
  752. width: 700px;
  753. background-image: url('/src/assets/receive-recall.png');
  754. position: fixed; // 固定定位,相对于浏览器窗口
  755. top: 50%; // 距离顶部50%
  756. left: 50%; // 距离左侧50%
  757. transform: translate(-50%, -50%); // 向左、向上平移自身宽高的50%,实现居中
  758. z-index: 1000; // 确保在其他元素上层显示
  759. .close {
  760. position: absolute;
  761. left: 625px;
  762. top: 20px;
  763. height: 38px;
  764. width: 38px;
  765. opacity: 0;
  766. .Btn {
  767. height: 100%;
  768. width: 100%;
  769. border-radius: 10px;
  770. }
  771. }
  772. .text {
  773. position: absolute;
  774. left: 185px;
  775. top: 190px;
  776. height: 67px;
  777. width: 500px;
  778. .txt {
  779. height: 100%;
  780. width: 100%;
  781. color: #001a42;
  782. font-family: "PingFang SC";
  783. font-size: 38px;
  784. font-style: normal;
  785. font-weight: 900;
  786. line-height: normal;
  787. }
  788. }
  789. .cancle {
  790. position: absolute;
  791. left: 185px;
  792. top: 304px;
  793. height: 55px;
  794. width: 150px;
  795. opacity: 0;
  796. .Btn {
  797. height: 100%;
  798. width: 100%;
  799. border-radius: 20px;
  800. }
  801. }
  802. .confirm {
  803. position: absolute;
  804. left: 375px;
  805. top: 304px;
  806. height: 55px;
  807. width: 150px;
  808. opacity: 0;
  809. .Btn {
  810. height: 100%;
  811. width: 100%;
  812. border-radius: 20px;
  813. }
  814. }
  815. }
  816. </style>