|
- {% extends "../layout.html" %}
-
- {% block title %}{{ columnInfo.name if columnInfo else '图片列表' }}{% endblock %}
-
- {% block content %}
- <div id="imageApp">
- <el-card shadow="never">
- <template #header>
- <div class="flex items-center justify-between">
- <div class="flex items-center gap-2">
- <span class="font-medium">{{ columnInfo.name if columnInfo else '图片列表' }}</span>
- <el-tag type="success" size="small">图片列表</el-tag>
- </div>
- <el-button type="primary" @click="openDialog()">+ 新增图片</el-button>
- </div>
- </template>
-
- <!-- 筛选栏 -->
- <div class="flex items-center gap-4 mb-4">
- <el-input v-model="query.keyword" placeholder="搜索标题..." style="width:180px;" clearable @keyup.enter="loadList"></el-input>
- <el-select v-model="query.status" placeholder="状态" style="width:100px;" clearable>
- <el-option label="启用" :value="1"></el-option>
- <el-option label="禁用" :value="0"></el-option>
- </el-select>
- <el-button type="primary" @click="loadList">搜索</el-button>
- <el-button @click="resetQuery">重置</el-button>
- </div>
-
- <!-- 卡片列表 -->
- <div class="image-grid" v-loading="loading">
- <div v-for="item in list" :key="item.id" class="image-card">
- <div class="image-thumb">
- <img :src="item.image || '/static/images/placeholder.png'" :alt="item.title">
- <span class="image-sort">${ item.sort }</span>
- </div>
- <div class="image-info">
- <div class="image-title">${ item.title }</div>
- <div class="image-meta">
- <el-tag :type="item.status ? 'success' : 'info'" size="small">${ item.status ? '启用' : '禁用' }</el-tag>
- <span class="image-date">${ item.create_time?.slice(0,10) }</span>
- </div>
- </div>
- <div class="image-actions">
- <el-button type="primary" link size="small" @click="openDialog(item)">编辑</el-button>
- <el-button type="danger" link size="small" @click="deleteItem(item)">删除</el-button>
- </div>
- </div>
- <!-- 新增卡片 -->
- <div class="image-card image-card-add" @click="openDialog()">
- <div class="add-inner">
- <el-icon :size="36"><Plus /></el-icon>
- <span>新增图片</span>
- </div>
- </div>
- </div>
-
- <!-- 分页 -->
- <div class="flex justify-end mt-4" v-if="total > pageSize">
- <el-pagination background layout="prev, pager, next" :total="total" :page-size="pageSize" v-model:current-page="page" @current-change="loadList"></el-pagination>
- </div>
- </el-card>
-
- <!-- 新增/编辑弹窗 -->
- <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px" destroy-on-close draggable top="5vh">
- <div class="dialog-scroll-body">
- <el-form :model="form" label-width="80px">
- <!-- 图片上传 -->
- <el-form-item label="图片" required>
- <div class="img-upload-wrap">
- <div class="img-upload-area" @click="triggerUpload">
- <img v-if="form.image" :src="form.image" class="img-preview">
- <div v-else class="img-placeholder">
- <el-icon :size="32"><Plus /></el-icon>
- <span>点击上传图片</span>
- </div>
- </div>
- <input type="file" ref="fileInput" accept="image/*" style="display:none;" @change="handleUpload">
- <div class="img-tip">支持 JPG/PNG/GIF,建议宽度不小于 800px</div>
- </div>
- </el-form-item>
-
- <el-form-item label="标题" required>
- <el-input v-model="form.title" placeholder="请输入标题"></el-input>
- </el-form-item>
- <el-form-item label="链接地址">
- <el-input v-model="form.link" placeholder="点击图片跳转的链接(可选)"></el-input>
- </el-form-item>
- <div class="flex gap-4">
- <el-form-item label="排序" class="flex-1">
- <el-input-number v-model="form.sort" :min="1" style="width:100%;"></el-input-number>
- </el-form-item>
- <el-form-item label="状态" class="flex-1">
- <el-switch v-model="form.status" :active-value="1" :inactive-value="0"></el-switch>
- </el-form-item>
- </div>
- </el-form>
- </div>
- <template #footer>
- <el-button @click="dialogVisible = false">取消</el-button>
- <el-button type="primary" @click="saveItem" :loading="saving">保存</el-button>
- </template>
- </el-dialog>
- </div>
-
- <style>
- .image-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px; margin-top: 16px; }
- .image-card { background: #fff; border: 1px solid #eee; border-radius: 8px; overflow: hidden; transition: .2s; }
- .image-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,.08); transform: translateY(-2px); }
- .image-thumb { position: relative; aspect-ratio: 4/3; background: #f5f5f5; overflow: hidden; }
- .image-thumb img { width: 100%; height: 100%; object-fit: contain; background: #fafafa; }
- .image-sort { position: absolute; top: 6px; left: 6px; background: #ff7800; color: #fff; font-size: 11px; font-weight: 600; width: 22px; height: 22px; border-radius: 4px; display: flex; align-items: center; justify-content: center; }
- .image-info { padding: 10px 12px 6px; }
- .image-title { font-size: 13px; font-weight: 500; color: #333; margin-bottom: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
- .image-meta { display: flex; align-items: center; gap: 8px; }
- .image-date { font-size: 11px; color: #bbb; }
- .image-actions { display: flex; align-items: center; gap: 4px; padding: 6px 12px 10px; border-top: 1px solid #f0f0f0; }
- .image-card-add { border: 2px dashed #ddd; display: flex; align-items: center; justify-content: center; cursor: pointer; min-height: 180px; }
- .image-card-add:hover { border-color: #ff7800; background: #fff7f0; }
- .add-inner { display: flex; flex-direction: column; align-items: center; gap: 8px; color: #bbb; font-size: 13px; }
-
- /* 弹窗滚动区域 */
- .dialog-scroll-body { max-height: 65vh; overflow-y: auto; overflow-x: hidden; }
-
- /* 图片上传 */
- .img-upload-wrap { width: 100%; max-width: 280px; }
- .img-upload-area { width: 100%; aspect-ratio: 4/3; border: 2px dashed #ddd; border-radius: 8px; cursor: pointer; display: flex; align-items: center; justify-content: center; overflow: hidden; transition: .2s; background: #fafafa; }
- .img-upload-area:hover { border-color: #ff7800; background: #fff7f0; }
- .img-preview { width: 100%; height: 100%; object-fit: contain; }
- .img-placeholder { display: flex; flex-direction: column; align-items: center; gap: 6px; color: #bbb; font-size: 12px; }
- .img-tip { font-size: 12px; color: #999; margin-top: 8px; }
- </style>
- {% endblock %}
-
- {% block js %}
- <script>
- const col = '{{ col }}';
- const { createApp, ref, reactive, onMounted } = Vue;
-
- const app = createApp({
- delimiters: ['${', '}'],
- setup() {
- const loading = ref(false);
- const list = ref([]);
- const total = ref(0);
- const page = ref(1);
- const pageSize = ref(50);
- const query = reactive({ keyword: '', status: '' });
-
- const dialogVisible = ref(false);
- const dialogTitle = ref('新增图片');
- const saving = ref(false);
- const fileInput = ref(null);
-
- const defaultForm = { id: null, title: '', image: '', link: '', sort: 1, status: 1 };
- const form = reactive({ ...defaultForm });
-
- async function loadList() {
- loading.value = true;
- try {
- const params = new URLSearchParams({ col, page: page.value, pageSize: pageSize.value, ...query });
- const res = await fetch('/admin/content/image/list?' + params).then(r => r.json());
- if (res.code === 0) {
- list.value = res.data.data || [];
- total.value = res.data.count || 0;
- }
- } finally { loading.value = false; }
- }
-
- function resetQuery() {
- query.keyword = '';
- query.status = '';
- page.value = 1;
- loadList();
- }
-
- function openDialog(item) {
- if (item) {
- dialogTitle.value = '编辑图片';
- Object.assign(form, {
- id: item.id, title: item.title, image: item.image,
- link: item.link || '', sort: item.sort || 1, status: item.status
- });
- } else {
- dialogTitle.value = '新增图片';
- Object.assign(form, { ...defaultForm, sort: list.value.length + 1 });
- }
- dialogVisible.value = true;
- }
-
- function triggerUpload() {
- fileInput.value?.click();
- }
-
- async function handleUpload(e) {
- const file = e.target.files[0];
- if (!file) return;
- const formData = new FormData();
- formData.append('file', file);
- try {
- const res = await fetch('/admin/upload', { method: 'POST', body: formData }).then(r => r.json());
- if (res.code === 0) {
- form.image = res.data.url;
- ElementPlus.ElMessage.success('上传成功');
- } else {
- ElementPlus.ElMessage.error(res.msg || '上传失败');
- }
- } catch (err) {
- ElementPlus.ElMessage.error('上传失败');
- }
- e.target.value = '';
- }
-
- async function saveItem() {
- if (!form.title.trim()) { ElementPlus.ElMessage.warning('请输入标题'); return; }
- if (!form.image) { ElementPlus.ElMessage.warning('请上传图片'); return; }
- saving.value = true;
- try {
- const url = form.id ? '/admin/content/image/edit' : '/admin/content/image/add';
- const body = { ...form, col };
- const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }).then(r => r.json());
- if (res.code === 0) {
- ElementPlus.ElMessage.success('保存成功');
- dialogVisible.value = false;
- loadList();
- } else {
- ElementPlus.ElMessage.error(res.msg || '保存失败');
- }
- } finally { saving.value = false; }
- }
-
- async function deleteItem(item) {
- try {
- await ElementPlus.ElMessageBox.confirm('确定删除"' + item.title + '"?', '提示', { type: 'warning' });
- const res = await fetch('/admin/content/image/delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: item.id }) }).then(r => r.json());
- if (res.code === 0) {
- ElementPlus.ElMessage.success('删除成功');
- loadList();
- } else {
- ElementPlus.ElMessage.error(res.msg || '删除失败');
- }
- } catch {}
- }
-
- onMounted(() => loadList());
-
- return {
- loading, list, total, page, pageSize, query,
- dialogVisible, dialogTitle, saving, form, fileInput,
- loadList, resetQuery, openDialog, triggerUpload, handleUpload, saveItem, deleteItem
- };
- }
- });
-
- for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
- app.component(key, component);
- }
- app.use(ElementPlus, { locale: ElementPlusLocaleZhCn });
- app.mount('#imageApp');
- </script>
- {% endblock %}
|