- 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
113 lines
5.2 KiB
Vue
113 lines
5.2 KiB
Vue
<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>
|