zhb 2 maanden geleden
bovenliggende
commit
40f13af752

+ 147 - 0
components/cwg-notice-drawer.vue

@@ -0,0 +1,147 @@
+<template>
+    <uni-popup ref="popupRef" type="right" background-color="#fff">
+        <view class="right-drawer">
+            <view class="notification-list" v-if="list.length">
+                <view v-for="item in list" :key="item.id" class="notification-item" @click="goPages(item)">
+                    <view class="item-content">
+                        <view class="item-title">{{ item.subject }}</view>
+                        <view class="item-time">{{ item.addTime }}</view>
+                    </view>
+                    <view class="item-badge" v-if="item.read == 0">
+                        <view class="dot"></view>
+                    </view>
+                </view>
+            </view>
+            <cwg-empty-state v-else />
+            <view class="logout-wrap">
+                <view class="logout-btn" @click="goMore">
+                    <cwg-icon name="logout" :size="16" color="#ff9800" />
+                    <text v-t="'News.More'" />
+                </view>
+            </view>
+        </view>
+    </uni-popup>
+</template>
+
+<script setup lang="ts">
+import { computed, ref, onMounted } from 'vue'
+import { newsApi } from '@/service/news'
+import { useI18n } from "vue-i18n";
+const { locale } = useI18n();
+import useRouter from "@/hooks/useRouter";
+const router = useRouter();
+const popupRef = ref<any>(null)
+const list = ref([])
+const getList = async () => {
+    const res = await newsApi.newsNoticeList({
+        page: { current: 1, row: 10 },
+        lang: locale.value
+    })
+    if (res.data && res.code == 200) {
+        list.value = res.data
+    } else {
+        list.value = []
+    }
+}
+function open() {
+    popupRef.value?.open()
+    getList()
+}
+function close() {
+    popupRef.value?.close()
+}
+const goPages = (e) => {
+    router.push({
+        path: '/pages/analytics/detail',
+        query: {
+            id: e.id,
+            type: 7
+        }
+    })
+    close()
+}
+const goMore = () => {
+    router.push({
+        path: '/pages/common/notice'
+    })
+    close()
+}
+function handleLogout() {
+    close()
+}
+
+defineExpose({
+    open,
+    close
+})
+</script>
+
+<style scoped lang="scss">
+@import "@/uni.scss";
+
+.right-drawer {
+    width: 300px;
+    height: 100vh;
+    display: flex;
+    flex-direction: column;
+    padding: 20px 16px;
+}
+
+.notification-list {
+    width: 100%;
+}
+
+.notification-item {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: px2rpx(12) px2rpx(16);
+    border-bottom: 1px solid #f0f0f0;
+    cursor: pointer;
+
+    .item-content {
+        flex: 1;
+
+        .item-title {
+            font-size: px2rpx(14);
+            color: #333;
+            line-height: 1.4;
+            margin-bottom: px2rpx(4);
+        }
+
+        .item-time {
+            font-size: px2rpx(12);
+            color: #999;
+        }
+    }
+
+    .item-badge {
+        margin-left: px2rpx(12);
+
+        .dot {
+            width: px2rpx(8);
+            height: px2rpx(8);
+            background-color: #f56c6c;
+            border-radius: 50%;
+        }
+    }
+}
+
+.logout-wrap {
+    margin-top: auto;
+    padding: 20px 16px;
+    margin-bottom: 20px;
+}
+
+.logout-btn {
+    height: 44px;
+    background: #f4eadf;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 8px;
+    color: #ff9800;
+    font-weight: 600;
+    cursor: pointer;
+}
+</style>

+ 53 - 0
components/cwg-notice.vue

@@ -0,0 +1,53 @@
+<template>
+    <view class="pc-header-btn" :class="{ 'has-dot': isRed }">
+        <cwg-icon name="xxtz" color="#141d22" @click="openNotice" />
+    </view>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import { useI18n } from 'vue-i18n'
+const { t, locale } = useI18n()
+import { newsApi } from '@/service/news'
+const isRed = ref(false)
+function openNotice() {
+    uni.$emit('open-notice-drawer')
+}
+const getData = async () => {
+    const res = await newsApi.newsNoticeList({
+        page: { current: 1, row: 10 },
+        lang: locale.value
+    })
+    if (res.data && res.code == 200) {
+        isRed.value = true
+    } else {
+        isRed.value = false
+    }
+}
+onMounted(() => {
+    getData()
+})
+</script>
+
+<style scoped lang="scss">
+@import "@/uni.scss";
+
+.pc-header-btn {
+    position: relative;
+
+    &::after {
+        content: '';
+        position: absolute;
+        top: px2rpx(4);
+        right: px2rpx(4);
+        width: px2rpx(8);
+        height: px2rpx(8);
+        background-color: #f56c6c;
+        border-radius: 50%;
+    }
+
+    &.has-dot::after {
+        display: block;
+    }
+}
+</style>

+ 7 - 1
components/cwg-page-wrapper.vue

@@ -36,6 +36,8 @@
     </cwg-match-media>
     <cwg-right-drawer v-if="!isLoginPage" ref="rightDrawerRef" @navigate="handleDrawerNavigate"
       @logout="handleDrawerLogout" />
+    <cwg-notice-drawer v-if="!isLoginPage" ref="noticeDrawerRef" @navigate="handleDrawerNavigate"
+      @logout="handleDrawerLogout" />
   </view>
 </template>
 
@@ -75,7 +77,11 @@ const isTabBarPage = ref(false)
 
 
 const rightDrawerRef = ref<any>(null)
-
+const noticeDrawerRef = ref<any>(null)
+uni.$on('open-notice-drawer', (data) => {
+  console.log(121212)
+  noticeDrawerRef.value?.open()
+})
 uni.$on('open-right-drawer', (data) => {
   openRightDrawer()
 })

+ 4 - 3
components/cwg-pc-header.vue

@@ -8,9 +8,7 @@
 			<image class="left-img" src="/static/images/logo.png" mode="widthFix" alt="logo" />
 		</div>
 		<div class="right">
-			<view class="pc-header-btn">
-				<cwg-icon name="xxtz" color="#141d22" />
-			</view>
+			<cwg-notice />
 			<view class="pc-header-btn" @click="openRightDrawer">
 				<cwg-icon name="icon_my" color="#141d22" />
 			</view>
@@ -39,6 +37,9 @@ function toggleTheme() {
 function openRightDrawer() {
 	emit('open-right-drawer');
 }
+function openNotice() {
+	uni.$emit('open-notice-drawer')
+}
 
 function openLeftDrawer() {
 	emit('open-left-drawer');

+ 1 - 0
components/cwg-sidebar.vue

@@ -105,6 +105,7 @@ const menu = ref<MenuItem[]>(
                 { path: '/pages/mine/info?type=2', isOpenMenu: false, label: 'PersonalManagement.Title.BankInformation', icon: 'crm-headset' },
                 { path: '/pages/mine/info?type=3', isOpenMenu: false, label: 'PersonalManagement.Title.FileManagement', icon: 'crm-headset' },
                 { path: '/pages/mine/info?type=4', isOpenMenu: false, label: 'PersonalManagement.Title.SecurityCenter', icon: 'crm-headset' },
+                { path: '/pages/common/notice', isOpenMenu: false, label: 'News.Notice', icon: 'crm-headset' },
             ]
         },
         {

+ 39 - 13
components/cwg-tabel.vue

@@ -153,6 +153,7 @@ const props = defineProps({
     headerStyle: { type: Object, default: () => ({}) },
     stickyHeader: { type: Boolean, default: true },
     stickyOffset: { type: [String, Number], default: '0' },
+    isPages: { type: Boolean, default: false },
 })
 
 const emit = defineEmits([
@@ -161,7 +162,8 @@ const emit = defineEmits([
     'page-change',
     'load-success',
     'load-error',
-    'sort-change'
+    'sort-change',
+    'go-pages'
 ])
 
 // ========== 响应式状态 ==========
@@ -387,13 +389,19 @@ const toggleRowExpand = (rowIndex) => {
 }
 // 打开详情弹窗(移动端使用)
 const openRowDetail = (row) => {
-    detailItems.value = props.columns
-        .filter((column) => column && column.prop && column.label)
-        .map((column) => ({
-            label: column.label,
-            value: String(formatCellValue(row[column.prop], column, row))
-        }))
-    detailVisible.value = true
+
+    if (props.isPages) {
+        emit('go-pages', row)
+    } else {
+        detailItems.value = props.columns
+            .filter((column) => column && column.prop && column.label)
+            .map((column) => ({
+                label: column.label,
+                value: String(formatCellValue(row[column.prop], column, row))
+            }))
+        detailVisible.value = true
+    }
+
 }
 
 // ========== 数据加载 ==========
@@ -401,10 +409,23 @@ const loadData = async () => {
     if (loading.value) return
     loading.value = true
     try {
+        const applyPaginationFromRes = (res) => {
+            const page = res && res.page
+            if (!page) return false
+            if (typeof page.current === 'number') pagination.value.current = page.current
+            if (typeof page.row === 'number') pagination.value.pageSize = page.row
+            if (typeof page.rowTotal === 'number') pagination.value.total = page.rowTotal
+            if (typeof page.pageTotal === 'number') pagination.value.pages = page.pageTotal
+            else if (typeof pagination.value.total === 'number' && typeof pagination.value.pageSize === 'number') {
+                pagination.value.pages = Math.ceil(pagination.value.total / pagination.value.pageSize)
+            }
+            return true
+        }
+
         const params = {
             page: {
                 current: pagination.value.current,
-                size: pagination.value.pageSize
+                row: pagination.value.pageSize
             },
             ...props.queryParams
         }
@@ -415,14 +436,19 @@ const loadData = async () => {
         const res = await props.api(params)
         if (res.code === 200 || res.code === 0) {
             const data = res.data || res
+            const hasPage = applyPaginationFromRes(res)
             if (Array.isArray(data)) {
                 tableData.value = data
-                pagination.value.total = data.length
-                pagination.value.pages = 1
+                if (!hasPage) {
+                    pagination.value.total = data.length
+                    pagination.value.pages = 1
+                }
             } else if (data.list || data.records) {
                 tableData.value = data.list || data.records
-                pagination.value.total = data.total || data.list?.length || 0
-                pagination.value.pages = data.pages || Math.ceil(pagination.value.total / pagination.value.pageSize)
+                if (!hasPage) {
+                    pagination.value.total = data.total || data.list?.length || 0
+                    pagination.value.pages = data.pages || Math.ceil(pagination.value.total / pagination.value.pageSize)
+                }
             } else {
                 tableData.value = []
             }

+ 7 - 0
pages.json

@@ -246,6 +246,13 @@
         "navigationBarTitleText": "",
         "navigationStyle": "custom"
       }
+    },
+    {
+      "path": "pages/common/notice",
+      "style": {
+        "navigationBarTitleText": "",
+        "navigationStyle": "custom"
+      }
     }
   ],
   "tabBar": {

+ 406 - 13
pages/analytics/detail.vue

@@ -1,23 +1,416 @@
 <template>
     <cwg-page-wrapper class="create-page" :isHeaderFixed="true">
-        <cwg-header :title="t('Home.page_ib.item9')" />
-        <view class="account-section">
+        <cwg-header :title="currentTitleText" />
+        <view id="News_Content" class="news-content">
+            <!-- 观点分析 / 新闻资讯 / 公告 / 新闻资讯(通讯) -->
+            <view class="content crm-border-radius" v-if="[1, 2, 3, 5].includes(type)">
+                <view class="img" v-if="imgContentIf">
+                    <image :src="imgContent" mode="widthFix" @click="previewImage(imgContent)" />
+                </view>
+                <rich-text :nodes="Content" />
+            </view>
+
+            <!-- 视频评论(WebTV 视频) -->
+            <view class="content crm-border-radius" v-if="type === 4">
+                <text class="con-title">{{ info.title }}</text>
+                <view class="con-time" style="display: flex; justify-content: space-between;">
+                    <text>{{ info.createTime }}</text>
+                    <image src="/static/acc_logo.png" style="height: 80rpx;" mode="heightFix" />
+                </view>
+                <view class="my_video" style="width: 100%">
+                    <video :id="`dplayer-${title}`" :src="info.url" controls class="video-player" />
+                </view>
+                <text class="con-des">{{ info.description }}</text>
+            </view>
+
+            <!-- 视频评论(另一种) -->
+            <view class="content crm-border-radius" v-if="type === 6">
+                <text class="con-title">{{ info.title }}</text>
+                <text class="con-time">{{ info.subTitle }}</text>
+                <view class="my_video" style="width: 100%">
+                    <video :id="`dplayer-${type}`" :src="imgContent" controls class="video-player" />
+                </view>
+                <view class="rich-text-wrapper">
+                    <rich-text :nodes="info.content" />
+                </view>
+            </view>
+
+            <!-- 公告详情 -->
+            <view class="content crm-border-radius" v-if="type === 7">
+                <text class="con-title">{{ info.subject }}</text>
+                <view class="rich-text-wrapper">
+                    <rich-text :nodes="info.content" />
+                </view>
+            </view>
+
+            <!-- 交易观点 / 财经日历(内嵌网页) -->
+            <view class="content crm-border-radius" v-if="[8, 9].includes(title)">
+                <web-view :src="imgContent" class="webview" />
+            </view>
+
+            <!-- 电子书 -->
+            <view class="content crm-border-radius" v-if="type === 10">
+                <view class="ebookBox">
+                    <image :src="imgUrl + info.coverImage" mode="widthFix" class="ebook-cover" />
+                    <view>
+                        <text class="news-title">{{ info.title }}</text>
+                        <view class="rich-text-wrapper">
+                            <rich-text :nodes="info.content" />
+                        </view>
+                        <view class="news-status">
+                            <a :href="imgUrl + info.bookUrl" target="_blank" v-t="'blockchain.item12'"></a>
+                        </view>
+                    </view>
+                </view>
+            </view>
+
+            <!-- 视频评论(iframe 嵌入) -->
+            <view class="content crm-border-radius" v-if="type === 11">
+                <view style="display: flex; justify-content: end; margin-bottom: 30rpx;">
+                    <image src="/static/acc_logo.png" style="height: 80rpx;" mode="heightFix" />
+                </view>
+                <view style="width: 80%; margin: auto;">
+                    <web-view v-if="isZh" src="https://videos.tradingcentral.cn/players/H5QuTuut-iodula4l.html" />
+                    <web-view v-else src="https://videos.tradingcentral.cn/players/SHILp3nA-iodula4l.html" />
+                </view>
+            </view>
         </view>
     </cwg-page-wrapper>
 </template>
 
-<script setup lang="ts">
-import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
+<script setup>
+import { ref, onMounted, onUnmounted, computed } from 'vue'
 import { onLoad } from '@dcloudio/uni-app'
-import { useI18n } from 'vue-i18n' // uni-app 中已集成,但需配置
-import { customApi } from '@/service/custom'
-import { financialApi } from '@/service/financial'
+import { useI18n } from 'vue-i18n'
+import { newsApi } from '@/service/news'
 import Config from '@/config/index'
-import AddBankDialog from '@/components/AddBankDialog.vue';
-import PaymentMethodsList from './components/PaymentMethodsList.vue'
-const { t, locale } = useI18n()
-const isZh = computed(() => ['cn', 'zhHant'].includes(locale.value))
+
+const { t } = useI18n()
+const { Code, Host80 } = Config
+
+// 路由参数
+const type = ref(null)
+const id = ref(null)
+const currentTitleText = computed(() => {
+    const map = {
+        1: t('News.ViewAnalysis'),
+        2: t('News.NewsInformation'),
+        3: t('News.Announcement'),
+        4: t('News.VideoCommentary'),
+        5: t('News.NewsInformation'),
+        6: t('News.VideoCommentary'),
+        7: t('News.Notice'),
+        8: t('News.TradeIdeas'),
+        9: t('News.FinancialCalendar'),
+        10: t('News.Ebook'),
+        11: t('News.VideoCommentary')
+    }
+    return map[type.value] || ''
+})
+// 数据
+const Content = ref('')
+const imgContent = ref('')
+const imgContentIf = ref(false)
+const info = ref({})
+const imgUrl = Host80
+
+// 语言判断
+const isZh = computed(() => ['cn', 'zh', 'zhHant'].includes(locale.value));
+
+// 图片预览
+const previewImage = (url) => {
+    uni.previewImage({
+        urls: [url]
+    })
+}
+
+// 获取详情(根据 title 调用不同接口)
+const getNewsSingle = async () => {
+    if (!type.value) return
+
+    switch (type.value) {
+        case 1: // 观点分析
+            const analysisRes = await newsApi.newsAnalysisSingle({ id: id.value })
+            if (analysisRes.code === Code.StatusOK && analysisRes.data) {
+                imgContent.value = analysisRes.data.media
+                Content.value = analysisRes.data.content
+                imgContentIf.value = !!analysisRes.data.media
+            } else {
+                uni.showToast({ title: analysisRes.msg, icon: 'none' })
+            }
+            break
+
+        case 3: // 公告
+            const infoRes = await newsApi.newsInformationSingle({ id: id.value })
+            if (infoRes.code === Code.StatusOK && infoRes.data) {
+                imgContent.value = Host80 + infoRes.data.coverImage
+                Content.value = infoRes.data.content
+                imgContentIf.value = !!infoRes.data.coverImage
+            } else {
+                uni.showToast({ title: infoRes.msg, icon: 'none' })
+            }
+            break
+
+        case 7: // 通知
+            const noticeRes = await newsApi.newsNoticeSingle({ id: id.value })
+            if (noticeRes.code === Code.StatusOK && noticeRes.data) {
+                info.value = noticeRes.data
+            } else {
+                uni.showToast({ title: noticeRes.msg, icon: 'none' })
+            }
+            break
+
+        case 4: // WebTV 视频
+            const webTvRes = await newsApi.newsWebTvSearchSingle({ id: id.value })
+            if (webTvRes.code === Code.StatusOK && webTvRes.data) {
+                info.value = webTvRes.data
+                // 视频通过 video 组件自动播放,无需手动初始化 DPlayer
+            } else {
+                uni.showToast({ title: webTvRes.msg, icon: 'none' })
+            }
+            break
+
+        case 5: // 新闻通讯
+            const newsletterRes = await newsApi.newsInformationNewsletterSingle({ id: id.value })
+            if (newsletterRes.code === Code.StatusOK && newsletterRes.data) {
+                imgContent.value = Host80 + newsletterRes.data.coverImage
+                Content.value = newsletterRes.data.content
+                imgContentIf.value = !!newsletterRes.data.coverImage
+            } else {
+                uni.showToast({ title: newsletterRes.msg, icon: 'none' })
+            }
+            break
+
+        case 6: // 视频评论
+            const videoRes = await newsApi.newsVideoSingle({ id: id.value })
+            if (videoRes.code === Code.StatusOK && videoRes.data) {
+                info.value = videoRes.data
+                imgContent.value = videoRes.data.videoUrl.includes('http')
+                    ? videoRes.data.videoUrl
+                    : Host80 + videoRes.data.videoUrl
+            } else {
+                uni.showToast({ title: videoRes.msg, icon: 'none' })
+            }
+            break
+
+        case 8: // 交易观点
+            const shakeRes = await newsApi.handShakeGet({})
+            if (shakeRes.code === Code.StatusOK) {
+                imgContent.value = shakeRes.msg
+            } else {
+                uni.showToast({ title: shakeRes.msg, icon: 'none' })
+            }
+            break
+
+        case 9: // 财经日历
+            const calRes = await newsApi.handFinancialCalendar({})
+            if (calRes.code === Code.StatusOK) {
+                imgContent.value = calRes.msg
+            } else {
+                uni.showToast({ title: calRes.msg, icon: 'none' })
+            }
+            break
+
+        case 10: // 电子书
+            const ebookRes = await newsApi.newsEbookSingle({ id: id.value })
+            if (ebookRes.code === Code.StatusOK && ebookRes.data) {
+                info.value = ebookRes.data
+            } else {
+                uni.showToast({ title: ebookRes.msg, icon: 'none' })
+            }
+            break
+
+        default:
+            break
+    }
+}
+
+
+// 页面生命周期:获取路由参数
+onLoad((options) => {
+    type.value = Number(options.type)
+    id.value = options.id
+    getNewsSingle()
+})
+
+// 如果需要在页面卸载时做一些清理(原组件无,可留空)
+onUnmounted(() => {
+    // 清理可能的视频播放器
+})
 </script>
+
 <style lang="scss" scoped>
-@import "@/uni.scss";
-</style>
+#News_Content {
+    height: 100%;
+
+    .crm-title-box {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        padding: 10rpx 15rpx;
+        background-color: #fff;
+        border-bottom: 1px solid #eee;
+
+        .tit {
+            font-size: 16rpx;
+            font-weight: bold;
+        }
+
+        .btn {
+            display: flex;
+            align-items: center;
+
+            .icon-back {
+                font-size: 18rpx;
+                margin-right: 4rpx;
+            }
+        }
+    }
+
+    .content {
+        width: 100%;
+        height: calc(100% - 50rpx);
+        background-color: #fff;
+        overflow: hidden;
+        text-align: left;
+        padding: 10rpx 15rpx;
+        box-sizing: border-box;
+        line-height: 1.8;
+
+        .img {
+            margin-bottom: 10rpx;
+
+            image {
+                width: 100%;
+            }
+        }
+
+        .con-title {
+            font-size: 18rpx;
+            font-weight: bold;
+            margin: 10rpx 0;
+        }
+
+        .con-time {
+            margin-bottom: 10rpx;
+            font-size: 12rpx;
+            color: #999;
+        }
+
+        .con-des {
+            margin: 10rpx 0;
+            font-size: 14rpx;
+        }
+
+        .video-player {
+            width: 100%;
+            height: 400rpx;
+        }
+
+        .webview {
+            width: 100%;
+            height: 100%;
+            min-height: 1200rpx;
+        }
+
+        .ebookBox {
+            display: flex;
+            flex-direction: column;
+
+            @media (min-width: 768px) {
+                flex-direction: row;
+                align-items: center;
+            }
+
+            .ebook-cover {
+                width: 100%;
+                max-width: 360rpx;
+                height: auto;
+                margin-right: 0;
+                margin-bottom: 15rpx;
+
+                @media (min-width: 768px) {
+                    margin-right: 25rpx;
+                    margin-bottom: 0;
+                }
+            }
+
+            .news-title {
+                color: #EB3F57;
+                font-size: 44rpx;
+                font-weight: bold;
+                margin-bottom: 10rpx;
+            }
+
+            .news-status {
+                margin-top: 10rpx;
+
+                a {
+                    display: inline-block;
+                    background-color: #EB3F57;
+                    color: #fff;
+                    padding: 8rpx 30rpx;
+                    border-radius: 8rpx;
+                    font-weight: bold;
+                }
+            }
+        }
+    }
+}
+
+// 富文本表格样式(保留)
+:deep(.con-des) {
+    table {
+        border-collapse: collapse !important;
+        width: 100% !important;
+        margin: 10px 0 !important;
+    }
+
+    th,
+    td {
+        border: 1px solid #dcdfe6 !important;
+        padding: 8px !important;
+        text-align: left !important;
+    }
+
+    th {
+        background-color: #f5f7fa !important;
+    }
+}
+
+
+.rich-text-wrapper {
+    width: 100%;
+    overflow-x: auto; // 允许横向滚动
+    -webkit-overflow-scrolling: touch; // 在 iOS 上提供惯性滚动
+}
+
+// 可选:让 rich-text 内的内容不换行(但不一定需要)
+.rich-text-wrapper rich-text {
+    display: block;
+    width: 100%;
+}
+</style>
+
+<style>
+/* 覆盖 rich-text 内所有段落样式 */
+uni-rich-text p {
+    margin: 12rpx 0 !important;
+    font-size: 14rpx !important;
+    /* 对应 14px */
+    line-height: 1.6 !important;
+    color: #333;
+}
+
+/* 覆盖 span 样式,移除固定字体和大小 */
+uni-rich-text span {
+    font-size: 14rpx !important;
+    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important;
+}
+
+/* 图片自适应 */
+uni-rich-text img {
+    max-width: 100% !important;
+    height: auto !important;
+}
+</style>

+ 187 - 0
pages/common/notice.vue

@@ -0,0 +1,187 @@
+<template>
+    <cwg-page-wrapper class="create-page" :isHeaderFixed="true">
+        <cwg-header :title="t('News.Notice')" />
+        <view class="info-card">
+            <view class="search-bar">
+                <cwg-combox v-model:value="search.read" :options="readOptions"
+                    :placeholder="t('Custom.PaymentHistory.StatusPlaceholder')" />
+            </view>
+            <cwg-tabel ref="tableRef" :columns="columns" :mobilePrimaryFields="mobilePrimaryFields" :isPages="true"
+                :queryParams="search" :api="listApi" @go-pages="goPages">
+                <template #status="{ row }">
+                    {{readOptions.find(item => item.value === row.read)?.text}}
+                </template>
+            </cwg-tabel>
+        </view>
+    </cwg-page-wrapper>
+</template>
+
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
+const { t, locale } = useI18n();
+import { newsApi } from '@/service/news';
+import useRouter from "@/hooks/useRouter";
+const router = useRouter();
+const search = ref({
+    read: null,
+    lang: locale.value
+})
+const readOptions = computed(() => [
+    { value: null, text: t('State.All') },
+    { value: 0, text: t('News.Unread') },
+    { value: 1, text: t('News.Read') }
+]);
+const tableRef = ref(null)
+
+watch(locale, () => {
+    search.value.lang = locale.value
+    tableRef.value.reload()
+})
+
+// 表格列配置(支持插槽和格式化)
+const columns = computed(() => [
+    {
+        prop: 'subject',
+        label: t('News.Title'),
+        align: 'left',
+        slot: 'subject'          // 使用插槽自定义标题点击
+    },
+    {
+        prop: 'addTime',
+        label: t('Drawer.Label.Date'),
+        align: 'center',
+        formatter: ({ row }) => row.addTime || '--'
+    },
+    {
+        prop: 'read',
+        label: t('Custom.Recording.Status'),
+        align: 'right',
+        slot: 'status'           // 使用插槽显示未读/已读状态
+    },
+    {
+        prop: 'more',
+        type: 'more',
+        width: 20,
+        align: 'right'
+    },
+])
+
+const mobilePrimaryFields = ref([
+    {
+        prop: 'subject',
+        label: t('News.Title'),
+        align: 'left',
+        slot: 'subject'          // 使用插槽自定义标题点击
+    },
+    {
+        prop: 'addTime',
+        label: t('Drawer.Label.Date'),
+        align: 'center',
+        formatter: ({ row }) => row.addTime || '--'
+    },
+    {
+        prop: 'read',
+        label: t('Custom.Recording.Status'),
+        align: 'right',
+        slot: 'status'           // 使用插槽显示未读/已读状态
+    },
+    {
+        prop: 'more',
+        type: 'more',
+        width: 20,
+        align: 'right'
+    },
+])
+
+const goPages = (e) => {
+    router.push({
+        path: '/pages/analytics/detail',
+        query: {
+            id: e.id,
+            type: 7
+        }
+    })
+}
+const listApi = ref(null)
+listApi.value = newsApi.newsNoticeList
+</script>
+
+<style scoped lang="scss">
+@import "@/uni.scss";
+
+.avatar {
+    width: px2rpx(60);
+    height: px2rpx(60);
+    border-radius: 4px;
+}
+
+.content-title {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    font-size: px2rpx(20);
+    font-weight: 500;
+
+    .content-title-btns {
+        margin: px2rpx(8) 0;
+
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        gap: px2rpx(12);
+
+        .btn-primary {
+            min-width: px2rpx(120);
+            background-color: var(--color-error);
+            color: white;
+            padding: 0 px2rpx(12);
+            border: none;
+            font-size: px2rpx(14);
+            text-align: center;
+            cursor: pointer;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            gap: px2rpx(8);
+        }
+
+        .btn-primary:active {
+            background-color: var(--color-navy-700);
+        }
+    }
+}
+
+.operation-btn {
+    :deep(span) {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        gap: px2rpx(4);
+        cursor: pointer;
+        background-color: var(--color-slate-150);
+        padding: px2rpx(8) 0;
+    }
+}
+
+.operation-btn.disabled {
+    cursor: not-allowed;
+    opacity: 0.5;
+}
+
+.search-bar {
+    display: flex;
+    align-items: center;
+    justify-content: flex-start;
+    flex-wrap: wrap;
+    gap: px2rpx(16);
+    margin: px2rpx(16) 0;
+
+    .cwg-combox,
+    .uni-easyinput,
+    .uni-date {
+        width: px2rpx(240) !important;
+        flex: none;
+    }
+}
+</style>

+ 1 - 0
windows/left-window.vue

@@ -95,6 +95,7 @@ const menu = ref<MenuItem[]>(
                 { path: '/pages/mine/info?type=2', isOpenMenu: false, label: 'PersonalManagement.Title.BankInformation', icon: 'crm-headset' },
                 { path: '/pages/mine/info?type=3', isOpenMenu: false, label: 'PersonalManagement.Title.FileManagement', icon: 'crm-headset' },
                 { path: '/pages/mine/info?type=4', isOpenMenu: false, label: 'PersonalManagement.Title.SecurityCenter', icon: 'crm-headset' },
+                { path: '/pages/common/notice', isOpenMenu: false, label: 'News.Notice', icon: 'crm-headset' },
             ]
         },
     ]);

+ 2 - 3
windows/top-window.vue

@@ -5,9 +5,7 @@
 		</div>
 		<div class="right">
 			<LanguageDropdown />
-			<view class="pc-header-btn">
-				<cwg-icon name="xxtz" color="#141d22" />
-			</view>
+			<cwg-notice />
 			<view class="pc-header-btn">
 				<cwg-icon name="icon_my" color="#141d22" @click="openRightDrawer" />
 			</view>
@@ -32,6 +30,7 @@ function openRightDrawer() {
 	uni.$emit('open-right-drawer')
 }
 
+
 </script>
 
 <style scoped lang="scss">