mirror of
https://github.com/Cccc-owo/CheckInApp.git
synced 2026-06-17 14:06:28 +00:00
feat(new-frontend): add theme and templates
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { Eye, Plus, Save, Trash2 } from 'lucide-vue-next'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { templateApi, type Template, type TemplatePreview } from '@/api'
|
||||
import StateBlock from '@/components/StateBlock.vue'
|
||||
import TemplateConfigEditor from '@/components/templates/TemplateConfigEditor.vue'
|
||||
import {
|
||||
buildTemplatePreviewPayload,
|
||||
parseTemplateFieldConfig,
|
||||
validateFieldConfig,
|
||||
} from '@/components/templates/template-config'
|
||||
import {
|
||||
alertClass,
|
||||
buttonBase,
|
||||
@@ -10,7 +16,6 @@ import {
|
||||
cardClass,
|
||||
inputClass,
|
||||
sectionHeaderClass,
|
||||
textareaClass,
|
||||
toneClass,
|
||||
} from '@/components/ui'
|
||||
import { extractErrorMessage, formatDateTime, stringifyJson } from '@/utils/format'
|
||||
@@ -21,6 +26,7 @@ const message = ref('')
|
||||
const templates = ref<Template[]>([])
|
||||
const editingId = ref<number | 'new' | null>(null)
|
||||
const preview = ref<TemplatePreview | null>(null)
|
||||
const editorValid = ref(true)
|
||||
const form = reactive({
|
||||
name: '',
|
||||
description: '',
|
||||
@@ -29,6 +35,12 @@ const form = reactive({
|
||||
is_active: true,
|
||||
})
|
||||
|
||||
const localPreviewPayload = computed(() => {
|
||||
const result = parseTemplateFieldConfig(form.field_config)
|
||||
if (!result.ok) return null
|
||||
return buildTemplatePreviewPayload(result.config)
|
||||
})
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
@@ -68,7 +80,10 @@ async function save() {
|
||||
error.value = ''
|
||||
message.value = ''
|
||||
try {
|
||||
JSON.parse(form.field_config)
|
||||
const parsed = parseTemplateFieldConfig(form.field_config)
|
||||
if (!parsed.ok) throw new Error(parsed.message || '字段配置无效')
|
||||
const validation = validateFieldConfig(parsed.config)
|
||||
if (!validation.ok) throw new Error(validation.message || '字段配置无效')
|
||||
const payload = {
|
||||
name: form.name,
|
||||
description: form.description || null,
|
||||
@@ -185,27 +200,28 @@ onMounted(load)
|
||||
:class="[cardClass, 'grid gap-4 overflow-hidden']"
|
||||
@submit.prevent="save"
|
||||
>
|
||||
<div class="border-b border-zinc-200 bg-zinc-50/70 px-5 py-4">
|
||||
<div
|
||||
class="border-b border-zinc-200 bg-zinc-50/70 px-5 py-4 dark:border-zinc-800 dark:bg-zinc-950/50"
|
||||
>
|
||||
<h2 class="font-semibold">{{ editingId === 'new' ? '新建模板' : '编辑模板' }}</h2>
|
||||
<p class="mt-1 text-sm text-zinc-500">字段配置必须是合法 JSON。</p>
|
||||
<p class="mt-1 text-sm text-zinc-500 dark:text-zinc-400">
|
||||
使用结构化字段编辑器维护配置,必要时切换 JSON 修复。
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid gap-4 p-5">
|
||||
<label class="grid gap-2">
|
||||
<span class="text-xs font-semibold text-zinc-500">名称</span>
|
||||
<span class="text-xs font-semibold text-zinc-500 dark:text-zinc-400">名称</span>
|
||||
<input v-model="form.name" :class="inputClass" required />
|
||||
</label>
|
||||
<label class="grid gap-2">
|
||||
<span class="text-xs font-semibold text-zinc-500">描述</span>
|
||||
<span class="text-xs font-semibold text-zinc-500 dark:text-zinc-400">描述</span>
|
||||
<input v-model="form.description" :class="inputClass" />
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm">
|
||||
<input v-model="form.is_active" type="checkbox" />
|
||||
启用模板
|
||||
</label>
|
||||
<label class="grid gap-2">
|
||||
<span class="text-xs font-semibold text-zinc-500">字段配置 JSON</span>
|
||||
<textarea v-model="form.field_config" :class="textareaClass" class="min-h-64" />
|
||||
</label>
|
||||
<TemplateConfigEditor v-model="form.field_config" @valid="editorValid = $event" />
|
||||
<div v-if="error" :class="alertClass.danger">
|
||||
{{ error }}
|
||||
</div>
|
||||
@@ -213,7 +229,11 @@ onMounted(load)
|
||||
{{ message }}
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button :class="[buttonBase, buttonTone.primary]" type="submit">
|
||||
<button
|
||||
:class="[buttonBase, buttonTone.primary]"
|
||||
:disabled="!editorValid"
|
||||
type="submit"
|
||||
>
|
||||
<Save class="size-4" />
|
||||
保存
|
||||
</button>
|
||||
@@ -235,6 +255,14 @@ onMounted(load)
|
||||
>{{ stringifyJson(preview.preview_payload) }}</pre
|
||||
>
|
||||
</section>
|
||||
|
||||
<section v-else-if="editingId && localPreviewPayload" :class="[cardClass, 'p-5']">
|
||||
<h2 class="font-semibold">当前配置预览</h2>
|
||||
<pre
|
||||
class="mt-3 max-h-96 overflow-auto rounded-md bg-zinc-950 p-3 text-xs leading-5 text-zinc-100"
|
||||
>{{ stringifyJson(localPreviewPayload) }}</pre
|
||||
>
|
||||
</section>
|
||||
</aside>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user