fix: 修复用户编辑和职位发布报错,优化首页跳转逻辑
- 修复超管编辑用户时password必填导致报错,改为更新时可选 - 修复单位管理员发布职位时organization_id必填校验失败 - 首页"一键进入"按钮跳转到公司列表页 - 成员单位卡片点击跳转到首页对应公司的职位列表 - 管理后台侧边栏新增"返回首页"入口 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bb896f8922
commit
0fc9ad7971
|
|
@ -45,12 +45,17 @@ class UserSerializer(serializers.ModelSerializer):
|
|||
|
||||
class AdminUserSerializer(serializers.ModelSerializer):
|
||||
"""超管用于创建/管理公司管理员账号"""
|
||||
password = serializers.CharField(write_only=True, min_length=6)
|
||||
password = serializers.CharField(write_only=True, min_length=6, required=False)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['id', 'username', 'email', 'phone', 'role', 'organization', 'password', 'is_active']
|
||||
|
||||
def validate(self, attrs):
|
||||
if not self.instance and not attrs.get('password'):
|
||||
raise serializers.ValidationError({'password': '创建用户时密码为必填项'})
|
||||
return attrs
|
||||
|
||||
def create(self, validated_data):
|
||||
password = validated_data.pop('password')
|
||||
user = User(**validated_data)
|
||||
|
|
@ -58,6 +63,15 @@ class AdminUserSerializer(serializers.ModelSerializer):
|
|||
user.save()
|
||||
return user
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
password = validated_data.pop('password', None)
|
||||
for attr, value in validated_data.items():
|
||||
setattr(instance, attr, value)
|
||||
if password:
|
||||
instance.set_password(password)
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
|
||||
class SendCodeSerializer(serializers.Serializer):
|
||||
"""发送验证码 serializer"""
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ class JobDetailSerializer(serializers.ModelSerializer):
|
|||
organization_id = serializers.PrimaryKeyRelatedField(
|
||||
source='organization',
|
||||
queryset=Organization.objects.all(),
|
||||
write_only=True
|
||||
write_only=True,
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<el-container style="height:100vh;overflow:hidden;flex-direction:column;">
|
||||
<el-header style="background:#001529;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #333;flex-shrink:0;">
|
||||
<div style="color:#fff;font-weight:bold;font-size:16px;">管理后台</div>
|
||||
<div class="admin-layout">
|
||||
<header class="admin-header">
|
||||
<div class="header-title">管理后台</div>
|
||||
<el-dropdown>
|
||||
<div style="color:#fff;cursor:pointer;display:flex;align-items:center;gap:8px;">
|
||||
<div class="header-user">
|
||||
<span>{{ auth.user?.email }}</span>
|
||||
<el-icon><ArrowDown /></el-icon>
|
||||
</div>
|
||||
|
|
@ -13,10 +13,9 @@
|
|||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-header>
|
||||
<el-container style="flex:1;overflow:hidden;">
|
||||
<el-aside width="200px" style="background:#001529;overflow-y:auto;height:100%;">
|
||||
<div style="padding:20px;color:#fff;font-weight:bold;font-size:16px;">菜单</div>
|
||||
</header>
|
||||
<div class="admin-body">
|
||||
<aside class="admin-aside">
|
||||
<el-menu router :default-active="$route.path" background-color="#001529" text-color="#fff" active-text-color="#409eff">
|
||||
<el-menu-item index="/admin/jobs">职位管理</el-menu-item>
|
||||
<el-menu-item index="/admin/applications">投递管理</el-menu-item>
|
||||
|
|
@ -24,11 +23,14 @@
|
|||
<el-menu-item index="/admin/organizations">组织架构</el-menu-item>
|
||||
<el-menu-item index="/admin/users">用户管理</el-menu-item>
|
||||
</template>
|
||||
<el-menu-item index="/home">返回首页</el-menu-item>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
<el-main style="overflow-y:auto;height:100%;"><router-view /></el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</aside>
|
||||
<main class="admin-main">
|
||||
<router-view />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
|
|
@ -45,3 +47,54 @@ function logout() {
|
|||
router.push({ name: 'Login' })
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.admin-layout {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.admin-header {
|
||||
height: 56px;
|
||||
flex-shrink: 0;
|
||||
background: #001529;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px;
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
.header-title {
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
.header-user {
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.admin-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
.admin-aside {
|
||||
width: 200px;
|
||||
flex-shrink: 0;
|
||||
background: #001529;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.admin-main {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
background: #f5f6fa;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -45,8 +45,8 @@
|
|||
<h1 class="hero-slogan">人才创造美好未来</h1>
|
||||
<p class="hero-sub">与优秀的人一起,做有价值的事</p>
|
||||
<div class="hero-btns">
|
||||
<button class="btn-primary-lg" @click="router.push('/jobs')">
|
||||
浏览职位
|
||||
<button class="btn-primary-lg" @click="router.push('/companies')">
|
||||
一键进入
|
||||
<span class="btn-arrow">→</span>
|
||||
</button>
|
||||
<button class="btn-secondary-lg" @click="router.push('/register')">
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@
|
|||
<el-table-column prop="organization_name" label="所属公司" />
|
||||
<el-table-column prop="location" label="地点" />
|
||||
<el-table-column prop="salary" label="薪资" />
|
||||
<el-table-column label="发布时间" width="160">
|
||||
<template #default="{ row }">
|
||||
{{ row.created_at ? new Date(row.created_at).toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 'published' ? 'success' : row.status === 'draft' ? 'info' : 'danger'">
|
||||
|
|
@ -148,7 +153,9 @@ async function handleSave() {
|
|||
saving.value = true
|
||||
try {
|
||||
const payload = { ...form }
|
||||
if (!auth.isSuperAdmin) delete payload.organization_id
|
||||
if (!auth.isSuperAdmin) {
|
||||
payload.organization_id = auth.user?.organization ?? undefined
|
||||
}
|
||||
if (editingJob.value) await updateJob(editingJob.value.id, payload)
|
||||
else await createJob(payload)
|
||||
ElMessage.success('保存成功')
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@
|
|||
v-for="child in group.children"
|
||||
:key="child.id"
|
||||
class="member-card"
|
||||
@click="$router.push(`/companies/${child.id}`)"
|
||||
@click="$router.push({ path: '/home', query: { org: child.id } })"
|
||||
>
|
||||
<div class="member-header">
|
||||
<div class="member-logo">
|
||||
|
|
@ -108,7 +108,7 @@
|
|||
<span class="member-email" v-if="child.email">
|
||||
<el-icon><Message /></el-icon> {{ child.email }}
|
||||
</span>
|
||||
<el-button type="primary" link size="small">查看详情 →</el-button>
|
||||
<el-button type="primary" link size="small">查看职位 →</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -102,13 +102,14 @@
|
|||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getOrganizations } from '@/api/organizations'
|
||||
import { getJobs } from '@/api/jobs'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const auth = useAuthStore()
|
||||
|
||||
const orgs = ref([])
|
||||
|
|
@ -155,7 +156,17 @@ onMounted(async () => {
|
|||
try {
|
||||
const { data } = await getOrganizations()
|
||||
orgs.value = data.results
|
||||
if (orgs.value.length > 0) selectOrg(orgs.value[0])
|
||||
const targetOrgId = route.query.org ? Number(route.query.org) : null
|
||||
let targetOrg = null
|
||||
if (targetOrgId) {
|
||||
for (const org of orgs.value) {
|
||||
if (org.id === targetOrgId) { targetOrg = org; break }
|
||||
const child = org.children?.find(c => c.id === targetOrgId)
|
||||
if (child) { targetOrg = child; break }
|
||||
}
|
||||
}
|
||||
if (targetOrg) selectOrg(targetOrg)
|
||||
else if (orgs.value.length > 0) selectOrg(orgs.value[0])
|
||||
} catch { orgsError.value = true }
|
||||
finally { orgsLoading.value = false }
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue