feat: 登录页密码+验证码双模式 / 首页岗位优化 / 法律页面 / 后端接口完善

- 前端:登录页重构,支持密码登录、验证码登录、注册三种模式
- 前端:首页热门岗位添加「参考示例」标签,去虚构数据
- 前端:面试页顶部优化,岗位名+状态标签展示
- 前端:新增用户协议、隐私政策页面及免责声明
- 后端:新增 POST /api/user/register 注册接口
- 后端:新增 POST /api/user/set-password 设置密码接口
- 后端:修复 user.schema.ts unique 索引导致 null 冲突问题
- 后端:新增 payment-order.schema、positions.schema、site-config.schema
- 后端:package.json 新增 postbuild 脚本自动复制证书
- 管理后台:新增订单管理 Tab
This commit is contained in:
yuzhiran
2026-06-09 15:39:17 +08:00
parent 511f60d0db
commit 37cfdfe93c
27 changed files with 1045 additions and 195 deletions
+18 -4
View File
@@ -5,6 +5,10 @@
<view class="topbar-inner">
<view class="back-btn" @click="confirmExit"><text class="back-arrow"></text></view>
<view class="topbar-center">
<view class="topbar-pos-row">
<text class="topbar-position">{{ position || 'AI面试' }}</text>
<text class="topbar-status">面试中</text>
</view>
<view class="progress-track" v-if="interviewId">
<view class="progress-fill" :style="{ width: progressPercent + '%' }"></view>
</view>
@@ -43,6 +47,8 @@
<text class="send-icon"></text>
</view>
</view>
<!-- AI 免责提示 -->
<view class="disclaimer-bar" v-if="!isComplete">AI 生成内容仅供参考请核实重要信息</view>
<!-- Complete -->
<view class="complete-bar" v-else>
@@ -56,14 +62,14 @@ import { ref, computed, onMounted, onBeforeUnmount, nextTick } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { api } from '../../config'
const messages = ref([{ role: 'ai', content: '你好!我是 AI 面试官,准备好就开始吧' }])
const messages = ref([{ role: 'ai', content: '你好!我是 AI 面试官,请选择岗位开始模拟面试' }])
const inputText = ref('')
const aiLoading = ref(false)
const interviewId = ref('')
const answeredCount = ref(0)
const isComplete = ref(false)
const scrollToId = ref('')
const position = ref('通用岗位')
const position = ref('')
let timerSeconds = 0
let timerInterval = null
@@ -75,7 +81,11 @@ const formatTime = computed(() => {
const token = computed(() => uni.getStorageSync('token') || '')
onLoad((options) => {
if (options?.position) position.value = decodeURIComponent(options.position)
if (options?.position) {
const pos = decodeURIComponent(options.position)
position.value = pos
messages.value = [{ role: 'ai', content: `你好!我是你的专属 ${pos} 面试官,准备好了就开始吧!` }]
}
})
onMounted(() => { timerInterval = setInterval(() => timerSeconds++, 1000); if (token.value) startInterview() })
@@ -151,9 +161,12 @@ const confirmExit = () => {
}
.back-arrow { font-size: 36rpx; color: #FFFFFF; font-weight: 300; line-height: 1; }
.topbar-center { flex: 1; display: flex; flex-direction: column; gap: 8rpx; }
.topbar-pos-row { display: flex; align-items: center; gap: 10rpx; }
.topbar-position { font-size: 26rpx; color: #FFFFFF; font-weight: 600; }
.topbar-status { font-size: 18rpx; color: #FFFFFF; background: rgba(255,255,255,0.2); padding: 2rpx 12rpx; border-radius: 20rpx; }
.progress-track { height: 6rpx; background: rgba(255,255,255,0.2); border-radius: 3rpx; overflow: hidden; }
.progress-fill { height: 100%; background: #FFFFFF; border-radius: 3rpx; transition: width 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); }
.topbar-timer { font-size: 22rpx; color: rgba(255,255,255,0.8); font-variant-numeric: tabular-nums; }
.topbar-timer { font-size: 20rpx; color: rgba(255,255,255,0.7); font-variant-numeric: tabular-nums; }
.topbar-right { width: 60rpx; flex-shrink: 0; }
/* ===== Chat ===== */
@@ -207,4 +220,5 @@ const confirmExit = () => {
/* Complete */
.complete-bar { background: #FFFFFF; padding: 20rpx 24rpx; padding-bottom: calc(20rpx + var(--safe-bottom)); }
.cta-btn { width: 100%; height: 88rpx; line-height: 88rpx; background: linear-gradient(135deg, var(--color-gradient-start), var(--color-gradient-mid)); color: #FFFFFF; border-radius: var(--radius-md); font-size: 28rpx; font-weight: 600; border: none; }
.disclaimer-bar { text-align: center; font-size: 20rpx; color: var(--color-text-tertiary); padding: 8rpx 24rpx; background: #FFFFFF; border-top: 1rpx solid var(--color-border); }
</style>