feat(web): update views and services

Views:
- AppShellRouteView.vue: update route view
- SettingsView.vue: update settings view
- TravelReimbursementCreateView.vue: update travel form view
- scripts/SettingsView.js: update settings view logic
- scripts/TravelReimbursementCreateView.js: update travel form logic

Services:
- services/orchestrator.js: update orchestrator service client
This commit is contained in:
caoxiaozhu
2026-05-12 06:40:19 +00:00
parent f6a5eeb620
commit c263fc9752
6 changed files with 561 additions and 89 deletions

View File

@@ -1,4 +1,4 @@
import { computed, onMounted, ref } from 'vue'
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { useSystemState } from '../../composables/useSystemState.js'
import { fetchSettings, saveSettings, testModelConnectivity } from '../../services/settings.js'
@@ -19,14 +19,22 @@ const SECTION_DEFINITIONS = [
longDesc: '统一维护企业名称、系统显示名称和版权信息,保存后会同步更新当前系统品牌展示。',
actionLabel: '保存企业信息'
},
{
id: 'admin',
label: '管理员安全',
title: '管理员账号与安全策略',
desc: '账号、密码与登录安全',
longDesc: '集中管理管理员账号、邮箱和登录安全策略。密码仅在当前输入时可见,不会写入浏览器草稿。',
actionLabel: '保存安全设置'
},
{
id: 'admin',
label: '管理员安全',
title: '管理员账号与安全策略',
desc: '账号、密码与登录安全',
longDesc: '集中管理管理员账号、邮箱和登录安全策略。密码仅在当前输入时可见,不会写入浏览器草稿。',
actionLabel: '保存安全设置'
},
{
id: 'session',
label: '会话设置',
title: '会话留存设置',
desc: '会话保留天数',
longDesc: '统一配置智能体会话的保留天数。超过保留期的历史会话会在后端清理,避免上下文和管理成本无限增长。',
actionLabel: '保存会话设置'
},
{
id: 'llm',
label: '大语言模型',
@@ -127,7 +135,11 @@ const MODEL_TEST_CONFIGS = {
}
}
const MODEL_API_KEY_CONFIGS = Object.values(MODEL_TEST_CONFIGS)
const MODEL_API_KEY_CONFIGS = Object.values(MODEL_TEST_CONFIGS)
const SESSION_RETENTION_OPTIONS = Array.from({ length: 10 }, (_item, index) => ({
value: index + 1,
label: `${index + 1}`
}))
function normalizeValue(value) {
return String(value ?? '').trim()
@@ -168,18 +180,21 @@ function buildDefaultState(companyProfile, currentUser) {
recordNumber: '',
copyright: `Copyright © 2024-${CURRENT_YEAR} ${companyName}. All Rights Reserved.`
},
adminForm: {
adminAccount,
adminEmail,
newPassword: '',
confirmPassword: '',
adminForm: {
adminAccount,
adminEmail,
newPassword: '',
confirmPassword: '',
sessionTimeout: Number(import.meta.env.VITE_AUTH_IDLE_TIMEOUT_MINUTES || 30),
noticeEmail: adminEmail,
mfaEnabled: true,
strongPassword: true,
loginAlertEnabled: true,
adminPasswordConfigured: false
},
strongPassword: true,
loginAlertEnabled: true,
adminPasswordConfigured: false
},
sessionForm: {
conversationRetentionDays: 3
},
llmForm: {
mainProvider: 'Codex',
mainModel: 'codex-mini-latest',
@@ -266,6 +281,7 @@ function mergeState(baseState, overrideState) {
return {
companyForm: { ...baseState.companyForm, ...(overrideState?.companyForm || {}) },
adminForm: { ...baseState.adminForm, ...(overrideState?.adminForm || {}) },
sessionForm: { ...baseState.sessionForm, ...(overrideState?.sessionForm || {}) },
llmForm: mergedLlmForm,
renderForm: { ...baseState.renderForm, ...(overrideState?.renderForm || {}) },
logForm: { ...baseState.logForm, ...(overrideState?.logForm || {}) },
@@ -276,11 +292,12 @@ function mergeState(baseState, overrideState) {
function sanitizeForStorage(state) {
return {
companyForm: { ...state.companyForm },
adminForm: {
...state.adminForm,
newPassword: '',
confirmPassword: ''
},
adminForm: {
...state.adminForm,
newPassword: '',
confirmPassword: ''
},
sessionForm: { ...state.sessionForm },
llmForm: {
...state.llmForm,
mainApiKey: '',
@@ -373,11 +390,15 @@ function computeSectionStatus(state) {
normalizeValue(state.companyForm.displayName) &&
normalizeValue(state.companyForm.copyright)
),
admin: Boolean(
normalizeValue(state.adminForm.adminAccount) &&
normalizeValue(state.adminForm.adminEmail) &&
Number(state.adminForm.sessionTimeout) >= 5
),
admin: Boolean(
normalizeValue(state.adminForm.adminAccount) &&
normalizeValue(state.adminForm.adminEmail) &&
Number(state.adminForm.sessionTimeout) >= 5
),
session: Boolean(
Number(state.sessionForm.conversationRetentionDays) >= 1 &&
Number(state.sessionForm.conversationRetentionDays) <= 10
),
llm: Boolean(
isModelConfigReady(state.llmForm.mainProvider, state.llmForm.mainModel, state.llmForm.mainEndpoint) &&
isModelConfigReady(state.llmForm.backupProvider, state.llmForm.backupModel, state.llmForm.backupEndpoint) &&
@@ -414,9 +435,11 @@ export default {
const { companyProfile, currentUser, updateCompanyProfilePreview } = useSystemState()
const buildResolvedDefaults = () => buildDefaultState(companyProfile.value, currentUser.value)
const pageState = ref(mergeState(buildResolvedDefaults(), readStoredSettings()))
const activeSection = ref('profile')
const modelTestState = ref({
const pageState = ref(mergeState(buildResolvedDefaults(), readStoredSettings()))
const activeSection = ref('profile')
const sessionRetentionPickerOpen = ref(false)
const sessionRetentionPickerRef = ref(null)
const modelTestState = ref({
main: { status: 'idle', message: '' },
backup: { status: 'idle', message: '' },
vlm: { status: 'idle', message: '' },
@@ -424,8 +447,9 @@ export default {
})
const sections = SECTION_DEFINITIONS
const logLevels = LOG_LEVELS
const providerOptions = PROVIDER_OPTIONS
const logLevels = LOG_LEVELS
const providerOptions = PROVIDER_OPTIONS
const sessionRetentionOptions = SESSION_RETENTION_OPTIONS
const sectionStatus = computed(() => computeSectionStatus(pageState.value))
const completedSectionCount = computed(() => Object.values(sectionStatus.value).filter(Boolean).length)
@@ -497,6 +521,7 @@ export default {
return {
companyForm: { ...pageState.value.companyForm },
adminForm: { ...pageState.value.adminForm },
sessionForm: { ...pageState.value.sessionForm },
llmForm: buildLlmPayload(pageState.value.llmForm),
renderForm: buildRenderPayload(pageState.value.renderForm),
logForm: { ...pageState.value.logForm },
@@ -504,7 +529,7 @@ export default {
}
}
async function persistRemoteSettings(successMessage, options = {}) {
async function persistRemoteSettings(successMessage, options = {}) {
try {
const snapshot = await saveSettings(buildSettingsPayload())
applyLoadedSnapshot(snapshot, options)
@@ -516,13 +541,40 @@ export default {
}
}
function activateSection(sectionId) {
activeSection.value = sectionId
}
function activateSection(sectionId) {
sessionRetentionPickerOpen.value = false
activeSection.value = sectionId
}
function toggleBoolean(formKey, field) {
pageState.value[formKey][field] = !pageState.value[formKey][field]
}
function toggleBoolean(formKey, field) {
pageState.value[formKey][field] = !pageState.value[formKey][field]
}
function toggleSessionRetentionPicker() {
sessionRetentionPickerOpen.value = !sessionRetentionPickerOpen.value
}
function closeSessionRetentionPicker() {
sessionRetentionPickerOpen.value = false
}
function selectSessionRetentionDays(value) {
pageState.value.sessionForm.conversationRetentionDays = Number(value)
closeSessionRetentionPicker()
}
function handleDocumentPointerDown(event) {
if (!sessionRetentionPickerOpen.value) {
return
}
const target = event.target
if (sessionRetentionPickerRef.value?.contains(target)) {
return
}
closeSessionRetentionPicker()
}
function applyProviderPreset(testKey) {
const config = MODEL_TEST_CONFIGS[testKey]
@@ -626,8 +678,8 @@ export default {
})
}
async function saveAdminSection() {
const adminForm = pageState.value.adminForm
async function saveAdminSection() {
const adminForm = pageState.value.adminForm
if (!normalizeValue(adminForm.adminAccount)) {
toast('请输入管理员账号。')
@@ -662,7 +714,24 @@ export default {
preserveRenderSecret: true,
preserveMailPassword: true
})
}
}
async function saveSessionSection() {
const sessionForm = pageState.value.sessionForm
const retentionDays = Number(sessionForm.conversationRetentionDays)
if (retentionDays < 1 || retentionDays > 10) {
toast('会话保留天数必须在 1 到 10 天之间。')
return
}
await persistRemoteSettings('会话设置已保存。', {
preserveModelApiKeys: true,
preserveAdminPasswords: true,
preserveRenderSecret: true,
preserveMailPassword: true
})
}
async function saveLlmSection() {
const llmForm = pageState.value.llmForm
@@ -757,15 +826,20 @@ export default {
return
}
if (activeSection.value === 'admin') {
await saveAdminSection()
return
}
if (activeSection.value === 'llm') {
await saveLlmSection()
return
}
if (activeSection.value === 'admin') {
await saveAdminSection()
return
}
if (activeSection.value === 'session') {
await saveSessionSection()
return
}
if (activeSection.value === 'llm') {
await saveLlmSection()
return
}
if (activeSection.value === 'logs') {
await saveLogsSection()
@@ -780,14 +854,23 @@ export default {
await saveMailSection()
}
onMounted(() => {
loadSettingsSnapshot()
})
return {
activeSection,
activeSectionConfig,
activateSection,
onMounted(() => {
if (typeof document !== 'undefined') {
document.addEventListener('pointerdown', handleDocumentPointerDown)
}
loadSettingsSnapshot()
})
onBeforeUnmount(() => {
if (typeof document !== 'undefined') {
document.removeEventListener('pointerdown', handleDocumentPointerDown)
}
})
return {
activeSection,
activeSectionConfig,
activateSection,
applyProviderPreset,
clearRenderSecretMask,
clearModelSecretMask,
@@ -795,14 +878,20 @@ export default {
getModelTestState,
isModelTesting,
logLevels,
modelTestState,
pageState,
providerOptions,
saveActiveSection,
sectionStatus,
sections,
testModelConnection,
toggleBoolean
}
}
}
modelTestState,
pageState,
providerOptions,
sessionRetentionOptions,
sessionRetentionPickerOpen,
sessionRetentionPickerRef,
saveActiveSection,
sectionStatus,
sections,
selectSessionRetentionDays,
testModelConnection,
toggleSessionRetentionPicker,
closeSessionRetentionPicker,
toggleBoolean
}
}
}