Bladeren bron

style: 调整样式

jiaxing.liao 1 week geleden
bovenliggende
commit
7a08b45384

+ 2 - 2
apps/web/src/i18n/locales/zh-cn.ts

@@ -258,7 +258,7 @@ export default {
 			]
 		},
 		createWorkflow: {
-			title: '新建工作流',
+			title: '新建编排',
 			editTitle: '编辑工作流',
 			fields: {
 				name: '名称',
@@ -311,7 +311,7 @@ export default {
 			},
 			quickActions: {
 				title: '快速操作',
-				newWorkflow: { title: '新建工作流', desc: '从零开始创建' },
+				newWorkflow: { title: '新建编排', desc: '从零开始创建' },
 				useTemplate: { title: '使用模板', desc: '快速上手' },
 				viewLogs: { title: '查看日志', desc: '执行记录' },
 				viewDocs: { title: '查看文档', desc: '学习使用' }

+ 4 - 1
apps/web/src/layouts/MainLayout.vue

@@ -6,7 +6,10 @@
 			/>
 		</el-aside>
 		<el-container>
-			<el-main style="padding: 0; overflow: auto" :style="mainStyle">
+			<el-main
+				style="padding: 0; overflow: auto; background: var(--bg-container)"
+				:style="mainStyle"
+			>
 				<router-view />
 			</el-main>
 		</el-container>

+ 28 - 22
apps/web/src/nodes/_base/OutputVariables.vue

@@ -12,28 +12,34 @@
 
 		<div class="space-y-2">
 			<div v-for="(output, index) in outputs" :key="index" class="flex items-center gap-2 group">
-				<el-input
-					v-model="output.name"
-					type="text"
-					:placeholder="t('common.nodeBase.outputVariables.namePlaceholder')"
-					class="w-1/3 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
-					@input="handleOutputChange"
-				/>
-				<el-select
-					v-if="setType"
-					v-model="output.type"
-					:options="variableTypeOptions"
-					class="w-1/3 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white min-w-[100px]"
-					@change="handleOutputChange"
-				>
-				</el-select>
-				<VarSelect
-					v-if="setValue"
-					v-model="output.value"
-					class="flex-1"
-					:placeholder="t('common.nodeBase.outputVariables.setValuePlaceholder')"
-					@change="(val) => val && (output.type = val.type as NodeVariableType)"
-				/>
+				<div class="flex-1">
+					<el-input
+						v-model="output.name"
+						type="text"
+						:placeholder="t('common.nodeBase.outputVariables.namePlaceholder')"
+						class="w-1/3 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
+						@input="handleOutputChange"
+					/>
+				</div>
+				<div v-if="setType" class="flex-1">
+					<el-select
+						v-model="output.type"
+						:options="variableTypeOptions"
+						class="w-1/3 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white min-w-[100px]"
+						@change="handleOutputChange"
+					>
+					</el-select>
+				</div>
+				<div class="flex-1">
+					<VarSelect
+						v-if="setValue"
+						v-model="output.value"
+						class="flex-1"
+						:placeholder="t('common.nodeBase.outputVariables.setValuePlaceholder')"
+						@change="(val) => val && (output.type = val.type as NodeVariableType)"
+					/>
+				</div>
+
 				<IconButton link icon="lucide:trash-2" class="text-red-500" @click="removeOutput(index)" />
 			</div>
 		</div>

+ 203 - 207
apps/web/src/views/mcp/index.vue

@@ -7,7 +7,7 @@
 			</div>
 		</div>
 
-		<div class="panel">
+		<el-card class="list-card">
 			<div class="toolbar">
 				<div class="toolbar-left">
 					<el-input
@@ -128,221 +128,215 @@
 					@size-change="handleSizeChange"
 				/>
 			</div>
-
-			<el-drawer
-				v-model="drawerVisible"
-				:title="currentId ? '编辑 MCP' : '新建 MCP'"
-				direction="rtl"
-				size="760px"
-			>
-				<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
-					<el-form-item label="名称" prop="name">
-						<el-input v-model="form.name" />
+		</el-card>
+
+		<el-drawer
+			v-model="drawerVisible"
+			:title="currentId ? '编辑 MCP' : '新建 MCP'"
+			direction="rtl"
+			size="760px"
+		>
+			<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
+				<el-form-item label="名称" prop="name">
+					<el-input v-model="form.name" />
+				</el-form-item>
+				<el-form-item label="描述" prop="description">
+					<el-input v-model="form.description" type="textarea" :rows="2" />
+				</el-form-item>
+				<div class="grid-2">
+					<el-form-item label="传输类型" prop="transport_type">
+						<el-select v-model="form.transport_type" style="width: 100%">
+							<el-option label="SSE(Server-Sent Events)" value="sse" />
+							<el-option label="HTTP Streamable" value="http-streamable" />
+						</el-select>
 					</el-form-item>
-					<el-form-item label="描述" prop="description">
-						<el-input v-model="form.description" type="textarea" :rows="2" />
+					<el-form-item label="启用" prop="enabled">
+						<el-switch v-model="form.enabled" />
 					</el-form-item>
-					<div class="grid-2">
-						<el-form-item label="传输类型" prop="transport_type">
-							<el-select v-model="form.transport_type" style="width: 100%">
-								<el-option label="SSE(Server-Sent Events)" value="sse" />
-								<el-option label="HTTP Streamable" value="http-streamable" />
-							</el-select>
-						</el-form-item>
-						<el-form-item label="启用" prop="enabled">
-							<el-switch v-model="form.enabled" />
-						</el-form-item>
-					</div>
-					<el-form-item label="地址" prop="url">
-						<el-input v-model="form.url" />
+				</div>
+				<el-form-item label="地址" prop="url">
+					<el-input v-model="form.url" />
+				</el-form-item>
+				<el-form-item label="超时(秒)" prop="advanced_config.timeout">
+					<el-input-number v-model="form.advanced_config.timeout" :min="0" style="width: 100%" />
+				</el-form-item>
+				<div class="grid-2">
+					<el-form-item label="重试次数" prop="advanced_config.retry_count">
+						<el-input-number
+							v-model="form.advanced_config.retry_count"
+							:min="0"
+							style="width: 100%"
+						/>
 					</el-form-item>
-					<el-form-item label="超时(秒)" prop="advanced_config.timeout">
-						<el-input-number v-model="form.advanced_config.timeout" :min="0" style="width: 100%" />
+					<el-form-item label="重试间隔(秒)" prop="advanced_config.retry_delay">
+						<el-input-number
+							v-model="form.advanced_config.retry_delay"
+							:min="0"
+							style="width: 100%"
+						/>
 					</el-form-item>
-					<div class="grid-2">
-						<el-form-item label="重试次数" prop="advanced_config.retry_count">
-							<el-input-number
-								v-model="form.advanced_config.retry_count"
-								:min="0"
-								style="width: 100%"
-							/>
-						</el-form-item>
-						<el-form-item label="重试间隔(秒)" prop="advanced_config.retry_delay">
-							<el-input-number
-								v-model="form.advanced_config.retry_delay"
-								:min="0"
-								style="width: 100%"
-							/>
-						</el-form-item>
-					</div>
-					<el-form-item label="Headers">
-						<div class="kv-config">
-							<div class="kv-config__top">
-								<span>请求头</span>
-								<el-button type="primary" link @click="addHeaderRow">
-									<el-icon> <Plus /> </el-icon>添加
-								</el-button>
-							</div>
-							<div class="kv-config__rows">
-								<div
-									v-for="(item, index) in headerList"
-									:key="`header-${index}`"
-									class="kv-config__row"
-								>
-									<el-input v-model="item.key" placeholder="Header 名称" />
-									<el-input v-model="item.value" placeholder="Header 值" />
-									<el-button
-										link
-										type="danger"
-										:disabled="headerList.length === 1"
-										@click="removeHeaderRow(index)"
-									>
-										删除
-									</el-button>
-								</div>
-							</div>
+				</div>
+				<el-form-item label="Headers">
+					<div class="kv-config">
+						<div class="kv-config__top">
+							<span>请求头</span>
+							<el-button type="primary" link @click="addHeaderRow">
+								<el-icon> <Plus /> </el-icon>添加
+							</el-button>
 						</div>
-					</el-form-item>
-					<el-form-item label="Auth Config">
-						<div class="kv-config">
-							<div class="kv-config__top">
-								<span>鉴权配置</span>
-								<el-button type="primary" link @click="addAuthRow">
-									<el-icon> <Plus /> </el-icon>添加
-								</el-button>
-							</div>
-							<div class="kv-config__rows">
-								<div
-									v-for="(item, index) in authList"
-									:key="`auth-${index}`"
-									class="kv-config__row"
+						<div class="kv-config__rows">
+							<div
+								v-for="(item, index) in headerList"
+								:key="`header-${index}`"
+								class="kv-config__row"
+							>
+								<el-input v-model="item.key" placeholder="Header 名称" />
+								<el-input v-model="item.value" placeholder="Header 值" />
+								<el-button
+									link
+									type="danger"
+									:disabled="headerList.length === 1"
+									@click="removeHeaderRow(index)"
 								>
-									<el-input v-model="item.key" placeholder="配置项名称" />
-									<el-input v-model="item.value" placeholder="配置项值" />
-									<el-button
-										link
-										type="danger"
-										:disabled="authList.length === 1"
-										@click="removeAuthRow(index)"
-									>
-										删除
-									</el-button>
-								</div>
+									删除
+								</el-button>
 							</div>
 						</div>
-					</el-form-item>
-					<el-form-item label="Env Vars">
-						<div class="kv-config">
-							<div class="kv-config__top">
-								<span>环境变量</span>
-								<el-button type="primary" link @click="addEnvRow">
-									<el-icon> <Plus /> </el-icon>添加
+					</div>
+				</el-form-item>
+				<el-form-item label="Auth Config">
+					<div class="kv-config">
+						<div class="kv-config__top">
+							<span>鉴权配置</span>
+							<el-button type="primary" link @click="addAuthRow">
+								<el-icon> <Plus /> </el-icon>添加
+							</el-button>
+						</div>
+						<div class="kv-config__rows">
+							<div v-for="(item, index) in authList" :key="`auth-${index}`" class="kv-config__row">
+								<el-input v-model="item.key" placeholder="配置项名称" />
+								<el-input v-model="item.value" placeholder="配置项值" />
+								<el-button
+									link
+									type="danger"
+									:disabled="authList.length === 1"
+									@click="removeAuthRow(index)"
+								>
+									删除
 								</el-button>
 							</div>
-							<div class="kv-config__rows">
-								<div v-for="(item, index) in envList" :key="`env-${index}`" class="kv-config__row">
-									<el-input v-model="item.key" placeholder="变量名" />
-									<el-input v-model="item.value" placeholder="变量值" />
-									<el-button
-										link
-										type="danger"
-										:disabled="envList.length === 1"
-										@click="removeEnvRow(index)"
-									>
-										删除
-									</el-button>
-								</div>
-							</div>
 						</div>
-					</el-form-item>
-					<el-form-item>
-						<div class="check-box">
-							<el-button :loading="checkLoading" :disabled="!currentId" @click="checkItemByForm">
-								测试连接
+					</div>
+				</el-form-item>
+				<el-form-item label="Env Vars">
+					<div class="kv-config">
+						<div class="kv-config__top">
+							<span>环境变量</span>
+							<el-button type="primary" link @click="addEnvRow">
+								<el-icon> <Plus /> </el-icon>添加
 							</el-button>
-							<el-alert
-								v-if="checkMessage"
-								:title="checkMessage"
-								:type="checkSuccess ? 'success' : 'error'"
-								:closable="false"
-								show-icon
-							/>
 						</div>
-					</el-form-item>
-				</el-form>
-				<template #footer>
-					<div class="drawer-footer">
-						<el-button @click="drawerVisible = false">取消</el-button>
-						<el-button type="primary" :loading="submitLoading" @click="handleSubmit"
-							>保存</el-button
-						>
+						<div class="kv-config__rows">
+							<div v-for="(item, index) in envList" :key="`env-${index}`" class="kv-config__row">
+								<el-input v-model="item.key" placeholder="变量名" />
+								<el-input v-model="item.value" placeholder="变量值" />
+								<el-button
+									link
+									type="danger"
+									:disabled="envList.length === 1"
+									@click="removeEnvRow(index)"
+								>
+									删除
+								</el-button>
+							</div>
+						</div>
 					</div>
-				</template>
-			</el-drawer>
-
-			<el-dialog v-model="detailVisible" title="MCP 详情" width="720px">
-				<el-descriptions v-if="detailItem" :column="1" border>
-					<el-descriptions-item label="名称">{{ detailItem.name }}</el-descriptions-item>
-					<el-descriptions-item label="传输类型">{{
-						detailItem.transport_type
-					}}</el-descriptions-item>
-					<el-descriptions-item label="地址">{{ detailItem.url || '-' }}</el-descriptions-item>
-					<el-descriptions-item label="描述">{{
-						detailItem.description || '-'
-					}}</el-descriptions-item>
-				</el-descriptions>
-			</el-dialog>
-
-			<el-dialog v-model="resourcesVisible" title="MCP 资源" width="720px">
-				<el-empty v-if="!resourceList.length" description="暂无资源" />
-				<el-space v-else wrap>
-					<el-tag v-for="item in resourceList" :key="item">{{ item }}</el-tag>
-				</el-space>
-			</el-dialog>
-
-			<el-dialog v-model="toolsVisible" title="MCP 工具" width="720px" class="tool-modal">
-				<div class="tool-dialog">
-					<el-input
-						v-model="toolKeyword"
-						clearable
-						placeholder="搜索工具名称或描述"
-						class="tool-search-input"
-					>
-						<template #prefix>
-							<el-icon>
-								<Search />
-							</el-icon>
-						</template>
-					</el-input>
-					<el-empty v-if="!filteredToolDetailList.length" description="暂无工具" />
-					<el-scrollbar v-else max-height="420px">
-						<el-collapse>
-							<el-collapse-item v-for="item in filteredToolDetailList" :key="item.name">
-								<template #title>
-									<div>
-										<div class="tool-desc text-16px text-primary mb-4px">{{ item.name }}</div>
-										<div class="tool-desc text-12px">{{ item.description }}</div>
-									</div>
-								</template>
-								<el-card>
-									<el-descriptions direction="vertical" :column="1">
-										<el-descriptions-item label="参数结构">
-											<CodeEditor
-												:model-value="JSON.stringify(item.inputSchema, null, 2)"
-												language="json"
-												:height="260"
-												:tools="false"
-												readonly
-											/>
-										</el-descriptions-item>
-									</el-descriptions>
-								</el-card>
-							</el-collapse-item>
-						</el-collapse>
-					</el-scrollbar>
+				</el-form-item>
+				<el-form-item>
+					<div class="check-box">
+						<el-button :loading="checkLoading" :disabled="!currentId" @click="checkItemByForm">
+							测试连接
+						</el-button>
+						<el-alert
+							v-if="checkMessage"
+							:title="checkMessage"
+							:type="checkSuccess ? 'success' : 'error'"
+							:closable="false"
+							show-icon
+						/>
+					</div>
+				</el-form-item>
+			</el-form>
+			<template #footer>
+				<div class="drawer-footer">
+					<el-button @click="drawerVisible = false">取消</el-button>
+					<el-button type="primary" :loading="submitLoading" @click="handleSubmit">保存</el-button>
 				</div>
-			</el-dialog>
-		</div>
+			</template>
+		</el-drawer>
+
+		<el-dialog v-model="detailVisible" title="MCP 详情" width="720px">
+			<el-descriptions v-if="detailItem" :column="1" border>
+				<el-descriptions-item label="名称">{{ detailItem.name }}</el-descriptions-item>
+				<el-descriptions-item label="传输类型">{{
+					detailItem.transport_type
+				}}</el-descriptions-item>
+				<el-descriptions-item label="地址">{{ detailItem.url || '-' }}</el-descriptions-item>
+				<el-descriptions-item label="描述">{{
+					detailItem.description || '-'
+				}}</el-descriptions-item>
+			</el-descriptions>
+		</el-dialog>
+
+		<el-dialog v-model="resourcesVisible" title="MCP 资源" width="720px">
+			<el-empty v-if="!resourceList.length" description="暂无资源" />
+			<el-space v-else wrap>
+				<el-tag v-for="item in resourceList" :key="item">{{ item }}</el-tag>
+			</el-space>
+		</el-dialog>
+
+		<el-dialog v-model="toolsVisible" title="MCP 工具" width="720px" class="tool-modal">
+			<div class="tool-dialog">
+				<el-input
+					v-model="toolKeyword"
+					clearable
+					placeholder="搜索工具名称或描述"
+					class="tool-search-input"
+				>
+					<template #prefix>
+						<el-icon>
+							<Search />
+						</el-icon>
+					</template>
+				</el-input>
+				<el-empty v-if="!filteredToolDetailList.length" description="暂无工具" />
+				<el-scrollbar v-else max-height="420px">
+					<el-collapse>
+						<el-collapse-item v-for="item in filteredToolDetailList" :key="item.name">
+							<template #title>
+								<div>
+									<div class="tool-desc text-16px text-primary mb-4px">{{ item.name }}</div>
+									<div class="tool-desc text-12px">{{ item.description }}</div>
+								</div>
+							</template>
+							<el-card>
+								<el-descriptions direction="vertical" :column="1">
+									<el-descriptions-item label="参数结构">
+										<CodeEditor
+											:model-value="JSON.stringify(item.inputSchema, null, 2)"
+											language="json"
+											:height="260"
+											:tools="false"
+											readonly
+										/>
+									</el-descriptions-item>
+								</el-descriptions>
+							</el-card>
+						</el-collapse-item>
+					</el-collapse>
+				</el-scrollbar>
+			</div>
+		</el-dialog>
 	</div>
 </template>
 
@@ -671,16 +665,18 @@ onMounted(() => {
 }
 
 .page-head {
-	margin-bottom: 18px;
+	margin-bottom: 20px;
 
 	h1 {
 		margin: 0;
 		font-size: 28px;
+		color: var(--text-primary);
 	}
 
 	p {
 		margin: 6px 0 0;
-		color: var(--text-tertiary);
+		font-size: 14px;
+		color: var(--text-secondary);
 	}
 }
 
@@ -688,10 +684,9 @@ onMounted(() => {
 	height: 80px;
 }
 
-.panel {
-	display: flex;
-	flex-direction: column;
-	gap: 16px;
+.list-card {
+	border-radius: 18px;
+	margin-bottom: 16px;
 }
 
 .section-title {
@@ -739,6 +734,7 @@ onMounted(() => {
 	align-items: center;
 	gap: 10px;
 	flex-wrap: wrap;
+	margin: 16px 0;
 }
 
 .pill {

+ 1 - 2
apps/web/src/views/model/index.vue

@@ -935,8 +935,7 @@ onMounted(() => {
 
 <style scoped lang="less">
 .management-page {
-	padding: 16px;
-	background: var(--bg-container);
+	padding: 24px;
 	min-height: 100%;
 	box-sizing: border-box;
 }

+ 70 - 74
apps/web/src/views/prompt/index.vue

@@ -7,7 +7,7 @@
 			</div>
 		</div>
 
-		<div class="panel">
+		<el-card class="list-card">
 			<div class="toolbar">
 				<div class="toolbar-left">
 					<el-input
@@ -103,78 +103,72 @@
 					@size-change="handleSizeChange"
 				/>
 			</div>
-
-			<el-drawer
-				v-model="drawerVisible"
-				:title="currentId ? '编辑提示词' : '新建提示词'"
-				direction="rtl"
-				size="760px"
-			>
-				<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
-					<el-form-item label="名称" prop="name">
-						<el-input v-model="form.name" />
+		</el-card>
+
+		<el-drawer
+			v-model="drawerVisible"
+			:title="currentId ? '编辑提示词' : '新建提示词'"
+			direction="rtl"
+			size="760px"
+		>
+			<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
+				<el-form-item label="名称" prop="name">
+					<el-input v-model="form.name" />
+				</el-form-item>
+				<el-form-item label="描述" prop="description">
+					<el-input v-model="form.description" type="textarea" :rows="2" />
+				</el-form-item>
+				<el-form-item label="内容" prop="content">
+					<el-input v-model="form.content" type="textarea" :rows="8" />
+				</el-form-item>
+				<el-form-item label="用户提示词" prop="user">
+					<el-input v-model="form.user" type="textarea" :rows="4" />
+				</el-form-item>
+				<div class="grid-2">
+					<el-form-item label="类型" prop="type">
+						<el-select v-model="form.type" style="width: 100%">
+							<el-option label="系统提示词" value="system-prompt" />
+							<el-option label="agent 系统提示词" value="agent-system-prompt" />
+							<el-option label="改写提示词" value="rewrite" />
+							<el-option label="回退提示词" value="fallback" />
+							<el-option label="上下文模板" value="context-template" />
+						</el-select>
 					</el-form-item>
-					<el-form-item label="描述" prop="description">
-						<el-input v-model="form.description" type="textarea" :rows="2" />
+					<el-form-item label="是否默认" prop="is_default">
+						<el-switch v-model="form.is_default" />
 					</el-form-item>
-					<el-form-item label="内容" prop="content">
-						<el-input v-model="form.content" type="textarea" :rows="8" />
+				</div>
+				<div class="grid-2">
+					<el-form-item label="包含知识库" prop="has_knowledge_base">
+						<el-switch v-model="form.has_knowledge_base" />
 					</el-form-item>
-					<el-form-item label="用户提示词" prop="user">
-						<el-input v-model="form.user" type="textarea" :rows="4" />
+					<el-form-item label="包含网络搜索" prop="has_web_search">
+						<el-switch v-model="form.has_web_search" />
 					</el-form-item>
-					<div class="grid-2">
-						<el-form-item label="类型" prop="type">
-							<el-select v-model="form.type" style="width: 100%">
-								<el-option label="系统提示词" value="system-prompt" />
-								<el-option label="agent 系统提示词" value="agent-system-prompt" />
-								<el-option label="改写提示词" value="rewrite" />
-								<el-option label="回退提示词" value="fallback" />
-								<el-option label="上下文模板" value="context-template" />
-							</el-select>
-						</el-form-item>
-						<el-form-item label="是否默认" prop="is_default">
-							<el-switch v-model="form.is_default" />
-						</el-form-item>
-					</div>
-					<div class="grid-2">
-						<el-form-item label="包含知识库" prop="has_knowledge_base">
-							<el-switch v-model="form.has_knowledge_base" />
-						</el-form-item>
-						<el-form-item label="包含网络搜索" prop="has_web_search">
-							<el-switch v-model="form.has_web_search" />
-						</el-form-item>
-					</div>
-				</el-form>
-				<template #footer>
-					<div class="drawer-footer">
-						<el-button @click="drawerVisible = false">取消</el-button>
-						<el-button type="primary" :loading="submitLoading" @click="handleSubmit"
-							>保存</el-button
-						>
+				</div>
+			</el-form>
+			<template #footer>
+				<div class="drawer-footer">
+					<el-button @click="drawerVisible = false">取消</el-button>
+					<el-button type="primary" :loading="submitLoading" @click="handleSubmit">保存</el-button>
+				</div>
+			</template>
+		</el-drawer>
+
+		<el-drawer v-model="detailVisible" title="提示词详情" width="760px">
+			<el-descriptions v-if="detailItem" :column="1" direction="vertical">
+				<el-descriptions-item label="名称">{{ detailItem.name }}</el-descriptions-item>
+				<el-descriptions-item label="类型">{{ formatType(detailItem.type) }}</el-descriptions-item>
+				<el-descriptions-item label="描述">{{
+					detailItem.description || '-'
+				}}</el-descriptions-item>
+				<el-descriptions-item label="内容">
+					<div class="w-full p-12px box-border border-1px rounded-md border-solid border-gray-200">
+						<SMarkdown :markdown="detailItem.content" />
 					</div>
-				</template>
-			</el-drawer>
-
-			<el-drawer v-model="detailVisible" title="提示词详情" width="760px">
-				<el-descriptions v-if="detailItem" :column="1" direction="vertical">
-					<el-descriptions-item label="名称">{{ detailItem.name }}</el-descriptions-item>
-					<el-descriptions-item label="类型">{{
-						formatType(detailItem.type)
-					}}</el-descriptions-item>
-					<el-descriptions-item label="描述">{{
-						detailItem.description || '-'
-					}}</el-descriptions-item>
-					<el-descriptions-item label="内容">
-						<div
-							class="w-full p-12px box-border border-1px rounded-md border-solid border-gray-200"
-						>
-							<SMarkdown :markdown="detailItem.content" />
-						</div>
-					</el-descriptions-item>
-				</el-descriptions>
-			</el-drawer>
-		</div>
+				</el-descriptions-item>
+			</el-descriptions>
+		</el-drawer>
 	</div>
 </template>
 
@@ -376,23 +370,24 @@ onMounted(() => {
 }
 
 .page-head {
-	margin-bottom: 18px;
+	margin-bottom: 20px;
 
 	h1 {
 		margin: 0;
 		font-size: 28px;
+		color: var(--text-primary);
 	}
 
 	p {
 		margin: 6px 0 0;
-		color: var(--text-tertiary);
+		font-size: 14px;
+		color: var(--text-secondary);
 	}
 }
 
-.panel {
-	display: flex;
-	flex-direction: column;
-	gap: 16px;
+.list-card {
+	border-radius: 18px;
+	margin-bottom: 16px;
 }
 
 .toolbar {
@@ -430,6 +425,7 @@ onMounted(() => {
 	grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
 	gap: 16px;
 	min-height: 200px;
+	margin: 16px 0;
 }
 
 .resource-card {

+ 16 - 9
apps/web/src/views/skills/index.vue

@@ -7,10 +7,15 @@
 			</div>
 		</div>
 
-		<div class="skills-panel">
+		<el-card class="list-card">
 			<div class="toolbar">
 				<div class="toolbar-left">
-					<el-input v-model="keyword" clearable placeholder="搜索技能名称或描述" class="search-input">
+					<el-input
+						v-model="keyword"
+						clearable
+						placeholder="搜索技能名称或描述"
+						class="search-input"
+					>
 						<template #prefix>
 							<el-icon><Search /></el-icon>
 						</template>
@@ -34,7 +39,7 @@
 					<div class="skill-card__desc">{{ item.description || '暂无描述' }}</div>
 				</div>
 			</div>
-		</div>
+		</el-card>
 	</div>
 </template>
 
@@ -83,22 +88,23 @@ onMounted(() => {
 }
 
 .page-head {
-	margin-bottom: 18px;
+	margin-bottom: 20px;
 
 	h1 {
 		margin: 0;
 		font-size: 28px;
+		color: var(--text-primary);
 	}
 
 	p {
 		margin: 6px 0 0;
-		color: var(--text-tertiary);
+		font-size: 14px;
+		color: var(--text-secondary);
 	}
 }
-.skills-panel {
-	display: flex;
-	flex-direction: column;
-	gap: 16px;
+.list-card {
+	border-radius: 18px;
+	margin-bottom: 16px;
 }
 
 .toolbar {
@@ -120,6 +126,7 @@ onMounted(() => {
 	display: flex;
 	align-items: center;
 	gap: 8px;
+	margin-bottom: 16px;
 
 	.el-button + .el-button {
 		margin-left: 0;

+ 204 - 202
apps/web/src/views/storage/index.vue

@@ -7,7 +7,7 @@
 			</div>
 		</div>
 
-		<div class="storage-manager" v-loading="pageLoading">
+		<el-card class="list-card" v-loading="pageLoading">
 			<div class="storage-actions">
 				<el-button type="primary" :loading="initLoading" @click="handleInit">
 					<el-icon><SetUp /></el-icon>
@@ -68,207 +68,207 @@
 				</div>
 				<el-empty v-if="!engines.length" class="empty" description="暂无引擎" />
 			</div>
+		</el-card>
+
+		<el-drawer
+			v-model="drawerVisible"
+			:title="selectedProvider ? `${formatProviderLabel(selectedProvider)} 配置` : '编辑存储引擎'"
+			direction="rtl"
+			size="760px"
+		>
+			<template v-if="selectedProvider">
+				<div class="drawer-intro">
+					<div class="drawer-intro__title">{{ formatProviderLabel(selectedProvider) }}</div>
+					<div class="drawer-intro__desc">
+						修改当前存储引擎配置后保存,可直接在此执行重新加载与连通测试。
+					</div>
+				</div>
 
-			<el-drawer
-				v-model="drawerVisible"
-				:title="selectedProvider ? `${formatProviderLabel(selectedProvider)} 配置` : '编辑存储引擎'"
-				direction="rtl"
-				size="760px"
-			>
-				<template v-if="selectedProvider">
-					<div class="drawer-intro">
-						<div class="drawer-intro__title">{{ formatProviderLabel(selectedProvider) }}</div>
-						<div class="drawer-intro__desc">
-							修改当前存储引擎配置后保存,可直接在此执行重新加载与连通测试。
+				<el-form :model="form" label-position="top">
+					<template v-if="selectedProvider === 'local'">
+						<el-form-item label="存储前缀">
+							<el-input v-model="form.local.path_prefix" placeholder="例如 knowledge/files" />
+						</el-form-item>
+					</template>
+
+					<template v-else-if="selectedProvider === 'minio'">
+						<div class="form-grid">
+							<el-form-item label="Endpoint">
+								<el-input v-model="form.minio.endpoint" placeholder="127.0.0.1:9000" />
+							</el-form-item>
+							<el-form-item label="Mode">
+								<el-input v-model="form.minio.mode" placeholder="docker" />
+							</el-form-item>
+							<el-form-item label="Access Key ID">
+								<el-input v-model="form.minio.access_key_id" />
+							</el-form-item>
+							<el-form-item label="Secret Access Key">
+								<el-input v-model="form.minio.secret_access_key" show-password />
+							</el-form-item>
+							<el-form-item label="Bucket">
+								<el-input v-model="form.minio.bucket_name" />
+							</el-form-item>
+							<el-form-item label="Path Prefix">
+								<el-input v-model="form.minio.path_prefix" />
+							</el-form-item>
 						</div>
-					</div>
+						<el-form-item label="Use SSL">
+							<el-switch v-model="form.minio.use_ssl" />
+						</el-form-item>
+					</template>
 
-					<el-form :model="form" label-position="top">
-						<template v-if="selectedProvider === 'local'">
-							<el-form-item label="存储前缀">
-								<el-input v-model="form.local.path_prefix" placeholder="例如 knowledge/files" />
-							</el-form-item>
-						</template>
-
-						<template v-else-if="selectedProvider === 'minio'">
-							<div class="form-grid">
-								<el-form-item label="Endpoint">
-									<el-input v-model="form.minio.endpoint" placeholder="127.0.0.1:9000" />
-								</el-form-item>
-								<el-form-item label="Mode">
-									<el-input v-model="form.minio.mode" placeholder="docker" />
-								</el-form-item>
-								<el-form-item label="Access Key ID">
-									<el-input v-model="form.minio.access_key_id" />
-								</el-form-item>
-								<el-form-item label="Secret Access Key">
-									<el-input v-model="form.minio.secret_access_key" show-password />
-								</el-form-item>
-								<el-form-item label="Bucket">
-									<el-input v-model="form.minio.bucket_name" />
-								</el-form-item>
-								<el-form-item label="Path Prefix">
-									<el-input v-model="form.minio.path_prefix" />
-								</el-form-item>
-							</div>
-							<el-form-item label="Use SSL">
-								<el-switch v-model="form.minio.use_ssl" />
-							</el-form-item>
-						</template>
-
-						<template v-else-if="selectedProvider === 'cos'">
-							<div class="form-grid">
-								<el-form-item label="App ID">
-									<el-input v-model="form.cos.app_id" />
-								</el-form-item>
-								<el-form-item label="Region">
-									<el-input v-model="form.cos.region" />
-								</el-form-item>
-								<el-form-item label="Secret ID">
-									<el-input v-model="form.cos.secret_id" />
-								</el-form-item>
-								<el-form-item label="Secret Key">
-									<el-input v-model="form.cos.secret_key" show-password />
-								</el-form-item>
-								<el-form-item label="Bucket">
-									<el-input v-model="form.cos.bucket_name" />
-								</el-form-item>
-								<el-form-item label="Path Prefix">
-									<el-input v-model="form.cos.path_prefix" />
-								</el-form-item>
-							</div>
-						</template>
-
-						<template v-else-if="selectedProvider === 'tos'">
-							<div class="form-grid">
-								<el-form-item label="Endpoint">
-									<el-input v-model="form.tos.endpoint" />
-								</el-form-item>
-								<el-form-item label="Region">
-									<el-input v-model="form.tos.region" />
-								</el-form-item>
-								<el-form-item label="Access Key">
-									<el-input v-model="form.tos.access_key" />
-								</el-form-item>
-								<el-form-item label="Secret Key">
-									<el-input v-model="form.tos.secret_key" show-password />
-								</el-form-item>
-								<el-form-item label="Bucket">
-									<el-input v-model="form.tos.bucket_name" />
-								</el-form-item>
-								<el-form-item label="Path Prefix">
-									<el-input v-model="form.tos.path_prefix" />
-								</el-form-item>
-							</div>
-						</template>
-
-						<template v-else-if="selectedProvider === 's3'">
-							<div class="form-grid">
-								<el-form-item label="Endpoint">
-									<el-input v-model="form.s3.endpoint" />
-								</el-form-item>
-								<el-form-item label="Region">
-									<el-input v-model="form.s3.region" />
-								</el-form-item>
-								<el-form-item label="Access Key">
-									<el-input v-model="form.s3.access_key" />
-								</el-form-item>
-								<el-form-item label="Secret Key">
-									<el-input v-model="form.s3.secret_key" show-password />
-								</el-form-item>
-								<el-form-item label="Bucket">
-									<el-input v-model="form.s3.bucket_name" />
-								</el-form-item>
-								<el-form-item label="Path Prefix">
-									<el-input v-model="form.s3.path_prefix" />
-								</el-form-item>
-							</div>
-						</template>
-
-						<template v-else-if="selectedProvider === 'ks3'">
-							<div class="form-grid">
-								<el-form-item label="Endpoint">
-									<el-input v-model="form.ks3.endpoint" />
-								</el-form-item>
-								<el-form-item label="Region">
-									<el-input v-model="form.ks3.region" />
-								</el-form-item>
-								<el-form-item label="Access Key">
-									<el-input v-model="form.ks3.access_key" />
-								</el-form-item>
-								<el-form-item label="Secret Key">
-									<el-input v-model="form.ks3.secret_key" show-password />
-								</el-form-item>
-								<el-form-item label="Bucket">
-									<el-input v-model="form.ks3.bucket_name" />
-								</el-form-item>
-								<el-form-item label="Path Prefix">
-									<el-input v-model="form.ks3.path_prefix" />
-								</el-form-item>
-							</div>
-						</template>
-
-						<template v-else-if="selectedProvider === 'oss'">
-							<div class="form-grid">
-								<el-form-item label="Endpoint">
-									<el-input v-model="form.oss.endpoint" />
-								</el-form-item>
-								<el-form-item label="Region">
-									<el-input v-model="form.oss.region" />
-								</el-form-item>
-								<el-form-item label="Access Key">
-									<el-input v-model="form.oss.access_key" />
-								</el-form-item>
-								<el-form-item label="Secret Key">
-									<el-input v-model="form.oss.secret_key" show-password />
-								</el-form-item>
-								<el-form-item label="Bucket">
-									<el-input v-model="form.oss.bucket_name" />
-								</el-form-item>
-								<el-form-item label="Path Prefix">
-									<el-input v-model="form.oss.path_prefix" />
-								</el-form-item>
-								<el-form-item label="Temp Bucket">
-									<el-input v-model="form.oss.temp_bucket_name" />
-								</el-form-item>
-								<el-form-item label="Temp Region">
-									<el-input v-model="form.oss.temp_region" />
-								</el-form-item>
-							</div>
-							<el-form-item label="Use Temp Bucket">
-								<el-switch v-model="form.oss.use_temp_bucket" />
+					<template v-else-if="selectedProvider === 'cos'">
+						<div class="form-grid">
+							<el-form-item label="App ID">
+								<el-input v-model="form.cos.app_id" />
+							</el-form-item>
+							<el-form-item label="Region">
+								<el-input v-model="form.cos.region" />
+							</el-form-item>
+							<el-form-item label="Secret ID">
+								<el-input v-model="form.cos.secret_id" />
+							</el-form-item>
+							<el-form-item label="Secret Key">
+								<el-input v-model="form.cos.secret_key" show-password />
+							</el-form-item>
+							<el-form-item label="Bucket">
+								<el-input v-model="form.cos.bucket_name" />
+							</el-form-item>
+							<el-form-item label="Path Prefix">
+								<el-input v-model="form.cos.path_prefix" />
+							</el-form-item>
+						</div>
+					</template>
+
+					<template v-else-if="selectedProvider === 'tos'">
+						<div class="form-grid">
+							<el-form-item label="Endpoint">
+								<el-input v-model="form.tos.endpoint" />
+							</el-form-item>
+							<el-form-item label="Region">
+								<el-input v-model="form.tos.region" />
+							</el-form-item>
+							<el-form-item label="Access Key">
+								<el-input v-model="form.tos.access_key" />
+							</el-form-item>
+							<el-form-item label="Secret Key">
+								<el-input v-model="form.tos.secret_key" show-password />
+							</el-form-item>
+							<el-form-item label="Bucket">
+								<el-input v-model="form.tos.bucket_name" />
+							</el-form-item>
+							<el-form-item label="Path Prefix">
+								<el-input v-model="form.tos.path_prefix" />
+							</el-form-item>
+						</div>
+					</template>
+
+					<template v-else-if="selectedProvider === 's3'">
+						<div class="form-grid">
+							<el-form-item label="Endpoint">
+								<el-input v-model="form.s3.endpoint" />
 							</el-form-item>
-						</template>
+							<el-form-item label="Region">
+								<el-input v-model="form.s3.region" />
+							</el-form-item>
+							<el-form-item label="Access Key">
+								<el-input v-model="form.s3.access_key" />
+							</el-form-item>
+							<el-form-item label="Secret Key">
+								<el-input v-model="form.s3.secret_key" show-password />
+							</el-form-item>
+							<el-form-item label="Bucket">
+								<el-input v-model="form.s3.bucket_name" />
+							</el-form-item>
+							<el-form-item label="Path Prefix">
+								<el-input v-model="form.s3.path_prefix" />
+							</el-form-item>
+						</div>
+					</template>
 
-						<el-form-item label="设为默认">
-							<el-switch v-model="form.is_default" />
+					<template v-else-if="selectedProvider === 'ks3'">
+						<div class="form-grid">
+							<el-form-item label="Endpoint">
+								<el-input v-model="form.ks3.endpoint" />
+							</el-form-item>
+							<el-form-item label="Region">
+								<el-input v-model="form.ks3.region" />
+							</el-form-item>
+							<el-form-item label="Access Key">
+								<el-input v-model="form.ks3.access_key" />
+							</el-form-item>
+							<el-form-item label="Secret Key">
+								<el-input v-model="form.ks3.secret_key" show-password />
+							</el-form-item>
+							<el-form-item label="Bucket">
+								<el-input v-model="form.ks3.bucket_name" />
+							</el-form-item>
+							<el-form-item label="Path Prefix">
+								<el-input v-model="form.ks3.path_prefix" />
+							</el-form-item>
+						</div>
+					</template>
+
+					<template v-else-if="selectedProvider === 'oss'">
+						<div class="form-grid">
+							<el-form-item label="Endpoint">
+								<el-input v-model="form.oss.endpoint" />
+							</el-form-item>
+							<el-form-item label="Region">
+								<el-input v-model="form.oss.region" />
+							</el-form-item>
+							<el-form-item label="Access Key">
+								<el-input v-model="form.oss.access_key" />
+							</el-form-item>
+							<el-form-item label="Secret Key">
+								<el-input v-model="form.oss.secret_key" show-password />
+							</el-form-item>
+							<el-form-item label="Bucket">
+								<el-input v-model="form.oss.bucket_name" />
+							</el-form-item>
+							<el-form-item label="Path Prefix">
+								<el-input v-model="form.oss.path_prefix" />
+							</el-form-item>
+							<el-form-item label="Temp Bucket">
+								<el-input v-model="form.oss.temp_bucket_name" />
+							</el-form-item>
+							<el-form-item label="Temp Region">
+								<el-input v-model="form.oss.temp_region" />
+							</el-form-item>
+						</div>
+						<el-form-item label="Use Temp Bucket">
+							<el-switch v-model="form.oss.use_temp_bucket" />
 						</el-form-item>
-					</el-form>
-				</template>
-
-				<template #footer>
-					<div class="drawer-footer">
-						<el-button @click="drawerVisible = false">取消</el-button>
-						<el-button
-							@click="reloadSelectedProvider"
-							:loading="providerLoading === selectedProvider"
-						>
-							重新加载
-						</el-button>
-						<el-button
-							type="primary"
-							plain
-							:disabled="!selectedProvider"
-							:loading="selectedProvider ? testingProvider === selectedProvider : false"
-							@click="selectedProvider && testProvider(selectedProvider)"
-						>
-							测试连接
-						</el-button>
-						<el-button type="primary" :loading="saving" @click="saveConfig">保存配置</el-button>
-					</div>
-				</template>
-			</el-drawer>
-		</div>
+					</template>
+
+					<el-form-item label="设为默认">
+						<el-switch v-model="form.is_default" />
+					</el-form-item>
+				</el-form>
+			</template>
+
+			<template #footer>
+				<div class="drawer-footer">
+					<el-button @click="drawerVisible = false">取消</el-button>
+					<el-button
+						@click="reloadSelectedProvider"
+						:loading="providerLoading === selectedProvider"
+					>
+						重新加载
+					</el-button>
+					<el-button
+						type="primary"
+						plain
+						:disabled="!selectedProvider"
+						:loading="selectedProvider ? testingProvider === selectedProvider : false"
+						@click="selectedProvider && testProvider(selectedProvider)"
+					>
+						测试连接
+					</el-button>
+					<el-button type="primary" :loading="saving" @click="saveConfig">保存配置</el-button>
+				</div>
+			</template>
+		</el-drawer>
 	</div>
 </template>
 
@@ -724,23 +724,24 @@ onMounted(async () => {
 }
 
 .page-head {
-	margin-bottom: 18px;
+	margin-bottom: 20px;
 
 	h1 {
 		margin: 0;
 		font-size: 28px;
+		color: var(--text-primary);
 	}
 
 	p {
 		margin: 6px 0 0;
-		color: var(--text-tertiary);
+		font-size: 14px;
+		color: var(--text-secondary);
 	}
 }
 
-.storage-manager {
-	display: flex;
-	flex-direction: column;
-	gap: 16px;
+.list-card {
+	border-radius: 18px;
+	margin-bottom: 16px;
 }
 
 .storage-actions {
@@ -748,6 +749,7 @@ onMounted(async () => {
 	align-items: center;
 	gap: 8px;
 	flex-wrap: wrap;
+	margin-bottom: 16px;
 
 	.el-button + .el-button {
 		margin-left: 0;

+ 27 - 25
apps/web/src/views/vector/index.vue

@@ -7,7 +7,7 @@
 			</div>
 		</div>
 
-		<div class="vector-manager" v-loading="pageLoading">
+		<el-card class="list-card" v-loading="pageLoading">
 			<!-- 搜索和操作栏 -->
 			<div class="toolbar">
 				<el-form :model="searchForm" inline class="search-form">
@@ -113,24 +113,24 @@
 					@current-change="loadList"
 				/>
 			</div>
-
-			<!-- 创建/编辑抽屉 -->
-			<VectorEditDrawer
-				v-model:visible="editDrawerVisible"
-				:is-edit="isEdit"
-				:edit-id="editId"
-				:engine-types="engineTypes"
-				:initial-data="editData"
-				@saved="loadList"
-			/>
-
-			<!-- 详情抽屉 -->
-			<VectorDetailDrawer
-				v-model:visible="detailDrawerVisible"
-				:target-id="detailTargetId"
-				:engine-types="engineTypes"
-			/>
-		</div>
+		</el-card>
+
+		<!-- 创建/编辑抽屉 -->
+		<VectorEditDrawer
+			v-model:visible="editDrawerVisible"
+			:is-edit="isEdit"
+			:edit-id="editId"
+			:engine-types="engineTypes"
+			:initial-data="editData"
+			@saved="loadList"
+		/>
+
+		<!-- 详情抽屉 -->
+		<VectorDetailDrawer
+			v-model:visible="detailDrawerVisible"
+			:target-id="detailTargetId"
+			:engine-types="engineTypes"
+		/>
 	</div>
 </template>
 
@@ -290,23 +290,24 @@ onMounted(async () => {
 }
 
 .page-head {
-	margin-bottom: 18px;
+	margin-bottom: 20px;
 
 	h1 {
 		margin: 0;
 		font-size: 28px;
+		color: var(--text-primary);
 	}
 
 	p {
 		margin: 6px 0 0;
-		color: var(--text-tertiary);
+		font-size: 14px;
+		color: var(--text-secondary);
 	}
 }
 
-.vector-manager {
-	display: flex;
-	flex-direction: column;
-	gap: 16px;
+.list-card {
+	border-radius: 18px;
+	margin-bottom: 16px;
 }
 
 .toolbar {
@@ -315,6 +316,7 @@ onMounted(async () => {
 	justify-content: space-between;
 	gap: 16px;
 	flex-wrap: wrap;
+	margin-bottom: 16px;
 }
 
 .search-form {

+ 10 - 9
apps/web/src/views/web-search/index.vue

@@ -7,7 +7,7 @@
 			</div>
 		</div>
 
-		<div class="panel">
+		<el-card class="list-card">
 			<div class="toolbar">
 				<div class="toolbar-left">
 					<el-input
@@ -107,8 +107,9 @@
 					@size-change="handleSizeChange"
 				/>
 			</div>
+		</el-card>
 
-			<el-drawer
+		<el-drawer
 				v-model="drawerVisible"
 				:title="currentId ? '编辑网络搜索' : '新建网络搜索'"
 				direction="rtl"
@@ -167,7 +168,6 @@
 					</div>
 				</template>
 			</el-drawer>
-		</div>
 	</div>
 </template>
 
@@ -391,23 +391,24 @@ onMounted(async () => {
 }
 
 .page-head {
-	margin-bottom: 18px;
+	margin-bottom: 20px;
 
 	h1 {
 		margin: 0;
 		font-size: 28px;
+		color: var(--text-primary);
 	}
 
 	p {
 		margin: 6px 0 0;
-		color: var(--text-tertiary);
+		font-size: 14px;
+		color: var(--text-secondary);
 	}
 }
 
-.panel {
-	display: flex;
-	flex-direction: column;
-	gap: 16px;
+.list-card {
+	border-radius: 18px;
+	margin-bottom: 16px;
 }
 
 .toolbar {