feat(ui): finalize shared shells and loading states

This commit is contained in:
caoxiaozhu
2026-05-29 13:17:39 +08:00
parent 64cc76c970
commit e080105f9f
52 changed files with 1559 additions and 861 deletions

View File

@@ -177,12 +177,6 @@ export default {
() =>
normalizeText(selectedSkill.value?.ruleDocument?.file_name) || '未上传规则表'
)
const selectedSpreadsheetModeLabel = computed(() => {
if (selectedSkill.value?.isPreviewMock) {
return canEditSpreadsheetInline.value ? '可编辑' : '只读'
}
return canEditSpreadsheetInline.value ? '在线可编辑' : '只读'
})
const {
versionSwitchTarget,
versionTimelineOpen,
@@ -438,11 +432,7 @@ export default {
const auditDetailTopBar = computed(() =>
buildAuditDetailTopBar({
skill: selectedSkill.value,
usesJsonRiskRule: selectedSkillUsesJsonRisk.value,
usesSpreadsheetRule: selectedSkillUsesSpreadsheet.value,
spreadsheetModeLabel: selectedSpreadsheetModeLabel.value,
spreadsheetFileName: selectedSpreadsheetFileName.value,
canEditSpreadsheetInline: canEditSpreadsheetInline.value
usesJsonRiskRule: selectedSkillUsesJsonRisk.value
})
)
@@ -711,7 +701,6 @@ export default {
selectedSkillUsesSpreadsheet,
selectedSkillUsesJsonRisk,
selectedSpreadsheetFileName,
selectedSpreadsheetModeLabel,
selectedVersionTimelineItems,
selectedSpreadsheetChangeRecords,
detailBusy,

View File

@@ -3,6 +3,7 @@ import { ElButton, ElInput, ElPagination, ElTable, ElTableColumn } from 'element
import BudgetTrendChart from '../../components/charts/BudgetTrendChart.vue'
import EnterpriseSelect from '../../components/shared/EnterpriseSelect.vue'
import TableLoadingState from '../../components/shared/TableLoadingState.vue'
import { fetchBudgetSummary } from '../../services/budgets.js'
import { fetchEmployeeMeta } from '../../services/employees.js'
import {
@@ -217,6 +218,7 @@ export default {
components: {
BudgetTrendChart,
EnterpriseSelect,
TableLoadingState,
ElButton,
ElInput,
ElPagination,
@@ -238,7 +240,7 @@ export default {
const budgetTableKeyword = ref('')
const budgetRows = ref([])
const budgetSummary = ref(null)
const budgetLoading = ref(false)
const budgetLoading = ref(true)
const budgetError = ref('')
const canEditBudget = computed(() => canEditBudgetCenter(props.currentUser))
const canSwitchDepartments = computed(() => canSwitchBudgetDepartments(props.currentUser))
@@ -424,6 +426,7 @@ export default {
}
async function loadDepartments() {
budgetLoading.value = true
try {
const payload = await fetchEmployeeMeta()
const options = Array.isArray(payload?.organizationOptions) ? payload.organizationOptions : []

View File

@@ -5,8 +5,12 @@ import EnterpriseListPage from '../../components/shared/EnterpriseListPage.vue'
import { useSystemState } from '../../composables/useSystemState.js'
import { useToast } from '../../composables/useToast.js'
import { fetchSystemLogEntries } from '../../services/systemLogs.js'
import { AGENT_RUN_POLL_INTERVAL_MS } from '../../utils/agentRunMonitor.js'
import { isManagerUser } from '../../utils/accessControl.js'
import {
DEFAULT_REFRESH_INTERVAL_MS,
REFRESH_INTERVAL_OPTIONS,
formatRefreshInterval
} from '../../utils/refreshIntervalOptions.js'
function formatDateTime(value) {
if (!value) {
@@ -79,6 +83,8 @@ export default {
const pageSize = ref(10)
const pageSizes = [10, 20, 50]
const pageSizeOptions = pageSizes.map((size) => ({ label: `${size} 条/页`, value: size }))
const refreshInterval = ref(DEFAULT_REFRESH_INTERVAL_MS)
const refreshIntervalOptions = REFRESH_INTERVAL_OPTIONS
let pollTimer = 0
const isAdmin = computed(() => isManagerUser(currentUser.value))
@@ -102,6 +108,7 @@ export default {
const systemEventTypeFilterLabel = computed(() =>
systemEventTypeFilterOptions.value.find((item) => item.value === systemEventTypeFilter.value)?.label || '全部类型'
)
const refreshIntervalLabel = computed(() => formatRefreshInterval(refreshInterval.value))
const hasActiveFilters = computed(() =>
Boolean(systemSearchKeyword.value.trim() || systemLevelFilter.value || systemEventTypeFilter.value)
)
@@ -175,6 +182,12 @@ export default {
openFilterKey.value = ''
}
function changeRefreshInterval(value) {
refreshInterval.value = Number(value) || DEFAULT_REFRESH_INTERVAL_MS
openFilterKey.value = ''
startPolling()
}
function resetFilters() {
systemSearchKeyword.value = ''
systemLevelFilter.value = ''
@@ -211,7 +224,7 @@ export default {
stopPolling()
pollTimer = window.setInterval(() => {
loadSystemLogs(false)
}, AGENT_RUN_POLL_INTERVAL_MS)
}, refreshInterval.value)
}
function stopPolling() {
@@ -253,6 +266,7 @@ export default {
return {
changePageSize,
changeRefreshInterval,
currentPage,
filteredSystemLogEntries,
formatDateTime,
@@ -263,6 +277,9 @@ export default {
openFilterKey,
pageSize,
pageSizeOptions,
refreshInterval,
refreshIntervalLabel,
refreshIntervalOptions,
resetFilters,
resolveSystemLevelTone,
resolveSystemOutcomeTone,

View File

@@ -1,5 +1,7 @@
import HermesEmployeeSettingsPanel from '../HermesEmployeeSettingsPanel.vue'
import LlmSettingsPanel from '../LlmSettingsPanel.vue'
import LogDetailView from '../LogDetailView.vue'
import LogsView from '../LogsView.vue'
import MailSettingsPanel from '../MailSettingsPanel.vue'
import EnterpriseSelect from '../../components/shared/EnterpriseSelect.vue'
import { useSettings } from '../../composables/useSettings.js'
@@ -10,6 +12,8 @@ export default {
HermesEmployeeSettingsPanel,
EnterpriseSelect,
LlmSettingsPanel,
LogDetailView,
LogsView,
MailSettingsPanel
},
setup() {

View File

@@ -10,11 +10,7 @@ function resolveRiskScoreCardColor(level) {
export function buildAuditDetailTopBar({
skill,
usesJsonRiskRule = false,
usesSpreadsheetRule = false,
spreadsheetModeLabel = '',
spreadsheetFileName = '',
canEditSpreadsheetInline = false
usesJsonRiskRule = false
} = {}) {
if (!skill) return null
@@ -39,15 +35,6 @@ export function buildAuditDetailTopBar({
: 'up',
color: resolveRiskScoreCardColor(scoreLevel)
})
} else if (usesSpreadsheetRule) {
kpis.push({
label: '编辑模式',
value: spreadsheetModeLabel,
unit: '',
meta: spreadsheetFileName,
trend: canEditSpreadsheetInline ? 'up' : 'down',
color: canEditSpreadsheetInline ? 'var(--success)' : '#64748b'
})
}
return {

View File

@@ -7,7 +7,10 @@ import {
VERSION_STATE_META
} from './auditViewMetadata.js'
import {
formatRiskRuleAge,
resolveRiskRuleFlow,
resolveRiskRuleScore,
resolveRiskRuleScoreDetail,
resolveRiskRuleScoreLabel,
resolveRiskRuleScoreLevel,
resolveRiskRuleSeverity,
@@ -71,6 +74,7 @@ import {
applyRiskRuleJsonState,
resolveRiskRuleBusinessStage,
resolveRiskRuleEnabled,
resolveLastOperationLabel,
resolveRiskRuleOnlineMeta
} from './auditViewRiskRuleState.js'
import {

View File

@@ -20,6 +20,7 @@ import {
} from './auditViewDataUtils.js'
import { formatDateTime } from './auditViewFormatters.js'
import {
buildRiskListSubtitle,
resolveRiskRuleCategory,
resolveRiskRuleDescription,
resolveRiskRuleSourceRef
@@ -83,7 +84,7 @@ export function resolveRiskRuleOnlineMeta(statusValue) {
return { label: '待上线', tone: 'draft', online: false }
}
function resolveLastOperationLabel(source, fallback = {}) {
export function resolveLastOperationLabel(source, fallback = {}) {
const configJson = readConfigJson(source)
const operation = isPlainObject(configJson.last_operation) ? configJson.last_operation : {}
const action = normalizeText(operation.action) || normalizeText(fallback.action) || 'create'
@@ -129,9 +130,12 @@ export function applyRiskRuleJsonState(target, payload, apiPayload) {
let publishedAt = target.publishedAt || '-'
if (apiPayload?.recent_versions) {
const history = buildHistory(apiPayload.recent_versions, { ...target, config_json: payload })
const publishedVersionObj = history.find((item) => item.isPublished || item.lifecycleState === 'published')
publishedAt = publishedVersionObj ? publishedVersionObj.time : (apiPayload?.latest_review?.reviewed_at ? formatDateTime(apiPayload.latest_review.reviewed_at) : '-')
const publishedVersionObj = apiPayload.recent_versions.find((item) =>
item?.is_current || item?.version === apiPayload?.published_version
)
publishedAt = publishedVersionObj?.created_at
? formatDateTime(publishedVersionObj.created_at)
: (apiPayload?.latest_review?.reviewed_at ? formatDateTime(apiPayload.latest_review.reviewed_at) : '-')
} else if (apiPayload?.latest_review?.reviewed_at) {
publishedAt = formatDateTime(apiPayload.latest_review.reviewed_at)
}