index.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808
  1. <template>
  2. <div class="p-2">
  3. <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
  4. <div v-show="showSearch" class="mb-[10px]">
  5. <el-card shadow="hover">
  6. <el-form ref="queryFormRef" :model="queryParams" :inline="true">
  7. <el-form-item label="用户昵称" prop="loginName">
  8. <el-input v-model="queryParams.nickName" placeholder="请输入用户昵称" clearable @keyup.enter="handleQuery" />
  9. </el-form-item>
  10. <el-form-item label="手机号" prop="phone">
  11. <el-input v-model="queryParams.phone" placeholder="请输入手机号" clearable @keyup.enter="handleQuery" />
  12. </el-form-item>
  13. <el-form-item label="注册时间">
  14. <el-date-picker
  15. v-model="data.queryParams.registerTimeRange"
  16. type="datetimerange"
  17. range-separator="至"
  18. start-placeholder="开始时间"
  19. end-placeholder="结束时间"
  20. value-format="YYYY-MM-DD HH:mm:ss"
  21. format="YYYY-MM-DD HH:mm:ss"
  22. />
  23. </el-form-item>
  24. <el-form-item label="登录时间">
  25. <el-date-picker
  26. v-model="data.queryParams.loginTimeRange"
  27. type="datetimerange"
  28. range-separator="至"
  29. start-placeholder="开始时间"
  30. end-placeholder="结束时间"
  31. value-format="YYYY-MM-DD HH:mm:ss"
  32. format="YYYY-MM-DD HH:mm:ss"
  33. />
  34. </el-form-item>
  35. <el-form-item>
  36. <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
  37. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  38. </el-form-item>
  39. </el-form>
  40. </el-card>
  41. </div>
  42. </transition>
  43. <el-card shadow="never">
  44. <template #header>
  45. <el-row :gutter="10" class="mb8">
  46. <el-col :span="1.5">
  47. <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['business:user:export']">导出</el-button>
  48. </el-col>
  49. </el-row>
  50. </template>
  51. <!-- <template #header>
  52. <el-row :gutter="10" class="mb8">
  53. &lt;!&ndash; <el-col :span="1.5">
  54. <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['business:user:add']">新增</el-button>
  55. </el-col>&ndash;&gt;
  56. &lt;!&ndash; <el-col :span="1.5">
  57. <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['business:user:edit']"
  58. >修改</el-button
  59. >
  60. </el-col>&ndash;&gt;
  61. &lt;!&ndash; <el-col :span="1.5">
  62. <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['business:user:remove']"
  63. >删除</el-button
  64. >
  65. </el-col>&ndash;&gt;
  66. &lt;!&ndash; <el-col :span="1.5">
  67. <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['business:user:export']">导出</el-button>
  68. </el-col>&ndash;&gt;
  69. <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
  70. </el-row>
  71. </template>-->
  72. <el-table v-loading="loading" border :data="userList" @selection-change="handleSelectionChange">
  73. <!-- <el-table-column type="selection" width="55" align="center" />-->
  74. <el-table-column label="用户id" align="center" prop="id" v-if="true" />
  75. <el-table-column label="登录名称" align="center">
  76. <template #default="scope">
  77. <span>{{ scope.row.loginName || scope.row.phone }}</span>
  78. </template>
  79. </el-table-column>
  80. <el-table-column label="用户姓名" align="center" prop="realName" />
  81. <el-table-column label="用户昵称" align="center" prop="nickName" />
  82. <el-table-column label="用户手机号码" align="center" prop="phone" />
  83. <el-table-column label="用户等级" align="center" prop="currentLevelCode" />
  84. <el-table-column label="注册时间" align="center" prop="createAt" width="180">
  85. <template #default="scope">
  86. <span>{{ parseTime(scope.row.createAt, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
  87. </template>
  88. </el-table-column>
  89. <el-table-column label="最新上线时间" align="center" prop="createAt" width="180">
  90. <template #default="scope">
  91. <span>{{ parseTime(scope.row.lastLoginTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
  92. </template>
  93. </el-table-column>
  94. <el-table-column label="版本" align="center" prop="appVersion" />
  95. <el-table-column label="积分数" align="center" prop="pointsQuantity" />
  96. <el-table-column label="门票数" align="center" prop="itemsQuantity" />
  97. <el-table-column label="比赛数" align="center" prop="tournamentCount" />
  98. <el-table-column label="账号状态" align="center" prop="statusText" />
  99. <el-table-column label="注销状态" align="center" prop="deactivateStatus">
  100. <template #default="scope">
  101. <el-tag v-if="scope.row.deactivateStatus === 0" type="success">正常</el-tag>
  102. <el-tag v-else-if="scope.row.deactivateStatus === 1" type="warning">注销中</el-tag>
  103. <el-tag v-else type="danger">已注销</el-tag>
  104. </template>
  105. </el-table-column>
  106. <el-table-column label="操作" align="center" width="550" class-name="small-padding fixed-width">
  107. <template #default="scope">
  108. <div class="action-container">
  109. <el-tooltip content="查看" placement="top">
  110. <el-button link type="primary" icon="View" @click="handleUpdate(scope.row)">查看</el-button>
  111. </el-tooltip>
  112. <!-- 禁用/启用 按钮 -->
  113. <el-tooltip :content="scope.row.status === 1 ? '禁用' : '启用'" placement="top">
  114. <el-button
  115. link
  116. type="primary"
  117. :icon="scope.row.status === 1 ? 'Lock' : 'Unlock'"
  118. @click="toggleStatus(scope.row)"
  119. v-hasPermi="['business:user:edit']"
  120. >
  121. {{ scope.row.status === 1 ? '禁用' : '启用' }}
  122. </el-button>
  123. </el-tooltip>
  124. <el-tooltip content="删除" placement="top">
  125. <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['business:user:remove']">删除</el-button>
  126. </el-tooltip>
  127. <el-tooltip content="编辑道具" placement="top">
  128. <el-button link type="primary" icon="Tools" @click="openGrantDialog(scope.row)" v-hasPermi="['business:user:remove']"
  129. >编辑道具</el-button
  130. >
  131. </el-tooltip>
  132. <el-tooltip content="查看流水" placement="top">
  133. <el-button link type="primary" icon="View" @click="viewPointsLog(scope.row)" v-hasPermi="['business:user:edit']">查看流水</el-button>
  134. </el-tooltip>
  135. <el-tooltip content="查看道具" placement="top">
  136. <el-button link type="primary" icon="View" @click="viewPointsBag(scope.row)" v-hasPermi="['business:user:edit']">查看道具</el-button>
  137. </el-tooltip>
  138. </div>
  139. </template>
  140. </el-table-column>
  141. </el-table>
  142. <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
  143. </el-card>
  144. <!-- 查看用户详情对话框 -->
  145. <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
  146. <el-form ref="userFormRef" :model="form" :rules="rules" label-width="80px">
  147. <!--
  148. <el-form-item label="用户ID" prop="id">
  149. <el-input v-model="form.id" disabled />
  150. </el-form-item>
  151. -->
  152. <el-form-item label="登录名称" prop="loginName">
  153. <el-input v-model="form.phone" disabled />
  154. </el-form-item>
  155. <el-form-item label="用户姓名" prop="loginPass">
  156. <el-input v-model="form.realName" disabled />
  157. </el-form-item>
  158. <el-form-item label="手机号" prop="phone">
  159. <div style="display: flex; align-items: center; gap: 8px">
  160. <el-input v-model="form.phone" disabled />
  161. <el-button type="primary" size="small" @click="openEditPhoneDialog(form.id, form.phone)">修改</el-button>
  162. </div>
  163. </el-form-item>
  164. <el-form-item label="注册时间" prop="createAt">
  165. <el-date-picker clearable v-model="form.createAt" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" disabled> </el-date-picker>
  166. </el-form-item>
  167. <el-form-item label="上线时间" prop="lastLoginTime">
  168. <el-date-picker clearable v-model="form.lastLoginTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" disabled> </el-date-picker>
  169. </el-form-item>
  170. <el-form-item label="积分数" prop="email">
  171. <el-input v-model="form.pointsQuantity" disabled />
  172. </el-form-item>
  173. <el-form-item label="门票数" prop="nickName">
  174. <el-input v-model="form.itemsQuantity" disabled />
  175. </el-form-item>
  176. <el-form-item label="比赛数" prop="remark">
  177. <el-input v-model="form.tournamentCount" disabled />
  178. </el-form-item>
  179. </el-form>
  180. <template #footer>
  181. <div class="dialog-footer">
  182. <!-- <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>-->
  183. <el-button @click="cancel">取 消</el-button>
  184. </div>
  185. </template>
  186. </el-dialog>
  187. <!-- 编辑用户对话框 -->
  188. <el-dialog :title="editDialog.title" v-model="editDialog.visible" width="500px" append-to-body>
  189. <el-form ref="editUserFormRef" :model="editForm" :rules="rules2" label-width="80px">
  190. <el-form-item label="用户ID" prop="id" v-show="false">
  191. <el-input v-model="editForm.id" disabled />
  192. </el-form-item>
  193. <el-form-item label="手机号" prop="phone">
  194. <el-input v-model="editForm.phone" placeholder="请输入手机号" @input="handlePhoneInput" />
  195. </el-form-item>
  196. <el-form-item label="积分数" prop="score">
  197. <el-input v-model.number="editForm.score" />
  198. </el-form-item>
  199. <el-form-item label="门票数" prop="ticketCount">
  200. <el-input v-model.number="editForm.ticketCount" />
  201. </el-form-item>
  202. </el-form>
  203. <template #footer>
  204. <div class="dialog-footer">
  205. <el-button :loading="buttonLoading" type="primary" @click="submitEditForm">确 定</el-button>
  206. <el-button @click="cancelEdit">取 消</el-button>
  207. </div>
  208. </template>
  209. </el-dialog>
  210. <!-- 发放道具弹窗 -->
  211. <el-dialog v-model="grantDialogVisible" title="操作道具" width="400px">
  212. <div class="dialog-content">
  213. <!-- 左侧内容 -->
  214. <div class="left-content">
  215. <el-form :model="grantForm" ref="grantFormRef">
  216. <el-input v-model="grantForm.userId" disabled v-show="false" />
  217. <el-form-item label="手机号" prop="id">
  218. <el-input v-model="grantForm.phone" disabled />
  219. </el-form-item>
  220. <el-form-item label="道具类型" prop="itemId">
  221. <!-- 道具选择 -->
  222. <el-select v-model="grantForm.itemId" placeholder="请选择道具" style="width: 120px; margin-right: 8px">
  223. <el-option v-for="item in itemOptions" :key="item.id" :label="item.label" :value="item.id" />
  224. </el-select>
  225. </el-form-item>
  226. <el-form-item label="操作类型" prop="itemId">
  227. <el-select v-model="grantForm.type" placeholder="请操作类型" style="width: 120px; margin-right: 8px">
  228. <el-option label="增加" :value="1" />
  229. <el-option label="扣除" :value="2" />
  230. </el-select>
  231. </el-form-item>
  232. <el-form-item label="数量" prop="quantity">
  233. <el-input-number v-model="grantForm.quantity" :min="1" :max="999999" />
  234. </el-form-item>
  235. </el-form>
  236. </div>
  237. <!-- 右侧内容 -->
  238. <div class="right-content">
  239. <!-- 这里可以放置其他内容或留空 -->
  240. </div>
  241. </div>
  242. <template #footer>
  243. <span class="dialog-footer">
  244. <el-button @click="grantDialogVisible = false">取消</el-button>
  245. <el-button type="primary" @click="submitGrantForm">确认</el-button>
  246. </span>
  247. </template>
  248. </el-dialog>
  249. <el-dialog :title="editPhoneDialog.title" v-model="editPhoneDialog.visible" width="400px" center>
  250. <div class="dialog-content">
  251. <!-- 提示信息 -->
  252. <div class="tip-text">
  253. 此操作将替换原手机号:<strong>{{ editPhoneForm.oldPhone }}</strong
  254. >,请谨慎操作
  255. </div>
  256. <el-form ref="editPhoneFormRef" :model="editPhoneForm" :rules="editPhoneRules" label-width="80px" style="margin-top: 20px">
  257. <el-form-item label="新手机号" prop="newPhone">
  258. <el-input v-model="editPhoneForm.newPhone" placeholder="请输入新的手机号" />
  259. </el-form-item>
  260. <el-form-item label="验证码" prop="verifyCode">
  261. <div
  262. STYLE=" display: flex;
  263. align-items: center;
  264. gap: 8px;"
  265. >
  266. <el-input v-model="editPhoneForm.verifyCode" placeholder="请输入验证码" style="width: 180px" />
  267. <el-button type="primary" size="small" style="margin-left: 10px" :disabled="countdown > 0" @click="sendVerifyCode">
  268. {{ countdown > 0 ? `${countdown}s` : '发送验证码' }}
  269. </el-button>
  270. </div>
  271. </el-form-item>
  272. </el-form>
  273. </div>
  274. <template #footer>
  275. <span class="dialog-footer">
  276. <el-button @click="editPhoneDialog.visible = false">取消</el-button>
  277. <el-button type="primary" @click="submitEditPhone">确认修改</el-button>
  278. </span>
  279. </template>
  280. </el-dialog>
  281. </div>
  282. </template>
  283. <script setup name="User" lang="ts">
  284. import { listUser, getUser, delUser, addUser, updateUser, sendRewardToos, getSmsCode, updateUserPhone } from '@/api/system/business/apiUsers';
  285. import { UserVO, UserQuery, UserForm } from '@/api/system/business/apiUsers/types';
  286. import { selectItemsSelList } from '@/api/system/business/items';
  287. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  288. import { parseTime } from '@/utils/dateUtils';
  289. const userList = ref<UserVO[]>([]);
  290. const buttonLoading = ref(false);
  291. const loading = ref(true);
  292. const showSearch = ref(true);
  293. const ids = ref<Array<string | number>>([]);
  294. const single = ref(true);
  295. const multiple = ref(true);
  296. const total = ref(0);
  297. const queryFormRef = ref<ElFormInstance>();
  298. const userFormRef = ref<ElFormInstance>();
  299. const initFormData: UserForm = {
  300. id: undefined,
  301. loginName: undefined,
  302. loginPass: undefined,
  303. phone: undefined,
  304. createAt: undefined,
  305. updateAt: undefined,
  306. sex: undefined,
  307. email: undefined,
  308. nickName: undefined,
  309. remark: undefined,
  310. captcha: undefined,
  311. avatar: undefined,
  312. province: undefined,
  313. city: undefined,
  314. area: undefined,
  315. placeDetail: undefined,
  316. registerIp: undefined,
  317. registerDevice: undefined,
  318. status: undefined,
  319. isLocked: undefined,
  320. lastLoginTime: undefined,
  321. lastLoginIp: undefined
  322. };
  323. const data = reactive<PageData<UserForm, UserQuery>>({
  324. form: { ...initFormData },
  325. queryParams: {
  326. pageNum: 1,
  327. pageSize: 10,
  328. loginName: undefined,
  329. loginPass: undefined,
  330. phone: undefined,
  331. createAt: undefined,
  332. updateAt: undefined,
  333. sex: undefined,
  334. email: undefined,
  335. nickName: undefined,
  336. captcha: undefined,
  337. avatar: undefined,
  338. province: undefined,
  339. city: undefined,
  340. area: undefined,
  341. placeDetail: undefined,
  342. registerIp: undefined,
  343. registerDevice: undefined,
  344. status: undefined,
  345. isLocked: undefined,
  346. lastLoginTime: undefined,
  347. lastLoginIp: undefined,
  348. params: {}
  349. },
  350. rules: {
  351. id: [{ required: true, message: '不能为空', trigger: 'blur' }],
  352. loginName: [{ required: true, message: '登录名称不能为空', trigger: 'blur' }],
  353. loginPass: [{ required: true, message: '登录密码不能为空', trigger: 'blur' }]
  354. }
  355. });
  356. const editForm = ref<UserForm>({
  357. id: undefined,
  358. phone: '',
  359. ticketCount: '',
  360. score: ''
  361. });
  362. const rules2 = ref({
  363. phone: [
  364. { required: true, message: '手机号不能为空', trigger: 'blur' },
  365. {
  366. pattern: /^1[3-9]\d{9}$/,
  367. message: '请输入正确的手机号格式',
  368. trigger: 'blur'
  369. }
  370. ]
  371. });
  372. const submitEditForm = async () => {
  373. try {
  374. await updateUser(editForm.value); // 调用更新接口
  375. proxy?.$modal.msgSuccess('编辑成功');
  376. editDialog.visible = false;
  377. getList(); // 刷新列表
  378. } catch (error) {
  379. console.error(error);
  380. }
  381. };
  382. const cancelEdit = () => {
  383. editDialog.visible = false;
  384. };
  385. const handleEdit = async (row: UserVO) => {
  386. const userId = row.id;
  387. const res = await getUser(userId);
  388. editForm.value = { ...res.data }; // 把数据赋值给可编辑表单
  389. editDialog.visible = true;
  390. editDialog.title = '编辑';
  391. };
  392. const { queryParams, form, rules } = toRefs(data);
  393. /** 查询【请填写功能名称】列表 */
  394. const getList = async () => {
  395. loading.value = true;
  396. const res = await listUser(queryParams.value);
  397. userList.value = res.rows;
  398. total.value = res.total;
  399. loading.value = false;
  400. };
  401. /** 取消按钮 */
  402. const cancel = () => {
  403. reset();
  404. dialog.visible = false;
  405. };
  406. /** 表单重置 */
  407. const reset = () => {
  408. form.value = { ...initFormData };
  409. userFormRef.value?.resetFields();
  410. };
  411. /** 搜索按钮操作 */
  412. const handleQuery = () => {
  413. queryParams.value.pageNum = 1;
  414. const timeRange = data.queryParams.loginTimeRange;
  415. if (timeRange && timeRange.length === 2) {
  416. data.queryParams.loginBeginTime = timeRange[0];
  417. data.queryParams.loginEndTime = timeRange[1];
  418. } else {
  419. data.queryParams.loginBeginTime = null;
  420. data.queryParams.loginEndTime = null;
  421. }
  422. const timeRange2 = data.queryParams.registerTimeRange;
  423. if (timeRange2 && timeRange2.length === 2) {
  424. data.queryParams.registerBeginTime = timeRange2[0];
  425. data.queryParams.registerEndTime = timeRange2[1];
  426. } else {
  427. data.queryParams.registerBeginTime = null;
  428. data.queryParams.registerEndTime = null;
  429. }
  430. getList();
  431. };
  432. /** 重置按钮操作 */
  433. const resetQuery = () => {
  434. queryFormRef.value?.resetFields();
  435. // 重置时间范围选择器
  436. data.queryParams.registerTimeRange = '';
  437. data.queryParams.loginTimeRange = '';
  438. data.queryParams.nickName = '';
  439. handleQuery();
  440. };
  441. /** 多选框选中数据 */
  442. const handleSelectionChange = (selection: UserVO[]) => {
  443. ids.value = selection.map((item) => item.id);
  444. single.value = selection.length != 1;
  445. multiple.value = !selection.length;
  446. };
  447. /** 新增按钮操作 */
  448. const handleAdd = () => {
  449. reset();
  450. dialog.visible = true;
  451. dialog.title = '添加【请填写功能名称】';
  452. };
  453. /** 修改按钮操作 */
  454. const handleUpdate = async (row?: UserVO) => {
  455. reset();
  456. const _id = row?.id || ids.value[0];
  457. const res = await getUser(_id);
  458. Object.assign(form.value, res.data);
  459. dialog.visible = true;
  460. dialog.title = '查看【用户详情】';
  461. };
  462. const toggleStatus = (row) => {
  463. const newStatus = row.status === 1 ? 0 : 1;
  464. const confirmText = newStatus === 0 ? '禁用' : '启用';
  465. row.status = newStatus;
  466. // 弹出确认框
  467. ElMessageBox.confirm(`确认要${confirmText}用户【${row.nickName || row.loginName}】吗?`, '提示', {
  468. confirmButtonText: '确定',
  469. cancelButtonText: '取消',
  470. type: 'warning'
  471. })
  472. .then(() => {
  473. // 调用 API 接口更新状态
  474. updateUser(row).then((response) => {
  475. if (response.code === 200) {
  476. ElMessage.success(confirmText + '成功');
  477. // 刷新列表
  478. handleQuery();
  479. } else {
  480. ElMessage.error('操作失败:' + response.msg);
  481. }
  482. });
  483. })
  484. .catch(() => {
  485. // 取消操作
  486. });
  487. };
  488. /** 提交按钮 */
  489. const submitForm = () => {
  490. userFormRef.value?.validate(async (valid: boolean) => {
  491. if (valid) {
  492. buttonLoading.value = true;
  493. if (form.value.id) {
  494. await updateUser(form.value).finally(() => (buttonLoading.value = false));
  495. } else {
  496. await addUser(form.value).finally(() => (buttonLoading.value = false));
  497. }
  498. proxy?.$modal.msgSuccess('操作成功');
  499. dialog.visible = false;
  500. await getList();
  501. }
  502. });
  503. };
  504. /** 删除按钮操作 */
  505. const handleDelete = async (row?: UserVO) => {
  506. const _ids = row?.id || ids.value;
  507. await proxy?.$modal.confirm('是否确认删除【用户管理】编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
  508. await delUser(_ids);
  509. proxy?.$modal.msgSuccess('删除成功');
  510. await getList();
  511. };
  512. /** 导出按钮操作 */
  513. const handleExport = () => {
  514. proxy?.download(
  515. 'business/user/export',
  516. {
  517. ...queryParams.value
  518. },
  519. `用户管理${parseTime(new Date(), '{y}{m}{d}{h}{i}{s}')}.xlsx`
  520. );
  521. };
  522. const handlePhoneInput = (value: string) => {
  523. editForm.value.phone = value.replace(/[^0-9]/g, '');
  524. };
  525. onMounted(() => {
  526. getList();
  527. loadItemOptions();
  528. });
  529. onUnmounted(() => {
  530. if (countdownTimer.value) {
  531. clearInterval(countdownTimer.value);
  532. }
  533. });
  534. // 关闭弹窗时清除倒计时
  535. /*watch(
  536. () => editPhoneDialog.visible,
  537. (newVal) => {
  538. if (!newVal && countdownTimer.value) {
  539. clearInterval(countdownTimer.value);
  540. countdownTimer.value = null;
  541. countdown.value = 0;
  542. }
  543. }
  544. );*/
  545. // 下拉选项数据 selectBlingStructuresInfo
  546. const itemOptions = ref<{ id: number; label: string }[]>([]);
  547. // 加载报名条件选项
  548. const loadItemOptions = async () => {
  549. try {
  550. const res = await selectItemsSelList();
  551. if (res.code === 200) {
  552. // 使用 unknown 中间类型进行类型转换
  553. const data = res.data as unknown as { id: number; name: string }[];
  554. // 过滤掉 id === 2 的项,并映射为 { id, label } 结构
  555. const list = data
  556. .filter((item) => item.id !== 2) // ✅ 过滤掉 id 为 1 的选项
  557. .map((item) => ({
  558. id: item.id,
  559. label: item.name
  560. }));
  561. itemOptions.value = list;
  562. } else {
  563. alert('加载失败:' + res.msg);
  564. }
  565. } catch (error) {
  566. console.error('请求出错:', error);
  567. }
  568. };
  569. // 弹窗是否显示
  570. const grantDialogVisible = ref(false);
  571. // 表单数据
  572. const grantForm = ref({
  573. itemId: null,
  574. quantity: 1,
  575. phone: null,
  576. userId: null,
  577. type: 1
  578. });
  579. // 表单校验规则
  580. const grantRules = {
  581. itemId: [{ required: true, message: '请选择道具类型', trigger: 'change' }],
  582. quantity: [
  583. { required: true, message: '请输入数量', trigger: 'blur' },
  584. { type: 'number', min: 1, message: '数量必须大于0', trigger: 'blur' }
  585. ]
  586. };
  587. const currentUser = ref(null);
  588. // 打开弹窗
  589. const openGrantDialog = (row) => {
  590. currentUser.value = row;
  591. grantForm.value.phone = row.phone;
  592. grantForm.value.userId = row.id;
  593. grantForm.value.itemId = null;
  594. grantForm.value.quantity = 1;
  595. grantDialogVisible.value = true;
  596. };
  597. const submitGrantForm = async () => {
  598. try {
  599. if (grantForm.value.itemId == null) {
  600. return proxy?.$modal.msgWarning('请选择对应道具');
  601. }
  602. if (grantForm.value.quantity == null) {
  603. return proxy?.$modal.msgWarning('请选择对应道具数量');
  604. }
  605. await sendRewardToos(grantForm.value); // 调用更新接口
  606. proxy?.$modal.msgSuccess('操作成功');
  607. grantDialogVisible.value = false;
  608. getList(); // 刷新列表
  609. } catch (error) {
  610. console.error(error);
  611. }
  612. };
  613. const router = useRouter(); // 获取 router 实例
  614. // 查看积分流水
  615. const viewPointsLog = (row: UserVO) => {
  616. // 跳转到积分流水页面,并传递用户ID作为查询参数
  617. router.push({
  618. path: '/pay/itemsLog',
  619. query: {
  620. userId: row.id,
  621. t: Date.now() // 添加时间戳确保URL变化,触发页面刷新
  622. }
  623. });
  624. };
  625. const viewPointsBag = (row: UserVO) => {
  626. // 跳转到积分流水页面,并传递用户ID作为查询参数
  627. router.push({
  628. path: '/pay/playerItems',
  629. query: {
  630. userId: row.id,
  631. t: Date.now() // 添加时间戳确保URL变化,触发页面刷新
  632. }
  633. });
  634. };
  635. // 弹窗状态和表单数据
  636. const editPhoneDialog = reactive({
  637. visible: false,
  638. title: '修改手机号'
  639. });
  640. const editPhoneForm = reactive({
  641. id: null,
  642. oldPhone: '',
  643. newPhone: '',
  644. verifyCode: ''
  645. });
  646. const openEditPhoneDialog = (userId: string | number, phone: string) => {
  647. editPhoneForm.id = userId;
  648. editPhoneForm.oldPhone = phone;
  649. editPhoneForm.newPhone = '';
  650. editPhoneForm.verifyCode = '';
  651. editPhoneDialog.visible = true;
  652. };
  653. const editPhoneRules = {
  654. newPhone: [
  655. { required: true, message: '请输入新手机号', trigger: 'blur' },
  656. { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
  657. ],
  658. verifyCode: [{ required: true, message: '请输入验证码', trigger: 'blur' }]
  659. };
  660. const dialog = reactive<DialogOption>({
  661. visible: false,
  662. title: ''
  663. });
  664. const editDialog = reactive<DialogOption>({
  665. visible: false,
  666. title: ''
  667. });
  668. // 倒计时相关
  669. const countdown = ref(0); // 当前倒计时秒数
  670. const countdownTimer = ref<NodeJS.Timeout | null>(null);
  671. // 发送验证码
  672. const sendVerifyCode = async () => {
  673. if (!editPhoneForm.newPhone) {
  674. ElMessage.warning('请先填写新手机号');
  675. return;
  676. }
  677. if (countdown.value > 0) {
  678. // 正在倒计时中,不允许重复发送
  679. return;
  680. }
  681. try {
  682. await getSmsCode(editPhoneForm.newPhone);
  683. ElMessage.success('验证码已发送');
  684. // 启动倒计时(60秒)
  685. startCountdown(60);
  686. } catch (error) {
  687. ElMessage.error('发送失败,请重试');
  688. }
  689. };
  690. // 启动倒计时
  691. const startCountdown = (seconds: number) => {
  692. countdown.value = seconds;
  693. if (countdownTimer.value) {
  694. clearInterval(countdownTimer.value);
  695. }
  696. countdownTimer.value = setInterval(() => {
  697. if (countdown.value > 0) {
  698. countdown.value--;
  699. } else {
  700. if (countdownTimer.value) {
  701. clearInterval(countdownTimer.value);
  702. countdownTimer.value = null;
  703. }
  704. }
  705. }, 1000);
  706. };
  707. const editPhoneFormRef = ref<ElFormInstance>();
  708. // 提交修改
  709. const submitEditPhone = async () => {
  710. try {
  711. // 1. 表单校验
  712. await editPhoneFormRef.value?.validate();
  713. const formData: Partial<UserForm> = {
  714. id: editPhoneForm.id,
  715. phone: form.value.phone,
  716. newPhone: editPhoneForm.newPhone,
  717. verifyCode: editPhoneForm.verifyCode
  718. };
  719. await updateUserPhone(formData).finally(() => (buttonLoading.value = false));
  720. ElMessage.success('手机号修改成功');
  721. editPhoneDialog.visible = false;
  722. // 更新主表单中的 phone 字段
  723. form.value.phone = editPhoneForm.newPhone;
  724. } catch (error) {
  725. console.error(error);
  726. }
  727. };
  728. </script>
  729. <style scoped>
  730. .action-container {
  731. display: flex;
  732. align-items: center;
  733. justify-content: center;
  734. gap: 8px;
  735. min-width: 100%;
  736. max-width: none;
  737. overflow-x: auto;
  738. overflow-y: hidden;
  739. white-space: nowrap;
  740. padding: 0 8px;
  741. }
  742. </style>