index.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  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. &lt;!&ndash; <el-col :span="1.5">
  47. <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['business:user:add']">新增</el-button>
  48. </el-col>&ndash;&gt;
  49. &lt;!&ndash; <el-col :span="1.5">
  50. <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['business:user:edit']"
  51. >修改</el-button
  52. >
  53. </el-col>&ndash;&gt;
  54. &lt;!&ndash; <el-col :span="1.5">
  55. <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['business:user:remove']"
  56. >删除</el-button
  57. >
  58. </el-col>&ndash;&gt;
  59. &lt;!&ndash; <el-col :span="1.5">
  60. <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['business:user:export']">导出</el-button>
  61. </el-col>&ndash;&gt;
  62. <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
  63. </el-row>
  64. </template>-->
  65. <el-table v-loading="loading" border :data="userList" @selection-change="handleSelectionChange">
  66. <!-- <el-table-column type="selection" width="55" align="center" />-->
  67. <el-table-column label="用户id" align="center" prop="id" v-if="true" />
  68. <el-table-column label="登录名称" align="center" prop="loginName" />
  69. <el-table-column label="用户姓名" align="center" prop="realName" />
  70. <el-table-column label="用户昵称" align="center" prop="nickName" />
  71. <el-table-column label="用户手机号码" align="center" prop="phone" />
  72. <el-table-column label="注册时间" align="center" prop="createAt" width="180">
  73. <template #default="scope">
  74. <span>{{ parseTime(scope.row.createAt, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
  75. </template>
  76. </el-table-column>
  77. <el-table-column label="最新上线时间" align="center" prop="createAt" width="180">
  78. <template #default="scope">
  79. <span>{{ parseTime(scope.row.lastLoginTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
  80. </template>
  81. </el-table-column>
  82. <el-table-column label="积分数" align="center" prop="pointsQuantity" />
  83. <el-table-column label="门票数" align="center" prop="itemsQuantity" />
  84. <el-table-column label="比赛数" align="center" prop="tournamentCount" />
  85. <el-table-column label="账号状态" align="center" prop="statusText" />
  86. <el-table-column label="操作" align="center" width="430" class-name="small-padding fixed-width">
  87. <template #default="scope">
  88. <div class="action-container">
  89. <el-tooltip content="查看" placement="top">
  90. <el-button link type="primary" icon="View" @click="handleUpdate(scope.row)" v-hasPermi="['business:user:edit']">查看</el-button>
  91. </el-tooltip>
  92. <!-- 禁用/启用 按钮 -->
  93. <el-tooltip :content="scope.row.status === 1 ? '禁用' : '启用'" placement="top">
  94. <el-button
  95. link
  96. type="primary"
  97. :icon="scope.row.status === 1 ? 'Lock' : 'Unlock'"
  98. @click="toggleStatus(scope.row)"
  99. v-hasPermi="['business:user:edit']"
  100. >
  101. {{ scope.row.status === 1 ? '禁用' : '启用' }}
  102. </el-button>
  103. </el-tooltip>
  104. <el-tooltip content="删除" placement="top">
  105. <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['business:user:remove']">删除</el-button>
  106. </el-tooltip>
  107. <el-tooltip content="编辑道具" placement="top">
  108. <el-button link type="primary" icon="Tools" @click="openGrantDialog(scope.row)" v-hasPermi="['business:user:remove']"
  109. >编辑道具</el-button
  110. >
  111. </el-tooltip>
  112. <el-tooltip content="查看流水" placement="top">
  113. <el-button link type="primary" icon="View" @click="viewPointsLog(scope.row)" v-hasPermi="['business:user:edit']"
  114. >查看流水</el-button
  115. >
  116. </el-tooltip>
  117. </div>
  118. </template>
  119. </el-table-column>
  120. </el-table>
  121. <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
  122. </el-card>
  123. <!-- 查看用户详情对话框 -->
  124. <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
  125. <el-form ref="userFormRef" :model="form" :rules="rules" label-width="80px">
  126. <!--
  127. <el-form-item label="用户ID" prop="id">
  128. <el-input v-model="form.id" disabled />
  129. </el-form-item>
  130. -->
  131. <el-form-item label="登录名称" prop="loginName">
  132. <el-input v-model="form.phone" disabled />
  133. </el-form-item>
  134. <el-form-item label="用户姓名" prop="loginPass">
  135. <el-input v-model="form.realName" disabled />
  136. </el-form-item>
  137. <el-form-item label="手机号" prop="phone">
  138. <el-input v-model="form.phone" disabled />
  139. </el-form-item>
  140. <el-form-item label="注册时间" prop="createAt">
  141. <el-date-picker clearable v-model="form.createAt" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" disabled> </el-date-picker>
  142. </el-form-item>
  143. <el-form-item label="上线时间" prop="lastLoginTime">
  144. <el-date-picker clearable v-model="form.lastLoginTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" disabled> </el-date-picker>
  145. </el-form-item>
  146. <el-form-item label="积分数" prop="email">
  147. <el-input v-model="form.pointsQuantity" disabled />
  148. </el-form-item>
  149. <el-form-item label="门票数" prop="nickName">
  150. <el-input v-model="form.itemsQuantity" disabled />
  151. </el-form-item>
  152. <el-form-item label="比赛数" prop="remark">
  153. <el-input v-model="form.tournamentCount" disabled />
  154. </el-form-item>
  155. </el-form>
  156. <template #footer>
  157. <div class="dialog-footer">
  158. <!-- <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>-->
  159. <el-button @click="cancel">取 消</el-button>
  160. </div>
  161. </template>
  162. </el-dialog>
  163. <!-- 编辑用户对话框 -->
  164. <el-dialog :title="editDialog.title" v-model="editDialog.visible" width="500px" append-to-body>
  165. <el-form ref="editUserFormRef" :model="editForm" :rules="rules2" label-width="80px">
  166. <el-form-item label="用户ID" prop="id" v-show="false">
  167. <el-input v-model="editForm.id" disabled />
  168. </el-form-item>
  169. <el-form-item label="手机号" prop="phone">
  170. <el-input v-model="editForm.phone" placeholder="请输入手机号" @input="handlePhoneInput" />
  171. </el-form-item>
  172. <el-form-item label="积分数" prop="score">
  173. <el-input v-model.number="editForm.score" />
  174. </el-form-item>
  175. <el-form-item label="门票数" prop="ticketCount">
  176. <el-input v-model.number="editForm.ticketCount" />
  177. </el-form-item>
  178. </el-form>
  179. <template #footer>
  180. <div class="dialog-footer">
  181. <el-button :loading="buttonLoading" type="primary" @click="submitEditForm">确 定</el-button>
  182. <el-button @click="cancelEdit">取 消</el-button>
  183. </div>
  184. </template>
  185. </el-dialog>
  186. <!-- 发放道具弹窗 -->
  187. <el-dialog v-model="grantDialogVisible" title="操作道具" width="400px">
  188. <div class="dialog-content">
  189. <!-- 左侧内容 -->
  190. <div class="left-content">
  191. <el-form :model="grantForm" ref="grantFormRef">
  192. <el-input v-model="grantForm.userId" disabled v-show="false" />
  193. <el-form-item label="手机号" prop="id">
  194. <el-input v-model="grantForm.phone" disabled />
  195. </el-form-item>
  196. <el-form-item label="道具类型" prop="itemId">
  197. <!-- 道具选择 -->
  198. <el-select v-model="grantForm.itemId" placeholder="请选择道具" style="width: 120px; margin-right: 8px">
  199. <el-option v-for="item in itemOptions" :key="item.id" :label="item.label" :value="item.id" />
  200. </el-select>
  201. </el-form-item>
  202. <el-form-item label="操作类型" prop="itemId">
  203. <el-select v-model="grantForm.type" placeholder="请操作类型" style="width: 120px; margin-right: 8px">
  204. <el-option label="增加" :value="1" />
  205. <el-option label="扣除" :value="2" />
  206. </el-select>
  207. </el-form-item>
  208. <el-form-item label="数量" prop="quantity">
  209. <el-input-number v-model="grantForm.quantity" :min="1" :max="999999" />
  210. </el-form-item>
  211. </el-form>
  212. </div>
  213. <!-- 右侧内容 -->
  214. <div class="right-content">
  215. <!-- 这里可以放置其他内容或留空 -->
  216. </div>
  217. </div>
  218. <template #footer>
  219. <span class="dialog-footer">
  220. <el-button @click="grantDialogVisible = false">取消</el-button>
  221. <el-button type="primary" @click="submitGrantForm">确认</el-button>
  222. </span>
  223. </template>
  224. </el-dialog>
  225. </div>
  226. </template>
  227. <script setup name="User" lang="ts">
  228. import { listUser, getUser, delUser, addUser, updateUser, sendRewardToos } from '@/api/system/business/apiUsers';
  229. import { UserVO, UserQuery, UserForm } from '@/api/system/business/apiUsers/types';
  230. import { selectItemsSelList } from '@/api/system/business/items';
  231. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  232. const userList = ref<UserVO[]>([]);
  233. const buttonLoading = ref(false);
  234. const loading = ref(true);
  235. const showSearch = ref(true);
  236. const ids = ref<Array<string | number>>([]);
  237. const single = ref(true);
  238. const multiple = ref(true);
  239. const total = ref(0);
  240. const queryFormRef = ref<ElFormInstance>();
  241. const userFormRef = ref<ElFormInstance>();
  242. const dialog = reactive<DialogOption>({
  243. visible: false,
  244. title: ''
  245. });
  246. const editDialog = reactive<DialogOption>({
  247. visible: false,
  248. title: ''
  249. });
  250. const initFormData: UserForm = {
  251. id: undefined,
  252. loginName: undefined,
  253. loginPass: undefined,
  254. phone: undefined,
  255. createAt: undefined,
  256. updateAt: undefined,
  257. sex: undefined,
  258. email: undefined,
  259. nickName: undefined,
  260. remark: undefined,
  261. captcha: undefined,
  262. avatar: undefined,
  263. province: undefined,
  264. city: undefined,
  265. area: undefined,
  266. placeDetail: undefined,
  267. registerIp: undefined,
  268. registerDevice: undefined,
  269. status: undefined,
  270. isLocked: undefined,
  271. lastLoginTime: undefined,
  272. lastLoginIp: undefined
  273. };
  274. const data = reactive<PageData<UserForm, UserQuery>>({
  275. form: { ...initFormData },
  276. queryParams: {
  277. pageNum: 1,
  278. pageSize: 10,
  279. loginName: undefined,
  280. loginPass: undefined,
  281. phone: undefined,
  282. createAt: undefined,
  283. updateAt: undefined,
  284. sex: undefined,
  285. email: undefined,
  286. nickName: undefined,
  287. captcha: undefined,
  288. avatar: undefined,
  289. province: undefined,
  290. city: undefined,
  291. area: undefined,
  292. placeDetail: undefined,
  293. registerIp: undefined,
  294. registerDevice: undefined,
  295. status: undefined,
  296. isLocked: undefined,
  297. lastLoginTime: undefined,
  298. lastLoginIp: undefined,
  299. params: {}
  300. },
  301. rules: {
  302. id: [{ required: true, message: '不能为空', trigger: 'blur' }],
  303. loginName: [{ required: true, message: '登录名称不能为空', trigger: 'blur' }],
  304. loginPass: [{ required: true, message: '登录密码不能为空', trigger: 'blur' }]
  305. }
  306. });
  307. const editForm = ref<UserForm>({
  308. id: undefined,
  309. phone: '',
  310. ticketCount: '',
  311. score: ''
  312. });
  313. const rules2 = ref({
  314. phone: [
  315. { required: true, message: '手机号不能为空', trigger: 'blur' },
  316. {
  317. pattern: /^1[3-9]\d{9}$/,
  318. message: '请输入正确的手机号格式',
  319. trigger: 'blur'
  320. }
  321. ]
  322. });
  323. const submitEditForm = async () => {
  324. try {
  325. await updateUser(editForm.value); // 调用更新接口
  326. proxy?.$modal.msgSuccess('编辑成功');
  327. editDialog.visible = false;
  328. getList(); // 刷新列表
  329. } catch (error) {
  330. console.error(error);
  331. }
  332. };
  333. const cancelEdit = () => {
  334. editDialog.visible = false;
  335. };
  336. const handleEdit = async (row: UserVO) => {
  337. const userId = row.id;
  338. const res = await getUser(userId);
  339. editForm.value = { ...res.data }; // 把数据赋值给可编辑表单
  340. editDialog.visible = true;
  341. editDialog.title = '编辑';
  342. };
  343. const { queryParams, form, rules } = toRefs(data);
  344. /** 查询【请填写功能名称】列表 */
  345. const getList = async () => {
  346. loading.value = true;
  347. const res = await listUser(queryParams.value);
  348. userList.value = res.rows;
  349. total.value = res.total;
  350. loading.value = false;
  351. };
  352. /** 取消按钮 */
  353. const cancel = () => {
  354. reset();
  355. dialog.visible = false;
  356. };
  357. /** 表单重置 */
  358. const reset = () => {
  359. form.value = { ...initFormData };
  360. userFormRef.value?.resetFields();
  361. };
  362. /** 搜索按钮操作 */
  363. const handleQuery = () => {
  364. queryParams.value.pageNum = 1;
  365. const timeRange = data.queryParams.loginTimeRange;
  366. if (timeRange && timeRange.length === 2) {
  367. data.queryParams.loginBeginTime = timeRange[0];
  368. data.queryParams.loginEndTime = timeRange[1];
  369. } else {
  370. data.queryParams.loginBeginTime = null;
  371. data.queryParams.loginEndTime = null;
  372. }
  373. const timeRange2 = data.queryParams.registerTimeRange;
  374. if (timeRange2 && timeRange2.length === 2) {
  375. data.queryParams.registerBeginTime = timeRange2[0];
  376. data.queryParams.registerEndTime = timeRange2[1];
  377. } else {
  378. data.queryParams.registerBeginTime = null;
  379. data.queryParams.registerEndTime = null;
  380. }
  381. getList();
  382. };
  383. /** 重置按钮操作 */
  384. const resetQuery = () => {
  385. queryFormRef.value?.resetFields();
  386. handleQuery();
  387. };
  388. /** 多选框选中数据 */
  389. const handleSelectionChange = (selection: UserVO[]) => {
  390. ids.value = selection.map((item) => item.id);
  391. single.value = selection.length != 1;
  392. multiple.value = !selection.length;
  393. };
  394. /** 新增按钮操作 */
  395. const handleAdd = () => {
  396. reset();
  397. dialog.visible = true;
  398. dialog.title = '添加【请填写功能名称】';
  399. };
  400. /** 修改按钮操作 */
  401. const handleUpdate = async (row?: UserVO) => {
  402. reset();
  403. const _id = row?.id || ids.value[0];
  404. const res = await getUser(_id);
  405. Object.assign(form.value, res.data);
  406. dialog.visible = true;
  407. dialog.title = '查看【用户详情】';
  408. };
  409. const toggleStatus = (row) => {
  410. const newStatus = row.status === 1 ? 0 : 1;
  411. const confirmText = newStatus === 0 ? '禁用' : '启用';
  412. row.status = newStatus;
  413. // 弹出确认框
  414. ElMessageBox.confirm(`确认要${confirmText}用户【${row.nickName || row.loginName}】吗?`, '提示', {
  415. confirmButtonText: '确定',
  416. cancelButtonText: '取消',
  417. type: 'warning'
  418. })
  419. .then(() => {
  420. // 调用 API 接口更新状态
  421. updateUser(row).then((response) => {
  422. if (response.code === 200) {
  423. ElMessage.success(confirmText + '成功');
  424. // 刷新列表
  425. handleQuery();
  426. } else {
  427. ElMessage.error('操作失败:' + response.msg);
  428. }
  429. });
  430. })
  431. .catch(() => {
  432. // 取消操作
  433. });
  434. };
  435. /** 提交按钮 */
  436. const submitForm = () => {
  437. userFormRef.value?.validate(async (valid: boolean) => {
  438. if (valid) {
  439. buttonLoading.value = true;
  440. if (form.value.id) {
  441. await updateUser(form.value).finally(() => (buttonLoading.value = false));
  442. } else {
  443. await addUser(form.value).finally(() => (buttonLoading.value = false));
  444. }
  445. proxy?.$modal.msgSuccess('操作成功');
  446. dialog.visible = false;
  447. await getList();
  448. }
  449. });
  450. };
  451. /** 删除按钮操作 */
  452. const handleDelete = async (row?: UserVO) => {
  453. const _ids = row?.id || ids.value;
  454. await proxy?.$modal.confirm('是否确认删除【用户管理】编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
  455. await delUser(_ids);
  456. proxy?.$modal.msgSuccess('删除成功');
  457. await getList();
  458. };
  459. /** 导出按钮操作 */
  460. const handleExport = () => {
  461. proxy?.download(
  462. 'business/user/export',
  463. {
  464. ...queryParams.value
  465. },
  466. `user_${new Date().getTime()}.xlsx`
  467. );
  468. };
  469. const handlePhoneInput = (value: string) => {
  470. editForm.value.phone = value.replace(/[^0-9]/g, '');
  471. };
  472. onMounted(() => {
  473. getList();
  474. loadItemOptions();
  475. });
  476. // 下拉选项数据 selectBlingStructuresInfo
  477. const itemOptions = ref<{ id: number; label: string }[]>([]);
  478. // 加载报名条件选项
  479. const loadItemOptions = async () => {
  480. try {
  481. const res = await selectItemsSelList();
  482. if (res.code === 200) {
  483. // 使用 unknown 中间类型进行类型转换
  484. const data = res.data as unknown as { id: number; name: string }[];
  485. // 过滤掉 id === 2 的项,并映射为 { id, label } 结构
  486. const list = data
  487. .filter((item) => item.id !== 2) // ✅ 过滤掉 id 为 1 的选项
  488. .map((item) => ({
  489. id: item.id,
  490. label: item.name
  491. }));
  492. itemOptions.value = list;
  493. } else {
  494. alert('加载失败:' + res.msg);
  495. }
  496. } catch (error) {
  497. console.error('请求出错:', error);
  498. }
  499. };
  500. // 弹窗是否显示
  501. const grantDialogVisible = ref(false);
  502. // 表单数据
  503. const grantForm = ref({
  504. itemId: null,
  505. quantity: 1,
  506. phone: null,
  507. userId: null,
  508. type: 1
  509. });
  510. // 表单校验规则
  511. const grantRules = {
  512. itemId: [{ required: true, message: '请选择道具类型', trigger: 'change' }],
  513. quantity: [
  514. { required: true, message: '请输入数量', trigger: 'blur' },
  515. { type: 'number', min: 1, message: '数量必须大于0', trigger: 'blur' }
  516. ]
  517. };
  518. const currentUser = ref(null);
  519. // 打开弹窗
  520. const openGrantDialog = (row) => {
  521. currentUser.value = row;
  522. grantForm.value.phone = row.phone;
  523. grantForm.value.userId = row.id;
  524. grantForm.value.itemId = null;
  525. grantForm.value.quantity = 1;
  526. grantDialogVisible.value = true;
  527. };
  528. const submitGrantForm = async () => {
  529. try {
  530. if (grantForm.value.itemId == null) {
  531. return proxy?.$modal.msgWarning('请选择对应道具');
  532. }
  533. if (grantForm.value.quantity == null) {
  534. return proxy?.$modal.msgWarning('请选择对应道具数量');
  535. }
  536. await sendRewardToos(grantForm.value); // 调用更新接口
  537. proxy?.$modal.msgSuccess('操作成功');
  538. grantDialogVisible.value = false;
  539. getList(); // 刷新列表
  540. } catch (error) {
  541. console.error(error);
  542. }
  543. };
  544. const router = useRouter(); // 获取 router 实例
  545. // 查看积分流水
  546. const viewPointsLog = (row: UserVO) => {
  547. // 跳转到积分流水页面,并传递用户ID作为查询参数
  548. router.push({
  549. path: '/business/itemsLog',
  550. query: {
  551. userId: row.id,
  552. t: Date.now() // 添加时间戳确保URL变化,触发页面刷新
  553. }
  554. });
  555. };
  556. </script>
  557. <style scoped>
  558. .action-container {
  559. display: flex;
  560. align-items: center;
  561. justify-content: center;
  562. gap: 8px;
  563. min-width: 100%;
  564. max-width: none;
  565. overflow-x: auto;
  566. overflow-y: hidden;
  567. white-space: nowrap;
  568. padding: 0 8px;
  569. }
  570. </style>