feat: JWT多租户数据权限隔离 & 主管系统管理权限 & 含税单价补齐

## 多租户公司数据隔离
- 新增 get_current_company_filter() 工具函数 (decorators.py)
  SUPER_ADMIN: 可传company_name参数过滤或传ALL看全量
  其他角色: 强制隔离到JWT中的company_name
- 重构 base_service.py / buy_service.py: 用集中式函数替换内联公司过滤
- SysRolePermission 表新增 company_name 字段,支持同角色不同公司权限
- get_user_permissions() 新增 company_name 参数,查公司定制+全局模板权限
- permission.py API 新增 @permission_required 拦截 + 公司过滤
- 19个API/service文件传递 company_name 到权限查询

## 主管系统管理权限
- delete_user() 允许SUPERVISOR删除同公司用户 (原仅SUPER_ADMIN)
- get_all_users() 新增 company_name 参数过滤
- 用户列表/权限分配 API 应用 get_current_company_filter()
- 前端 UserCreate.vue: 超管可见公司下拉框,主管隐藏部门字段

## 前端多租户适配
- material/list.vue / buy.vue: 公司下拉框仅超管可见,默认ALL
- UserCreate.vue: 新增搜索栏公司筛选,部门字段按角色显隐
- auth.ts: getUserList() 支持 params 参数

## Bug修复: 含税单价字段补齐
- buy.vue: 表格列/高级筛选/排序/权限映射新增 post_tax_unit_price
- buy_service.py: allowed_fields/sort_field_map 新增 post_tax_unit_price
This commit is contained in:
yueli
2026-07-13 15:12:22 +08:00
parent 4ce42309db
commit 4f5965db02
22 changed files with 289 additions and 172 deletions

View File

@ -15,6 +15,23 @@
</div>
</template>
<!-- 搜索栏 -->
<div class="filter-container" style="margin-bottom: 16px; display: flex; align-items: center; gap: 10px;">
<el-select
v-if="isSuperAdmin"
v-model="queryParams.company"
placeholder="所属公司"
clearable
filterable
style="width: 180px;"
@change="getList"
>
<el-option label="全部 (跨域)" value="ALL" />
<el-option v-for="item in companyOptions" :key="item" :label="item" :value="item" />
</el-select>
<el-button type="primary" plain @click="getList">搜索</el-button>
</div>
<el-table
v-loading="tableLoading"
:data="tableData"
@ -104,7 +121,7 @@
/>
</el-form-item>
<el-form-item label="所属部门" prop="department" v-if="hasFormFieldPermission('department')">
<el-form-item label="所属部门" prop="department" v-if="hasFormFieldPermission('department') && isSuperAdmin">
<el-select
v-model="form.department"
placeholder="请输入或选择部门"
@ -147,7 +164,7 @@
<!-- 批量新增弹窗 -->
<el-dialog v-model="batchDialogVisible" title="批量新增员工" width="600px" destroy-on-close @close="batchForm.namesText = ''">
<el-form :model="batchForm" label-width="100px">
<el-form-item label="所属部门" required>
<el-form-item label="所属部门" required v-if="isSuperAdmin">
<el-select v-model="batchForm.department" style="width: 100%" placeholder="请选择部门">
<el-option v-for="d in departmentOptions" :key="d" :label="d" :value="d" />
</el-select>
@ -194,6 +211,11 @@ import { ElMessage } from 'element-plus'
import { pinyin } from 'pinyin-pro' // ★ 务必安装: npm install pinyin-pro
const userStore = useUserStore()
const isSuperAdmin = computed(() => userStore.role === 'SUPER_ADMIN')
// 查询参数 & 公司列表
const queryParams = reactive({ company: 'ALL' })
const companyOptions = ref<string[]>([])
// 列与权限Code的映射关系(数据库中的code)
const permissionMap: Record<string, string> = {
@ -327,7 +349,9 @@ const rules = computed(() => {
{ validator: validateNameStrict, trigger: 'blur' }
],
role: [{ required: true, message: '请选择角色', trigger: 'change' }],
department: [{ required: true, message: '请输入或选择部门', trigger: ['blur', 'change'] }],
department: isSuperAdmin.value
? [{ required: true, message: '请输入或选择部门', trigger: ['blur', 'change'] }]
: [],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: ['blur', 'change'] }
@ -357,11 +381,18 @@ const rules = computed(() => {
const getList = async () => {
tableLoading.value = true
try {
const res = await getUserList()
const params: any = {}
if (queryParams.company && queryParams.company !== 'ALL') {
params.company_name = queryParams.company
}
const res = await getUserList(params)
tableData.value = res.data || []
extractDepartments(tableData.value)
// 提取公司列表(从部门字段)
const deptSet = new Set<string>()
tableData.value.forEach(u => { if (u.department) deptSet.add(u.department) })
companyOptions.value = Array.from(deptSet)
} catch (error) {
// 错误已由全局拦截器统一处理
console.error('Fetch users failed:', error)
} finally {
tableLoading.value = false
@ -448,8 +479,11 @@ const onSubmit = async () => {
// 批量提交逻辑
const handleBatchSubmit = async () => {
if (!batchForm.department || !batchForm.role || !batchForm.namesText.trim()) {
return ElMessage.warning('请填写完整部门、角色及员工名单')
if (!batchForm.role || !batchForm.namesText.trim()) {
return ElMessage.warning('请填写完整角色及员工名单')
}
if (isSuperAdmin.value && !batchForm.department) {
return ElMessage.warning('请填写所属部门')
}
const names = batchForm.namesText.split('\n').map(n => n.trim()).filter(n => n)