335 lines
12 KiB
Vue
335 lines
12 KiB
Vue
|
|
<template>
|
||
|
|
<article class="skill-list panel">
|
||
|
|
<nav class="status-tabs" aria-label="能力类型">
|
||
|
|
<button
|
||
|
|
v-for="tab in tabs"
|
||
|
|
:key="tab.id"
|
||
|
|
type="button"
|
||
|
|
:class="{ active: activeType === tab.id }"
|
||
|
|
@click="emit('update:activeType', tab.id)"
|
||
|
|
>
|
||
|
|
{{ tab.label }}
|
||
|
|
</button>
|
||
|
|
</nav>
|
||
|
|
|
||
|
|
<div class="list-toolbar">
|
||
|
|
<div class="filter-set">
|
||
|
|
<label class="search-filter">
|
||
|
|
<i class="mdi mdi-magnify"></i>
|
||
|
|
<input
|
||
|
|
:value="keyword"
|
||
|
|
type="search"
|
||
|
|
:placeholder="searchPlaceholder"
|
||
|
|
@input="emit('update:keyword', $event.target.value)"
|
||
|
|
/>
|
||
|
|
</label>
|
||
|
|
|
||
|
|
<AuditPickerFilter
|
||
|
|
id="domain"
|
||
|
|
title="选择业务域"
|
||
|
|
close-label="关闭业务域选择"
|
||
|
|
:active-filter-popover="activeFilterPopover"
|
||
|
|
:label="selectedDomainLabel"
|
||
|
|
:options="domainOptions"
|
||
|
|
:selected-value="selectedDomain"
|
||
|
|
@toggle="emit('toggle-filter-popover', $event)"
|
||
|
|
@close="emit('close-filter-popover')"
|
||
|
|
@select="selectFilter('domain', $event)"
|
||
|
|
/>
|
||
|
|
|
||
|
|
<AuditPickerFilter
|
||
|
|
v-if="showOwnerFilter"
|
||
|
|
id="owner"
|
||
|
|
title="选择负责人"
|
||
|
|
close-label="关闭负责人选择"
|
||
|
|
:active-filter-popover="activeFilterPopover"
|
||
|
|
:label="selectedOwnerLabel"
|
||
|
|
:options="ownerOptions"
|
||
|
|
:selected-value="selectedOwner"
|
||
|
|
@toggle="emit('toggle-filter-popover', $event)"
|
||
|
|
@close="emit('close-filter-popover')"
|
||
|
|
@select="selectFilter('owner', $event)"
|
||
|
|
/>
|
||
|
|
|
||
|
|
<AuditPickerFilter
|
||
|
|
v-if="showRiskLevelFilter"
|
||
|
|
id="riskLevel"
|
||
|
|
title="选择风险等级"
|
||
|
|
close-label="关闭风险等级选择"
|
||
|
|
:active-filter-popover="activeFilterPopover"
|
||
|
|
:label="selectedRiskLevelLabel"
|
||
|
|
:options="riskLevelOptions"
|
||
|
|
:selected-value="selectedRiskLevel"
|
||
|
|
@toggle="emit('toggle-filter-popover', $event)"
|
||
|
|
@close="emit('close-filter-popover')"
|
||
|
|
@select="selectFilter('riskLevel', $event)"
|
||
|
|
/>
|
||
|
|
|
||
|
|
<AuditPickerFilter
|
||
|
|
v-if="showRiskScenarioFilter"
|
||
|
|
id="riskScenario"
|
||
|
|
title="选择使用场景"
|
||
|
|
close-label="关闭使用场景选择"
|
||
|
|
:active-filter-popover="activeFilterPopover"
|
||
|
|
:label="selectedRiskScenarioLabel"
|
||
|
|
:options="riskScenarioOptions"
|
||
|
|
:selected-value="selectedRiskScenario"
|
||
|
|
@toggle="emit('toggle-filter-popover', $event)"
|
||
|
|
@close="emit('close-filter-popover')"
|
||
|
|
@select="selectFilter('riskScenario', $event)"
|
||
|
|
/>
|
||
|
|
|
||
|
|
<AuditPickerFilter
|
||
|
|
v-if="showOnlineFilter"
|
||
|
|
id="online"
|
||
|
|
title="选择上线状态"
|
||
|
|
close-label="关闭上线状态选择"
|
||
|
|
:active-filter-popover="activeFilterPopover"
|
||
|
|
:label="selectedOnlineStateLabel"
|
||
|
|
:options="onlineStateOptions"
|
||
|
|
:selected-value="selectedOnlineState"
|
||
|
|
@toggle="emit('toggle-filter-popover', $event)"
|
||
|
|
@close="emit('close-filter-popover')"
|
||
|
|
@select="selectFilter('online', $event)"
|
||
|
|
/>
|
||
|
|
|
||
|
|
<AuditPickerFilter
|
||
|
|
v-if="showEnabledFilter"
|
||
|
|
id="enabled"
|
||
|
|
title="选择启用状态"
|
||
|
|
close-label="关闭启用状态选择"
|
||
|
|
:active-filter-popover="activeFilterPopover"
|
||
|
|
:label="selectedEnabledStateLabel"
|
||
|
|
:options="enabledStateOptions"
|
||
|
|
:selected-value="selectedEnabledState"
|
||
|
|
@toggle="emit('toggle-filter-popover', $event)"
|
||
|
|
@close="emit('close-filter-popover')"
|
||
|
|
@select="selectFilter('enabled', $event)"
|
||
|
|
/>
|
||
|
|
|
||
|
|
<AuditPickerFilter
|
||
|
|
v-if="showStatusFilter"
|
||
|
|
id="status"
|
||
|
|
title="选择状态"
|
||
|
|
close-label="关闭状态选择"
|
||
|
|
:active-filter-popover="activeFilterPopover"
|
||
|
|
:label="selectedStatusLabel"
|
||
|
|
:options="statusOptions"
|
||
|
|
:selected-value="selectedStatus"
|
||
|
|
@toggle="emit('toggle-filter-popover', $event)"
|
||
|
|
@close="emit('close-filter-popover')"
|
||
|
|
@select="selectFilter('status', $event)"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="toolbar-actions">
|
||
|
|
<button
|
||
|
|
v-if="activeFilterTokens.length"
|
||
|
|
class="ghost-filter-btn"
|
||
|
|
type="button"
|
||
|
|
@click="emit('reset-filters')"
|
||
|
|
>
|
||
|
|
<i class="mdi mdi-filter-remove-outline"></i>
|
||
|
|
<span>清空筛选</span>
|
||
|
|
</button>
|
||
|
|
|
||
|
|
<button
|
||
|
|
class="create-btn"
|
||
|
|
type="button"
|
||
|
|
:disabled="!canCreateRiskRule"
|
||
|
|
@click="emit('create-risk-rule')"
|
||
|
|
>
|
||
|
|
<i class="mdi mdi-plus"></i>
|
||
|
|
<span>{{ createButtonLabel }}</span>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<p class="hint"><i class="mdi mdi-information-outline"></i> {{ hintText }}</p>
|
||
|
|
|
||
|
|
<div v-if="activeFilterTokens.length" class="active-filter-strip">
|
||
|
|
<span v-for="token in activeFilterTokens" :key="token" class="active-filter-chip">
|
||
|
|
{{ token }}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div
|
||
|
|
class="table-wrap"
|
||
|
|
:class="{ 'is-empty': !loading && !errorMessage && !visibleSkills.length }"
|
||
|
|
>
|
||
|
|
<div v-if="loading" class="table-state">
|
||
|
|
<TableLoadingState
|
||
|
|
variant="panel"
|
||
|
|
:title="`${activeTabLabel}资产同步中`"
|
||
|
|
:message="`正在加载${activeTabLabel}资产`"
|
||
|
|
icon="mdi mdi-view-list-outline"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div v-else-if="errorMessage" class="table-state error">
|
||
|
|
<i class="mdi mdi-alert-circle-outline"></i>
|
||
|
|
<p>{{ errorMessage }}</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<TableEmptyState
|
||
|
|
v-else-if="!visibleSkills.length"
|
||
|
|
:eyebrow="auditEmptyState.eyebrow"
|
||
|
|
:title="auditEmptyState.title"
|
||
|
|
:description="auditEmptyState.desc"
|
||
|
|
:icon="auditEmptyState.icon"
|
||
|
|
:action-label="auditEmptyState.actionLabel"
|
||
|
|
:action-icon="auditEmptyState.actionIcon"
|
||
|
|
:tone="auditEmptyState.tone"
|
||
|
|
:art-label="auditEmptyState.artLabel"
|
||
|
|
:tips="auditEmptyState.tips"
|
||
|
|
@action="emit('empty-action')"
|
||
|
|
/>
|
||
|
|
|
||
|
|
<table v-else>
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>{{ tableColumns.name }}</th>
|
||
|
|
<th>{{ tableColumns.category }}</th>
|
||
|
|
<th>{{ tableColumns.owner }}</th>
|
||
|
|
<th>{{ tableColumns.scope }}</th>
|
||
|
|
<th v-if="showRuntimeColumn">{{ tableColumns.runtime }}</th>
|
||
|
|
<th v-if="showVersionColumn">{{ tableColumns.version }}</th>
|
||
|
|
<th v-if="showStatusColumn">{{ tableColumns.status || '状态' }}</th>
|
||
|
|
<th v-if="showMetricColumn">{{ tableColumns.metric }}</th>
|
||
|
|
<th v-if="showOnlineColumn">是否上线</th>
|
||
|
|
<th v-if="showEnabledColumn">是否启用</th>
|
||
|
|
<th>{{ tableColumns.updatedAt || '最近更新' }}</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr
|
||
|
|
v-for="skill in visibleSkills"
|
||
|
|
:key="skill.id"
|
||
|
|
:class="{ 'is-disabled': skill.usesJsonRiskRule && skill.statusValue === 'generating' }"
|
||
|
|
@click="emit('open-asset-detail', skill)"
|
||
|
|
>
|
||
|
|
<td>
|
||
|
|
<div class="skill-name-cell">
|
||
|
|
<span class="skill-avatar" :class="skill.badgeTone">{{ skill.short }}</span>
|
||
|
|
<div>
|
||
|
|
<strong>{{ skill.name }}</strong>
|
||
|
|
<span class="skill-list-subtitle">{{ skill.listSubtitle || skill.summary }}</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
<td>{{ skill.category }}</td>
|
||
|
|
<td>
|
||
|
|
<span
|
||
|
|
v-if="skill.usesJsonRiskRule"
|
||
|
|
class="json-risk-meta-badge"
|
||
|
|
:class="skill.riskLevelTone"
|
||
|
|
>
|
||
|
|
{{ skill.riskLevelLabel || '-' }}
|
||
|
|
</span>
|
||
|
|
<template v-else>{{ skill.owner }}</template>
|
||
|
|
</td>
|
||
|
|
<td><span class="scope-pill">{{ skill.scope }}</span></td>
|
||
|
|
<td v-if="showRuntimeColumn">{{ skill.model }}</td>
|
||
|
|
<td v-if="showVersionColumn">{{ skill.versionDisplay || skill.version }}</td>
|
||
|
|
<td v-if="showStatusColumn">
|
||
|
|
<span class="status-pill" :class="skill.statusTone">{{ skill.status }}</span>
|
||
|
|
</td>
|
||
|
|
<td v-if="showMetricColumn">{{ skill.hitRate }}</td>
|
||
|
|
<td v-if="showOnlineColumn">
|
||
|
|
<span class="status-pill" :class="skill.isOnlineTone">{{ skill.isOnlineLabel }}</span>
|
||
|
|
</td>
|
||
|
|
<td v-if="showEnabledColumn">
|
||
|
|
<span class="status-pill" :class="skill.isEnabledTone">{{ skill.isEnabledLabel }}</span>
|
||
|
|
</td>
|
||
|
|
<td>{{ skill.updatedAt }}</td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<footer v-if="!loading && !errorMessage && visibleSkills.length" class="list-foot">
|
||
|
|
<span class="page-summary">当前展示 {{ visibleSkills.length }} 条资产</span>
|
||
|
|
</footer>
|
||
|
|
</article>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup>
|
||
|
|
import AuditPickerFilter from './AuditPickerFilter.vue'
|
||
|
|
import TableEmptyState from '../shared/TableEmptyState.vue'
|
||
|
|
import TableLoadingState from '../shared/TableLoadingState.vue'
|
||
|
|
|
||
|
|
defineOptions({
|
||
|
|
name: 'AuditAssetList'
|
||
|
|
})
|
||
|
|
|
||
|
|
defineProps({
|
||
|
|
tabs: { type: Array, default: () => [] },
|
||
|
|
activeType: { type: String, default: '' },
|
||
|
|
activeTabLabel: { type: String, default: '' },
|
||
|
|
keyword: { type: String, default: '' },
|
||
|
|
searchPlaceholder: { type: String, default: '' },
|
||
|
|
createButtonLabel: { type: String, default: '' },
|
||
|
|
hintText: { type: String, default: '' },
|
||
|
|
tableColumns: { type: Object, default: () => ({}) },
|
||
|
|
showRuntimeColumn: { type: Boolean, default: false },
|
||
|
|
showVersionColumn: { type: Boolean, default: false },
|
||
|
|
showMetricColumn: { type: Boolean, default: false },
|
||
|
|
showStatusColumn: { type: Boolean, default: false },
|
||
|
|
showOnlineColumn: { type: Boolean, default: false },
|
||
|
|
showEnabledColumn: { type: Boolean, default: false },
|
||
|
|
visibleSkills: { type: Array, default: () => [] },
|
||
|
|
auditEmptyState: { type: Object, default: () => ({}) },
|
||
|
|
loading: { type: Boolean, default: false },
|
||
|
|
errorMessage: { type: String, default: '' },
|
||
|
|
selectedDomain: { type: String, default: '' },
|
||
|
|
selectedOwner: { type: String, default: '' },
|
||
|
|
selectedRiskLevel: { type: String, default: '' },
|
||
|
|
selectedStatus: { type: String, default: '' },
|
||
|
|
selectedRiskScenario: { type: String, default: '' },
|
||
|
|
selectedOnlineState: { type: String, default: '' },
|
||
|
|
selectedEnabledState: { type: String, default: '' },
|
||
|
|
selectedDomainLabel: { type: String, default: '' },
|
||
|
|
selectedOwnerLabel: { type: String, default: '' },
|
||
|
|
selectedRiskLevelLabel: { type: String, default: '' },
|
||
|
|
selectedStatusLabel: { type: String, default: '' },
|
||
|
|
selectedRiskScenarioLabel: { type: String, default: '' },
|
||
|
|
selectedOnlineStateLabel: { type: String, default: '' },
|
||
|
|
selectedEnabledStateLabel: { type: String, default: '' },
|
||
|
|
showRiskScenarioFilter: { type: Boolean, default: false },
|
||
|
|
showOwnerFilter: { type: Boolean, default: false },
|
||
|
|
showRiskLevelFilter: { type: Boolean, default: false },
|
||
|
|
showStatusFilter: { type: Boolean, default: false },
|
||
|
|
showOnlineFilter: { type: Boolean, default: false },
|
||
|
|
showEnabledFilter: { type: Boolean, default: false },
|
||
|
|
domainOptions: { type: Array, default: () => [] },
|
||
|
|
ownerOptions: { type: Array, default: () => [] },
|
||
|
|
riskLevelOptions: { type: Array, default: () => [] },
|
||
|
|
statusOptions: { type: Array, default: () => [] },
|
||
|
|
riskScenarioOptions: { type: Array, default: () => [] },
|
||
|
|
onlineStateOptions: { type: Array, default: () => [] },
|
||
|
|
enabledStateOptions: { type: Array, default: () => [] },
|
||
|
|
activeFilterPopover: { type: String, default: '' },
|
||
|
|
activeFilterTokens: { type: Array, default: () => [] },
|
||
|
|
canCreateRiskRule: { type: Boolean, default: false }
|
||
|
|
})
|
||
|
|
|
||
|
|
const emit = defineEmits([
|
||
|
|
'update:activeType',
|
||
|
|
'update:keyword',
|
||
|
|
'toggle-filter-popover',
|
||
|
|
'close-filter-popover',
|
||
|
|
'select-filter',
|
||
|
|
'reset-filters',
|
||
|
|
'create-risk-rule',
|
||
|
|
'empty-action',
|
||
|
|
'open-asset-detail'
|
||
|
|
])
|
||
|
|
|
||
|
|
function selectFilter(type, value) {
|
||
|
|
emit('select-filter', type, value)
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style scoped src="../../assets/styles/views/audit-view.css"></style>
|
||
|
|
<style scoped src="../../assets/styles/views/audit-view-part2.css"></style>
|