Fix Bug #550: fallback修复
This commit is contained in:
@@ -14,7 +14,8 @@
|
|||||||
@click="onItemCardClick(item)"
|
@click="onItemCardClick(item)"
|
||||||
>
|
>
|
||||||
<div class="card-body">
|
<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>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
@@ -47,92 +48,144 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 其它表单内容 -->
|
|
||||||
<!-- ... -->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, watch } from 'vue';
|
||||||
import { ArrowDown, ArrowRight } from '@element-plus/icons-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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 说明
|
* 项目卡点击处理
|
||||||
* -----
|
* 1. 自动勾选/取消
|
||||||
* 本组件原有的交互存在以下问题(Bug #550):
|
* 2. 检查冲突(同一检查类别只能选一个),若冲突弹出提示并自动取消旧选项
|
||||||
* 1. 勾选检查项目后会自动勾选同属项目的检查方法,导致“项目‑方法”耦合。
|
* 3. 同步更新 selectedItems 与 selectedMethods
|
||||||
* 2. 已选项目区域默认展开,页面高度占用过大。
|
|
||||||
* 3. 项目名称若带有 “套餐” 前缀,会直接展示,影响视觉。
|
|
||||||
*
|
|
||||||
* 为了解耦、优化交互并兼容旧数据,做了以下改动:
|
|
||||||
* - 项目卡片点击仅切换 `item.checked`,不再自动勾选关联的检查方法。
|
|
||||||
* - 已选区域默认收起(`selectedGroupExpanded = false`),用户点击标题后才展开。
|
|
||||||
* - 在展示名称时统一去除 “套餐” 前缀,并在名称过长时通过 `title` 属性提供完整提示。
|
|
||||||
* - 为已选项目提供“一键清空”功能,提升用户体验。
|
|
||||||
* - 方法的勾选仍然保持双向绑定,业务层可根据 `method.checked` 进行后续处理。
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* ---------- 数据模型 ---------- */
|
|
||||||
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) {
|
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) {
|
function onMethodCheckChange(method) {
|
||||||
// 此处仅保留业务钩子,若有额外业务需求可在此处补充
|
// 这里不再与其它项目的 method 产生耦合,直接更新状态即可
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换已选区域展开/收起
|
/**
|
||||||
function toggleSelectedGroup() {
|
* 清空全部已选
|
||||||
selectedGroupExpanded.value = !selectedGroupExpanded.value
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
// 清空所有已选项目及方法
|
|
||||||
function clearAllSelection() {
|
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 ''
|
function toggleSelectedGroup() {
|
||||||
// 去除所有以 “套餐” 开头的前缀(可能出现 “套餐-” 或 “套餐 ” 等情况)
|
selectedGroupExpanded.value = !selectedGroupExpanded.value;
|
||||||
return name.replace(/^套餐[\s-]*/, '')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已选区域标题(已选项目名称拼接,使用 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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.exam-apply {
|
.exam-apply {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 项目卡 */
|
||||||
.item-card {
|
.item-card {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: box-shadow 0.2s;
|
transition: box-shadow 0.2s;
|
||||||
@@ -141,28 +194,34 @@ function formatItemName(name) {
|
|||||||
border-color: #409eff;
|
border-color: #409eff;
|
||||||
box-shadow: 0 0 8px rgba(64, 158, 255, 0.5);
|
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 {
|
.selected-group {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
border: 1px solid #ebeef5;
|
border-top: 1px solid #ebeef5;
|
||||||
border-radius: 4px;
|
padding-top: 10px;
|
||||||
}
|
}
|
||||||
.selected-group-header {
|
.selected-group-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px;
|
|
||||||
background: #f5f7fa;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.selected-group-header .item-name {
|
.selected-group-header .item-name {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
.selected-methods {
|
.selected-methods {
|
||||||
padding: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
.method-item {
|
.method-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user