- 成就主题/等级管理 - 梦想目标图标管理(dream-presets)与路由「梦想图标」 Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,37 @@
|
|||||||
|
import request from '../utils/request'
|
||||||
|
|
||||||
|
export function listThemes() {
|
||||||
|
return request({ url: '/api/admin/achievement/themes', method: 'get' })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTheme(id) {
|
||||||
|
return request({ url: `/api/admin/achievement/themes/${id}`, method: 'get' })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTheme(data) {
|
||||||
|
return request({ url: '/api/admin/achievement/themes', method: 'post', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateTheme(id, data) {
|
||||||
|
return request({ url: `/api/admin/achievement/themes/${id}`, method: 'put', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteTheme(id) {
|
||||||
|
return request({ url: `/api/admin/achievement/themes/${id}`, method: 'delete' })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listLevels(themeId) {
|
||||||
|
return request({ url: `/api/admin/achievement/themes/${themeId}/levels`, method: 'get' })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createLevel(data) {
|
||||||
|
return request({ url: '/api/admin/achievement/levels', method: 'post', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateLevel(id, data) {
|
||||||
|
return request({ url: `/api/admin/achievement/levels/${id}`, method: 'put', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteLevel(id) {
|
||||||
|
return request({ url: `/api/admin/achievement/levels/${id}`, method: 'delete' })
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import request from '../utils/request'
|
||||||
|
|
||||||
|
export function listDreamPresets() {
|
||||||
|
return request({ url: '/api/admin/dream-presets', method: 'get' })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDreamPreset(data) {
|
||||||
|
return request({ url: '/api/admin/dream-presets', method: 'post', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateDreamPreset(id, data) {
|
||||||
|
return request({ url: `/api/admin/dream-presets/${id}`, method: 'put', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteDreamPreset(id) {
|
||||||
|
return request({ url: `/api/admin/dream-presets/${id}`, method: 'delete' })
|
||||||
|
}
|
||||||
@@ -102,6 +102,18 @@ const routes = [
|
|||||||
component: () => import('../views/smoke/index.vue'),
|
component: () => import('../views/smoke/index.vue'),
|
||||||
meta: { title: '戒烟小程序', icon: 'Opportunity' }
|
meta: { title: '戒烟小程序', icon: 'Opportunity' }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'achievement',
|
||||||
|
name: 'Achievement',
|
||||||
|
component: () => import('../views/achievement/index.vue'),
|
||||||
|
meta: { title: '成就管理', icon: 'Trophy' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'dream-presets',
|
||||||
|
name: 'DreamPresets',
|
||||||
|
component: () => import('../views/dream-presets/index.vue'),
|
||||||
|
meta: { title: '梦想图标', icon: 'Flag' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
|
|||||||
@@ -0,0 +1,276 @@
|
|||||||
|
<template>
|
||||||
|
<div class="achievement-page">
|
||||||
|
<el-card>
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>成就主题管理</span>
|
||||||
|
<el-button type="primary" @click="openThemeDialog()">新增主题</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-table :data="themes" v-loading="loading" stripe>
|
||||||
|
<el-table-column prop="id" label="ID" width="60" />
|
||||||
|
<el-table-column prop="icon" label="图标" width="60" />
|
||||||
|
<el-table-column prop="name" label="名称" width="100" />
|
||||||
|
<el-table-column prop="key" label="标识" width="100" />
|
||||||
|
<el-table-column label="等级数" width="80">
|
||||||
|
<template #default="{ row }">{{ row.levels?.length || 0 }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="等级预览" min-width="280">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-for="(level, idx) in (row.levels || [])" :key="level.id" class="level-preview">
|
||||||
|
{{ level.name }}({{ level.required_days }}天)<span v-if="idx < row.levels.length - 1"> → </span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="sort_order" label="排序" width="60" />
|
||||||
|
<el-table-column label="状态" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.is_active ? 'success' : 'info'" size="small">
|
||||||
|
{{ row.is_active ? '启用' : '禁用' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="200" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button size="small" @click="openLevelDialog(row)">管理等级</el-button>
|
||||||
|
<el-button size="small" @click="openThemeDialog(row)">编辑</el-button>
|
||||||
|
<el-button size="small" type="danger" @click="handleDeleteTheme(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 主题编辑弹窗 -->
|
||||||
|
<el-dialog v-model="themeDialogVisible" :title="editingTheme ? '编辑主题' : '新增主题'" width="460px">
|
||||||
|
<el-form :model="themeForm" label-width="80px">
|
||||||
|
<el-form-item label="名称">
|
||||||
|
<el-input v-model="themeForm.name" placeholder="如:修仙" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="标识">
|
||||||
|
<el-input v-model="themeForm.key" placeholder="如:xiuxian" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="图标">
|
||||||
|
<el-input v-model="themeForm.icon" placeholder="如:⚔️" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序">
|
||||||
|
<el-input-number v-model="themeForm.sort_order" :min="0" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="启用">
|
||||||
|
<el-switch v-model="themeForm.is_active" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="themeDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="saveTheme" :loading="saving">保存</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 等级管理弹窗 -->
|
||||||
|
<el-dialog v-model="levelDialogVisible" :title="`管理等级 - ${currentTheme?.name || ''}`" width="680px">
|
||||||
|
<div class="level-toolbar">
|
||||||
|
<el-button type="primary" size="small" @click="openLevelForm()">新增等级</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table :data="levels" v-loading="levelLoading" stripe size="small">
|
||||||
|
<el-table-column prop="name" label="名称" width="120" />
|
||||||
|
<el-table-column prop="icon" label="图标" width="60" />
|
||||||
|
<el-table-column prop="required_days" label="所需天数" width="100" />
|
||||||
|
<el-table-column prop="sort_order" label="排序" width="60" />
|
||||||
|
<el-table-column label="操作" width="160">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button size="small" @click="openLevelForm(row)">编辑</el-button>
|
||||||
|
<el-button size="small" type="danger" @click="handleDeleteLevel(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<el-divider v-if="showLevelForm" />
|
||||||
|
<el-form v-if="showLevelForm" :model="levelForm" label-width="80px" size="small" class="level-form">
|
||||||
|
<el-form-item label="名称">
|
||||||
|
<el-input v-model="levelForm.name" placeholder="如:炼体" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="图标">
|
||||||
|
<el-input v-model="levelForm.icon" placeholder="可选" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="所需天数">
|
||||||
|
<el-input-number v-model="levelForm.required_days" :min="0" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序">
|
||||||
|
<el-input-number v-model="levelForm.sort_order" :min="0" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="showLevelForm = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="saveLevel" :loading="levelSaving">保存</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import {
|
||||||
|
listThemes, createTheme, updateTheme, deleteTheme,
|
||||||
|
listLevels, createLevel, updateLevel, deleteLevel
|
||||||
|
} from '../../api/achievement'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const saving = ref(false)
|
||||||
|
const themes = ref([])
|
||||||
|
const themeDialogVisible = ref(false)
|
||||||
|
const editingTheme = ref(null)
|
||||||
|
const themeForm = ref({ name: '', key: '', icon: '', sort_order: 0, is_active: true })
|
||||||
|
|
||||||
|
const levelDialogVisible = ref(false)
|
||||||
|
const levelLoading = ref(false)
|
||||||
|
const levelSaving = ref(false)
|
||||||
|
const currentTheme = ref(null)
|
||||||
|
const levels = ref([])
|
||||||
|
const showLevelForm = ref(false)
|
||||||
|
const editingLevel = ref(null)
|
||||||
|
const levelForm = ref({ name: '', icon: '', required_days: 0, sort_order: 0 })
|
||||||
|
|
||||||
|
async function loadThemes() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await listThemes()
|
||||||
|
themes.value = res.data?.themes || []
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('加载主题失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openThemeDialog(theme = null) {
|
||||||
|
editingTheme.value = theme
|
||||||
|
if (theme) {
|
||||||
|
themeForm.value = { name: theme.name, key: theme.key, icon: theme.icon, sort_order: theme.sort_order, is_active: theme.is_active }
|
||||||
|
} else {
|
||||||
|
themeForm.value = { name: '', key: '', icon: '', sort_order: 0, is_active: true }
|
||||||
|
}
|
||||||
|
themeDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveTheme() {
|
||||||
|
if (!themeForm.value.name || !themeForm.value.key) {
|
||||||
|
ElMessage.warning('名称和标识必填')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
saving.value = true
|
||||||
|
try {
|
||||||
|
if (editingTheme.value) {
|
||||||
|
await updateTheme(editingTheme.value.id, themeForm.value)
|
||||||
|
ElMessage.success('更新成功')
|
||||||
|
} else {
|
||||||
|
await createTheme(themeForm.value)
|
||||||
|
ElMessage.success('创建成功')
|
||||||
|
}
|
||||||
|
themeDialogVisible.value = false
|
||||||
|
await loadThemes()
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('保存失败')
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDeleteTheme(theme) {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(`确定删除主题「${theme.name}」?`, '确认')
|
||||||
|
await deleteTheme(theme.id)
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
await loadThemes()
|
||||||
|
} catch (e) {
|
||||||
|
if (e !== 'cancel') ElMessage.error('删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openLevelDialog(theme) {
|
||||||
|
currentTheme.value = theme
|
||||||
|
levelDialogVisible.value = true
|
||||||
|
showLevelForm.value = false
|
||||||
|
await loadLevels(theme.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadLevels(themeId) {
|
||||||
|
levelLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await listLevels(themeId)
|
||||||
|
levels.value = res.data?.levels || []
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('加载等级失败')
|
||||||
|
} finally {
|
||||||
|
levelLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openLevelForm(level = null) {
|
||||||
|
editingLevel.value = level
|
||||||
|
if (level) {
|
||||||
|
levelForm.value = { name: level.name, icon: level.icon || '', required_days: level.required_days, sort_order: level.sort_order }
|
||||||
|
} else {
|
||||||
|
levelForm.value = { name: '', icon: '', required_days: 0, sort_order: levels.value.length }
|
||||||
|
}
|
||||||
|
showLevelForm.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveLevel() {
|
||||||
|
if (!levelForm.value.name) {
|
||||||
|
ElMessage.warning('名称必填')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
levelSaving.value = true
|
||||||
|
try {
|
||||||
|
if (editingLevel.value) {
|
||||||
|
await updateLevel(editingLevel.value.id, levelForm.value)
|
||||||
|
ElMessage.success('更新成功')
|
||||||
|
} else {
|
||||||
|
await createLevel({ ...levelForm.value, theme_id: currentTheme.value.id })
|
||||||
|
ElMessage.success('创建成功')
|
||||||
|
}
|
||||||
|
showLevelForm.value = false
|
||||||
|
await loadLevels(currentTheme.value.id)
|
||||||
|
await loadThemes()
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('保存失败')
|
||||||
|
} finally {
|
||||||
|
levelSaving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDeleteLevel(level) {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(`确定删除等级「${level.name}」?`, '确认')
|
||||||
|
await deleteLevel(level.id)
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
await loadLevels(currentTheme.value.id)
|
||||||
|
await loadThemes()
|
||||||
|
} catch (e) {
|
||||||
|
if (e !== 'cancel') ElMessage.error('删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadThemes()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.level-preview {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
.level-toolbar {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.level-form {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dream-icons-page">
|
||||||
|
<el-card>
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>梦想目标图标管理</span>
|
||||||
|
<el-button type="primary" @click="openDialog()">新增图标</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-alert type="info" :closable="false" style="margin-bottom: 16px">
|
||||||
|
用户添加梦想目标时,从此处配置的图标中选择。名称和价格由用户自行填写。
|
||||||
|
</el-alert>
|
||||||
|
|
||||||
|
<el-table :data="icons" v-loading="loading" stripe>
|
||||||
|
<el-table-column prop="id" label="ID" width="60" />
|
||||||
|
<el-table-column label="图标" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.cover_image && row.cover_image.startsWith('icon:')" style="font-size: 28px">
|
||||||
|
{{ row.cover_image.replace('icon:', '') }}
|
||||||
|
</span>
|
||||||
|
<el-image v-else-if="row.cover_image" :src="row.cover_image" style="width: 44px; height: 44px; border-radius: 8px" fit="cover" />
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="title" label="图标名称" min-width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span style="color: #999">{{ row.title || '-' }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="sort_order" label="排序" width="80" />
|
||||||
|
<el-table-column label="状态" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.is_active ? 'success' : 'info'" size="small">
|
||||||
|
{{ row.is_active ? '启用' : '禁用' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="180" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button size="small" @click="openDialog(row)">编辑</el-button>
|
||||||
|
<el-popconfirm title="确定删除?" @confirm="handleDelete(row.id)">
|
||||||
|
<template #reference>
|
||||||
|
<el-button size="small" type="danger">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-popconfirm>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-dialog v-model="dialogVisible" :title="editingId ? '编辑图标' : '新增图标'" width="480px">
|
||||||
|
<el-form :model="form" label-width="90px">
|
||||||
|
<el-form-item label="图标名称">
|
||||||
|
<el-input v-model="form.title" placeholder="备注名(如:耳机、手机)" maxlength="20" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Emoji" required>
|
||||||
|
<div class="icon-grid">
|
||||||
|
<div
|
||||||
|
v-for="icon in emojiOptions"
|
||||||
|
:key="icon"
|
||||||
|
class="icon-option"
|
||||||
|
:class="{ active: form.cover_image === `icon:${icon}` }"
|
||||||
|
@click="selectEmoji(icon)"
|
||||||
|
>
|
||||||
|
{{ icon }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="或图片URL">
|
||||||
|
<el-input v-model="form.imageUrl" placeholder="https://... 图片链接(优先于 Emoji)" @input="onImageUrlInput" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="预览" v-if="previewImage">
|
||||||
|
<div class="preview-box">
|
||||||
|
<span v-if="previewImage.startsWith('icon:')" style="font-size: 36px">{{ previewImage.replace('icon:', '') }}</span>
|
||||||
|
<el-image v-else :src="previewImage" style="width: 60px; height: 60px; border-radius: 10px" fit="cover" />
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序">
|
||||||
|
<el-input-number v-model="form.sort_order" :min="0" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="启用">
|
||||||
|
<el-switch v-model="form.is_active" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="saving" @click="handleSave">保存</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { listDreamPresets, createDreamPreset, updateDreamPreset, deleteDreamPreset } from '../../api/dreamPreset'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const saving = ref(false)
|
||||||
|
const icons = ref([])
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const editingId = ref(null)
|
||||||
|
|
||||||
|
const emojiOptions = [
|
||||||
|
'🎧', '👟', '📱', '⌚', '🎮', '📷',
|
||||||
|
'💻', '🎸', '🏖️', '🎂', '🎁', '🚲',
|
||||||
|
'🎒', '👜', '🕶️', '🧸', '🏠', '✈️',
|
||||||
|
'🚗', '💎', '🎨', '📚', '🍰', '🌸',
|
||||||
|
]
|
||||||
|
|
||||||
|
const defaultForm = () => ({
|
||||||
|
title: '',
|
||||||
|
cover_image: '',
|
||||||
|
imageUrl: '',
|
||||||
|
sort_order: 0,
|
||||||
|
is_active: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const form = ref(defaultForm())
|
||||||
|
|
||||||
|
const previewImage = computed(() => {
|
||||||
|
if (form.value.imageUrl.trim()) return form.value.imageUrl.trim()
|
||||||
|
if (form.value.cover_image) return form.value.cover_image
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
function selectEmoji(emoji) {
|
||||||
|
form.value.cover_image = `icon:${emoji}`
|
||||||
|
form.value.imageUrl = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function onImageUrlInput() {
|
||||||
|
if (form.value.imageUrl.trim()) {
|
||||||
|
form.value.cover_image = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchList() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await listDreamPresets()
|
||||||
|
icons.value = res.data?.items || []
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('获取失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDialog(row) {
|
||||||
|
if (row) {
|
||||||
|
editingId.value = row.id
|
||||||
|
const isIcon = row.cover_image?.startsWith('icon:')
|
||||||
|
form.value = {
|
||||||
|
title: row.title || '',
|
||||||
|
cover_image: isIcon ? row.cover_image : '',
|
||||||
|
imageUrl: isIcon ? '' : (row.cover_image || ''),
|
||||||
|
sort_order: row.sort_order,
|
||||||
|
is_active: row.is_active,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
editingId.value = null
|
||||||
|
form.value = defaultForm()
|
||||||
|
}
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSave() {
|
||||||
|
const coverImage = form.value.imageUrl.trim() || form.value.cover_image || ''
|
||||||
|
if (!coverImage) {
|
||||||
|
ElMessage.warning('请选择 Emoji 或填入图片链接')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
saving.value = true
|
||||||
|
const data = {
|
||||||
|
title: form.value.title.trim(),
|
||||||
|
cover_image: coverImage,
|
||||||
|
sort_order: form.value.sort_order,
|
||||||
|
is_active: form.value.is_active,
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (editingId.value) {
|
||||||
|
await updateDreamPreset(editingId.value, data)
|
||||||
|
ElMessage.success('更新成功')
|
||||||
|
} else {
|
||||||
|
await createDreamPreset(data)
|
||||||
|
ElMessage.success('创建成功')
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
await fetchList()
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('保存失败')
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDelete(id) {
|
||||||
|
try {
|
||||||
|
await deleteDreamPreset(id)
|
||||||
|
ElMessage.success('已删除')
|
||||||
|
await fetchList()
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(fetchList)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(8, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-option {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 22px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
background: #f5f7fa;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-option:hover {
|
||||||
|
border-color: #c6e7d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-option.active {
|
||||||
|
border-color: #14936d;
|
||||||
|
background: #ecfdf5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #f9fafb;
|
||||||
|
border: 1px dashed #e5e7eb;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user