diff --git a/zhiyin-app/src/pages/admin/admin.vue b/zhiyin-app/src/pages/admin/admin.vue
index 3a0b128..ba1dc8d 100644
--- a/zhiyin-app/src/pages/admin/admin.vue
+++ b/zhiyin-app/src/pages/admin/admin.vue
@@ -21,6 +21,7 @@
订单
定价
分享
+ 岗位
管理
@@ -272,6 +273,31 @@
暂无访问记录
+
+
+
+ 岗位列表({{ positions.length }})
+
+
+
+
+
+ {{ p.category === 'ai' ? 'AI' : '传统' }}
+
+ {{ p.name }}
+ {{ p.company || '-' }} · {{ p.salary || '-' }} · sort:{{ p.sort }}
+
+
+
+ 编辑
+ 删除
+
+
+ 暂无岗位
+
+ 加载中...
+
+
@@ -286,6 +312,32 @@
+
+
+
+
+ {{ posModal.isNew ? '新增岗位' : '编辑岗位' }}
+ 岗位名称
+ 薪资
+ 公司
+ 排序
+ 分类
+ posForm.category = e.detail.value === 0 ? 'ai' : 'traditional'">
+ {{ posForm.category === 'ai' ? 'AI岗位' : '传统岗位' }}
+
+
+ 启用
+ posForm.active = e.detail.value === 0" :value="posForm.active ? 0 : 1">
+ {{ posForm.active ? '启用' : '停用' }}
+
+
+
+
+
+
+
+
+
@@ -356,6 +408,20 @@ const sprintPriceTemp = ref(49.9)
const growthFeaturesText = ref('')
const sprintFeaturesText = ref('')
+// Position management
+const positions = ref([])
+const posLoading = ref(false)
+const posModal = ref({ show: false, isNew: false })
+const posForm = reactive({
+ name: '',
+ salary: '',
+ company: '',
+ icon: '',
+ sort: 0,
+ active: true,
+ category: 'ai',
+})
+
const growthPriceDisplay = computed(() => growthPriceTemp.value.toFixed(1))
const sprintPriceDisplay = computed(() => sprintPriceTemp.value.toFixed(1))
@@ -421,6 +487,7 @@ const switchTab = (t) => {
tab.value = t
if (t === 'users' && users.value.length === 0) loadUsers()
if (t === 'interviews' && interviews.value.length === 0) loadInterviews()
+ if (t === 'positions') loadPositions()
if (t === 'resumes' && resumes.value.length === 0) loadResumes()
if (t === 'admins' && adminList.value.length === 0) loadAdmins()
if (t === 'pricing') loadPricing()
@@ -540,6 +607,84 @@ const loadAdmins = async () => {
} catch(e) { console.error(e) }
}
+// ─── 岗位管理 ──────────────────────
+const apiPositions = (path, opts = {}) => {
+ return uni.request({
+ url: api('/positions' + path),
+ method: opts.method || 'POST',
+ header: { 'Authorization': `Bearer ${token()}`, 'Content-Type': 'application/json', ...opts.headers },
+ data: opts.body || opts.data,
+ })
+}
+
+const loadPositions = async () => {
+ posLoading.value = true
+ try {
+ const res = await apiPositions('/admin/list')
+ if (res.statusCode === 200) positions.value = res.data || []
+ } catch (e) { console.error(e) }
+ finally { posLoading.value = false }
+}
+
+const openPositionModal = (position) => {
+ if (position) {
+ posForm.name = position.name || ''
+ posForm.salary = position.salary || ''
+ posForm.company = position.company || ''
+ posForm.icon = position.icon || ''
+ posForm.sort = position.sort ?? 0
+ posForm.active = position.active ?? true
+ posForm.category = position.category || 'traditional'
+ posModal.value = { show: true, isNew: false }
+ } else {
+ posForm.name = ''
+ posForm.salary = ''
+ posForm.company = ''
+ posForm.icon = ''
+ posForm.sort = positions.value.length + 1
+ posForm.active = true
+ posForm.category = 'ai'
+ posModal.value = { show: true, isNew: true }
+ }
+}
+
+const closePositionModal = () => {
+ posModal.value = { show: false, isNew: false }
+}
+
+const savePosition = async () => {
+ if (!posForm.name.trim()) {
+ uni.showToast({ title: '岗位名称不能为空', icon: 'none' })
+ return
+ }
+ try {
+ const res = await apiPositions('/admin/save', {
+ method: 'POST', body: { ...posForm },
+ })
+ if (res.statusCode === 200) {
+ uni.showToast({ title: '保存成功', icon: 'success' })
+ closePositionModal()
+ loadPositions()
+ } else throw new Error()
+ } catch { uni.showToast({ title: '保存失败', icon: 'none' }) }
+}
+
+const deletePosition = (id, name) => {
+ uni.showModal({
+ title: '删除岗位', content: `确定删除"${name}"?`,
+ success: async (r) => {
+ if (!r.confirm) return
+ try {
+ const res = await apiPositions('/admin/' + id, { method: 'DELETE' })
+ if (res.statusCode === 200) {
+ uni.showToast({ title: '已删除', icon: 'success' })
+ loadPositions()
+ } else throw new Error()
+ } catch { uni.showToast({ title: '删除失败', icon: 'none' }) }
+ },
+ })
+}
+
const searchAdmin = async () => {
if (!adminKeyword.value.trim()) return
try {
@@ -740,4 +885,18 @@ const doAdjustCredits = async () => {
.modal-btn { flex: 1; height: 72rpx; border-radius: var(--radius-sm); font-size: 26rpx; border: none; }
.modal-btn.cancel { background: #F3F4F6; color: var(--color-text-secondary); }
.modal-btn.confirm { background: var(--color-primary); color: #FFF; }
+/* ─── 岗位管理 ───── */
+.position-mgr-list { display: flex; flex-direction: column; gap: 8rpx; }
+.pos-mgr-row { background: #FFF; padding: 16rpx; border-radius: var(--radius-sm); display: flex; align-items: center; gap: 12rpx; }
+.pos-mgr-main { flex: 1; display: flex; align-items: center; gap: 12rpx; }
+.pos-mgr-cat { font-size: 18rpx; padding: 2rpx 12rpx; border-radius: var(--radius-round); font-weight: 600; }
+.pos-mgr-cat.cat-ai { background: #EEF2FF; color: var(--color-primary); }
+.pos-mgr-cat.cat-tr { background: #F3F4F6; color: var(--color-text-tertiary); }
+.pos-mgr-body { display: flex; flex-direction: column; }
+.pos-mgr-name { font-size: 24rpx; font-weight: 600; color: var(--color-text); }
+.pos-mgr-meta { font-size: 20rpx; color: var(--color-text-tertiary); margin-top: 2rpx; }
+.pos-mgr-actions { display: flex; gap: 8rpx; }
+.pos-mgr-btn { font-size: 20rpx; padding: 4rpx 16rpx; border-radius: var(--radius-round); }
+.pos-mgr-btn.edit { color: var(--color-primary); border: 2rpx solid var(--color-primary); }
+.pos-mgr-btn.del { color: #EF4444; border: 2rpx solid #EF4444; }
diff --git a/zhiyin-app/src/pages/index/index.vue b/zhiyin-app/src/pages/index/index.vue
index ea688d5..8238c27 100644
--- a/zhiyin-app/src/pages/index/index.vue
+++ b/zhiyin-app/src/pages/index/index.vue
@@ -43,6 +43,15 @@
开始
+
+
+
+ 🎯
+ AI 择业顾问
+
+ 专业分析 · 岗位匹配 · 智能推荐
+
+
@@ -246,6 +255,7 @@ const goProgress = () => uni.navigateTo({ url: '/pages/progress/progress' })
const goContribute = () => uni.navigateTo({ url: '/pages/contribute/contribute' })
const goBank = () => uni.navigateTo({ url: '/pages/company-bank/bank' })
const goInternship = () => uni.navigateTo({ url: '/pages/internship/internship' })
+const goCareer = () => uni.navigateTo({ url: '/pages/career/career' })
const startInterview = (pos) => uni.navigateTo({ url: `/pages/interview/interview?position=${encodeURIComponent(pos.name)}` })