Merge remote-tracking branch 'origin/dev-h2.45' into dev-2.45hjf

This commit is contained in:
Jafeng 2026-05-28 19:07:03 +08:00
commit 0a26673111
2 changed files with 99 additions and 56 deletions

View File

@ -1,5 +1,5 @@
<template> <template>
<view class="article-detail-page"> <full-page :customScroll="true" pageStyle="background:#fff">
<view v-if="loading" class="loading-container"> <view v-if="loading" class="loading-container">
<uni-icons type="spinner-cycle" size="40" color="#999" /> <uni-icons type="spinner-cycle" size="40" color="#999" />
<text class="loading-text">加载中...</text> <text class="loading-text">加载中...</text>
@ -10,7 +10,7 @@
<button class="retry-btn" @click="loadArticle">重试</button> <button class="retry-btn" @click="loadArticle">重试</button>
</view> </view>
<scroll-view v-else scroll-y class="article-content"> <view v-else class="article-content">
<view class="article-header"> <view class="article-header">
<text class="article-title">{{ articleData.title }}</text> <text class="article-title">{{ articleData.title }}</text>
<text class="article-date">{{ articleData.date }}</text> <text class="article-date">{{ articleData.date }}</text>
@ -20,13 +20,33 @@
<rich-text :nodes="articleData.content"></rich-text> <rich-text :nodes="articleData.content"></rich-text>
</view> </view>
</view> </view>
</scroll-view> </view>
</view> <template #footer>
<button-footer :showConfirm="showStar" cancelText="关闭" @cancel="back()">
<template #confirm>
<view class="flex justify-center items-center h-full border-primary rounded bg-primary" @click="star()">
<uni-icons class="flex-shrinl-0" :type="article.star ? 'star-filled' : 'star'" color="#FFD700"
size="20"></uni-icons>
<view class="text-base text-white">{{ article.star ? '已收藏' : '收藏' }}</view>
</view>
</template>
</button-footer>
</template>
</full-page>
</template> </template>
<script setup> <script setup>
import { computed } from "vue";
import { onLoad } from "@dcloudio/uni-app"; import { onLoad } from "@dcloudio/uni-app";
import { storeToRefs } from "pinia";
import fullPage from '@/components/full-page.vue';
import buttonFooter from '@/components/button-footer.vue';
import useAccountStore from "@/store/account.js";
import useGuard from "@/hooks/useGuard.js";
import api from "@/utils/api.js"; import api from "@/utils/api.js";
import { toast, loading as showLoading, hideLoading } from "@/utils/widget";
import { ref } from "vue"; import { ref } from "vue";
const env = __VITE_ENV__; const env = __VITE_ENV__;
const corpId = env.MP_CORP_ID; const corpId = env.MP_CORP_ID;
@ -39,6 +59,17 @@ const articleData = ref({
}); });
let articleId = ""; let articleId = "";
const article = ref({});
const accountStore = useAccountStore();
const { doctorInfo, account } = storeToRefs(accountStore);
const userId = computed(() => doctorInfo.value?.userid || "");
const { useLoad } = useGuard()
const showStar = computed(() => {
const isMine = article.value && article.value.creatorSignature && article.value.creatorSignature.person && article.value.creatorSignature.person.userId === userId.value;
return userId.value && !isMine;
})
// 使 // 使
const processRichTextContent = (html) => { const processRichTextContent = (html) => {
@ -75,7 +106,7 @@ const loadArticle = async () => {
loading.value = true; loading.value = true;
error.value = ""; error.value = "";
try { try {
const res = await api("getArticle", { id: articleId, corpId }); const res = await api("getArticle", { id: articleId, corpId, userId: userId.value });
if (res.success && res.data) { if (res.success && res.data) {
// //
@ -87,7 +118,7 @@ const loadArticle = async () => {
const day = String(d.getDate()).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0");
date = `${year}-${month}-${day}`; date = `${year}-${month}-${day}`;
} }
article.value = res.data;
articleData.value = { articleData.value = {
title: res.data.title || "宣教文章", title: res.data.title || "宣教文章",
content: processRichTextContent(res.data.content || ""), content: processRichTextContent(res.data.content || ""),
@ -104,7 +135,23 @@ const loadArticle = async () => {
} }
}; };
onLoad((options) => { async function star() {
showLoading();
const res = await api("starArticle", { articleId: articleId, userId: userId.value, star: !article.value.star });
hideLoading();
if (res.success) {
uni.$emit('changeArticleStar', article.value._id);
loadArticle()
} else {
toast(res.message || "操作失败");
}
}
function back() {
uni.navigateBack();
}
useLoad((options) => {
if (options.id) { if (options.id) {
articleId = options.id; articleId = options.id;
loadArticle(); loadArticle();
@ -116,12 +163,11 @@ onLoad((options) => {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.article-detail-page { .ml-15 {
width: 100%; margin-left: 30rpx;
height: 100vh;
background-color: #fff;
} }
.loading-container, .loading-container,
.error-container { .error-container {
display: flex; display: flex;

View File

@ -18,26 +18,20 @@
<empty-data title="暂无文章" /> <empty-data title="暂无文章" />
</view> </view>
<view v-else> <view v-else>
<view v-for="article in searchList" :key="article._id" class="article-item "> <view v-for="article in searchList" :key="article._id" class="article-item" @click="previewArticle(article)">
<view> <view>
<view class="flex items-center py-12 px-15"> <view class="flex items-center py-12 px-15">
<view class="text-lg leading-normal font-semibold w-0 flex-grow truncate mr-10"> <view class="text-lg leading-normal font-semibold w-0 flex-grow truncate mr-10">
{{ article.title }} {{ article.title }}
</view> </view>
<view @click.stop="star(article)"> <view v-if="!article.isMine" @click.stop="star(article)">
<uni-icons class="flex-shrinl-0" :type="article.star ? 'star-filled' : 'star'" <uni-icons class="flex-shrinl-0" :type="article.star ? 'star-filled' : 'star'"
:color="article.star ? '#FFD700' : '#999'" size="20"></uni-icons> :color="article.star ? '#FFD700' : '#999'" size="20"></uni-icons>
</view> </view>
</view> </view>
<view v-if="article.authorName" class="px-15 mb-10 truncate text-base text-dark">{{ article.authorName }} <view class="flex items-center px-15 pb-10">
</view> <view class="mr-10 w-0 flex-growt runcate text-base text-dark">{{ article.authorName }}</view>
<view class="border-b"></view> <view class="bg-primary text-white text-sm px-10 leading-normal rounded-sm"
<view class="flex items-center justify-between py-10 px-15">
<view class="flex items-center" @click="previewArticle(article)">
<view class="text-base text-primary">文章详情</view>
<uni-icons type="right" color="#0074ff" size="16"></uni-icons>
</view>
<view class="bg-primary text-white text-base px-15 py-5 rounded-sm"
@click.stop="handlePrimaryAction(article)"> @click.stop="handlePrimaryAction(article)">
{{ isSelectMode ? '选择' : '发送' }} {{ isSelectMode ? '选择' : '发送' }}
</view> </view>
@ -75,20 +69,25 @@
</view> </view>
<view v-else> <view v-else>
<view v-for="article in articleList" :key="article._id" class="article-item "> <view v-for="article in articleList" :key="article._id" class="article-item" @click="previewArticle(article)">
<view> <view>
<view class="flex items-center py-12 px-15"> <view class="flex items-center py-12 px-15">
<view class="text-lg leading-normal font-semibold w-0 flex-grow truncate mr-10"> <view class="text-lg leading-normal font-semibold w-0 flex-grow truncate mr-10">
{{ article.title }} {{ article.title }}
</view> </view>
<view @click.stop="star(article)"> <view v-if="!article.isMine" @click.stop="star(article)">
<uni-icons class="flex-shrinl-0" :type="article.star ? 'star-filled' : 'star'" <uni-icons class="flex-shrinl-0" :type="article.star ? 'star-filled' : 'star'"
:color="article.star ? '#FFD700' : '#999'" size="20"></uni-icons> :color="article.star ? '#FFD700' : '#999'" size="20"></uni-icons>
</view> </view>
</view> </view>
<view v-if="article.authorName" class="px-15 mb-10 truncate text-base text-dark">{{ article.authorName }} <view class="flex items-center px-15 pb-10">
<view class="mr-10 w-0 flex-grow truncate text-base text-dark">{{ article.authorName }}</view>
<view class="bg-primary text-white text-sm px-10 leading-normal rounded-sm"
@click.stop="handlePrimaryAction(article)">
{{ isSelectMode ? '选择' : '发送' }}
</view>
</view> </view>
<view class="border-b"></view> <!-- <view class="border-b"></view>
<view class="flex items-center justify-between py-10 px-15"> <view class="flex items-center justify-between py-10 px-15">
<view class="flex items-center" @click="previewArticle(article)"> <view class="flex items-center" @click="previewArticle(article)">
<view class="text-base text-primary">文章详情</view> <view class="text-base text-primary">文章详情</view>
@ -98,7 +97,7 @@
@click.stop="handlePrimaryAction(article)"> @click.stop="handlePrimaryAction(article)">
{{ isSelectMode ? '选择' : '发送' }} {{ isSelectMode ? '选择' : '发送' }}
</view> </view>
</view> </view> -->
<!-- <view class="article-footer"> <!-- <view class="article-footer">
<text class="article-date">{{ article.date }}</text> <text class="article-date">{{ article.date }}</text>
<button class="send-btn" size="mini" type="primary" @click.stop="handlePrimaryAction(article)"> <button class="send-btn" size="mini" type="primary" @click.stop="handlePrimaryAction(article)">
@ -156,7 +155,7 @@
<script setup> <script setup>
import { ref, computed, watch } from "vue"; import { ref, computed, watch } from "vue";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { onLoad } from "@dcloudio/uni-app"; import { onLoad, onUnload, onShow } from "@dcloudio/uni-app";
import api from "@/utils/api.js"; import api from "@/utils/api.js";
import useAccountStore from "@/store/account.js"; import useAccountStore from "@/store/account.js";
import useGuard from "@/hooks/useGuard.js"; import useGuard from "@/hooks/useGuard.js";
@ -166,7 +165,6 @@ import fullPage from '@/components/full-page.vue';
import EmptyData from "@/components/empty-data.vue"; import EmptyData from "@/components/empty-data.vue";
import { toast, loading as showLoading, hideLoading } from "@/utils/widget"; import { toast, loading as showLoading, hideLoading } from "@/utils/widget";
const accountStore = useAccountStore(); const accountStore = useAccountStore();
const env = __VITE_ENV__; const env = __VITE_ENV__;
const corpId = env.MP_CORP_ID; const corpId = env.MP_CORP_ID;
@ -186,6 +184,7 @@ const pageParams = ref({
}); });
const currentCateId = ref("my"); // ""_id const currentCateId = ref("my"); // ""_id
const teams = ref([]) const teams = ref([])
const showRefresh = ref(false);
const cateList = computed(() => { const cateList = computed(() => {
const arr = [{ label: '我的文章', value: 'my', type: 'mine' }, { label: '团队文章', value: 'team', type: 'team' }]; const arr = [{ label: '我的文章', value: 'my', type: 'mine' }, { label: '团队文章', value: 'team', type: 'team' }];
@ -282,31 +281,9 @@ const processRichTextContent = (html) => {
// //
const previewArticle = async (article) => { const previewArticle = async (article) => {
try { uni.navigateTo({
uni.showLoading({ title: "加载中..." }); url: `/pages/message/article-detail?id=${article._id}`,
const res = await api("getArticle", { id: article._id, corpId: corpId }); });
uni.hideLoading();
if (res.success && res.data) {
previewArticleData.value = {
title: res.data.title || article.title,
content: processRichTextContent(res.data.content || ""),
};
previewPopup.value?.open();
} else {
uni.showToast({
title: res.message || "预览文章失败",
icon: "none",
});
}
} catch (error) {
uni.hideLoading();
console.error("预览文章失败:", error);
uni.showToast({
title: "预览失败,请重试",
icon: "none",
});
}
}; };
// //
@ -470,6 +447,9 @@ async function star(article) {
hideLoading(); hideLoading();
if (res.success) { if (res.success) {
article.star = !article.star; article.star = !article.star;
if (currentCateId.value === 'my') {
getMyArticleList();
}
} else { } else {
toast(res.message || "操作失败"); toast(res.message || "操作失败");
} }
@ -479,6 +459,7 @@ function formatArticles(articles) {
return articles.map(article => { return articles.map(article => {
const item = { ...article }; const item = { ...article };
if (article.creatorSignature && article.creatorSignature.type === 'personal') { if (article.creatorSignature && article.creatorSignature.type === 'personal') {
item.isMine = article.creatorSignature.person.userId === userId.value;
item.authorName = article.creatorSignature.person ? article.creatorSignature.person.userName : ''; item.authorName = article.creatorSignature.person ? article.creatorSignature.person.userName : '';
} else if (article.creatorSignature && article.creatorSignature.type === 'institution') { } else if (article.creatorSignature && article.creatorSignature.type === 'institution') {
item.authorName = article.creatorSignature.institution ? article.creatorSignature.institution.name : ''; item.authorName = article.creatorSignature.institution ? article.creatorSignature.institution.name : '';
@ -521,11 +502,27 @@ onLoad((options) => {
pageParams.value.teamId = options.teamId; pageParams.value.teamId = options.teamId;
} }
ensureTeamId(); ensureTeamId();
uni.$on('changeArticleStar', (articleId) => {
if (articleList.value.some(i => i._id === articleId)) {
showRefresh.value = true;
}
});
}); });
useLoad(() => { useLoad(() => { });
onUnload(() => {
uni.$off('changeArticleStar');
}); });
onShow(() => {
if (showRefresh.value) {
page.value = 1;
getArticleList();
showRefresh.value = false;
}
});
watch(userId, n => { watch(userId, n => {
if (n) { if (n) {
getArticleList(); getArticleList();