customer.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. <template>
  2. <cwg-page-wrapper class="create-page" :isHeaderFixed="true">
  3. <cwg-header :title="t('Home.page_ib.item2')" />
  4. <view class="info-card">
  5. <!-- 移动端:分类标签在搜索组件上方 -->
  6. <cwg-match-media :max-width="991">
  7. <view class="search-content mobile-search-content">
  8. <view class="search-tabs">
  9. <view class="crm-cursor tab-item" :class="{ active: search.belongsType == 1 }"
  10. @click="chooseBelongsType(1)">
  11. {{ t('Ib.Custom.Unverified') }}
  12. <view v-if="statistics.unverifiedNum !== undefined" class="count-badge">({{ statistics.unverifiedNum }})
  13. </view>
  14. </view>
  15. <view class="crm-cursor tab-item" :class="{ active: search.belongsType == 2 }"
  16. @click="chooseBelongsType(2)">
  17. {{ t('Ib.Custom.UnDeposit') }}
  18. <view v-if="statistics.unDepositNum !== undefined" class="count-badge">({{ statistics.unDepositNum }})
  19. </view>
  20. </view>
  21. <view class="crm-cursor tab-item" :class="{ active: search.belongsType == 3 }"
  22. @click="chooseBelongsType(3)">
  23. {{ t('Ib.Custom.Deposited') }}
  24. <view v-if="statistics.depositNum !== undefined" class="count-badge">({{ statistics.depositNum }})</view>
  25. </view>
  26. </view>
  27. <view class="search-bar">
  28. <cwg-complex-search :fields="filterFields" v-model="search" @search="handleSearch"
  29. @reset="handleReset" />
  30. </view>
  31. </view>
  32. </cwg-match-media>
  33. <!-- PC端:分类标签和搜索组件在同一行 (搜索在左,标签在右) -->
  34. <cwg-match-media :min-width="992">
  35. <view class="search-content pc-search-content">
  36. <view class="search-bar">
  37. <cwg-complex-search :fields="filterFields" v-model="search" @search="handleSearch"
  38. @reset="handleReset" />
  39. </view>
  40. <view class="search-tabs">
  41. <view class="crm-cursor tab-item" :class="{ active: search.belongsType == 1 }"
  42. @click="chooseBelongsType(1)">
  43. {{ t('Ib.Custom.Unverified') }}
  44. <view v-if="statistics.unverifiedNum !== undefined" class="count-badge">({{ statistics.unverifiedNum }})
  45. </view>
  46. </view>
  47. <view class="crm-cursor tab-item" :class="{ active: search.belongsType == 2 }"
  48. @click="chooseBelongsType(2)">
  49. {{ t('Ib.Custom.UnDeposit') }}
  50. <view v-if="statistics.unDepositNum !== undefined" class="count-badge">({{ statistics.unDepositNum }})
  51. </view>
  52. </view>
  53. <view class="crm-cursor tab-item" :class="{ active: search.belongsType == 3 }"
  54. @click="chooseBelongsType(3)">
  55. {{ t('Ib.Custom.Deposited') }}
  56. <view v-if="statistics.depositNum !== undefined" class="count-badge">({{ statistics.depositNum }})</view>
  57. </view>
  58. </view>
  59. </view>
  60. </cwg-match-media>
  61. <cwg-tabel
  62. ref="tableRef"
  63. :columns="columns"
  64. :mobilePrimaryFields="mobilePrimaryFields"
  65. :queryParams="search"
  66. :api="listApi"
  67. :show-operation="true"
  68. :showPagination="true"
  69. >
  70. <template #documentary="{ row }">
  71. <button class="action-btn" @click.stop="handleMenuClick({type: 'documentary', row})">
  72. {{ t('Documentary.AgentBackground.item1') }}
  73. </button>
  74. </template>
  75. <template #applyIb="{ row }">
  76. <button v-if="row.ibStatus == 1 && row.belongsType != 1" class="action-btn"
  77. @click.stop="handleMenuClick({type: 'applyIb', row})">{{ t('Home.msg.ibTitle') }}
  78. </button>
  79. <view v-else>--</view>
  80. </template>
  81. <template #Point="{ row }">
  82. <button v-if="row.belongsType != 1" class="action-btn" @click.stop="handleMenuClick({type: 'Point', row})">
  83. {{ t('Home.msg.ibTitle') }}
  84. </button>
  85. <view v-else>--</view>
  86. </template>
  87. </cwg-tabel>
  88. <!-- 跟单全局设置 -->
  89. <DocumentaryDialog :visible="docVisible" :detail="formInfoRow" @close="closeDoc" @confirm="confirmDoc" />
  90. <!-- 开户调整 -->
  91. <PointDialog :visible="pointVisible" :detail="pointForm" @close="closePoint" @confirm="confirmPoint" />
  92. <!-- 申请成为代理 -->
  93. <ApplyIbDialog :visible="applyVisible" :detail="applyForm" @close="closeApply" @confirm="confirmApply" />
  94. </view>
  95. </cwg-page-wrapper>
  96. </template>
  97. <script setup lang="ts">
  98. import { computed, ref, onMounted, nextTick } from 'vue'
  99. import { useI18n } from 'vue-i18n'
  100. import Config from '@/config/index'
  101. const { t, locale } = useI18n()
  102. import { customApi } from '@/service/custom'
  103. import { ibApi } from '@/service/ib'
  104. import DocumentaryDialog from '@/pages/ib/components/documentaryDialog.vue'
  105. import PointDialog from '@/pages/ib/components/pointDialog.vue'
  106. import ApplyIbDialog from '@/pages/ib/components/applyIbDialog.vue'
  107. const { Code } = Config
  108. const statistics = ref({
  109. unverifiedNum: 0,
  110. unDepositNum: 0,
  111. depositNum: 0,
  112. })
  113. const search = ref({
  114. 'name': '',
  115. 'email': '',
  116. 'cId': '',
  117. // 1:未实名 2:未入金 3:已入金
  118. belongsType: null,
  119. })
  120. const filterFields = [
  121. { key: 'cId', type: 'input', label: 'CID', placeholder: 'CID', defaultValue: '' },
  122. {
  123. key: 'name',
  124. type: 'input',
  125. label: t('Ib.Custom.NameLabel'),
  126. placeholder: t('Ib.Custom.NameLabel'),
  127. defaultValue: '',
  128. },
  129. { key: 'email', type: 'input', label: t('Label.Email'), placeholder: t('Label.Email'), defaultValue: '' },
  130. ]
  131. const formInfoRow = ref({})
  132. const pointForm = ref({})
  133. const applyForm = ref({})
  134. const applyTable = ref([])
  135. const docVisible = ref(false)
  136. const pointVisible = ref(false)
  137. const applyVisible = ref(false)
  138. const tableRef = ref(null)
  139. // 表格列配置
  140. const columns = ref([
  141. {
  142. prop: 'cId',
  143. label: t('Label.CidAccount'),
  144. align: 'center',
  145. },
  146. {
  147. prop: 'name',
  148. label: t('Ib.Custom.NameLabel'),
  149. align: 'center',
  150. },
  151. {
  152. prop: 'email',
  153. label: t('Label.Email'),
  154. align: 'center',
  155. },
  156. {
  157. prop: 'countryEnName',
  158. label: t('Label.Nationality'),
  159. align: 'center',
  160. width: locale.value == 'en' ? 110 : 0,
  161. },
  162. {
  163. prop: 'addTime',
  164. label: t('Label.RegistrationTime'),
  165. align: 'center',
  166. width: locale.value == 'en' ? 110 : 0,
  167. },
  168. {
  169. prop: 'belongsType',
  170. label: t('Ib.Custom.CustomerStatus'),
  171. align: 'center',
  172. formatter: ({ row }) => row.belongsType == 1 ? t('Ib.Custom.Unverified') :
  173. row.belongsType == 2 ? t('Ib.Custom.UnDeposit') :
  174. row.belongsType == 3 ? t('Ib.Custom.Deposited') : '--',
  175. },
  176. {
  177. prop: 'ibStatus',
  178. label: t('Ib.Custom.ApplyAgent'),
  179. align: 'center',
  180. formatter: ({ row }) => row.ibStatus == 2 ? t('Ib.Custom.Yes') : t('Ib.Custom.No'),
  181. },
  182. {
  183. prop: 'action',
  184. label: t('Label.Action'),
  185. type: 'action',
  186. align: 'center',
  187. menuList: [
  188. {
  189. label: t('Documentary.AgentBackground.item1'),
  190. type: 'documentary',
  191. btnClick: (row) => handleMenuClick({ type: 'documentary', row }),
  192. show: true,
  193. },
  194. {
  195. label: t('Home.msg.ibTitle'),
  196. type: 'applyIb',
  197. btnClick: (row) => handleMenuClick({ type: 'applyIb', row }),
  198. show: (row) => row.ibStatus == 1 && row.belongsType != 1,
  199. },
  200. {
  201. label: t('Ib.Custom.AccountAdjust'),
  202. type: 'Point',
  203. btnClick: (row) => handleMenuClick({ type: 'Point', row }),
  204. show: (row) => row.belongsType != 1,
  205. },
  206. ],
  207. },
  208. ])
  209. const mobilePrimaryFields = ref([
  210. {
  211. prop: 'cId',
  212. label: t('Label.CidAccount'),
  213. align: 'center',
  214. },
  215. {
  216. prop: 'name',
  217. label: t('Ib.Custom.NameLabel'),
  218. align: 'center',
  219. },
  220. {
  221. prop: 'email',
  222. label: t('Label.Email'),
  223. align: 'center',
  224. },
  225. {
  226. prop: 'more',
  227. type: 'more',
  228. width: 20,
  229. align: 'right',
  230. },
  231. ])
  232. const listApi = ref(null)
  233. listApi.value = ibApi.customerSubs
  234. const handleSearch = (params: any) => {
  235. console.log(params)
  236. // 合并表单的过滤参数,同时保留当前的 belongsType 标签选中状态
  237. search.value = {
  238. ...params,
  239. belongsType: search.value.belongsType,
  240. }
  241. nextTick(() => {
  242. tableRef.value.refreshTable()
  243. })
  244. }
  245. const handleReset = (params: any) => {
  246. search.value = {
  247. ...params,
  248. belongsType: search.value.belongsType, // 保持当前的分类标签选中
  249. }
  250. nextTick(() => {
  251. tableRef.value.refreshTable()
  252. })
  253. }
  254. //选择belongsType(点击已选中的 tab 可反选取消)
  255. const chooseBelongsType = (belongsType) => {
  256. if (search.value.belongsType == belongsType) {
  257. search.value.belongsType = null // 反选:取消选中
  258. } else {
  259. search.value.belongsType = belongsType
  260. }
  261. // 切换 tab 时自动重新请求列表
  262. nextTick(() => {
  263. tableRef.value.refreshTable()
  264. })
  265. }
  266. // 下拉菜单配置
  267. const menuList = (row) => {
  268. return [
  269. {
  270. label: t('Documentary.AgentBackground.item1'),
  271. type: 'documentary',
  272. row,
  273. show: true,
  274. },
  275. {
  276. label: t('Home.msg.ibTitle'),
  277. type: 'applyIb',
  278. row,
  279. show: row.ibStatus == 1 && row.belongsType != 1,
  280. },
  281. {
  282. label: t('Ib.Custom.AccountAdjust'),
  283. type: 'Point',
  284. row,
  285. show: row.belongsType != 1,
  286. },
  287. ].filter((item) => item.show)
  288. }
  289. const handleMenuClick = (item) => {
  290. if (item.type == 'documentary') {
  291. const { cId, id, permissionDisplay } = item.row
  292. formInfoRow.value = {
  293. cId, id, permissionDisplay,
  294. }
  295. docVisible.value = true
  296. } else if (item.type == 'applyIb') {
  297. applyForm.value = item.row
  298. applyVisible.value = true
  299. } else if (item.type == 'Point') {
  300. pointForm.value = item.row
  301. nextTick(() => {
  302. pointVisible.value = true
  303. })
  304. }
  305. }
  306. //获取统计数
  307. const getStatistics = async () => {
  308. try {
  309. let res = await ibApi.customerSubsStatistics({})
  310. if (res.code == Code.StatusOK && res.data) {
  311. statistics.value = {
  312. unverifiedNum: res.data.unverifiedNum || 0,
  313. unDepositNum: res.data.unDepositNum || 0,
  314. depositNum: res.data.depositNum || 0,
  315. }
  316. }
  317. } catch (error) {
  318. }
  319. }
  320. onMounted(() => {
  321. getStatistics()
  322. })
  323. const closeDoc = () => {
  324. docVisible.value = false
  325. }
  326. const closeApply = () => {
  327. applyVisible.value = false
  328. }
  329. const closePoint = () => {
  330. pointVisible.value = false
  331. }
  332. const confirmDoc = () => {
  333. docVisible.value = false
  334. tableRef.value.refreshTable()
  335. }
  336. const confirmPoint = () => {
  337. pointVisible.value = false
  338. tableRef.value.refreshTable()
  339. }
  340. const confirmApply = () => {
  341. applyVisible.value = false
  342. tableRef.value.refreshTable()
  343. }
  344. </script>
  345. <style scoped lang="scss">
  346. @import "@/uni.scss";
  347. .search-content {
  348. display: flex;
  349. justify-content: space-between;
  350. align-items: flex-start;
  351. }
  352. .mobile-search-content {
  353. flex-direction: column;
  354. align-items: flex-start;
  355. gap: px2rpx(10);
  356. .search-tabs {
  357. padding: 0;
  358. margin: 0;
  359. }
  360. }
  361. .pc-search-content {
  362. flex-direction: row;
  363. align-items: flex-start;
  364. }
  365. .search-bar {
  366. display: flex;
  367. align-items: center;
  368. justify-content: flex-start;
  369. flex-wrap: wrap;
  370. gap: px2rpx(10);
  371. margin: px2rpx(16) 0;
  372. .cwg-combox,
  373. .uni-easyinput,
  374. .uni-date {
  375. width: px2rpx(180) !important;
  376. flex: none;
  377. }
  378. }
  379. .search-tabs {
  380. display: flex;
  381. align-items: center;
  382. flex-wrap: wrap;
  383. gap: px2rpx(10);
  384. margin: px2rpx(16) 0;
  385. .tab-item {
  386. display: flex;
  387. min-width: px2rpx(100);
  388. border: 1px solid #F0F0F0;
  389. border-radius: px2rpx(4);
  390. margin-right: px2rpx(5);
  391. height: px2rpx(33);
  392. line-height: px2rpx(33);
  393. justify-content: center;
  394. &.active {
  395. color: var(--color-white);
  396. background-color: var(--color-error);
  397. border-color: var(--color-error);
  398. }
  399. }
  400. }
  401. .action-btn {
  402. display: flex;
  403. //align-items: center;
  404. justify-content: center;
  405. }
  406. </style>