|
@@ -0,0 +1,328 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <el-main>
|
|
|
|
|
+ <el-card shadow="never">
|
|
|
|
|
+ <template #header>
|
|
|
|
|
+ <div class="header-row">
|
|
|
|
|
+ <span class="card-title">用户等级</span>
|
|
|
|
|
+ <div class="header-actions">
|
|
|
|
|
+ <el-button type="primary" @click="openCreate">添加等级</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table v-loading="loading" :data="list" stripe class="level-table">
|
|
|
|
|
+ <el-table-column prop="id" label="ID" width="88" />
|
|
|
|
|
+ <el-table-column prop="name" label="名称" min-width="120" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column prop="grade" label="等级" width="88" align="center" />
|
|
|
|
|
+ <el-table-column prop="exp_num" label="所需经验" width="110" align="right" />
|
|
|
|
|
+ <el-table-column label="图标" width="88" align="center">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-image
|
|
|
|
|
+ v-if="row.icon"
|
|
|
|
|
+ class="level-icon"
|
|
|
|
|
+ :src="row.icon"
|
|
|
|
|
+ fit="cover"
|
|
|
|
|
+ :preview-src-list="[row.icon]"
|
|
|
|
|
+ preview-teleported
|
|
|
|
|
+ />
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="explain" label="说明" min-width="200" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="显示" width="120" align="center">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-switch
|
|
|
|
|
+ :model-value="Number(row.is_show)"
|
|
|
|
|
+ :active-value="1"
|
|
|
|
|
+ :inactive-value="0"
|
|
|
|
|
+ inline-prompt
|
|
|
|
|
+ active-text="显示"
|
|
|
|
|
+ inactive-text="隐藏"
|
|
|
|
|
+ active-color="#13ce66"
|
|
|
|
|
+ inactive-color="#909399"
|
|
|
|
|
+ @change="(val) => changeShow(row, val)"
|
|
|
|
|
+ />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="创建时间" min-width="170" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ formatTime(row.created_at) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="更新时间" min-width="170" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ formatTime(row.updated_at) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="操作" width="248" fixed="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <div class="table-row-actions">
|
|
|
|
|
+ <el-button type="info" size="small" @click="showDetail(row)">详情</el-button>
|
|
|
|
|
+ <el-button type="primary" size="small" @click="openEdit(row)">编辑</el-button>
|
|
|
|
|
+ <el-button type="danger" size="small" @click="removeRow(row)">删除</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+
|
|
|
|
|
+ <el-dialog v-model="detailDialog" title="等级详情" width="560px" destroy-on-close>
|
|
|
|
|
+ <el-descriptions :column="1" border>
|
|
|
|
|
+ <el-descriptions-item label="ID">{{ detail.id ?? "-" }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="名称">{{ detail.name ?? "-" }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="等级">{{ detail.grade ?? "-" }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="所需经验">{{ detail.exp_num ?? "-" }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="说明">{{ detail.explain ?? "-" }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="显示">
|
|
|
|
|
+ <el-tag :type="Number(detail.is_show) === 1 ? 'success' : 'info'" size="small">
|
|
|
|
|
+ {{ Number(detail.is_show) === 1 ? "显示" : "隐藏" }}
|
|
|
|
|
+ </el-tag>
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="图标">
|
|
|
|
|
+ <el-image
|
|
|
|
|
+ v-if="detail.icon"
|
|
|
|
|
+ class="detail-icon"
|
|
|
|
|
+ :src="detail.icon"
|
|
|
|
|
+ fit="cover"
|
|
|
|
|
+ :preview-src-list="[detail.icon]"
|
|
|
|
|
+ preview-teleported
|
|
|
|
|
+ />
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="创建时间">{{ formatTime(detail.created_at) }}</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="更新时间">{{ formatTime(detail.updated_at) }}</el-descriptions-item>
|
|
|
|
|
+ </el-descriptions>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+
|
|
|
|
|
+ <el-dialog v-model="editDialog" :title="editTitle" width="520px" destroy-on-close @closed="resetEditForm">
|
|
|
|
|
+ <el-form ref="formRef" :model="form" :rules="rules" label-width="96px">
|
|
|
|
|
+ <el-form-item label="名称" prop="name">
|
|
|
|
|
+ <el-input v-model="form.name" clearable maxlength="64" placeholder="如 Lv1" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="等级" prop="grade">
|
|
|
|
|
+ <el-input-number v-model="form.grade" :min="0" :max="9999" :step="1" controls-position="right" class="w-full-num" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="所需经验" prop="exp_num">
|
|
|
|
|
+ <el-input-number v-model="form.exp_num" :min="0" :max="999999999" :step="1" controls-position="right" class="w-full-num" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="图标" prop="icon">
|
|
|
|
|
+ <div class="icon-field">
|
|
|
|
|
+ <sc-upload v-model="form.icon" title="上传" :api-obj="$API.common.upload" />
|
|
|
|
|
+ <el-input v-model="form.icon" placeholder="或填写图片地址" clearable />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="说明" prop="explain">
|
|
|
|
|
+ <el-input v-model="form.explain" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="等级说明" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <el-button @click="editDialog = false">取消</el-button>
|
|
|
|
|
+ <el-button type="primary" :loading="saving" @click="submitForm">保存</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+ </el-main>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+function defaultLevelForm() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ id: "",
|
|
|
|
|
+ name: "",
|
|
|
|
|
+ grade: 1,
|
|
|
|
|
+ exp_num: 0,
|
|
|
|
|
+ icon: "",
|
|
|
|
|
+ explain: ""
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ name: "UserLevelList",
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ loading: false,
|
|
|
|
|
+ saving: false,
|
|
|
|
|
+ list: [],
|
|
|
|
|
+ detailDialog: false,
|
|
|
|
|
+ detail: {},
|
|
|
|
|
+ editDialog: false,
|
|
|
|
|
+ editTitle: "添加等级",
|
|
|
|
|
+ form: defaultLevelForm(),
|
|
|
|
|
+ rules: {
|
|
|
|
|
+ name: [{ required: true, message: "请输入名称", trigger: "blur" }],
|
|
|
|
|
+ grade: [{ required: true, message: "请输入等级", trigger: "change" }],
|
|
|
|
|
+ exp_num: [{ required: true, message: "请输入所需经验", trigger: "change" }],
|
|
|
|
|
+ icon: [{ required: true, message: "请上传或填写图标地址", trigger: "blur" }],
|
|
|
|
|
+ explain: [{ required: true, message: "请输入说明", trigger: "blur" }]
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ },
|
|
|
|
|
+ created() {
|
|
|
|
|
+ this.getList();
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ formatTime(val) {
|
|
|
|
|
+ if (val === undefined || val === null || val === "") return "-";
|
|
|
|
|
+ if (typeof val === "string" && /^\d{4}-\d{2}-\d{2}/.test(val.trim())) return val.trim();
|
|
|
|
|
+ const num = Number(val);
|
|
|
|
|
+ if (!Number.isFinite(num) || num <= 0) return String(val);
|
|
|
|
|
+ const sec = num > 1e12 ? Math.floor(num / 1000) : num;
|
|
|
|
|
+ return this.$TOOL.getTime(sec, "YYYY-MM-DD HH:mm:ss");
|
|
|
|
|
+ },
|
|
|
|
|
+ async getList() {
|
|
|
|
|
+ this.loading = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await this.$API.user.levelList.get();
|
|
|
|
|
+ if (res.code !== 1) {
|
|
|
|
|
+ this.$message.error(res.msg || "获取用户等级列表失败");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const raw = res.data || {};
|
|
|
|
|
+ this.list = Array.isArray(raw) ? raw : raw.list || raw.data || [];
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.loading = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ async showDetail(row) {
|
|
|
|
|
+ const id = row?.id;
|
|
|
|
|
+ if (id == null) return;
|
|
|
|
|
+ const res = await this.$API.user.levelInfo.get(id);
|
|
|
|
|
+ if (res.code !== 1) {
|
|
|
|
|
+ this.$message.error(res.msg || "获取详情失败");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.detail = res.data || {};
|
|
|
|
|
+ this.detailDialog = true;
|
|
|
|
|
+ },
|
|
|
|
|
+ openCreate() {
|
|
|
|
|
+ this.editTitle = "添加等级";
|
|
|
|
|
+ this.form = defaultLevelForm();
|
|
|
|
|
+ this.editDialog = true;
|
|
|
|
|
+ this.$nextTick(() => this.$refs.formRef?.clearValidate());
|
|
|
|
|
+ },
|
|
|
|
|
+ openEdit(row) {
|
|
|
|
|
+ this.editTitle = "编辑等级";
|
|
|
|
|
+ this.form = {
|
|
|
|
|
+ id: row.id != null ? String(row.id) : "",
|
|
|
|
|
+ name: row.name || "",
|
|
|
|
|
+ grade: row.grade != null ? Number(row.grade) : 1,
|
|
|
|
|
+ exp_num: row.exp_num != null ? Number(row.exp_num) : 0,
|
|
|
|
|
+ icon: row.icon || "",
|
|
|
|
|
+ explain: row.explain || ""
|
|
|
|
|
+ };
|
|
|
|
|
+ this.editDialog = true;
|
|
|
|
|
+ this.$nextTick(() => this.$refs.formRef?.clearValidate());
|
|
|
|
|
+ },
|
|
|
|
|
+ resetEditForm() {
|
|
|
|
|
+ this.form = defaultLevelForm();
|
|
|
|
|
+ },
|
|
|
|
|
+ submitForm() {
|
|
|
|
|
+ this.$refs.formRef.validate(async (valid) => {
|
|
|
|
|
+ if (!valid) return false;
|
|
|
|
|
+ this.saving = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const payload = {
|
|
|
|
|
+ name: (this.form.name || "").trim(),
|
|
|
|
|
+ grade: this.form.grade,
|
|
|
|
|
+ exp_num: this.form.exp_num,
|
|
|
|
|
+ icon: (this.form.icon || "").trim(),
|
|
|
|
|
+ explain: (this.form.explain || "").trim()
|
|
|
|
|
+ };
|
|
|
|
|
+ if (this.form.id) payload.id = String(this.form.id);
|
|
|
|
|
+ const res = await this.$API.user.levelSave.post(payload);
|
|
|
|
|
+ if (res.code !== 1) {
|
|
|
|
|
+ this.$message.error(res.msg || "保存失败");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$message.success(res.msg || "保存成功");
|
|
|
|
|
+ this.editDialog = false;
|
|
|
|
|
+ this.getList();
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.saving = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ async removeRow(row) {
|
|
|
|
|
+ const id = row?.id;
|
|
|
|
|
+ if (id == null) return;
|
|
|
|
|
+ try {
|
|
|
|
|
+ await this.$confirm("确认删除该等级吗?", "提示", {
|
|
|
|
|
+ type: "warning",
|
|
|
|
|
+ confirmButtonText: "删除",
|
|
|
|
|
+ cancelButtonText: "取消"
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const res = await this.$API.user.levelDel.delete(id);
|
|
|
|
|
+ if (res.code !== 1) {
|
|
|
|
|
+ this.$message.error(res.msg || "删除失败");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$message.success(res.msg || "删除成功");
|
|
|
|
|
+ this.getList();
|
|
|
|
|
+ },
|
|
|
|
|
+ async changeShow(row, show) {
|
|
|
|
|
+ const id = row?.id;
|
|
|
|
|
+ if (id == null) {
|
|
|
|
|
+ this.$message.error("缺少等级 ID");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const res = await this.$API.user.levelSetShow.get(id, show);
|
|
|
|
|
+ if (res.code !== 1) {
|
|
|
|
|
+ this.$message.error(res.msg || "显示状态更新失败");
|
|
|
|
|
+ this.getList();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$message.success(res.msg || "已更新");
|
|
|
|
|
+ row.is_show = show;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.card-title {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
|
|
+ letter-spacing: 0.02em;
|
|
|
|
|
+}
|
|
|
|
|
+.header-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.header-actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.level-table :deep(.el-table__header th.el-table__cell) {
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: var(--el-text-color-regular);
|
|
|
|
|
+}
|
|
|
|
|
+.level-icon {
|
|
|
|
|
+ width: 40px;
|
|
|
|
|
+ height: 40px;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-icon {
|
|
|
|
|
+ width: 72px;
|
|
|
|
|
+ height: 72px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+.icon-field {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+.w-full-num {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+.w-full-num :deep(.el-input__inner) {
|
|
|
|
|
+ text-align: left;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|