177 lines
5.2 KiB
Vue
177 lines
5.2 KiB
Vue
<template>
|
||
<div v-loading="loading" class="flex flex-col gap-4 min-h-40">
|
||
<ElTabs v-if="ready" v-model="activeTab" type="border-card">
|
||
<ElTabPane label="在线教育" name="platforms">
|
||
<div class="flex flex-wrap items-center justify-between gap-3 mb-4">
|
||
<p class="m-0 text-sm text-g-600"
|
||
>每张卡片一张图,图下是教育网站名称。门户点击后在新窗口打开网址。</p
|
||
>
|
||
<ElButton v-auth="'edu:edit'" type="primary" :loading="saving" @click="save"
|
||
>保存</ElButton
|
||
>
|
||
</div>
|
||
<div class="media-grid">
|
||
<article v-for="(item, index) in items" :key="index" class="media-card">
|
||
<HomeImageField
|
||
:url="item.imageUrl"
|
||
:disabled="!canEdit"
|
||
@pick="(payload) => assignImage(item, payload)"
|
||
@clear="clearImage(item)"
|
||
/>
|
||
<ElInput
|
||
v-model="item.name"
|
||
class="mt-3"
|
||
:disabled="!canEdit"
|
||
placeholder="教育网站名称"
|
||
/>
|
||
<ElInput v-model="item.url" class="mt-2" :disabled="!canEdit" placeholder="https://" />
|
||
<div class="mt-3 flex items-center justify-between gap-2">
|
||
<div class="flex items-center gap-2">
|
||
<span class="text-sm text-g-600">顺序</span>
|
||
<ElInputNumber
|
||
v-model="item.sortOrder"
|
||
:min="0"
|
||
:disabled="!canEdit"
|
||
controls-position="right"
|
||
/>
|
||
</div>
|
||
<ElButton v-if="canEdit" link type="danger" @click="items.splice(index, 1)"
|
||
>删除</ElButton
|
||
>
|
||
</div>
|
||
</article>
|
||
<button v-if="canEdit" type="button" class="media-add" @click="addItem">
|
||
<span>添加教育网站</span>
|
||
</button>
|
||
</div>
|
||
</ElTabPane>
|
||
<ElTabPane label="教育公告" name="notices">
|
||
<AboutColumnEditor
|
||
v-if="activeTab === 'notices'"
|
||
category="edu_notices"
|
||
title="教育公告"
|
||
edit-auth="edu:edit"
|
||
enable-cover
|
||
/>
|
||
</ElTabPane>
|
||
<ElTabPane label="协会教育" name="association">
|
||
<AssociationEducation v-if="activeTab === 'association'" />
|
||
</ElTabPane>
|
||
</ElTabs>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import AssociationEducation from './components/AssociationEducation.vue'
|
||
import AboutColumnEditor from '@/views/about/components/AboutColumnEditor.vue'
|
||
import { fetchEduPlatforms, saveEduPlatforms } from '@/api/home'
|
||
import { useAuth } from '@/hooks/core/useAuth'
|
||
import HomeImageField from '@/views/home/components/HomeImageField.vue'
|
||
|
||
defineOptions({ name: 'ContinuingEducation' })
|
||
|
||
interface PlatformItem {
|
||
name: string
|
||
url: string
|
||
imageFileId: number | null
|
||
imageUrl: string
|
||
sortOrder: number
|
||
}
|
||
|
||
const { hasAuth } = useAuth()
|
||
const canEdit = computed(() => hasAuth('edu:edit'))
|
||
const activeTab = ref('platforms')
|
||
const loading = ref(false)
|
||
const ready = ref(false)
|
||
const saving = ref(false)
|
||
const items = ref<PlatformItem[]>([])
|
||
|
||
const load = async () => {
|
||
loading.value = true
|
||
try {
|
||
const data = await fetchEduPlatforms()
|
||
items.value = (data.items || []).map((item) => ({
|
||
name: item.name || '',
|
||
url: item.url || '',
|
||
imageFileId: item.imageFileId ?? null,
|
||
imageUrl: item.imageUrl || '',
|
||
sortOrder: item.sortOrder ?? 0
|
||
}))
|
||
ready.value = true
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
const assignImage = (item: PlatformItem, payload: { id: number; url: string }) => {
|
||
item.imageFileId = payload.id
|
||
item.imageUrl = payload.url
|
||
}
|
||
|
||
const clearImage = (item: PlatformItem) => {
|
||
item.imageFileId = null
|
||
item.imageUrl = ''
|
||
}
|
||
|
||
const addItem = () => {
|
||
items.value.push({
|
||
name: '',
|
||
url: '',
|
||
imageFileId: null,
|
||
imageUrl: '',
|
||
sortOrder: items.value.length + 1
|
||
})
|
||
}
|
||
|
||
const save = async () => {
|
||
if (items.value.some((item) => !item.name.trim() || !item.url.trim() || !item.imageFileId)) {
|
||
ElMessage.warning('每条在线教育都需要名称、网址和图片,或删除未完成的条目')
|
||
return
|
||
}
|
||
saving.value = true
|
||
try {
|
||
await saveEduPlatforms({
|
||
items: items.value.map((item) => ({
|
||
name: item.name.trim(),
|
||
url: item.url.trim(),
|
||
imageFileId: item.imageFileId,
|
||
sortOrder: item.sortOrder
|
||
}))
|
||
})
|
||
ElMessage.success('已保存')
|
||
await load()
|
||
} finally {
|
||
saving.value = false
|
||
}
|
||
}
|
||
|
||
onMounted(load)
|
||
</script>
|
||
|
||
<style scoped>
|
||
.media-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.media-card,
|
||
.media-add {
|
||
padding: 14px;
|
||
border: 1px solid var(--el-border-color);
|
||
border-radius: 10px;
|
||
background: var(--el-bg-color);
|
||
}
|
||
|
||
.media-add {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 168px;
|
||
border-style: dashed;
|
||
color: var(--el-color-primary);
|
||
cursor: pointer;
|
||
background: var(--el-fill-color-lighter);
|
||
}
|
||
</style>
|