Compare commits

..
Author SHA1 Message Date
zlwuandCursor d30dbad788 fix(personal-dev): 结束 Gitea 短页评论翻页 (#57)
避免评论列表短于 PAGE_SIZE 时因忽略 page 参数反复同页而触发分页上限。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-05 17:00:16 +08:00
cykj e92112e9fd fix(web-admin): 简化项目归属维护调整弹窗 (#52)
Merge pull request #56 from cursor/issue-52-assignment-dialog

关联 #52
2026-11-05 08:56:30 +00:00
zlwuandCursor d6dc768efb fix(web-admin): 加宽归属审计记录弹窗 (#52)
历史归属维护页审计表格列较多,800px 偏挤,调整为 1100px。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-05 16:54:22 +08:00
zlwuandCursor 96518c08fb fix(web-admin): 简化项目归属维护调整弹窗 (#52)
展示原项目组/项目,录入新项目组/项目与原因;确认时静默预览并仍级联关联单据。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-05 16:54:22 +08:00
cykj 54a34b24ba fix(web-admin): 协议单扁平行列表展示所属项目与项目组 (#55)
fix(web-admin): 协议单扁平行列表展示所属项目与项目组 (#55)

Refs: #54
2026-11-05 08:47:38 +00:00
3 changed files with 87 additions and 64 deletions

No files matched your search

@@ -240,6 +240,10 @@ class Tracker:
if not value:
return records
records.extend(value)
# Gitea comment pages may ignore page and keep returning the same short
# list; a short page is the conventional last-page signal.
if len(value) < PAGE_SIZE:
return records
raise InstallError('issue tracker pagination limit exceeded')
def issues(self):
@@ -20,7 +20,13 @@
v-for="option in displayOptions"
:key="option.id"
:value="option.id"
:label="team ? option.teamName : `${option.projectName} · ${option.teamName}`"
:label="
team
? option.teamName
: teamId
? option.projectName
: `${option.projectName} · ${option.teamName}`
"
:disabled="!filter && option.disabled"
/>
</el-select>
@@ -43,6 +49,8 @@
const props = defineProps<{
filter?: boolean
team?: boolean
/** When selecting projects, keep options under this team. */
teamId?: string
modelValue?: string
bizType?: BizType
disabled?: boolean
@@ -54,15 +62,18 @@
ready: [value: boolean]
}>()
const options = ref<ProjectOption[]>([])
const displayOptions = computed(() =>
props.team
? [
...new Map(
options.value.map((option) => [option.teamId, { ...option, id: option.teamId }])
).values()
]
const displayOptions = computed(() => {
if (props.team) {
return [
...new Map(
options.value.map((option) => [option.teamId, { ...option, id: option.teamId }])
).values()
]
}
return props.teamId
? options.value.filter((option) => option.teamId === props.teamId)
: options.value
)
})
const loading = ref(false)
const error = ref(false),
retry = ref(0)
@@ -143,57 +143,58 @@
<el-dialog
v-model="visible"
title="项目归属维护"
width="720px"
width="560px"
class="dialog-form-layout"
append-to-body
modal-class="protocol-dialog-mask"
:close-on-click-modal="false"
>
<el-form label-position="top"
><el-form-item label="目标项目" required
><ProjectSelect
v-model="target"
:key="selected.id"
<el-form class="form-grid-2" label-position="top">
<el-form-item label="原所属项目组">
<el-input :model-value="selected.teamName || '未归属'" disabled />
</el-form-item>
<el-form-item label="原所属项目">
<el-input :model-value="selected.projectName || '未归属'" disabled />
</el-form-item>
<el-form-item label="新项目组" required>
<ProjectSelect
:key="`team-${selected.id}`"
team
v-model="targetTeamId"
:biz-type="selected.bizType"
@update:model-value="previewRows = []" /></el-form-item
><el-form-item label="调整原因" required
><el-input v-model="reason" type="textarea" :maxlength="500" /></el-form-item
></el-form>
<el-alert
title="请先预览全部关联单据,再确认调整。关联集合或归属发生变化时需要重新预览。"
type="info"
:closable="false"
/>
<el-table :data="previewRows" class="list-table" stripe
><el-table-column
type="index"
label="序号"
min-width="68"
align="center"
header-align="center" />
<el-table-column prop="bizNo" label="关联单据" min-width="200" /><el-table-column
prop="projectName"
label="原项目"
min-width="160"
/></el-table>
<template #footer
><el-button @click="visible = false"
@update:model-value="onTargetTeamChange"
/>
</el-form-item>
<el-form-item label="新项目" required>
<ProjectSelect
:key="`project-${selected.id}-${targetTeamId}`"
v-model="target"
:team-id="targetTeamId"
:biz-type="selected.bizType"
:disabled="!targetTeamId"
/>
</el-form-item>
<el-form-item label="调整原因" required class="form-col-span-2">
<el-input v-model="reason" type="textarea" :maxlength="500" show-word-limit />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible = false"
><el-icon><Close /></el-icon>取消</el-button
><el-button :loading="busy" @click="preview"
><el-icon><View /></el-icon>预览关联单据</el-button
><el-button
>
<el-button
type="primary"
:disabled="!previewRows.length || !reason.trim()"
:disabled="!target || !reason.trim()"
:loading="busy"
@click="commit"
><el-icon><Check /></el-icon>确认调整</el-button
></template
>
>
</template>
</el-dialog>
<el-dialog
v-model="historyVisible"
title="归属审计记录"
width="800px"
width="1100px"
append-to-body
class="dialog-form-layout"
><el-table :data="historyRows" class="list-table" stripe
@@ -233,7 +234,7 @@
presignOrderStatusTagType,
applicationStatusTagType
} from '@/utils/status-tag-registry'
import { Search, Edit, Clock, Close, View, Check, RefreshRight } from '@element-plus/icons-vue'
import { Search, Edit, Clock, Close, Check, RefreshRight } from '@element-plus/icons-vue'
import { assignmentTabs, assignmentColumns, assignmentKeywordHints } from './assignment-columns'
import ProjectSelect from '@/components/business/ProjectSelect.vue'
import { assignmentSnapshot } from '@/utils/project-management'
@@ -268,10 +269,10 @@
loadError = ref(false),
visible = ref(false),
busy = ref(false),
targetTeamId = ref(''),
target = ref(''),
reason = ref(''),
selected = ref<ProjectRecord>({}),
previewRows = ref<ProjectRecord[]>([]),
historyVisible = ref(false),
historyRows = ref<ProjectRecord[]>([])
const visibleColumns = computed(() =>
@@ -361,47 +362,54 @@
}
function open(row: ProjectRecord) {
selected.value = row
targetTeamId.value = ''
target.value = ''
reason.value = ''
previewRows.value = []
visible.value = true
}
async function preview() {
function onTargetTeamChange() {
target.value = ''
}
async function commit() {
if (!targetTeamId.value) {
ElMessage.warning('请选择新项目组')
return
}
if (!target.value) {
ElMessage.warning('请选择目标项目')
ElMessage.warning('请选择新项目')
return
}
if (!reason.value.trim()) {
ElMessage.warning('请填写调整原因')
return
}
busy.value = true
const previewTarget = target.value
const previewRecord = selected.value
const commitTarget = target.value
const commitRecord = selected.value
try {
const result = await previewAssignment({
const preview = await previewAssignment({
bizType: selected.value.bizType,
id: selected.value.id,
projectId: target.value
})
if (selected.value === previewRecord && target.value === previewTarget && visible.value) {
previewRows.value = result.records
if (selected.value !== commitRecord || target.value !== commitTarget || !visible.value) {
return
}
} finally {
busy.value = false
}
}
async function commit() {
busy.value = true
try {
await saveAssignment({
bizType: selected.value.bizType,
id: selected.value.id,
projectId: target.value,
reason: reason.value.trim(),
expectedAssignments: assignmentSnapshot(previewRows.value)
expectedAssignments: assignmentSnapshot(preview.records)
})
if (selected.value !== commitRecord || target.value !== commitTarget || !visible.value) {
return
}
visible.value = false
ElMessage.success('归属已调整')
await load()
} catch {
previewRows.value = []
// API layer already surfaces the conflict / validation message.
} finally {
busy.value = false
}