diff --git a/pages/case/plan-list.vue b/pages/case/plan-list.vue index cb9c3af..6df936a 100644 --- a/pages/case/plan-list.vue +++ b/pages/case/plan-list.vue @@ -240,10 +240,21 @@ function onSearchInput() { function canToggleFavorite(plan) { if (!plan || !plan._id) return false; + if (isCreatedByCurrentUser(plan)) return false; if (activeViewType.value !== 'my') return true; return Boolean(plan.isFavorite); } +function isCreatedByCurrentUser(plan = {}) { + const userId = getUserId(); + if (!userId) return false; + if (String(plan.createor || plan.creator || '') === userId) return true; + const signature = plan.creatorSignature && typeof plan.creatorSignature === 'object' + ? plan.creatorSignature + : null; + return signature?.type === 'personal' && String(signature.person?.userId || '') === userId; +} + async function toggleFavorite(plan) { const corpId = getCorpId(); const userId = getUserId(); @@ -355,10 +366,8 @@ function preview(plan) { .category-text { width: 100%; - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; + display: block; + overflow: visible; word-break: break-all; } @@ -430,9 +439,7 @@ function preview(plan) { color: #6b7280; font-size: 30rpx; line-height: 40rpx; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + word-break: break-all; } .task-link { diff --git a/pages/message/common-phrases.vue b/pages/message/common-phrases.vue index cb08211..9ce70ed 100644 --- a/pages/message/common-phrases.vue +++ b/pages/message/common-phrases.vue @@ -21,14 +21,23 @@ + + + {{ category.name }} { return activeTab.value === "more" ? corpCategories.value : myCategories.value; }); +const getCategorySortValue = (category) => { + return category?.sort >= 0 ? Number(category.sort) : 10000; +}; + +const buildCategoryTree = (categories) => { + if (!Array.isArray(categories)) return []; + + const level3 = categories + .filter((item) => item.level === 3) + .map((item) => { + const children = categories.filter((child) => child.parentId === item.id).map((child) => ({ ...child })); + const childrenIds = children.map((child) => child.id); + return { ...item, children, childrenIds }; + }); + + const level2 = categories + .filter((item) => item.level === 2) + .map((item) => { + const children = level3.filter((child) => child.parentId === item.id).map((child) => ({ ...child })); + const childrenIds = children.map((child) => child.id); + children.forEach((child) => { + childrenIds.push(child.id); + if (Array.isArray(child.childrenIds)) { + childrenIds.push(...child.childrenIds); + } + }); + return { ...item, children, childrenIds }; + }); + + return categories + .filter((item) => item.level === 1) + .map((item) => { + const children = level2.filter((child) => child.parentId === item.id); + const childrenIds = children.map((child) => child.id); + children.forEach((child) => { + if (Array.isArray(child.childrenIds) && child.childrenIds.length > 0) { + childrenIds.push(...child.childrenIds); + } + }); + return { ...item, children, childrenIds }; + }) + .sort((a, b) => getCategorySortValue(a) - getCategorySortValue(b)); +}; + +const flattenCategoryTree = (nodes, visibleOnly = false) => { + const list = []; + const walk = (items) => { + items.forEach((item) => { + const children = Array.isArray(item.children) ? item.children : []; + const expanded = expandedCategoryMap.value[item.id] !== false; + list.push({ ...item, hasChildren: children.length > 0, expanded }); + if (children.length > 0 && (!visibleOnly || expanded)) { + walk(item.children); + } + }); + }; + walk(nodes); + return list; +}; + +const currentCategoryTree = computed(() => buildCategoryTree(currentCategories.value)); + +const currentCategoryNodes = computed(() => flattenCategoryTree(currentCategoryTree.value, true)); + const currentCategoryName = computed(() => { const category = currentCategories.value.find((item) => item.id === currentCategory.value); return category ? category.name : "未选择"; }); const firstMyCategory = computed(() => { - return myCategories.value[0]; + return flattenCategoryTree(buildCategoryTree(myCategories.value))[0]; +}); + +const firstCorpCategory = computed(() => { + return flattenCategoryTree(buildCategoryTree(corpCategories.value))[0]; }); const defaultMyCategory = computed(() => { @@ -393,7 +471,7 @@ const normalizeCategory = (item, categoryType) => { return { id: item._id || item.id, name, - sort: item.sort || 0, + sort: item.sort, level: item.level || 1, parentId: item.parentId || "", type: categoryType, @@ -402,6 +480,10 @@ const normalizeCategory = (item, categoryType) => { }; }; +const sortFlatCategoriesByTree = (categories) => { + return flattenCategoryTree(buildCategoryTree(categories)).map(({ hasChildren, expanded, ...item }) => item); +}; + const normalizeFiles = (files) => { if (!Array.isArray(files)) return []; return files @@ -470,19 +552,37 @@ const switchTab = (tab) => { currentCategory.value = tab === "mine" ? defaultMyCategory.value?.id || firstMyCategory.value?.id || "" - : corpCategories.value[0]?.id || ""; + : firstCorpCategory.value?.id || ""; }; const toggleEditMode = () => { isEditMode.value = !isEditMode.value; }; +const getCategoryItemStyle = (category) => { + const level = Math.max(Number(category?.level || 1), 1); + return { + paddingLeft: `${8 + (level - 1) * 12}rpx`, + }; +}; + +const toggleCategoryExpand = (category) => { + if (!category?.hasChildren) return; + expandedCategoryMap.value = { + ...expandedCategoryMap.value, + [category.id]: !category.expanded, + }; +}; + const handleCategoryClick = (category) => { if (activeTab.value === "mine" && isEditMode.value && category.deletable) { editCategory(category); return; } currentCategory.value = category.id; + if (category.hasChildren) { + toggleCategoryExpand(category); + } }; const sendPhrase = (phrase) => { @@ -882,7 +982,8 @@ const loadMyCategories = async ({ ensureDefault = true } = {}) => { const result = await api("getUserCommonWordCate", { corpId, userId }); if (result.success) { const list = Array.isArray(result.list) ? result.list : []; - myCategories.value = list.map((item) => normalizeCategory(item, "user")).filter((item) => item.id); + const categories = list.map((item) => normalizeCategory(item, "user")).filter((item) => item.id); + myCategories.value = sortFlatCategoriesByTree(categories); if (ensureDefault && !defaultMyCategory.value) { await createDefaultMyCategory(); return; @@ -922,9 +1023,10 @@ const loadCorpCategories = async () => { const result = await api("getCorpCommonWordCate", { corpId, userId }, false); if (result.success) { const list = Array.isArray(result.list) ? result.list : []; - corpCategories.value = list.map((item) => normalizeCategory(item, "corp")).filter((item) => item.id); + const categories = list.map((item) => normalizeCategory(item, "corp")).filter((item) => item.id); + corpCategories.value = sortFlatCategoriesByTree(categories); if (activeTab.value === "more" && !currentCategory.value) { - currentCategory.value = corpCategories.value[0]?.id || ""; + currentCategory.value = firstCorpCategory.value?.id || ""; } } }; @@ -1051,7 +1153,7 @@ $border-color: #edf0f5; min-height: 88rpx; display: flex; align-items: center; - justify-content: center; + justify-content: flex-start; padding: 12rpx 16rpx; color: #344054; background: #fff; @@ -1080,15 +1182,28 @@ $border-color: #edf0f5; .category-content { position: relative; width: 100%; - text-align: center; + display: flex; + align-items: center; + min-width: 0; } .category-name { + flex: 1; + min-width: 0; font-size: 30rpx; line-height: 40rpx; word-break: break-all; } + .category-toggle { + width: 32rpx; + height: 40rpx; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + } + .delete-badge { position: absolute; top: -22rpx; diff --git a/pages/message/index.vue b/pages/message/index.vue index 926aa1c..2c7231a 100644 --- a/pages/message/index.vue +++ b/pages/message/index.vue @@ -994,43 +994,34 @@ onHide(() => { const sendCommonPhrase = async (phraseOrContent) => { if (!chatInputRef.value) return; - - const content = - typeof phraseOrContent === "string" + + const content = + typeof phraseOrContent === "string" ? phraseOrContent : phraseOrContent?.content || ""; const files = Array.isArray(phraseOrContent?.files) ? phraseOrContent.files : []; + const hasContent = Boolean(content.trim()); - if (content.trim()) { - chatInputRef.value.setInputText(content); - } - - if (files.length === 0) { + if (!hasContent && files.length === 0) { return; } - const shouldSendImages = await new Promise((resolve) => { - uni.showModal({ - title: "提示", - content: `该常用语包含${files.length}张图片,是否发送图片?`, - cancelText: "取消", - confirmText: "发送", - success: (res) => { - resolve(Boolean(res.confirm)); - }, - fail: (error) => { - console.error("常用语图片发送确认失败:", error); - resolve(false); - }, - }); - }); - - if (!shouldSendImages) { - return; + if (files.length > 0) { + uni.showLoading({ title: "发送中...", mask: true }); } - for (const file of files) { - await chatInputRef.value.sendImageMessageFromPhrase(file); + try { + if (hasContent) { + await chatInputRef.value.sendTextMessageFromPhrase(content); + } + + for (const file of files) { + await chatInputRef.value.sendImageMessageFromPhrase(file); + } + } finally { + if (files.length > 0) { + uni.hideLoading(); + } } };