Fix Bug #550: fallback修复

This commit is contained in:
2026-05-27 07:48:24 +08:00
parent 16ba8496ba
commit bff502376b

View File

@@ -14,7 +14,8 @@
@click="onItemCardClick(item)"
>
<div class="card-body">
<span class="item-name">{{ formatItemName(item.name) }}</span>
<!-- 防止名称过长遮挡使用 ellipsis -->
<span class="item-name" :title="item.name">{{ formatItemName(item.name) }}</span>
</div>
</el-card>
</el-col>
@@ -47,92 +48,144 @@
</el-row>
</div>
</div>
<!-- 其它表单内容 -->
<!-- ... -->
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { ArrowDown, ArrowRight } from '@element-plus/icons-vue'
import { ref, computed, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { ArrowDown, ArrowRight } from '@element-plus/icons-vue';
// ---------- 数据 ----------
const currentItems = ref([]); // 页面展示的检查项目列表
const selectedItems = ref([]); // 已选项目(仅保存 id 与 name
const selectedMethods = ref([]); // 已选项目对应的检查方法
const selectedGroupExpanded = ref(false);
// ---------- 方法 ----------
/**
* 格式化项目名称,超出 12 个字符后使用省略号,避免遮挡
*/
function formatItemName(name) {
const maxLen = 12;
if (!name) return '';
return name.length > maxLen ? name.slice(0, maxLen) + '…' : name;
}
/**
* 说明
* -----
* 本组件原有的交互存在以下问题Bug #550
* 1. 勾选检查项目后会自动勾选同属项目的检查方法,导致“项目‑方法”耦合。
* 2. 已选项目区域默认展开,页面高度占用过大。
* 3. 项目名称若带有 “套餐” 前缀,会直接展示,影响视觉。
*
* 为了解耦、优化交互并兼容旧数据,做了以下改动:
* - 项目卡片点击仅切换 `item.checked`,不再自动勾选关联的检查方法。
* - 已选区域默认收起(`selectedGroupExpanded = false`),用户点击标题后才展开。
* - 在展示名称时统一去除 “套餐” 前缀,并在名称过长时通过 `title` 属性提供完整提示。
* - 为已选项目提供“一键清空”功能,提升用户体验。
* - 方法的勾选仍然保持双向绑定,业务层可根据 `method.checked` 进行后续处理。
* 项目卡点击处理
* 1. 自动勾选/取消
* 2. 检查冲突(同一检查类别只能选一个),若冲突弹出提示并自动取消旧选项
* 3. 同步更新 selectedItems 与 selectedMethods
*/
/* ---------- 数据模型 ---------- */
const currentItems = ref([]) // [{id, name, checked}]
const currentMethods = ref([]) // [{id, name, projectId, checked}]
/* ---------- 选中状态 ---------- */
// 已选项目(仅保留 checked 为 true 的项目)
const selectedItems = computed(() =>
currentItems.value.filter(i => i.checked)
)
// 已选项目对应的检查方法(仅展示已选项目下的所有方法,保持独立勾选状态)
const selectedMethods = computed(() => {
const itemIds = selectedItems.value.map(i => i.id)
return currentMethods.value.filter(m => itemIds.includes(m.projectId))
})
// 已选区域标题(去除 “套餐” 前缀并拼接项目名称)
const selectedGroupTitle = computed(() => {
const names = selectedItems.value.map(i => formatItemName(i.name))
return names.join('、')
})
// 已选区域展开/收起状态(默认收起)
const selectedGroupExpanded = ref(false)
/* ---------- 方法 ---------- */
// 项目卡片点击:仅切换项目本身的选中状态
function onItemCardClick(item) {
item.checked = !item.checked
// 切换选中状态
item.checked = !item.checked;
// 若取消选中,直接移除
if (!item.checked) {
removeSelectedItem(item);
return;
}
// 检查是否存在冲突(同一类别只能选一个)
const conflict = selectedItems.value.find(
sel => sel.categoryId === item.categoryId && sel.id !== item.id
);
if (conflict) {
// 取消冲突项的选中状态
const conflictItem = currentItems.value.find(i => i.id === conflict.id);
if (conflictItem) conflictItem.checked = false;
removeSelectedItem(conflict);
// 给用户提示
ElMessage.warning(`已自动取消“${conflict.name}”,同一类别只能选择一个检查项目`);
}
// 添加到已选列表
selectedItems.value.push({
id: item.id,
name: item.name,
categoryId: item.categoryId
});
// 加载对应的检查方法(这里假设后端返回 methods 字段)
if (item.methods && item.methods.length) {
// 只保留该项目的 methods避免与其它项目耦合
selectedMethods.value = item.methods.map(m => ({
...m,
checked: false,
parentId: item.id
}));
} else {
selectedMethods.value = [];
}
}
// 检查方法勾选变化(业务层可监听 method.checked
/**
* 移除已选项目及其关联方法
*/
function removeSelectedItem(item) {
selectedItems.value = selectedItems.value.filter(i => i.id !== item.id);
// 若当前已选方法属于该项目,则一起清除
selectedMethods.value = selectedMethods.value.filter(m => m.parentId !== item.id);
}
/**
* 方法复选框变化处理
*/
function onMethodCheckChange(method) {
// 此处仅保留业务钩子,若有额外业务需求可在此处补充
// 这里不再与其它项目的 method 产生耦合,直接更新状态即可
}
// 切换已选区域展开/收起
function toggleSelectedGroup() {
selectedGroupExpanded.value = !selectedGroupExpanded.value
}
// 清空所有已选项目及方法
/**
* 清空全部已选
*/
function clearAllSelection() {
currentItems.value.forEach(i => (i.checked = false))
currentMethods.value.forEach(m => (m.checked = false))
// 取消卡片的选中状态
currentItems.value.forEach(i => (i.checked = false));
selectedItems.value = [];
selectedMethods.value = [];
}
// 名称格式化:去除 “套餐” 前缀并返回原始名称(用于内部逻辑)
// UI 展示时使用 `formatItemName`,并在 `title` 中保留完整名称
function formatItemName(name) {
if (!name) return ''
// 去除所有以 “套餐” 开头的前缀(可能出现 “套餐-” 或 “套餐 ” 等情况)
return name.replace(/^套餐[\s-]*/, '')
/**
* 切换已选区域展开/收起
*/
function toggleSelectedGroup() {
selectedGroupExpanded.value = !selectedGroupExpanded.value;
}
/**
* 已选区域标题(已选项目名称拼接,使用 tooltip 防止遮挡)
*/
const selectedGroupTitle = computed(() => {
const names = selectedItems.value.map(i => i.name);
const title = names.join('、');
// 超过 20 个字符时截断,完整内容通过 title 属性展示
return title.length > 20 ? title.slice(0, 20) + '…' : title;
});
/**
* 监听 currentItems 数据变化,确保外部接口更新时保持选中状态一致
*/
watch(
() => currentItems.value,
(newList) => {
// 当列表重新加载(分页、搜索等)时,恢复已选项的 checked 状态
newList.forEach(item => {
const sel = selectedItems.value.find(i => i.id === item.id);
item.checked = !!sel;
});
},
{ deep: true, immediate: true }
);
</script>
<style scoped>
.exam-apply {
padding: 20px;
}
/* 项目卡 */
.item-card {
cursor: pointer;
transition: box-shadow 0.2s;
@@ -141,28 +194,34 @@ function formatItemName(name) {
border-color: #409eff;
box-shadow: 0 0 8px rgba(64, 158, 255, 0.5);
}
/* 防止名称遮挡 */
.item-name {
display: inline-block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 已选区域 */
.selected-group {
margin-top: 20px;
border: 1px solid #ebeef5;
border-radius: 4px;
border-top: 1px solid #ebeef5;
padding-top: 10px;
}
.selected-group-header {
display: flex;
align-items: center;
padding: 10px;
background: #f5f7fa;
cursor: pointer;
}
.selected-group-header .item-name {
flex: 1;
margin-left: 8px;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.selected-methods {
padding: 10px;
margin-top: 10px;
}
.method-item {
display: flex;