增加租户官网模块
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// ==================== 官网模板管理(平台端) ====================
|
||||
|
||||
// 模板列表
|
||||
export function getTemplateList(params) {
|
||||
return request({
|
||||
url: '/platform/template/index',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 扫描 themes/ 目录,自动登记新模板
|
||||
export function scanTemplates() {
|
||||
return request({
|
||||
url: '/platform/template/scan',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
// 上传模板 zip
|
||||
export function uploadTemplate(formData) {
|
||||
return request({
|
||||
url: '/platform/template/upload',
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
}
|
||||
|
||||
// 模板目录下可编辑文件列表
|
||||
export function getTemplateFiles(code) {
|
||||
return request({
|
||||
url: `/platform/template/files/${code}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 读取模板文件内容
|
||||
export function getTemplateFile(code, path) {
|
||||
return request({
|
||||
url: '/platform/template/file',
|
||||
method: 'get',
|
||||
params: { code, path }
|
||||
})
|
||||
}
|
||||
|
||||
// 保存模板文件
|
||||
export function saveTemplateFile(data) {
|
||||
return request({
|
||||
url: '/platform/template/file/save',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 启用/停用模板
|
||||
export function setTemplateStatus(id, status) {
|
||||
return request({
|
||||
url: '/platform/template/status',
|
||||
method: 'post',
|
||||
data: { id, status }
|
||||
})
|
||||
}
|
||||
|
||||
// 删除模板登记(可选删除磁盘目录)
|
||||
export function deleteTemplate(id, removeFiles = false) {
|
||||
return request({
|
||||
url: `/platform/template/delete/${id}`,
|
||||
method: 'post',
|
||||
data: { remove_files: removeFiles }
|
||||
})
|
||||
}
|
||||
|
||||
// 标签调用说明数据
|
||||
export function getTagDocs() {
|
||||
return request({
|
||||
url: '/platform/template/tags',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -21,6 +21,18 @@ const staticMainChildren = [
|
||||
component: () => import("@/views/system/email/index.vue"),
|
||||
meta: { requiresAuth: true, title: "邮箱管理" }
|
||||
},
|
||||
{
|
||||
path: "/template/index",
|
||||
name: "PlatformTemplateManage",
|
||||
component: () => import("@/views/template/index.vue"),
|
||||
meta: { requiresAuth: true, title: "官网模板管理" }
|
||||
},
|
||||
{
|
||||
path: "/template/tags",
|
||||
name: "PlatformTemplateTags",
|
||||
component: () => import("@/views/template/tags.vue"),
|
||||
meta: { requiresAuth: true, title: "标签调用说明" }
|
||||
},
|
||||
// 兼容拼写错误的路径重定向
|
||||
{
|
||||
path: "/apps/erp/dashborad",
|
||||
|
||||
@@ -1,11 +1,414 @@
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<div class="template-manage">
|
||||
<div class="header-bar">
|
||||
<h2>官网模板管理</h2>
|
||||
<div class="header-actions">
|
||||
<el-button type="primary" @click="uploadVisible = true">
|
||||
<el-icon><Upload /></el-icon>上传模板
|
||||
</el-button>
|
||||
<el-button @click="handleScan" :loading="scanning">
|
||||
<el-icon><Refresh /></el-icon>扫描目录
|
||||
</el-button>
|
||||
<el-button @click="fetchList" :loading="loading">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-alert type="info" :closable="false" style="margin-bottom: 12px">
|
||||
模板文件存放于服务器 themes/{编码}/ 目录;上传 zip 或扫描目录后自动登记。
|
||||
租户端(backend)只能选用已启用的模板,模板编辑仅平台端可操作。
|
||||
</el-alert>
|
||||
|
||||
<el-table :data="list" v-loading="loading" border style="width: 100%">
|
||||
<el-table-column label="预览" width="140" align="center">
|
||||
<template #default="scope">
|
||||
<el-image
|
||||
v-if="scope.row.preview"
|
||||
:src="getPreviewUrl(scope.row.preview)"
|
||||
fit="cover"
|
||||
style="width: 110px; height: 66px; border-radius: 4px"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
<span v-else style="color: #ccc">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="名称" min-width="140" />
|
||||
<el-table-column prop="code" label="编码" width="130" />
|
||||
<el-table-column prop="description" label="描述" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="目录" width="80" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.dir_exists" type="success" size="small">存在</el-tag>
|
||||
<el-tag v-else type="danger" size="small">缺失</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="90" align="center">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
:model-value="scope.row.status === 1"
|
||||
@change="handleStatusChange(scope.row, $event)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="登记时间" width="170" />
|
||||
<el-table-column label="操作" width="180" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" text type="primary" @click="openEditor(scope.row)">
|
||||
编辑文件
|
||||
</el-button>
|
||||
<el-button size="small" text type="danger" @click="handleDelete(scope.row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 上传模板 -->
|
||||
<el-dialog v-model="uploadVisible" title="上传模板 zip" width="460px">
|
||||
<el-form label-width="90px">
|
||||
<el-form-item label="模板编码">
|
||||
<el-input v-model="uploadCode" placeholder="留空则取 zip 文件名(仅字母数字下划线中划线)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="zip 文件">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:auto-upload="false"
|
||||
:limit="1"
|
||||
accept=".zip"
|
||||
:on-change="handleFileChange"
|
||||
:on-exceed="() => ElMessage.warning('只能上传一个 zip,请先移除')"
|
||||
>
|
||||
<el-button type="primary">选择 zip</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="uploadVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="uploading" @click="handleUpload">上传并登记</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 在线编辑 -->
|
||||
<el-dialog v-model="editorVisible" :title="`模板文件编辑 - ${editorCode}`" width="80%" top="5vh">
|
||||
<div class="editor-layout">
|
||||
<div class="editor-files">
|
||||
<div
|
||||
v-for="f in editorFiles"
|
||||
:key="f.path"
|
||||
class="file-item"
|
||||
:class="{ active: f.path === currentFile }"
|
||||
@click="openFile(f.path)"
|
||||
>
|
||||
{{ f.path }}
|
||||
</div>
|
||||
<el-empty v-if="editorFiles.length === 0" description="无可编辑文件" :image-size="60" />
|
||||
</div>
|
||||
<div class="editor-main">
|
||||
<div class="editor-toolbar">
|
||||
<span class="editor-path">{{ currentFile || '请选择左侧文件' }}</span>
|
||||
<el-button type="primary" size="small" :loading="saving" :disabled="!currentFile" @click="handleSave">
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="fileContent"
|
||||
class="editor-textarea"
|
||||
spellcheck="false"
|
||||
placeholder="选择左侧 html/css/js 文件进行编辑"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Upload, Refresh } from '@element-plus/icons-vue'
|
||||
import {
|
||||
getTemplateList,
|
||||
scanTemplates,
|
||||
uploadTemplate,
|
||||
getTemplateFiles,
|
||||
getTemplateFile,
|
||||
saveTemplateFile,
|
||||
setTemplateStatus,
|
||||
deleteTemplate
|
||||
} from '@/api/templateManage'
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || ''
|
||||
|
||||
const list = ref([])
|
||||
const loading = ref(false)
|
||||
const scanning = ref(false)
|
||||
|
||||
// 上传相关
|
||||
const uploadVisible = ref(false)
|
||||
const uploading = ref(false)
|
||||
const uploadCode = ref('')
|
||||
const uploadRef = ref(null)
|
||||
const uploadFile = ref(null)
|
||||
|
||||
// 编辑器相关
|
||||
const editorVisible = ref(false)
|
||||
const editorCode = ref('')
|
||||
const editorFiles = ref([])
|
||||
const currentFile = ref('')
|
||||
const fileContent = ref('')
|
||||
const saving = ref(false)
|
||||
|
||||
const getPreviewUrl = (path) => {
|
||||
if (!path) return ''
|
||||
if (path.startsWith('http')) return path
|
||||
return API_BASE_URL + path
|
||||
}
|
||||
|
||||
const handleImageError = (event) => {
|
||||
event.target.style.display = 'none'
|
||||
}
|
||||
|
||||
const fetchList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getTemplateList()
|
||||
if (res.code === 200) {
|
||||
list.value = res.data.list || []
|
||||
} else {
|
||||
ElMessage.error(res.msg || '获取模板列表失败')
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('获取模板列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleScan = async () => {
|
||||
scanning.value = true
|
||||
try {
|
||||
const res = await scanTemplates()
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(`扫描完成,新登记 ${res.data.added} 个模板`)
|
||||
fetchList()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '扫描失败')
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('扫描失败')
|
||||
} finally {
|
||||
scanning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleFileChange = (file) => {
|
||||
uploadFile.value = file.raw
|
||||
}
|
||||
|
||||
const handleUpload = async () => {
|
||||
if (!uploadFile.value) {
|
||||
ElMessage.warning('请先选择 zip 文件')
|
||||
return
|
||||
}
|
||||
uploading.value = true
|
||||
try {
|
||||
const fd = new FormData()
|
||||
fd.append('file', uploadFile.value)
|
||||
if (uploadCode.value.trim()) {
|
||||
fd.append('code', uploadCode.value.trim())
|
||||
}
|
||||
const res = await uploadTemplate(fd)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('上传成功,模板编码:' + res.data.code)
|
||||
uploadVisible.value = false
|
||||
uploadCode.value = ''
|
||||
uploadFile.value = null
|
||||
uploadRef.value?.clearFiles()
|
||||
fetchList()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '上传失败')
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('上传失败')
|
||||
} finally {
|
||||
uploading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleStatusChange = async (row, val) => {
|
||||
try {
|
||||
const res = await setTemplateStatus(row.id, val ? 1 : 0)
|
||||
if (res.code === 200) {
|
||||
row.status = val ? 1 : 0
|
||||
ElMessage.success(val ? '已启用' : '已停用(已选用租户将回退默认模板)')
|
||||
} else {
|
||||
ElMessage.error(res.msg || '操作失败')
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (row) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定删除模板「${row.name}」的登记吗?`,
|
||||
'删除确认',
|
||||
{
|
||||
confirmButtonText: '删除登记',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
distinguishCancelAndClose: true,
|
||||
showClose: true
|
||||
}
|
||||
)
|
||||
const res = await deleteTemplate(row.id, false)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('删除成功')
|
||||
fetchList()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '删除失败')
|
||||
}
|
||||
} catch (e) {
|
||||
// 用户取消或接口异常均不提示
|
||||
}
|
||||
}
|
||||
|
||||
const openEditor = async (row) => {
|
||||
if (!row.dir_exists) {
|
||||
ElMessage.warning('模板目录不存在,无法编辑')
|
||||
return
|
||||
}
|
||||
editorCode.value = row.code
|
||||
currentFile.value = ''
|
||||
fileContent.value = ''
|
||||
editorVisible.value = true
|
||||
try {
|
||||
const res = await getTemplateFiles(row.code)
|
||||
if (res.code === 200) {
|
||||
editorFiles.value = res.data.files || []
|
||||
} else {
|
||||
ElMessage.error(res.msg || '获取文件列表失败')
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('获取文件列表失败')
|
||||
}
|
||||
}
|
||||
|
||||
const openFile = async (path) => {
|
||||
currentFile.value = path
|
||||
fileContent.value = ''
|
||||
try {
|
||||
const res = await getTemplateFile(editorCode.value, path)
|
||||
if (res.code === 200) {
|
||||
fileContent.value = res.data.content || ''
|
||||
} else {
|
||||
ElMessage.error(res.msg || '读取失败')
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('读取失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
saving.value = true
|
||||
try {
|
||||
const res = await saveTemplateFile({
|
||||
code: editorCode.value,
|
||||
path: currentFile.value,
|
||||
content: fileContent.value
|
||||
})
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('保存成功')
|
||||
} else {
|
||||
ElMessage.error(res.msg || '保存失败')
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.template-manage {
|
||||
padding: 16px;
|
||||
}
|
||||
.header-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.header-bar h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.editor-layout {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
height: 62vh;
|
||||
}
|
||||
.editor-files {
|
||||
width: 220px;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 4px;
|
||||
overflow-y: auto;
|
||||
padding: 6px;
|
||||
}
|
||||
.file-item {
|
||||
padding: 6px 10px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
word-break: break-all;
|
||||
}
|
||||
.file-item:hover {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
.file-item.active {
|
||||
background: #ecf5ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
.editor-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
.editor-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.editor-path {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
.editor-textarea {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
resize: none;
|
||||
outline: none;
|
||||
}
|
||||
.editor-textarea:focus {
|
||||
border-color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<div class="tag-docs">
|
||||
<div class="header-bar">
|
||||
<h2>标签调用说明</h2>
|
||||
<el-button @click="fetchDocs" :loading="loading">
|
||||
<el-icon><Refresh /></el-icon>刷新
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-alert type="info" :closable="false" style="margin-bottom: 16px">
|
||||
模板中使用 <b>{yz:标签}</b> 调用数据,循环标签内用 <b>[field:字段/]</b> 输出字段。
|
||||
本页由引擎注册表自动生成,新增标签后无需改文档。
|
||||
</el-alert>
|
||||
|
||||
<div v-loading="loading">
|
||||
<template v-for="cat in categories" :key="cat.key">
|
||||
<div v-if="grouped[cat.key]?.length" class="cat-block">
|
||||
<h3 class="cat-title">{{ cat.label }}</h3>
|
||||
<el-card v-for="tag in grouped[cat.key]" :key="tag.name" class="tag-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="tag-head">
|
||||
<span class="tag-name">{{ tag.name }}</span>
|
||||
<code class="tag-syntax">{{ tag.syntax }}</code>
|
||||
<el-button size="small" text type="primary" @click="copy(tag.syntax)">复制语法</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<p class="tag-desc">{{ tag.desc }}</p>
|
||||
|
||||
<div v-if="tag.params?.length" class="tag-section">
|
||||
<div class="sec-label">参数</div>
|
||||
<ul>
|
||||
<li v-for="p in tag.params" :key="p"><code>{{ paramName(p) }}</code>{{ paramDesc(p) }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="tag.fields?.length" class="tag-section">
|
||||
<div class="sec-label">可用字段</div>
|
||||
<div class="field-tags">
|
||||
<el-tag
|
||||
v-for="f in tag.fields"
|
||||
:key="f"
|
||||
size="small"
|
||||
effect="plain"
|
||||
class="field-tag"
|
||||
@click="copy('[field:' + fieldName(f) + '/]')"
|
||||
>
|
||||
[field:{{ fieldName(f) }}/]
|
||||
</el-tag>
|
||||
</div>
|
||||
<ul class="field-desc">
|
||||
<li v-for="f in tag.fields" :key="f"><code>[field:{{ fieldName(f) }}/]</code>{{ fieldDesc(f) }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="tag.example" class="tag-section">
|
||||
<div class="sec-label">
|
||||
示例
|
||||
<el-button size="small" text type="primary" @click="copy(tag.example)">复制</el-button>
|
||||
</div>
|
||||
<pre class="tag-example">{{ tag.example }}</pre>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Refresh } from '@element-plus/icons-vue'
|
||||
import { getTagDocs } from '@/api/templateManage'
|
||||
|
||||
const docs = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
const categories = [
|
||||
{ key: 'global', label: '全局标签' },
|
||||
{ key: 'loop', label: '循环标签' },
|
||||
{ key: 'single', label: '单条内容标签' },
|
||||
{ key: 'other', label: '其他标签' }
|
||||
]
|
||||
|
||||
const grouped = computed(() => {
|
||||
const out = {}
|
||||
for (const d of docs.value) {
|
||||
if (!out[d.category]) out[d.category] = []
|
||||
out[d.category].push(d)
|
||||
}
|
||||
return out
|
||||
})
|
||||
|
||||
// "row 输出条数,默认 10" -> 名称与说明拆分
|
||||
const paramName = (p) => (p || '').split(' ')[0]
|
||||
const paramDesc = (p) => {
|
||||
const i = (p || '').indexOf(' ')
|
||||
return i > 0 ? ' — ' + p.slice(i + 1) : ''
|
||||
}
|
||||
const fieldName = (f) => (f || '').split(' ')[0]
|
||||
const fieldDesc = (f) => {
|
||||
const i = (f || '').indexOf(' ')
|
||||
return i > 0 ? ' — ' + f.slice(i + 1) : ''
|
||||
}
|
||||
|
||||
const copy = async (text) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
ElMessage.success('已复制')
|
||||
} catch (e) {
|
||||
ElMessage.error('复制失败,请手动选择复制')
|
||||
}
|
||||
}
|
||||
|
||||
const fetchDocs = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getTagDocs()
|
||||
if (res.code === 200) {
|
||||
docs.value = res.data || []
|
||||
} else {
|
||||
ElMessage.error(res.msg || '获取标签说明失败')
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('获取标签说明失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchDocs()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tag-docs {
|
||||
padding: 16px;
|
||||
}
|
||||
.header-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.header-bar h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.cat-block {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.cat-title {
|
||||
font-size: 16px;
|
||||
border-left: 3px solid #1890ff;
|
||||
padding-left: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.tag-card {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.tag-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.tag-name {
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
}
|
||||
.tag-syntax {
|
||||
background: #f5f7fa;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
color: #d63384;
|
||||
}
|
||||
.tag-desc {
|
||||
margin: 0 0 12px;
|
||||
color: #555;
|
||||
font-size: 14px;
|
||||
}
|
||||
.tag-section {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.sec-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.tag-section ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
.tag-section li {
|
||||
margin: 3px 0;
|
||||
}
|
||||
.field-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.field-tag {
|
||||
cursor: pointer;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
}
|
||||
.field-desc code,
|
||||
.tag-section code {
|
||||
background: #f5f7fa;
|
||||
padding: 1px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
color: #1890ff;
|
||||
}
|
||||
.tag-example {
|
||||
background: #282c34;
|
||||
color: #abb2bf;
|
||||
padding: 12px 14px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user