Fix Bug #550: fallback修复
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user