fix: 前端表格行类型断言修复并补充 psycopg-pool 依赖
- 审批/项目/租户/用户设置等视图新增 asXxx 类型断言辅助函数 - search-fields 由字符串改为数组传参 - backend 依赖新增 psycopg-pool,Dockerfile 依赖校验同步更新
This commit is contained in:
@@ -10,6 +10,7 @@ dependencies = [
|
||||
"pydantic>=2.7.0",
|
||||
"sqlalchemy>=2.0.30",
|
||||
"psycopg[binary]>=3.2.1",
|
||||
"psycopg-pool>=3.2.1",
|
||||
"alembic>=1.13.1",
|
||||
"redis>=5.0.4",
|
||||
"httpx>=0.27.0",
|
||||
|
||||
@@ -4,6 +4,7 @@ python-multipart>=0.0.9
|
||||
pydantic>=2.7.0
|
||||
sqlalchemy>=2.0.30
|
||||
psycopg[binary]>=3.2.1
|
||||
psycopg-pool>=3.2.1
|
||||
alembic>=1.13.1
|
||||
redis>=5.0.4
|
||||
httpx>=0.27.0
|
||||
|
||||
@@ -11,7 +11,7 @@ RUN pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple \
|
||||
&& pip install -r /tmp/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple \
|
||||
&& rm -f /tmp/requirements.txt
|
||||
|
||||
RUN python -c "import fastapi, uvicorn, psycopg, sqlalchemy, redis, jwt, passlib, httpx, alembic; print('backend dependency check ok')"
|
||||
RUN python -c "import fastapi, uvicorn, psycopg, psycopg_pool, sqlalchemy, redis, jwt, passlib, httpx, alembic; print('backend dependency check ok')"
|
||||
|
||||
RUN mkdir -p /opt/yg-ft/logs/backend /data/yg-ft \
|
||||
&& chmod -R 0775 /opt/yg-ft /data/yg-ft
|
||||
|
||||
@@ -3,7 +3,8 @@ import { onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import DataTablePage from '@/components/DataTablePage.vue'
|
||||
import { getApprovalInstances, decideApproval, type ApprovalInstance } from '@/api/modules/approval'
|
||||
import { getUsers, type SystemUser } from '@/api/modules/system'
|
||||
import { getUsers } from '@/api/modules/system'
|
||||
import type { SystemUser } from '@/types'
|
||||
|
||||
const loading = ref(false)
|
||||
const instances = ref<ApprovalInstance[]>([])
|
||||
@@ -48,6 +49,10 @@ function openDecide(inst: ApprovalInstance) {
|
||||
showDecide.value = true
|
||||
}
|
||||
|
||||
function asApprovalInstance(row: unknown): ApprovalInstance {
|
||||
return row as ApprovalInstance
|
||||
}
|
||||
|
||||
async function submitDecision() {
|
||||
if (!current.value) return
|
||||
if (!decision.value.approver_id) {
|
||||
@@ -72,7 +77,7 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<DataTablePage title="审批实例" :data="instances" :loading="loading" searchable search-fields="resource_type,resource_id">
|
||||
<DataTablePage title="审批实例" :data="instances" :loading="loading" searchable :search-fields="['resource_type', 'resource_id']">
|
||||
<template #toolbar-extra>
|
||||
<el-select v-model="statusFilter" placeholder="状态" clearable style="width: 140px" @change="load">
|
||||
<el-option v-for="s in statusOptions" :key="s.value" :label="s.label" :value="s.value" />
|
||||
@@ -82,14 +87,14 @@ onMounted(() => {
|
||||
<el-table-column prop="resource_type" label="资源类型" min-width="120" />
|
||||
<el-table-column prop="resource_id" label="资源 ID" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="applicant_id" label="申请人" min-width="120">
|
||||
<template #default="{ row }">{{ userName(row.applicant_id) }}</template>
|
||||
<template #default="{ row }">{{ userName(asApprovalInstance(row).applicant_id) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" min-width="100" />
|
||||
<el-table-column prop="current_step" label="当前步骤" min-width="100" />
|
||||
<el-table-column prop="create_time" label="创建时间" min-width="180" />
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<el-button v-if="row.status === 'pending'" link type="primary" @click="openDecide(row)">审批</el-button>
|
||||
<el-button v-if="asApprovalInstance(row).status === 'pending'" link type="primary" @click="openDecide(asApprovalInstance(row))">审批</el-button>
|
||||
</template>
|
||||
</DataTablePage>
|
||||
<el-dialog v-model="showDecide" title="审批决策" width="480px">
|
||||
|
||||
@@ -5,7 +5,8 @@ import { ElMessage } from 'element-plus'
|
||||
import DataTablePage from '@/components/DataTablePage.vue'
|
||||
import AclDialog from '@/components/AclDialog.vue'
|
||||
import { getProject, getProjectMembers, addProjectMember, removeProjectMember, type Project, type ProjectMember } from '@/api/modules/project'
|
||||
import { getUsers, type SystemUser } from '@/api/modules/system'
|
||||
import { getUsers } from '@/api/modules/system'
|
||||
import type { SystemUser } from '@/types'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -56,6 +57,10 @@ async function removeMember(userId: string) {
|
||||
load()
|
||||
}
|
||||
|
||||
function asProjectMember(row: unknown): ProjectMember {
|
||||
return row as ProjectMember
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadUsers()
|
||||
load()
|
||||
@@ -94,7 +99,7 @@ onMounted(() => {
|
||||
<el-table-column prop="create_time" label="加入时间" min-width="180" />
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<el-button link type="danger" @click="removeMember(row.user_id)">移除</el-button>
|
||||
<el-button link type="danger" @click="removeMember(asProjectMember(row).user_id)">移除</el-button>
|
||||
</template>
|
||||
</DataTablePage>
|
||||
</el-card>
|
||||
|
||||
@@ -41,6 +41,10 @@ function openDetail(id: string) {
|
||||
router.push(`/projects/${id}`)
|
||||
}
|
||||
|
||||
function asProject(row: unknown): Project {
|
||||
return row as Project
|
||||
}
|
||||
|
||||
async function submitCreate() {
|
||||
if (!form.value.name || !form.value.code) {
|
||||
ElMessage.warning('请填写项目名与编码')
|
||||
@@ -61,7 +65,7 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<DataTablePage title="项目空间" :data="projects" :loading="loading" searchable search-fields="name,code">
|
||||
<DataTablePage title="项目空间" :data="projects" :loading="loading" searchable :search-fields="['name', 'code']">
|
||||
<template #toolbar-extra>
|
||||
<el-select v-model="tenantId" placeholder="租户" style="width: 160px" @change="load">
|
||||
<el-option v-for="t in tenantOptions" :key="t.value" :label="t.label" :value="t.value" />
|
||||
@@ -76,7 +80,7 @@ onMounted(() => {
|
||||
<el-table-column prop="create_time" label="创建时间" min-width="180" />
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<el-button link type="primary" @click="openDetail(row.id)">详情</el-button>
|
||||
<el-button link type="primary" @click="openDetail(asProject(row).id)">详情</el-button>
|
||||
</template>
|
||||
</DataTablePage>
|
||||
<el-dialog v-model="showCreate" title="新建项目" width="520px">
|
||||
|
||||
@@ -54,6 +54,18 @@ function isSelf(row: SystemUser) {
|
||||
return row.username === currentUsername.value
|
||||
}
|
||||
|
||||
function asSystemUser(row: unknown): SystemUser {
|
||||
return row as SystemUser
|
||||
}
|
||||
|
||||
function userPermissions(row: unknown): PermissionCode[] {
|
||||
return (asSystemUser(row).permissions || []) as PermissionCode[]
|
||||
}
|
||||
|
||||
function permissionLabel(code: PermissionCode): string {
|
||||
return PERMISSION_LABELS[code] || code
|
||||
}
|
||||
|
||||
// ---------- 启停 ----------
|
||||
async function toggleStatus(row: SystemUser, next: boolean) {
|
||||
const nextStatus: UserStatus = next ? 'active' : 'disabled'
|
||||
@@ -162,31 +174,31 @@ async function removeUser(row: SystemUser) {
|
||||
<el-table-column prop="role" label="角色" width="120" />
|
||||
<el-table-column label="状态" width="130">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" size="small">{{ statusLabel(row.status) }}</el-tag>
|
||||
<el-tag :type="statusTagType(asSystemUser(row).status)" size="small">{{ statusLabel(asSystemUser(row).status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="页面权限" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
v-for="p in (row.permissions || []).slice(0, 3)"
|
||||
v-for="p in userPermissions(row).slice(0, 3)"
|
||||
:key="p"
|
||||
size="small"
|
||||
type="info"
|
||||
class="perm-tag"
|
||||
>{{ PERMISSION_LABELS[p] || p }}</el-tag>
|
||||
<span v-if="(row.permissions || []).length > 3" class="perm-more">
|
||||
+{{ (row.permissions || []).length - 3 }}
|
||||
>{{ permissionLabel(p) }}</el-tag>
|
||||
<span v-if="userPermissions(row).length > 3" class="perm-more">
|
||||
+{{ userPermissions(row).length - 3 }}
|
||||
</span>
|
||||
<span v-if="!(row.permissions || []).length" class="perm-more">无</span>
|
||||
<span v-if="!userPermissions(row).length" class="perm-more">无</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="创建时间" min-width="180" />
|
||||
<el-table-column label="操作" width="260" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
:model-value="row.status === 'active'"
|
||||
:disabled="row.protected || isSelf(row)"
|
||||
@change="(v: any) => toggleStatus(row, v)"
|
||||
:model-value="asSystemUser(row).status === 'active'"
|
||||
:disabled="asSystemUser(row).protected || isSelf(asSystemUser(row))"
|
||||
@change="(v: any) => toggleStatus(asSystemUser(row), v)"
|
||||
inline-prompt
|
||||
active-text="启用"
|
||||
inactive-text="停用"
|
||||
@@ -194,19 +206,19 @@ async function removeUser(row: SystemUser) {
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
:disabled="row.protected"
|
||||
@click="openResetPwd(row)"
|
||||
:disabled="asSystemUser(row).protected"
|
||||
@click="openResetPwd(asSystemUser(row))"
|
||||
>重置密码</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openPerms(row)"
|
||||
@click="openPerms(asSystemUser(row))"
|
||||
>页面权限</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
:disabled="row.protected || isSelf(row)"
|
||||
@click="removeUser(row)"
|
||||
:disabled="asSystemUser(row).protected || isSelf(asSystemUser(row))"
|
||||
@click="removeUser(asSystemUser(row))"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -25,6 +25,15 @@ function openDetail(id: string) {
|
||||
router.push(`/tenants/${id}`)
|
||||
}
|
||||
|
||||
function asTenant(row: unknown): Tenant {
|
||||
return row as Tenant
|
||||
}
|
||||
|
||||
function quotaText(row: unknown): string {
|
||||
const quota = asTenant(row).quota || {}
|
||||
return Object.keys(quota).length ? JSON.stringify(quota) : '—'
|
||||
}
|
||||
|
||||
async function submitCreate() {
|
||||
if (!form.value.name) {
|
||||
ElMessage.warning('请填写租户名称')
|
||||
@@ -75,15 +84,15 @@ onMounted(load)
|
||||
<el-table-column prop="code" label="编码" min-width="100" />
|
||||
<el-table-column label="配额" min-width="160">
|
||||
<template #default="{ row }">
|
||||
{{ Object.keys(row.quota || {}).length ? JSON.stringify(row.quota) : '—' }}
|
||||
{{ quotaText(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" min-width="100" />
|
||||
<el-table-column prop="create_time" label="创建时间" min-width="180" />
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<el-button link type="primary" @click="openDetail(row.id)">详情</el-button>
|
||||
<el-button link type="primary" @click="setQuota(row)">配额</el-button>
|
||||
<el-button link type="primary" @click="openDetail(asTenant(row).id)">详情</el-button>
|
||||
<el-button link type="primary" @click="setQuota(asTenant(row))">配额</el-button>
|
||||
</template>
|
||||
</DataTablePage>
|
||||
<el-dialog v-model="showCreate" title="新建租户" width="520px">
|
||||
|
||||
Reference in New Issue
Block a user