|
@@ -21,13 +21,31 @@
|
|
|
v-if="row.icon"
|
|
v-if="row.icon"
|
|
|
class="level-icon"
|
|
class="level-icon"
|
|
|
:src="row.icon"
|
|
:src="row.icon"
|
|
|
- fit="cover"
|
|
|
|
|
|
|
+ fit="contain"
|
|
|
:preview-src-list="[row.icon]"
|
|
:preview-src-list="[row.icon]"
|
|
|
preview-teleported
|
|
preview-teleported
|
|
|
/>
|
|
/>
|
|
|
<span v-else>-</span>
|
|
<span v-else>-</span>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
|
|
+ <el-table-column label="背景图" width="88" align="center">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-image
|
|
|
|
|
+ v-if="row.background"
|
|
|
|
|
+ class="level-icon"
|
|
|
|
|
+ :src="row.background"
|
|
|
|
|
+ fit="contain"
|
|
|
|
|
+ :preview-src-list="[row.background]"
|
|
|
|
|
+ preview-teleported
|
|
|
|
|
+ />
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="颜色" min-width="140" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ rowColorSummary(row) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
<el-table-column prop="explain" label="说明" min-width="200" show-overflow-tooltip />
|
|
<el-table-column prop="explain" label="说明" min-width="200" show-overflow-tooltip />
|
|
|
<el-table-column label="显示" width="120" align="center">
|
|
<el-table-column label="显示" width="120" align="center">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
@@ -89,12 +107,29 @@
|
|
|
/>
|
|
/>
|
|
|
<span v-else>-</span>
|
|
<span v-else>-</span>
|
|
|
</el-descriptions-item>
|
|
</el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="背景图">
|
|
|
|
|
+ <el-image
|
|
|
|
|
+ v-if="detail.background"
|
|
|
|
|
+ class="detail-bg"
|
|
|
|
|
+ :src="detail.background"
|
|
|
|
|
+ fit="cover"
|
|
|
|
|
+ :preview-src-list="[detail.background]"
|
|
|
|
|
+ preview-teleported
|
|
|
|
|
+ />
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="颜色">
|
|
|
|
|
+ <template v-if="detailColorTags.length">
|
|
|
|
|
+ <el-tag v-for="(c, i) in detailColorTags" :key="i" size="small" class="color-tag">{{ c }}</el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
<el-descriptions-item label="创建时间">{{ formatTime(detail.created_at) }}</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-item label="更新时间">{{ formatTime(detail.updated_at) }}</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
</el-descriptions>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
|
- <el-dialog v-model="editDialog" :title="editTitle" width="520px" destroy-on-close @closed="resetEditForm">
|
|
|
|
|
|
|
+ <el-dialog v-model="editDialog" :title="editTitle" width="580px" destroy-on-close @closed="resetEditForm">
|
|
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="96px">
|
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="96px">
|
|
|
<el-form-item label="名称" prop="name">
|
|
<el-form-item label="名称" prop="name">
|
|
|
<el-input v-model="form.name" clearable maxlength="64" placeholder="如 Lv1" />
|
|
<el-input v-model="form.name" clearable maxlength="64" placeholder="如 Lv1" />
|
|
@@ -111,6 +146,28 @@
|
|
|
<el-input v-model="form.icon" placeholder="或填写图片地址" clearable />
|
|
<el-input v-model="form.icon" placeholder="或填写图片地址" clearable />
|
|
|
</div>
|
|
</div>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
|
|
+ <el-form-item label="背景图" prop="background">
|
|
|
|
|
+ <div class="icon-field">
|
|
|
|
|
+ <sc-upload v-model="form.background" title="上传" :api-obj="$API.common.upload" />
|
|
|
|
|
+ <el-input v-model="form.background" placeholder="或填写背景图地址" clearable />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="颜色" prop="colorList">
|
|
|
|
|
+ <div class="color-palette-field">
|
|
|
|
|
+ <p v-if="!form.colorList.length" class="palette-hint">点击「添加颜色」,用取色器选择</p>
|
|
|
|
|
+ <div v-for="(_, index) in form.colorList" :key="'palette-row-' + index" class="palette-row">
|
|
|
|
|
+ <el-color-picker
|
|
|
|
|
+ :model-value="form.colorList[index]"
|
|
|
|
|
+ color-format="hex"
|
|
|
|
|
+ :show-alpha="false"
|
|
|
|
|
+ @update:model-value="(v) => updatePaletteColor(index, v)"
|
|
|
|
|
+ />
|
|
|
|
|
+ <span class="palette-hex">{{ form.colorList[index] }}</span>
|
|
|
|
|
+ <el-button type="danger" link size="small" @click="removePaletteColor(index)">移除</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-button size="small" type="primary" plain @click="addPaletteColor">添加颜色</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
<el-form-item label="说明" prop="explain">
|
|
<el-form-item label="说明" prop="explain">
|
|
|
<el-input v-model="form.explain" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="等级说明" />
|
|
<el-input v-model="form.explain" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="等级说明" />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
@@ -131,12 +188,50 @@ function defaultLevelForm() {
|
|
|
grade: 1,
|
|
grade: 1,
|
|
|
exp_num: 0,
|
|
exp_num: 0,
|
|
|
icon: "",
|
|
icon: "",
|
|
|
|
|
+ background: "",
|
|
|
|
|
+ colorList: [],
|
|
|
explain: ""
|
|
explain: ""
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/** 规范为六位小写 #rrggbb,供接口 ["#000000"] 格式 */
|
|
|
|
|
+function normalizeHex(val) {
|
|
|
|
|
+ if (val == null || val === "") return "#000000";
|
|
|
|
|
+ let s = String(val).trim();
|
|
|
|
|
+ if (!s.startsWith("#")) s = `#${s}`;
|
|
|
|
|
+ if (s.length === 9 && /^#[0-9a-fA-F]{8}$/.test(s)) s = s.slice(0, 7);
|
|
|
|
|
+ if (s.length === 4 && /^#[0-9a-fA-F]{3}$/.test(s)) {
|
|
|
|
|
+ s = `#${s[1]}${s[1]}${s[2]}${s[2]}${s[3]}${s[3]}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!/^#[0-9a-fA-F]{6}$/.test(s)) return "#000000";
|
|
|
|
|
+ return s.toLowerCase();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 将接口里的 color(一维字符串数组;兼容历史 JSON 字符串或逗号分隔)解析为字符串数组 */
|
|
|
|
|
+function parseColorList(val) {
|
|
|
|
|
+ if (val == null || val === "") return [];
|
|
|
|
|
+ if (Array.isArray(val)) return val.map(String).filter(Boolean);
|
|
|
|
|
+ if (typeof val === "string") {
|
|
|
|
|
+ const s = val.trim();
|
|
|
|
|
+ if (!s) return [];
|
|
|
|
|
+ try {
|
|
|
|
|
+ const parsed = JSON.parse(s);
|
|
|
|
|
+ if (Array.isArray(parsed)) return parsed.map(String).filter(Boolean);
|
|
|
|
|
+ } catch (_) {
|
|
|
|
|
+ /* 非 JSON 时按分隔符拆分 */
|
|
|
|
|
+ }
|
|
|
|
|
+ return s.split(/[,,]/).map((x) => x.trim()).filter(Boolean);
|
|
|
|
|
+ }
|
|
|
|
|
+ return [];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
export default {
|
|
export default {
|
|
|
name: "UserLevelList",
|
|
name: "UserLevelList",
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ detailColorTags() {
|
|
|
|
|
+ return parseColorList(this.detail?.color);
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
data() {
|
|
data() {
|
|
|
return {
|
|
return {
|
|
|
loading: false,
|
|
loading: false,
|
|
@@ -160,6 +255,25 @@ export default {
|
|
|
this.getList();
|
|
this.getList();
|
|
|
},
|
|
},
|
|
|
methods: {
|
|
methods: {
|
|
|
|
|
+ rowColorSummary(row) {
|
|
|
|
|
+ const arr = parseColorList(row?.color);
|
|
|
|
|
+ if (!arr.length) return "-";
|
|
|
|
|
+ return arr.join("、");
|
|
|
|
|
+ },
|
|
|
|
|
+ addPaletteColor() {
|
|
|
|
|
+ this.form.colorList.push("#409eff");
|
|
|
|
|
+ },
|
|
|
|
|
+ removePaletteColor(index) {
|
|
|
|
|
+ this.form.colorList.splice(index, 1);
|
|
|
|
|
+ },
|
|
|
|
|
+ updatePaletteColor(index, val) {
|
|
|
|
|
+ if (val == null || val === "") {
|
|
|
|
|
+ this.form.colorList.splice(index, 1);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const hex = normalizeHex(val);
|
|
|
|
|
+ this.form.colorList.splice(index, 1, hex);
|
|
|
|
|
+ },
|
|
|
formatTime(val) {
|
|
formatTime(val) {
|
|
|
if (val === undefined || val === null || val === "") return "-";
|
|
if (val === undefined || val === null || val === "") return "-";
|
|
|
if (typeof val === "string" && /^\d{4}-\d{2}-\d{2}/.test(val.trim())) return val.trim();
|
|
if (typeof val === "string" && /^\d{4}-\d{2}-\d{2}/.test(val.trim())) return val.trim();
|
|
@@ -207,6 +321,8 @@ export default {
|
|
|
grade: row.grade != null ? Number(row.grade) : 1,
|
|
grade: row.grade != null ? Number(row.grade) : 1,
|
|
|
exp_num: row.exp_num != null ? Number(row.exp_num) : 0,
|
|
exp_num: row.exp_num != null ? Number(row.exp_num) : 0,
|
|
|
icon: row.icon || "",
|
|
icon: row.icon || "",
|
|
|
|
|
+ background: row.background || "",
|
|
|
|
|
+ colorList: parseColorList(row.color).map((c) => normalizeHex(c)),
|
|
|
explain: row.explain || ""
|
|
explain: row.explain || ""
|
|
|
};
|
|
};
|
|
|
this.editDialog = true;
|
|
this.editDialog = true;
|
|
@@ -220,11 +336,15 @@ export default {
|
|
|
if (!valid) return false;
|
|
if (!valid) return false;
|
|
|
this.saving = true;
|
|
this.saving = true;
|
|
|
try {
|
|
try {
|
|
|
|
|
+ /** 接口约定:color 为一维数组,元素为颜色字符串,如 ["#000000","#ffffff"] */
|
|
|
|
|
+ const color = (this.form.colorList || []).map((x) => normalizeHex(x));
|
|
|
const payload = {
|
|
const payload = {
|
|
|
name: (this.form.name || "").trim(),
|
|
name: (this.form.name || "").trim(),
|
|
|
grade: this.form.grade,
|
|
grade: this.form.grade,
|
|
|
exp_num: this.form.exp_num,
|
|
exp_num: this.form.exp_num,
|
|
|
icon: (this.form.icon || "").trim(),
|
|
icon: (this.form.icon || "").trim(),
|
|
|
|
|
+ background: (this.form.background || "").trim(),
|
|
|
|
|
+ color,
|
|
|
explain: (this.form.explain || "").trim()
|
|
explain: (this.form.explain || "").trim()
|
|
|
};
|
|
};
|
|
|
if (this.form.id) payload.id = String(this.form.id);
|
|
if (this.form.id) payload.id = String(this.form.id);
|
|
@@ -325,4 +445,33 @@ export default {
|
|
|
.w-full-num :deep(.el-input__inner) {
|
|
.w-full-num :deep(.el-input__inner) {
|
|
|
text-align: left;
|
|
text-align: left;
|
|
|
}
|
|
}
|
|
|
|
|
+.color-palette-field {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+.palette-hint {
|
|
|
|
|
+ margin: 0 0 10px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: var(--el-text-color-placeholder);
|
|
|
|
|
+}
|
|
|
|
|
+.palette-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+.palette-hex {
|
|
|
|
|
+ font-family: ui-monospace, monospace;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: var(--el-text-color-regular);
|
|
|
|
|
+ min-width: 7.5em;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-bg {
|
|
|
|
|
+ max-width: 240px;
|
|
|
|
|
+ max-height: 120px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+.color-tag {
|
|
|
|
|
+ margin-right: 6px;
|
|
|
|
|
+ margin-bottom: 4px;
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|