第14章 - 二次开发指南

第14章 - 二次开发指南

14.1 开发准备

14.1.1 开发环境确认

确保开发环境已正确配置:

  • JDK 1.8+
  • Maven 3.6+
  • Node.js 14+
  • IDE(推荐IDEA)
  • Git

14.1.2 项目结构理解

RuoYi-Cloud/
├── ruoyi-api/                # 接口模块(远程调用接口)
├── ruoyi-auth/               # 认证中心
├── ruoyi-common/             # 通用模块
├── ruoyi-gateway/            # 网关服务
├── ruoyi-modules/            # 业务模块(在此扩展新功能)
│   ├── ruoyi-system/         # 系统管理
│   ├── ruoyi-gen/            # 代码生成
│   ├── ruoyi-job/            # 定时任务
│   └── ruoyi-file/           # 文件服务
├── ruoyi-ui/                 # 前端项目
└── ruoyi-visual/             # 可视化模块

14.2 新增业务模块

14.2.1 创建模块

步骤1:创建Maven模块

ruoyi-modules目录下创建新模块:

<!-- ruoyi-modules/ruoyi-demo/pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>com.ruoyi</groupId>
        <artifactId>ruoyi-modules</artifactId>
        <version>3.6.7</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>ruoyi-modules-demo</artifactId>
    <packaging>jar</packaging>
    <description>演示模块</description>

    <dependencies>
        <!-- SpringCloud Alibaba Nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- SpringCloud Alibaba Nacos Config -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!-- SpringCloud Alibaba Sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!-- SpringBoot Actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- RuoYi Common Security -->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-common-security</artifactId>
        </dependency>
        <!-- RuoYi Common Swagger -->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-common-swagger</artifactId>
        </dependency>
        <!-- RuoYi Common Log -->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-common-log</artifactId>
        </dependency>
        <!-- MySQL Connector -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

步骤2:创建启动类

package com.ruoyi.demo;

import com.ruoyi.common.security.annotation.EnableCustomConfig;
import com.ruoyi.common.security.annotation.EnableRyFeignClients;
import com.ruoyi.common.swagger.annotation.EnableCustomSwagger2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 演示模块
 */
@EnableCustomConfig
@EnableCustomSwagger2
@EnableRyFeignClients
@SpringBootApplication
public class RuoYiDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(RuoYiDemoApplication.class, args);
        System.out.println("演示模块启动成功");
    }
}

步骤3:创建配置文件

# bootstrap.yml
spring:
  application:
    name: ruoyi-demo
  profiles:
    active: dev
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: ${spring.profiles.active}
      config:
        server-addr: localhost:8848
        namespace: ${spring.profiles.active}
        file-extension: yml
        shared-configs:
          - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

步骤4:Nacos配置

在Nacos中添加ruoyi-demo-dev.yml

server:
  port: 9204

spring:
  datasource:
    druid:
      stat-view-servlet:
        enabled: true
        loginUsername: admin
        loginPassword: 123456
    dynamic:
      druid:
        initial-size: 5
        min-idle: 5
        maxActive: 20
        maxWait: 60000
      datasource:
        master:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/ry-cloud?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
          username: root
          password: password

mybatis:
  typeAliasesPackage: com.ruoyi.demo.domain
  mapperLocations: classpath:mapper/**/*.xml

14.2.2 添加网关路由

ruoyi-gateway-dev.yml中添加路由配置:

spring:
  cloud:
    gateway:
      routes:
        # 演示模块
        - id: ruoyi-demo
          uri: lb://ruoyi-demo
          predicates:
            - Path=/demo/**
          filters:
            - StripPrefix=1

14.3 业务功能开发

14.3.1 创建实体类

package com.ruoyi.demo.domain;

import com.ruoyi.common.core.web.domain.BaseEntity;
import com.ruoyi.common.core.annotation.Excel;

/**
 * 示例对象
 */
public class Demo extends BaseEntity {
    
    private static final long serialVersionUID = 1L;
    
    /** 主键ID */
    private Long id;
    
    /** 名称 */
    @Excel(name = "名称")
    private String name;
    
    /** 编码 */
    @Excel(name = "编码")
    private String code;
    
    /** 状态 */
    @Excel(name = "状态", readConverterExp = "0=正常,1=停用")
    private String status;
    
    // getter/setter...
}

14.3.2 创建Mapper

package com.ruoyi.demo.mapper;

import com.ruoyi.demo.domain.Demo;
import java.util.List;

/**
 * 示例Mapper接口
 */
public interface DemoMapper {
    
    /**
     * 查询示例列表
     */
    List<Demo> selectDemoList(Demo demo);
    
    /**
     * 根据ID查询示例
     */
    Demo selectDemoById(Long id);
    
    /**
     * 新增示例
     */
    int insertDemo(Demo demo);
    
    /**
     * 修改示例
     */
    int updateDemo(Demo demo);
    
    /**
     * 删除示例
     */
    int deleteDemoById(Long id);
    
    /**
     * 批量删除示例
     */
    int deleteDemoByIds(Long[] ids);
}
<!-- mapper/demo/DemoMapper.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.demo.mapper.DemoMapper">
    
    <resultMap type="Demo" id="DemoResult">
        <id     property="id"         column="id"          />
        <result property="name"       column="name"        />
        <result property="code"       column="code"        />
        <result property="status"     column="status"      />
        <result property="createBy"   column="create_by"   />
        <result property="createTime" column="create_time" />
        <result property="updateBy"   column="update_by"   />
        <result property="updateTime" column="update_time" />
        <result property="remark"     column="remark"      />
    </resultMap>
    
    <sql id="selectDemoVo">
        select id, name, code, status, create_by, create_time, update_by, update_time, remark 
        from demo
    </sql>
    
    <select id="selectDemoList" parameterType="Demo" resultMap="DemoResult">
        <include refid="selectDemoVo"/>
        <where>
            <if test="name != null  and name != ''"> and name like concat('%', #{name}, '%')</if>
            <if test="code != null  and code != ''"> and code = #{code}</if>
            <if test="status != null  and status != ''"> and status = #{status}</if>
        </where>
    </select>
    
    <select id="selectDemoById" parameterType="Long" resultMap="DemoResult">
        <include refid="selectDemoVo"/>
        where id = #{id}
    </select>
    
    <insert id="insertDemo" parameterType="Demo" useGeneratedKeys="true" keyProperty="id">
        insert into demo
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="name != null">name,</if>
            <if test="code != null">code,</if>
            <if test="status != null">status,</if>
            <if test="createBy != null">create_by,</if>
            <if test="createTime != null">create_time,</if>
            <if test="remark != null">remark,</if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="name != null">#{name},</if>
            <if test="code != null">#{code},</if>
            <if test="status != null">#{status},</if>
            <if test="createBy != null">#{createBy},</if>
            <if test="createTime != null">#{createTime},</if>
            <if test="remark != null">#{remark},</if>
        </trim>
    </insert>
    
    <update id="updateDemo" parameterType="Demo">
        update demo
        <trim prefix="SET" suffixOverrides=",">
            <if test="name != null">name = #{name},</if>
            <if test="code != null">code = #{code},</if>
            <if test="status != null">status = #{status},</if>
            <if test="updateBy != null">update_by = #{updateBy},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="remark != null">remark = #{remark},</if>
        </trim>
        where id = #{id}
    </update>
    
    <delete id="deleteDemoById" parameterType="Long">
        delete from demo where id = #{id}
    </delete>
    
    <delete id="deleteDemoByIds" parameterType="String">
        delete from demo where id in 
        <foreach item="id" collection="array" open="(" separator="," close=")">
            #{id}
        </foreach>
    </delete>
</mapper>

14.3.3 创建Service

package com.ruoyi.demo.service;

import com.ruoyi.demo.domain.Demo;
import java.util.List;

/**
 * 示例服务接口
 */
public interface IDemoService {
    
    List<Demo> selectDemoList(Demo demo);
    
    Demo selectDemoById(Long id);
    
    int insertDemo(Demo demo);
    
    int updateDemo(Demo demo);
    
    int deleteDemoByIds(Long[] ids);
}
package com.ruoyi.demo.service.impl;

import com.ruoyi.demo.domain.Demo;
import com.ruoyi.demo.mapper.DemoMapper;
import com.ruoyi.demo.service.IDemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

/**
 * 示例服务实现
 */
@Service
public class DemoServiceImpl implements IDemoService {
    
    @Autowired
    private DemoMapper demoMapper;
    
    @Override
    public List<Demo> selectDemoList(Demo demo) {
        return demoMapper.selectDemoList(demo);
    }
    
    @Override
    public Demo selectDemoById(Long id) {
        return demoMapper.selectDemoById(id);
    }
    
    @Override
    public int insertDemo(Demo demo) {
        return demoMapper.insertDemo(demo);
    }
    
    @Override
    public int updateDemo(Demo demo) {
        return demoMapper.updateDemo(demo);
    }
    
    @Override
    public int deleteDemoByIds(Long[] ids) {
        return demoMapper.deleteDemoByIds(ids);
    }
}

14.3.4 创建Controller

package com.ruoyi.demo.controller;

import com.ruoyi.common.core.web.controller.BaseController;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.core.web.page.TableDataInfo;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.security.annotation.RequiresPermissions;
import com.ruoyi.demo.domain.Demo;
import com.ruoyi.demo.service.IDemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;

/**
 * 示例控制器
 */
@RestController
@RequestMapping("/demo")
public class DemoController extends BaseController {
    
    @Autowired
    private IDemoService demoService;
    
    /**
     * 查询列表
     */
    @RequiresPermissions("demo:demo:list")
    @GetMapping("/list")
    public TableDataInfo list(Demo demo) {
        startPage();
        List<Demo> list = demoService.selectDemoList(demo);
        return getDataTable(list);
    }
    
    /**
     * 获取详细信息
     */
    @RequiresPermissions("demo:demo:query")
    @GetMapping(value = "/{id}")
    public AjaxResult getInfo(@PathVariable("id") Long id) {
        return success(demoService.selectDemoById(id));
    }
    
    /**
     * 新增
     */
    @RequiresPermissions("demo:demo:add")
    @Log(title = "示例管理", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody Demo demo) {
        return toAjax(demoService.insertDemo(demo));
    }
    
    /**
     * 修改
     */
    @RequiresPermissions("demo:demo:edit")
    @Log(title = "示例管理", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody Demo demo) {
        return toAjax(demoService.updateDemo(demo));
    }
    
    /**
     * 删除
     */
    @RequiresPermissions("demo:demo:remove")
    @Log(title = "示例管理", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public AjaxResult remove(@PathVariable Long[] ids) {
        return toAjax(demoService.deleteDemoByIds(ids));
    }
}

14.4 前端页面开发

14.4.1 创建API接口

// src/api/demo/demo.js
import request from '@/utils/request'

// 查询列表
export function listDemo(query) {
  return request({
    url: '/demo/demo/list',
    method: 'get',
    params: query
  })
}

// 查询详细
export function getDemo(id) {
  return request({
    url: '/demo/demo/' + id,
    method: 'get'
  })
}

// 新增
export function addDemo(data) {
  return request({
    url: '/demo/demo',
    method: 'post',
    data: data
  })
}

// 修改
export function updateDemo(data) {
  return request({
    url: '/demo/demo',
    method: 'put',
    data: data
  })
}

// 删除
export function delDemo(id) {
  return request({
    url: '/demo/demo/' + id,
    method: 'delete'
  })
}

14.4.2 创建Vue页面

<!-- src/views/demo/demo/index.vue -->
<template>
  <div class="app-container">
    <!-- 搜索区域 -->
    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
      <el-form-item label="名称" prop="name">
        <el-input v-model="queryParams.name" placeholder="请输入名称" clearable @keyup.enter="handleQuery" />
      </el-form-item>
      <el-form-item label="状态" prop="status">
        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
          <el-option label="正常" value="0" />
          <el-option label="停用" value="1" />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>

    <!-- 操作按钮 -->
    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['demo:demo:add']">新增</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['demo:demo:edit']">修改</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['demo:demo:remove']">删除</el-button>
      </el-col>
      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
    </el-row>

    <!-- 数据表格 -->
    <el-table v-loading="loading" :data="demoList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="ID" align="center" prop="id" />
      <el-table-column label="名称" align="center" prop="name" />
      <el-table-column label="编码" align="center" prop="code" />
      <el-table-column label="状态" align="center" prop="status">
        <template #default="scope">
          <el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
            {{ scope.row.status === '0' ? '正常' : '停用' }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="创建时间" align="center" prop="createTime" width="180" />
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template #default="scope">
          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['demo:demo:edit']">修改</el-button>
          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['demo:demo:remove']">删除</el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页 -->
    <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />

    <!-- 添加或修改对话框 -->
    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
      <el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="名称" prop="name">
          <el-input v-model="form.name" placeholder="请输入名称" />
        </el-form-item>
        <el-form-item label="编码" prop="code">
          <el-input v-model="form.code" placeholder="请输入编码" />
        </el-form-item>
        <el-form-item label="状态" prop="status">
          <el-radio-group v-model="form.status">
            <el-radio value="0">正常</el-radio>
            <el-radio value="1">停用</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确 定</el-button>
          <el-button @click="cancel">取 消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>

<script setup name="Demo">
import { listDemo, getDemo, addDemo, updateDemo, delDemo } from "@/api/demo/demo"

const { proxy } = getCurrentInstance()

const demoList = ref([])
const open = ref(false)
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const title = ref("")

const data = reactive({
  form: {},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    name: undefined,
    status: undefined
  },
  rules: {
    name: [{ required: true, message: "名称不能为空", trigger: "blur" }],
    code: [{ required: true, message: "编码不能为空", trigger: "blur" }]
  }
})

const { queryParams, form, rules } = toRefs(data)

/** 查询列表 */
function getList() {
  loading.value = true
  listDemo(queryParams.value).then(response => {
    demoList.value = response.rows
    total.value = response.total
    loading.value = false
  })
}

/** 搜索按钮操作 */
function handleQuery() {
  queryParams.value.pageNum = 1
  getList()
}

/** 重置按钮操作 */
function resetQuery() {
  proxy.resetForm("queryRef")
  handleQuery()
}

/** 多选框选中数据 */
function handleSelectionChange(selection) {
  ids.value = selection.map(item => item.id)
  single.value = selection.length != 1
  multiple.value = !selection.length
}

/** 新增按钮操作 */
function handleAdd() {
  reset()
  open.value = true
  title.value = "添加示例"
}

/** 修改按钮操作 */
function handleUpdate(row) {
  reset()
  const id = row.id || ids.value
  getDemo(id).then(response => {
    form.value = response.data
    open.value = true
    title.value = "修改示例"
  })
}

/** 提交按钮 */
function submitForm() {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      if (form.value.id != undefined) {
        updateDemo(form.value).then(response => {
          proxy.$modal.msgSuccess("修改成功")
          open.value = false
          getList()
        })
      } else {
        addDemo(form.value).then(response => {
          proxy.$modal.msgSuccess("新增成功")
          open.value = false
          getList()
        })
      }
    }
  })
}

/** 删除按钮操作 */
function handleDelete(row) {
  const delIds = row.id || ids.value
  proxy.$modal.confirm('是否确认删除?').then(function() {
    return delDemo(delIds)
  }).then(() => {
    getList()
    proxy.$modal.msgSuccess("删除成功")
  }).catch(() => {})
}

/** 取消按钮 */
function cancel() {
  open.value = false
  reset()
}

/** 表单重置 */
function reset() {
  form.value = {
    id: undefined,
    name: undefined,
    code: undefined,
    status: "0",
    remark: undefined
  }
  proxy.resetForm("formRef")
}

getList()
</script>

14.5 配置菜单权限

-- 插入菜单
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES('示例管理', '0', '10', 'demo', NULL, 1, 0, 'M', '0', '0', '', 'example', 'admin', sysdate(), '', null, '示例管理目录');

-- 获取上一步插入的菜单ID
SELECT @parentId := LAST_INSERT_ID();

INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES('示例列表', @parentId, '1', 'demo', 'demo/demo/index', 1, 0, 'C', '0', '0', 'demo:demo:list', '#', 'admin', sysdate(), '', null, '示例列表菜单');

SELECT @menuId := LAST_INSERT_ID();

-- 按钮权限
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES('示例查询', @menuId, '1', '#', '', 1, 0, 'F', '0', '0', 'demo:demo:query', '#', 'admin', sysdate(), '', null, '');

INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES('示例新增', @menuId, '2', '#', '', 1, 0, 'F', '0', '0', 'demo:demo:add', '#', 'admin', sysdate(), '', null, '');

INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES('示例修改', @menuId, '3', '#', '', 1, 0, 'F', '0', '0', 'demo:demo:edit', '#', 'admin', sysdate(), '', null, '');

INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES('示例删除', @menuId, '4', '#', '', 1, 0, 'F', '0', '0', 'demo:demo:remove', '#', 'admin', sysdate(), '', null, '');

14.6 小结

本章详细介绍了RuoYi-Cloud的二次开发指南,包括:

  1. 创建新模块:Maven模块、启动类、配置文件
  2. 业务开发:实体类、Mapper、Service、Controller
  3. 前端开发:API接口、Vue页面
  4. 权限配置:菜单和按钮权限SQL

掌握这些内容可以快速进行业务功能开发。


上一章:Docker容器化部署 | 返回目录 | 下一章:最佳实践与常见问题

posted @ 2026-01-08 14:05  我才是银古  阅读(4)  评论(0)    收藏  举报