[REQ-通用-006] 预生产测试通道:三端测试登录入口 + 文档链 + 部署脚本
- 三端 Login.vue(坐席/管理/H5)新增「测试账号登录」面板:探测 /api/dev/health 决定可见性,公网 403 自动隐藏,免企微扫码登录(token 写入对应 localStorage 键) - 新增 REQ-通用-006 文档链五件套:PRD / 技术方案 / 任务说明书 / 测试用例 / 部署方案(product-doc-standard 规范) - 版本迭代总览追加 v5.1(预生产测试通道)行 - 部署辅助脚本:nginx /api/dev/ 内网闸门注入、H5 版本化 v20260808→v20260811 升级 部署已落地预生产(10.90.5.110):公网 /api/dev/* 403、内网 200、三端登录页新 hash 在线。
This commit is contained in:
@@ -46,6 +46,18 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 测试账号登录(REQ-通用-006 预生产测试通道,仅内网探测成功时显示) -->
|
||||
<div v-if="showTestPanel" class="test-login">
|
||||
<el-divider><span class="test-login-divider-text">测试账号登录</span></el-divider>
|
||||
<div class="test-login-row">
|
||||
<el-select v-model="testUser" size="small" placeholder="选择测试角色" style="width: 180px">
|
||||
<el-option v-for="u in testUsers" :key="u.userid" :label="u.label" :value="u.userid" />
|
||||
</el-select>
|
||||
<el-button type="primary" size="small" :loading="testLogining" @click="testLogin">免企微登录</el-button>
|
||||
</div>
|
||||
<p class="test-login-hint">仅内网可用,用于自动化测试</p>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-container">
|
||||
<el-icon class="is-loading" size="32"><Loading /></el-icon>
|
||||
@@ -94,6 +106,26 @@ let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
/** 当前扫码登录票据 */
|
||||
let currentTicket = ''
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 测试通道状态(REQ-通用-006:/api/dev/* 仅内网可用,公网 403 则隐藏)
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/** 是否显示测试账号登录面板 */
|
||||
const showTestPanel = ref(false)
|
||||
|
||||
/** 测试登录中 */
|
||||
const testLogining = ref(false)
|
||||
|
||||
/** 选中的测试账号 userid */
|
||||
const testUser = ref('dev-admin-001')
|
||||
|
||||
/** 预设测试账号(与后端 PRESET_DEV_USERS 对应) */
|
||||
const testUsers = [
|
||||
{ userid: 'dev-user-001', label: '员工(user)', role: 'user' },
|
||||
{ userid: 'dev-agent-001', label: '坐席(agent)', role: 'agent' },
|
||||
{ userid: 'dev-admin-001', label: '管理员(admin)', role: 'admin' },
|
||||
]
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 方法
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -293,11 +325,58 @@ async function redirectToOAuth(): Promise<void> {
|
||||
window.location.href = oauthUrl
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 测试通道方法(REQ-通用-006)
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 探测测试通道可用性(内网 200 → 显示测试入口;公网 403 → 隐藏)
|
||||
*/
|
||||
async function checkTestChannel(): Promise<void> {
|
||||
try {
|
||||
const apiClient = (await import('@/api/index')).default
|
||||
const resp = await apiClient.get('/dev/health', { timeout: 3000 })
|
||||
if (resp?.data?.data?.dev_mode) {
|
||||
showTestPanel.value = true
|
||||
}
|
||||
} catch {
|
||||
showTestPanel.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试账号免企微登录(仅内网):/api/dev/login → token 写入 admin_token
|
||||
*/
|
||||
async function testLogin(): Promise<void> {
|
||||
const target = testUsers.find((u) => u.userid === testUser.value)
|
||||
if (!target) return
|
||||
|
||||
testLogining.value = true
|
||||
try {
|
||||
const apiClient = (await import('@/api/index')).default
|
||||
const resp = await apiClient.get('/dev/login', {
|
||||
params: { userid: target.userid, name: target.label, role: target.role },
|
||||
})
|
||||
await handleLoginSuccess(resp.data.data.token, {
|
||||
userid: target.userid,
|
||||
name: target.label,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('测试登录失败:', error)
|
||||
ElMessage.error('测试登录失败,请确认内网环境')
|
||||
} finally {
|
||||
testLogining.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 生命周期
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
onMounted(async () => {
|
||||
// 探测测试通道(不阻塞主登录流程)
|
||||
checkTestChannel()
|
||||
|
||||
// 检查 URL 中的 code 参数(OAuth2 回调)
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const code = urlParams.get('code')
|
||||
@@ -467,4 +546,28 @@ onUnmounted(() => {
|
||||
padding: 40px 0;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 测试账号登录(REQ-通用-006) */
|
||||
.test-login {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.test-login-divider-text {
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.test-login-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.test-login-hint {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -45,6 +45,18 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 测试账号登录(REQ-通用-006 预生产测试通道,仅内网探测成功时显示) -->
|
||||
<div v-if="showTestPanel" class="test-login">
|
||||
<el-divider><span class="test-login-divider-text">测试账号登录</span></el-divider>
|
||||
<div class="test-login-row">
|
||||
<el-select v-model="testUser" size="small" placeholder="选择测试角色" style="width: 180px">
|
||||
<el-option v-for="u in testUsers" :key="u.userid" :label="u.label" :value="u.userid" />
|
||||
</el-select>
|
||||
<el-button type="primary" size="small" :loading="testLogining" @click="testLogin">免企微登录</el-button>
|
||||
</div>
|
||||
<p class="test-login-hint">仅内网可用,用于自动化测试</p>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-container">
|
||||
<el-icon class="is-loading" size="32"><Loading /></el-icon>
|
||||
@@ -96,6 +108,26 @@ let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
/** 当前扫码登录票据 */
|
||||
let currentTicket = ''
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 测试通道状态(REQ-通用-006:/api/dev/* 仅内网可用,公网 403 则隐藏)
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/** 是否显示测试账号登录面板 */
|
||||
const showTestPanel = ref(false)
|
||||
|
||||
/** 测试登录中 */
|
||||
const testLogining = ref(false)
|
||||
|
||||
/** 选中的测试账号 userid */
|
||||
const testUser = ref('dev-agent-001')
|
||||
|
||||
/** 预设测试账号(与后端 PRESET_DEV_USERS 对应) */
|
||||
const testUsers = [
|
||||
{ userid: 'dev-user-001', label: '员工(user)', role: 'user' },
|
||||
{ userid: 'dev-agent-001', label: '坐席(agent)', role: 'agent' },
|
||||
{ userid: 'dev-admin-001', label: '管理员(admin)', role: 'admin' },
|
||||
]
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 方法
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -291,11 +323,58 @@ async function redirectToOAuth(): Promise<void> {
|
||||
window.location.href = oauthUrl
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 测试通道方法(REQ-通用-006)
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 探测测试通道可用性(内网 200 → 显示测试入口;公网 403 → 隐藏)
|
||||
*/
|
||||
async function checkTestChannel(): Promise<void> {
|
||||
try {
|
||||
const apiClient = (await import('@/api/index')).default
|
||||
const resp = await apiClient.get('/dev/health', { timeout: 3000 })
|
||||
if (resp?.data?.data?.dev_mode) {
|
||||
showTestPanel.value = true
|
||||
}
|
||||
} catch {
|
||||
showTestPanel.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试账号免企微登录(仅内网):/api/dev/login → token 写入 agent_token
|
||||
*/
|
||||
async function testLogin(): Promise<void> {
|
||||
const target = testUsers.find((u) => u.userid === testUser.value)
|
||||
if (!target) return
|
||||
|
||||
testLogining.value = true
|
||||
try {
|
||||
const apiClient = (await import('@/api/index')).default
|
||||
const resp = await apiClient.get('/dev/login', {
|
||||
params: { userid: target.userid, name: target.label, role: target.role },
|
||||
})
|
||||
await handleLoginSuccess(resp.data.data.token, {
|
||||
userid: target.userid,
|
||||
name: target.label,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('测试登录失败:', error)
|
||||
ElMessage.error('测试登录失败,请确认内网环境')
|
||||
} finally {
|
||||
testLogining.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 生命周期
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
onMounted(async () => {
|
||||
// 探测测试通道(不阻塞主登录流程)
|
||||
checkTestChannel()
|
||||
|
||||
// 检查 URL 中的 code 参数(OAuth2 回调)
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const code = urlParams.get('code')
|
||||
@@ -471,6 +550,30 @@ onUnmounted(() => {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
/* 测试账号登录(REQ-通用-006) */
|
||||
.test-login {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.test-login-divider-text {
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary, #909399);
|
||||
}
|
||||
|
||||
.test-login-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.test-login-hint {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary, #909399);
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
|
||||
@@ -40,6 +40,18 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 测试账号登录(REQ-通用-006 预生产测试通道,仅内网探测成功时显示) -->
|
||||
<div v-if="showTestPanel" class="test-login">
|
||||
<div class="test-login-title">测试账号登录</div>
|
||||
<div class="test-login-row">
|
||||
<select v-model="testUser" class="test-login-select">
|
||||
<option v-for="u in testUsers" :key="u.userid" :value="u.userid">{{ u.label }}</option>
|
||||
</select>
|
||||
<van-button type="primary" size="small" :loading="testLogining" @click="testLogin">免企微登录</van-button>
|
||||
</div>
|
||||
<p class="test-login-hint">仅内网可用,用于自动化测试</p>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-container">
|
||||
<van-loading type="spinner" size="40px" />
|
||||
@@ -87,6 +99,26 @@ let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
/** 当前扫码登录票据 */
|
||||
let currentTicket = ''
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 测试通道状态(REQ-通用-006:/api/dev/* 仅内网可用,公网 403 则隐藏)
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/** 是否显示测试账号登录面板 */
|
||||
const showTestPanel = ref(false)
|
||||
|
||||
/** 测试登录中 */
|
||||
const testLogining = ref(false)
|
||||
|
||||
/** 选中的测试账号 userid */
|
||||
const testUser = ref('dev-user-001')
|
||||
|
||||
/** 预设测试账号(与后端 PRESET_DEV_USERS 对应) */
|
||||
const testUsers = [
|
||||
{ userid: 'dev-user-001', label: '员工(user)', role: 'user' },
|
||||
{ userid: 'dev-agent-001', label: '坐席(agent)', role: 'agent' },
|
||||
{ userid: 'dev-admin-001', label: '管理员(admin)', role: 'admin' },
|
||||
]
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 方法
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -251,11 +283,58 @@ async function processOAuthCallback(code: string, state?: string): Promise<void>
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 测试通道方法(REQ-通用-006)
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 探测测试通道可用性(内网 200 → 显示测试入口;公网 403 → 隐藏)
|
||||
*/
|
||||
async function checkTestChannel(): Promise<void> {
|
||||
try {
|
||||
const apiClient = (await import('@/api/index')).default
|
||||
const resp = await apiClient.get('/dev/health', { timeout: 3000 })
|
||||
if (resp?.data?.data?.dev_mode) {
|
||||
showTestPanel.value = true
|
||||
}
|
||||
} catch {
|
||||
showTestPanel.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试账号免企微登录(仅内网):/api/dev/login → token 写入 h5_token
|
||||
*/
|
||||
async function testLogin(): Promise<void> {
|
||||
const target = testUsers.find((u) => u.userid === testUser.value)
|
||||
if (!target) return
|
||||
|
||||
testLogining.value = true
|
||||
try {
|
||||
const apiClient = (await import('@/api/index')).default
|
||||
const resp = await apiClient.get('/dev/login', {
|
||||
params: { userid: target.userid, name: target.label, role: target.role },
|
||||
})
|
||||
await handleLoginSuccess(resp.data.data.token, {
|
||||
userid: target.userid,
|
||||
name: target.label,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('测试登录失败:', error)
|
||||
showToast('测试登录失败,请确认内网环境')
|
||||
} finally {
|
||||
testLogining.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 生命周期
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
onMounted(async () => {
|
||||
// 探测测试通道(不阻塞主登录流程)
|
||||
checkTestChannel()
|
||||
|
||||
// 检查 URL 中的 code 参数(OAuth2 回调)
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const code = urlParams.get('code')
|
||||
@@ -421,4 +500,40 @@ onUnmounted(() => {
|
||||
padding: 40px 0;
|
||||
color: var(--text-secondary, #606266);
|
||||
}
|
||||
|
||||
/* 测试账号登录(REQ-通用-006) */
|
||||
.test-login {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.test-login-title {
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary, #606266);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.test-login-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.test-login-select {
|
||||
height: 30px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 8px;
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.test-login-hint {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary, #909399);
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user