index.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727
  1. <template>
  2. <div
  3. id="review_Email"
  4. v-loading="pictLoading"
  5. class="view"
  6. element-loading-background="rgba(43, 48, 67, 0.65)"
  7. element-loading-spinner="el-icon-loading"
  8. >
  9. <div class="crm_search">
  10. <el-form ref="searchForm" label-position="" :model="search" label-width="">
  11. <el-row>
  12. <el-col :span="24" :md="24" :lg="24">
  13. <el-form-item style="margin-right: 10px">
  14. <el-select
  15. v-model="search.payoutCurrency"
  16. class="crm-border-radius-no"
  17. clearable
  18. :placeholder="$t('global.p1')"
  19. @change="toSearch"
  20. >
  21. <el-option
  22. v-for="(item, index) in currenciesDropdown"
  23. :key="index"
  24. :label="item"
  25. :value="item"
  26. />
  27. </el-select>
  28. </el-form-item>
  29. <el-form-item style="margin-right: 10px">
  30. <el-select
  31. v-model="search.transferTypeId"
  32. class="crm-border-radius-no"
  33. clearable
  34. :placeholder="$t('global.p2')"
  35. @change="toSearch"
  36. >
  37. <el-option
  38. v-for="([key, value], index) in Object.entries(transferType)"
  39. :key="index"
  40. :label="value"
  41. :value="key"
  42. />
  43. </el-select>
  44. </el-form-item>
  45. <el-form-item style="margin-right: 10px">
  46. <el-select
  47. v-model="search.payoutMethodId"
  48. class="crm-border-radius-no"
  49. clearable
  50. :placeholder="$t('global.p3')"
  51. @change="toSearch"
  52. >
  53. <el-option
  54. v-for="([key, value], index) in Object.entries(payoutMethod)"
  55. :key="index"
  56. :label="$t(value)"
  57. :value="key"
  58. />
  59. </el-select>
  60. </el-form-item>
  61. <el-form-item>
  62. <el-select
  63. v-model="search.status"
  64. class="crm-border-radius-no"
  65. clearable
  66. :placeholder="$t('Ucard.CardType.s3')"
  67. @change="toSearch"
  68. >
  69. <el-option :label="$t('Ucard.CardType.t5')" value="offline"></el-option>
  70. <el-option :label="$t('Ucard.CardType.t6')" value="online"></el-option>
  71. </el-select>
  72. </el-form-item>
  73. <el-form-item>
  74. <el-button class="crm-border-radius-no crm-border-left-no" @click="toSearch">
  75. <el-icon><Search /></el-icon>
  76. </el-button>
  77. </el-form-item>
  78. </el-col>
  79. </el-row>
  80. <el-form-item>
  81. <div class="search_action_btn">
  82. <el-button
  83. v-if="display['R-GlobalCurrency-Sync'].show"
  84. type="primary"
  85. :loading="syncLoading"
  86. class="crm-cursor"
  87. @click="updateCardTypes"
  88. >
  89. {{ $t('R-GlobalCurrency-Sync') }}
  90. </el-button>
  91. </div>
  92. </el-form-item>
  93. </el-form>
  94. <div class="card-mock-demo" style="margin-top: 30px">
  95. <el-table :data="mergeFirstRowData" stripe style="margin-top: 20px; width: 100%">
  96. <el-table-column prop="feeRateConfig" align="left" :label="$t('global.p5')" />
  97. <el-table-column prop="fixedFeeConfig" align="left" :label="$t('global.p6')" />
  98. <el-table-column :label="$t('Ucard.Business.item20')" align="center">
  99. <template #default="scope">
  100. <el-dropdown trigger="click" @command="handleCommand">
  101. <span class="el-dropdown-link crm-cursor">
  102. <i style="font-weight: bold; font-size: 20px" class="iconfont iconcaidan"></i>
  103. </span>
  104. <template #dropdown>
  105. <el-dropdown-menu>
  106. <el-dropdown-item
  107. v-if="
  108. display['R-GlobalCurrency-Update'] &&
  109. display['R-GlobalCurrency-Update'].show
  110. "
  111. :command="{ type: 1, row: scope.row }"
  112. >
  113. <el-icon><Edit /></el-icon>
  114. <span>
  115. {{ $t('R-GlobalCurrency-Update') }}
  116. </span>
  117. </el-dropdown-item>
  118. </el-dropdown-menu>
  119. </template>
  120. </el-dropdown>
  121. </template>
  122. </el-table-column>
  123. </el-table>
  124. </div>
  125. <div class="card-mock-demo" style="margin-bottom: 30px">
  126. <el-table :data="mock_tableData" stripe style="margin-top: 20px; width: 100%">
  127. <el-table-column prop="payoutCurrency" align="left" :label="$t('global.p1')">
  128. </el-table-column>
  129. <el-table-column prop="transferTypeValue" align="left" :label="$t('global.p2')" />
  130. <el-table-column prop="payoutMethodValue" align="left" :label="$t('global.p3')">
  131. <template #default="scope">
  132. <span>{{ $t(payoutMethod[scope.row.payoutMethodId]) }}</span>
  133. </template>
  134. </el-table-column>
  135. <el-table-column prop="country" align="left" :label="$t('global.p4')" />
  136. <el-table-column prop="feeRateConfig" align="left" :label="$t('global.p5')" />
  137. <el-table-column prop="fixedFeeConfig" align="left" :label="$t('global.p6')" />
  138. <el-table-column prop="minQuotaConfig" align="left" :label="$t('global.p7')" />
  139. <el-table-column prop="maxQuotaConfig" align="left" :label="$t('global.p8')" />
  140. <el-table-column
  141. prop="yearTransferAmountQuotaConfig"
  142. align="left"
  143. :label="$t('global.p9')"
  144. />
  145. <el-table-column prop="status" align="left" :label="$t('Ucard.CardType.item7')">
  146. <template #default="scope">
  147. <el-tag :type="scope.row.status === 'online' ? 'success' : 'info'"
  148. >{{
  149. scope.row.status === 'online' ? $t('Ucard.CardType.t6') : $t('Ucard.CardType.t5')
  150. }}
  151. </el-tag>
  152. </template>
  153. </el-table-column>
  154. <el-table-column :label="$t('Ucard.Business.item20')" align="center">
  155. <template #default="scope">
  156. <el-dropdown trigger="click" @command="handleCommand">
  157. <span class="el-dropdown-link crm-cursor">
  158. <i style="font-weight: bold; font-size: 20px" class="iconfont iconcaidan"></i>
  159. </span>
  160. <template #dropdown>
  161. <el-dropdown-menu>
  162. <el-dropdown-item
  163. v-if="
  164. display['R-GlobalCurrency-Update1'] &&
  165. display['R-GlobalCurrency-Update1'].show
  166. "
  167. :command="{ type: 2, row: scope.row }"
  168. >
  169. <el-icon><Edit /></el-icon>
  170. <span>
  171. {{ $t('R-GlobalCurrency-Update1') }}
  172. </span>
  173. </el-dropdown-item>
  174. </el-dropdown-menu>
  175. </template>
  176. </el-dropdown>
  177. </template>
  178. </el-table-column>
  179. </el-table>
  180. </div>
  181. </div>
  182. <PagePagination
  183. :pager-info="pagerInfo"
  184. @current-change="handleCurrentChange"
  185. @size-change="handleSizeChange"
  186. />
  187. <el-dialog
  188. v-model="approvalAllDialog"
  189. :title="form.type == 1 ? $t('R-GlobalCurrency-Update') : $t('R-GlobalCurrency-Update1')"
  190. width="500px"
  191. :close-on-click-modal="false"
  192. >
  193. <el-form
  194. ref="formRef"
  195. :rules="rules"
  196. :model="form"
  197. label-position="top"
  198. class="business-edit-form"
  199. >
  200. <el-form-item prop="feeRateConfig" :label="$t('global.p5')">
  201. <el-input-number
  202. v-model="form.feeRateConfig"
  203. :controls="false"
  204. :disabled-scientific="true"
  205. :placeholder="$t('Placeholder.Input')"
  206. />
  207. </el-form-item>
  208. <el-form-item prop="fixedFeeConfig" :label="$t('global.p6')">
  209. <el-input-number
  210. v-model="form.fixedFeeConfig"
  211. :controls="false"
  212. :placeholder="$t('Placeholder.Input')"
  213. />
  214. </el-form-item>
  215. <!-- 判断一下全局不用展示下面的,普通的需要展示 乘以汇率比对config的数值 -->
  216. <template v-if="form.type == '2'">
  217. <el-form-item prop="minQuotaConfig">
  218. <template #label>
  219. <span>
  220. <span>{{ $t('global.p7') }}</span>
  221. <span v-if="minRate" class="rate-tip">{{
  222. `${$t('Ucard.GlobalOrder.currencyTip')}${minRate}`
  223. }}</span>
  224. </span>
  225. </template>
  226. <el-input
  227. v-model="form.minQuotaConfig"
  228. type="number"
  229. :max="form.maxMinQuota"
  230. :min="form.maxQuotaConfig"
  231. :placeholder="$t('Placeholder.Input')"
  232. />
  233. </el-form-item>
  234. <el-form-item prop="maxQuotaConfig">
  235. <template #label>
  236. <span>
  237. <span>{{ $t('global.p8') }}</span>
  238. <span v-if="maxRate" class="rate-tip">{{
  239. `${$t('Ucard.GlobalOrder.currencyTip')} ${maxRate}`
  240. }}</span>
  241. </span>
  242. </template>
  243. <el-input
  244. v-model="form.maxQuotaConfig"
  245. type="number"
  246. :placeholder="$t('Placeholder.Input')"
  247. />
  248. </el-form-item>
  249. <el-form-item prop="yearTransferAmountQuotaConfig" :label="$t('global.p9')">
  250. <template #label>
  251. <span>
  252. <span>{{ $t('global.p9') }}</span>
  253. <span v-if="yearRate" class="rate-tip">{{
  254. `${$t('Ucard.GlobalOrder.currencyTip')} ${yearRate}`
  255. }}</span>
  256. </span>
  257. </template>
  258. <el-input-number
  259. v-model="form.yearTransferAmountQuotaConfig"
  260. type="number"
  261. :controls="false"
  262. :max="form.minYearTransferAmountQuota"
  263. :placeholder="$t('Placeholder.Input')"
  264. />
  265. </el-form-item>
  266. </template>
  267. </el-form>
  268. <template #footer>
  269. <div class="dialog-footer">
  270. <el-button @click="approvalAllDialog = false">
  271. {{ $t('Ucard.Business.p32') }}
  272. </el-button>
  273. <el-button type="primary" @click="updateCardTypesConfig">
  274. {{ $t('card.Btn.Confirm') }}
  275. </el-button>
  276. </div>
  277. </template>
  278. </el-dialog>
  279. </div>
  280. </template>
  281. <script setup>
  282. import { computed, inject, onMounted, reactive, ref, watch } from 'vue'
  283. import { useI18n } from 'vue-i18n'
  284. import { ElMessage } from 'element-plus'
  285. import { multiply } from 'lodash-es'
  286. import Service from '@/service/ucard'
  287. import Config from '@/config/index'
  288. import PagePagination from '@/components/PagePagination'
  289. import { payoutMethod, transferType } from './const'
  290. import { floatReg } from '@/utils/const'
  291. import { Edit } from '@element-plus/icons-vue'
  292. const { Code } = Config
  293. const { t } = useI18n()
  294. const Session = inject('session')
  295. const pigeon = inject('pigeon')
  296. // 响应式数据
  297. const pictLoading = ref(false)
  298. const syncLoading = ref(false)
  299. const approvalAllDialog = ref(false)
  300. const search = reactive({
  301. status: '',
  302. payoutCurrency: '',
  303. transferTypeId: '',
  304. payoutMethodId: '',
  305. })
  306. const form = reactive({
  307. feeRateConfig: null,
  308. fixedFeeConfig: null,
  309. minQuotaConfig: null,
  310. maxQuotaConfig: null,
  311. yearTransferAmountQuotaConfig: null,
  312. })
  313. const currenciesDropdown = ref([])
  314. const mergeFirstRowData = ref([])
  315. const mock_tableData = ref([])
  316. const pagerInfo = reactive({
  317. row: 10,
  318. current: 1,
  319. pageTotal: 0,
  320. rowTotal: 0,
  321. })
  322. const minRate = ref(0)
  323. const maxRate = ref(0)
  324. const yearRate = ref(0)
  325. const formRef = ref()
  326. // 搜索
  327. const searchForm = ref()
  328. // 计算属性
  329. const display = computed(() => {
  330. return JSON.parse(Session.Get('display', true))
  331. })
  332. // 表单验证规则
  333. const rules = computed(() => ({
  334. feeRateConfig: [
  335. {
  336. validator: (rule, value, callback) => {
  337. if (value) {
  338. const test = floatReg.test(value)
  339. if (test) {
  340. callback()
  341. } else {
  342. callback(new Error(t('global.validator.v13')))
  343. }
  344. }
  345. callback()
  346. },
  347. trigger: 'change',
  348. },
  349. ],
  350. fixedFeeConfig: [
  351. {
  352. validator: (rule, value, callback) => {
  353. if (value) {
  354. const test = floatReg.test(value)
  355. if (test) {
  356. callback()
  357. } else {
  358. callback(new Error(t('global.validator.v13')))
  359. }
  360. }
  361. callback()
  362. },
  363. trigger: 'change',
  364. },
  365. ],
  366. minQuotaConfig: [
  367. {
  368. validator: (rule, value, callback) => {
  369. if (form.type == 1 || !value) {
  370. return callback()
  371. }
  372. const minQuota = Number(form.minQuota)
  373. const maxQuota = Number(form.maxQuota)
  374. const maxConfig = Number(form.maxQuotaConfig)
  375. const exchangeRate = Number(form.exchangeRate)
  376. const test = floatReg.test(value)
  377. if (!test) {
  378. return callback(new Error(t('global.validator.v13')))
  379. }
  380. value = Number(value)
  381. if (isNaN(value)) {
  382. return callback(new Error(t('global.validator.v12')))
  383. }
  384. const val = multiply(value, exchangeRate)
  385. minRate.value = val
  386. if (val < minQuota) {
  387. callback(new Error(t('global.validator.v2', { minQuota })))
  388. } else if (val > maxQuota) {
  389. callback(new Error(t('global.validator.v3', { maxQuota })))
  390. } else if (maxConfig && value > maxConfig) {
  391. callback(new Error(t('global.validator.v4', { maxConfig })))
  392. } else {
  393. callback()
  394. }
  395. },
  396. trigger: 'change',
  397. },
  398. ],
  399. maxQuotaConfig: [
  400. {
  401. validator: (rule, value, callback) => {
  402. if (form.type == 1 || !value) {
  403. return callback()
  404. }
  405. const minQuota = Number(form.minQuota)
  406. const maxQuota = Number(form.maxQuota)
  407. const minConfig = Number(form.minQuotaConfig)
  408. const exchangeRate = Number(form.exchangeRate)
  409. const test = floatReg.test(value)
  410. if (!test) {
  411. return callback(new Error(t('global.validator.v13')))
  412. }
  413. value = Number(value)
  414. if (isNaN(value)) {
  415. return callback(new Error(t('global.validator.v12')))
  416. }
  417. const val = multiply(value, exchangeRate)
  418. maxRate.value = val
  419. if (val > maxQuota) {
  420. callback(new Error(t('global.validator.v6', { maxQuota })))
  421. } else if (val < minQuota) {
  422. callback(new Error(t('global.validator.v7', { minQuota })))
  423. } else if (minConfig && value < minConfig) {
  424. callback(new Error(t('global.validator.v8', { minConfig })))
  425. } else {
  426. callback()
  427. }
  428. },
  429. trigger: 'change',
  430. },
  431. ],
  432. yearTransferAmountQuotaConfig: [
  433. {
  434. validator: (rule, value, callback) => {
  435. if (form.type == 1 || !value) {
  436. return callback()
  437. }
  438. const yearMax = Number(form.yearTransferAmountQuota)
  439. const maxConfig = Number(form.maxQuotaConfig)
  440. const exchangeRate = Number(form.exchangeRate)
  441. const test = floatReg.test(value)
  442. if (!test) {
  443. return callback(new Error(t('global.validator.v13')))
  444. }
  445. value = Number(value)
  446. if (isNaN(value)) {
  447. return callback(new Error(t('global.validator.v12')))
  448. }
  449. const val = multiply(value, exchangeRate)
  450. yearRate.value = val
  451. if (val > yearMax) {
  452. callback(new Error(t('global.validator.v10', { yearMax })))
  453. } else if (maxConfig && val < maxConfig) {
  454. callback(new Error(t('global.validator.v11', { maxConfig })))
  455. } else {
  456. callback()
  457. }
  458. },
  459. trigger: 'change',
  460. },
  461. ],
  462. }))
  463. // 方法
  464. const keydown = (e, s) => {
  465. console.log(e, s, 'key')
  466. }
  467. const handleCommand = async (command) => {
  468. console.log(command)
  469. switch (command.type) {
  470. case 1: {
  471. const data = {
  472. ...command.row,
  473. type: '1',
  474. }
  475. Object.assign(form, data)
  476. approvalAllDialog.value = true
  477. break
  478. }
  479. case 2: {
  480. let data = {
  481. ...command.row,
  482. type: '2',
  483. }
  484. const { payoutCurrency, payoutMethodId, transferTypeId } = data
  485. const res = await Service.globalLatestExchangeRate({
  486. payoutCurrency,
  487. payoutMethodId,
  488. transferTypeId,
  489. })
  490. if (res.code == Code.StatusOK) {
  491. data = {
  492. ...data,
  493. exchangeRate: res.data,
  494. }
  495. }
  496. Object.assign(form, data)
  497. console.log(form.type)
  498. approvalAllDialog.value = true
  499. break
  500. }
  501. }
  502. }
  503. const updateCardTypesConfig = async () => {
  504. try {
  505. // 注意:这里需要获取实际的表单引用,假设 formRef 已经绑定到模板
  506. if (!formRef.value) return
  507. console.log(formRef)
  508. const valid = await formRef.value.validate()
  509. if (!valid) return
  510. const res = await Service.updateGlobalFee({ ...form })
  511. if (res.code == Code.StatusOK) {
  512. Object.assign(form, {
  513. feeRateConfig: null,
  514. fixedFeeConfig: null,
  515. minQuotaConfig: null,
  516. maxQuotaConfig: null,
  517. yearTransferAmountQuotaConfig: null,
  518. })
  519. approvalAllDialog.value = false
  520. toSearch()
  521. } else {
  522. pigeon.MessageError(res.msg)
  523. }
  524. } catch (error) {
  525. console.log(error, 1212)
  526. }
  527. }
  528. const closeForm = () => {
  529. minRate.value = 0
  530. maxRate.value = 0
  531. yearRate.value = 0
  532. }
  533. const toSearch = () => {
  534. pagerInfo.current = 1
  535. globalCurrenciesConfig()
  536. searchFunc()
  537. }
  538. const updateCardTypes = async () => {
  539. syncLoading.value = true
  540. const res = await Service.globalCurrenciesSave({})
  541. if (res.code == Code.StatusOK) {
  542. pigeon.MessageOK(t('Msg.SearchSuccess'))
  543. searchFunc()
  544. } else {
  545. ElMessage.error(res.msg)
  546. }
  547. syncLoading.value = false
  548. }
  549. const updateCardType = () => {
  550. approvalAllDialog.value = true
  551. }
  552. const globalCurrenciesDropdown = async () => {
  553. const res = await Service.globalCurrenciesDropdown({})
  554. if (res.code == Code.StatusOK) {
  555. currenciesDropdown.value = [...new Set(res.data.map((item) => item.payoutCurrency))]
  556. } else {
  557. ElMessage.error(res.msg)
  558. }
  559. }
  560. const globalCurrenciesConfig = async () => {
  561. const res = await Service.globalCurrenciesConfig({})
  562. if (res.code == Code.StatusOK) {
  563. mergeFirstRowData.value = [res.data]
  564. } else {
  565. ElMessage.error(res.msg)
  566. }
  567. }
  568. const searchFunc = async () => {
  569. pictLoading.value = true
  570. if (!display.value['R-GlobalCurrency-List'].show) {
  571. ElMessage.warning(t('Msg.NotDisplay'))
  572. pictLoading.value = false
  573. return
  574. }
  575. const res = await Service.globalCurrenciesList({
  576. ...search,
  577. page: {
  578. current: pagerInfo.current,
  579. row: pagerInfo.row,
  580. },
  581. })
  582. if (res.code == Code.StatusOK) {
  583. mock_tableData.value = res.data
  584. if (res.page != null) {
  585. pagerInfo.rowTotal = res.page.rowTotal
  586. pagerInfo.pageTotal = res.page.pageTotal
  587. } else {
  588. pagerInfo.rowTotal = 0
  589. }
  590. ElMessage.success(t('Msg.SearchSuccess'))
  591. } else {
  592. ElMessage.error(res.msg)
  593. }
  594. pictLoading.value = false
  595. }
  596. const handleSizeChange = (val) => {
  597. pagerInfo.row = val
  598. searchFunc()
  599. }
  600. const handleCurrentChange = (val) => {
  601. pagerInfo.current = val
  602. searchFunc()
  603. }
  604. // 生命周期
  605. onMounted(() => {
  606. globalCurrenciesConfig()
  607. searchFunc()
  608. globalCurrenciesDropdown()
  609. })
  610. // 侦听器
  611. watch(approvalAllDialog, (newVal) => {
  612. if (!newVal) {
  613. closeForm()
  614. }
  615. })
  616. </script>
  617. <style scoped lang="scss">
  618. #review_Email {
  619. .el-table .state {
  620. display: inline-block;
  621. min-width: 80px;
  622. max-width: 150px;
  623. box-sizing: border-box;
  624. line-height: 1.5;
  625. border-radius: 2px;
  626. padding: 2px 10px;
  627. color: #ffffff;
  628. }
  629. }
  630. </style>
  631. <style scoped lang="scss">
  632. #review_Email {
  633. .dialog_header_w {
  634. .crm_search_down {
  635. width: 400px;
  636. }
  637. }
  638. :deep(.business-edit-form) {
  639. .el-form-item__label {
  640. width: 100%;
  641. text-align: left;
  642. padding: 0;
  643. }
  644. .el-form-item {
  645. width: 100%;
  646. text-align: left;
  647. padding: 0;
  648. }
  649. .el-input,
  650. .el-select,
  651. .el-date-picker,
  652. .el-date-editor {
  653. width: 100%;
  654. }
  655. .el-form-item__label {
  656. font-weight: 500;
  657. }
  658. .el-row {
  659. margin-bottom: 0;
  660. }
  661. .el-col {
  662. margin-bottom: 0;
  663. }
  664. }
  665. .rate-tip {
  666. margin-left: 10px;
  667. color: #999;
  668. font-size: 12px;
  669. }
  670. }
  671. </style>