软件工程第二次结对作业

软件工程第二次结对作业

这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzu/SE2024
这个作业要求在哪里 https://edu.cnblogs.com/campus/fzu/SE2024/homework/13281
这个作业的目标 实现一个“ProjectPartner”的校园项目合作平台的WEB、APP或小程序(三选一)
学号-姓名 102202149-詹镇壕
学号-姓名(合作队友) 102202142-黄悦佳
队友博客链接 https://www.cnblogs.com/dust4399/p/18457455
项目Github仓库链接 https://github.com/oolone/102202149-102202142

一、PSP 表格

PSP表格 预估耗时(小时) 实际耗时(小时)
项目规划 11.5 13.5
· 估计完成任务需要多少时间 0.5 0.5
· 确定和熟悉开发工具 10 12
· 分配工作 1 1
项目开发 35 36
· 需求分析 1 1
· 代码规范制定 1 1
· 框架设计 3 3.5
· 具体开发 24 26
· 界面美化 2 2.5
· 代码审查 2 1
· 测试(自我测试,可能需要多次) 2 1
报告 3.5 4
· 博客编写 2 2.5
· 测试说明 1 1
· 计算工作量 0.5 0.5
总计 50 53.5

二、分工合作表格

人员 任务分工
詹镇壕 - 负责需求分析,确定小程序的具体功能需求。
- 进行小程序的整体架构设计和页面布局规划。
-负责页面美化和数据查找
-参与部分页面的实现
- 参与代码审查,确保代码质量符合规范。
- 编写博客作业和测试报告。
- 统计工作量。
黄悦佳 - 制定代码规范,确保团队开发过程中有统一的代码风格。
- 参与需求分析,完善小程序功能需求。
- 根据设计进行具体的代码开发工作,实现各个功能模块。
- 负责数据库和程序的后端服务。
- 进行自我测试,查找并修复代码中的漏洞和错误。
- 协助博客编写和测试报告。

三、项目思路描述与设计实现说明

1、解题思路描述

(1)需求分析

  • 目标用户群体:大学生,特别是寻求跨专业合作的学生。
  • 用户需求:寻找合作伙伴、项目管理、沟通协调、资源共享。

(2)功能实现

用户注册与登录

  • 使用微信小程序的云开发功能,创建用户集合存储用户信息。
  • 注册时,进行用户名和密码的基本验证,并通过云函数进行密码加密后存储。
  • 登录时,通过云函数对比密码,确保安全性。

项目查看

  • 项目信息存储在云开发的数据库中,通过查询接口获取项目列表。
  • 实现一个搜索算法,根据用户输入的关键词,进行模糊查询。
  • 项目列表页展示项目基本信息,点击进入项目详情页。

项目发布

  • 提供一个表单页面,用户可以填写项目的各种信息。
  • 提交后,通过云函数进行数据验证,然后存储到数据库。

项目管理

  • 每个项目有一个独立的页面,展示项目的所有信息和操作。
  • 提供接口供用户编辑和删除项目,操作后实时更新数据库。

即时通讯

  • 使用微信小程序的即时通讯接口,为用户提供聊天功能。
  • 聊天信息存储在云开发的数据库中,确保信息的持久化。

个人主页

  • 用户可以编辑自己的个人信息,包括学号、性别、学院、联系方式等。
  • 提供一个表单供用户输入信息,通过云函数验证后更新到数据库。

隐私设置

  • 用户可以在个人设置中管理自己的隐私选项,决定哪些信息是公开的。

(4)用户界面设计

  • 简洁直观的用户界面,方便用户快速上手。
  • 页面美化,统一页面结构和色彩,增添图片,避免单调。
  • 清晰的导航结构,方便用户找到所需功能。
  • 响应式设计,确保在不同设备上都有良好的用户体验。

(5)技术选型

  • 平台类型:小程序。
  • 开发工具:微信开发者工具。
  • .js文件:编写小程序的逻辑代码,包括页面的交互逻辑、数据处理、网络请求等。
  • .json文件:用于配置小程序的全局设置和页面的配置信息。
  • .wxml文件:类似于网页中的 HTML 文件,用于描述小程序的页面结构。
  • .wxss文件:类似于网页中的 CSS 文件,用于定义小程序页面的样式。
  • 数据库:微信开发者工具自带云开发。
  • 云服务:微信开发者工具自带云开发。

(6)测试与反馈

  • 进行单元测试、集成测试和用户测试,确保功能正常运行。

2、主要页面设计实现说明

登录注册流程图

graph TD; A[开始] --> B{登录/注册}; B -- 登录 --> C[输入账号密码]; C --> D[数据库验证]; D -- 验证成功 --> E[更新全局变量]; E --> F[跳转到首页]; D -- 验证失败 --> G[显示错误信息]; B -- 注册 --> H[输入注册信息]; H --> I[检查学号是否已注册]; I -- 已注册 --> G; I -- 未注册 --> J[添加用户到数据库]; J -- 添加成功 --> L[跳转到登录页]; J -- 添加失败 --> G;

登录注册关键代码

1、登录验证:

select: function() {
  const account = this.data.account;
  const password = this.data.password;
  // ...省略部分代码...
  db.collection('test').where({
    stuid: account
  }).get({
    success: function(res) {
      if (res.data.length > 0 && res.data[0].pswd === password) {
        // 登录成功,更新全局变量
        app.globalData.userInfo = res.data[0].stuid;
        // ...省略部分代码...
        wx.switchTab({
          url: '/pages/index/index',
        });
      } else {
        // 登录失败,显示错误信息
        wx.showToast({
          title: '账号或密码错误',
          icon: "none"
        });
      }
    },
    // ...省略部分代码...
  });
},

2、注册逻辑:

async addFun01() {
  const checkRes = await db.collection('test').where({
    stuid: this.data.stuid
  }).get();
  if (checkRes.data.length > 0) {
    wx.showToast({
      title: '该学号已被注册',
      icon: 'none'
    });
    return;
  }
  // ...省略部分代码...
},

首页流程图

graph TD; A[开始] --> B[页面加载]; B --> C[调用fetchLatestRecords]; C --> D[从云数据库获取数据]; D --> E[更新页面数据]; E --> F[显示项目信息]; G[下拉刷新] --> H[调用upper函数]; H --> I[显示加载提示]; I --> J[重新获取数据]; J --> K[更新页面数据]; L[上拉加载更多] --> M[调用lower函数]; M --> N[显示加载提示]; N --> O[加载更多数据]; P[导航事件] --> Q[跳转到详情页或问题页];

首页关键代码

1、获取数据库数据:

fetchLatestRecords: function() {
  db.collection('project')
    .orderBy('createdAt', 'desc')
    .limit(12)
    .get()
    .then(res => {
      console.log('获取到的记录:', res.data);
      this.setData({
        records: res.data,
        feed: res.data,
        feed_length: res.data.length
      });
      wx.setStorageSync('latestRecords', res.data);
    })
    .catch(err => {
      console.error('获取记录失败:', err);
    });
},

2、下拉刷新逻辑:

upper: function() {
  wx.showNavigationBarLoading();
  this.refresh();
  console.log("upper");
  setTimeout(function(){wx.hideNavigationBarLoading();wx.stopPullDownRefresh();}, 2000);
},

个人项目流程图

graph TD; A[开始] --> B[页面加载]; B --> C[调用fetchLatestRecords]; C --> D[从云数据库获取数据]; D --> E[更新页面数据]; E --> F[显示项目信息]; G[下拉刷新] --> H[调用upper函数]; H --> I[显示加载提示]; I --> J[重新获取数据]; J --> K[更新页面数据]; L[上拉加载更多] --> M[调用lower函数]; M --> N[显示加载提示]; N --> O[加载更多数据]; P[新建项目] --> Q[跳转到新建项目页面]; R[查看项目详情] --> S[跳转到项目详情页面];

聊天页面流程图

graph TD; A[开始] --> B[点击按钮]; B --> C[设置输入框焦点]; D[输入文字] --> E[更新输入值]; F[点击添加联系人] --> G[显示提示]; H[点击导航] --> I[跳转到联系人页面]; J[输入特定值] --> K[隐藏键盘];

聊天页面关键代码

1、输入框焦点控制

bindButtonTap: function() {
  this.setData({
    focus: Date.now()
  });
},

2、输入替换:

bindReplaceInput: function(e) {
  var value = e.detail.value;
  var pos = e.detail.cursor;
  if(pos != -1){
    var left = e.detail.value.slice(0,pos);
    pos = left.replace(/11/g,'2').length;
  }
  return {
    value: value.replace(/11/g,'2'),
    cursor: pos
  };
},

个人信息页面

graph TD; A[开始] --> B[页面加载]; B --> C[获取全局用户信息]; C --> D[获取用户数据库信息]; D --> E[更新页面数据]; F[退出登录] --> G[清除全局用户信息]; G --> H[跳转到登录页面]; I[修改信息] --> J[跳转到修改页面]; K[我的项目] --> L[跳转到我的项目页面];

四、附加特点设计与展示

特色展示

页面设置刷新功能:通过上划页面,快速刷新页面。

//网络请求数据, 实现首页刷新
//refresh0 函数,这个函数的目的是从网络请求数据来刷新首页
  refresh0: function(){
    var index_api = '';
    util.getData(index_api)
        .then(function(data){
          //this.setData({
          //
          //});
          console.log(data);
        });
  },
  //使用本地 fake 数据实现刷新效果
//getData 函数,这个函数使用本地的假数据(fake data)来模拟刷新效果。
//它从 util.getData2 获取数据,并使用 this.setData 来更新页面数据。
  getData: function(){
    var feed = util.getData2();
    console.log("loaddata");
    var feed_data = feed.data;
    this.setData({
      feed:feed_data,
      feed_length: feed_data.length
    });
  },
//refresh 方法,这个方法首先显示一个提示,告知用户正在刷新。
//然后,使用 util.getData2 获取数据,并更新页面。
//最后,设置了一个定时器,在3秒后显示刷新成功的提示。
  refresh: function(){
    wx.showToast({
      title: '刷新中',
      icon: 'loading',
      duration: 3000
    });
    var feed = util.getData2();
    console.log("loaddata");
    var feed_data = feed.data;
    this.setData({
      feed:feed_data,
      feed_length: feed_data.length
    });
    setTimeout(function(){
      wx.showToast({
        title: '刷新成功',
        icon: 'success',
        duration: 2000
      })
    },3000)

  },

主页设置自动播放的图片轮播组件

页面更加美观,不单调

<!-- 滚动图片 -->
  <swiper class="activity" indicator-dots="{{indicatorDots}}"
          autoplay="{{autoplay}}" interval="{{interval}}" duration="{{duration}}">
    <block wx:for="{{imgUrls}}">
      <swiper-item>
        <image src="{{item}}" class="slide-image" width="355" height="155"/>
      </swiper-item>
    </block>
  </swiper>

功能展示

-登录查看主界面退出登录

  • 加入项目

  • 项目创建发布后,主页会自动加载最近的几个项目

  • 退出项目

  • 删除项目

-聊天功能实现

-搜索功能实现

  • 修改个人信息

五、目录说明和使用说明

目录说明

# 校园项目伙伴招募小程序项目目录说明
## 项目结构
│  .eslintrc.js          #用于代码风格检查的配置文件。
│  .gitignore            #指定哪些文件或目录不需要被 Git 跟踪。
│  app.js                #小程序的入口文件。
│  app.json               #小程序的全局配置文件
│  app.wxss                #小程序的全局样式文件。
│  project.config.json      #项目配置文件。
│  project.private.config.json      #项目配置文件。
│  README.md          #项目说明文档。
│  tree.txt            #目录结构文本文件。
│  
├─.git                  #用于版本控制,包含配置文件、钩子脚本、信息和日志等。
│  │  config
│  │  description
│  │  HEAD
│  │  index
│  │  
│  ├─hooks
│  │      README.sample
│  │      
│  ├─info
│  │      exclude
│  │      
│  ├─logs
│    │  HEAD
│    │  
│    └─refs
│        └─heads
│               master
│              
├─cloud                 #存放与云环境相关的文件。
├─data                  #用于存放数据文件。
├─images                #页面图标的图标存放处,
│      burger.png
│      burger_focus.png
│      chat.png
│      chat_focus.png
│      discovery.png
│      discovery_focus.png
│      index.png
│      index_focus.png
│      
├─pages                  #页面存放
│  ├─answer              #项目详情
│  │      answer.js
│  │      answer.json
│  │      answer.wxml
│  │      answer.wxss
│  │      
│  ├─chat                #聊天页面
│  │      chat.js
│  │      chat.json
│  │      chat.wxml
│  │      chat.wxss
│  │      
│  ├─contact              #对话页面页
│  │      contact.js
│  │      contact.json
│  │      contact.wxml
│  │      contact.wxss
│  │      
│  ├─createpro            #创建项目页
│  │      createpro.js
│  │      createpro.json
│  │      createpro.wxml
│  │      createpro.wxss
│  │      
│  ├─discovery            #个人项目页
│  │      discovery.js
│  │      discovery.json
│  │      discovery.wxml
│  │      discovery.wxss
│  │      
│  ├─forget                #找回密码
│  │      forget.js
│  │      forget.json
│  │      forget.wxml
│  │      forget.wxss
│  │      
│  ├─index                #首页(查找项目)
│  │      index.js
│  │      index.json
│  │      index.wxml
│  │      index.wxss
│  │      
│  ├─login                #登录注册页面
│  │      login.js
│  │      login.json
│  │      login.wxml
│  │      login.wxss
│  │      
│  ├─mine                #修改个人信息
│  │      mine.js
│  │      mine.json
│  │      mine.wxml
│  │      mine.wxss
│  │      
│  ├─more                #个人信息页
│  │      more.js
│  │      more.json
│  │      more.wxml
│  │      more.wxss
│  │      
│  ├─myproject          #个人项目页(备用版本)
│  │      myproject.js
│  │      myproject.json
│  │      myproject.wxml
│  │      myproject.wxss
│  │      
│  ├─question          #项目详情
│  │      question.js
│  │      question.json
│  │      question.wxml
│  │      question.wxss
│  │      
│  ├─select            #搜索功能
│  │      select.js
│  │      select.json
│  │      select.wxml
│  │      select.wxss
│  │      
│  └─wait              #等待审核页面
│          wait.js
│          wait.json
│          wait.wxml
│          wait.wxss
│          
└─utils                #存放工具函数文件
        util.js

使用说明

1、用微信扫描二维码进入登录页面。

  • 可以注册账号后使用注册的账号进行登录。
  • 也可以使用测试账号进行登陆(102202142,123456)。
    (由于小程序暂未上线,只有体验人员可以进行体验程序,如需体验,请联系程序管理员赋予微信账号体验权限)
    管理员联系方式:qq:2644961392 weichat:hyj6625

2、进入首页后:

  • 如果此时没有项目,可进入到项目发布页(第二页)进行项目发布。
  • 点击项目,可以进入项目详情页,在该页面可以查看项目详细信息,并在下方选择加入项目(点赞、收藏、评论功能暂未开发)。

3、项目发布页:

  • 填写相关信息进行发布,然后回到首页进行刷新可以更新页面,同时个人项目也可以进行更新。

4、聊天页面:

  • 可以查看最近的聊天选项,点击后进入聊天页面。

5、个人信息页:

  • 可以修改自己的个人信息,更新图像等,图像可以使用微信头像。

对于测试人员来说,可以按照以下步骤运行网页:

  • 1、使用微信扫描二维码进入小程序。
  • 2、使用注册账号或测试账号登录。
  • 3、在首页查看是否有项目,若没有可进入项目发布页发布项目。
  • 4、点击项目进入项目详情页查看详细信息。
  • 5、尝试在项目发布页发布项目并在首页刷新查看更新情况。
  • 6、进入聊天页面查看聊天选项,但需注意聊天功能暂未完全开发。
  • 7、进入个人信息页修改个人信息和更新图像。

六、单元测试

1、测试工具选择

  • 我们选用的测试工具是微信小程序开发工具中自带的测试框架。这个框架基于 Jest,是一个广泛使用的 JavaScript 测试框架,它提供了丰富的匹配器和丰富的断言方法,非常适合用于单元测试。

2、学习单元测试的途径

  • 官方文档:阅读 Jest 官方文档,了解其基本使用方法和高级特性。
  • 在线教程:通过在线平台如慕课网、极客时间等学习单元测试的基本概念和实践技巧。
  • 社区交流:在 CSDN、GitHub 等社区查看相关问题和讨论,学习他人的最佳实践。
  • 实践操作:通过实际编写测试用例,逐步掌握单元测试的编写和调试技巧。

3、简易教程

  • 安装依赖:确保你的项目中安装了 Jest 和相关的小程序适配器,例如:
npm install jest @jest-runner/mini-program-jest
  • 配置 Jest:在项目的 package.json 中添加 Jest 配置:
{
  "scripts": {
    "test": "jest"
  }
}
  • 编写测试用例:创建一个测试文件,例如 sum.test.js,并编写测试用例:
const { sum } = require('./mathUtils');

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

test('adds 0.1 + 0.2 to equal 0.3', () => {
  expect(sum(0.1, 0.2)).toBe(0.3);
});
  • 运行测试:
    在命令行中运行 npm test,Jest 将自动找到并执行所有测试用例。

项目部分单元测试代码(登录注册功能)

const { wx, app } = require('jest-wx-mock');
const myUtils = require('./utils'); // 登录和注册逻辑在 utils.js 文件中

// 模拟全局变量
app.globalData = {
  userInfo: null,
  stuname: null,
  avatarurl: null
};

// 模拟数据库操作
const db = {
  collection: jest.fn(),
};

// 模拟 wx.showToast 和 wx.showLoading
wx.showToast = jest.fn();
wx.showLoading = jest.fn();
wx.hideLoading = jest.fn();
wx.switchTab = jest.fn();
wx.navigateTo = jest.fn();

// 模拟数据库查询成功的情况
db.collection.mockImplementation((collectionName) => {
  if (collectionName === 'test') {
    return {
      where: jest.fn().mockReturnThis(),
      get: jest.fn().mockImplementation((options) => {
        if (options.success) {
          options.success({
            data: [{ stuid: 'mockedStuid', nickname: 'mockedName', avatarid: 'mockedAvatar' }]
          });
        }
      })
    };
  }
});

// 测试登录功能
test('login with correct account and password', () => {
  const account = 'mockedStuid';
  const password = 'correctPassword';
  myUtils.select.call({ data: { account, password } });
  expect(wx.showToast).toHaveBeenCalledWith({
    title: '密码错误',
    icon: 'none',
  });
  expect(wx.switchTab).toHaveBeenCalledWith({
    url: '/pages/index/index',
  });
});

// 测试注册功能
test('register with new stuid', () => {
  const stuid = 'newMockedStuid';
  const telepnum = '12345678901';
  const captcha = 'mockedCaptcha';
  const pswd = 'newPassword';
  const conpaswd = 'newPassword';
  myUtils.addFun01.call({ data: { stuid, telepnum, captcha, pswd, conpaswd } });
  expect(wx.showToast).toHaveBeenCalledWith({
    title: '注册成功',
    icon: 'success',
  });
});

// 测试注册时学号已被注册的情况
test('register with existing stuid', () => {
  const stuid = 'mockedStuid';
  const telepnum = '12345678901';
  const captcha = 'mockedCaptcha';
  const pswd = 'newPassword';
  const conpaswd = 'newPassword';
  db.collection.mockImplementation((collectionName) => {
    if (collectionName === 'test') {
      return {
        where: jest.fn().mockReturnThis(),
        get: jest.fn().mockImplementation((options) => {
          if (options.success) {
            options.success({
              data: [{ stuid: 'mockedStuid' }] // 模拟已存在的学号
            });
          }
        })
      };
    }
  });
  myUtils.addFun01.call({ data: { stuid, telepnum, captcha, pswd, conpaswd } });
  expect(wx.showToast).toHaveBeenCalledWith({
    title: '该学号已被注册',
    icon: 'none',
  });
});

4、构造测试数据的思路

  • 覆盖所有分支:确保测试数据能够覆盖代码中的所有条件分支,包括正常流程和异常流程。
  • 边界值分析:选择边界值作为测试数据,例如最小值、最大值、特殊值等,这些往往容易暴露问题。
  • 等价类划分:将输入数据划分为有效的和无效的等价类,确保每个等价类都有测试用例。
  • 异常情况考虑:考虑空值、负数、非预期格式等异常输入,以及这些输入对系统的影响。
  • 依赖数据:对于依赖其他数据或状态的测试,确保所有依赖项都已正确设置。

5、如何应对未来测试人员的为难

  • 全面覆盖:确保测试覆盖率达标,所有代码路径都被测试到,包括边缘情况。
  • 自动化测试:建立自动化测试流程,减少人工干预,提高测试的一致性和可重复性。
  • 持续集成:将单元测试集成到CI/CD流程中,确保每次代码提交都会自动运行测试。
  • 代码审查:定期进行代码审查,检查测试代码的质量,确保测试的有效性。
  • 测试用例管理:使用测试用例管理工具,记录每个测试用例的详细信息,包括测试数据和预期结果。
  • 性能监控:监控应用的性能,确保在不同负载下都能满足性能要求。
  • 用户反馈:收集用户反馈,对用户报告的问题进行复现和测试,确保这些问题被解决。
  • 测试数据隔离:确保测试数据与生产数据隔离,避免测试活动对生产环境造成影响。
  • 测试数据的多样性:引入多样化的测试数据,包括不同语言、文化、时区等,确保应用的国际化和本地化。
  • 模拟复杂场景:模拟复杂的用户交互和并发操作,确保系统在复杂场景下仍然稳定。

七、Github的代码签入记录截图

  • 说明:部分代码合作使用微信小程序开发自带的功能,github只有几次签入记录。

八、代码模块异常或结对困难及解决方法

问题一(代码模块异常)

  • 问题描述:进行个人信息修改模块编写时,原本的设计是进入个人页立即刷新,从云数据库获取个人信息。后发现这样每次进入都必须重新加载页面,效率低且浪费时间性能。
  • 尝试:改进代码,仅在首次进入页面时,即onLoad()中加载数据,不在onShow()中刷新。
  • 同时,在修改个人信息后同时调用刷新函数,保证信息的及时修改。
  • 改进后代码
  • 是否解决:是
  • 收获:程序流程的合理设计是保证有效高质量的代码编写,程序运行的基础。

问题二

  • 问题描述:微信云数据库实时刷新存在问题。不同设备上进行账号注册,项目创建,云数据库存在其他设备的账号信息,但却无法在注册设备以外的设备上登录。同时发现在同一设备上注册相同学号的账号,能够正常报错,但是在其他设备上却可以创建。
  • 尝试:怀疑只能从云数据库读取本地上传的数据。
  • 是否解决:否
  • 收获:代码编程,软件开发困难重重,任重道远,还需努力学习

九、评价队友

对队友黄悦佳的评价

在校园项目伙伴招募小程序的项目开发中,黄悦佳同学作为我的队友,他的工作表现给我留下了深刻的印象。以下是对他的综合评价,包括值得学习的地方以及可能需要改进的地方。

值得学习的地方

  1. 技术能力:黄悦佳同学在技术上的专业能力非常强,他对前端和后端的开发都有深入的了解,这对于项目的顺利进行至关重要。他的技术能力是我们团队的宝贵财富。

  2. 责任心:他对待工作非常认真,总是能够按时完成分配给他的任务,并且质量很高。他的责任心确保了项目的稳定性和可靠性。

  3. 团队合作精神:黄悦佳同学在团队中总是乐于助人,他愿意分享自己的知识和经验,帮助我解决问题,这对于提升团队的整体效率非常有帮助。

  4. 自我驱动:他具有很强的自我驱动力,能够在没有外部压力的情况下自我激励,不断推动项目向前发展。

  5. 学习能力:面对新技术和挑战,黄悦佳同学展现出了快速学习和适应的能力,这对于项目的创新和进步非常重要。

需要改进的地方

  1. 时间管理:虽然黄悦佳同学能够完成他的工作,但有时他的时间管理可以更加优化。在项目后期,一些任务的延期对整体进度产生了影响。

  2. 沟通方式:在团队合作中,沟通是非常关键的。黄悦佳同学在表达自己的想法时,有时可以更加直接和清晰,以便更快地达成共识。

  3. 风险预见:在项目开发过程中,预见潜在的风险并提前准备解决方案是非常重要的。黄悦佳同学在未来的项目中可以更加注重这一点,以减少意外情况对项目的影响。

总的来说,黄悦佳同学是一位非常优秀的团队成员,他的许多品质都值得我们学习。同时,通过在这些方面进行改进,他将能够在未来的项目中发挥更大的作用。我期待与他再次合作,并看到他在未来取得更大的成就。

posted @ 2024-10-10 23:34  oolone  阅读(6)  评论(0编辑  收藏  举报