Files
cma-management/frontend/src/views/Login.vue
T
Hermes CI Fix 40d522cd34 fix(security): retire default-credential surface (yanxue-20260914-cma-defaultcred-code-fix)
- Login.vue: no prefilled default creds; no anonymous entity enumeration on mount

- deploy.sh: readiness polling (0.5s/30s cap) replaces fixed sleep 3; login smoke reads same-source 600 secret file, missing file = explicit abort

- 19 verify/check/gen_report scripts: shared _admin_creds() loader (CMA_ADMIN_ENV -> ops .secrets -> profile copy); no hardcoded creds

- tests: single TEST_USER_PASSWORD fixture constant; docs: pointer to 600 secret file; removed 4 legacy .bak + pycache residue
2026-09-14 17:39:53 +08:00

113 lines
5.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="login-page">
<div class="login-card">
<h1>管理会计OS</h1>
<p class="subtitle">博海网络科技</p>
<el-form ref="formRef" :model="form" label-width="0" @keyup.enter="handleLogin">
<el-form-item>
<el-input v-model="form.username" placeholder="用户名" size="large" @input="onUsernameChange" />
</el-form-item>
<!-- 固定企业版(VITE_FIXED_ENTITY_ID):隐藏企业下拉,显示只读企业徽标 -->
<el-form-item v-if="!isFixedEntity()">
<el-select v-model="form.entity_id" placeholder="选择登录公司" size="large" style="width:100%" :disabled="entities.length === 0">
<el-option v-for="e in entities" :key="e.id" :label="e.short_name || e.name" :value="e.id">
<span>{{ e.short_name || e.name }}</span>
<span style="float:right;color:#999;font-size:12px;">{{ e.name }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item v-else>
<div class="fixed-entity-badge">
<span class="fe-label">登录企业</span>
<span class="fe-name">{{ fixedEntityLabel }}</span>
</div>
</el-form-item>
<el-form-item><el-input v-model="form.password" type="password" placeholder="密码" size="large" show-password /></el-form-item>
<el-form-item><el-button type="primary" size="large" style="width:100%" :loading="loading" @click="handleLogin">登 录</el-button></el-form-item>
</el-form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { authApi } from '../api/index'
import { isFixedEntity, getFixedEntityId } from '../utils/entity'
const router = useRouter()
const loading = ref(false)
const entities = ref<any[]>([])
// 凭据面收口(2026-09-14 cma-defaultcred-code-fix):不再预填默认口令。
// 演示包若确需"一键登录"便利,只能以构建期开关(如 VITE_DEMO_PREFILL,默认关闭)恢复,不在源码里留默认口令。
const form = reactive({ username: '', password: '', entity_id: undefined as number | undefined })
// ── 固定企业模式(Phase C):构建期锁定单一企业,隐藏下拉、登录直接带固定 entity_id ──
const fixedMode = isFixedEntity()
const fixedEntityId = getFixedEntityId()
const fixedEntityLabel = computed(() => {
if (fixedEntityId === undefined) return ''
const e = entities.value.find((x: any) => x.id === fixedEntityId)
if (e) return e.short_name || e.name
return `企业 #${fixedEntityId}`
})
async function loadEntities(username: string) {
try {
const r: any = await authApi.loginEntities(username)
entities.value = r.data || []
// 切换版:默认选中第一个授权企业;固定企业版:登录时带构建锁定值,不自动改选
if (!fixedMode && entities.value.length > 0 && (form.entity_id === undefined || !entities.value.some((e: any) => e.id === form.entity_id))) {
form.entity_id = entities.value[0].id
}
} catch (_) {
entities.value = []
}
}
function onUsernameChange(val: string) {
if (val && val.trim()) loadEntities(val.trim())
}
async function handleLogin() {
const entityId = fixedMode ? fixedEntityId : form.entity_id
if (entityId === undefined) {
ElMessage.warning('请选择登录公司')
return
}
loading.value = true
try {
const res: any = await authApi.login({ ...form, entity_id: Number(entityId) })
localStorage.setItem('cma_token', res.token)
localStorage.setItem('cma_user', JSON.stringify(res.user))
localStorage.setItem('cma_entity_id', String(res.entity_id ?? res.user?.entity_id ?? 1))
localStorage.setItem('cma_entity_name', res.entity_short_name || res.entity_name || '')
router.push('/my-dashboard')
} catch (e: any) {
ElMessage.error(e?.response?.data?.detail || '登录失败')
}
loading.value = false
}
// 凭据面收口:不在挂载时用硬编码用户名(原 'admin')触发企业枚举
// (login-entities 是公开端点,以固定用户名预取=给匿名访客做用户名存在性探测)。
// 企业列表仅在用户输入用户名时由 onUsernameChange 加载;固定企业版展示走 fixedEntityLabel 的"企业 #id"兜底。
</script>
<style scoped>
.login-page { display: flex; align-items: center; justify-content: center; height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
.login-card { background: #fff; padding: 40px; border-radius: 12px; width: 380px; text-align: center; }
.login-card h1 { margin: 0 0 4px; font-size: 24px; }
.subtitle { color: #999; margin-bottom: 24px; }
/* 固定企业版登录徽标 */
.fixed-entity-badge { display: flex; align-items: center; justify-content: center; gap: 10px; width: 100%; height: 40px; border: 1px dashed #c0c4cc; border-radius: 6px; background: #f8f9fc; padding: 0 12px; }
.fe-label { color: #909399; font-size: 13px; }
.fe-name { color: #303133; font-size: 14px; font-weight: 600; }
@media screen and (max-width: 768px) {
.login-page { padding: 20px; }
.login-card { width: 100%; max-width: 380px; padding: 30px 24px; }
}
</style>