2026-05-06 11:00:38 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<section class="skill-center">
|
|
|
|
|
|
<Transition name="skill-view" mode="out-in">
|
|
|
|
|
|
<article v-if="selectedSkill" key="detail" class="skill-detail">
|
|
|
|
|
|
<div class="detail-scroll">
|
|
|
|
|
|
<section class="detail-hero panel">
|
|
|
|
|
|
<div class="hero-title">
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<div class="skill-badge" :class="selectedSkill.badgeTone">{{ selectedSkill.typeLabel }}</div>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<h2>{{ selectedSkill.name }}</h2>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<p>{{ selectedSkill.summary || '当前资产尚未补充说明。' }}</p>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="hero-review-meta">
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<i class="mdi mdi-code-tags"></i>
|
|
|
|
|
|
{{ selectedSkill.code }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<i class="mdi mdi-account-outline"></i>
|
|
|
|
|
|
负责人:{{ selectedSkill.owner }}
|
|
|
|
|
|
</span>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<span>
|
|
|
|
|
|
<i class="mdi mdi-account-check-outline"></i>
|
|
|
|
|
|
审核人:{{ selectedSkill.reviewer }}
|
|
|
|
|
|
</span>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<b :class="['status-pill', selectedSkill.statusTone]">{{ selectedSkill.status }}</b>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<b
|
|
|
|
|
|
v-if="selectedSkillIsRule"
|
|
|
|
|
|
:class="['status-pill', selectedSkill.reviewStatusTone]"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ selectedSkill.reviewStatusLabel }}
|
|
|
|
|
|
</b>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-if="selectedSkillIsRule" class="review-note-block">
|
|
|
|
|
|
<strong>上线约束</strong>
|
|
|
|
|
|
<p>{{ activateBlockedReason || '当前规则版本审核通过后可正式上线。' }}</p>
|
|
|
|
|
|
<span v-if="showReviewNote">
|
|
|
|
|
|
审核时间:{{ selectedSkill.reviewTimeLabel }}
|
|
|
|
|
|
<template v-if="selectedSkill.reviewNote"> · 审核意见:{{ selectedSkill.reviewNote }}</template>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="hero-stats">
|
|
|
|
|
|
<div class="hero-stat">
|
|
|
|
|
|
<span>资产编码</span>
|
|
|
|
|
|
<strong>{{ selectedSkill.code }}</strong>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="hero-stat">
|
|
|
|
|
|
<span>业务域</span>
|
|
|
|
|
|
<strong>{{ selectedSkill.category }}</strong>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="hero-stat">
|
|
|
|
|
|
<span>{{ selectedSkillIsRule ? '当前展示版本' : '当前版本' }}</span>
|
|
|
|
|
|
<strong>{{ selectedSkill.displayVersion || selectedSkill.version }}</strong>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="hero-stat">
|
|
|
|
|
|
<span>最近更新</span>
|
|
|
|
|
|
<strong>{{ selectedSkill.updatedAt }}</strong>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<section v-if="detailError" class="detail-inline-state panel error">
|
|
|
|
|
|
<i class="mdi mdi-alert-circle-outline"></i>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>资产详情加载失败</strong>
|
|
|
|
|
|
<p>{{ detailError }}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button class="state-action" type="button" @click="openAssetDetail(selectedSkill)">重新加载</button>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section v-else-if="detailLoading && selectedSkill.loading" class="detail-inline-state panel">
|
|
|
|
|
|
<i class="mdi mdi-loading mdi-spin"></i>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>正在加载资产详情</strong>
|
|
|
|
|
|
<p>列表数据已就绪,正在补充版本、审核和运行信息。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-else
|
|
|
|
|
|
class="detail-grid"
|
|
|
|
|
|
:class="{ 'skill-md-detail-grid': selectedSkill.type === 'rules' }"
|
|
|
|
|
|
>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<section class="detail-main">
|
2026-05-11 01:53:30 +00:00
|
|
|
|
<article v-if="selectedSkill.type === 'rules'" class="detail-card panel markdown-card">
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<div class="card-head">
|
|
|
|
|
|
<div>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<h3>Markdown 规则内容</h3>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<p>当前展示版本:{{ selectedSkill.displayVersion }},保存后会生成新的版本快照。</p>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
</div>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<button
|
|
|
|
|
|
class="mini-btn primary"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:disabled="!canEditMarkdown || detailBusy"
|
|
|
|
|
|
@click="saveRuleMarkdown"
|
|
|
|
|
|
>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<i class="mdi mdi-content-save-outline"></i>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<span>{{ actionState === 'save-markdown' ? '保存中...' : '保存 Markdown' }}</span>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<div v-if="detailLoading" class="subtle-banner">
|
|
|
|
|
|
<i class="mdi mdi-loading mdi-spin"></i>
|
|
|
|
|
|
<span>正在刷新规则详情...</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<label class="field">
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<span>{{ selectedSkill.code }}</span>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="selectedSkill.markdownContent"
|
|
|
|
|
|
class="markdown-editor"
|
2026-05-11 06:32:38 +00:00
|
|
|
|
:class="{ disabled: !canEditMarkdown }"
|
2026-05-09 15:46:16 +00:00
|
|
|
|
spellcheck="false"
|
2026-05-11 06:32:38 +00:00
|
|
|
|
:readonly="!canEditMarkdown || detailBusy"
|
2026-05-09 15:46:16 +00:00
|
|
|
|
></textarea>
|
|
|
|
|
|
</label>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
|
|
|
|
|
|
<div class="editor-foot">
|
|
|
|
|
|
<span>版本说明:{{ selectedSkill.currentVersionChangeNote }}</span>
|
|
|
|
|
|
<span>最近保存:{{ selectedSkill.updatedAt }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-if="!canEditMarkdown" class="review-note-block muted">
|
|
|
|
|
|
<strong>只读模式</strong>
|
|
|
|
|
|
<p>当前账号没有规则编辑权限,Markdown 仅可查看。</p>
|
|
|
|
|
|
</div>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
</article>
|
|
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<article class="detail-card panel">
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<div class="card-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3>{{ selectedSkill.configTitle }}</h3>
|
|
|
|
|
|
<p>{{ selectedSkill.configDesc }}</p>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</div>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<span class="edit-badge">{{ selectedSkill.version }}</span>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="form-grid">
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<label v-for="field in selectedSkill.fields" :key="field.label" class="field">
|
|
|
|
|
|
<span>{{ field.label }}</span>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<input :value="field.value" readonly />
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</label>
|
|
|
|
|
|
<label class="field span-2">
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<span>适用场景</span>
|
|
|
|
|
|
<textarea rows="3" :value="selectedSkill.scope" readonly></textarea>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
|
2026-05-11 01:53:30 +00:00
|
|
|
|
<article v-if="selectedSkill.type !== 'rules'" class="detail-card panel">
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<div class="card-head">
|
|
|
|
|
|
<div>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<h3>{{ selectedSkill.detailTitle }}</h3>
|
|
|
|
|
|
<p>{{ selectedSkill.detailDesc }}</p>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<span class="edit-badge">{{ selectedSkill.promptSections.length }} 段</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="prompt-stack">
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<section
|
|
|
|
|
|
v-for="section in selectedSkill.promptSections"
|
|
|
|
|
|
:key="section.title"
|
|
|
|
|
|
class="prompt-block"
|
|
|
|
|
|
>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<header>
|
|
|
|
|
|
<strong>{{ section.title }}</strong>
|
|
|
|
|
|
<span>{{ section.intent }}</span>
|
|
|
|
|
|
</header>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<textarea rows="5" :value="section.content" readonly></textarea>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</section>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<article class="detail-card panel">
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<div class="card-head">
|
|
|
|
|
|
<div>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<h3>{{ selectedSkill.outputTitle }}</h3>
|
|
|
|
|
|
<p>{{ selectedSkill.outputDesc }}</p>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</div>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<span class="edit-badge">
|
|
|
|
|
|
{{ selectedSkill.type === 'rules' ? selectedSkill.reviewStatusLabel : selectedSkill.publishState }}
|
|
|
|
|
|
</span>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="contract-grid">
|
|
|
|
|
|
<div class="contract-panel">
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<h4>{{ selectedSkill.ruleListTitle }}</h4>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<ul>
|
|
|
|
|
|
<li v-for="rule in selectedSkill.outputRules" :key="rule">{{ rule }}</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<div class="contract-panel">
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<h4>{{ selectedSkill.checkListTitle }}</h4>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<div v-for="test in selectedSkill.tests" :key="test.name" class="test-row">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>{{ test.name }}</strong>
|
|
|
|
|
|
<span>{{ test.input }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<b :class="['test-state', test.tone]">{{ test.result }}</b>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
|
|
|
|
|
|
<div v-if="selectedSkillIsRule" class="review-action-strip">
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="minor-action"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:disabled="!canManageSelected || detailBusy"
|
|
|
|
|
|
@click="reviewSelectedRule('pending')"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i class="mdi mdi-send-outline"></i>
|
|
|
|
|
|
<span>{{ actionState === 'review-pending' ? '提交中...' : '提交审核' }}</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="minor-action success-action"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:disabled="!canManageSelected || detailBusy"
|
|
|
|
|
|
@click="reviewSelectedRule('approved')"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i class="mdi mdi-check-decagram-outline"></i>
|
|
|
|
|
|
<span>{{ actionState === 'review-approved' ? '处理中...' : '审核通过' }}</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="minor-action danger-action"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:disabled="!canManageSelected || detailBusy"
|
|
|
|
|
|
@click="reviewSelectedRule('rejected')"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i class="mdi mdi-close-octagon-outline"></i>
|
|
|
|
|
|
<span>{{ actionState === 'review-rejected' ? '处理中...' : '驳回版本' }}</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<p v-if="selectedSkillIsRule && activateBlockedReason" class="action-help">
|
|
|
|
|
|
{{ activateBlockedReason }}
|
|
|
|
|
|
</p>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</article>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
2026-05-11 01:53:30 +00:00
|
|
|
|
<aside v-if="selectedSkill.type === 'rules'" class="detail-side skill-review-side">
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<article class="side-card panel review-card">
|
|
|
|
|
|
<div class="card-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3>版本信息</h3>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<p>最近 5 个规则版本,仅切换当前展示内容。</p>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<div v-if="selectedSkill.history.length" class="version-list">
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<button
|
|
|
|
|
|
v-for="item in selectedSkill.history.slice(0, 5)"
|
|
|
|
|
|
:key="item.version + item.time"
|
|
|
|
|
|
class="version-row"
|
2026-05-11 06:32:38 +00:00
|
|
|
|
:class="{ active: item.version === selectedSkill.displayVersion }"
|
2026-05-09 15:46:16 +00:00
|
|
|
|
type="button"
|
|
|
|
|
|
@click="openVersionSwitch(item)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="version-row-head">
|
|
|
|
|
|
<strong>{{ item.version }}</strong>
|
|
|
|
|
|
<span class="version-current-slot">
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<b v-if="item.version === selectedSkill.currentVersion" class="current-version">当前</b>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
</span>
|
|
|
|
|
|
<span>{{ item.time }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p>{{ item.note }}</p>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
|
|
|
|
|
|
<div v-else class="empty-side-note">
|
|
|
|
|
|
<i class="mdi mdi-history"></i>
|
|
|
|
|
|
<span>暂无版本历史</span>
|
|
|
|
|
|
</div>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
</article>
|
|
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
|
|
<aside v-else class="detail-side">
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<article class="side-card panel">
|
|
|
|
|
|
<div class="card-head">
|
|
|
|
|
|
<div>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<h3>{{ selectedSkill.triggerTitle }}</h3>
|
|
|
|
|
|
<p>{{ selectedSkill.triggerDesc }}</p>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="tag-list">
|
|
|
|
|
|
<span v-for="item in selectedSkill.triggers" :key="item">{{ item }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
|
|
<article class="side-card panel">
|
|
|
|
|
|
<div class="card-head">
|
|
|
|
|
|
<div>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<h3>{{ selectedSkill.toolTitle }}</h3>
|
|
|
|
|
|
<p>{{ selectedSkill.toolDesc }}</p>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<div v-if="selectedSkill.tools.length" class="tool-list">
|
|
|
|
|
|
<div v-for="tool in selectedSkill.tools" :key="tool.name + tool.scope" class="tool-row">
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<div>
|
|
|
|
|
|
<strong>{{ tool.name }}</strong>
|
|
|
|
|
|
<span>{{ tool.scope }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<b :class="['tool-state', tool.tone]">{{ tool.mode }}</b>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<div v-else class="empty-side-note">
|
|
|
|
|
|
<i class="mdi mdi-connection"></i>
|
|
|
|
|
|
<span>暂无依赖信息</span>
|
|
|
|
|
|
</div>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
|
|
<article class="side-card panel">
|
|
|
|
|
|
<div class="card-head">
|
|
|
|
|
|
<div>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<h3>{{ selectedSkill.historyTitle }}</h3>
|
|
|
|
|
|
<p>{{ selectedSkill.historyDesc }}</p>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<div v-if="selectedSkill.history.length" class="history-list">
|
|
|
|
|
|
<div v-for="item in selectedSkill.history" :key="item.version + item.time" class="history-row">
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<strong>{{ item.version }}</strong>
|
|
|
|
|
|
<span>{{ item.note }}</span>
|
|
|
|
|
|
<small>{{ item.time }}</small>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<div v-else class="empty-side-note">
|
|
|
|
|
|
<i class="mdi mdi-clock-outline"></i>
|
|
|
|
|
|
<span>暂无版本记录</span>
|
|
|
|
|
|
</div>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
|
|
<article class="side-card panel publish-card">
|
|
|
|
|
|
<div>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<h3>{{ selectedSkill.publishTitle }}</h3>
|
|
|
|
|
|
<p>{{ selectedSkill.publishDesc }}</p>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="publish-summary">
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<span>{{ selectedSkill.publishMeta }}</span>
|
|
|
|
|
|
<strong>{{ selectedSkill.publishState }}</strong>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
</aside>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<footer class="detail-actions">
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<button class="back-action" type="button" @click="closeDetail">
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<i class="mdi mdi-arrow-left"></i>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<span>返回能力列表</span>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</button>
|
|
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<div v-if="selectedSkillIsRule" class="detail-action-group">
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="minor-action"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:disabled="!canEditMarkdown || detailBusy"
|
|
|
|
|
|
@click="saveRuleMarkdown"
|
|
|
|
|
|
>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<i class="mdi mdi-content-save-outline"></i>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<span>{{ actionState === 'save-markdown' ? '保存中...' : '保存 Markdown' }}</span>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</button>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<button
|
|
|
|
|
|
class="major-action"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:disabled="!canActivateSelected"
|
|
|
|
|
|
:title="activateBlockedReason"
|
|
|
|
|
|
@click="activateSelectedRule"
|
|
|
|
|
|
>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<i class="mdi mdi-rocket-launch-outline"></i>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<span>{{ actionState === 'activate' ? '上线中...' : selectedSkill.statusValue === 'active' ? '已上线' : '正式上线' }}</span>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
|
|
|
|
|
|
<div v-else class="detail-action-group detail-meta-actions">
|
|
|
|
|
|
<span class="footer-note">{{ selectedSkill.publishMeta }}</span>
|
|
|
|
|
|
</div>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</footer>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
|
|
<article v-else key="list" class="skill-list panel">
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<nav class="status-tabs" aria-label="能力类型">
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<button
|
|
|
|
|
|
v-for="tab in tabs"
|
2026-05-09 15:46:16 +00:00
|
|
|
|
:key="tab.id"
|
2026-05-06 11:00:38 +08:00
|
|
|
|
type="button"
|
2026-05-09 15:46:16 +00:00
|
|
|
|
:class="{ active: activeType === tab.id }"
|
|
|
|
|
|
@click="activeType = tab.id"
|
2026-05-06 11:00:38 +08:00
|
|
|
|
>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
{{ tab.label }}
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</button>
|
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="list-toolbar">
|
|
|
|
|
|
<div class="filter-set">
|
2026-05-11 01:53:30 +00:00
|
|
|
|
<label class="search-filter">
|
|
|
|
|
|
<i class="mdi mdi-magnify"></i>
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model="keyword"
|
|
|
|
|
|
type="search"
|
|
|
|
|
|
:placeholder="searchPlaceholder"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
|
|
|
|
|
|
<div class="picker-filter" :class="{ open: activeFilterPopover === 'domain' }">
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="picker-trigger"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:aria-expanded="activeFilterPopover === 'domain'"
|
|
|
|
|
|
aria-haspopup="dialog"
|
|
|
|
|
|
@click="toggleFilterPopover('domain')"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="picker-label">{{ selectedDomainLabel }}</span>
|
|
|
|
|
|
<i class="mdi mdi-chevron-down"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="activeFilterPopover === 'domain'"
|
|
|
|
|
|
class="picker-popover"
|
|
|
|
|
|
role="dialog"
|
|
|
|
|
|
aria-label="选择业务域"
|
|
|
|
|
|
>
|
|
|
|
|
|
<header>
|
|
|
|
|
|
<strong>选择业务域</strong>
|
|
|
|
|
|
<button type="button" aria-label="关闭业务域选择" @click="closeFilterPopover">
|
|
|
|
|
|
<i class="mdi mdi-close"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<div class="picker-option-list">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="option in domainOptions"
|
|
|
|
|
|
:key="option.value || 'all-domain'"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="picker-option"
|
|
|
|
|
|
:class="{ active: selectedDomain === option.value }"
|
|
|
|
|
|
@click="selectFilter('domain', option.value)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ option.label }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="picker-filter" :class="{ open: activeFilterPopover === 'owner' }">
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="picker-trigger"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:aria-expanded="activeFilterPopover === 'owner'"
|
|
|
|
|
|
aria-haspopup="dialog"
|
|
|
|
|
|
@click="toggleFilterPopover('owner')"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="picker-label">{{ selectedOwnerLabel }}</span>
|
|
|
|
|
|
<i class="mdi mdi-chevron-down"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="activeFilterPopover === 'owner'"
|
|
|
|
|
|
class="picker-popover"
|
|
|
|
|
|
role="dialog"
|
|
|
|
|
|
aria-label="选择负责人"
|
|
|
|
|
|
>
|
|
|
|
|
|
<header>
|
|
|
|
|
|
<strong>选择负责人</strong>
|
|
|
|
|
|
<button type="button" aria-label="关闭负责人选择" @click="closeFilterPopover">
|
|
|
|
|
|
<i class="mdi mdi-close"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<div class="picker-option-list">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="option in ownerOptions"
|
|
|
|
|
|
:key="option.value || 'all-owner'"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="picker-option"
|
|
|
|
|
|
:class="{ active: selectedOwner === option.value }"
|
|
|
|
|
|
@click="selectFilter('owner', option.value)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ option.label }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="picker-filter" :class="{ open: activeFilterPopover === 'status' }">
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="picker-trigger"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:aria-expanded="activeFilterPopover === 'status'"
|
|
|
|
|
|
aria-haspopup="dialog"
|
|
|
|
|
|
@click="toggleFilterPopover('status')"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="picker-label">{{ selectedStatusLabel }}</span>
|
|
|
|
|
|
<i class="mdi mdi-chevron-down"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="activeFilterPopover === 'status'"
|
|
|
|
|
|
class="picker-popover"
|
|
|
|
|
|
role="dialog"
|
|
|
|
|
|
aria-label="选择状态"
|
|
|
|
|
|
>
|
|
|
|
|
|
<header>
|
|
|
|
|
|
<strong>选择状态</strong>
|
|
|
|
|
|
<button type="button" aria-label="关闭状态选择" @click="closeFilterPopover">
|
|
|
|
|
|
<i class="mdi mdi-close"></i>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<div class="picker-option-list">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="option in statusOptions"
|
|
|
|
|
|
:key="option.value || 'all-status'"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="picker-option"
|
|
|
|
|
|
:class="{ active: selectedStatus === option.value }"
|
|
|
|
|
|
@click="selectFilter('status', option.value)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ option.label }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="toolbar-actions">
|
|
|
|
|
|
<button v-if="activeFilterTokens.length" class="ghost-filter-btn" type="button" @click="resetFilters">
|
|
|
|
|
|
<i class="mdi mdi-filter-remove-outline"></i>
|
|
|
|
|
|
<span>清空筛选</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
<button class="create-btn" type="button" disabled>
|
|
|
|
|
|
<i class="mdi mdi-plus"></i>
|
|
|
|
|
|
<span>{{ createButtonLabel }}</span>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<p class="hint"><i class="mdi mdi-information-outline"></i> {{ hintText }}</p>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<div v-if="activeFilterTokens.length" class="active-filter-strip">
|
|
|
|
|
|
<span v-for="token in activeFilterTokens" :key="token" class="active-filter-chip">
|
|
|
|
|
|
{{ token }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<div class="table-wrap">
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<div v-if="loading" class="table-state">
|
|
|
|
|
|
<i class="mdi mdi-loading mdi-spin"></i>
|
|
|
|
|
|
<p>正在加载{{ activeTabLabel }}资产...</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-else-if="errorMessage" class="table-state error">
|
|
|
|
|
|
<i class="mdi mdi-alert-circle-outline"></i>
|
|
|
|
|
|
<p>{{ errorMessage }}</p>
|
|
|
|
|
|
<button type="button" class="state-action" @click="loadAssets">重新加载</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-else-if="!visibleSkills.length" class="table-state empty">
|
|
|
|
|
|
<i class="mdi mdi-database-search-outline"></i>
|
|
|
|
|
|
<p>没有匹配的资产数据</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<table v-else>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<th>{{ tableColumns.name }}</th>
|
|
|
|
|
|
<th>{{ tableColumns.category }}</th>
|
|
|
|
|
|
<th>{{ tableColumns.owner }}</th>
|
|
|
|
|
|
<th>{{ tableColumns.scope }}</th>
|
|
|
|
|
|
<th>{{ tableColumns.runtime }}</th>
|
|
|
|
|
|
<th>{{ tableColumns.version }}</th>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<th>状态</th>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
<th>{{ tableColumns.metric }}</th>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
<th>最近更新</th>
|
|
|
|
|
|
<th>操作</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr
|
|
|
|
|
|
v-for="skill in visibleSkills"
|
|
|
|
|
|
:key="skill.id"
|
|
|
|
|
|
:class="{ spotlight: skill.spotlight }"
|
2026-05-11 06:32:38 +00:00
|
|
|
|
@click="openAssetDetail(skill)"
|
2026-05-06 11:00:38 +08:00
|
|
|
|
>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<div class="skill-name-cell">
|
|
|
|
|
|
<span class="skill-avatar" :class="skill.badgeTone">{{ skill.short }}</span>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>{{ skill.name }}</strong>
|
|
|
|
|
|
<span>{{ skill.summary }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td>{{ skill.category }}</td>
|
|
|
|
|
|
<td>{{ skill.owner }}</td>
|
|
|
|
|
|
<td><span class="scope-pill">{{ skill.scope }}</span></td>
|
|
|
|
|
|
<td>{{ skill.model }}</td>
|
|
|
|
|
|
<td>{{ skill.version }}</td>
|
|
|
|
|
|
<td><span class="status-pill" :class="skill.statusTone">{{ skill.status }}</span></td>
|
|
|
|
|
|
<td>{{ skill.hitRate }}</td>
|
|
|
|
|
|
<td>{{ skill.updatedAt }}</td>
|
|
|
|
|
|
<td>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<button class="row-action" type="button" @click.stop="openAssetDetail(skill)">
|
|
|
|
|
|
查看详情
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</button>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
|
|
|
|
|
|
<footer v-if="!loading && !errorMessage" class="list-foot">
|
|
|
|
|
|
<span class="page-summary">当前展示 {{ visibleSkills.length }} 条资产</span>
|
|
|
|
|
|
</footer>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</article>
|
|
|
|
|
|
</Transition>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
|
|
|
|
|
|
<Teleport to="body">
|
|
|
|
|
|
<div v-if="versionSwitchTarget" class="modal-backdrop" @click.self="cancelVersionSwitch">
|
|
|
|
|
|
<section class="version-modal panel" role="dialog" aria-modal="true" aria-labelledby="version-switch-title">
|
|
|
|
|
|
<div class="card-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3 id="version-switch-title">切换规则版本</h3>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<p>切换后编辑器只会替换当前展示内容,不会直接回滚后端当前版本。</p>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="version-modal-summary">
|
|
|
|
|
|
<div>
|
2026-05-11 06:32:38 +00:00
|
|
|
|
<span>当前展示版本</span>
|
|
|
|
|
|
<strong>{{ selectedSkill?.displayVersion }}</strong>
|
2026-05-09 15:46:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<i class="mdi mdi-arrow-right"></i>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<span>目标版本</span>
|
|
|
|
|
|
<strong>{{ versionSwitchTarget.version }}</strong>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="version-modal-note">
|
|
|
|
|
|
<strong>{{ versionSwitchTarget.note }}</strong>
|
|
|
|
|
|
<span>{{ versionSwitchTarget.time }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<footer class="modal-actions">
|
|
|
|
|
|
<button class="minor-action" type="button" @click="cancelVersionSwitch">取消</button>
|
|
|
|
|
|
<button class="major-action" type="button" @click="confirmVersionSwitch">确认切换</button>
|
|
|
|
|
|
</footer>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Teleport>
|
2026-05-06 11:00:38 +08:00
|
|
|
|
</section>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script src="./scripts/AuditView.js"></script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped src="../assets/styles/views/audit-view.css"></style>
|