index.vue 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298
  1. <template>
  2. <div class="new-app-container layout-vertical">
  3. <YMTableSearch :loading="loading" :search-form="searchForm" @onValueChange="onValueChange" @submit="onSubmit">
  4. <template #dateRange="{ form }">
  5. <div style="margin-right: 20px">
  6. <el-date-picker v-model="form.dateRange" type="daterange" align="right" unlink-panels range-separator="至"
  7. start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd HH:mm:ss"
  8. :default-time="['00:00:00', '23:59:59']" :picker-options="pickerOptions" style="width: 260px">
  9. </el-date-picker>
  10. </div>
  11. </template>
  12. </YMTableSearch>
  13. <div style="margin-bottom: 12px; padding-left: 16px">
  14. <el-button type="primary" :disabled="selectedMemberIds.length === 0"
  15. @click="handleBatchSetChannel">批量设置通道</el-button>
  16. </div>
  17. <YMTable ref="table" :table-key="1" :index="true" :selection="true" @selection-change="handleSelectionChange"
  18. :table-options="tableOptions" :edit-form="editForm" :edit-config="editConfig" :columns="columns"
  19. :rows-actions="rowsActions" :show-summary="false" :apis="apis" :actionsWidth="100" @headerClick="headerClick"
  20. :custom-style="{ height: 'calc(100vh - 280px)' }">
  21. <template #agent="scoped">
  22. <div v-if="scoped.row.agent">
  23. <div>
  24. {{ scoped.row.agent.first_name || "--" }}
  25. </div>
  26. <div>
  27. ID: {{ scoped.row.agent.member_id }}
  28. </div>
  29. <div>
  30. 邀请码: {{ scoped.row.agent.user_code }}
  31. </div>
  32. </div>
  33. <span v-else style="color: #c0c4cc">--</span>
  34. </template>
  35. <!-- 注册来源 -->
  36. <template #from="{ row }">
  37. <span :style="{
  38. color: Number(row.from) === 1 ? '#05A58F' : Number(row.from) === 2 ? '#F3061F' : '#606266'
  39. }">
  40. {{
  41. Number(row.from) === 1 ? '真实用户' :
  42. Number(row.from) === 2 ? '虚拟用户' :
  43. Number(row.from) === -1 ? 'tg用户' :
  44. Number(row.from) === 0 ? '后台客服账号' : '--'
  45. }}
  46. </span>
  47. </template>
  48. <!-- 冻结金额 -->
  49. <template #wallet="{ row }">
  50. <span :style="{
  51. color: (row.wallet && Number(row.wallet.frozen_balance) > 0) ? '#F3061F' : '#606266'
  52. }">
  53. {{ row.wallet ? Number(row.wallet.frozen_balance) : '--' }}
  54. </span>
  55. </template>
  56. <!-- 余额宝金额 -->
  57. <template #yuebao="{ row }">
  58. <span :style="{
  59. color: (row.wallet && Number(row.wallet.yuebao) > 0) ? '#05A58F' : '#606266'
  60. }">
  61. {{ row.wallet ? Number(row.wallet.yuebao) : '--' }}
  62. </span>
  63. </template>
  64. <!-- 打码量 -->
  65. <template #total_consume="{ row }">
  66. <span :style="{
  67. color: Number(row.total_consume) > 0 ? '#18C80A' : '#606266'
  68. }">
  69. {{ row.total_consume != null ? Number(row.total_consume) : '--' }}
  70. </span>
  71. </template>
  72. <!-- 是否封号 -->
  73. <template #is_banned="{ row }">
  74. <span :style="{
  75. color: Number(row.is_banned) === 0 ? '#18C80A' : '#F3061F',
  76. fontWeight: 'bold'
  77. }">
  78. {{ Number(row.is_banned) === 0 ? '正常' : '封号' }}
  79. </span>
  80. </template>
  81. <!-- 是否封号 -->
  82. <template #is_banned="{ row }">
  83. <span :style="{
  84. color: Number(row.is_banned) === 0 ? '#18C80A' : '#F3061F'
  85. }">
  86. {{ Number(row.is_banned) === 0 ? '正常' : '封号' }}
  87. </span>
  88. </template>
  89. </YMTable>
  90. <!-- 用户详情弹窗 -->
  91. <Dialog ref="dialogDetail" :dialog-actions="{
  92. width: '800px',
  93. title: '用户详情',
  94. confirmButtom: false,
  95. }">
  96. <template slot="content">
  97. <!-- 原有详情展示区 -->
  98. <div style="display: flex; flex-wrap: wrap; gap: 12px; padding: 10px 0; max-height: 400px; overflow-y: auto;">
  99. <div v-for="item in detailItems" :key="item.label"
  100. style="flex: 0 0 calc(33.33% - 12px); background: #f5f7fa; border-radius: 6px; padding: 12px 16px; box-sizing: border-box; border: 1px solid #e4e7ed;">
  101. <div style="font-size: 13px; color: #909399; margin-bottom: 4px;">{{ item.label }}</div>
  102. <!-- 三方余额:显示数值 + 一键转出按钮 -->
  103. <div v-if="item.label === '三方余额'" style="display: flex; align-items: center; gap: 8px;">
  104. <span :style="{ color: item.color || '#303133', fontSize: '15px' }">{{ item.value }}</span>
  105. <el-button type="primary" size="mini"
  106. @click="sfBalanceTransferClick(detailData.member_id)">一键转出</el-button>
  107. </div>
  108. <!-- 其他字段 -->
  109. <div v-else :style="{ color: item.color || '#303133', fontSize: '15px', wordBreak: 'break-all' }">
  110. {{ item.value }}
  111. </div>
  112. </div>
  113. </div>
  114. <!-- ========== 新增:操作按钮区域 ========== -->
  115. <div style="margin-top: 16px; text-align: center; border-top: 1px solid #e4e7ed; padding-top: 16px;">
  116. <!-- 充值 -->
  117. <el-button type="primary" size="small"
  118. @click="rowsActions.find(a => a.key === 'upWallet').method(detailData)">
  119. 充值
  120. </el-button>
  121. <!-- 扣款 -->
  122. <el-button type="danger" size="small" @click="rowsActions.find(a => a.key === 'unWallet').method(detailData)">
  123. 扣款
  124. </el-button>
  125. <!-- 提现地址 -->
  126. <el-button type="info" size="small" @click="rowsActions.find(a => a.key === 'address').method(detailData)">
  127. 提现地址
  128. </el-button>
  129. <!-- 设置通道 -->
  130. <el-button type="warning" size="small"
  131. @click="rowsActions.find(a => a.key === 'setChannel').method(detailData)">
  132. 设置通道
  133. </el-button>
  134. <!-- 修改密码 -->
  135. <el-button type="primary" size="small"
  136. @click="rowsActions.find(a => a.key === 'modifyPassword').method(detailData)">
  137. 修改密码
  138. </el-button>
  139. <!-- 合并用户 -->
  140. <el-button type="danger" size="small"
  141. @click="rowsActions.find(a => a.key === 'secret_key').method(detailData)">
  142. 合并用户
  143. </el-button>
  144. <!-- 备注 -->
  145. <el-button type="info" size="small" @click="rowsActions.find(a => a.key === 'note').method(detailData)">
  146. 备注
  147. </el-button>
  148. <!-- 用户日志 -->
  149. <el-button type="primary" size="small" @click="rowsActions.find(a => a.key === 'log').method(detailData)">
  150. 用户日志
  151. </el-button>
  152. <!-- 封号(仅当未封号) -->
  153. <el-button v-if="detailData.is_banned === 0" type="danger" size="small"
  154. @click="rowsActions.find(a => a.key === 'banned').method(detailData)">
  155. 封号
  156. </el-button>
  157. <!-- 解封(仅当已封号) -->
  158. <el-button v-if="detailData.is_banned === 1" type="success" size="small"
  159. @click="rowsActions.find(a => a.key === 'jiefeng').method(detailData)">
  160. 解封
  161. </el-button>
  162. </div>
  163. </template>
  164. </Dialog>
  165. <Dialog ref="sfLog" :dialog-actions="{
  166. width: '800px',
  167. title: '三方余额',
  168. confirmButtom: false,
  169. }">
  170. <template slot="content">
  171. <div>
  172. <span style="padding-right: 10px">换算后总余额:{{ sfBalanceData.total_balance }}</span>
  173. <span style="padding-right: 10px">原始游戏币总余额:{{ sfBalanceData.total_game_balance }}</span>
  174. <span style="padding-right: 10px">当前换算比例:{{ sfBalanceData.rate }}</span>
  175. <el-button type="primary" @click="sfBalanceTransferClick(sfBalanceData.member_id)">一键转出</el-button>
  176. <!-- sfBalanceTransfer -->
  177. </div>
  178. <el-table :data="sfBalanceData.list" border stripe height="400px" v-loading="logLoading">
  179. <el-table-column prop="platform" label="平台名称" align="center" />
  180. <el-table-column prop="balance" label="平台余额" align="center" />
  181. </el-table>
  182. </template>
  183. </Dialog>
  184. <Dialog ref="dialog" :dialog-actions="{ width: '480px', title: '扣款金额' }" :loading="loading" @confirm="confirm">
  185. <template slot="content">
  186. <el-form ref="formDataref" :rules="rulesData" :model="formData" label-width="80px">
  187. <el-form-item label="扣款金额 " prop="amount">
  188. <el-input type="number" v-model="formData.amount" style="width: 300px" />
  189. </el-form-item>
  190. <el-form-item label="说明" prop="remark">
  191. <el-input v-model="formData.remark" style="width: 300px" />
  192. </el-form-item>
  193. </el-form>
  194. </template>
  195. </Dialog>
  196. <Dialog ref="dialog1" :dialog-actions="{ width: '480px', title: '充值金额' }" :loading="loading" @confirm="confirm">
  197. <template slot="content">
  198. <el-form ref="formDataref" :rules="rulesData" :model="formData" label-width="80px">
  199. <el-form-item label="充值类型" prop="change_type">
  200. <el-select v-model="formData.change_type" placeholder="请选择" style="width: 300px">
  201. <el-option v-for="item in listTypes" :key="item" :label="item" :value="item">
  202. </el-option>
  203. </el-select>
  204. </el-form-item>
  205. <el-form-item label="充值金额" prop="amount">
  206. <el-input type="number" v-model="formData.amount" style="width: 300px" />
  207. </el-form-item>
  208. <el-form-item label="说明" prop="remark">
  209. <el-input v-model="formData.remark" style="width: 300px" />
  210. </el-form-item>
  211. </el-form>
  212. </template>
  213. </Dialog>
  214. <Dialog ref="dialogAddress" :dialog-actions="{
  215. width: '720px',
  216. title: '提现地址',
  217. confirmButtom: false,
  218. }">
  219. <template slot="content">
  220. <div style="padding: 10px 0">
  221. <div style="margin-bottom: 24px">
  222. <h3 style="margin: 0 0 12px 0; color: #303133">
  223. 银行卡 / 支付账户
  224. </h3>
  225. <el-table :data="listDataBank" border stripe height="260" empty-text="暂无银行卡/支付账户">
  226. <el-table-column prop="bank_name" label="银行/渠道" width="140" />
  227. <el-table-column prop="account" label="户名" width="120" />
  228. <el-table-column prop="card_no" label="卡号/账号" />
  229. <el-table-column prop="alias" label="别名" />
  230. <el-table-column prop="channel" label="渠道码" width="100" />
  231. </el-table>
  232. </div>
  233. <div>
  234. <h3 style="margin: 0 0 12px 0; color: #303133">数字货币地址</h3>
  235. <el-table :data="listDataAddress" border stripe height="260" empty-text="暂无数字货币地址">
  236. <el-table-column prop="address" label="提现地址" />
  237. <el-table-column prop="alias" label="别名" width="180" />
  238. </el-table>
  239. </div>
  240. </div>
  241. </template>
  242. </Dialog>
  243. <Dialog ref="dialogLog" :dialog-actions="{
  244. width: '800px',
  245. title: '用户日志',
  246. confirmButtom: false,
  247. }">
  248. <template slot="content">
  249. <el-form :model="logFormData" inline label-width="80px">
  250. <el-form-item label="日期范围">
  251. <el-date-picker v-model="logFormData.dateRange" type="daterange" value-format="yyyy-MM-dd"
  252. range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" />
  253. </el-form-item>
  254. <el-form-item>
  255. <el-button type="primary" @click="getLogList">查询</el-button>
  256. </el-form-item>
  257. </el-form>
  258. <el-table :data="logTableData" border stripe height="400px" v-loading="logLoading">
  259. <el-table-column prop="login_ip" label="IP" align="center" />
  260. <el-table-column prop="created_at" label="时间" align="center" />
  261. </el-table>
  262. </template>
  263. </Dialog>
  264. <Dialog ref="dialogChannel" :dialog-actions="{ width: '480px', title: '设置通道组合' }" :loading="loading"
  265. @confirm="confirmChannel">
  266. <template slot="content">
  267. <el-form ref="channelFormRef" :rules="channelRules" :model="channelFormData" label-width="120px">
  268. <el-form-item label="通道组合" prop="recharge_channel_group_id">
  269. <el-select v-model="channelFormData.recharge_channel_group_id" placeholder="请选择通道组合" style="width: 280px">
  270. <el-option v-for="item in channelGroupList" :key="item.id" :label="item.name"
  271. :value="item.id"></el-option>
  272. </el-select>
  273. </el-form-item>
  274. </el-form>
  275. </template>
  276. </Dialog>
  277. <Dialog ref="dialogPassword" :dialog-actions="{ width: '480px', title: '修改密码' }" :loading="loading"
  278. @confirm="confirmPassword">
  279. <template slot="content">
  280. <el-form ref="pwdFormRef" :rules="pwdRules" :model="pwdFormData" label-width="100px">
  281. <el-form-item label="密码类型">
  282. <el-radio-group v-model="pwdFormData.pwdType">
  283. <el-radio label="login">登录密码</el-radio>
  284. <el-radio label="payment">资金密码</el-radio>
  285. </el-radio-group>
  286. </el-form-item>
  287. <el-form-item label="新密码" prop="newPassword">
  288. <el-input v-model="pwdFormData.newPassword" placeholder="请输入新密码" style="width: 280px" />
  289. </el-form-item>
  290. </el-form>
  291. </template>
  292. </Dialog>
  293. </div>
  294. </template>
  295. <script>
  296. import Dialog from "@/components/Dialog/index.vue";
  297. import {
  298. getChangeTypes,
  299. menucheck,
  300. sfBalance,
  301. sfBalanceTransfer,
  302. useraddress,
  303. userbanned,
  304. usermerge,
  305. usersetNote,
  306. walletdebiting,
  307. withdraw,
  308. userLoginLog,
  309. walletAddress,
  310. walletBankList,
  311. setUserRechargeChannelGroup,
  312. getRechargeChannelGroupList,
  313. setUserPassword,
  314. } from "@/api/data";
  315. export default {
  316. components: {
  317. Dialog,
  318. },
  319. computed: {
  320. searchForm() {
  321. return [
  322. {
  323. label: "用户名称",
  324. type: "input",
  325. prop: "username",
  326. clearable: false,
  327. },
  328. {
  329. label: "用户昵称",
  330. type: "input",
  331. prop: "like_first_name",
  332. clearable: false,
  333. },
  334. {
  335. label: "会员ID",
  336. type: "input",
  337. prop: "member_id",
  338. clearable: false,
  339. },
  340. {
  341. label: "用户等级",
  342. type: "input",
  343. prop: "level",
  344. clearable: false,
  345. },
  346. {
  347. label: "用户邀请码",
  348. labelWidth: "110px",
  349. type: "input",
  350. prop: "user_code",
  351. clearable: false,
  352. },
  353. {
  354. label: "邀请人邀请码",
  355. labelWidth: "110px",
  356. type: "input",
  357. prop: "agent_user_code",
  358. clearable: false,
  359. },
  360. {
  361. label: "充值通道",
  362. type: "select",
  363. prop: "recharge_channel_group_id",
  364. options: this.channelGroupList.map((group) => ({
  365. label: group.name,
  366. value: group.id,
  367. })),
  368. },
  369. {
  370. label: "注册来源",
  371. type: "select",
  372. prop: "from",
  373. default: "1",
  374. options: [
  375. { label: "tg用户", value: "-1" },
  376. { label: "后台客服账号", value: "0" },
  377. { label: "真实用户", value: "1" },
  378. { label: "虚拟用户", value: "2" },
  379. ],
  380. },
  381. {
  382. label: "新增用户",
  383. type: "slot",
  384. prop: "dateRange",
  385. slotName: "dateRange",
  386. },
  387. {
  388. label: "开始时间",
  389. prop: "start_time",
  390. isHide: true,
  391. },
  392. {
  393. label: "结束时间",
  394. prop: "end_time",
  395. isHide: true,
  396. },
  397. {
  398. label: "IP",
  399. type: "input",
  400. prop: "register_ip",
  401. clearable: false,
  402. },
  403. ];
  404. },
  405. detailItems() {
  406. if (!this.detailData || !Object.keys(this.detailData).length) return [];
  407. const row = this.detailData;
  408. return [
  409. { label: '会员ID', value: row.member_id },
  410. { label: '邀请码', value: row.user_code },
  411. { label: '会员昵称', value: row.first_name || '--' },
  412. {
  413. label: '注册来源',
  414. value: this.getFromText(row.from),
  415. color: Number(row.from) === 1 ? '#05A58F' : Number(row.from) === 2 ? '#F3061F' : '#606266'
  416. },
  417. { label: '飞机账号', value: row.username || '--' },
  418. {
  419. label: '上级邀请人',
  420. value: row.agent ? `${row.agent.first_name} (ID:${row.agent.member_id}, 邀请码:${row.agent.user_code})` : '--'
  421. },
  422. { label: '会员等级', value: row.level?.level_name || '--' },
  423. {
  424. label: '钱包余额',
  425. value: row.wallet ? Number(row.wallet.available_balance).toFixed(2) : '--',
  426. color: '#409EFF' // 蓝色
  427. },
  428. {
  429. label: '三方余额',
  430. value: row.third_game_total_balance != null ? Number(row.third_game_total_balance).toFixed(2) : '--',
  431. color: '#409EFF' // 蓝色
  432. },
  433. {
  434. label: '冻结金额',
  435. value: row.wallet ? Number(row.wallet.frozen_balance) : '--',
  436. color: (row.wallet && Number(row.wallet.frozen_balance) > 0) ? '#F3061F' : '#606266'
  437. },
  438. {
  439. label: '余额宝金额',
  440. value: row.wallet ? row.wallet.yuebao : '--',
  441. color: (row.wallet && Number(row.wallet.yuebao) > 0) ? '#05A58F' : '#606266'
  442. },
  443. {
  444. label: '打码量',
  445. value: row.total_consume != null ? Number(row.total_consume) : '--',
  446. color: Number(row.total_consume) > 0 ? '#18C80A' : '#606266'
  447. },
  448. { label: '充值通道', value: this.getChannelName(row.recharge_channel_group_id) },
  449. { label: '注册时间', value: row.created_at || '--' },
  450. { label: '新增用户', value: this.getNewUserType(row.created_at) },
  451. { label: '活跃时间', value: row.last_active_time || '--' },
  452. {
  453. label: '是否封号',
  454. value: row.is_banned === 0 ? '正常' : '封号',
  455. color: Number(row.is_banned) === 0 ? '#18C80A' : '#F3061F'
  456. },
  457. { label: '秘钥', value: row.secret_key || '--' },
  458. { label: '访客ID', value: row.visitor_id || '--' },
  459. { label: '备注', value: row.admin_note || '--' },
  460. { label: '手机号', value: row.phone || '--' },
  461. { label: 'IP', value: row.register_ip || '--' },
  462. ];
  463. }
  464. },
  465. data() {
  466. return {
  467. pwdFormData: {
  468. member_id: "",
  469. pwdType: "login",
  470. newPassword: "",
  471. },
  472. sfBalanceData: [],
  473. pwdRules: {
  474. newPassword: [
  475. { required: true, message: "请输入新密码", trigger: "blur" },
  476. ],
  477. },
  478. pickerOptions: {
  479. shortcuts: [
  480. {
  481. text: "今天",
  482. onClick(picker) {
  483. const start = new Date();
  484. start.setHours(0, 0, 0, 0);
  485. const end = new Date();
  486. end.setHours(23, 59, 59, 999);
  487. picker.$emit("pick", [start, end]);
  488. },
  489. },
  490. {
  491. text: "昨天",
  492. onClick(picker) {
  493. const start = new Date();
  494. start.setTime(start.getTime() - 3600 * 1000 * 24 * 1);
  495. start.setHours(0, 0, 0, 0);
  496. const end = new Date();
  497. end.setTime(end.getTime() - 3600 * 1000 * 24 * 1);
  498. end.setHours(23, 59, 59, 999);
  499. picker.$emit("pick", [start, end]);
  500. },
  501. },
  502. {
  503. text: "7天内",
  504. onClick(picker) {
  505. const start = new Date();
  506. start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
  507. start.setHours(0, 0, 0, 0);
  508. const end = new Date();
  509. end.setHours(23, 59, 59, 999);
  510. picker.$emit("pick", [start, end]);
  511. },
  512. },
  513. ],
  514. },
  515. selectedMemberIds: [],
  516. channelGroupList: [],
  517. channelFormData: {
  518. member_ids: [],
  519. recharge_channel_group_id: "",
  520. },
  521. channelRules: {
  522. recharge_channel_group_id: [
  523. { required: true, message: "请选择充值通道组合", trigger: "change" },
  524. ],
  525. },
  526. listDataBank: [],
  527. listDataAddress: [],
  528. listTypes: [],
  529. positive: true,
  530. SHOW: false,
  531. loading: false,
  532. queryData: {
  533. order: "",
  534. by: "",
  535. },
  536. visibleBasicDrawer: false,
  537. renderContent: "",
  538. rowsActions: [
  539. {
  540. key: "view",
  541. label: "查看",
  542. method: (row) => this.showDetail(row)
  543. },
  544. {
  545. key: "upWallet",
  546. label: "充值",
  547. // 隐藏该按钮(列表不显示,保留数组项供详情弹窗调用)
  548. vIF: () => false,
  549. method: (row) => {
  550. this.positive = true;
  551. this.formData.member_id = row.member_id;
  552. this.$refs.dialog1.open();
  553. getChangeTypes().then((res) => {
  554. this.listTypes = res.data;
  555. });
  556. },
  557. },
  558. {
  559. key: "unWallet",
  560. label: "扣款",
  561. // 隐藏该按钮(列表不显示,保留数组项供详情弹窗调用)
  562. vIF: () => false,
  563. method: (row) => {
  564. this.positive = false;
  565. this.formData.member_id = row.member_id;
  566. this.$refs.dialog.open();
  567. },
  568. },
  569. {
  570. key: "address",
  571. label: "提现地址",
  572. // 隐藏该按钮(列表不显示,保留数组项供详情弹窗调用)
  573. vIF: () => false,
  574. method: (row) => this.showWithdrawalAddress(row),
  575. },
  576. {
  577. key: "setChannel",
  578. label: "设置通道",
  579. // 隐藏该按钮(列表不显示,保留数组项供详情弹窗调用)
  580. vIF: () => false,
  581. method: (row) =>
  582. this.openChannelDialog(
  583. [row.member_id],
  584. row.recharge_channel_group_id
  585. ),
  586. },
  587. {
  588. key: "modifyPassword",
  589. label: "修改密码",
  590. // 隐藏该按钮(列表不显示,保留数组项供详情弹窗调用)
  591. vIF: () => false,
  592. method: (row) => {
  593. this.pwdFormData.member_id = row.member_id;
  594. this.pwdFormData.pwdType = "login";
  595. this.pwdFormData.newPassword = "";
  596. this.$refs.dialogPassword.open();
  597. this.$nextTick(() => {
  598. this.$refs.pwdFormRef && this.$refs.pwdFormRef.clearValidate();
  599. });
  600. },
  601. },
  602. {
  603. key: "secret_key",
  604. label: "合并用户",
  605. // 隐藏该按钮(列表不显示,保留数组项供详情弹窗调用)
  606. vIF: () => false,
  607. method: (row) => {
  608. this.$prompt("请输入被合并的用户的秘钥", "提示", {
  609. confirmButtonText: "确定",
  610. cancelButtonText: "取消",
  611. inputPattern: /^\S+$/,
  612. inputErrorMessage: "请输入被合并的用户的秘钥",
  613. }).then(({ value }) => {
  614. usermerge({
  615. secret_key: value,
  616. member_id: row.member_id,
  617. }).then((res) => {
  618. this.$message({
  619. type: "success",
  620. message: "合并成功!",
  621. });
  622. this.onSubmit(this.queryData);
  623. });
  624. });
  625. },
  626. },
  627. {
  628. key: "note",
  629. label: "备注",
  630. // 隐藏该按钮(列表不显示,保留数组项供详情弹窗调用)
  631. vIF: () => false,
  632. method: (row) => {
  633. this.$prompt("请输入备注", "备注", {
  634. confirmButtonText: "确定",
  635. cancelButtonText: "取消",
  636. inputPattern: /^\S+$/,
  637. inputValue: row.admin_note,
  638. inputErrorMessage: "请输入备注",
  639. }).then(({ value }) => {
  640. usersetNote({
  641. member_id: row.member_id,
  642. admin_note: value,
  643. }).then(() => {
  644. this.onSubmit(this.queryData);
  645. this.$message({
  646. type: "success",
  647. message: "操作成功",
  648. });
  649. });
  650. });
  651. },
  652. },
  653. {
  654. key: "log",
  655. label: "用户日志",
  656. // 隐藏该按钮(列表不显示,保留数组项供详情弹窗调用)
  657. vIF: () => false,
  658. method: (row) => {
  659. this.logFormData.user_id = row.member_id;
  660. this.logFormData.dateRange = [];
  661. this.logTableData = [];
  662. this.$refs.dialogLog.open();
  663. this.getLogList();
  664. },
  665. },
  666. {
  667. key: "banned",
  668. label: "封号",
  669. // 隐藏该按钮(列表不显示,保留数组项供详情弹窗调用)
  670. vIF: () => false,
  671. method: (row) => {
  672. this.$confirm(
  673. "此操作将修改用户是否封号,保存封号修改?",
  674. "是否封号",
  675. {
  676. distinguishCancelAndClose: true,
  677. confirmButtonText: "封号",
  678. cancelButtonText: "取消",
  679. }
  680. ).then(() => {
  681. userbanned({
  682. member_id: row.member_id,
  683. is_banned: 1,
  684. }).then((res) => {
  685. this.onSubmit(this.queryData);
  686. this.$message({
  687. type: "warning",
  688. message: "保存修改为封号!",
  689. });
  690. });
  691. });
  692. },
  693. },
  694. {
  695. key: "jiefeng",
  696. label: "解封",
  697. // 隐藏该按钮(列表不显示,保留数组项供详情弹窗调用)
  698. vIF: () => false,
  699. method: (row) => {
  700. this.$confirm(
  701. "此操作将修改用户是否解封,保存解封修改?",
  702. "是否解封",
  703. {
  704. distinguishCancelAndClose: true,
  705. confirmButtonText: "正常",
  706. cancelButtonText: "取消",
  707. }
  708. ).then(() => {
  709. userbanned({
  710. member_id: row.member_id,
  711. is_banned: 0,
  712. }).then((res) => {
  713. this.onSubmit(this.queryData);
  714. this.$message({
  715. type: "success",
  716. message: "保存修改为正常!",
  717. });
  718. });
  719. });
  720. },
  721. },
  722. ],
  723. apis: {
  724. list: {
  725. url: "user",
  726. dataParam: "data.data",
  727. resultParam: "data",
  728. init: false,
  729. },
  730. },
  731. tableOptions: {
  732. add: {
  733. show: false,
  734. initData: () => {
  735. return { test: "test" };
  736. },
  737. },
  738. },
  739. editForm: [
  740. {
  741. body: [],
  742. },
  743. ],
  744. editConfig: {},
  745. formData: {
  746. amount: "",
  747. member_id: "",
  748. remark: "",
  749. change_type: "",
  750. },
  751. rulesData: {
  752. change_type: [{ required: true, message: "请输入", trigger: "blur" }],
  753. amount: [{ required: true, message: "请输入", trigger: "blur" }],
  754. remark: [{ required: true, message: "请输入", trigger: "blur" }],
  755. },
  756. logFormData: {
  757. user_id: "",
  758. dateRange: [],
  759. },
  760. logTableData: [],
  761. logLoading: false,
  762. columns: [
  763. {
  764. label: "会员ID",
  765. prop: "member_id",
  766. },
  767. {
  768. label: "邀请码",
  769. prop: "user_code",
  770. },
  771. {
  772. label: "会员昵称",
  773. popover: true,
  774. width: 140,
  775. props: ["first_name"],
  776. tagType: (row) => (row?.status === 1 ? "danger" : ""),
  777. options: [
  778. {
  779. label: "会员ID",
  780. value: ["member_id"],
  781. },
  782. {
  783. label: "用户名称",
  784. value: ["username"],
  785. },
  786. {
  787. label: "备注",
  788. vShow: (row) => (row?.admin_note ? true : false),
  789. value: ["admin_note"],
  790. },
  791. ],
  792. },
  793. {
  794. label: "注册来源",
  795. prop: "from",
  796. method: (row) => {
  797. const map = {
  798. "-1": "tg用户",
  799. 0: "后台客服账号",
  800. 1: "真实用户",
  801. 2: "虚拟用户",
  802. };
  803. return map[row.from] || "--";
  804. },
  805. },
  806. {
  807. label: "飞机账号",
  808. prop: "username",
  809. },
  810. {
  811. label: "上级邀请人",
  812. prop: "agent",
  813. width: 240,
  814. },
  815. {
  816. label: "会员等级",
  817. prop: "level",
  818. method: (row) => row.level?.level_name || "--",
  819. },
  820. {
  821. label: "钱包余额",
  822. link: true,
  823. // sortable: "custom",
  824. method: (row) =>
  825. row.wallet
  826. ? `${Number(row.wallet.available_balance).toFixed(2)}`
  827. : "--",
  828. click: (row) => {
  829. this.$router.push({
  830. name: "balance",
  831. query: {
  832. member_id: row.member_id,
  833. },
  834. });
  835. }
  836. },
  837. {
  838. label: "三方余额",
  839. link: true,
  840. // sortable: "custom",
  841. method: (row) =>
  842. row.wallet
  843. ? `${Number(row.third_game_total_balance).toFixed(2)}`
  844. : "--",
  845. click: (row) => {
  846. this.$refs.sfLog.open();
  847. this.getSfList(row.member_id);
  848. },
  849. },
  850. {
  851. label: "冻结金额",
  852. prop: "wallet",
  853. method: (row) =>
  854. row.wallet ? Number(row.wallet.frozen_balance) : "--",
  855. },
  856. {
  857. label: "余额宝金额",
  858. prop: "yuebao",
  859. method: (row) => (row.wallet ? row.wallet.yuebao : "--"),
  860. },
  861. {
  862. label: "打码量",
  863. prop: "total_consume",
  864. },
  865. {
  866. label: "充值通道",
  867. prop: "recharge_channel",
  868. method: (row) => {
  869. return (
  870. this.channelGroupList.find(
  871. (group) => group.id === row.recharge_channel_group_id
  872. )?.name || "--"
  873. );
  874. },
  875. },
  876. {
  877. label: "注册时间",
  878. prop: "created_at",
  879. // sortable: "custom",
  880. },
  881. {
  882. label: "新增用户",
  883. prop: "created_at",
  884. method: (row) => {
  885. if (!row.created_at) return "--";
  886. const targetDate = new Date(row.created_at.replace(/-/g, "/"));
  887. const targetTime = targetDate.getTime();
  888. const now = new Date();
  889. const todayStart = new Date(
  890. now.getFullYear(),
  891. now.getMonth(),
  892. now.getDate()
  893. ).getTime();
  894. const yesterdayStart = todayStart - 24 * 60 * 60 * 1000;
  895. const sevenDaysAgoStart = todayStart - 7 * 24 * 60 * 60 * 1000;
  896. if (targetTime >= todayStart) {
  897. return "今天";
  898. } else if (
  899. targetTime >= yesterdayStart &&
  900. targetTime < todayStart
  901. ) {
  902. return "昨天";
  903. } else if (
  904. targetTime >= sevenDaysAgoStart &&
  905. targetTime < yesterdayStart
  906. ) {
  907. return "7天内";
  908. } else {
  909. return "7天前";
  910. }
  911. },
  912. },
  913. {
  914. label: "活跃时间",
  915. // sortable: "custom",
  916. prop: "last_active_time",
  917. },
  918. {
  919. label: "是否封号",
  920. prop: "is_banned",
  921. method: (row) => (row.is_banned === 0 ? "正常" : "封号"),
  922. },
  923. {
  924. label: "秘钥",
  925. popover: true,
  926. default: "秘钥",
  927. width: 70,
  928. options: [
  929. {
  930. label: "找回账号的秘钥",
  931. value: ["secret_key"],
  932. },
  933. ],
  934. },
  935. {
  936. label: "访客ID",
  937. default: "访客ID",
  938. width: 80,
  939. popover: true,
  940. options: [
  941. {
  942. label: "访客ID",
  943. value: ["visitor_id"],
  944. },
  945. ],
  946. },
  947. {
  948. label: "备注",
  949. prop: "admin_note",
  950. },
  951. {
  952. label: "手机号",
  953. prop: "phone",
  954. },
  955. {
  956. label: "IP",
  957. prop: "register_ip",
  958. },
  959. ],
  960. detailData: {},
  961. };
  962. },
  963. mounted() {
  964. this.onSubmit();
  965. menucheck("admin/wallet/topUp").then(({ code }) => {
  966. this.rowsActions[0].vIF = () => code === 0;
  967. });
  968. getRechargeChannelGroupList({ page: 1, limit: 1000 })
  969. .then((res) => {
  970. this.channelGroupList = res.data?.data || [];
  971. })
  972. .catch((err) => console.log("获取通道组合失败", err));
  973. },
  974. methods: {
  975. getSfList(member_id) {
  976. this.logLoading = true;
  977. const params = {
  978. member_id: member_id,
  979. };
  980. sfBalance(params)
  981. .then((res) => {
  982. this.sfBalanceData = res.data;
  983. console.log(this.sfBalanceData);
  984. this.logLoading = false;
  985. })
  986. .catch(() => {
  987. this.logLoading = false;
  988. });
  989. },
  990. // 转换注册来源文字
  991. getFromText(from) {
  992. const map = {
  993. '-1': 'tg用户',
  994. '0': '后台客服账号',
  995. '1': '真实用户',
  996. '2': '虚拟用户'
  997. };
  998. return map[from] || '--';
  999. },
  1000. // 根据通道组合ID获取名称
  1001. getChannelName(id) {
  1002. const group = this.channelGroupList.find(g => g.id === id);
  1003. return group ? group.name : '--';
  1004. },
  1005. // 计算新增用户分类(与列表逻辑一致)
  1006. getNewUserType(created_at) {
  1007. if (!created_at) return '--';
  1008. const targetDate = new Date(created_at.replace(/-/g, '/'));
  1009. const targetTime = targetDate.getTime();
  1010. const now = new Date();
  1011. const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
  1012. const yesterdayStart = todayStart - 24 * 60 * 60 * 1000;
  1013. const sevenDaysAgoStart = todayStart - 7 * 24 * 60 * 60 * 1000;
  1014. if (targetTime >= todayStart) return '今天';
  1015. else if (targetTime >= yesterdayStart && targetTime < todayStart) return '昨天';
  1016. else if (targetTime >= sevenDaysAgoStart && targetTime < yesterdayStart) return '7天内';
  1017. else return '7天前';
  1018. },
  1019. // 打开详情弹窗
  1020. showDetail(row) {
  1021. this.detailData = { ...row };
  1022. this.$refs.dialogDetail.open();
  1023. },
  1024. sfBalanceTransferClick(member_id) {
  1025. this.logLoading = true;
  1026. const params = {
  1027. member_id: member_id,
  1028. };
  1029. sfBalanceTransfer(params)
  1030. .then((res) => {
  1031. this.logLoading = false;
  1032. this.$notify({
  1033. title: "成功",
  1034. message: "操作成功",
  1035. type: "success",
  1036. });
  1037. this.getSfList(member_id);
  1038. this.onSubmit(this.queryData);
  1039. })
  1040. .catch(() => {
  1041. this.logLoading = false;
  1042. });
  1043. },
  1044. confirmPassword() {
  1045. this.$refs.pwdFormRef.validate((valid) => {
  1046. if (valid) {
  1047. this.loading = true;
  1048. const payload = {
  1049. member_id: this.pwdFormData.member_id,
  1050. };
  1051. if (this.pwdFormData.pwdType === "login") {
  1052. payload.password = this.pwdFormData.newPassword;
  1053. } else {
  1054. payload.payment_password = this.pwdFormData.newPassword;
  1055. }
  1056. setUserPassword(payload)
  1057. .then((res) => {
  1058. this.loading = false;
  1059. if (res.code === 0) {
  1060. this.$notify({
  1061. title: "成功",
  1062. message: "修改成功",
  1063. type: "success",
  1064. });
  1065. this.$refs.dialogPassword.close();
  1066. } else {
  1067. this.$message.error(res.msg || "修改失败");
  1068. }
  1069. })
  1070. .catch(() => {
  1071. this.loading = false;
  1072. });
  1073. }
  1074. });
  1075. },
  1076. handleSelectionChange(val) {
  1077. this.selectedMemberIds = val.map((item) => item.member_id);
  1078. },
  1079. handleBatchSetChannel() {
  1080. this.openChannelDialog(this.selectedMemberIds, "");
  1081. },
  1082. openChannelDialog(ids, groupId) {
  1083. this.channelFormData.member_ids = ids;
  1084. this.channelFormData.recharge_channel_group_id = groupId || "";
  1085. this.$refs.dialogChannel.open();
  1086. if (this.channelGroupList.length === 0) {
  1087. getRechargeChannelGroupList({ page: 1, limit: 1000 })
  1088. .then((res) => {
  1089. this.channelGroupList = res.data?.data || [];
  1090. })
  1091. .catch((err) => console.log("获取通道组合失败", err));
  1092. }
  1093. },
  1094. confirmChannel() {
  1095. this.$refs.channelFormRef.validate((valid) => {
  1096. if (valid) {
  1097. this.loading = true;
  1098. setUserRechargeChannelGroup({
  1099. member_id: this.channelFormData.member_ids,
  1100. recharge_channel_group_id:
  1101. this.channelFormData.recharge_channel_group_id,
  1102. })
  1103. .then((res) => {
  1104. this.loading = false;
  1105. if (res.code === 0) {
  1106. this.$notify({
  1107. title: "成功",
  1108. message: "设置成功",
  1109. type: "success",
  1110. });
  1111. this.$refs.dialogChannel.close();
  1112. this.onSubmit(this.queryData);
  1113. } else {
  1114. this.$message.error(res.msg || "设置失败");
  1115. }
  1116. })
  1117. .catch(() => {
  1118. this.loading = false;
  1119. });
  1120. }
  1121. });
  1122. },
  1123. showWithdrawalAddress(row) {
  1124. this.listDataBank = [];
  1125. this.listDataAddress = [];
  1126. Promise.all([
  1127. walletBankList({ member_id: row.member_id }),
  1128. walletAddress({ member_id: row.member_id }),
  1129. ])
  1130. .then(([bankRes, addrRes]) => {
  1131. this.listDataBank = bankRes?.data?.list || [];
  1132. this.listDataAddress = addrRes?.data?.list || [];
  1133. this.$refs.dialogAddress.open();
  1134. })
  1135. .catch(() => {
  1136. this.$message.error("获取提现地址失败");
  1137. });
  1138. },
  1139. headerClick(column) {
  1140. const order =
  1141. column.order === "ascending"
  1142. ? "asc"
  1143. : column.order === "descending"
  1144. ? "desc"
  1145. : "";
  1146. let byValue = "";
  1147. if (column.label === "钱包余额") {
  1148. byValue = "available_balance";
  1149. }
  1150. if (column.label === "注册时间") {
  1151. byValue = "created_at";
  1152. }
  1153. if (column.label === "活跃时间") {
  1154. byValue = "last_active_time";
  1155. }
  1156. if (order) {
  1157. this.$set(this.queryData, "by", byValue);
  1158. this.$set(this.queryData, "order", order);
  1159. this.onSubmit(this.queryData);
  1160. } else {
  1161. this.$delete(this.queryData, "by");
  1162. this.$delete(this.queryData, "order");
  1163. this.onSubmit(this.queryData);
  1164. }
  1165. },
  1166. confirm() {
  1167. console.log("提交数据:", this.formData);
  1168. this.$refs.formDataref.validate((valid) => {
  1169. let fn = withdraw;
  1170. if (valid) {
  1171. this.loading = true;
  1172. if (!this.positive) {
  1173. fn = walletdebiting;
  1174. }
  1175. fn({
  1176. member_id: this.formData.member_id,
  1177. remark: this.formData.remark,
  1178. amount: this.formData.amount,
  1179. change_type: this.formData.change_type,
  1180. })
  1181. .then((res) => {
  1182. this.loading = false;
  1183. if (res.code === 0) {
  1184. this.$notify({
  1185. title: "成功",
  1186. message: "操作成功",
  1187. type: "success",
  1188. });
  1189. this.$refs.dialog.close();
  1190. this.$refs.dialog1.close();
  1191. this.onSubmit(this.queryData);
  1192. this.$refs.formDataref.resetFields();
  1193. }
  1194. })
  1195. .catch((err) => {
  1196. this.loading = false;
  1197. });
  1198. } else {
  1199. console.log("error submit!!");
  1200. return false;
  1201. }
  1202. });
  1203. },
  1204. onValueChange(val) { },
  1205. onSubmit(params = {}) {
  1206. this.queryData = params;
  1207. const reqData = { ...params };
  1208. // 注册来源默认真实用户:仅当未通过表单指定(undefined)时兜底,
  1209. // 用户手动选择/清空后则尊重所选值
  1210. if (reqData.from === undefined) {
  1211. reqData.from = "1";
  1212. }
  1213. if (reqData.dateRange && reqData.dateRange.length === 2) {
  1214. reqData.start_time = reqData.dateRange[0].split(" ")[0];
  1215. reqData.end_time = reqData.dateRange[1].split(" ")[0];
  1216. } else if (
  1217. reqData.dateRange === null ||
  1218. reqData.dateRange === undefined ||
  1219. reqData.dateRange.length === 0
  1220. ) {
  1221. reqData.start_time = "";
  1222. reqData.end_time = "";
  1223. }
  1224. delete reqData.dateRange;
  1225. this.$refs.table.getList(reqData);
  1226. },
  1227. getLogList() {
  1228. this.logLoading = true;
  1229. const params = {
  1230. user_id: this.logFormData.user_id,
  1231. start_time: this.logFormData.dateRange
  1232. ? this.logFormData.dateRange[0]
  1233. : "",
  1234. end_time: this.logFormData.dateRange
  1235. ? this.logFormData.dateRange[1]
  1236. : "",
  1237. };
  1238. userLoginLog(params)
  1239. .then((res) => {
  1240. this.logTableData = res.data?.data || [];
  1241. this.logLoading = false;
  1242. })
  1243. .catch(() => {
  1244. this.logLoading = false;
  1245. });
  1246. },
  1247. },
  1248. };
  1249. </script>