[
  {
    "path": ".gitignore",
    "content": ".DS_Store\r\nnode_modules\r\n/dist\r\n/tar\r\n\r\n# local env files\r\n.env.local\r\n.env.*.local\r\n\r\n# Log files\r\nnpm-debug.log*\r\nyarn-debug.log*\r\nyarn-error.log*\r\n\r\n# Editor directories and files\r\n.idea\r\n*.suo\r\n*.ntvs*\r\n*.njsproj\r\n*.sln\r\n*.sw?\r\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"eslint.workingDirectories\": [\n    \"./onlyoffice-server\",\n    \"./onlyoffice-vue\"\n  ],\n  \"vetur.validation.interpolation\": false\n}"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 wytxer\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# DEMO ONLYOFFICE\r\n\r\n基于 Nest 和 Vue.js 的 Onlyoffice 示例。\r\n\r\n> Onlyoffice@7.2 之后的版本默认开启了 JWT 加密，运行仓库例子时请先关闭 JWT 校验。\r\n\r\n\r\n## 快速开始\r\n\r\n注意，在启动之前，需要将 **onlyoffice-vue/.env** 里面的 `VUE_APP_HOST` 和 **onlyoffice-server/.env** 里面的 `HOST` 字段统一替换成自己的本机 IP。\r\n\r\n```bash\r\n# 启动前端\r\ncd onlyoffice-vue\r\n# 安装依赖\r\npnpm install\r\n# 启动开发服务\r\npnpm run dev\r\n\r\n# 启动后端\r\ncd onlyoffice-server\r\n# 安装依赖\r\npnpm install\r\n# 启动开发服务\r\npnpm run dev\r\n```\r\n\r\n\r\n## 相关文章\r\n\r\n文章与仓库代码是一体的，强烈建议跟着文章步骤运行例子。\r\n\r\n- [在 Vue 中接入 Onlyoffice](https://bszhct.com/2022/08/15/onlyoffice-quick-start/)\r\n- [Onlyoffice 二次开发指南，包含自定义 Onlyoffice 配置、使用 JWT 加密 Onlyoffice 配置、Onlyoffice 文档转换等](https://bszhct.com/2022/08/19/onlyoffice-usage/)\r\n- [Onlyoffice 插件开发指南](https://bszhct.com/2022/08/20/onlyoffice-plugin/)\r\n- [在线 Office 解决方案调研总结](https://bszhct.com/2022/08/14/online-office-summary/)\r\n\r\n\r\n## 填坑交流群\r\n\r\n添加后请备注「oo」\r\n\r\n<img src=\"./qrcode.jpg\" style=\"width: 120px\" />\r\n\r\n\r\n## License\r\n\r\n[MIT](/LICENSE)\r\n"
  },
  {
    "path": "onlyoffice-server/.eslintrc.js",
    "content": "module.exports = {\n  parser: '@typescript-eslint/parser',\n  parserOptions: {\n    project: 'tsconfig.json',\n    tsconfigRootDir : __dirname, \n    sourceType: 'module',\n  },\n  plugins: ['@typescript-eslint/eslint-plugin'],\n  extends: [\n    'plugin:@typescript-eslint/recommended',\n    'plugin:prettier/recommended',\n  ],\n  root: true,\n  env: {\n    node: true,\n    jest: true,\n  },\n  ignorePatterns: ['.eslintrc.js'],\n  rules: {\n    '@typescript-eslint/interface-name-prefix': 'off',\n    '@typescript-eslint/explicit-function-return-type': 'off',\n    '@typescript-eslint/explicit-module-boundary-types': 'off',\n    '@typescript-eslint/no-explicit-any': 'off',\n  },\n};\n"
  },
  {
    "path": "onlyoffice-server/.gitignore",
    "content": "# compiled output\n/dist\n/node_modules\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\npnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# OS\n.DS_Store\n\n# Tests\n/coverage\n/.nyc_output\n\n# IDEs and editors\n/.idea\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# IDE - VSCode\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json"
  },
  {
    "path": "onlyoffice-server/.prettierrc",
    "content": "{\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\"\n}"
  },
  {
    "path": "onlyoffice-server/README.md",
    "content": "# Onlyoffice Server\n\n基于 nest 的 Onlyoffice 示例。\n\n\n## 快速使用\n\n```bash\n# 安装依赖\npnpm install\n\n# 开发\npnpm run dev\n\n# 打包\npnpm run build\n```\n\n\n## License\n\n[MIT](/LICENSE)\n"
  },
  {
    "path": "onlyoffice-server/nest-cli.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/nest-cli\",\n  \"collection\": \"@nestjs/schematics\",\n  \"sourceRoot\": \"src\",\n  \"compilerOptions\": {\n    \"plugins\": [\n      {\n        \"name\": \"@nestjs/swagger\",\n        \"options\": {\n          \"classValidatorShim\": true,\n          \"introspectComments\": true\n        }\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "onlyoffice-server/package.json",
    "content": "{\n  \"name\": \"onlyoffice-server\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"author\": \"wytxer\",\n  \"private\": true,\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"prebuild\": \"rimraf dist\",\n    \"build\": \"nest build\",\n    \"format\": \"prettier --write \\\"src/**/*.ts\\\" \\\"test/**/*.ts\\\"\",\n    \"dev\": \"pnpm run start:test\",\n    \"start\": \"cross-env NODE_ENV=online nest start\",\n    \"start:test\": \"cross-env NODE_ENV=test nest start --watch\",\n    \"start:debug\": \"cross-env NODE_ENV=test nest start --debug --watch\",\n    \"start:online\": \"cross-env NODE_ENV=online node dist/main\",\n    \"lint\": \"eslint \\\"{src,apps,libs,test}/**/*.ts\\\" --fix\",\n    \"test\": \"jest\",\n    \"test:watch\": \"jest --watch\",\n    \"test:cov\": \"jest --coverage\",\n    \"test:debug\": \"node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand\",\n    \"test:e2e\": \"jest --config ./test/jest-e2e.json\"\n  },\n  \"dependencies\": {\n    \"@nestjs/axios\": \"^0.1.0\",\n    \"@nestjs/common\": \"^9.0.0\",\n    \"@nestjs/config\": \"^2.2.0\",\n    \"@nestjs/core\": \"^9.0.0\",\n    \"@nestjs/jwt\": \"^9.0.0\",\n    \"@nestjs/platform-express\": \"^9.0.0\",\n    \"@nestjs/serve-static\": \"^3.0.0\",\n    \"@nestjs/swagger\": \"^6.0.1\",\n    \"axios\": \"^0.27.2\",\n    \"chalk\": \"^5.0.1\",\n    \"class-transformer\": \"^0.5.1\",\n    \"class-validator\": \"^0.13.2\",\n    \"cross-env\": \"^7.0.3\",\n    \"reflect-metadata\": \"^0.1.13\",\n    \"rimraf\": \"^3.0.2\",\n    \"rxjs\": \"^7.2.0\",\n    \"swagger-ui-express\": \"^4.4.0\"\n  },\n  \"devDependencies\": {\n    \"@nestjs/cli\": \"^9.0.0\",\n    \"@nestjs/schematics\": \"^9.0.0\",\n    \"@nestjs/testing\": \"^9.0.0\",\n    \"@types/express\": \"^4.17.13\",\n    \"@types/jest\": \"28.1.4\",\n    \"@types/node\": \"^16.0.0\",\n    \"@types/supertest\": \"^2.0.11\",\n    \"@typescript-eslint/eslint-plugin\": \"^5.0.0\",\n    \"@typescript-eslint/parser\": \"^5.0.0\",\n    \"eslint\": \"^8.0.1\",\n    \"eslint-config-prettier\": \"^8.3.0\",\n    \"eslint-plugin-prettier\": \"^4.0.0\",\n    \"jest\": \"28.1.2\",\n    \"prettier\": \"^2.3.2\",\n    \"source-map-support\": \"^0.5.20\",\n    \"supertest\": \"^6.1.3\",\n    \"ts-jest\": \"28.0.5\",\n    \"ts-loader\": \"^9.2.3\",\n    \"ts-node\": \"^10.0.0\",\n    \"tsconfig-paths\": \"4.0.0\",\n    \"typescript\": \"^4.3.5\"\n  },\n  \"jest\": {\n    \"moduleFileExtensions\": [\n      \"js\",\n      \"json\",\n      \"ts\"\n    ],\n    \"rootDir\": \"src\",\n    \"testRegex\": \".*\\\\.spec\\\\.ts$\",\n    \"transform\": {\n      \"^.+\\\\.(t|j)s$\": \"ts-jest\"\n    },\n    \"collectCoverageFrom\": [\n      \"**/*.(t|j)s\"\n    ],\n    \"coverageDirectory\": \"../coverage\",\n    \"testEnvironment\": \"node\"\n  }\n}\n"
  },
  {
    "path": "onlyoffice-server/src/app.controller.ts",
    "content": "import { Controller } from '@nestjs/common';\n\n@Controller()\nexport class AppController {}\n"
  },
  {
    "path": "onlyoffice-server/src/app.module.ts",
    "content": "import { Module } from '@nestjs/common';\nimport { ConfigModule } from '@nestjs/config';\nimport { ServeStaticModule } from '@nestjs/serve-static';\nimport { join } from 'path';\nimport config from './shared/config';\nimport { SharedModule } from './shared/shared.module';\nimport { AppController } from './app.controller';\nimport { OnlyofficeModule } from './onlyoffice/onlyoffice.module';\nimport { DocumentModule } from './document/document.module';\n\n@Module({\n  imports: [\n    // 导入全局变量配置\n    ConfigModule.forRoot({\n      isGlobal: true,\n      expandVariables: true,\n      load: [config],\n    }),\n    // 静态资源服务配置\n    ServeStaticModule.forRoot({\n      serveRoot: '/static',\n      rootPath: join(__dirname, '..', 'static'),\n    }),\n    SharedModule,\n    OnlyofficeModule,\n    DocumentModule,\n  ],\n  controllers: [AppController],\n  providers: [],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "onlyoffice-server/src/document/document.controller.ts",
    "content": "import { Controller, Post, Get, Body, Query } from '@nestjs/common';\nimport { ApiTags, ApiOperation } from '@nestjs/swagger';\nimport { DocumentForceSaveDto, DocumentInfoDto } from './document.dto';\nimport { DocumentInfo } from './document.entity';\nimport { DocumentService } from './document.service';\n\n@ApiTags('Document')\n@Controller({\n  path: 'document',\n  version: '1',\n})\nexport class DocumentController {\n  constructor(private documentService: DocumentService) {}\n  @Post('forceSave')\n  @ApiOperation({\n    summary: '强制保存文档',\n    description:\n      '通过调用 Onlyoffice 提供的指令接口间接保存文件，最终文件的报错操作还是在 editorConfig.callbackUrl 所指定的接口里面完成的',\n  })\n  async forceSave(@Body() body: DocumentForceSaveDto): Promise<any> {\n    return await this.documentService.forceSave(body);\n  }\n\n  @Get('documentInfo')\n  @ApiOperation({\n    summary: '获取文档信息',\n    description: '仅构造 Onlyoffice 文档编辑器显示和保存需要的必要信息',\n  })\n  async documentInfo(@Query() query: DocumentInfoDto): Promise<DocumentInfo> {\n    return await this.documentService.documentInfo(query);\n  }\n\n  @Get('excelInfo')\n  @ApiOperation({\n    summary: '获取表格信息',\n    description: '仅构造 Onlyoffice 表格编辑器显示和保存需要的必要信息',\n  })\n  async excelInfo(@Query() query: DocumentInfoDto): Promise<DocumentInfo> {\n    return await this.documentService.excelInfo(query);\n  }\n}\n"
  },
  {
    "path": "onlyoffice-server/src/document/document.dto.ts",
    "content": "import { IsString, IsNumber, IsNotEmpty, IsIn } from 'class-validator';\n\n/**\n * 文档强制保存请求参数\n */\nexport class DocumentForceSaveDto {\n  /**\n   * 业务 id\n   */\n  @IsNumber()\n  id: string;\n\n  /**\n   * 文档标识符\n   */\n  @IsString()\n  key: string;\n\n  /**\n   * 使用 JWT 加密文档参数，默认不加密，需要配合 Onlyoffice 的 secret 配置使用。\n   */\n  @IsString()\n  @IsIn(['y', 'n'])\n  useJwtEncrypt?: string = 'n';\n}\n\n/**\n * 获取文档信息请求参数\n */\nexport class DocumentInfoDto {\n  /**\n   * 文档标识符（为了获取本地缓存的文档文件）\n   * @example test1.docx\n   */\n  @IsNotEmpty()\n  @IsString()\n  key: string;\n\n  /**\n   * 使用 JWT 加密文档参数，默认不加密，需要配合 Onlyoffice 的 secret 配置使用。\n   */\n  @IsString()\n  @IsIn(['y', 'n'])\n  useJwtEncrypt?: string = 'n';\n\n  /**\n   * 使用插件。默认不返回插件配置\n   */\n  @IsString()\n  @IsIn(['y', 'n'])\n  usePlugin?: string = 'n';\n}\n"
  },
  {
    "path": "onlyoffice-server/src/document/document.entity.ts",
    "content": "import { IsString, IsNumber, IsObject } from 'class-validator';\nimport { OnlyofficeEditorConfig } from '../onlyoffice/onlyoffice.entity';\n\nexport class DocumentInfo {\n  /**\n   * 业务 id\n   */\n  @IsNumber()\n  id?: number;\n\n  /**\n   * 备注\n   */\n  @IsNumber()\n  remarks?: string;\n\n  /**\n   * 编辑器配置\n   */\n  @IsObject()\n  editorConfig: OnlyofficeEditorConfig;\n}\n\nexport class DocumentForceSave {\n  /**\n   * 状态码\n   */\n  @IsNumber()\n  code: number;\n\n  /**\n   * 消息\n   */\n  @IsString()\n  message?: string;\n}\n"
  },
  {
    "path": "onlyoffice-server/src/document/document.module.ts",
    "content": "import { Module } from '@nestjs/common';\n\nimport { DocumentController } from './document.controller';\nimport { DocumentService } from './document.service';\nimport { OnlyofficeService } from '../onlyoffice/onlyoffice.service';\n\n@Module({\n  controllers: [DocumentController],\n  providers: [DocumentService, OnlyofficeService],\n})\nexport class DocumentModule {}\n"
  },
  {
    "path": "onlyoffice-server/src/document/document.service.ts",
    "content": "import { ConfigService } from '@nestjs/config';\nimport { Injectable, HttpStatus, HttpException } from '@nestjs/common';\nimport { OnlyofficeService } from '../onlyoffice/onlyoffice.service';\nimport { DocumentForceSaveDto, DocumentInfoDto } from './document.dto';\nimport { DocumentForceSave, DocumentInfo } from './document.entity';\n\n@Injectable()\nexport class DocumentService {\n  constructor(\n    private readonly config: ConfigService,\n    private onlyofficeService: OnlyofficeService,\n  ) {}\n\n  async forceSave(body: DocumentForceSaveDto): Promise<DocumentForceSave> {\n    // 1、保存业务数据\n    // 2、调用 Onlyoffice 的强制保存，实际业务中可能还有更多的业务操作，可根据实际情况删改\n    const { id: userdata, key, useJwtEncrypt } = body;\n    const data = await this.onlyofficeService.forceSave({\n      key,\n      // 将业务参数传给 Onlyoffice 服务，当回调里面存在多个请求时，标识符将有助于区分特定请求\n      userdata,\n      useJwtEncrypt,\n    });\n    // 保存成功\n    if (data.error === 0) {\n      return null;\n    }\n    throw new HttpException(data, HttpStatus.OK);\n  }\n\n  async documentInfo(query: DocumentInfoDto): Promise<DocumentInfo> {\n    const editorConfig = this.onlyofficeService.editorDefaultConfig();\n    // 添加文档\n    editorConfig.document = {\n      ...editorConfig.document,\n      fileType: 'docx',\n      key: query.key,\n      url: `${this.config.get('domain')}/static/${query.key}`,\n      title: '测试文档.docx',\n    };\n    // 添加用户信息\n    editorConfig.editorConfig.user = {\n      group: '技术部',\n      id: 'wytxer',\n      name: '程序员未央',\n    };\n    // 添加插件配置\n    if (query.usePlugin === 'y') {\n      editorConfig.editorConfig.plugins = {\n        autostart: [],\n        pluginsData: [\n          `${this.config.get(\n            'domain',\n          )}/static/plugins/plugin-hello/config.json`,\n        ],\n      };\n    }\n    // 加密编辑器参数\n    if (query.useJwtEncrypt === 'y') {\n      this.onlyofficeService.signJwt(editorConfig);\n    }\n    return {\n      id: 1,\n      remarks: '业务字段',\n      editorConfig,\n    };\n  }\n\n  async excelInfo(query: DocumentInfoDto): Promise<DocumentInfo> {\n    const editorConfig = this.onlyofficeService.editorDefaultConfig();\n    // 添加文档\n    editorConfig.document = {\n      ...editorConfig.document,\n      fileType: 'xlsx',\n      key: query.key,\n      url: `${this.config.get('domain')}/static/${query.key}`,\n      title: '测试表格.xlsx',\n    };\n    // 修改文档宽度\n    editorConfig.width = '100%';\n    // 修改编辑器类型\n    editorConfig.documentType = 'cell';\n    // 添加用户信息\n    editorConfig.editorConfig.user = {\n      group: '技术部',\n      id: 'wytxer',\n      name: '程序员未央',\n    };\n    // 加密编辑器参数\n    if (query.useJwtEncrypt === 'y') {\n      this.onlyofficeService.signJwt(editorConfig);\n    }\n    return {\n      id: 1,\n      remarks: '业务字段',\n      editorConfig,\n    };\n  }\n}\n"
  },
  {
    "path": "onlyoffice-server/src/main.ts",
    "content": "import { NestFactory } from '@nestjs/core';\nimport { ValidationPipe, VersioningType } from '@nestjs/common';\nimport { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';\nimport { AppModule } from './app.module';\nimport { ResponseInterceptor } from './shared/interceptors/response.interceptor';\nimport { LoggingInterceptor } from './shared/interceptors/logger.interceptor';\n\nasync function bootstrap() {\n  const app = await NestFactory.create(AppModule, { cors: true });\n\n  // 设置接口前缀\n  app.setGlobalPrefix(process.env.API_PREFIX);\n\n  app.useGlobalPipes(\n    new ValidationPipe({\n      // 跳过验证对象中值为 null 或 undefined 的属性的验证。完整配置文档参见：https://docs.nestjs.cn/9/techniques?id=%e9%aa%8c%e8%af%81\n      skipNullProperties: true,\n      stopAtFirstError: true,\n      transform: true,\n    }),\n  );\n\n  // 添加全局数据响应拦截器\n  app.useGlobalInterceptors(\n    new ResponseInterceptor(),\n    new LoggingInterceptor(),\n  );\n\n  // 版本号配置\n  app.enableVersioning({\n    type: VersioningType.URI,\n    defaultVersion: '1',\n  });\n\n  // 接口文档配置\n  const isTest = process.env.NODE_ENV === 'test';\n  if (isTest) {\n    const options = new DocumentBuilder()\n      .setTitle('Demo Onlyoffice 接口文档')\n      .setVersion('1.0.0')\n      .addTag('Onlyoffice')\n      .addTag('Document')\n      .build();\n    const document = SwaggerModule.createDocument(app, options);\n    SwaggerModule.setup('docs', app, document);\n  }\n\n  //  启动服务\n  await app.listen(process.env.PORT);\n}\nbootstrap();\n"
  },
  {
    "path": "onlyoffice-server/src/onlyoffice/onlyoffice.controller.ts",
    "content": "import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';\nimport { ApiTags, ApiOperation } from '@nestjs/swagger';\nimport { OnlyofficeCallbackDto } from './onlyoffice.dto';\nimport { OnlyofficeCallback } from './onlyoffice.entity';\nimport { OnlyofficeService } from './onlyoffice.service';\n\n@ApiTags('Onlyoffice')\n@Controller({\n  path: 'onlyoffice',\n  version: '1',\n})\nexport class OnlyofficeController {\n  constructor(private onlyofficeService: OnlyofficeService) {}\n  @Post('callback')\n  @ApiOperation({\n    summary: '文档回调地址',\n    description: '对应 Onlyoffice 的 editorConfig.callbackUrl 字段',\n  })\n  // 这里表示成功的 statusCode 状态不能返回 201，否则会报错「这份文件无法保存。请检查连接设置或联系您的管理员」，因为在 Onlyoffice 如果 statusCode 不等于 200 认为是失败\n  @HttpCode(HttpStatus.OK)\n  async callback(\n    // @Body() body: OnlyofficeCallbackDto,\n    @Body() body: any,\n  ): Promise<OnlyofficeCallback> {\n    return await this.onlyofficeService.callback(body);\n  }\n}\n"
  },
  {
    "path": "onlyoffice-server/src/onlyoffice/onlyoffice.dto.ts",
    "content": "import {\n  IsString,\n  IsNumber,\n  IsObject,\n  IsArray,\n  IsDefined,\n  IsIn,\n} from 'class-validator';\n\nexport class IActions {\n  /**\n   * 操作类型\n   */\n  @IsNumber()\n  type: number;\n\n  /**\n   * 用户 id\n   */\n  @IsNumber()\n  userid?: string | number;\n}\n\nexport class IUser {\n  /**\n   * 用户 id\n   */\n  @IsNumber()\n  id?: string | number;\n\n  /**\n   * 用户名，里面包含了组织信息和用户名\n   * @example 技术部 程序员未央\n   */\n  @IsString()\n  name?: string;\n}\n\nclass IChange {\n  /**\n   * 创建时间\n   */\n  @IsString()\n  created?: string;\n\n  /**\n   * 用户信息\n   */\n  @IsObject()\n  user?: IUser;\n}\n\nexport class IHistory {\n  /**\n   * 当前 Onlyoffice 服务版本号\n   */\n  @IsString()\n  serverVersion?: string;\n\n  /**\n   * 历史记录信息\n   */\n  @IsArray()\n  changes?: IChange[];\n}\n\n/**\n * 回调请求参数，文档地址：https://api.onlyoffice.com/editors/callback\n */\nexport class OnlyofficeCallbackDto {\n  /**\n   * 用户与文档的交互状态。0：用户断开与文档共同编辑的连接；1：新用户连接到文档共同编辑；2：用户单击强制保存按钮\n   */\n  @IsArray()\n  actions?: IActions[] = null;\n\n  /**\n   * 字段已在 4.2 后版本废弃，请使用 history 代替\n   */\n  @IsObject()\n  changeshistory?: IHistory = null;\n\n  /**\n   * 文档变更的历史记录，仅当 status 等于 2 或者 3 时该字段才有值。其中的 serverVersion 字段也是 refreshHistory 方法的入参\n   */\n  @IsObject()\n  history?: IHistory = null;\n\n  /**\n   * 文档编辑的元数据信息，用来跟踪显示文档更改记录，仅当 status 等于 2 或者 2 时该字段才有值。该字段也是 setHistoryData（显示与特定文档版本对应的更改，类似 Git 历史记录）方法的入参\n   */\n  @IsString()\n  changesurl?: string = null;\n\n  /**\n   * url 字段下载的文档扩展名，文件类型默认为 OOXML 格式，如果启用了 assemblyFormatAsOrigin（https://api.onlyoffice.com/editors/save#assemblyFormatAsOrigin） 服务器设置则文件以原始格式保存\n   */\n  @IsString()\n  filetype?: string = null;\n\n  /**\n   * 文档强制保存类型。0：对命令服务（https://api.onlyoffice.com/editors/command/forcesave）执行强制保存；1：每次保存完成时都会执行强制保存请求，仅设置 forcesave 等于 true 时生效；2：强制保存请求由计时器使用服务器中的设置执行。该字段仅 status 等于 7 或者 7 时才有值\n   */\n  @IsNumber()\n  forcesavetype?: number = null;\n\n  /**\n   * 文档标识符，类似 id，在 Onlyoffice 服务内部唯一\n   */\n  @IsDefined()\n  @IsString()\n  key: string;\n\n  /**\n   * 文档状态。1：文档编辑中；2：文档已准备好保存；3：文档保存出错；4：文档没有变化无需保存；6：正在编辑文档，但保存了当前文档状态；7：强制保存文档出错\n   */\n  @IsNumber()\n  @IsIn([1, 2, 3, 4, 6, 7])\n  status?: number = null;\n\n  /**\n   * 已编辑文档的链接，可以通过它下载到最新的文档，仅当 status 等于 2、3、6 或 7 时该字段才有值\n   */\n  @IsString()\n  url?: string = null;\n\n  /**\n   * 自定义参数，对应指令服务的 userdata 字段\n   */\n  @IsObject()\n  userdata?: object = null;\n\n  /**\n   * 打开文档进行编辑的用户标识列表，当文档被修改时，该字段将返回最后编辑文档的用户标识符，当 status 字段等于 2 或者 6 时有值\n   */\n  @IsArray()\n  users?: string[] = null;\n\n  /**\n   * 最近保存时间\n   */\n  @IsString()\n  lastsave?: string = null;\n\n  /**\n   * 加密令牌\n   */\n  @IsString()\n  token?: string = null;\n}\n\n/**\n * 强制保存请求参数\n */\nexport class OnlyofficeForceSaveDto {\n  /**\n   * 文档标识符\n   */\n  @IsString()\n  key: string;\n\n  /**\n   * 用户自定义的数据\n   */\n  @IsString()\n  userdata?: string = null;\n\n  /**\n   * 使用 JWT 加密文档参数，默认不加密，需要配合 Onlyoffice 的 secret 配置使用。\n   */\n  @IsString()\n  @IsIn(['y', 'n'])\n  useJwtEncrypt?: string = 'n';\n}\n"
  },
  {
    "path": "onlyoffice-server/src/onlyoffice/onlyoffice.entity.ts",
    "content": "import { IsString, IsNumber, IsObject, IsIn } from 'class-validator';\n\nclass IFeatures {\n  /**\n   * 是否启用拼写检查\n   */\n  spellcheck?: boolean = false;\n}\n\nclass ICustomization {\n  /**\n   * 强制保存\n   */\n  forcesave?: boolean = true;\n\n  /**\n   * 自定义参数\n   */\n  features?: IFeatures = new IFeatures();\n}\n\nclass IPlugins {\n  /**\n   * 需要自动加载的插件列表\n   */\n  autostart: string[] = [];\n\n  /**\n   * 自定义的插件列表\n   */\n  pluginsData: string[] = [];\n}\n\nclass IPermission {\n  /**\n   * 是否启用评论\n   */\n  comment?: boolean = false;\n\n  /**\n   * 是否启用下载\n   */\n  download?: boolean = true;\n\n  /**\n   * 是否启用编辑\n   */\n  edit?: boolean = true;\n\n  /**\n   * 是否启用导出\n   */\n  print?: boolean = true;\n\n  /**\n   * 是否启用预览\n   */\n  review?: boolean = true;\n}\n\nclass IDocument {\n  /**\n   * 文件类型\n   */\n  fileType = 'docx';\n\n  /**\n   * 文档标识符\n   */\n  key: string;\n\n  /**\n   * 文档地址，绝对路径\n   */\n  url: string;\n\n  /**\n   * 文档标题\n   */\n  title: string;\n\n  /**\n   * 文档权限配置\n   */\n  permissions: IPermission = new IPermission();\n}\n\nclass IEditorConfig {\n  /**\n   * 回调地址\n   */\n  callbackUrl: string = process.env.ONLYOFFICE_CALLBACK;\n\n  /**\n   * 语言\n   */\n  lang?: string = 'zh-CN';\n\n  /**\n   * 用户信息\n   */\n  user: object = {};\n\n  /**\n   * 模板列表\n   */\n  templates?: object[] = [];\n\n  /**\n   * 自定义配置。字段相关配置详解：https://api.onlyoffice.com/editors/config/editor/\n   */\n  customization?: ICustomization = new ICustomization();\n\n  /**\n   * 插件列表\n   */\n  plugins?: IPlugins = new IPlugins();\n}\n\nexport class OnlyofficeCallback {\n  /**\n   * 返回 0 表示成功，否则表示失败\n   */\n  @IsNumber()\n  error: number;\n}\n\nexport class OnlyofficeForceSave {\n  /**\n   * 状态码\n   */\n  @IsNumber()\n  code: number;\n\n  /**\n   * 状态码\n   */\n  @IsNumber()\n  error: number;\n\n  /**\n   * 消息\n   */\n  @IsString()\n  message?: string;\n}\n\n/**\n * 编辑器配置项，完整配置项参见：https://api.onlyoffice.com/editors/config/\n */\nexport class OnlyofficeEditorConfig {\n  /**\n   * 编辑器宽度\n   */\n  @IsNumber()\n  width?: number | string = 1200;\n\n  /**\n   * 编辑器高度\n   */\n  @IsNumber()\n  height?: number | string = 800;\n\n  /**\n   * 文档类型。word：文档，cell：表格，slide：PPT\n   */\n  @IsString()\n  @IsIn(['word', 'cell', 'slide'])\n  documentType = 'word';\n\n  /**\n   * 文档配置\n   */\n  @IsObject()\n  document: IDocument = new IDocument();\n\n  /**\n   * 编辑器配置\n   */\n  @IsObject()\n  editorConfig: IEditorConfig = new IEditorConfig();\n\n  /**\n   * 编辑器加密令牌，开启加密时有值\n   */\n  @IsString()\n  token?: string;\n}\n"
  },
  {
    "path": "onlyoffice-server/src/onlyoffice/onlyoffice.interface.ts",
    "content": "enum ErrorCode {\n  NoError = 0,\n  NoSecret = 1,\n  ErrorCallbackUrl = 2,\n  ServerError = 3,\n  NoChange = 4,\n  ErrorCommand = 5,\n  InvalidToken = 6,\n}\nexport interface IOnlyofficeCommand {\n  error: ErrorCode;\n}\n\nexport interface IOnlyofficeForceSave {\n  c?: string;\n  key?: string;\n  userdata?: string;\n  token?: string;\n}\n"
  },
  {
    "path": "onlyoffice-server/src/onlyoffice/onlyoffice.module.ts",
    "content": "import { Module } from '@nestjs/common';\n\nimport { OnlyofficeController } from './onlyoffice.controller';\nimport { OnlyofficeService } from './onlyoffice.service';\n\n@Module({\n  controllers: [OnlyofficeController],\n  providers: [OnlyofficeService],\n  imports: [],\n})\nexport class OnlyofficeModule {}\n"
  },
  {
    "path": "onlyoffice-server/src/onlyoffice/onlyoffice.service.ts",
    "content": "import { HttpService } from '@nestjs/axios';\nimport { AxiosResponse } from 'axios';\nimport { ConfigService } from '@nestjs/config';\nimport { JwtService } from '@nestjs/jwt';\nimport { createWriteStream, WriteStream } from 'fs';\nimport { join } from 'path';\nimport { Injectable, Logger, HttpStatus, HttpException } from '@nestjs/common';\nimport {\n  OnlyofficeCallbackDto,\n  OnlyofficeForceSaveDto,\n} from './onlyoffice.dto';\nimport {\n  OnlyofficeCallback,\n  OnlyofficeForceSave,\n  OnlyofficeEditorConfig,\n} from './onlyoffice.entity';\nimport {\n  IOnlyofficeCommand,\n  IOnlyofficeForceSave,\n} from './onlyoffice.interface';\n\n@Injectable()\nexport class OnlyofficeService {\n  constructor(\n    private readonly request: HttpService,\n    private readonly config: ConfigService,\n    private readonly jwt: JwtService,\n  ) {}\n\n  private readonly logger = new Logger(OnlyofficeService.name);\n\n  async callback(body: OnlyofficeCallbackDto): Promise<OnlyofficeCallback> {\n    const { url, status } = body;\n    // 正在编辑文档但保存了当前文档状态\n    if (status === 6) {\n      try {\n        // 根据地址下载文档文件\n        const file: AxiosResponse = await this.request.axiosRef.get(url, {\n          responseType: 'stream',\n        });\n        const stream: WriteStream = createWriteStream(\n          join(this.config.get('staticPath'), body.key),\n        );\n        file.data.pipe(stream);\n      } catch (error) {\n        this.logger.error(error);\n        // 返回 Onlyoffice 服务认识的报错\n        throw new HttpException({ error: 7 }, HttpStatus.OK);\n      }\n    } else if (status === 7) {\n      // 强制保存文档出错\n      throw new HttpException({ error: status }, HttpStatus.OK);\n    }\n    // 默认返回成功的状态\n    throw new HttpException({ error: 0 }, HttpStatus.OK);\n  }\n\n  // 强制保存文档\n  async forceSave(body: OnlyofficeForceSaveDto): Promise<OnlyofficeForceSave> {\n    const { key, userdata, useJwtEncrypt } = body;\n    let newBody: IOnlyofficeForceSave = {\n      c: 'forcesave',\n      key,\n      userdata,\n    };\n    // 如果是使用了 JWT 加密，重新组装请求参数\n    if (useJwtEncrypt === 'y') {\n      newBody = {\n        token: this.jwt.sign(newBody, {\n          secret: this.config.get('onlyoffice.secret'),\n        }),\n      };\n    }\n    const saveState: IOnlyofficeCommand = await this.request.axiosRef\n      .post(this.config.get('onlyoffice.commandUrl'), newBody)\n      .then((res) => res.data);\n\n    // 组装返回值\n    const data: OnlyofficeForceSave = {\n      error: saveState.error,\n      code: 602,\n    };\n    // 保存成功\n    if (saveState.error === 0) {\n      data.code = 0;\n    } else if (saveState.error === 4) {\n      // 文档未改变无需保存\n      data.message = '文档未改变无需保存';\n    } else {\n      // 文档保存失败\n      data.message = '文档保存失败';\n    }\n    return data;\n  }\n\n  // 编辑器默认配置\n  editorDefaultConfig(): OnlyofficeEditorConfig {\n    const { ...defaultConfig } = new OnlyofficeEditorConfig();\n    return defaultConfig;\n  }\n\n  // 以 JWT 加密方式签名编辑器配置\n  signJwt(editorConfig: OnlyofficeEditorConfig): OnlyofficeEditorConfig {\n    editorConfig.token = this.jwt.sign(editorConfig, {\n      secret: this.config.get('onlyoffice.secret'),\n    });\n    return editorConfig;\n  }\n}\n"
  },
  {
    "path": "onlyoffice-server/src/shared/config.ts",
    "content": "import { join } from 'path';\n\nexport default () => ({\n  isTest: process.env.NODE_ENV === 'test',\n  isOnline: process.env.NODE_ENV === 'online',\n  port: process.env.PORT,\n  apiPrefix: process.env.API_PREFIX,\n  domain: process.env.DOMAIN,\n  staticPath: join(process.cwd(), '/static'),\n  onlyoffice: {\n    secret: process.env.ONLYOFFICE_SECRET,\n    domain: process.env.ONLYOFFICE_DOMAIN,\n    commandUrl: process.env.ONLYOFFICE_COMMAND_URL,\n    callback: process.env.ONLYOFFICE_CALLBACK,\n  },\n});\n"
  },
  {
    "path": "onlyoffice-server/src/shared/interceptors/logger.interceptor.ts",
    "content": "import {\n  CallHandler,\n  ExecutionContext,\n  Injectable,\n  Logger,\n  NestInterceptor,\n} from '@nestjs/common';\nimport { Request, Response } from 'express';\nimport { Observable } from 'rxjs';\nimport { tap } from 'rxjs/operators';\n\n@Injectable()\nexport class LoggingInterceptor implements NestInterceptor {\n  private readonly logger: Logger = new Logger(LoggingInterceptor.name);\n\n  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {\n    const request: Request = context.switchToHttp().getRequest();\n    const { method, url, body, headers } = request;\n\n    // 输出请求类型和请求参数等信息\n    this.logger.log(\n      `Request {${method.toUpperCase()}, ${url}} ${JSON.stringify(\n        body,\n      )} ${JSON.stringify(headers)}`,\n    );\n    return next.handle().pipe(\n      tap({\n        next: (data: unknown): void => this.logResponse(data, context),\n      }),\n    );\n  }\n\n  // 输出响应日志\n  private logResponse(data: unknown, context: ExecutionContext): void {\n    const request: Request = context.switchToHttp().getRequest<Request>();\n    const response: Response = context.switchToHttp().getResponse<Response>();\n\n    this.logger.log(\n      `Response {${response.statusCode}, ${request.method.toUpperCase()}, ${\n        request.url\n      }} ${JSON.stringify(data)}`,\n    );\n  }\n}\n"
  },
  {
    "path": "onlyoffice-server/src/shared/interceptors/response.interceptor.ts",
    "content": "import {\n  Injectable,\n  NestInterceptor,\n  ExecutionContext,\n  CallHandler,\n} from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\n\nexport interface Response<T> {\n  data: T;\n}\n\n@Injectable()\nexport class ResponseInterceptor<T> implements NestInterceptor<T, Response<T>> {\n  intercept(\n    context: ExecutionContext,\n    next: CallHandler,\n  ): Observable<Response<T>> {\n    return next.handle().pipe(map((data) => ({ code: 0, data })));\n  }\n}\n"
  },
  {
    "path": "onlyoffice-server/src/shared/shared.module.ts",
    "content": "import { Module, Global } from '@nestjs/common';\nimport { HttpModule } from '@nestjs/axios';\nimport { JwtModule } from '@nestjs/jwt';\n\n@Global()\n@Module({\n  imports: [HttpModule, JwtModule],\n  exports: [HttpModule, JwtModule],\n  providers: [],\n})\nexport class SharedModule {}\n"
  },
  {
    "path": "onlyoffice-server/static/docbuilder/test1.docbuilder",
    "content": "builder.CreateFile(\"docx\")\nvar oDocument = Api.GetDocument()\n\n// 添加标题\nvar oParagraph = oDocument.GetElement(0)\noParagraph.AddText(\"标题\")\noParagraph.SetJc(\"center\")\noParagraph.SetFontSize(40)\noParagraph.SetFontFamily(\"Arial\")\n\n// 添加正文\noParagraph = Api.CreateParagraph()\noParagraph.AddText(\"\\n\\n正文内容。\\n正文内容。\\n正文内容。\\n\\n\\n此致\\n\")\noParagraph.SetSpacingLine(400, \"auto\")\noParagraph.SetFontSize(28)\noParagraph.SetFontFamily(\"Arial\")\noDocument.Push(oParagraph)\n\n// 添加变量\nvar inlineContent = Api.CreateInlineLvlSdt()\ninlineContent.SetTag(\"var-customName\")\ncontent = Api.CreateRun()\ncontent.AddText(\"自定义变量名称\")\ncontent.SetFontSize(28)\ncontent.SetFontFamily(\"Arial\")\ncontent.SetHighlight(255, 255, 0)\ninlineContent.AddElement(content, 0)\noParagraph.AddInlineLvlSdt(inlineContent)\n\n// 添加落款\noParagraph = Api.CreateParagraph()\noParagraph.AddText(\"落款：\")\noParagraph.SetSpacingLine(400, \"auto\")\noParagraph.SetJc(\"right\")\noParagraph.SetFontSize(28)\noParagraph.SetFontFamily(\"Arial\")\n\n// 添加落款名称\ninlineContent = Api.CreateInlineLvlSdt()\ninlineContent.SetTag(\"var-signName\")\ncontent = Api.CreateRun()\ncontent.AddText(\"落款名称\")\ncontent.SetFontSize(28)\ncontent.SetFontFamily(\"Arial\")\ncontent.SetHighlight(255, 255, 0)\ninlineContent.AddElement(content, 0)\noParagraph.AddInlineLvlSdt(inlineContent)\noDocument.Push(oParagraph)\n\n// 添加日期\noParagraph = Api.CreateParagraph()\noParagraph.SetSpacingLine(400, \"auto\")\noParagraph.SetJc(\"right\")\ninlineContent = Api.CreateInlineLvlSdt()\ninlineContent.SetTag(\"var-updatedTime\")\ncontent = Api.CreateRun()\ncontent.AddText(\"当前日期\")\ncontent.SetFontSize(28)\ncontent.SetFontFamily(\"Arial\")\ncontent.SetHighlight(255, 255, 0)\ninlineContent.AddElement(content, 0)\noParagraph.AddInlineLvlSdt(inlineContent)\noDocument.Push(oParagraph)\nbuilder.SaveFile(\"docx\", \"test1.docx\")\nbuilder.CloseFile()\n"
  },
  {
    "path": "onlyoffice-server/static/plugins/plugin-hello/config.json",
    "content": "{\n  \"name\": \"Hello\",\n  \"guid\": \"asc.{11700c35-1fdb-4e37-9edb-b31637139601}\",\n  \"variations\": [\n    {\n      \"description\": \"Hello Word\",\n      \"url\": \"index.html\",\n      \"icons\": [\n        \"icon.png\",\n        \"icon@2x.png\"\n      ],\n      \"isViewer\": false,\n      \"EditorsSupport\": [\n        \"word\"\n      ],\n      \"isVisual\": true,\n      \"isModal\": false,\n      \"isInsideMode\": true,\n      \"initDataType\": \"none\",\n      \"initData\": \"\",\n      \"isUpdateOleOnResize\": false,\n      \"buttons\": [],\n      \"events\" : [\n        \"onClick\"\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "onlyoffice-server/static/plugins/plugin-hello/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n    <title></title>\n    <script src=\"https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js\"></script>\n    <script src=\"https://onlyoffice.github.io/sdkjs-plugins/v1/plugins.js\"></script>\n    <script src=\"main.js\"></script>\n    <style>\n      html, body {\n        margin: 0;\n        padding: 0;\n      }\n      body {\n        padding: 12px;\n      }\n    </style>\n  </head>\n\n  <body>\n    <button id=\"addText\" class=\"btn-text-default submit primary\">添加</button>\n  </body>\n</html>\n"
  },
  {
    "path": "onlyoffice-server/static/plugins/plugin-hello/main.js",
    "content": "/* eslint-disable */\n\n(function(window, undefined) {\n  window.Asc.plugin.init = function(initData) {\n    console.log('插件开始初始化')\n    console.log(window)\n\n    var me = this\n    $('#addText').click(function() {\n      me.callCommand(function() {\n        try {\n          // 获取文档对象\n          var oDocument = Api.GetDocument()\n\n          console.log('文档对象')\n          console.log(oDocument)\n\n          // 生成一个新的段落对象\n          var oParagraph = Api.CreateParagraph()\n          // 往段落里面添加一个字符串文本\n          oParagraph.AddText('Hello world')\n          // 最后往文档里面添加一个段落对象\n          oDocument.Push(oParagraph)\n        } catch (error) {\n          console.error(error)\n        }\n      }, false, true, function () {\n        console.log('操作成功')\n      })\n    })\n\n    // 在插件 iframe 之外释放鼠标按钮时调用的函数\n    window.Asc.plugin.onExternalMouseUp = function() {\n      var event = document.createEvent('MouseEvents')\n      event.initMouseEvent('mouseup', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null)\n      document.dispatchEvent(event)\n    }\n\n    window.Asc.plugin.button = function(id) {\n      // 被中断或关闭窗口\n      if (id === -1) {\n        this.executeCommand('close', '')\n      }\n\t  }\n  }\n})(window, undefined)\n"
  },
  {
    "path": "onlyoffice-server/static/plugins/plugin-hello/translations/zh-CN.json",
    "content": "{}"
  },
  {
    "path": "onlyoffice-server/static/test5.html",
    "content": "﻿<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /></head><body><h1 style=\"text-align:center;mso-pagination:widow-orphan lines-together;page-break-after:avoid;margin-top:24.000000000000004pt;margin-bottom:10pt;border:none;mso-border-left-alt:none;mso-border-top-alt:none;mso-border-right-alt:none;mso-border-bottom-alt:none;mso-border-between:none\"><span style=\"font-family:'Arial';font-size:20pt;color:#000000;mso-style-textfill-fill-color:#000000\"><b>标题</b></span></h1><p style=\"margin-top:0pt;margin-bottom:10pt;border:none;mso-border-left-alt:none;mso-border-top-alt:none;mso-border-right-alt:none;mso-border-bottom-alt:none;mso-border-between:none\"><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span></p><p style=\"margin-top:0pt;margin-bottom:10pt;border:none;mso-border-left-alt:none;mso-border-top-alt:none;mso-border-right-alt:none;mso-border-bottom-alt:none;mso-border-between:none\"><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span></p><p style=\"margin-top:0pt;margin-bottom:10pt;border:none;mso-border-left-alt:none;mso-border-top-alt:none;mso-border-right-alt:none;mso-border-bottom-alt:none;mso-border-between:none\"><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span></p><p style=\"margin-top:0pt;margin-bottom:10pt;border:none;mso-border-left-alt:none;mso-border-top-alt:none;mso-border-right-alt:none;mso-border-bottom-alt:none;mso-border-between:none\"><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容，</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容，</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容。</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容。</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容。</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容，</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容。</span></p><p style=\"margin-top:0pt;margin-bottom:10pt;border:none;mso-border-left-alt:none;mso-border-top-alt:none;mso-border-right-alt:none;mso-border-bottom-alt:none;mso-border-between:none\"><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span></p><p style=\"margin-top:0pt;margin-bottom:10pt;border:none;mso-border-left-alt:none;mso-border-top-alt:none;mso-border-right-alt:none;mso-border-bottom-alt:none;mso-border-between:none\">&nbsp;</p><p style=\"margin-top:0pt;margin-bottom:10pt;border:none;mso-border-left-alt:none;mso-border-top-alt:none;mso-border-right-alt:none;mso-border-bottom-alt:none;mso-border-between:none\"><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span></p><p style=\"margin-top:0pt;margin-bottom:10pt;border:none;mso-border-left-alt:none;mso-border-top-alt:none;mso-border-right-alt:none;mso-border-bottom-alt:none;mso-border-between:none\"><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span></p><p style=\"margin-top:0pt;margin-bottom:10pt;border:none;mso-border-left-alt:none;mso-border-top-alt:none;mso-border-right-alt:none;mso-border-bottom-alt:none;mso-border-between:none\"><span style=\"font-family:'Arial';font-size:11pt;color:#000000;mso-style-textfill-fill-color:#000000\">内容</span></p><p style=\"margin-top:0pt;margin-bottom:10pt;border:none;border-left:none;border-top:none;border-right:none;border-bottom:none;mso-border-between:none\">&nbsp;</p></body></html>"
  },
  {
    "path": "onlyoffice-server/tsconfig.build.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"exclude\": [\"node_modules\", \"test\", \"dist\", \"**/*spec.ts\"]\n}\n"
  },
  {
    "path": "onlyoffice-server/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"declaration\": true,\n    \"removeComments\": true,\n    \"emitDecoratorMetadata\": true,\n    \"experimentalDecorators\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"target\": \"es2017\",\n    \"sourceMap\": true,\n    \"outDir\": \"./dist\",\n    \"baseUrl\": \"./\",\n    \"incremental\": true,\n    \"skipLibCheck\": true,\n    \"strictNullChecks\": false,\n    \"noImplicitAny\": false,\n    \"strictBindCallApply\": false,\n    \"forceConsistentCasingInFileNames\": false,\n    \"noFallthroughCasesInSwitch\": false\n  }\n}\n"
  },
  {
    "path": "onlyoffice-vue/.editorconfig",
    "content": "[*.{js,jsx,ts,tsx,vue}]\r\nindent_style = space\r\nindent_size = 2\r\ntrim_trailing_whitespace = true\r\ninsert_final_newline = true\r\n"
  },
  {
    "path": "onlyoffice-vue/.eslintignore",
    "content": "lib/\ndist/\n"
  },
  {
    "path": "onlyoffice-vue/.eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  env: {\n    node: true\n  },\n  extends: [\n    'plugin:vue/essential',\n    '@vue/standard'\n  ],\n  parserOptions: {\n    parser: 'babel-eslint'\n  },\n  rules: {\n    'no-unused-vars': 'warn',\n    'vue/no-unused-components': 'warn',\n    'no-trailing-spaces': 'warn',\n    'import/newline-after-import': 'error',\n    'vue/mustache-interpolation-spacing': 'warn',\n    'vue/no-multi-spaces': 'warn'\n  }\n}\n"
  },
  {
    "path": "onlyoffice-vue/.gitignore",
    "content": ".DS_Store\r\nnode_modules\r\n/dist\r\npackage-lock.json\r\nyarn.lock\r\n\r\n# local env files\r\n.env.local\r\n.env.*.local\r\n\r\n# Log files\r\nnpm-debug.log*\r\nyarn-debug.log*\r\nyarn-error.log*\r\n\r\n# Editor directories and files\r\n.idea\r\n.vscode\r\n*.suo\r\n*.ntvs*\r\n*.njsproj\r\n*.sln\r\n*.sw?\r\n"
  },
  {
    "path": "onlyoffice-vue/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 wytxer\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "onlyoffice-vue/README.md",
    "content": "# Onlyoffice Vue\r\n\r\n基于 Vue.js 的 Onlyoffice 示例。\r\n\r\n\r\n### 快速使用\r\n\r\n```bash\r\n# 安装依赖\r\npnpm install\r\n\r\n# 开发\r\npnpm run dev\r\n\r\n# 打包\r\npnpm run build\r\n```\r\n\r\n\r\n## License\r\n\r\n[MIT](/LICENSE)\r\n"
  },
  {
    "path": "onlyoffice-vue/babel.config.js",
    "content": "module.exports = {\r\n  presets: [\r\n    '@vue/cli-plugin-babel/preset'\r\n  ],\r\n  plugins: [\r\n    [\r\n      'import', {\r\n        libraryName: 'ant-design-vue',\r\n        libraryDirectory: 'es',\r\n        style: true\r\n      }\r\n    ]\r\n  ]\r\n}\r\n"
  },
  {
    "path": "onlyoffice-vue/package.json",
    "content": "{\n  \"name\": \"onlyoffice-vue\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"vue-cli-service serve\",\n    \"build\": \"vue-cli-service build\",\n    \"lint\": \"vue-cli-service lint\"\n  },\n  \"dependencies\": {\n    \"@wytxer/style-utils\": \"^1.0.2\",\n    \"ant-design-vue\": \"^1.7.8\",\n    \"axios\": \"^0.27.2\",\n    \"core-js\": \"^3.6.5\",\n    \"lodash.merge\": \"^4.6.2\",\n    \"onlyoffice-vue\": \"^1.0.1\",\n    \"vue\": \"^2.6.14\",\n    \"vue-router\": \"^3.5.3\",\n    \"vuex\": \"^3.6.2\"\n  },\n  \"devDependencies\": {\n    \"@vue/cli-plugin-babel\": \"~4.5.0\",\n    \"@vue/cli-plugin-eslint\": \"~4.5.0\",\n    \"@vue/cli-plugin-router\": \"~4.5.0\",\n    \"@vue/cli-plugin-vuex\": \"~4.5.0\",\n    \"@vue/cli-service\": \"~4.5.0\",\n    \"@vue/eslint-config-standard\": \"^5.1.2\",\n    \"babel-eslint\": \"^10.1.0\",\n    \"babel-plugin-import\": \"^1.13.5\",\n    \"eslint\": \"^6.7.2\",\n    \"eslint-plugin-import\": \"^2.20.2\",\n    \"eslint-plugin-node\": \"^11.1.0\",\n    \"eslint-plugin-promise\": \"^4.2.1\",\n    \"eslint-plugin-standard\": \"^4.0.0\",\n    \"eslint-plugin-vue\": \"^6.2.2\",\n    \"less\": \"^3.13.1\",\n    \"less-loader\": \"^4.1.0\",\n    \"lint-staged\": \"^9.5.0\",\n    \"vue-template-compiler\": \"^2.6.14\"\n  },\n  \"engines\": {\n    \"node\": \">=10\"\n  },\n  \"license\": \"MIT\",\n  \"homepage\": \"https://github.com/wytxer/demo-onlyoffice/#readme\",\n  \"keywords\": [\n    \"onlyoffice\",\n    \"demo\",\n    \"office\",\n    \"vue\",\n    \"nestjs\",\n    \"docx\"\n  ],\n  \"author\": {\n    \"name\": \"wytxer\",\n    \"url\": \"https://github.com/wytxer\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git@github.com:wytxer/demo-onlyoffice.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/wytxer/demo-onlyoffice/issues\"\n  },\n  \"browserslist\": [\n    \"> 1%\",\n    \"last 2 versions\",\n    \"not dead\"\n  ],\n  \"gitHooks\": {\n    \"pre-commit\": \"lint-staged\"\n  },\n  \"lint-staged\": {\n    \"*.{js,jsx,vue}\": [\n      \"vue-cli-service lint\",\n      \"git add\"\n    ]\n  }\n}\n"
  },
  {
    "path": "onlyoffice-vue/public/index.html",
    "content": "<!DOCTYPE html>\r\n<html lang=\"en\">\r\n  <head>\r\n    <meta charset=\"utf-8\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\r\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\r\n    <link rel=\"icon\" href=\"<%= BASE_URL %>favicon.svg\">\r\n    <title><%= htmlWebpackPlugin.options.title %></title>\r\n  </head>\r\n  <body>\r\n    <noscript><strong>请启用 JavaScript，否则将无法显示页面。</strong></noscript>\r\n    <div id=\"app\"></div>\r\n  </body>\r\n</html>\r\n"
  },
  {
    "path": "onlyoffice-vue/src/api/onlyoffice.js",
    "content": "import request from './request'\n\n// Onlyoffice 文档转换接口地址\nexport function onlyofficeConversion (data) {\n  return request({\n    method: 'post',\n    url: process.env.VUE_APP_ONLYOFFICE_CONVERT,\n    data\n  })\n}\n\n// Onlyoffice 文档构建接口地址\nexport function onlyofficeBuilder (data) {\n  return request({\n    method: 'post',\n    url: process.env.VUE_APP_ONLYOFFICE_DOCBUILDER,\n    data\n  })\n}\n\n// 保存文档信息\nexport function forceSaveDocumentInfo (data) {\n  return request({\n    method: 'post',\n    url: '/api/v1/document/forceSave',\n    data\n  })\n}\n\n// 获取文档信息\nexport function queryDocumentInfo (params) {\n  return request({\n    method: 'get',\n    url: '/api/v1/document/documentInfo',\n    params\n  })\n}\n\n// 获取表格信息\nexport function queryExcelInfo (params) {\n  return request({\n    method: 'get',\n    url: '/api/v1/document/excelInfo',\n    params\n  })\n}\n"
  },
  {
    "path": "onlyoffice-vue/src/api/request.js",
    "content": "import axios from 'axios'\n\nconst request = axios.create({\n  timeout: 60000,\n  baseURL: ''\n})\n\n// 请求拦截器\nrequest.interceptors.request.use(config => config)\n\n// 响应拦截器\nrequest.interceptors.response.use(res => res.data)\n\nexport default request\n"
  },
  {
    "path": "onlyoffice-vue/src/app.vue",
    "content": "<template>\r\n  <a-config-provider :locale=\"locale\">\r\n    <div class=\"page-app\">\r\n      <header class=\"page-app__header\">\r\n      <router-link class=\"page-app__title\" to=\"/\">Onlyoffice</router-link>\r\n        <span>{{ $route.meta.title }}</span>\r\n      </header>\r\n      <div class=\"page-app__body\">\r\n        <router-view />\r\n      </div>\r\n    </div>\r\n  </a-config-provider>\r\n</template>\r\n\r\n<script>\r\nimport locale from 'ant-design-vue/lib/locale-provider/zh_CN'\r\n\r\nexport default {\r\n  data () {\r\n    return {\r\n      locale\r\n    }\r\n  }\r\n}\r\n</script>\r\n\r\n<style lang=\"less\">\r\nbody {\r\n  padding: 0;\r\n  margin: 0;\r\n}\r\nhtml,\r\nbody {\r\n  font-size: 14px;\r\n  color: #333;\r\n  font-family: \"Helvetica Neue\", Helvetica, \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", \"微软雅黑\", Arial, sans-serif;\r\n}\r\n.page-app {\r\n  &__header {\r\n    padding: 16px 32px;\r\n    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\r\n  }\r\n  &__title {\r\n    font-size: 24px;\r\n    margin-right: 16px;\r\n    color: #333;\r\n  }\r\n  &__body {\r\n    padding: 20px 32px;\r\n  }\r\n}\r\n</style>\r\n"
  },
  {
    "path": "onlyoffice-vue/src/layouts/block.vue",
    "content": "<template>\r\n  <router-view />\r\n</template>\r\n\r\n<script>\r\nexport default {\r\n  name: 'layout-block'\r\n}\r\n</script>\r\n"
  },
  {
    "path": "onlyoffice-vue/src/main.js",
    "content": "import Vue from 'vue'\r\nimport { ConfigProvider, Button, Skeleton, message } from 'ant-design-vue'\r\nimport App from '@/app.vue'\r\nimport router from '@/router'\r\n\r\nimport '@wytxer/style-utils/lib/common.less'\r\nimport 'ant-design-vue/dist/antd.less'\r\n\r\nVue.use(ConfigProvider)\r\nVue.use(Button)\r\nVue.use(Skeleton)\r\n\r\nVue.prototype.$message = message\r\n\r\nVue.config.productionTip = false\r\n\r\nnew Vue({\r\n  router,\r\n  render: h => h(App)\r\n}).$mount('#app')\r\n"
  },
  {
    "path": "onlyoffice-vue/src/router/index.js",
    "content": "import Vue from 'vue'\r\nimport VueRouter from 'vue-router'\r\nimport routes from './routes'\r\n\r\nVue.use(VueRouter)\r\n\r\nconst router = new VueRouter({\r\n  base: process.env.BASE_URL,\r\n  mode: 'history',\r\n  routes\r\n})\r\n\r\nexport default router\r\n"
  },
  {
    "path": "onlyoffice-vue/src/router/routes.js",
    "content": "import LayoutBlock from '@/layouts/block.vue'\n\nconst routes = [{\n  path: '/',\n  redirect: '/home'\n}, {\n  path: '/home',\n  component: () => import(/* webpackChunkName: \"home\" */ '@/views/home.vue'),\n  meta: {\n    title: '首页'\n  }\n}, {\n  path: '/onlyoffice',\n  component: LayoutBlock,\n  children: [{\n    path: 'document',\n    component: LayoutBlock,\n    meta: {\n      title: '文档编辑器'\n    },\n    children: [{\n      path: 'quick-start',\n      component: () => import(/* webpackChunkName: \"document-quick-start\" */ '@/views/onlyoffice/document-quick-start.vue'),\n      meta: {\n        title: '快速接入'\n      }\n    }, {\n      path: 'custom-config',\n      component: () => import(/* webpackChunkName: \"document-custom-config\" */ '@/views/onlyoffice/document-custom-config.vue'),\n      meta: {\n        title: '自定义 onlyoffice 配置'\n      }\n    }, {\n      path: 'editor',\n      component: () => import(/* webpackChunkName: \"document-editor\" */ '@/views/onlyoffice/document-editor.vue'),\n      meta: {\n        title: '接口获取配置项'\n      }\n    }, {\n      path: 'editor-jwt',\n      component: () => import(/* webpackChunkName: \"document-editor-jwt\" */ '@/views/onlyoffice/document-editor-jwt.vue'),\n      meta: {\n        title: '开启 JWT 加密'\n      }\n    }, {\n      path: 'plugin',\n      component: () => import(/* webpackChunkName: \"document-plugin\" */ '@/views/onlyoffice/document-plugin.vue'),\n      meta: {\n        title: '插件'\n      }\n    }, {\n      path: 'conversion',\n      component: () => import(/* webpackChunkName: \"document-conversion\" */ '@/views/onlyoffice/document-conversion.vue'),\n      meta: {\n        title: '文档转换'\n      }\n    }, {\n      path: 'onlyoffice-vue',\n      component: () => import(/* webpackChunkName: \"onlyoffice-vue\" */ '@/views/onlyoffice/onlyoffice-vue.vue'),\n      meta: {\n        title: '使用 onlyoffice-vue 组件'\n      }\n    }]\n  }, {\n    path: 'excel',\n    component: LayoutBlock,\n    meta: {\n      title: '表格编辑器'\n    },\n    children: [{\n      path: 'quick-start',\n      component: () => import(/* webpackChunkName: \"excel-quick-start\" */ '@/views/onlyoffice/excel-quick-start.vue'),\n      meta: {\n        title: '快速接入'\n      }\n    }, {\n      path: 'formula',\n      component: () => import(/* webpackChunkName: \"excel-formula\" */ '@/views/onlyoffice/excel-formula.vue'),\n      meta: {\n        title: '自动公式'\n      }\n    }]\n  }]\n}]\n\nexport default routes\n"
  },
  {
    "path": "onlyoffice-vue/src/store/index.js",
    "content": "import Vue from 'vue'\r\nimport Vuex from 'vuex'\r\n\r\nVue.use(Vuex)\r\n\r\nexport default new Vuex.Store({\r\n  state: {},\r\n  mutations: {},\r\n  actions: {},\r\n  modules: {}\r\n})\r\n"
  },
  {
    "path": "onlyoffice-vue/src/views/home.vue",
    "content": "<template>\r\n  <div class=\"page-home\">\r\n    <div v-for=\"item in links\" :key=\"item.path\">\r\n      <router-link :to=\"item.path\">{{ item.title }}</router-link>\r\n    </div>\r\n  </div>\r\n</template>\r\n\r\n<script>\r\nexport default {\r\n  data () {\r\n    const routes = this.$router.getRoutes() || []\r\n    const links = routes\r\n      .filter(item => item.meta.title && item.path && item.parent && item.parent.meta.title)\r\n      .map(item => {\r\n        return {\r\n          title: `${item.parent.meta.title} - ${item.meta.title}`, path: item.path\r\n        }\r\n      })\r\n    return {\r\n      links\r\n    }\r\n  }\r\n}\r\n</script>\r\n"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/document-conversion.vue",
    "content": "<template>\n  <div class=\"PT24 PB24\">\n    <a-button type=\"primary\" @click=\"onHtmlToDocx\" :loading=\"loading.toDocx\">html 转 docx</a-button>\n    <a-button type=\"primary\" class=\"ML16\" @click=\"onBuilder\" :loading=\"loading.builder\">doc builder 转 docx</a-button>\n  </div>\n</template>\n\n<script>\nimport { onlyofficeConversion, onlyofficeBuilder } from '@/api/onlyoffice'\n\nexport default {\n  data () {\n    return {\n      loading: {\n        toDocx: false,\n        builder: false\n      }\n    }\n  },\n  methods: {\n    // html 转 docx\n    onHtmlToDocx () {\n      this.loading.toDocx = true\n      onlyofficeConversion({\n        // 请求参数详解：https://api.onlyoffice.com/editors/conversionapi\n        async: false,\n        filetype: 'html',\n        outputtype: 'docx',\n        url: `${process.env.VUE_APP_API_SERVER}/static/test5.html`,\n        key: 'test5.html'\n      })\n        .then(res => {\n          if (res.endConvert) this.$message.success('转换成功')\n          // 下载转换后的文件\n          if (res.fileUrl) {\n            window.open(res.fileUrl.replace(process.env.VUE_APP_PORT, process.env.VUE_APP_ONLYOFFICE_PORT))\n          }\n        })\n        .finally(() => {\n          this.loading.toDocx = false\n        })\n    },\n    // 调用构建服务转换文档\n    onBuilder () {\n      this.loading.builder = true\n      onlyofficeBuilder({\n        // 请求参数详解：https://api.onlyoffice.com/editors/documentbuilderapi\n        async: false,\n        url: `${process.env.VUE_APP_API_SERVER}/static/docbuilder/test1.docbuilder`\n      })\n        .then(res => {\n          if (res.endConvert) this.$message.success('转换成功')\n          if (res.urls && res.urls['test1.docx']) {\n            window.open(res.urls['test1.docx'].replace(process.env.VUE_APP_PORT, process.env.VUE_APP_ONLYOFFICE_PORT))\n          }\n        })\n        .finally(() => {\n          this.loading.builder = false\n        })\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/document-custom-config.vue",
    "content": "<template>\n  <div><div :id=\"id\"></div></div>\n</template>\n\n<script>\n// 编辑器配置项，完整配置项参见：https://api.onlyoffice.com/editors/config/\nconst editorConfig = {\n  // 编辑器宽度\n  width: 1200,\n  // 编辑器高度\n  height: 800,\n  // 编辑器类型，支持 word、cell（表格）、slide（PPT）\n  documentType: 'word',\n  // 文档配置\n  document: {\n    // 当前文档类型\n    fileType: 'docx',\n    // 文档标识符\n    key: 'test2.docx',\n    // 文档地址，绝对路径\n    url: `${process.env.VUE_APP_API_SERVER}/static/test2.docx`,\n    // 文档标题\n    title: '测试文档二.docx',\n    // 权限\n    permissions: {\n      // 启用评论\n      comment: false,\n      // 启用下载\n      download: true,\n      // 启用编辑\n      edit: true,\n      // 启用导出\n      print: true,\n      // 启用预览\n      review: true\n    }\n  },\n  editorConfig: {\n    // 回调地址\n    callbackUrl: process.env.VUE_APP_ONLYOFFICE_CALLBACK,\n    // 设置语言\n    lang: 'zh-CN',\n    // 添加用户信息\n    user: {\n      group: '技术部', id: 'wytxer', name: '程序员未央'\n    },\n    // 模板列表\n    templates: [],\n    // customization 字段相关配置详解：https://api.onlyoffice.com/editors/config/editor/customization\n    customization: {\n      // 强制保存\n      forcesave: true,\n      features: {\n        // 关闭拼写检查\n        spellcheck: false\n      }\n      // ! 社区版不支持自定义 logo 字段\n      // logo: {\n      //   image: 'http://example.com/logo.png',\n      //   imageEmbedded: 'http://example.com/logo.png',\n      //   url: 'http://www.example.com'\n      // }\n    }\n  }\n}\n\nexport default {\n  data () {\n    return {\n      id: `editor-${new Date().getTime().toString('32')}`\n    }\n  },\n  mounted () {\n    this.initEditor()\n  },\n  beforeDestroy () {\n    // 组件销毁前销毁编辑器\n    if (this.editor) {\n      this.editor.destroyEditor()\n      this.editor = null\n    }\n  },\n  methods: {\n    // 初始化编辑器\n    initEditor () {\n      const scriptId = `script-${this.id}`\n      const added = document.querySelector(`#${scriptId}`)\n      if (!added) {\n        const script = document.createElement('script')\n        script.id = scriptId\n        script.src = process.env.VUE_APP_ONLYOFFICE_API_URL\n        script.onload = this.createEditor\n        document.head.appendChild(script)\n      } else {\n        this.createEditor()\n      }\n    },\n    // 创建编辑器\n    createEditor () {\n      if (this.editor) {\n        this.editor.destroyEditor()\n        this.editor = null\n      }\n      this.editor = new window.DocsAPI.DocEditor(this.id, editorConfig)\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/document-editor-jwt.vue",
    "content": "<template>\n  <onlyoffice-editor :loading=\"loading.editor\" :config=\"editorConfig\">\n    <span slot=\"actions\">\n      <a-button type=\"primary\" @click=\"onSave\" :loading=\"loading.save\">保存</a-button>\n    </span>\n  </onlyoffice-editor>\n</template>\n\n<script>\nimport OnlyofficeEditor from './modules/onlyoffice-editor'\nimport { queryDocumentInfo, forceSaveDocumentInfo } from '@/api/onlyoffice'\n\nexport default {\n  data () {\n    return {\n      loading: {\n        editor: false,\n        save: false,\n        forceSave: false\n      },\n      detail: {},\n      editorConfig: {}\n    }\n  },\n  components: {\n    OnlyofficeEditor\n  },\n  created () {\n    this.queryDocumentInfo()\n  },\n  methods: {\n    // 获取文档配置信息\n    queryDocumentInfo () {\n      this.loading.editor = true\n      queryDocumentInfo({ key: 'test4.docx', useJwtEncrypt: 'y' })\n        .then(res => {\n          const data = res.data || {}\n          const { id, remarks } = data\n          this.detail = { id, remarks }\n          this.editorConfig = data.editorConfig\n        })\n        .finally(() => {\n          this.loading.editor = false\n        })\n    },\n    // 保存\n    onSave () {\n      this.loading.forceSave = true\n      const { key } = this.editorConfig.document\n      const { id } = this.detail\n      // 如果开启了 JWT 加密，useJwtEncrypt 字段要传递 y\n      forceSaveDocumentInfo({ id, key, useJwtEncrypt: 'n' })\n        .then(res => {\n          if (res.code === 0) {\n            this.$message.success('保存成功')\n          } else {\n            this.$message.error(res.message)\n          }\n        })\n        .finally(() => {\n          this.loading.forceSave = false\n        })\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/document-editor.vue",
    "content": "<template>\n  <onlyoffice-editor :loading=\"loading.editor\" :config=\"editorConfig\">\n    <span slot=\"actions\">\n      <a-button type=\"primary\" @click=\"onSave\" :loading=\"loading.save\">保存</a-button>\n    </span>\n  </onlyoffice-editor>\n</template>\n\n<script>\nimport OnlyofficeEditor from './modules/onlyoffice-editor'\nimport { queryDocumentInfo, forceSaveDocumentInfo } from '@/api/onlyoffice'\n\nexport default {\n  data () {\n    return {\n      loading: {\n        editor: false,\n        save: false\n      },\n      detail: {},\n      editorConfig: {}\n    }\n  },\n  components: {\n    OnlyofficeEditor\n  },\n  created () {\n    this.queryDocumentInfo()\n  },\n  methods: {\n    // 获取文档配置信息\n    queryDocumentInfo () {\n      this.loading.editor = true\n      queryDocumentInfo({ key: 'test3.docx' })\n        .then(res => {\n          const data = res.data || {}\n          const { id, remarks } = data\n          this.detail = { id, remarks }\n          this.editorConfig = data.editorConfig\n        })\n        .finally(() => {\n          this.loading.editor = false\n        })\n    },\n    // 保存\n    onSave () {\n      this.loading.save = true\n      const { key } = this.editorConfig.document\n      const { id } = this.detail\n      forceSaveDocumentInfo({ key, id })\n        .then(res => {\n          if (res.code === 0) {\n            this.$message.success('保存成功')\n          } else {\n            this.$message.error(res.message)\n          }\n        })\n        .finally(() => {\n          this.loading.save = false\n        })\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/document-plugin.vue",
    "content": "<template>\n  <onlyoffice-editor :loading=\"loading.editor\" :config=\"editorConfig\">\n    <span slot=\"actions\">\n      <a-button type=\"primary\" @click=\"onSave\" :loading=\"loading.save\">保存</a-button>\n    </span>\n  </onlyoffice-editor>\n</template>\n\n<script>\nimport OnlyofficeEditor from './modules/onlyoffice-editor'\nimport { queryDocumentInfo, forceSaveDocumentInfo } from '@/api/onlyoffice'\n\nexport default {\n  data () {\n    return {\n      loading: {\n        editor: false,\n        save: false\n      },\n      detail: {},\n      editorConfig: {}\n    }\n  },\n  components: {\n    OnlyofficeEditor\n  },\n  created () {\n    this.queryDocumentInfo()\n  },\n  methods: {\n    // 获取文档配置信息\n    queryDocumentInfo () {\n      this.loading.editor = true\n      queryDocumentInfo({ key: 'test5.docx', usePlugin: 'y' })\n        .then(res => {\n          const data = res.data || {}\n          const { id, remarks } = data\n          this.detail = { id, remarks }\n          this.editorConfig = data.editorConfig\n        })\n        .finally(() => {\n          this.loading.editor = false\n        })\n    },\n    // 保存\n    onSave () {\n      this.loading.save = true\n      const { key } = this.editorConfig.document\n      const { id } = this.detail\n      forceSaveDocumentInfo({ key, id })\n        .then(res => {\n          if (res.code === 0) {\n            this.$message.success('保存成功')\n          } else {\n            this.$message.error(res.message)\n          }\n        })\n        .finally(() => {\n          this.loading.save = false\n        })\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/document-quick-start.vue",
    "content": "<template>\n  <div><div :id=\"id\"></div></div>\n</template>\n\n<script>\n// 编辑器配置项，完整配置项参见：https://api.onlyoffice.com/editors/config/\nconst editorConfig = {\n  // 编辑器宽度\n  width: 1200,\n  // 编辑器高度\n  height: 800,\n  // 编辑器类型，支持 word（文档）、cell（表格）、slide（PPT）\n  documentType: 'word',\n  // 文档配置\n  document: {\n    // 文件类型\n    fileType: 'docx',\n    // 文档标识符\n    key: 'test1.docx',\n    // 文档地址，绝对路径\n    url: `${process.env.VUE_APP_API_SERVER}/static/test1.docx`,\n    // 文档标题\n    title: '测试文档一.docx'\n  }\n}\n\nexport default {\n  data () {\n    return {\n      id: `editor-${new Date().getTime().toString('32')}`\n    }\n  },\n  mounted () {\n    this.initEditor()\n  },\n  beforeDestroy () {\n    // 组件销毁前销毁编辑器\n    if (this.editor) {\n      this.editor.destroyEditor()\n      this.editor = null\n    }\n  },\n  methods: {\n    // 初始化编辑器\n    initEditor () {\n      const scriptId = `script-${this.id}`\n      const added = document.querySelector(`#${scriptId}`)\n      if (!added) {\n        const script = document.createElement('script')\n        script.id = scriptId\n        script.src = process.env.VUE_APP_ONLYOFFICE_API_URL\n        script.onload = this.createEditor\n        document.head.appendChild(script)\n      } else {\n        this.createEditor()\n      }\n    },\n    // 创建编辑器\n    createEditor () {\n      if (this.editor) {\n        this.editor.destroyEditor()\n        this.editor = null\n      }\n      this.editor = new window.DocsAPI.DocEditor(this.id, editorConfig)\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/excel-formula.vue",
    "content": "<template>\n  <onlyoffice-editor :loading=\"loading.editor\" :config=\"editorConfig\">\n    <span slot=\"actions\">\n      <a-button type=\"primary\" @click=\"onSave\" :loading=\"loading.save\">保存</a-button>\n    </span>\n  </onlyoffice-editor>\n</template>\n\n<script>\nimport OnlyofficeEditor from './modules/onlyoffice-editor'\nimport { queryExcelInfo, forceSaveDocumentInfo } from '@/api/onlyoffice'\n\nexport default {\n  data () {\n    return {\n      loading: {\n        editor: false,\n        save: false\n      },\n      detail: {},\n      editorConfig: {}\n    }\n  },\n  components: {\n    OnlyofficeEditor\n  },\n  created () {\n    this.queryExcelInfo()\n  },\n  methods: {\n    // 获取表格配置信息\n    queryExcelInfo () {\n      this.loading.editor = true\n      queryExcelInfo({ key: 'test2.xlsx' })\n        .then(res => {\n          const data = res.data || {}\n          const { id, remarks } = data\n          this.detail = { id, remarks }\n          this.editorConfig = data.editorConfig\n        })\n        .finally(() => {\n          this.loading.editor = false\n        })\n    },\n    // 保存\n    onSave () {\n      this.loading.save = true\n      const { key } = this.editorConfig.document\n      const { id } = this.detail\n      forceSaveDocumentInfo({ key, id })\n        .then(res => {\n          if (res.code === 0) {\n            this.$message.success('保存成功')\n          } else {\n            this.$message.error(res.message)\n          }\n        })\n        .finally(() => {\n          this.loading.save = false\n        })\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/excel-quick-start.vue",
    "content": "<template>\n  <div><div :id=\"id\"></div></div>\n</template>\n\n<script>\n// 编辑器配置项，完整配置项参见：https://api.onlyoffice.com/editors/config/\nconst editorConfig = {\n  // 编辑器宽度\n  width: '100%',\n  // 编辑器高度\n  height: 800,\n  // 编辑器类型，支持 word（文档）、cell（表格）、slide（PPT）\n  documentType: 'cell',\n  // 文档配置\n  document: {\n    // 文件类型\n    fileType: 'xlsx',\n    // 文档标识符\n    key: 'test1.xlsx',\n    // 文档地址，绝对路径\n    url: `${process.env.VUE_APP_API_SERVER}/static/test1.xlsx`,\n    // 文档标题\n    title: '测试表格一.docx'\n  },\n  editorConfig: {\n    // 设置语言\n    lang: 'zh-CN'\n  }\n}\n\nexport default {\n  data () {\n    return {\n      id: `editor-${new Date().getTime().toString('32')}`\n    }\n  },\n  mounted () {\n    this.initEditor()\n  },\n  beforeDestroy () {\n    // 组件销毁前销毁编辑器\n    if (this.editor) {\n      this.editor.destroyEditor()\n      this.editor = null\n    }\n  },\n  methods: {\n    // 初始化编辑器\n    initEditor () {\n      const scriptId = `script-${this.id}`\n      const added = document.querySelector(`#${scriptId}`)\n      if (!added) {\n        const script = document.createElement('script')\n        script.id = scriptId\n        script.src = process.env.VUE_APP_ONLYOFFICE_API_URL\n        script.onload = this.createEditor\n        document.head.appendChild(script)\n      } else {\n        this.createEditor()\n      }\n    },\n    // 创建编辑器\n    createEditor () {\n      if (this.editor) {\n        this.editor.destroyEditor()\n        this.editor = null\n      }\n      this.editor = new window.DocsAPI.DocEditor(this.id, editorConfig)\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/modules/onlyoffice-editor.vue",
    "content": "<template>\n  <a-skeleton v-bind=\"skeletonAttrs\" :loading=\"loading\">\n    <div :key=\"id\" :id=\"id\"></div>\n    <div v-if=\"$slots.actions\" class=\"MT24 PB24\"><slot name=\"actions\"></slot></div>\n  </a-skeleton>\n</template>\n\n<script>\nimport merge from 'lodash.merge'\n\nlet script\n// 脚本标识\nconst scriptId = 'onlyoffice-editor'\n// 异步加载 api.js\nconst loadScript = () => new Promise((resolve, reject) => {\n  const src = process.env.VUE_APP_ONLYOFFICE_API_URL\n  script = document.querySelector(`#${scriptId}`)\n  // 加载成功\n  const onLoad = () => {\n    resolve()\n    script.removeEventListener('load', onLoad)\n  }\n  // 加载失败\n  const onError = () => {\n    reject(new Error(`脚本 ${src} 加载失败`))\n    script.removeEventListener('error', onError)\n  }\n  if (!script) {\n    script = document.createElement('script')\n    script.id = scriptId\n    script.src = src\n    script.addEventListener('load', onLoad)\n    script.addEventListener('error', onError)\n    document.head.appendChild(script)\n  } else if (window.DocsAPI) {\n    resolve()\n  } else {\n    script.addEventListener('load', onLoad)\n    script.addEventListener('error', onError)\n  }\n})\n\nexport default {\n  props: {\n    config: {\n      type: Object,\n      default: null\n    },\n    loading: {\n      type: Boolean,\n      default: true\n    }\n  },\n  data () {\n    return {\n      // 编辑器配置项，完整配置项参见：https://api.onlyoffice.com/editors/config/\n      editorConfig: {\n        // 编辑器宽度\n        width: 1200,\n        // 编辑器高度\n        height: 600,\n        // 编辑器类型，支持 word、cell（表格）、slide（PPT）\n        documentType: 'word',\n        // 文档配置\n        document: {\n          // 权限\n          permissions: {\n            // 启用评论\n            comment: false,\n            // 启用下载\n            download: true,\n            // 启用编辑\n            edit: true,\n            // 启用导出\n            print: true,\n            // 启用预览\n            review: true\n          }\n        },\n        editorConfig: {\n          // 回调地址\n          callbackUrl: process.env.VUE_APP_ONLYOFFICE_CALLBACK,\n          // 设置语言\n          lang: 'zh-CN',\n          // customization 字段相关配置详解：https://api.onlyoffice.com/editors/config/editor/customization\n          customization: {\n            // 强制保存\n            forcesave: true,\n            features: {\n              // 关闭拼写检查\n              spellcheck: false\n            }\n          }\n        }\n      },\n      id: `editor-${new Date().getTime().toString('32')}`\n    }\n  },\n  computed: {\n    skeletonAttrs () {\n      return {\n        active: true,\n        style: { width: `${this.editorConfig.width}px` },\n        paragraph: { rows: 10 }\n      }\n    }\n  },\n  watch: {\n    loading (newLoading) {\n      if (newLoading === false) this.initEditor()\n    },\n    config: {\n      handler () {\n        this.initEditor()\n      },\n      deep: true\n    }\n  },\n  mounted () {\n    this.initEditor()\n  },\n  beforeDestroy () {\n    // 组件销毁前销毁编辑器\n    if (this.editor) {\n      this.editor.destroyEditor()\n      this.editor = null\n    }\n  },\n  methods: {\n    // 初始化编辑器\n    initEditor () {\n      loadScript(this.src).then(this.createEditor)\n    },\n    // 创建编辑器\n    createEditor () {\n      if (this.editor) {\n        this.editor.destroyEditor()\n        this.editor = null\n      }\n      if (window.DocsAPI) this.editor = new window.DocsAPI.DocEditor(this.id, merge({}, this.editorConfig, this.config))\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/onlyoffice-vue.vue",
    "content": "<template>\n  <a-skeleton v-bind=\"skeletonAttrs\" :loading=\"loading.page\">\n    <onlyoffice-editor :src=\"src\" :config=\"editorConfig\" @ready=\"onEditorReady\" />\n    <div class=\"PT24 PB24\">\n      <a-button type=\"primary\" @click=\"onSave\" :loading=\"loading.save\">保存</a-button>\n    </div>\n  </a-skeleton>\n</template>\n\n<script>\nimport { OnlyofficeEditor } from 'onlyoffice-vue'\nimport { queryDocumentInfo, forceSaveDocumentInfo } from '@/api/onlyoffice'\n\nexport default {\n  data () {\n    return {\n      loading: {\n        page: false,\n        save: false\n      },\n      src: process.env.VUE_APP_ONLYOFFICE_API_URL,\n      detail: {},\n      editorConfig: {}\n    }\n  },\n  computed: {\n    skeletonAttrs () {\n      return {\n        active: true,\n        style: { width: '100%' },\n        paragraph: { rows: 10 }\n      }\n    }\n  },\n  components: {\n    OnlyofficeEditor\n  },\n  created () {\n    this.queryDocumentInfo()\n  },\n  methods: {\n    // 获取文档配置信息\n    queryDocumentInfo () {\n      this.loading.page = true\n      queryDocumentInfo({ key: 'test5.docx' })\n        .then(res => {\n          const data = res.data || {}\n          const { id, remarks } = data\n          this.detail = { id, remarks }\n          this.editorConfig = data.editorConfig\n        })\n        .finally(() => {\n          this.loading.page = false\n        })\n    },\n    // 保存\n    onSave () {\n      this.loading.save = true\n      const { key } = this.editorConfig.document\n      const { id } = this.detail\n      forceSaveDocumentInfo({ key, id })\n        .then(res => {\n          if (res.code === 0) {\n            this.$message.success('保存成功')\n          } else {\n            this.$message.error(res.message)\n          }\n        })\n        .finally(() => {\n          this.loading.save = false\n        })\n    },\n    // 编辑器加载完毕后回调 ready 函数，editor 为当前编辑器实例\n    onEditorReady (editor) {\n      console.log(editor)\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "onlyoffice-vue/vue.config.js",
    "content": "/**\r\n * vue.config.js\r\n * https://cli.vuejs.org/zh/config/\r\n */\r\n\r\n// 默认的接口前缀，在 .env 中设置\r\nconst apiPrefix = `^${process.env.VUE_APP_API_PREFIX}`\r\n// Onlyoffice 接口前缀，在 .env 中设置\r\nconst onlyofficeApiPrefix = `^${process.env.VUE_APP_ONLYOFFICE_API_PREFIX}`\r\n\r\nmodule.exports = {\r\n  css: {\r\n    loaderOptions: {\r\n      less: {\r\n        modifyVars: {},\r\n        javascriptEnabled: true\r\n      }\r\n    }\r\n  },\r\n  devServer: {\r\n    port: process.env.VUE_APP_PORT,\r\n    proxy: {\r\n      [apiPrefix]: {\r\n        target: 'http://127.0.0.1:3000',\r\n        ws: false,\r\n        changeOrigin: true,\r\n        logLevel: 'debug'\r\n      },\r\n      [onlyofficeApiPrefix]: {\r\n        target: 'http://127.0.0.1:8701',\r\n        ws: false,\r\n        changeOrigin: true,\r\n        pathRewrite: {\r\n          [onlyofficeApiPrefix]: ''\r\n        }\r\n      }\r\n    }\r\n  },\r\n  chainWebpack: config => {}\r\n}\r\n"
  }
]