绿满眶商城微信小程序-uniapp
zxl
2025-06-24 c146b580fdca424b6bb35b0f8d31ffec3952e642
新闻
3个文件已修改
3个文件已添加
488 ■■■■■ 已修改文件
api/news.js 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages.json 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/mine/activity/reportActivity.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/news/detail.vue 212 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/news/news.vue 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/tabbar/user/utils/tool.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
api/news.js
New file
@@ -0,0 +1,56 @@
/**
 * 新闻相关API
 */
import { http, Method } from "@/utils/request.js";
import api from "@/config/api.js";
export  function getNews(params){
    return http.request({
        url: "/lmk/news/page",
        method: Method.GET,
        needToken: true,
        params: params,
    })
}
export  function detail(params){
    return http.request({
        url: "/lmk/news/" + params,
        method: Method.GET,
        needToken: true,
    })
}
export  function addNews(params){
    return http.request({
        url: "/lmk/news",
        method: Method.POST,
        needToken: true,
        data: params,
    })
}
export  function editNews(params){
    return http.request({
        url: "/lmk/news",
        method: Method.PUT,
        needToken: true,
        data: params,
    })
}
export  function publish(params){
    return http.request({
        url: "/lmk/news/publish/" + params,
        method: Method.PUT,
        needToken: true,
    })
}
export  function delById(params){
    return http.request({
        url: "/lmk/news/" + params,
        method: Method.DELETE,
        needToken: true,
    })
}
pages.json
@@ -282,6 +282,42 @@
                }
            }
        },
        {
            "path": "pages/news/news",
            "style": {
                "navigationBarTitleText": "新闻",
                "componentPlaceholder": {
                    "u-icon": "view",
                    "u-button": "view",
                    "u-form": "view",
                    "u-form-item": "view",
                    "u-input": "view",
                    "u-popup": "view",
                    "u-search": "view",
                    "u-loading": "view",
                    "u-navbar": "view",
                    "u-loadmore":"view"
                }
            }
        },
        {
            "path": "pages/news/detail",
            "style": {
                "navigationBarTitleText": "新闻详情",
                "componentPlaceholder": {
                    "u-icon": "view",
                    "u-button": "view",
                    "u-form": "view",
                    "u-form-item": "view",
                    "u-input": "view",
                    "u-popup": "view",
                    "u-search": "view",
                    "u-loading": "view",
                    "u-navbar": "view",
                    "u-loadmore":"view"
                }
            }
        },
        // {
        //     "path": "pages/customerManager/customerManager",
        //     "style": {
pages/mine/activity/reportActivity.vue
@@ -64,6 +64,7 @@
                query: {
                    pageNumber: 1,
                    pageSize: 10,
                    publish:1,
                },
                loading: false, // 是否正在加载
                noMore: false, // 是否没有更多数据
@@ -84,7 +85,6 @@
                this.getActivityList();
            },
            loadMore() {
                // 显示加载状态
                this.loading = true;
pages/news/detail.vue
New file
@@ -0,0 +1,212 @@
<template>
  <view class="detail-page">
    <!-- 加载状态 -->
    <view v-if="loading" class="loading-container">
      <u-loading mode="circle" size="48"></u-loading>
      <text class="loading-text">加载中...</text>
    </view>
    <!-- 内容区域 -->
    <template v-else>
      <!-- 标题区 -->
      <view class="header">
        <text class="title">{{ detailData.title }}</text>
        <text class="meta">
          <text class="date">{{ formatDate(detailData.publishDate) }}</text>
        </text>
      </view>
      <!-- 富文本内容 -->
      <scroll-view scroll-y class="content-wrapper">
        <rich-text
          :nodes="formatContent(detailData.content)"
          class="content"
        ></rich-text>
      </scroll-view>
    </template>
    <!-- 错误提示 -->
    <u-toast ref="uToast"></u-toast>
  </view>
</template>
<script>
import { detail } from '@/api/news.js'
export default {
  data() {
    return {
      detailId: '',
      loading: true,
      detailData: {
        title: '',
        content: '',
        publishDate: ''
      }
    }
  },
  onLoad(options) {
    if (options.id) {
      this.detailId = options.id
      this.loadDetailData()
    }
  },
  methods: {
    async loadDetailData() {
      this.loading = true
      try {
        const res = await detail(this.detailId)
        if (res.statusCode === 200) {
            this.detailData = {
            title: res.data.data.title || '无标题',
            content: res.data.data.content || '',
            publishDate: res.data.data.publishDate || ''
          }
        } else {
          this.showError('加载失败: ' + (res.message || '未知错误'))
        }
      } catch (error) {
        console.error('加载详情出错:', error)
        this.showError('网络请求失败')
      } finally {
        this.loading = false
      }
    },
    formatDate(dateString) {
      if (!dateString) return '未知时间'
      try {
        const date = new Date(dateString)
        if (isNaN(date.getTime())) return dateString
        const year = date.getFullYear()
        const month = (date.getMonth() + 1).toString().padStart(2, '0')
        const day = date.getDate().toString().padStart(2, '0')
        const hours = date.getHours().toString().padStart(2, '0')
        const minutes = date.getMinutes().toString().padStart(2, '0')
        return `${year}-${month}-${day} ${hours}:${minutes}`
      } catch (e) {
        console.error('日期格式化错误:', e)
        return dateString
      }
    },
    formatContent(content) {
      if (!content) return ''
      // 基础处理:确保图片自适应
      const processedContent = content
        .replace(/<img/gi, '<img style="max-width:100%;height:auto;display:block;margin:20rpx auto;"')
        .replace(/<table/gi, '<table style="width:100%;border-collapse:collapse;"')
        .replace(/<td/gi, '<td style="border:1px solid #ddd;padding:8px;"')
      return processedContent
    },
    showError(message) {
      this.$refs.uToast.show({
        type: 'error',
        message: message,
        duration: 2000
      })
    }
  }
}
</script>
<style lang="scss" scoped>
.detail-page {
  padding: 30rpx;
  min-height: 100vh;
  background-color: #f8f8f8;
}
.loading-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 60vh;
  .loading-text {
    margin-top: 20rpx;
    font-size: 28rpx;
    color: #999;
  }
}
.header {
  background-color: #fff;
  border-radius: 16rpx;
  padding: 30rpx;
  margin-bottom: 30rpx;
  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
  .title {
    display: block;
    font-size: 36rpx;
    font-weight: bold;
    color: #333;
    line-height: 1.5;
    margin-bottom: 20rpx;
  }
  .meta {
    display: flex;
    justify-content: flex-end;
    font-size: 24rpx;
    color: #999;
    .date {
      margin-left: 15rpx;
    }
  }
}
.content-wrapper {
  background-color: #fff;
  border-radius: 16rpx;
  padding: 30rpx;
  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
  height: calc(100vh - 300rpx);
  .content {
    font-size: 30rpx;
    line-height: 1.8;
    color: #333;
    // 覆盖rich-text内部元素样式
    /deep/ p {
      margin-bottom: 20rpx;
    }
    /deep/ h1, /deep/ h2, /deep/ h3 {
      margin: 40rpx 0 20rpx;
      font-weight: bold;
    }
    /deep/ ul, /deep/ ol {
      padding-left: 40rpx;
      margin-bottom: 20rpx;
    }
    /deep/ li {
      margin-bottom: 10rpx;
    }
    /deep/ a {
      color: #007aff;
      word-break: break-all;
    }
    /deep/ blockquote {
      border-left: 4rpx solid #ddd;
      padding-left: 20rpx;
      color: #666;
      margin: 20rpx 0;
    }
  }
}
</style>
pages/news/news.vue
New file
@@ -0,0 +1,178 @@
<template>
    <view class="wrapper">
        <view style="height: 100rpx"></view>
        <!-- 内容区域 -->
        <scroll-view scroll-y class="content" style="height: 40vh;" @scrolltolower="loadMore" :lower-threshold="100">
            <view class="waterfall">
                    <view class="item" v-for="(item, idx) in mockData" :key="item.id" @click="handleItemClick(item)">
                        <text class="title">{{ item.title }}</text>
                        <text class="publishDate">发布时间:{{ item.publishDate }}</text>
                    </view>
            </view>
            <!--     <view style="height: 150rpx;"></view> -->
            <!-- 改进的加载更多提示 -->
            <view class="load-more">
                <u-loadmore v-if="mockData.length > 0" :status="loading ? 'loading' : noMore ? 'nomore' : 'loadmore'"
                    :load-text="{
                        loadmore: '上拉加载更多',
                        loading: '正在加载',
                        nomore: '没有更多了'
                      }" />
            </view>
            <view style="height:150rpx">
            </view>
        </scroll-view>
    </view>
</template>
<script>
    import '@/components/uview-components/uview-ui';
    import { getNews,addNews,editNews,publish,delById } from '@/api/news.js'
    export default {
        data() {
            return {
                query: {
                    pageNumber: 1,
                    pageSize: 10,
                    publish:1,
                },
                loading:false,
                total:0,
                mockData:[],
                noMore:false,
            }
        },
        mounted() {
        },
        onLoad() {
            this.getNewsPage();
        },
        methods: {
            handleItemClick(item){
                uni.navigateTo({
                    url: `/pages/news/detail?id=${item.id}` // 参数通过 URL 传递
                });
            },
            async getNewsPage(){
                try {
                    const res = await getNews(this.query);
                    this.loading = false;
                    if(res.statusCode === 200){
                        const newData = res.data.data.map(value =>({
                            id:value.id,
                            title:value.title,
                            publish:value.publish,
                            publishDate:value.publishDate
                        }));
                        // 更新总数据量
                        this.total = res.data.total || 0;
                        // 追加或替换数据
                        this.mockData = this.query.pageNumber === 1 ?
                            newData :
                            [...this.mockData, ...newData];
                        // 判断是否还有更多数据
                        this.noMore = newData.length < this.query.pageSize ||
                            this.mockData.length >= this.total;
                    }
                } catch (error) {
                    console.error('加载失败:', error);
                    // 失败时回退页码
                    if (this.query.pageNumber > 1) {
                        this.query.pageNumber -= 1;
                    }
                } finally {
                    this.loading = false;
                    uni.hideLoading();
                    uni.stopPullDownRefresh();
                }
            }
        }
    }
</script>
<style lang="scss" scoped>
.wrapper {
  padding: 0 20rpx;
  box-sizing: border-box;
  background-color: #f5f5f5;
  height: 100%;
  .content {
    padding-bottom: 20rpx;
    box-sizing: border-box;
    .waterfall {
      display: flex;
      flex-direction: column;
      padding: 0 10rpx;
      .item {
        background: #fff;
        border-radius: 12rpx;
        padding: 24rpx;
        margin-bottom: 20rpx;
        box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
        transition: transform 0.2s ease, box-shadow 0.2s ease;
        &:active {
          transform: scale(0.98);
          box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1);
        }
        .title {
          display: block;
          font-size: 28rpx;
          color: #333;
          font-weight: 500;
          line-height: 1.5;
          margin-bottom: 12rpx;
          overflow: hidden;
          text-overflow: ellipsis;
          display: -webkit-box;
          -webkit-line-clamp: 3;
          -webkit-box-orient: vertical;
        }
        .publishDate {
          display: block;
          font-size: 22rpx;
          color: #999;
          text-align: right;
        }
      }
    }
    .load-more {
      padding: 30rpx 0;
      background-color: transparent;
      .u-loadmore {
        font-size: 26rpx !important;
        color: #999 !important;
      }
    }
  }
}
/* 平板及以上设备适配 */
@media (min-width: 768px) {
  .waterfall {
    flex-direction: row !important;
    flex-wrap: wrap;
    .item {
      width: calc(50% - 10rpx);
      margin: 0 5rpx 20rpx;
    }
  }
}
</style>
pages/tabbar/user/utils/tool.vue
@@ -66,6 +66,10 @@
                        <image src="/static/mine/shensu.png" mode=""></image>
                        <view>用户权限</view>
                    </view>
                    <view class="interact-item" v-if="isStoreManger" @click="navigateTo('/pages/news/news')">
                        <image src="/static/mine/shensu.png" mode=""></image>
                        <view>新闻</view>
                    </view>
                <!--     <view class="interact-item" v-if="isStoreManger" @click="navigateTo('/pages/customerManager/customerManager')">
                        <image src="/static/mine/shensu.png" mode=""></image>