TypeScript细碎知识点:tsconfig json 中有哪些配置项信息

tsconfig.json介绍

tsconfig.json是 TypeScript 项目的配置文件,放在项目的根目录。反过来说,如果一个目录里面有tsconfig.json,TypeScript 就认为这是项目的根目录。

🔔: 如果项目源码是 JavaScript,但是想用 TypeScript 处理,那么配置文件的名字是jsconfig.json,它跟tsconfig的写法是一样的。

tsconfig.json文件主要供tsc编译器使用,它的命令行参数--project-p可以指定tsconfig.json的位置(目录或文件皆可)。

$ tsc -p ./dir

🔔: 如果不指定配置文件的位置,tsc就会在当前目录下搜索tsconfig.json文件,如果不存在,就到上一级目录搜索,直到找到为止。

tsconfig.json文件的格式,是一个 JSON 对象,最简单的情况可以只放置一个空对象{}。下面是一个示例。

{
  "compilerOptions": {
    "outDir": "./built",
    "allowJs": true,
    "target": "es5"
  },
  "include": ["./src/**/*"]
}

上面示例的四个属性的含义。

  • include:指定哪些文件需要编译。
  • allowJs:指定源目录的 JavaScript 文件是否原样拷贝到编译后的目录。
  • outDir:指定编译产物存放的目录。
  • target:指定编译产物的 JS 版本。

tsconfig.json文件可以不必手写,使用 tsc 命令的--init参数自动生成。

$ tsc --init

上面命令生成的tsconfig.json文件,里面会有一些默认配置。

你也可以使用别人预先写好的 tsconfig.json 文件,npm 的@tsconfig名称空间下面有很多模块,都是写好的tsconfig.json样本,比如 @tsconfig/recommended@tsconfig/node16

这些模块需要安装,以@tsconfig/deno为例。

$ npm install --save-dev @tsconfig/deno
# 或者
$ yarn add --dev @tsconfig/deno

安装以后,就可以在tsconfig.json里面引用这个模块,相当于继承它的设置,然后进行扩展。

{
  "extends": "@tsconfig/deno/tsconfig.json"
}

@tsconfig空间下包含的完整 tsconfig 文件目录,可以查看 GitHub

tsconfig.json 重要字段

tsconfig.json的一级属性并不多,只有很少几个,但是compilerOptions属性有很多二级属性。下面先逐一介绍一级属性,然后再介绍compilerOptions的二级属性,按照首字母排序。

:一级属性

extends

tsconfig.json可以继承另一个tsconfig.json文件的配置。如果一个项目有多个配置,可以把共同的配置写成tsconfig.base.json,其他的配置文件继承该文件,这样便于维护和修改。

extends属性用来指定所要继承的配置文件。它可以是本地文件。

{
  "extends": "../tsconfig.base.json"
}

🔔: 如果extends属性指定的路径不是以./../开头,那么编译器将在node_modules目录下查找指定的配置文件。

extends属性也可以继承已发布的 npm 模块里面的 tsconfig 文件。

{
  "extends": "@tsconfig/node12/tsconfig.json"
}

extends指定的tsconfig.json会先加载,然后加载当前的tsconfig.json。如果两者有重名的属性,后者会覆盖前者。

files

files属性指定编译的文件列表,如果其中有一个文件不存在,就会报错。

它是一个数组,排在前面的文件先编译。

{
  "files": ["a.ts", "b.ts"]
}

⚠️:该属性必须逐一列出文件,不支持文件匹配。如果文件较多,建议使用includeexclude属性。

include

include属性指定所要编译的文件列表,既支持逐一列出文件,也支持通配符。文件位置相对于当前配置文件而定。

{
  "include": ["src/**/*", "tests/**/*"]
}

include属性支持三种通配符。

  • ?:指代单个字符
  • *:指代任意字符,不含路径分隔符
  • **:指定任意目录层级。

⚠️ 如果不指定文件后缀名,默认包括.ts.tsx.d.ts文件。如果打开了allowJs,那么还包括.js.jsx

exclude

exclude属性是一个数组,必须与include属性一起使用,用来从编译列表中去除指定的文件。它也支持使用与include属性相同的通配符。

{
  "include": ["**/*"],
  "exclude": ["**/*.spec.ts"]
}

references

references属性是一个数组,数组成员为对象,适合一个大项目由许多小项目构成的情况,用来设置需要引用的底层项目。

{
  "references": [
    { "path": "../pkg1" },
    { "path": "../pkg2/tsconfig.json" }
  ]
}

references数组成员对象的path属性,既可以是含有文件tsconfig.json的目录,也可以直接是该文件。

与此同时,引用的底层项目的tsconfig.json必须启用composite属性。

{
  "compilerOptions": {
    "composite": true
  }
}

compilerOptions 选项

compilerOptions属性用来定制编译行为。这个属性可以省略,这时编译器将使用默认设置。

1. allowJs

allowJs允许 TypeScript 项目加载 JS 脚本。编译时,也会将 JS 文件,一起拷贝到输出目录。

{
  "compilerOptions": {
    "allowJs": true
  }
}

2. alwaysStrict

alwaysStrict确保脚本以 ECMAScript 严格模式进行解析,因此脚本头部不用写"use strict"。它的值是一个布尔值,默认为true

3. allowSyntheticDefaultImports

allowSyntheticDefaultImports允许import命令默认加载没有default输出的模块。

☘️: 比如,打开这个设置,就可以写import React from "react";,而不是import * as React from "react";

4. allowUnreachableCode

allowUnreachableCode设置是否允许存在不可能执行到的代码。它的值有三种可能。

  • undefined: 默认值,编辑器显示警告。
  • true:忽略不可能执行到的代码。
  • false:编译器报错。

5. allowUnusedLabels

allowUnusedLabels设置是否允许存在没有用到的代码标签(label)。它的值有三种可能。

  • undefined: 默认值,编辑器显示警告。
  • true:忽略没有用到的代码标签。
  • false:编译器报错。

6. baseUrl

baseUrl的值为字符串,指定 TypeScript 项目的基准目录。

🔔: 由于默认是以 tsconfig.json 的位置作为基准目录,所以一般情况不需要使用该属性。

{
  "compilerOptions": {
    "baseUrl": "./"
  }
}

上面示例中,baseUrl为当前目录./。那么,当遇到下面的语句,TypeScript 将以./为起点,寻找hello/world.ts

import { helloWorld } from "hello/world";

7. checkJs

checkJS设置对 JS 文件同样进行类型检查。打开这个属性,也会自动打开allowJs。它等同于在 JS 脚本的头部添加// @ts-check命令。

{
  "compilerOptions":{
    "checkJs": true
  }
}

8. composite

composite打开某些设置,使得 TypeScript 项目可以进行增量构建,往往跟incremental属性配合使用。

9. declaration

declaration设置编译时是否为每个脚本生成类型声明文件.d.ts

{
  "compilerOptions": {
    "declaration": true
  }
}

10. declarationDir

declarationDir设置生成的.d.ts文件所在的目录。

{
  "compilerOptions": {
    "declaration": true,
    "declarationDir": "./types"
  }
}

11. declarationMap

declarationMap设置生成.d.ts类型声明文件的同时,还会生成对应的 Source Map 文件。

{
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true
  }
}

12. emitBOM

emitBOM设置是否在编译结果的文件头添加字节顺序标志 BOM,默认值是false

13. emitDeclarationOnly

emitDeclarationOnly设置编译后只生成.d.ts文件,不生成.js文件。

14. esModuleInterop

esModuleInterop修复了一些 CommonJS 和 ES6 模块之间的兼容性问题。

☘️ 如果module属性为node16nodenext,则esModuleInterop默认为true,其他情况默认为false

打开这个属性,使用import命令加载 CommonJS 模块时,TypeScript 会严格检查兼容性问题是否存在。

import * as moment from 'moment'
moment(); // 报错

上面示例中,根据 ES6 规范,import * as moment里面的moment是一个对象,不能当作函数调用,所以第二行报错了。

解决方法就是改写上面的语句,改成加载默认接口。

import moment from 'moment'
moment(); // 不报错

打开esModuleInterop以后,如果将上面的代码编译成 CommonJS 模块格式,就会加入一些辅助函数,保证编译后的代码行为正确。

⚠️注意,打开esModuleInterop,将自动打开allowSyntheticDefaultImports

15. exactOptionalPropertyTypes

exactOptionalPropertyTypes设置可选属性不能赋值为undefined

// 打开 exactOptionalPropertyTypes
interface MyObj {
  foo?: 'A' | 'B';
}
let obj:MyObj = { foo: 'A' };
obj.foo = undefined; // 报错

上面示例中,foo是可选属性,打开exactOptionalPropertyTypes以后,该属性就不能显式赋值为undefined

16. forceConsistentCasingInFileNames

forceConsistentCasingInFileNames设置文件名是否为大小写敏感,默认为true

17. incremental

incremental让 TypeScript 项目构建时产生文件tsbuildinfo,从而完成增量构建。

18. inlineSourceMap

inlineSourceMap设置将 SourceMap 文件写入编译后的 JS 文件中,否则会单独生成一个.js.map文件。

19. inlineSources

inlineSources设置将原始的.ts代码嵌入编译后的 JS 中。

它要求sourceMapinlineSourceMap至少打开一个。

20. isolatedModules

isolatedModules设置如果当前 TypeScript 脚本作为单个模块编译,是否会因为缺少其他脚本的类型信息而报错,主要便于非官方的编译工具(比如 Babel)正确编译单个脚本。

21. jsx

jsx设置如何处理.tsx文件。它可以取以下五个值。

  • preserve:保持 jsx 语法不变,输出的文件名为.jsx
  • react:将<div />编译成React.createElement("div"),输出的文件名为.js
  • react-native:保持 jsx 语法不变,输出的文件后缀名为.js
  • react-jsx:将<div />编译成_jsx("div"),输出的文件名为.js
  • react-jsxdev:跟react-jsx类似,但是为_jsx()加上更多的开发调试项,输出的文件名为.js
{
  "compilerOptions": {
    "jsx": "preserve"
  }
}

22. lib

lib值是一个数组,描述项目需要加载的 TypeScript 内置类型描述文件,跟三斜线指令/// <reference lib="" />作用相同。

{
  "compilerOptions": {
    "lib": ["dom", "es2021"]
  }
}

TypeScript 内置的类型描述文件,主要有以下一些,完整的清单可以参考 TypeScript 源码

  • ES5
  • ES2015
  • ES6
  • ES2016
  • ES7
  • ES2017
  • ES2018
  • ES2019
  • ES2020
  • ES2021
  • ES2022
  • ESNext
  • DOM
  • WebWorker
  • ScriptHost

23. listEmittedFiles

listEmittedFiles设置编译时在终端显示,生成了哪些文件。

{
  "compilerOptions": {
    "listEmittedFiles": true
  }
}

24. listFiles

listFiles设置编译时在终端显示,参与本次编译的文件列表。

{
  "compilerOptions": {
    "listFiles": true
  }
}

25. mapRoot

mapRoot指定 SourceMap 文件的位置,而不是默认的生成位置。

{
  "compilerOptions": {
    "sourceMap": true,
    "mapRoot": "https://my-website.com/debug/sourcemaps/"
  }
}

26. module

module指定编译产物的模块格式。它的默认值与target属性有关,如果targetES3ES5,它的默认值是commonjs,否则就是ES6/ES2015

{
  "compilerOptions": {
    "module": "commonjs"
  }
}

它可以取以下值:none、commonjs、amd、umd、system、es6/es2015、es2020、es2022、esnext、node16、nodenext。

27. moduleResolution

moduleResolution确定模块路径的算法,即如何查找模块。它可以取以下四种值。

  • node:采用 Node.js 的 CommonJS 模块算法。
  • node16nodenext:采用 Node.js 的 ECMAScript 模块算法,从 TypeScript 4.7 开始支持。
  • classic:TypeScript 1.6 之前的算法,新项目不建议使用。
  • bundler:TypeScript 5.0 新增的选项,表示当前代码会被其他打包器(比如 Webpack、Vite、esbuild、Parcel、rollup、swc)处理,从而放宽加载规则,它要求module设为es2015或更高版本,详见加入该功能的 PR 说明

它的默认值与module属性有关,如果moduleAMDUMDSystemES6/ES2015,默认值为classic;如果modulenode16nodenext,默认值为这两个值;其他情况下,默认值为Node

28. moduleSuffixes

moduleSuffixes指定模块的后缀名。

{
  "compilerOptions": {
    "moduleSuffixes": [".ios", ".native", ""]
  }
}

上面的设置使得 TypeScript 对于语句import * as foo from "./foo";,会搜索以下脚本./foo.ios.ts./foo.native.ts./foo.ts

29. newLine

newLine设置换行符为CRLF(Windows)还是LF(Linux)。

30. noEmit

noEmit设置是否产生编译结果。如果不生成,TypeScript 编译就纯粹作为类型检查了。

31. noEmitHelpers

noEmitHelpers设置在编译结果文件不插入 TypeScript 辅助函数,而是通过外部引入辅助函数来解决,比如 NPM 模块tslib

32. noEmitOnError

noEmitOnError指定一旦编译报错,就不生成编译产物,默认为false

33. noFallthroughCasesInSwitch

noFallthroughCasesInSwitch设置是否对没有break语句(或者returnthrow语句)的 switch 分支报错,即case代码里面必须有终结语句(比如break)。

34. noImplicitAny

noImplicitAny设置当一个表达式没有明确的类型描述、且编译器无法推断出具体类型时,是否允许将它推断为any类型。

它是一个布尔值,默认为true,即只要推断出any类型就报错。

35. noImplicitReturns

noImplicitReturns设置是否要求函数任何情况下都必须返回一个值,即函数必须有return语句。

36. noImplicitThis

noImplicitThis设置如果this被推断为any类型是否报错。

37. noUnusedLocals

noUnusedLocals设置是否允许未使用的局部变量。

38. noUnusedParameters

noUnusedParameters设置是否允许未使用的函数参数。

39. outDir

outDir指定编译产物的存放目录。如果不指定,编译出来的.js文件存放在对应的.ts文件的相同位置。

40. outFile

outFile设置将所有非模块的全局文件,编译在同一个文件里面。它只有在module属性为NoneSystemAMD时才生效,并且不能用来打包 CommonJS 或 ES6 模块。

41. paths

paths设置模块名和模块路径的映射,也就是 TypeScript 如何导入requireimports语句加载的模块。

paths基于baseUrl进行加载,所以必须同时设置后者。

{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "b": ["bar/b"]
    }
  }
}

它还可以使用通配符“*”。

{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@bar/*": ["bar/*"]
    }
  }
}

42. preserveConstEnums

preserveConstEnumsconst enum结构保留下来,不替换成常量值。

{
  "compilerOptions": {
    "preserveConstEnums": true
  }
}

43. pretty

pretty设置美化输出终端的编译信息,默认为true

44. removeComments

removeComments移除 TypeScript 脚本里面的注释,默认为false

45. resolveJsonModule

resolveJsonModule允许 import 命令导入 JSON 文件。

46. rootDir

rootDir设置源码脚本所在的目录,主要跟编译后的脚本结构有关。rootDir对应目录下的所有脚本,会成为输出目录里面的顶层脚本。

47. rootDirs

rootDirs把多个不同目录,合并成一个虚拟目录,便于模块定位。

{
  "compilerOptions": {
    "rootDirs": ["bar", "foo"]
  }
}

上面示例中,rootDirsbarfoo组成一个虚拟目录。

48. sourceMap

sourceMap设置编译时是否生成 SourceMap 文件。

49. sourceRoot

sourceRoot在 SourceMap 里面设置 TypeScript 源文件的位置。

{
  "compilerOptions": {
    "sourceMap": true,
    "sourceRoot": "https://my-website.com/debug/source/"
  }
}

50. strict

strict用来打开 TypeScript 的严格检查。它的值是一个布尔值,默认是关闭的。

{
  "compilerOptions": {
    "strict": true
  }
}

这个设置相当于同时打开以下的一系列设置。

  • alwaysStrict
  • strictNullChecks
  • strictBindCallApply
  • strictFunctionTypes
  • strictPropertyInitialization
  • noImplicitAny
  • noImplicitThis
  • useUnknownInCatchVariables

打开strict的时候,允许单独关闭其中一项。

{
  "compilerOptions": {
    "strict": true,
    "alwaysStrict": false
  }
}

51. strictBindCallApply

strictBindCallApply设置是否对函数的call()bind()apply()这三个方法进行类型检查。

如果不打开strictBindCallApply编译选项,编译器不会对以上三个方法进行类型检查,参数类型都是any,传入任何参数都不会产生编译错误。

function fn(x: string) {
  return parseInt(x);
}

// strictBindCallApply:false
const n = fn.call(undefined, false);
// 以上不报错

52. strictFunctionTypes

strictFunctionTypes允许对函数更严格的参数检查。具体来说,如果函数 B 的参数是函数 A 参数的子类型,那么函数 B 不能替代函数 A。

function fn(x:string) {
  console.log('Hello, ' + x.toLowerCase());
}

type StringOrNumberFunc = (ns:string|number) => void;

// 打开 strictFunctionTypes,下面代码会报错
let func:StringOrNumberFunc = fn;

上面示例中,函数fn()的参数是StringOrNumberFunc参数的子集,因此fn不能替代StringOrNumberFunc

53. strictNullChecks

strictNullChecks设置对nullundefined进行严格类型检查。如果打开strict属性,这一项就会自动设为true,否则为false

let value:string;

// strictNullChecks:false
// 下面语句不报错
value = null;

它可以理解成只要打开,就需要显式检查nullundefined

function doSomething(x:string|null) {
  if (x === null) {
    // do nothing
  } else {
    console.log("Hello, " + x.toUpperCase());
  }
}

54. strictPropertyInitialization

strictPropertyInitialization设置类的实例属性都必须初始化,包括以下几种情况。

  • 设为undefined类型
  • 显式初始化
  • 构造函数中赋值

⚠️ 注意,使用该属性的同时,必须打开strictNullChecks

// strictPropertyInitialization:true
class User {
  // 报错,属性 username 没有初始化
  username: string;
}

// 解决方法一
class User {
  username = '张三';
}

// 解决方法二
class User {
  username:string|undefined;
}

// 解决方法三
class User {
  username:string;

  constructor(username:string) {
    this.username = username;
  }
}
// 或者
class User {
  constructor(public username:string) {}
}

// 解决方法四:赋值断言
class User {
  username!:string;

  constructor(username:string) {
    this.initialize(username);
  }

  private initialize(username:string) {
    this.username = username;
  }
}

55. suppressExcessPropertyErrors

suppressExcessPropertyErrors关闭对象字面量的多余参数的报错。

56. target

target指定编译出来的 JavaScript 代码的 ECMAScript 版本,比如es2021,默认是es3

它可以取以下值。

  • es3
  • es5
  • es6/es2015
  • es2016
  • es2017
  • es2018
  • es2019
  • es2020
  • es2021
  • es2022
  • esnext

⚠️注意,如果编译的目标版本过老,比如"target": "es3",有些语法可能无法编译,tsc命令会报错。

57. traceResolution

traceResolution设置编译时,在终端输出模块解析的具体步骤。

{
  "compilerOptions": {
    "traceResolution": true
  }
}

58. typeRoots

typeRoots设置类型模块所在的目录,默认是node_modules/@types,该目录里面的模块会自动加入编译。一旦指定了该属性,就不会再用默认值node_modules/@types里面的类型模块。

该属性的值是一个数组,数组的每个成员就是一个目录,它们的路径是相对于tsconfig.json位置。

{
  "compilerOptions": {
    "typeRoots": ["./typings", "./vendor/types"]
  }
}

59. types

默认情况下,typeRoots目录下所有模块都会自动加入编译,如果指定了types属性,那么只有其中列出的模块才会自动加入编译。

{
  "compilerOptions": {
    "types": ["node", "jest", "express"]
  }
}

上面的设置表示,默认情况下,只有./node_modules/@types/node./node_modules/@types/jest./node_modules/@types/express会自动加入编译,其他node_modules/@types/目录下的模块不会加入编译。

如果"types": [],就表示不会自动将所有@types模块加入编译。

60. useDefineForClassFields

useDefineForClassFields这个设置针对的是,在类(class)的顶部声明的属性。TypeScript 早先对这一类属性的处理方法,与写入 ES2022 标准的处理方法不一致。这个设置设为true,就用来开启 ES2022 的处理方法,设为false就是 TypeScript 原有的处理方法。

它的默认值跟target属性有关,如果编译目标是ES2022或更高,那么useDefineForClassFields默认值为true,否则为false

61. useUnknownInCatchVariables

useUnknownInCatchVariables设置catch语句捕获的try抛出的返回值类型,从any变成unknown

try {
  someExternalFunction();
} catch (err) {
  err; // 类型 any
}

上面示例中,默认情况下,catch语句的参数err类型是any,即可以是任何值。

打开useUnknownInCatchVariables以后,err的类型抛出的错误将是unknown类型。这带来的变化就是使用err之前,必须缩小它的类型,否则会报错。

try {
  someExternalFunction();
} catch (err) {
  if (err instanceof Error) {
    console.log(err.message);
  }
}

compilerOptions 选项示例

{
  "compilerOptions": {
  
    /* 基本选项 */
    "target": "es5",                       // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
    "module": "commonjs",                  // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "lib": [],                             // 指定要包含在编译中的库文件
    "allowJs": true,                       // 允许编译 javascript 文件
    "checkJs": true,                       // 报告 javascript 文件中的错误
    "jsx": "preserve",                     // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
    "declaration": true,                   // 生成相应的 '.d.ts' 文件
    "sourceMap": true,                     // 生成相应的 '.map' 文件
    "outFile": "./",                       // 将输出文件合并为一个文件
    "outDir": "./",                        // 指定输出目录
    "rootDir": "./",                       // 用来控制输出目录结构 --outDir.
    "removeComments": true,                // 删除编译后的所有的注释
    "noEmit": true,                        // 不生成输出文件
    "importHelpers": true,                 // 从 tslib 导入辅助工具函数
    "isolatedModules": true,               // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).

    /* 严格的类型检查选项 */
    "strict": true,                        // 启用所有严格类型检查选项
    "noImplicitAny": true,                 // 在表达式和声明上有隐含的 any类型时报错
    "strictNullChecks": true,              // 启用严格的 null 检查
    "noImplicitThis": true,                // 当 this 表达式值为 any 类型的时候,生成一个错误
    "alwaysStrict": true,                  // 以严格模式检查每个模块,并在每个文件里加入 'use strict'

    /* 额外的检查 */
    "noUnusedLocals": true,                // 有未使用的变量时,抛出错误
    "noUnusedParameters": true,            // 有未使用的参数时,抛出错误
    "noImplicitReturns": true,             // 并不是所有函数里的代码都有返回值时,抛出错误
    "noFallthroughCasesInSwitch": true,    // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)

    /* 模块解析选项 */
    "moduleResolution": "node",            // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
    "baseUrl": "./",                       // 用于解析非相对模块名称的基目录
    "paths": {},                           // 模块名到基于 baseUrl 的路径映射的列表
    "rootDirs": [],                        // 根文件夹列表,其组合内容表示项目运行时的结构内容
    "typeRoots": [],                       // 包含类型声明的文件列表
    "types": [],                           // 需要包含的类型声明文件名列表
    "allowSyntheticDefaultImports": true,  // 允许从没有设置默认导出的模块中默认导入。

    /* Source Map Options */
    "sourceRoot": "./",                    // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
    "mapRoot": "./",                       // 指定调试器应该找到映射文件而不是生成文件的位置
    "inlineSourceMap": true,               // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
    "inlineSources": true,                 // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性

    /* 其他选项 */
    "experimentalDecorators": true,        // 启用装饰器
    "emitDecoratorMetadata": true          // 为装饰器提供元数据的支持
  }
}

posted on 2024-04-10 15:53  梁飞宇  阅读(611)  评论(0)    收藏  举报