index.vue 26 KB

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