deepchart后台管理系统
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

561 lines
14 KiB

4 weeks ago
  1. <template>
  2. <div class="page-container">
  3. <div class="search-container">
  4. <el-button type="danger" @click="add">添加</el-button>
  5. </div>
  6. <!-- 数据 -->
  7. <el-table
  8. :data="tableData"
  9. style="width: 100%; margin-top: 20px"
  10. header-cell-class-name="table-header"
  11. @sort-change="handleSortChange"
  12. :default-sort="{ prop: null, order: null }"
  13. class="table-rounded"
  14. :loading="tableLoading"
  15. >
  16. <el-table-column
  17. prop="id"
  18. label="序号"
  19. align="center"
  20. header-align="center"
  21. width="80"
  22. >
  23. <template #default="scope">
  24. {{ (currentPage - 1) * pageSize + scope.$index + 1 }}
  25. </template>
  26. </el-table-column>
  27. <el-table-column
  28. prop="prize_type"
  29. label="类型"
  30. align="center"
  31. header-align="center"
  32. />
  33. <el-table-column
  34. prop="prize_name"
  35. label="物品名称"
  36. align="center"
  37. header-align="center"
  38. />
  39. <el-table-column
  40. prop="stick_type"
  41. label="福签"
  42. align="center"
  43. header-align="center"
  44. />
  45. <el-table-column
  46. prop="probability"
  47. label="概率"
  48. align="center"
  49. header-align="center"
  50. />
  51. <el-table-column label="状态" prop="status">
  52. <template #default="scope">
  53. <el-switch
  54. v-model="scope.row.status"
  55. :active-value="1"
  56. :inactive-value="0"
  57. inline-prompt
  58. style="
  59. --el-switch-on-color: #13ce66;
  60. --el-switch-off-color: #ff4949;
  61. "
  62. active-text="ON"
  63. inactive-text="OFF"
  64. @change="changeState(scope.row)"
  65. >
  66. </el-switch>
  67. </template>
  68. </el-table-column>
  69. <el-table-column
  70. prop="time"
  71. label="时间"
  72. align="center"
  73. header-align="center"
  74. />
  75. <el-table-column label="操作" align="center" header-align="center">
  76. <template #default="scope">
  77. <el-button type="text" @click="deleteDraw(scope.row)">删除</el-button>
  78. </template>
  79. </el-table-column>
  80. </el-table>
  81. <!-- 分页组件 -->
  82. <div class="demo-pagination-block">
  83. <el-pagination
  84. @size-change="handleSizeChange"
  85. @current-change="handleCurrentChange"
  86. :current-page="currentPage"
  87. :page-sizes="[10, 15, 20, 50, 100]"
  88. :page-size="pageSize"
  89. layout="total, sizes, prev, pager, next, jumper"
  90. :total="datatotal"
  91. />
  92. </div>
  93. <el-dialog v-model="dialogFormVisible" width="500" :show-close="false">
  94. <el-form
  95. :model="form"
  96. style="width: 400px; margin: 0 auto"
  97. :rules="rules"
  98. ref="formRef"
  99. >
  100. <el-form-item label="类型" prop="type">
  101. <el-select v-model="form.type" placeholder="请选择类型" clearable>
  102. <el-option
  103. v-for="item in prizeTypeOptions"
  104. :key="item.value"
  105. :label="item.label"
  106. :value="item.value"
  107. />
  108. </el-select>
  109. </el-form-item>
  110. <el-form-item :label="nameConfig.label" :prop="nameConfig.prop">
  111. <el-input
  112. v-model="form[nameConfig.prop]"
  113. autocomplete="off"
  114. :placeholder="nameConfig.placeholder"
  115. clearable
  116. />
  117. </el-form-item>
  118. <el-form-item label="概率" prop="probability">
  119. <el-input
  120. v-model.number="form.probability"
  121. type="number"
  122. autocomplete="off"
  123. placeholder="请输入概率"
  124. clearable
  125. >
  126. <template #append>%</template>
  127. </el-input>
  128. <div class="tip">(小于等于100%)</div>
  129. </el-form-item>
  130. <el-form-item label="福签" prop="stick_type">
  131. <el-select
  132. v-model="form.stick_type"
  133. placeholder="请选择类型"
  134. clearable
  135. >
  136. <el-option
  137. v-for="item in typeOptions"
  138. :key="item.value"
  139. :label="item.label"
  140. :value="item.value"
  141. />
  142. </el-select>
  143. </el-form-item>
  144. <el-form-item label="图片" prop="img">
  145. <el-upload
  146. ref="uploadRef"
  147. v-model:file-list="fileList"
  148. class="avatar-uploader"
  149. :action="uploadUrl"
  150. :limit="1"
  151. list-type="picture-card"
  152. :on-success="handleSuccess"
  153. :before-upload="beforeUpload"
  154. :on-remove="handleRemove"
  155. :on-exceed="handleExceed"
  156. >
  157. <el-icon><Plus /></el-icon>
  158. </el-upload>
  159. <div class="tip">
  160. 大小180*180像素支持PNGJPG格式图片需小于500K
  161. </div>
  162. </el-form-item>
  163. </el-form>
  164. <template #footer>
  165. <div class="dialog-footer">
  166. <el-button @click="dialogFormVisible = false">取消</el-button>
  167. <el-button type="danger" @click="submitForm(formRef)">
  168. 提交
  169. </el-button>
  170. </div>
  171. </template>
  172. </el-dialog>
  173. </div>
  174. </template>
  175. <script setup>
  176. import { ref, reactive, onMounted, computed, watch } from "vue";
  177. import { ElMessage, genFileId, ElMessageBox } from "element-plus";
  178. import router from "../../router";
  179. import {
  180. getContentListApi,
  181. addDrawConfigApi,
  182. deleteDrawApi,
  183. changeStatusApi,
  184. } from "../../api/eventManagement";
  185. const uploadUrl = import.meta.env.VITE_API_BASE_URLXXCG + "hljw/api/aws/upload";
  186. const uploadRef = ref();
  187. const fileList = ref([]);
  188. const formRef = ref();
  189. const form = reactive({
  190. stick_type: "",
  191. type: "",
  192. item_name: "",
  193. num: null,
  194. probability: null,
  195. img: "",
  196. });
  197. const typeOptions = ref([
  198. { label: "好运签", value: 1 },
  199. { label: "福气签", value: 2 },
  200. { label: "富贵签", value: 3 },
  201. { label: "财神签", value: 4 },
  202. { label: "上上签", value: 5 },
  203. { label: "锦鲤签", value: 6 },
  204. ]);
  205. const prizeTypeOptions = ref([
  206. { label: "金币", value: 2 },
  207. { label: "金豆", value: 3 },
  208. { label: "Token", value: 1 },
  209. { label: "实物", value: 4 },
  210. ]);
  211. const nameConfig = computed(() => {
  212. switch (form.type) {
  213. case 1: // Token
  214. return { label: "数量", placeholder: "请输入Token数量", prop: "num" };
  215. case 2: // 金币
  216. return { label: "数量", placeholder: "请输入金币数量", prop: "num" };
  217. case 3: // 金豆
  218. return { label: "数量", placeholder: "请输入金豆数量", prop: "num" };
  219. default: // 默认情况(未选择或实物)
  220. return { label: "名称", placeholder: "请输入物品名称", prop: "item_name" };
  221. }
  222. });
  223. const handleSuccess = (response, uploadFile) => {
  224. form.img = response.data.url;
  225. };
  226. const beforeUpload = (rawFile) => {
  227. if (!rawFile.type.startsWith("image/")) {
  228. ElMessage.error("请上传图片文件!");
  229. return false;
  230. } else if (rawFile.size / 1024 > 500) {
  231. ElMessage.error("图片大小必须小于500K!");
  232. return false;
  233. }
  234. return true;
  235. };
  236. const handleRemove = (file, fileList) => {
  237. form.img = "";
  238. };
  239. const handleExceed = (files) => {
  240. // 1. 清空当前文件列表
  241. uploadRef.value.clearFiles();
  242. const file = files[0];
  243. // 2. 必须生成新的 uid,否则可能导致 key 冲突无法上传
  244. file.uid = genFileId();
  245. // 3. 手动选择文件
  246. uploadRef.value.handleStart(file);
  247. // 4. 手动触发上传
  248. uploadRef.value.submit();
  249. };
  250. // token
  251. const token = localStorage.getItem("token");
  252. const dialogFormVisible = ref(false);
  253. const rules = computed(() => {
  254. const baseRules = {
  255. stick_type: [{ required: true, message: "请选择类型", trigger: "change" }],
  256. type: [{ required: true, message: "请选择类型", trigger: "change" }],
  257. probability: [
  258. { required: true, message: "请输入概率", trigger: "blur" },
  259. // 为负数时提示
  260. { validator: validateNum, trigger: "blur" },
  261. ],
  262. img: [{ required: true, message: "请上传图片", trigger: "change" }], // 上传通常用 change
  263. };
  264. if ([1, 2, 3].includes(form.type)) {
  265. return {
  266. ...baseRules,
  267. num: [
  268. { required: true, message: "请输入数量", trigger: "blur" },
  269. // 为负数时提示
  270. { validator: validateNum, trigger: "blur" },
  271. ],
  272. };
  273. } else {
  274. return {
  275. ...baseRules,
  276. item_name: [
  277. { required: true, message: "请输入物品名称", trigger: "blur" },
  278. ],
  279. };
  280. }
  281. });
  282. watch(
  283. () => form.type,
  284. () => {
  285. // 切换类型时,清除之前的输入值,避免校验报错或数据混乱
  286. form.item_name = "";
  287. form.num = null;
  288. // 也可以清除校验状态
  289. if (formRef.value) {
  290. formRef.value.clearValidate(["item_name", "num"]);
  291. }
  292. }
  293. );
  294. const validateNum = (rule, value, callback) => {
  295. // 如果值为空,交给 required 规则处理
  296. if (value === "" || value === null || value === undefined) {
  297. callback();
  298. return;
  299. }
  300. // 转换为数字进行判断
  301. if (Number(value) < 0) {
  302. callback(new Error("不能为负数"));
  303. } else {
  304. callback();
  305. }
  306. };
  307. // 表格数据
  308. const tableData = ref([]);
  309. const tableLoading = ref(false);
  310. const datatotal = ref(0);
  311. // 分页参数
  312. const currentPage = ref(1);
  313. const pageSize = ref(15);
  314. const changeState = async (row) => {
  315. try {
  316. const res = await changeStatusApi({
  317. token: token,
  318. id: row.id,
  319. status: row.status,
  320. });
  321. ElMessage.success("状态更新成功");
  322. fetchTableData();
  323. } catch (error) {
  324. // 由响应拦截器处理错误
  325. }
  326. };
  327. // 重置表单数据
  328. const resetForm = () => {
  329. form.stick_type = "";
  330. form.type = "";
  331. form.item_name = "";
  332. form.num = null;
  333. form.probability = null;
  334. form.img = "";
  335. fileList.value = [];
  336. };
  337. // 添加按钮
  338. const add = () => {
  339. resetForm();
  340. dialogFormVisible.value = true;
  341. };
  342. const submitForm = async () => {
  343. try {
  344. await formRef.value.validate();
  345. const requestParams = {
  346. token: token,
  347. stick_type: form.stick_type,
  348. type: form.type,
  349. probability: form.probability,
  350. img: form.img,
  351. };
  352. if ([1, 2, 3].includes(form.type)) {
  353. requestParams.num = parseInt(form.num, 10);
  354. } else {
  355. requestParams.item_name = form.item_name;
  356. }
  357. const data = await addDrawConfigApi(requestParams);
  358. ElMessage.success("添加成功");
  359. dialogFormVisible.value = false;
  360. fetchTableData();
  361. } catch (error) {}
  362. };
  363. const deleteDraw = async (row) => {
  364. try {
  365. await ElMessageBox.confirm("确定要删除吗?", "确认删除", {
  366. confirmButtonText: "确定",
  367. cancelButtonText: "取消",
  368. type: "warning",
  369. confirmButtonClass: "custom-confirm-btn",
  370. });
  371. const requestParams = {
  372. token: token,
  373. id: row.id,
  374. };
  375. const data = await deleteDrawApi(requestParams);
  376. ElMessage.success("删除成功");
  377. fetchTableData();
  378. } catch (error) {
  379. if (error === "cancel") {
  380. // 用户点击取消
  381. ElMessage.info("已取消删除");
  382. } else {
  383. // 删除操作失败
  384. ElMessage.error("删除失败");
  385. }
  386. }
  387. };
  388. // 获取表格数据
  389. const fetchTableData = async () => {
  390. try {
  391. tableLoading.value = true;
  392. const requestParams = {
  393. token: token,
  394. page: currentPage.value,
  395. page_size: pageSize.value,
  396. };
  397. const data = await getContentListApi(requestParams);
  398. tableData.value = data.list;
  399. datatotal.value = data.total;
  400. } catch (error) {
  401. console.error("获取表格数据失败:", error);
  402. tableData.value = [];
  403. datatotal.value = 0;
  404. } finally {
  405. tableLoading.value = false;
  406. }
  407. };
  408. // 组件挂载时:获取地区列表 + 初始化表格数据
  409. onMounted(() => {
  410. fetchTableData();
  411. });
  412. // 分页方法
  413. const handleSizeChange = (val) => {
  414. pageSize.value = val;
  415. fetchTableData();
  416. console.log(`每页 ${val}`);
  417. };
  418. const handleCurrentChange = (val) => {
  419. currentPage.value = val;
  420. fetchTableData();
  421. console.log(`当前页: ${val}`);
  422. };
  423. </script>
  424. <style scoped>
  425. /* 父容器 */
  426. .page-container {
  427. position: relative;
  428. min-height: 600px;
  429. }
  430. /* 搜索区域 */
  431. .search-container {
  432. display: flex;
  433. height: auto;
  434. flex-direction: column;
  435. justify-content: center;
  436. align-items: flex-start;
  437. gap: 12px;
  438. align-self: stretch;
  439. border-radius: 8px;
  440. background: #fefaf9;
  441. box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
  442. padding: 15px;
  443. margin-bottom: 20px;
  444. }
  445. /* 表格样式 */
  446. .table-rounded {
  447. border-radius: 12px !important;
  448. overflow: hidden !important;
  449. border: 1px solid #e4e7ed !important;
  450. height: 750px;
  451. }
  452. .table-header {
  453. text-align: center !important;
  454. font-weight: 800 !important;
  455. font-size: 15px !important;
  456. color: #333 !important;
  457. background-color: #f8f9fa !important;
  458. }
  459. .el-table__cell {
  460. border-right: none !important;
  461. border-bottom: 1px solid #e4e7ed !important;
  462. }
  463. .el-table__header th.el-table__cell {
  464. border-right: none !important;
  465. border-bottom: 1px solid #e4e7ed !important;
  466. }
  467. .el-table__row:hover .el-table__cell {
  468. background-color: #fafafa !important;
  469. }
  470. /* 分页组件样式 */
  471. .demo-pagination-block {
  472. display: flex;
  473. width: 100%;
  474. height: 44px;
  475. padding: 0 16px;
  476. align-items: center;
  477. gap: 16px;
  478. position: absolute;
  479. margin-top: 10px;
  480. border-radius: 0 0 3px 3px;
  481. border-top: 1px solid #eaeaea;
  482. background: #fefbfb;
  483. box-sizing: border-box;
  484. }
  485. .tip {
  486. font-size: 12px;
  487. color: #8c939d;
  488. }
  489. .avatar-uploader .avatar {
  490. width: 120px;
  491. height: 120px;
  492. display: block;
  493. }
  494. </style>
  495. <style>
  496. .custom-confirm-btn {
  497. background: #e13d52;
  498. border-color: #e13d52;
  499. color: white !important;
  500. border-radius: 6px !important;
  501. padding: 8px 16px !important;
  502. }
  503. .custom-confirm-btn:hover {
  504. background: #d88b95;
  505. border-color: #d88b95;
  506. }
  507. .avatar-uploader .el-upload {
  508. border: 1px dashed var(--el-border-color);
  509. border-radius: 6px;
  510. cursor: pointer;
  511. position: relative;
  512. overflow: hidden;
  513. transition: var(--el-transition-duration-fast);
  514. }
  515. .avatar-uploader .el-upload:hover {
  516. border-color: var(--el-color-primary);
  517. }
  518. .el-icon.avatar-uploader-icon {
  519. font-size: 28px;
  520. color: #8c939d;
  521. width: 120px;
  522. height: 120px;
  523. text-align: center;
  524. }
  525. </style>