Full Code of wytxer/demo-onlyoffice for AI

main c676d197094c cached
72 files
68.6 KB
22.1k tokens
58 symbols
1 requests
Download .txt
Repository: wytxer/demo-onlyoffice
Branch: main
Commit: c676d197094c
Files: 72
Total size: 68.6 KB

Directory structure:
gitextract_6eix9r5i/

├── .gitignore
├── .vscode/
│   └── settings.json
├── LICENSE
├── README.md
├── onlyoffice-server/
│   ├── .eslintrc.js
│   ├── .gitignore
│   ├── .prettierrc
│   ├── README.md
│   ├── nest-cli.json
│   ├── package.json
│   ├── src/
│   │   ├── app.controller.ts
│   │   ├── app.module.ts
│   │   ├── document/
│   │   │   ├── document.controller.ts
│   │   │   ├── document.dto.ts
│   │   │   ├── document.entity.ts
│   │   │   ├── document.module.ts
│   │   │   └── document.service.ts
│   │   ├── main.ts
│   │   ├── onlyoffice/
│   │   │   ├── onlyoffice.controller.ts
│   │   │   ├── onlyoffice.dto.ts
│   │   │   ├── onlyoffice.entity.ts
│   │   │   ├── onlyoffice.interface.ts
│   │   │   ├── onlyoffice.module.ts
│   │   │   └── onlyoffice.service.ts
│   │   └── shared/
│   │       ├── config.ts
│   │       ├── interceptors/
│   │       │   ├── logger.interceptor.ts
│   │       │   └── response.interceptor.ts
│   │       └── shared.module.ts
│   ├── static/
│   │   ├── docbuilder/
│   │   │   └── test1.docbuilder
│   │   ├── plugins/
│   │   │   └── plugin-hello/
│   │   │       ├── config.json
│   │   │       ├── index.html
│   │   │       ├── main.js
│   │   │       └── translations/
│   │   │           └── zh-CN.json
│   │   ├── test1.docx
│   │   ├── test1.xlsx
│   │   ├── test2.docx
│   │   ├── test2.xlsx
│   │   ├── test3.docx
│   │   ├── test4.docx
│   │   ├── test5.docx
│   │   └── test5.html
│   ├── tsconfig.build.json
│   └── tsconfig.json
└── onlyoffice-vue/
    ├── .editorconfig
    ├── .eslintignore
    ├── .eslintrc.js
    ├── .gitignore
    ├── LICENSE
    ├── README.md
    ├── babel.config.js
    ├── package.json
    ├── public/
    │   └── index.html
    ├── src/
    │   ├── api/
    │   │   ├── onlyoffice.js
    │   │   └── request.js
    │   ├── app.vue
    │   ├── layouts/
    │   │   └── block.vue
    │   ├── main.js
    │   ├── router/
    │   │   ├── index.js
    │   │   └── routes.js
    │   ├── store/
    │   │   └── index.js
    │   └── views/
    │       ├── home.vue
    │       └── onlyoffice/
    │           ├── document-conversion.vue
    │           ├── document-custom-config.vue
    │           ├── document-editor-jwt.vue
    │           ├── document-editor.vue
    │           ├── document-plugin.vue
    │           ├── document-quick-start.vue
    │           ├── excel-formula.vue
    │           ├── excel-quick-start.vue
    │           ├── modules/
    │           │   └── onlyoffice-editor.vue
    │           └── onlyoffice-vue.vue
    └── vue.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
/dist
/tar

# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?


================================================
FILE: .vscode/settings.json
================================================
{
  "eslint.workingDirectories": [
    "./onlyoffice-server",
    "./onlyoffice-vue"
  ],
  "vetur.validation.interpolation": false
}

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2022 wytxer

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# DEMO ONLYOFFICE

基于 Nest 和 Vue.js 的 Onlyoffice 示例。

> Onlyoffice@7.2 之后的版本默认开启了 JWT 加密,运行仓库例子时请先关闭 JWT 校验。


## 快速开始

注意,在启动之前,需要将 **onlyoffice-vue/.env** 里面的 `VUE_APP_HOST` 和 **onlyoffice-server/.env** 里面的 `HOST` 字段统一替换成自己的本机 IP。

```bash
# 启动前端
cd onlyoffice-vue
# 安装依赖
pnpm install
# 启动开发服务
pnpm run dev

# 启动后端
cd onlyoffice-server
# 安装依赖
pnpm install
# 启动开发服务
pnpm run dev
```


## 相关文章

文章与仓库代码是一体的,强烈建议跟着文章步骤运行例子。

- [在 Vue 中接入 Onlyoffice](https://bszhct.com/2022/08/15/onlyoffice-quick-start/)
- [Onlyoffice 二次开发指南,包含自定义 Onlyoffice 配置、使用 JWT 加密 Onlyoffice 配置、Onlyoffice 文档转换等](https://bszhct.com/2022/08/19/onlyoffice-usage/)
- [Onlyoffice 插件开发指南](https://bszhct.com/2022/08/20/onlyoffice-plugin/)
- [在线 Office 解决方案调研总结](https://bszhct.com/2022/08/14/online-office-summary/)


## 填坑交流群

添加后请备注「oo」

<img src="./qrcode.jpg" style="width: 120px" />


## License

[MIT](/LICENSE)


================================================
FILE: onlyoffice-server/.eslintrc.js
================================================
module.exports = {
  parser: '@typescript-eslint/parser',
  parserOptions: {
    project: 'tsconfig.json',
    tsconfigRootDir : __dirname, 
    sourceType: 'module',
  },
  plugins: ['@typescript-eslint/eslint-plugin'],
  extends: [
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
  ],
  root: true,
  env: {
    node: true,
    jest: true,
  },
  ignorePatterns: ['.eslintrc.js'],
  rules: {
    '@typescript-eslint/interface-name-prefix': 'off',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
  },
};


================================================
FILE: onlyoffice-server/.gitignore
================================================
# compiled output
/dist
/node_modules

# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# OS
.DS_Store

# Tests
/coverage
/.nyc_output

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

================================================
FILE: onlyoffice-server/.prettierrc
================================================
{
  "singleQuote": true,
  "trailingComma": "all"
}

================================================
FILE: onlyoffice-server/README.md
================================================
# Onlyoffice Server

基于 nest 的 Onlyoffice 示例。


## 快速使用

```bash
# 安装依赖
pnpm install

# 开发
pnpm run dev

# 打包
pnpm run build
```


## License

[MIT](/LICENSE)


================================================
FILE: onlyoffice-server/nest-cli.json
================================================
{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "plugins": [
      {
        "name": "@nestjs/swagger",
        "options": {
          "classValidatorShim": true,
          "introspectComments": true
        }
      }
    ]
  }
}


================================================
FILE: onlyoffice-server/package.json
================================================
{
  "name": "onlyoffice-server",
  "version": "1.0.0",
  "description": "",
  "author": "wytxer",
  "private": true,
  "license": "MIT",
  "scripts": {
    "prebuild": "rimraf dist",
    "build": "nest build",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "dev": "pnpm run start:test",
    "start": "cross-env NODE_ENV=online nest start",
    "start:test": "cross-env NODE_ENV=test nest start --watch",
    "start:debug": "cross-env NODE_ENV=test nest start --debug --watch",
    "start:online": "cross-env NODE_ENV=online node dist/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json"
  },
  "dependencies": {
    "@nestjs/axios": "^0.1.0",
    "@nestjs/common": "^9.0.0",
    "@nestjs/config": "^2.2.0",
    "@nestjs/core": "^9.0.0",
    "@nestjs/jwt": "^9.0.0",
    "@nestjs/platform-express": "^9.0.0",
    "@nestjs/serve-static": "^3.0.0",
    "@nestjs/swagger": "^6.0.1",
    "axios": "^0.27.2",
    "chalk": "^5.0.1",
    "class-transformer": "^0.5.1",
    "class-validator": "^0.13.2",
    "cross-env": "^7.0.3",
    "reflect-metadata": "^0.1.13",
    "rimraf": "^3.0.2",
    "rxjs": "^7.2.0",
    "swagger-ui-express": "^4.4.0"
  },
  "devDependencies": {
    "@nestjs/cli": "^9.0.0",
    "@nestjs/schematics": "^9.0.0",
    "@nestjs/testing": "^9.0.0",
    "@types/express": "^4.17.13",
    "@types/jest": "28.1.4",
    "@types/node": "^16.0.0",
    "@types/supertest": "^2.0.11",
    "@typescript-eslint/eslint-plugin": "^5.0.0",
    "@typescript-eslint/parser": "^5.0.0",
    "eslint": "^8.0.1",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-prettier": "^4.0.0",
    "jest": "28.1.2",
    "prettier": "^2.3.2",
    "source-map-support": "^0.5.20",
    "supertest": "^6.1.3",
    "ts-jest": "28.0.5",
    "ts-loader": "^9.2.3",
    "ts-node": "^10.0.0",
    "tsconfig-paths": "4.0.0",
    "typescript": "^4.3.5"
  },
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "rootDir": "src",
    "testRegex": ".*\\.spec\\.ts$",
    "transform": {
      "^.+\\.(t|j)s$": "ts-jest"
    },
    "collectCoverageFrom": [
      "**/*.(t|j)s"
    ],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node"
  }
}


================================================
FILE: onlyoffice-server/src/app.controller.ts
================================================
import { Controller } from '@nestjs/common';

@Controller()
export class AppController {}


================================================
FILE: onlyoffice-server/src/app.module.ts
================================================
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';
import config from './shared/config';
import { SharedModule } from './shared/shared.module';
import { AppController } from './app.controller';
import { OnlyofficeModule } from './onlyoffice/onlyoffice.module';
import { DocumentModule } from './document/document.module';

@Module({
  imports: [
    // 导入全局变量配置
    ConfigModule.forRoot({
      isGlobal: true,
      expandVariables: true,
      load: [config],
    }),
    // 静态资源服务配置
    ServeStaticModule.forRoot({
      serveRoot: '/static',
      rootPath: join(__dirname, '..', 'static'),
    }),
    SharedModule,
    OnlyofficeModule,
    DocumentModule,
  ],
  controllers: [AppController],
  providers: [],
})
export class AppModule {}


================================================
FILE: onlyoffice-server/src/document/document.controller.ts
================================================
import { Controller, Post, Get, Body, Query } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { DocumentForceSaveDto, DocumentInfoDto } from './document.dto';
import { DocumentInfo } from './document.entity';
import { DocumentService } from './document.service';

@ApiTags('Document')
@Controller({
  path: 'document',
  version: '1',
})
export class DocumentController {
  constructor(private documentService: DocumentService) {}
  @Post('forceSave')
  @ApiOperation({
    summary: '强制保存文档',
    description:
      '通过调用 Onlyoffice 提供的指令接口间接保存文件,最终文件的报错操作还是在 editorConfig.callbackUrl 所指定的接口里面完成的',
  })
  async forceSave(@Body() body: DocumentForceSaveDto): Promise<any> {
    return await this.documentService.forceSave(body);
  }

  @Get('documentInfo')
  @ApiOperation({
    summary: '获取文档信息',
    description: '仅构造 Onlyoffice 文档编辑器显示和保存需要的必要信息',
  })
  async documentInfo(@Query() query: DocumentInfoDto): Promise<DocumentInfo> {
    return await this.documentService.documentInfo(query);
  }

  @Get('excelInfo')
  @ApiOperation({
    summary: '获取表格信息',
    description: '仅构造 Onlyoffice 表格编辑器显示和保存需要的必要信息',
  })
  async excelInfo(@Query() query: DocumentInfoDto): Promise<DocumentInfo> {
    return await this.documentService.excelInfo(query);
  }
}


================================================
FILE: onlyoffice-server/src/document/document.dto.ts
================================================
import { IsString, IsNumber, IsNotEmpty, IsIn } from 'class-validator';

/**
 * 文档强制保存请求参数
 */
export class DocumentForceSaveDto {
  /**
   * 业务 id
   */
  @IsNumber()
  id: string;

  /**
   * 文档标识符
   */
  @IsString()
  key: string;

  /**
   * 使用 JWT 加密文档参数,默认不加密,需要配合 Onlyoffice 的 secret 配置使用。
   */
  @IsString()
  @IsIn(['y', 'n'])
  useJwtEncrypt?: string = 'n';
}

/**
 * 获取文档信息请求参数
 */
export class DocumentInfoDto {
  /**
   * 文档标识符(为了获取本地缓存的文档文件)
   * @example test1.docx
   */
  @IsNotEmpty()
  @IsString()
  key: string;

  /**
   * 使用 JWT 加密文档参数,默认不加密,需要配合 Onlyoffice 的 secret 配置使用。
   */
  @IsString()
  @IsIn(['y', 'n'])
  useJwtEncrypt?: string = 'n';

  /**
   * 使用插件。默认不返回插件配置
   */
  @IsString()
  @IsIn(['y', 'n'])
  usePlugin?: string = 'n';
}


================================================
FILE: onlyoffice-server/src/document/document.entity.ts
================================================
import { IsString, IsNumber, IsObject } from 'class-validator';
import { OnlyofficeEditorConfig } from '../onlyoffice/onlyoffice.entity';

export class DocumentInfo {
  /**
   * 业务 id
   */
  @IsNumber()
  id?: number;

  /**
   * 备注
   */
  @IsNumber()
  remarks?: string;

  /**
   * 编辑器配置
   */
  @IsObject()
  editorConfig: OnlyofficeEditorConfig;
}

export class DocumentForceSave {
  /**
   * 状态码
   */
  @IsNumber()
  code: number;

  /**
   * 消息
   */
  @IsString()
  message?: string;
}


================================================
FILE: onlyoffice-server/src/document/document.module.ts
================================================
import { Module } from '@nestjs/common';

import { DocumentController } from './document.controller';
import { DocumentService } from './document.service';
import { OnlyofficeService } from '../onlyoffice/onlyoffice.service';

@Module({
  controllers: [DocumentController],
  providers: [DocumentService, OnlyofficeService],
})
export class DocumentModule {}


================================================
FILE: onlyoffice-server/src/document/document.service.ts
================================================
import { ConfigService } from '@nestjs/config';
import { Injectable, HttpStatus, HttpException } from '@nestjs/common';
import { OnlyofficeService } from '../onlyoffice/onlyoffice.service';
import { DocumentForceSaveDto, DocumentInfoDto } from './document.dto';
import { DocumentForceSave, DocumentInfo } from './document.entity';

@Injectable()
export class DocumentService {
  constructor(
    private readonly config: ConfigService,
    private onlyofficeService: OnlyofficeService,
  ) {}

  async forceSave(body: DocumentForceSaveDto): Promise<DocumentForceSave> {
    // 1、保存业务数据
    // 2、调用 Onlyoffice 的强制保存,实际业务中可能还有更多的业务操作,可根据实际情况删改
    const { id: userdata, key, useJwtEncrypt } = body;
    const data = await this.onlyofficeService.forceSave({
      key,
      // 将业务参数传给 Onlyoffice 服务,当回调里面存在多个请求时,标识符将有助于区分特定请求
      userdata,
      useJwtEncrypt,
    });
    // 保存成功
    if (data.error === 0) {
      return null;
    }
    throw new HttpException(data, HttpStatus.OK);
  }

  async documentInfo(query: DocumentInfoDto): Promise<DocumentInfo> {
    const editorConfig = this.onlyofficeService.editorDefaultConfig();
    // 添加文档
    editorConfig.document = {
      ...editorConfig.document,
      fileType: 'docx',
      key: query.key,
      url: `${this.config.get('domain')}/static/${query.key}`,
      title: '测试文档.docx',
    };
    // 添加用户信息
    editorConfig.editorConfig.user = {
      group: '技术部',
      id: 'wytxer',
      name: '程序员未央',
    };
    // 添加插件配置
    if (query.usePlugin === 'y') {
      editorConfig.editorConfig.plugins = {
        autostart: [],
        pluginsData: [
          `${this.config.get(
            'domain',
          )}/static/plugins/plugin-hello/config.json`,
        ],
      };
    }
    // 加密编辑器参数
    if (query.useJwtEncrypt === 'y') {
      this.onlyofficeService.signJwt(editorConfig);
    }
    return {
      id: 1,
      remarks: '业务字段',
      editorConfig,
    };
  }

  async excelInfo(query: DocumentInfoDto): Promise<DocumentInfo> {
    const editorConfig = this.onlyofficeService.editorDefaultConfig();
    // 添加文档
    editorConfig.document = {
      ...editorConfig.document,
      fileType: 'xlsx',
      key: query.key,
      url: `${this.config.get('domain')}/static/${query.key}`,
      title: '测试表格.xlsx',
    };
    // 修改文档宽度
    editorConfig.width = '100%';
    // 修改编辑器类型
    editorConfig.documentType = 'cell';
    // 添加用户信息
    editorConfig.editorConfig.user = {
      group: '技术部',
      id: 'wytxer',
      name: '程序员未央',
    };
    // 加密编辑器参数
    if (query.useJwtEncrypt === 'y') {
      this.onlyofficeService.signJwt(editorConfig);
    }
    return {
      id: 1,
      remarks: '业务字段',
      editorConfig,
    };
  }
}


================================================
FILE: onlyoffice-server/src/main.ts
================================================
import { NestFactory } from '@nestjs/core';
import { ValidationPipe, VersioningType } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
import { ResponseInterceptor } from './shared/interceptors/response.interceptor';
import { LoggingInterceptor } from './shared/interceptors/logger.interceptor';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, { cors: true });

  // 设置接口前缀
  app.setGlobalPrefix(process.env.API_PREFIX);

  app.useGlobalPipes(
    new ValidationPipe({
      // 跳过验证对象中值为 null 或 undefined 的属性的验证。完整配置文档参见:https://docs.nestjs.cn/9/techniques?id=%e9%aa%8c%e8%af%81
      skipNullProperties: true,
      stopAtFirstError: true,
      transform: true,
    }),
  );

  // 添加全局数据响应拦截器
  app.useGlobalInterceptors(
    new ResponseInterceptor(),
    new LoggingInterceptor(),
  );

  // 版本号配置
  app.enableVersioning({
    type: VersioningType.URI,
    defaultVersion: '1',
  });

  // 接口文档配置
  const isTest = process.env.NODE_ENV === 'test';
  if (isTest) {
    const options = new DocumentBuilder()
      .setTitle('Demo Onlyoffice 接口文档')
      .setVersion('1.0.0')
      .addTag('Onlyoffice')
      .addTag('Document')
      .build();
    const document = SwaggerModule.createDocument(app, options);
    SwaggerModule.setup('docs', app, document);
  }

  //  启动服务
  await app.listen(process.env.PORT);
}
bootstrap();


================================================
FILE: onlyoffice-server/src/onlyoffice/onlyoffice.controller.ts
================================================
import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { OnlyofficeCallbackDto } from './onlyoffice.dto';
import { OnlyofficeCallback } from './onlyoffice.entity';
import { OnlyofficeService } from './onlyoffice.service';

@ApiTags('Onlyoffice')
@Controller({
  path: 'onlyoffice',
  version: '1',
})
export class OnlyofficeController {
  constructor(private onlyofficeService: OnlyofficeService) {}
  @Post('callback')
  @ApiOperation({
    summary: '文档回调地址',
    description: '对应 Onlyoffice 的 editorConfig.callbackUrl 字段',
  })
  // 这里表示成功的 statusCode 状态不能返回 201,否则会报错「这份文件无法保存。请检查连接设置或联系您的管理员」,因为在 Onlyoffice 如果 statusCode 不等于 200 认为是失败
  @HttpCode(HttpStatus.OK)
  async callback(
    // @Body() body: OnlyofficeCallbackDto,
    @Body() body: any,
  ): Promise<OnlyofficeCallback> {
    return await this.onlyofficeService.callback(body);
  }
}


================================================
FILE: onlyoffice-server/src/onlyoffice/onlyoffice.dto.ts
================================================
import {
  IsString,
  IsNumber,
  IsObject,
  IsArray,
  IsDefined,
  IsIn,
} from 'class-validator';

export class IActions {
  /**
   * 操作类型
   */
  @IsNumber()
  type: number;

  /**
   * 用户 id
   */
  @IsNumber()
  userid?: string | number;
}

export class IUser {
  /**
   * 用户 id
   */
  @IsNumber()
  id?: string | number;

  /**
   * 用户名,里面包含了组织信息和用户名
   * @example 技术部 程序员未央
   */
  @IsString()
  name?: string;
}

class IChange {
  /**
   * 创建时间
   */
  @IsString()
  created?: string;

  /**
   * 用户信息
   */
  @IsObject()
  user?: IUser;
}

export class IHistory {
  /**
   * 当前 Onlyoffice 服务版本号
   */
  @IsString()
  serverVersion?: string;

  /**
   * 历史记录信息
   */
  @IsArray()
  changes?: IChange[];
}

/**
 * 回调请求参数,文档地址:https://api.onlyoffice.com/editors/callback
 */
export class OnlyofficeCallbackDto {
  /**
   * 用户与文档的交互状态。0:用户断开与文档共同编辑的连接;1:新用户连接到文档共同编辑;2:用户单击强制保存按钮
   */
  @IsArray()
  actions?: IActions[] = null;

  /**
   * 字段已在 4.2 后版本废弃,请使用 history 代替
   */
  @IsObject()
  changeshistory?: IHistory = null;

  /**
   * 文档变更的历史记录,仅当 status 等于 2 或者 3 时该字段才有值。其中的 serverVersion 字段也是 refreshHistory 方法的入参
   */
  @IsObject()
  history?: IHistory = null;

  /**
   * 文档编辑的元数据信息,用来跟踪显示文档更改记录,仅当 status 等于 2 或者 2 时该字段才有值。该字段也是 setHistoryData(显示与特定文档版本对应的更改,类似 Git 历史记录)方法的入参
   */
  @IsString()
  changesurl?: string = null;

  /**
   * url 字段下载的文档扩展名,文件类型默认为 OOXML 格式,如果启用了 assemblyFormatAsOrigin(https://api.onlyoffice.com/editors/save#assemblyFormatAsOrigin) 服务器设置则文件以原始格式保存
   */
  @IsString()
  filetype?: string = null;

  /**
   * 文档强制保存类型。0:对命令服务(https://api.onlyoffice.com/editors/command/forcesave)执行强制保存;1:每次保存完成时都会执行强制保存请求,仅设置 forcesave 等于 true 时生效;2:强制保存请求由计时器使用服务器中的设置执行。该字段仅 status 等于 7 或者 7 时才有值
   */
  @IsNumber()
  forcesavetype?: number = null;

  /**
   * 文档标识符,类似 id,在 Onlyoffice 服务内部唯一
   */
  @IsDefined()
  @IsString()
  key: string;

  /**
   * 文档状态。1:文档编辑中;2:文档已准备好保存;3:文档保存出错;4:文档没有变化无需保存;6:正在编辑文档,但保存了当前文档状态;7:强制保存文档出错
   */
  @IsNumber()
  @IsIn([1, 2, 3, 4, 6, 7])
  status?: number = null;

  /**
   * 已编辑文档的链接,可以通过它下载到最新的文档,仅当 status 等于 2、3、6 或 7 时该字段才有值
   */
  @IsString()
  url?: string = null;

  /**
   * 自定义参数,对应指令服务的 userdata 字段
   */
  @IsObject()
  userdata?: object = null;

  /**
   * 打开文档进行编辑的用户标识列表,当文档被修改时,该字段将返回最后编辑文档的用户标识符,当 status 字段等于 2 或者 6 时有值
   */
  @IsArray()
  users?: string[] = null;

  /**
   * 最近保存时间
   */
  @IsString()
  lastsave?: string = null;

  /**
   * 加密令牌
   */
  @IsString()
  token?: string = null;
}

/**
 * 强制保存请求参数
 */
export class OnlyofficeForceSaveDto {
  /**
   * 文档标识符
   */
  @IsString()
  key: string;

  /**
   * 用户自定义的数据
   */
  @IsString()
  userdata?: string = null;

  /**
   * 使用 JWT 加密文档参数,默认不加密,需要配合 Onlyoffice 的 secret 配置使用。
   */
  @IsString()
  @IsIn(['y', 'n'])
  useJwtEncrypt?: string = 'n';
}


================================================
FILE: onlyoffice-server/src/onlyoffice/onlyoffice.entity.ts
================================================
import { IsString, IsNumber, IsObject, IsIn } from 'class-validator';

class IFeatures {
  /**
   * 是否启用拼写检查
   */
  spellcheck?: boolean = false;
}

class ICustomization {
  /**
   * 强制保存
   */
  forcesave?: boolean = true;

  /**
   * 自定义参数
   */
  features?: IFeatures = new IFeatures();
}

class IPlugins {
  /**
   * 需要自动加载的插件列表
   */
  autostart: string[] = [];

  /**
   * 自定义的插件列表
   */
  pluginsData: string[] = [];
}

class IPermission {
  /**
   * 是否启用评论
   */
  comment?: boolean = false;

  /**
   * 是否启用下载
   */
  download?: boolean = true;

  /**
   * 是否启用编辑
   */
  edit?: boolean = true;

  /**
   * 是否启用导出
   */
  print?: boolean = true;

  /**
   * 是否启用预览
   */
  review?: boolean = true;
}

class IDocument {
  /**
   * 文件类型
   */
  fileType = 'docx';

  /**
   * 文档标识符
   */
  key: string;

  /**
   * 文档地址,绝对路径
   */
  url: string;

  /**
   * 文档标题
   */
  title: string;

  /**
   * 文档权限配置
   */
  permissions: IPermission = new IPermission();
}

class IEditorConfig {
  /**
   * 回调地址
   */
  callbackUrl: string = process.env.ONLYOFFICE_CALLBACK;

  /**
   * 语言
   */
  lang?: string = 'zh-CN';

  /**
   * 用户信息
   */
  user: object = {};

  /**
   * 模板列表
   */
  templates?: object[] = [];

  /**
   * 自定义配置。字段相关配置详解:https://api.onlyoffice.com/editors/config/editor/
   */
  customization?: ICustomization = new ICustomization();

  /**
   * 插件列表
   */
  plugins?: IPlugins = new IPlugins();
}

export class OnlyofficeCallback {
  /**
   * 返回 0 表示成功,否则表示失败
   */
  @IsNumber()
  error: number;
}

export class OnlyofficeForceSave {
  /**
   * 状态码
   */
  @IsNumber()
  code: number;

  /**
   * 状态码
   */
  @IsNumber()
  error: number;

  /**
   * 消息
   */
  @IsString()
  message?: string;
}

/**
 * 编辑器配置项,完整配置项参见:https://api.onlyoffice.com/editors/config/
 */
export class OnlyofficeEditorConfig {
  /**
   * 编辑器宽度
   */
  @IsNumber()
  width?: number | string = 1200;

  /**
   * 编辑器高度
   */
  @IsNumber()
  height?: number | string = 800;

  /**
   * 文档类型。word:文档,cell:表格,slide:PPT
   */
  @IsString()
  @IsIn(['word', 'cell', 'slide'])
  documentType = 'word';

  /**
   * 文档配置
   */
  @IsObject()
  document: IDocument = new IDocument();

  /**
   * 编辑器配置
   */
  @IsObject()
  editorConfig: IEditorConfig = new IEditorConfig();

  /**
   * 编辑器加密令牌,开启加密时有值
   */
  @IsString()
  token?: string;
}


================================================
FILE: onlyoffice-server/src/onlyoffice/onlyoffice.interface.ts
================================================
enum ErrorCode {
  NoError = 0,
  NoSecret = 1,
  ErrorCallbackUrl = 2,
  ServerError = 3,
  NoChange = 4,
  ErrorCommand = 5,
  InvalidToken = 6,
}
export interface IOnlyofficeCommand {
  error: ErrorCode;
}

export interface IOnlyofficeForceSave {
  c?: string;
  key?: string;
  userdata?: string;
  token?: string;
}


================================================
FILE: onlyoffice-server/src/onlyoffice/onlyoffice.module.ts
================================================
import { Module } from '@nestjs/common';

import { OnlyofficeController } from './onlyoffice.controller';
import { OnlyofficeService } from './onlyoffice.service';

@Module({
  controllers: [OnlyofficeController],
  providers: [OnlyofficeService],
  imports: [],
})
export class OnlyofficeModule {}


================================================
FILE: onlyoffice-server/src/onlyoffice/onlyoffice.service.ts
================================================
import { HttpService } from '@nestjs/axios';
import { AxiosResponse } from 'axios';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { createWriteStream, WriteStream } from 'fs';
import { join } from 'path';
import { Injectable, Logger, HttpStatus, HttpException } from '@nestjs/common';
import {
  OnlyofficeCallbackDto,
  OnlyofficeForceSaveDto,
} from './onlyoffice.dto';
import {
  OnlyofficeCallback,
  OnlyofficeForceSave,
  OnlyofficeEditorConfig,
} from './onlyoffice.entity';
import {
  IOnlyofficeCommand,
  IOnlyofficeForceSave,
} from './onlyoffice.interface';

@Injectable()
export class OnlyofficeService {
  constructor(
    private readonly request: HttpService,
    private readonly config: ConfigService,
    private readonly jwt: JwtService,
  ) {}

  private readonly logger = new Logger(OnlyofficeService.name);

  async callback(body: OnlyofficeCallbackDto): Promise<OnlyofficeCallback> {
    const { url, status } = body;
    // 正在编辑文档但保存了当前文档状态
    if (status === 6) {
      try {
        // 根据地址下载文档文件
        const file: AxiosResponse = await this.request.axiosRef.get(url, {
          responseType: 'stream',
        });
        const stream: WriteStream = createWriteStream(
          join(this.config.get('staticPath'), body.key),
        );
        file.data.pipe(stream);
      } catch (error) {
        this.logger.error(error);
        // 返回 Onlyoffice 服务认识的报错
        throw new HttpException({ error: 7 }, HttpStatus.OK);
      }
    } else if (status === 7) {
      // 强制保存文档出错
      throw new HttpException({ error: status }, HttpStatus.OK);
    }
    // 默认返回成功的状态
    throw new HttpException({ error: 0 }, HttpStatus.OK);
  }

  // 强制保存文档
  async forceSave(body: OnlyofficeForceSaveDto): Promise<OnlyofficeForceSave> {
    const { key, userdata, useJwtEncrypt } = body;
    let newBody: IOnlyofficeForceSave = {
      c: 'forcesave',
      key,
      userdata,
    };
    // 如果是使用了 JWT 加密,重新组装请求参数
    if (useJwtEncrypt === 'y') {
      newBody = {
        token: this.jwt.sign(newBody, {
          secret: this.config.get('onlyoffice.secret'),
        }),
      };
    }
    const saveState: IOnlyofficeCommand = await this.request.axiosRef
      .post(this.config.get('onlyoffice.commandUrl'), newBody)
      .then((res) => res.data);

    // 组装返回值
    const data: OnlyofficeForceSave = {
      error: saveState.error,
      code: 602,
    };
    // 保存成功
    if (saveState.error === 0) {
      data.code = 0;
    } else if (saveState.error === 4) {
      // 文档未改变无需保存
      data.message = '文档未改变无需保存';
    } else {
      // 文档保存失败
      data.message = '文档保存失败';
    }
    return data;
  }

  // 编辑器默认配置
  editorDefaultConfig(): OnlyofficeEditorConfig {
    const { ...defaultConfig } = new OnlyofficeEditorConfig();
    return defaultConfig;
  }

  // 以 JWT 加密方式签名编辑器配置
  signJwt(editorConfig: OnlyofficeEditorConfig): OnlyofficeEditorConfig {
    editorConfig.token = this.jwt.sign(editorConfig, {
      secret: this.config.get('onlyoffice.secret'),
    });
    return editorConfig;
  }
}


================================================
FILE: onlyoffice-server/src/shared/config.ts
================================================
import { join } from 'path';

export default () => ({
  isTest: process.env.NODE_ENV === 'test',
  isOnline: process.env.NODE_ENV === 'online',
  port: process.env.PORT,
  apiPrefix: process.env.API_PREFIX,
  domain: process.env.DOMAIN,
  staticPath: join(process.cwd(), '/static'),
  onlyoffice: {
    secret: process.env.ONLYOFFICE_SECRET,
    domain: process.env.ONLYOFFICE_DOMAIN,
    commandUrl: process.env.ONLYOFFICE_COMMAND_URL,
    callback: process.env.ONLYOFFICE_CALLBACK,
  },
});


================================================
FILE: onlyoffice-server/src/shared/interceptors/logger.interceptor.ts
================================================
import {
  CallHandler,
  ExecutionContext,
  Injectable,
  Logger,
  NestInterceptor,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  private readonly logger: Logger = new Logger(LoggingInterceptor.name);

  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    const request: Request = context.switchToHttp().getRequest();
    const { method, url, body, headers } = request;

    // 输出请求类型和请求参数等信息
    this.logger.log(
      `Request {${method.toUpperCase()}, ${url}} ${JSON.stringify(
        body,
      )} ${JSON.stringify(headers)}`,
    );
    return next.handle().pipe(
      tap({
        next: (data: unknown): void => this.logResponse(data, context),
      }),
    );
  }

  // 输出响应日志
  private logResponse(data: unknown, context: ExecutionContext): void {
    const request: Request = context.switchToHttp().getRequest<Request>();
    const response: Response = context.switchToHttp().getResponse<Response>();

    this.logger.log(
      `Response {${response.statusCode}, ${request.method.toUpperCase()}, ${
        request.url
      }} ${JSON.stringify(data)}`,
    );
  }
}


================================================
FILE: onlyoffice-server/src/shared/interceptors/response.interceptor.ts
================================================
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface Response<T> {
  data: T;
}

@Injectable()
export class ResponseInterceptor<T> implements NestInterceptor<T, Response<T>> {
  intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Observable<Response<T>> {
    return next.handle().pipe(map((data) => ({ code: 0, data })));
  }
}


================================================
FILE: onlyoffice-server/src/shared/shared.module.ts
================================================
import { Module, Global } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';
import { JwtModule } from '@nestjs/jwt';

@Global()
@Module({
  imports: [HttpModule, JwtModule],
  exports: [HttpModule, JwtModule],
  providers: [],
})
export class SharedModule {}


================================================
FILE: onlyoffice-server/static/docbuilder/test1.docbuilder
================================================
builder.CreateFile("docx")
var oDocument = Api.GetDocument()

// 添加标题
var oParagraph = oDocument.GetElement(0)
oParagraph.AddText("标题")
oParagraph.SetJc("center")
oParagraph.SetFontSize(40)
oParagraph.SetFontFamily("Arial")

// 添加正文
oParagraph = Api.CreateParagraph()
oParagraph.AddText("\n\n正文内容。\n正文内容。\n正文内容。\n\n\n此致\n")
oParagraph.SetSpacingLine(400, "auto")
oParagraph.SetFontSize(28)
oParagraph.SetFontFamily("Arial")
oDocument.Push(oParagraph)

// 添加变量
var inlineContent = Api.CreateInlineLvlSdt()
inlineContent.SetTag("var-customName")
content = Api.CreateRun()
content.AddText("自定义变量名称")
content.SetFontSize(28)
content.SetFontFamily("Arial")
content.SetHighlight(255, 255, 0)
inlineContent.AddElement(content, 0)
oParagraph.AddInlineLvlSdt(inlineContent)

// 添加落款
oParagraph = Api.CreateParagraph()
oParagraph.AddText("落款:")
oParagraph.SetSpacingLine(400, "auto")
oParagraph.SetJc("right")
oParagraph.SetFontSize(28)
oParagraph.SetFontFamily("Arial")

// 添加落款名称
inlineContent = Api.CreateInlineLvlSdt()
inlineContent.SetTag("var-signName")
content = Api.CreateRun()
content.AddText("落款名称")
content.SetFontSize(28)
content.SetFontFamily("Arial")
content.SetHighlight(255, 255, 0)
inlineContent.AddElement(content, 0)
oParagraph.AddInlineLvlSdt(inlineContent)
oDocument.Push(oParagraph)

// 添加日期
oParagraph = Api.CreateParagraph()
oParagraph.SetSpacingLine(400, "auto")
oParagraph.SetJc("right")
inlineContent = Api.CreateInlineLvlSdt()
inlineContent.SetTag("var-updatedTime")
content = Api.CreateRun()
content.AddText("当前日期")
content.SetFontSize(28)
content.SetFontFamily("Arial")
content.SetHighlight(255, 255, 0)
inlineContent.AddElement(content, 0)
oParagraph.AddInlineLvlSdt(inlineContent)
oDocument.Push(oParagraph)
builder.SaveFile("docx", "test1.docx")
builder.CloseFile()


================================================
FILE: onlyoffice-server/static/plugins/plugin-hello/config.json
================================================
{
  "name": "Hello",
  "guid": "asc.{11700c35-1fdb-4e37-9edb-b31637139601}",
  "variations": [
    {
      "description": "Hello Word",
      "url": "index.html",
      "icons": [
        "icon.png",
        "icon@2x.png"
      ],
      "isViewer": false,
      "EditorsSupport": [
        "word"
      ],
      "isVisual": true,
      "isModal": false,
      "isInsideMode": true,
      "initDataType": "none",
      "initData": "",
      "isUpdateOleOnResize": false,
      "buttons": [],
      "events" : [
        "onClick"
      ]
    }
  ]
}

================================================
FILE: onlyoffice-server/static/plugins/plugin-hello/index.html
================================================
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title></title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
    <script src="https://onlyoffice.github.io/sdkjs-plugins/v1/plugins.js"></script>
    <script src="main.js"></script>
    <style>
      html, body {
        margin: 0;
        padding: 0;
      }
      body {
        padding: 12px;
      }
    </style>
  </head>

  <body>
    <button id="addText" class="btn-text-default submit primary">添加</button>
  </body>
</html>


================================================
FILE: onlyoffice-server/static/plugins/plugin-hello/main.js
================================================
/* eslint-disable */

(function(window, undefined) {
  window.Asc.plugin.init = function(initData) {
    console.log('插件开始初始化')
    console.log(window)

    var me = this
    $('#addText').click(function() {
      me.callCommand(function() {
        try {
          // 获取文档对象
          var oDocument = Api.GetDocument()

          console.log('文档对象')
          console.log(oDocument)

          // 生成一个新的段落对象
          var oParagraph = Api.CreateParagraph()
          // 往段落里面添加一个字符串文本
          oParagraph.AddText('Hello world')
          // 最后往文档里面添加一个段落对象
          oDocument.Push(oParagraph)
        } catch (error) {
          console.error(error)
        }
      }, false, true, function () {
        console.log('操作成功')
      })
    })

    // 在插件 iframe 之外释放鼠标按钮时调用的函数
    window.Asc.plugin.onExternalMouseUp = function() {
      var event = document.createEvent('MouseEvents')
      event.initMouseEvent('mouseup', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null)
      document.dispatchEvent(event)
    }

    window.Asc.plugin.button = function(id) {
      // 被中断或关闭窗口
      if (id === -1) {
        this.executeCommand('close', '')
      }
	  }
  }
})(window, undefined)


================================================
FILE: onlyoffice-server/static/plugins/plugin-hello/translations/zh-CN.json
================================================
{}

================================================
FILE: onlyoffice-server/static/test5.html
================================================
<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>

================================================
FILE: onlyoffice-server/tsconfig.build.json
================================================
{
  "extends": "./tsconfig.json",
  "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}


================================================
FILE: onlyoffice-server/tsconfig.json
================================================
{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true,
    "strictNullChecks": false,
    "noImplicitAny": false,
    "strictBindCallApply": false,
    "forceConsistentCasingInFileNames": false,
    "noFallthroughCasesInSwitch": false
  }
}


================================================
FILE: onlyoffice-vue/.editorconfig
================================================
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true


================================================
FILE: onlyoffice-vue/.eslintignore
================================================
lib/
dist/


================================================
FILE: onlyoffice-vue/.eslintrc.js
================================================
module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: [
    'plugin:vue/essential',
    '@vue/standard'
  ],
  parserOptions: {
    parser: 'babel-eslint'
  },
  rules: {
    'no-unused-vars': 'warn',
    'vue/no-unused-components': 'warn',
    'no-trailing-spaces': 'warn',
    'import/newline-after-import': 'error',
    'vue/mustache-interpolation-spacing': 'warn',
    'vue/no-multi-spaces': 'warn'
  }
}


================================================
FILE: onlyoffice-vue/.gitignore
================================================
.DS_Store
node_modules
/dist
package-lock.json
yarn.lock

# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?


================================================
FILE: onlyoffice-vue/LICENSE
================================================
MIT License

Copyright (c) 2022 wytxer

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: onlyoffice-vue/README.md
================================================
# Onlyoffice Vue

基于 Vue.js 的 Onlyoffice 示例。


### 快速使用

```bash
# 安装依赖
pnpm install

# 开发
pnpm run dev

# 打包
pnpm run build
```


## License

[MIT](/LICENSE)


================================================
FILE: onlyoffice-vue/babel.config.js
================================================
module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  plugins: [
    [
      'import', {
        libraryName: 'ant-design-vue',
        libraryDirectory: 'es',
        style: true
      }
    ]
  ]
}


================================================
FILE: onlyoffice-vue/package.json
================================================
{
  "name": "onlyoffice-vue",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "dev": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "@wytxer/style-utils": "^1.0.2",
    "ant-design-vue": "^1.7.8",
    "axios": "^0.27.2",
    "core-js": "^3.6.5",
    "lodash.merge": "^4.6.2",
    "onlyoffice-vue": "^1.0.1",
    "vue": "^2.6.14",
    "vue-router": "^3.5.3",
    "vuex": "^3.6.2"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-plugin-router": "~4.5.0",
    "@vue/cli-plugin-vuex": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "@vue/eslint-config-standard": "^5.1.2",
    "babel-eslint": "^10.1.0",
    "babel-plugin-import": "^1.13.5",
    "eslint": "^6.7.2",
    "eslint-plugin-import": "^2.20.2",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-standard": "^4.0.0",
    "eslint-plugin-vue": "^6.2.2",
    "less": "^3.13.1",
    "less-loader": "^4.1.0",
    "lint-staged": "^9.5.0",
    "vue-template-compiler": "^2.6.14"
  },
  "engines": {
    "node": ">=10"
  },
  "license": "MIT",
  "homepage": "https://github.com/wytxer/demo-onlyoffice/#readme",
  "keywords": [
    "onlyoffice",
    "demo",
    "office",
    "vue",
    "nestjs",
    "docx"
  ],
  "author": {
    "name": "wytxer",
    "url": "https://github.com/wytxer"
  },
  "repository": {
    "type": "git",
    "url": "git@github.com:wytxer/demo-onlyoffice.git"
  },
  "bugs": {
    "url": "https://github.com/wytxer/demo-onlyoffice/issues"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ],
  "gitHooks": {
    "pre-commit": "lint-staged"
  },
  "lint-staged": {
    "*.{js,jsx,vue}": [
      "vue-cli-service lint",
      "git add"
    ]
  }
}


================================================
FILE: onlyoffice-vue/public/index.html
================================================
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.svg">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript><strong>请启用 JavaScript,否则将无法显示页面。</strong></noscript>
    <div id="app"></div>
  </body>
</html>


================================================
FILE: onlyoffice-vue/src/api/onlyoffice.js
================================================
import request from './request'

// Onlyoffice 文档转换接口地址
export function onlyofficeConversion (data) {
  return request({
    method: 'post',
    url: process.env.VUE_APP_ONLYOFFICE_CONVERT,
    data
  })
}

// Onlyoffice 文档构建接口地址
export function onlyofficeBuilder (data) {
  return request({
    method: 'post',
    url: process.env.VUE_APP_ONLYOFFICE_DOCBUILDER,
    data
  })
}

// 保存文档信息
export function forceSaveDocumentInfo (data) {
  return request({
    method: 'post',
    url: '/api/v1/document/forceSave',
    data
  })
}

// 获取文档信息
export function queryDocumentInfo (params) {
  return request({
    method: 'get',
    url: '/api/v1/document/documentInfo',
    params
  })
}

// 获取表格信息
export function queryExcelInfo (params) {
  return request({
    method: 'get',
    url: '/api/v1/document/excelInfo',
    params
  })
}


================================================
FILE: onlyoffice-vue/src/api/request.js
================================================
import axios from 'axios'

const request = axios.create({
  timeout: 60000,
  baseURL: ''
})

// 请求拦截器
request.interceptors.request.use(config => config)

// 响应拦截器
request.interceptors.response.use(res => res.data)

export default request


================================================
FILE: onlyoffice-vue/src/app.vue
================================================
<template>
  <a-config-provider :locale="locale">
    <div class="page-app">
      <header class="page-app__header">
      <router-link class="page-app__title" to="/">Onlyoffice</router-link>
        <span>{{ $route.meta.title }}</span>
      </header>
      <div class="page-app__body">
        <router-view />
      </div>
    </div>
  </a-config-provider>
</template>

<script>
import locale from 'ant-design-vue/lib/locale-provider/zh_CN'

export default {
  data () {
    return {
      locale
    }
  }
}
</script>

<style lang="less">
body {
  padding: 0;
  margin: 0;
}
html,
body {
  font-size: 14px;
  color: #333;
  font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
}
.page-app {
  &__header {
    padding: 16px 32px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  }
  &__title {
    font-size: 24px;
    margin-right: 16px;
    color: #333;
  }
  &__body {
    padding: 20px 32px;
  }
}
</style>


================================================
FILE: onlyoffice-vue/src/layouts/block.vue
================================================
<template>
  <router-view />
</template>

<script>
export default {
  name: 'layout-block'
}
</script>


================================================
FILE: onlyoffice-vue/src/main.js
================================================
import Vue from 'vue'
import { ConfigProvider, Button, Skeleton, message } from 'ant-design-vue'
import App from '@/app.vue'
import router from '@/router'

import '@wytxer/style-utils/lib/common.less'
import 'ant-design-vue/dist/antd.less'

Vue.use(ConfigProvider)
Vue.use(Button)
Vue.use(Skeleton)

Vue.prototype.$message = message

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')


================================================
FILE: onlyoffice-vue/src/router/index.js
================================================
import Vue from 'vue'
import VueRouter from 'vue-router'
import routes from './routes'

Vue.use(VueRouter)

const router = new VueRouter({
  base: process.env.BASE_URL,
  mode: 'history',
  routes
})

export default router


================================================
FILE: onlyoffice-vue/src/router/routes.js
================================================
import LayoutBlock from '@/layouts/block.vue'

const routes = [{
  path: '/',
  redirect: '/home'
}, {
  path: '/home',
  component: () => import(/* webpackChunkName: "home" */ '@/views/home.vue'),
  meta: {
    title: '首页'
  }
}, {
  path: '/onlyoffice',
  component: LayoutBlock,
  children: [{
    path: 'document',
    component: LayoutBlock,
    meta: {
      title: '文档编辑器'
    },
    children: [{
      path: 'quick-start',
      component: () => import(/* webpackChunkName: "document-quick-start" */ '@/views/onlyoffice/document-quick-start.vue'),
      meta: {
        title: '快速接入'
      }
    }, {
      path: 'custom-config',
      component: () => import(/* webpackChunkName: "document-custom-config" */ '@/views/onlyoffice/document-custom-config.vue'),
      meta: {
        title: '自定义 onlyoffice 配置'
      }
    }, {
      path: 'editor',
      component: () => import(/* webpackChunkName: "document-editor" */ '@/views/onlyoffice/document-editor.vue'),
      meta: {
        title: '接口获取配置项'
      }
    }, {
      path: 'editor-jwt',
      component: () => import(/* webpackChunkName: "document-editor-jwt" */ '@/views/onlyoffice/document-editor-jwt.vue'),
      meta: {
        title: '开启 JWT 加密'
      }
    }, {
      path: 'plugin',
      component: () => import(/* webpackChunkName: "document-plugin" */ '@/views/onlyoffice/document-plugin.vue'),
      meta: {
        title: '插件'
      }
    }, {
      path: 'conversion',
      component: () => import(/* webpackChunkName: "document-conversion" */ '@/views/onlyoffice/document-conversion.vue'),
      meta: {
        title: '文档转换'
      }
    }, {
      path: 'onlyoffice-vue',
      component: () => import(/* webpackChunkName: "onlyoffice-vue" */ '@/views/onlyoffice/onlyoffice-vue.vue'),
      meta: {
        title: '使用 onlyoffice-vue 组件'
      }
    }]
  }, {
    path: 'excel',
    component: LayoutBlock,
    meta: {
      title: '表格编辑器'
    },
    children: [{
      path: 'quick-start',
      component: () => import(/* webpackChunkName: "excel-quick-start" */ '@/views/onlyoffice/excel-quick-start.vue'),
      meta: {
        title: '快速接入'
      }
    }, {
      path: 'formula',
      component: () => import(/* webpackChunkName: "excel-formula" */ '@/views/onlyoffice/excel-formula.vue'),
      meta: {
        title: '自动公式'
      }
    }]
  }]
}]

export default routes


================================================
FILE: onlyoffice-vue/src/store/index.js
================================================
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {},
  modules: {}
})


================================================
FILE: onlyoffice-vue/src/views/home.vue
================================================
<template>
  <div class="page-home">
    <div v-for="item in links" :key="item.path">
      <router-link :to="item.path">{{ item.title }}</router-link>
    </div>
  </div>
</template>

<script>
export default {
  data () {
    const routes = this.$router.getRoutes() || []
    const links = routes
      .filter(item => item.meta.title && item.path && item.parent && item.parent.meta.title)
      .map(item => {
        return {
          title: `${item.parent.meta.title} - ${item.meta.title}`, path: item.path
        }
      })
    return {
      links
    }
  }
}
</script>


================================================
FILE: onlyoffice-vue/src/views/onlyoffice/document-conversion.vue
================================================
<template>
  <div class="PT24 PB24">
    <a-button type="primary" @click="onHtmlToDocx" :loading="loading.toDocx">html 转 docx</a-button>
    <a-button type="primary" class="ML16" @click="onBuilder" :loading="loading.builder">doc builder 转 docx</a-button>
  </div>
</template>

<script>
import { onlyofficeConversion, onlyofficeBuilder } from '@/api/onlyoffice'

export default {
  data () {
    return {
      loading: {
        toDocx: false,
        builder: false
      }
    }
  },
  methods: {
    // html 转 docx
    onHtmlToDocx () {
      this.loading.toDocx = true
      onlyofficeConversion({
        // 请求参数详解:https://api.onlyoffice.com/editors/conversionapi
        async: false,
        filetype: 'html',
        outputtype: 'docx',
        url: `${process.env.VUE_APP_API_SERVER}/static/test5.html`,
        key: 'test5.html'
      })
        .then(res => {
          if (res.endConvert) this.$message.success('转换成功')
          // 下载转换后的文件
          if (res.fileUrl) {
            window.open(res.fileUrl.replace(process.env.VUE_APP_PORT, process.env.VUE_APP_ONLYOFFICE_PORT))
          }
        })
        .finally(() => {
          this.loading.toDocx = false
        })
    },
    // 调用构建服务转换文档
    onBuilder () {
      this.loading.builder = true
      onlyofficeBuilder({
        // 请求参数详解:https://api.onlyoffice.com/editors/documentbuilderapi
        async: false,
        url: `${process.env.VUE_APP_API_SERVER}/static/docbuilder/test1.docbuilder`
      })
        .then(res => {
          if (res.endConvert) this.$message.success('转换成功')
          if (res.urls && res.urls['test1.docx']) {
            window.open(res.urls['test1.docx'].replace(process.env.VUE_APP_PORT, process.env.VUE_APP_ONLYOFFICE_PORT))
          }
        })
        .finally(() => {
          this.loading.builder = false
        })
    }
  }
}
</script>


================================================
FILE: onlyoffice-vue/src/views/onlyoffice/document-custom-config.vue
================================================
<template>
  <div><div :id="id"></div></div>
</template>

<script>
// 编辑器配置项,完整配置项参见:https://api.onlyoffice.com/editors/config/
const editorConfig = {
  // 编辑器宽度
  width: 1200,
  // 编辑器高度
  height: 800,
  // 编辑器类型,支持 word、cell(表格)、slide(PPT)
  documentType: 'word',
  // 文档配置
  document: {
    // 当前文档类型
    fileType: 'docx',
    // 文档标识符
    key: 'test2.docx',
    // 文档地址,绝对路径
    url: `${process.env.VUE_APP_API_SERVER}/static/test2.docx`,
    // 文档标题
    title: '测试文档二.docx',
    // 权限
    permissions: {
      // 启用评论
      comment: false,
      // 启用下载
      download: true,
      // 启用编辑
      edit: true,
      // 启用导出
      print: true,
      // 启用预览
      review: true
    }
  },
  editorConfig: {
    // 回调地址
    callbackUrl: process.env.VUE_APP_ONLYOFFICE_CALLBACK,
    // 设置语言
    lang: 'zh-CN',
    // 添加用户信息
    user: {
      group: '技术部', id: 'wytxer', name: '程序员未央'
    },
    // 模板列表
    templates: [],
    // customization 字段相关配置详解:https://api.onlyoffice.com/editors/config/editor/customization
    customization: {
      // 强制保存
      forcesave: true,
      features: {
        // 关闭拼写检查
        spellcheck: false
      }
      // ! 社区版不支持自定义 logo 字段
      // logo: {
      //   image: 'http://example.com/logo.png',
      //   imageEmbedded: 'http://example.com/logo.png',
      //   url: 'http://www.example.com'
      // }
    }
  }
}

export default {
  data () {
    return {
      id: `editor-${new Date().getTime().toString('32')}`
    }
  },
  mounted () {
    this.initEditor()
  },
  beforeDestroy () {
    // 组件销毁前销毁编辑器
    if (this.editor) {
      this.editor.destroyEditor()
      this.editor = null
    }
  },
  methods: {
    // 初始化编辑器
    initEditor () {
      const scriptId = `script-${this.id}`
      const added = document.querySelector(`#${scriptId}`)
      if (!added) {
        const script = document.createElement('script')
        script.id = scriptId
        script.src = process.env.VUE_APP_ONLYOFFICE_API_URL
        script.onload = this.createEditor
        document.head.appendChild(script)
      } else {
        this.createEditor()
      }
    },
    // 创建编辑器
    createEditor () {
      if (this.editor) {
        this.editor.destroyEditor()
        this.editor = null
      }
      this.editor = new window.DocsAPI.DocEditor(this.id, editorConfig)
    }
  }
}
</script>


================================================
FILE: onlyoffice-vue/src/views/onlyoffice/document-editor-jwt.vue
================================================
<template>
  <onlyoffice-editor :loading="loading.editor" :config="editorConfig">
    <span slot="actions">
      <a-button type="primary" @click="onSave" :loading="loading.save">保存</a-button>
    </span>
  </onlyoffice-editor>
</template>

<script>
import OnlyofficeEditor from './modules/onlyoffice-editor'
import { queryDocumentInfo, forceSaveDocumentInfo } from '@/api/onlyoffice'

export default {
  data () {
    return {
      loading: {
        editor: false,
        save: false,
        forceSave: false
      },
      detail: {},
      editorConfig: {}
    }
  },
  components: {
    OnlyofficeEditor
  },
  created () {
    this.queryDocumentInfo()
  },
  methods: {
    // 获取文档配置信息
    queryDocumentInfo () {
      this.loading.editor = true
      queryDocumentInfo({ key: 'test4.docx', useJwtEncrypt: 'y' })
        .then(res => {
          const data = res.data || {}
          const { id, remarks } = data
          this.detail = { id, remarks }
          this.editorConfig = data.editorConfig
        })
        .finally(() => {
          this.loading.editor = false
        })
    },
    // 保存
    onSave () {
      this.loading.forceSave = true
      const { key } = this.editorConfig.document
      const { id } = this.detail
      // 如果开启了 JWT 加密,useJwtEncrypt 字段要传递 y
      forceSaveDocumentInfo({ id, key, useJwtEncrypt: 'n' })
        .then(res => {
          if (res.code === 0) {
            this.$message.success('保存成功')
          } else {
            this.$message.error(res.message)
          }
        })
        .finally(() => {
          this.loading.forceSave = false
        })
    }
  }
}
</script>


================================================
FILE: onlyoffice-vue/src/views/onlyoffice/document-editor.vue
================================================
<template>
  <onlyoffice-editor :loading="loading.editor" :config="editorConfig">
    <span slot="actions">
      <a-button type="primary" @click="onSave" :loading="loading.save">保存</a-button>
    </span>
  </onlyoffice-editor>
</template>

<script>
import OnlyofficeEditor from './modules/onlyoffice-editor'
import { queryDocumentInfo, forceSaveDocumentInfo } from '@/api/onlyoffice'

export default {
  data () {
    return {
      loading: {
        editor: false,
        save: false
      },
      detail: {},
      editorConfig: {}
    }
  },
  components: {
    OnlyofficeEditor
  },
  created () {
    this.queryDocumentInfo()
  },
  methods: {
    // 获取文档配置信息
    queryDocumentInfo () {
      this.loading.editor = true
      queryDocumentInfo({ key: 'test3.docx' })
        .then(res => {
          const data = res.data || {}
          const { id, remarks } = data
          this.detail = { id, remarks }
          this.editorConfig = data.editorConfig
        })
        .finally(() => {
          this.loading.editor = false
        })
    },
    // 保存
    onSave () {
      this.loading.save = true
      const { key } = this.editorConfig.document
      const { id } = this.detail
      forceSaveDocumentInfo({ key, id })
        .then(res => {
          if (res.code === 0) {
            this.$message.success('保存成功')
          } else {
            this.$message.error(res.message)
          }
        })
        .finally(() => {
          this.loading.save = false
        })
    }
  }
}
</script>


================================================
FILE: onlyoffice-vue/src/views/onlyoffice/document-plugin.vue
================================================
<template>
  <onlyoffice-editor :loading="loading.editor" :config="editorConfig">
    <span slot="actions">
      <a-button type="primary" @click="onSave" :loading="loading.save">保存</a-button>
    </span>
  </onlyoffice-editor>
</template>

<script>
import OnlyofficeEditor from './modules/onlyoffice-editor'
import { queryDocumentInfo, forceSaveDocumentInfo } from '@/api/onlyoffice'

export default {
  data () {
    return {
      loading: {
        editor: false,
        save: false
      },
      detail: {},
      editorConfig: {}
    }
  },
  components: {
    OnlyofficeEditor
  },
  created () {
    this.queryDocumentInfo()
  },
  methods: {
    // 获取文档配置信息
    queryDocumentInfo () {
      this.loading.editor = true
      queryDocumentInfo({ key: 'test5.docx', usePlugin: 'y' })
        .then(res => {
          const data = res.data || {}
          const { id, remarks } = data
          this.detail = { id, remarks }
          this.editorConfig = data.editorConfig
        })
        .finally(() => {
          this.loading.editor = false
        })
    },
    // 保存
    onSave () {
      this.loading.save = true
      const { key } = this.editorConfig.document
      const { id } = this.detail
      forceSaveDocumentInfo({ key, id })
        .then(res => {
          if (res.code === 0) {
            this.$message.success('保存成功')
          } else {
            this.$message.error(res.message)
          }
        })
        .finally(() => {
          this.loading.save = false
        })
    }
  }
}
</script>


================================================
FILE: onlyoffice-vue/src/views/onlyoffice/document-quick-start.vue
================================================
<template>
  <div><div :id="id"></div></div>
</template>

<script>
// 编辑器配置项,完整配置项参见:https://api.onlyoffice.com/editors/config/
const editorConfig = {
  // 编辑器宽度
  width: 1200,
  // 编辑器高度
  height: 800,
  // 编辑器类型,支持 word(文档)、cell(表格)、slide(PPT)
  documentType: 'word',
  // 文档配置
  document: {
    // 文件类型
    fileType: 'docx',
    // 文档标识符
    key: 'test1.docx',
    // 文档地址,绝对路径
    url: `${process.env.VUE_APP_API_SERVER}/static/test1.docx`,
    // 文档标题
    title: '测试文档一.docx'
  }
}

export default {
  data () {
    return {
      id: `editor-${new Date().getTime().toString('32')}`
    }
  },
  mounted () {
    this.initEditor()
  },
  beforeDestroy () {
    // 组件销毁前销毁编辑器
    if (this.editor) {
      this.editor.destroyEditor()
      this.editor = null
    }
  },
  methods: {
    // 初始化编辑器
    initEditor () {
      const scriptId = `script-${this.id}`
      const added = document.querySelector(`#${scriptId}`)
      if (!added) {
        const script = document.createElement('script')
        script.id = scriptId
        script.src = process.env.VUE_APP_ONLYOFFICE_API_URL
        script.onload = this.createEditor
        document.head.appendChild(script)
      } else {
        this.createEditor()
      }
    },
    // 创建编辑器
    createEditor () {
      if (this.editor) {
        this.editor.destroyEditor()
        this.editor = null
      }
      this.editor = new window.DocsAPI.DocEditor(this.id, editorConfig)
    }
  }
}
</script>


================================================
FILE: onlyoffice-vue/src/views/onlyoffice/excel-formula.vue
================================================
<template>
  <onlyoffice-editor :loading="loading.editor" :config="editorConfig">
    <span slot="actions">
      <a-button type="primary" @click="onSave" :loading="loading.save">保存</a-button>
    </span>
  </onlyoffice-editor>
</template>

<script>
import OnlyofficeEditor from './modules/onlyoffice-editor'
import { queryExcelInfo, forceSaveDocumentInfo } from '@/api/onlyoffice'

export default {
  data () {
    return {
      loading: {
        editor: false,
        save: false
      },
      detail: {},
      editorConfig: {}
    }
  },
  components: {
    OnlyofficeEditor
  },
  created () {
    this.queryExcelInfo()
  },
  methods: {
    // 获取表格配置信息
    queryExcelInfo () {
      this.loading.editor = true
      queryExcelInfo({ key: 'test2.xlsx' })
        .then(res => {
          const data = res.data || {}
          const { id, remarks } = data
          this.detail = { id, remarks }
          this.editorConfig = data.editorConfig
        })
        .finally(() => {
          this.loading.editor = false
        })
    },
    // 保存
    onSave () {
      this.loading.save = true
      const { key } = this.editorConfig.document
      const { id } = this.detail
      forceSaveDocumentInfo({ key, id })
        .then(res => {
          if (res.code === 0) {
            this.$message.success('保存成功')
          } else {
            this.$message.error(res.message)
          }
        })
        .finally(() => {
          this.loading.save = false
        })
    }
  }
}
</script>


================================================
FILE: onlyoffice-vue/src/views/onlyoffice/excel-quick-start.vue
================================================
<template>
  <div><div :id="id"></div></div>
</template>

<script>
// 编辑器配置项,完整配置项参见:https://api.onlyoffice.com/editors/config/
const editorConfig = {
  // 编辑器宽度
  width: '100%',
  // 编辑器高度
  height: 800,
  // 编辑器类型,支持 word(文档)、cell(表格)、slide(PPT)
  documentType: 'cell',
  // 文档配置
  document: {
    // 文件类型
    fileType: 'xlsx',
    // 文档标识符
    key: 'test1.xlsx',
    // 文档地址,绝对路径
    url: `${process.env.VUE_APP_API_SERVER}/static/test1.xlsx`,
    // 文档标题
    title: '测试表格一.docx'
  },
  editorConfig: {
    // 设置语言
    lang: 'zh-CN'
  }
}

export default {
  data () {
    return {
      id: `editor-${new Date().getTime().toString('32')}`
    }
  },
  mounted () {
    this.initEditor()
  },
  beforeDestroy () {
    // 组件销毁前销毁编辑器
    if (this.editor) {
      this.editor.destroyEditor()
      this.editor = null
    }
  },
  methods: {
    // 初始化编辑器
    initEditor () {
      const scriptId = `script-${this.id}`
      const added = document.querySelector(`#${scriptId}`)
      if (!added) {
        const script = document.createElement('script')
        script.id = scriptId
        script.src = process.env.VUE_APP_ONLYOFFICE_API_URL
        script.onload = this.createEditor
        document.head.appendChild(script)
      } else {
        this.createEditor()
      }
    },
    // 创建编辑器
    createEditor () {
      if (this.editor) {
        this.editor.destroyEditor()
        this.editor = null
      }
      this.editor = new window.DocsAPI.DocEditor(this.id, editorConfig)
    }
  }
}
</script>


================================================
FILE: onlyoffice-vue/src/views/onlyoffice/modules/onlyoffice-editor.vue
================================================
<template>
  <a-skeleton v-bind="skeletonAttrs" :loading="loading">
    <div :key="id" :id="id"></div>
    <div v-if="$slots.actions" class="MT24 PB24"><slot name="actions"></slot></div>
  </a-skeleton>
</template>

<script>
import merge from 'lodash.merge'

let script
// 脚本标识
const scriptId = 'onlyoffice-editor'
// 异步加载 api.js
const loadScript = () => new Promise((resolve, reject) => {
  const src = process.env.VUE_APP_ONLYOFFICE_API_URL
  script = document.querySelector(`#${scriptId}`)
  // 加载成功
  const onLoad = () => {
    resolve()
    script.removeEventListener('load', onLoad)
  }
  // 加载失败
  const onError = () => {
    reject(new Error(`脚本 ${src} 加载失败`))
    script.removeEventListener('error', onError)
  }
  if (!script) {
    script = document.createElement('script')
    script.id = scriptId
    script.src = src
    script.addEventListener('load', onLoad)
    script.addEventListener('error', onError)
    document.head.appendChild(script)
  } else if (window.DocsAPI) {
    resolve()
  } else {
    script.addEventListener('load', onLoad)
    script.addEventListener('error', onError)
  }
})

export default {
  props: {
    config: {
      type: Object,
      default: null
    },
    loading: {
      type: Boolean,
      default: true
    }
  },
  data () {
    return {
      // 编辑器配置项,完整配置项参见:https://api.onlyoffice.com/editors/config/
      editorConfig: {
        // 编辑器宽度
        width: 1200,
        // 编辑器高度
        height: 600,
        // 编辑器类型,支持 word、cell(表格)、slide(PPT)
        documentType: 'word',
        // 文档配置
        document: {
          // 权限
          permissions: {
            // 启用评论
            comment: false,
            // 启用下载
            download: true,
            // 启用编辑
            edit: true,
            // 启用导出
            print: true,
            // 启用预览
            review: true
          }
        },
        editorConfig: {
          // 回调地址
          callbackUrl: process.env.VUE_APP_ONLYOFFICE_CALLBACK,
          // 设置语言
          lang: 'zh-CN',
          // customization 字段相关配置详解:https://api.onlyoffice.com/editors/config/editor/customization
          customization: {
            // 强制保存
            forcesave: true,
            features: {
              // 关闭拼写检查
              spellcheck: false
            }
          }
        }
      },
      id: `editor-${new Date().getTime().toString('32')}`
    }
  },
  computed: {
    skeletonAttrs () {
      return {
        active: true,
        style: { width: `${this.editorConfig.width}px` },
        paragraph: { rows: 10 }
      }
    }
  },
  watch: {
    loading (newLoading) {
      if (newLoading === false) this.initEditor()
    },
    config: {
      handler () {
        this.initEditor()
      },
      deep: true
    }
  },
  mounted () {
    this.initEditor()
  },
  beforeDestroy () {
    // 组件销毁前销毁编辑器
    if (this.editor) {
      this.editor.destroyEditor()
      this.editor = null
    }
  },
  methods: {
    // 初始化编辑器
    initEditor () {
      loadScript(this.src).then(this.createEditor)
    },
    // 创建编辑器
    createEditor () {
      if (this.editor) {
        this.editor.destroyEditor()
        this.editor = null
      }
      if (window.DocsAPI) this.editor = new window.DocsAPI.DocEditor(this.id, merge({}, this.editorConfig, this.config))
    }
  }
}
</script>


================================================
FILE: onlyoffice-vue/src/views/onlyoffice/onlyoffice-vue.vue
================================================
<template>
  <a-skeleton v-bind="skeletonAttrs" :loading="loading.page">
    <onlyoffice-editor :src="src" :config="editorConfig" @ready="onEditorReady" />
    <div class="PT24 PB24">
      <a-button type="primary" @click="onSave" :loading="loading.save">保存</a-button>
    </div>
  </a-skeleton>
</template>

<script>
import { OnlyofficeEditor } from 'onlyoffice-vue'
import { queryDocumentInfo, forceSaveDocumentInfo } from '@/api/onlyoffice'

export default {
  data () {
    return {
      loading: {
        page: false,
        save: false
      },
      src: process.env.VUE_APP_ONLYOFFICE_API_URL,
      detail: {},
      editorConfig: {}
    }
  },
  computed: {
    skeletonAttrs () {
      return {
        active: true,
        style: { width: '100%' },
        paragraph: { rows: 10 }
      }
    }
  },
  components: {
    OnlyofficeEditor
  },
  created () {
    this.queryDocumentInfo()
  },
  methods: {
    // 获取文档配置信息
    queryDocumentInfo () {
      this.loading.page = true
      queryDocumentInfo({ key: 'test5.docx' })
        .then(res => {
          const data = res.data || {}
          const { id, remarks } = data
          this.detail = { id, remarks }
          this.editorConfig = data.editorConfig
        })
        .finally(() => {
          this.loading.page = false
        })
    },
    // 保存
    onSave () {
      this.loading.save = true
      const { key } = this.editorConfig.document
      const { id } = this.detail
      forceSaveDocumentInfo({ key, id })
        .then(res => {
          if (res.code === 0) {
            this.$message.success('保存成功')
          } else {
            this.$message.error(res.message)
          }
        })
        .finally(() => {
          this.loading.save = false
        })
    },
    // 编辑器加载完毕后回调 ready 函数,editor 为当前编辑器实例
    onEditorReady (editor) {
      console.log(editor)
    }
  }
}
</script>


================================================
FILE: onlyoffice-vue/vue.config.js
================================================
/**
 * vue.config.js
 * https://cli.vuejs.org/zh/config/
 */

// 默认的接口前缀,在 .env 中设置
const apiPrefix = `^${process.env.VUE_APP_API_PREFIX}`
// Onlyoffice 接口前缀,在 .env 中设置
const onlyofficeApiPrefix = `^${process.env.VUE_APP_ONLYOFFICE_API_PREFIX}`

module.exports = {
  css: {
    loaderOptions: {
      less: {
        modifyVars: {},
        javascriptEnabled: true
      }
    }
  },
  devServer: {
    port: process.env.VUE_APP_PORT,
    proxy: {
      [apiPrefix]: {
        target: 'http://127.0.0.1:3000',
        ws: false,
        changeOrigin: true,
        logLevel: 'debug'
      },
      [onlyofficeApiPrefix]: {
        target: 'http://127.0.0.1:8701',
        ws: false,
        changeOrigin: true,
        pathRewrite: {
          [onlyofficeApiPrefix]: ''
        }
      }
    }
  },
  chainWebpack: config => {}
}
Download .txt
gitextract_6eix9r5i/

├── .gitignore
├── .vscode/
│   └── settings.json
├── LICENSE
├── README.md
├── onlyoffice-server/
│   ├── .eslintrc.js
│   ├── .gitignore
│   ├── .prettierrc
│   ├── README.md
│   ├── nest-cli.json
│   ├── package.json
│   ├── src/
│   │   ├── app.controller.ts
│   │   ├── app.module.ts
│   │   ├── document/
│   │   │   ├── document.controller.ts
│   │   │   ├── document.dto.ts
│   │   │   ├── document.entity.ts
│   │   │   ├── document.module.ts
│   │   │   └── document.service.ts
│   │   ├── main.ts
│   │   ├── onlyoffice/
│   │   │   ├── onlyoffice.controller.ts
│   │   │   ├── onlyoffice.dto.ts
│   │   │   ├── onlyoffice.entity.ts
│   │   │   ├── onlyoffice.interface.ts
│   │   │   ├── onlyoffice.module.ts
│   │   │   └── onlyoffice.service.ts
│   │   └── shared/
│   │       ├── config.ts
│   │       ├── interceptors/
│   │       │   ├── logger.interceptor.ts
│   │       │   └── response.interceptor.ts
│   │       └── shared.module.ts
│   ├── static/
│   │   ├── docbuilder/
│   │   │   └── test1.docbuilder
│   │   ├── plugins/
│   │   │   └── plugin-hello/
│   │   │       ├── config.json
│   │   │       ├── index.html
│   │   │       ├── main.js
│   │   │       └── translations/
│   │   │           └── zh-CN.json
│   │   ├── test1.docx
│   │   ├── test1.xlsx
│   │   ├── test2.docx
│   │   ├── test2.xlsx
│   │   ├── test3.docx
│   │   ├── test4.docx
│   │   ├── test5.docx
│   │   └── test5.html
│   ├── tsconfig.build.json
│   └── tsconfig.json
└── onlyoffice-vue/
    ├── .editorconfig
    ├── .eslintignore
    ├── .eslintrc.js
    ├── .gitignore
    ├── LICENSE
    ├── README.md
    ├── babel.config.js
    ├── package.json
    ├── public/
    │   └── index.html
    ├── src/
    │   ├── api/
    │   │   ├── onlyoffice.js
    │   │   └── request.js
    │   ├── app.vue
    │   ├── layouts/
    │   │   └── block.vue
    │   ├── main.js
    │   ├── router/
    │   │   ├── index.js
    │   │   └── routes.js
    │   ├── store/
    │   │   └── index.js
    │   └── views/
    │       ├── home.vue
    │       └── onlyoffice/
    │           ├── document-conversion.vue
    │           ├── document-custom-config.vue
    │           ├── document-editor-jwt.vue
    │           ├── document-editor.vue
    │           ├── document-plugin.vue
    │           ├── document-quick-start.vue
    │           ├── excel-formula.vue
    │           ├── excel-quick-start.vue
    │           ├── modules/
    │           │   └── onlyoffice-editor.vue
    │           └── onlyoffice-vue.vue
    └── vue.config.js
Download .txt
SYMBOL INDEX (58 symbols across 18 files)

FILE: onlyoffice-server/src/app.controller.ts
  class AppController (line 4) | class AppController {}

FILE: onlyoffice-server/src/app.module.ts
  class AppModule (line 31) | class AppModule {}

FILE: onlyoffice-server/src/document/document.controller.ts
  class DocumentController (line 12) | class DocumentController {
    method constructor (line 13) | constructor(private documentService: DocumentService) {}
    method forceSave (line 20) | async forceSave(@Body() body: DocumentForceSaveDto): Promise<any> {
    method documentInfo (line 29) | async documentInfo(@Query() query: DocumentInfoDto): Promise<DocumentI...
    method excelInfo (line 38) | async excelInfo(@Query() query: DocumentInfoDto): Promise<DocumentInfo> {

FILE: onlyoffice-server/src/document/document.dto.ts
  class DocumentForceSaveDto (line 6) | class DocumentForceSaveDto {
  class DocumentInfoDto (line 30) | class DocumentInfoDto {

FILE: onlyoffice-server/src/document/document.entity.ts
  class DocumentInfo (line 4) | class DocumentInfo {
  class DocumentForceSave (line 24) | class DocumentForceSave {

FILE: onlyoffice-server/src/document/document.module.ts
  class DocumentModule (line 11) | class DocumentModule {}

FILE: onlyoffice-server/src/document/document.service.ts
  class DocumentService (line 8) | class DocumentService {
    method constructor (line 9) | constructor(
    method forceSave (line 14) | async forceSave(body: DocumentForceSaveDto): Promise<DocumentForceSave> {
    method documentInfo (line 31) | async documentInfo(query: DocumentInfoDto): Promise<DocumentInfo> {
    method excelInfo (line 69) | async excelInfo(query: DocumentInfoDto): Promise<DocumentInfo> {

FILE: onlyoffice-server/src/main.ts
  function bootstrap (line 8) | async function bootstrap() {

FILE: onlyoffice-server/src/onlyoffice/onlyoffice.controller.ts
  class OnlyofficeController (line 12) | class OnlyofficeController {
    method constructor (line 13) | constructor(private onlyofficeService: OnlyofficeService) {}
    method callback (line 21) | async callback(

FILE: onlyoffice-server/src/onlyoffice/onlyoffice.dto.ts
  class IActions (line 10) | class IActions {
  class IUser (line 24) | class IUser {
  class IChange (line 39) | class IChange {
  class IHistory (line 53) | class IHistory {
  class OnlyofficeCallbackDto (line 70) | class OnlyofficeCallbackDto {
  class OnlyofficeForceSaveDto (line 155) | class OnlyofficeForceSaveDto {

FILE: onlyoffice-server/src/onlyoffice/onlyoffice.entity.ts
  class IFeatures (line 3) | class IFeatures {
  class ICustomization (line 10) | class ICustomization {
  class IPlugins (line 22) | class IPlugins {
  class IPermission (line 34) | class IPermission {
  class IDocument (line 61) | class IDocument {
  class IEditorConfig (line 88) | class IEditorConfig {
  class OnlyofficeCallback (line 120) | class OnlyofficeCallback {
  class OnlyofficeForceSave (line 128) | class OnlyofficeForceSave {
  class OnlyofficeEditorConfig (line 151) | class OnlyofficeEditorConfig {

FILE: onlyoffice-server/src/onlyoffice/onlyoffice.interface.ts
  type ErrorCode (line 1) | enum ErrorCode {
  type IOnlyofficeCommand (line 10) | interface IOnlyofficeCommand {
  type IOnlyofficeForceSave (line 14) | interface IOnlyofficeForceSave {

FILE: onlyoffice-server/src/onlyoffice/onlyoffice.module.ts
  class OnlyofficeModule (line 11) | class OnlyofficeModule {}

FILE: onlyoffice-server/src/onlyoffice/onlyoffice.service.ts
  class OnlyofficeService (line 23) | class OnlyofficeService {
    method constructor (line 24) | constructor(
    method callback (line 32) | async callback(body: OnlyofficeCallbackDto): Promise<OnlyofficeCallbac...
    method forceSave (line 59) | async forceSave(body: OnlyofficeForceSaveDto): Promise<OnlyofficeForce...
    method editorDefaultConfig (line 97) | editorDefaultConfig(): OnlyofficeEditorConfig {
    method signJwt (line 103) | signJwt(editorConfig: OnlyofficeEditorConfig): OnlyofficeEditorConfig {

FILE: onlyoffice-server/src/shared/interceptors/logger.interceptor.ts
  class LoggingInterceptor (line 13) | class LoggingInterceptor implements NestInterceptor {
    method intercept (line 16) | intercept(context: ExecutionContext, next: CallHandler): Observable<un...
    method logResponse (line 34) | private logResponse(data: unknown, context: ExecutionContext): void {

FILE: onlyoffice-server/src/shared/interceptors/response.interceptor.ts
  type Response (line 10) | interface Response<T> {
  class ResponseInterceptor (line 15) | class ResponseInterceptor<T> implements NestInterceptor<T, Response<T>> {
    method intercept (line 16) | intercept(

FILE: onlyoffice-server/src/shared/shared.module.ts
  class SharedModule (line 11) | class SharedModule {}

FILE: onlyoffice-vue/src/api/onlyoffice.js
  function onlyofficeConversion (line 4) | function onlyofficeConversion (data) {
  function onlyofficeBuilder (line 13) | function onlyofficeBuilder (data) {
  function forceSaveDocumentInfo (line 22) | function forceSaveDocumentInfo (data) {
  function queryDocumentInfo (line 31) | function queryDocumentInfo (params) {
  function queryExcelInfo (line 40) | function queryExcelInfo (params) {
Condensed preview — 72 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (85K chars).
[
  {
    "path": ".gitignore",
    "chars": 232,
    "preview": ".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"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 133,
    "preview": "{\n  \"eslint.workingDirectories\": [\n    \"./onlyoffice-server\",\n    \"./onlyoffice-vue\"\n  ],\n  \"vetur.validation.interpolat"
  },
  {
    "path": "LICENSE",
    "chars": 1063,
    "preview": "MIT License\n\nCopyright (c) 2022 wytxer\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
  },
  {
    "path": "README.md",
    "chars": 935,
    "preview": "# 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##"
  },
  {
    "path": "onlyoffice-server/.eslintrc.js",
    "chars": 665,
    "preview": "module.exports = {\n  parser: '@typescript-eslint/parser',\n  parserOptions: {\n    project: 'tsconfig.json',\n    tsconfigR"
  },
  {
    "path": "onlyoffice-server/.gitignore",
    "chars": 391,
    "preview": "# 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*\n"
  },
  {
    "path": "onlyoffice-server/.prettierrc",
    "chars": 51,
    "preview": "{\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\"\n}"
  },
  {
    "path": "onlyoffice-server/README.md",
    "chars": 159,
    "preview": "# Onlyoffice Server\n\n基于 nest 的 Onlyoffice 示例。\n\n\n## 快速使用\n\n```bash\n# 安装依赖\npnpm install\n\n# 开发\npnpm run dev\n\n# 打包\npnpm run b"
  },
  {
    "path": "onlyoffice-server/nest-cli.json",
    "chars": 326,
    "preview": "{\n  \"$schema\": \"https://json.schemastore.org/nest-cli\",\n  \"collection\": \"@nestjs/schematics\",\n  \"sourceRoot\": \"src\",\n  \""
  },
  {
    "path": "onlyoffice-server/package.json",
    "chars": 2480,
    "preview": "{\n  \"name\": \"onlyoffice-server\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"author\": \"wytxer\",\n  \"private\": true,\n  \""
  },
  {
    "path": "onlyoffice-server/src/app.controller.ts",
    "chars": 90,
    "preview": "import { Controller } from '@nestjs/common';\n\n@Controller()\nexport class AppController {}\n"
  },
  {
    "path": "onlyoffice-server/src/app.module.ts",
    "chars": 870,
    "preview": "import { Module } from '@nestjs/common';\nimport { ConfigModule } from '@nestjs/config';\nimport { ServeStaticModule } fro"
  },
  {
    "path": "onlyoffice-server/src/document/document.controller.ts",
    "chars": 1295,
    "preview": "import { Controller, Post, Get, Body, Query } from '@nestjs/common';\nimport { ApiTags, ApiOperation } from '@nestjs/swag"
  },
  {
    "path": "onlyoffice-server/src/document/document.dto.ts",
    "chars": 766,
    "preview": "import { IsString, IsNumber, IsNotEmpty, IsIn } from 'class-validator';\n\n/**\n * 文档强制保存请求参数\n */\nexport class DocumentForc"
  },
  {
    "path": "onlyoffice-server/src/document/document.entity.ts",
    "chars": 496,
    "preview": "import { IsString, IsNumber, IsObject } from 'class-validator';\nimport { OnlyofficeEditorConfig } from '../onlyoffice/on"
  },
  {
    "path": "onlyoffice-server/src/document/document.module.ts",
    "chars": 359,
    "preview": "import { Module } from '@nestjs/common';\n\nimport { DocumentController } from './document.controller';\nimport { DocumentS"
  },
  {
    "path": "onlyoffice-server/src/document/document.service.ts",
    "chars": 2703,
    "preview": "import { ConfigService } from '@nestjs/config';\nimport { Injectable, HttpStatus, HttpException } from '@nestjs/common';\n"
  },
  {
    "path": "onlyoffice-server/src/main.ts",
    "chars": 1442,
    "preview": "import { NestFactory } from '@nestjs/core';\nimport { ValidationPipe, VersioningType } from '@nestjs/common';\nimport { Sw"
  },
  {
    "path": "onlyoffice-server/src/onlyoffice/onlyoffice.controller.ts",
    "chars": 937,
    "preview": "import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';\nimport { ApiTags, ApiOperation } from '@n"
  },
  {
    "path": "onlyoffice-server/src/onlyoffice/onlyoffice.dto.ts",
    "chars": 2814,
    "preview": "import {\n  IsString,\n  IsNumber,\n  IsObject,\n  IsArray,\n  IsDefined,\n  IsIn,\n} from 'class-validator';\n\nexport class IAc"
  },
  {
    "path": "onlyoffice-server/src/onlyoffice/onlyoffice.entity.ts",
    "chars": 2329,
    "preview": "import { IsString, IsNumber, IsObject, IsIn } from 'class-validator';\n\nclass IFeatures {\n  /**\n   * 是否启用拼写检查\n   */\n  spe"
  },
  {
    "path": "onlyoffice-server/src/onlyoffice/onlyoffice.interface.ts",
    "chars": 321,
    "preview": "enum ErrorCode {\n  NoError = 0,\n  NoSecret = 1,\n  ErrorCallbackUrl = 2,\n  ServerError = 3,\n  NoChange = 4,\n  ErrorComman"
  },
  {
    "path": "onlyoffice-server/src/onlyoffice/onlyoffice.module.ts",
    "chars": 299,
    "preview": "import { Module } from '@nestjs/common';\n\nimport { OnlyofficeController } from './onlyoffice.controller';\nimport { Onlyo"
  },
  {
    "path": "onlyoffice-server/src/onlyoffice/onlyoffice.service.ts",
    "chars": 3077,
    "preview": "import { HttpService } from '@nestjs/axios';\nimport { AxiosResponse } from 'axios';\nimport { ConfigService } from '@nest"
  },
  {
    "path": "onlyoffice-server/src/shared/config.ts",
    "chars": 493,
    "preview": "import { join } from 'path';\n\nexport default () => ({\n  isTest: process.env.NODE_ENV === 'test',\n  isOnline: process.env"
  },
  {
    "path": "onlyoffice-server/src/shared/interceptors/logger.interceptor.ts",
    "chars": 1291,
    "preview": "import {\n  CallHandler,\n  ExecutionContext,\n  Injectable,\n  Logger,\n  NestInterceptor,\n} from '@nestjs/common';\nimport {"
  },
  {
    "path": "onlyoffice-server/src/shared/interceptors/response.interceptor.ts",
    "chars": 487,
    "preview": "import {\n  Injectable,\n  NestInterceptor,\n  ExecutionContext,\n  CallHandler,\n} from '@nestjs/common';\nimport { Observabl"
  },
  {
    "path": "onlyoffice-server/src/shared/shared.module.ts",
    "chars": 276,
    "preview": "import { Module, Global } from '@nestjs/common';\nimport { HttpModule } from '@nestjs/axios';\nimport { JwtModule } from '"
  },
  {
    "path": "onlyoffice-server/static/docbuilder/test1.docbuilder",
    "chars": 1789,
    "preview": "builder.CreateFile(\"docx\")\nvar oDocument = Api.GetDocument()\n\n// 添加标题\nvar oParagraph = oDocument.GetElement(0)\noParagrap"
  },
  {
    "path": "onlyoffice-server/static/plugins/plugin-hello/config.json",
    "chars": 547,
    "preview": "{\n  \"name\": \"Hello\",\n  \"guid\": \"asc.{11700c35-1fdb-4e37-9edb-b31637139601}\",\n  \"variations\": [\n    {\n      \"description\""
  },
  {
    "path": "onlyoffice-server/static/plugins/plugin-hello/index.html",
    "chars": 629,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-w"
  },
  {
    "path": "onlyoffice-server/static/plugins/plugin-hello/main.js",
    "chars": 1206,
    "preview": "/* eslint-disable */\n\n(function(window, undefined) {\n  window.Asc.plugin.init = function(initData) {\n    console.log('插件"
  },
  {
    "path": "onlyoffice-server/static/plugins/plugin-hello/translations/zh-CN.json",
    "chars": 2,
    "preview": "{}"
  },
  {
    "path": "onlyoffice-server/static/test5.html",
    "chars": 8643,
    "preview": "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /></head><body><h1 style=\"text-align:cen"
  },
  {
    "path": "onlyoffice-server/tsconfig.build.json",
    "chars": 97,
    "preview": "{\n  \"extends\": \"./tsconfig.json\",\n  \"exclude\": [\"node_modules\", \"test\", \"dist\", \"**/*spec.ts\"]\n}\n"
  },
  {
    "path": "onlyoffice-server/tsconfig.json",
    "chars": 546,
    "preview": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"declaration\": true,\n    \"removeComments\": true,\n    \"emitDecorat"
  },
  {
    "path": "onlyoffice-vue/.editorconfig",
    "chars": 126,
    "preview": "[*.{js,jsx,ts,tsx,vue}]\r\nindent_style = space\r\nindent_size = 2\r\ntrim_trailing_whitespace = true\r\ninsert_final_newline = "
  },
  {
    "path": "onlyoffice-vue/.eslintignore",
    "chars": 11,
    "preview": "lib/\ndist/\n"
  },
  {
    "path": "onlyoffice-vue/.eslintrc.js",
    "chars": 428,
    "preview": "module.exports = {\n  root: true,\n  env: {\n    node: true\n  },\n  extends: [\n    'plugin:vue/essential',\n    '@vue/standar"
  },
  {
    "path": "onlyoffice-vue/.gitignore",
    "chars": 265,
    "preview": ".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 fil"
  },
  {
    "path": "onlyoffice-vue/LICENSE",
    "chars": 1063,
    "preview": "MIT License\n\nCopyright (c) 2022 wytxer\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
  },
  {
    "path": "onlyoffice-vue/README.md",
    "chars": 181,
    "preview": "# 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#"
  },
  {
    "path": "onlyoffice-vue/babel.config.js",
    "chars": 234,
    "preview": "module.exports = {\r\n  presets: [\r\n    '@vue/cli-plugin-babel/preset'\r\n  ],\r\n  plugins: [\r\n    [\r\n      'import', {\r\n    "
  },
  {
    "path": "onlyoffice-vue/package.json",
    "chars": 1876,
    "preview": "{\n  \"name\": \"onlyoffice-vue\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"v"
  },
  {
    "path": "onlyoffice-vue/public/index.html",
    "chars": 459,
    "preview": "<!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="
  },
  {
    "path": "onlyoffice-vue/src/api/onlyoffice.js",
    "chars": 834,
    "preview": "import request from './request'\n\n// Onlyoffice 文档转换接口地址\nexport function onlyofficeConversion (data) {\n  return request({"
  },
  {
    "path": "onlyoffice-vue/src/api/request.js",
    "chars": 239,
    "preview": "import axios from 'axios'\n\nconst request = axios.create({\n  timeout: 60000,\n  baseURL: ''\n})\n\n// 请求拦截器\nrequest.intercept"
  },
  {
    "path": "onlyoffice-vue/src/app.vue",
    "chars": 1034,
    "preview": "<template>\r\n  <a-config-provider :locale=\"locale\">\r\n    <div class=\"page-app\">\r\n      <header class=\"page-app__header\">\r"
  },
  {
    "path": "onlyoffice-vue/src/layouts/block.vue",
    "chars": 112,
    "preview": "<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",
    "chars": 448,
    "preview": "import Vue from 'vue'\r\nimport { ConfigProvider, Button, Skeleton, message } from 'ant-design-vue'\r\nimport App from '@/ap"
  },
  {
    "path": "onlyoffice-vue/src/router/index.js",
    "chars": 236,
    "preview": "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 "
  },
  {
    "path": "onlyoffice-vue/src/router/routes.js",
    "chars": 2359,
    "preview": "import LayoutBlock from '@/layouts/block.vue'\n\nconst routes = [{\n  path: '/',\n  redirect: '/home'\n}, {\n  path: '/home',\n"
  },
  {
    "path": "onlyoffice-vue/src/store/index.js",
    "chars": 167,
    "preview": "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  muta"
  },
  {
    "path": "onlyoffice-vue/src/views/home.vue",
    "chars": 603,
    "preview": "<template>\r\n  <div class=\"page-home\">\r\n    <div v-for=\"item in links\" :key=\"item.path\">\r\n      <router-link :to=\"item.pa"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/document-conversion.vue",
    "chars": 1852,
    "preview": "<template>\n  <div class=\"PT24 PB24\">\n    <a-button type=\"primary\" @click=\"onHtmlToDocx\" :loading=\"loading.toDocx\">html 转"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/document-custom-config.vue",
    "chars": 2325,
    "preview": "<template>\n  <div><div :id=\"id\"></div></div>\n</template>\n\n<script>\n// 编辑器配置项,完整配置项参见:https://api.onlyoffice.com/editors/"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/document-editor-jwt.vue",
    "chars": 1634,
    "preview": "<template>\n  <onlyoffice-editor :loading=\"loading.editor\" :config=\"editorConfig\">\n    <span slot=\"actions\">\n      <a-but"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/document-editor.vue",
    "chars": 1514,
    "preview": "<template>\n  <onlyoffice-editor :loading=\"loading.editor\" :config=\"editorConfig\">\n    <span slot=\"actions\">\n      <a-but"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/document-plugin.vue",
    "chars": 1530,
    "preview": "<template>\n  <onlyoffice-editor :loading=\"loading.editor\" :config=\"editorConfig\">\n    <span slot=\"actions\">\n      <a-but"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/document-quick-start.vue",
    "chars": 1454,
    "preview": "<template>\n  <div><div :id=\"id\"></div></div>\n</template>\n\n<script>\n// 编辑器配置项,完整配置项参见:https://api.onlyoffice.com/editors/"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/excel-formula.vue",
    "chars": 1502,
    "preview": "<template>\n  <onlyoffice-editor :loading=\"loading.editor\" :config=\"editorConfig\">\n    <span slot=\"actions\">\n      <a-but"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/excel-quick-start.vue",
    "chars": 1509,
    "preview": "<template>\n  <div><div :id=\"id\"></div></div>\n</template>\n\n<script>\n// 编辑器配置项,完整配置项参见:https://api.onlyoffice.com/editors/"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/modules/onlyoffice-editor.vue",
    "chars": 3309,
    "preview": "<template>\n  <a-skeleton v-bind=\"skeletonAttrs\" :loading=\"loading\">\n    <div :key=\"id\" :id=\"id\"></div>\n    <div v-if=\"$s"
  },
  {
    "path": "onlyoffice-vue/src/views/onlyoffice/onlyoffice-vue.vue",
    "chars": 1882,
    "preview": "<template>\n  <a-skeleton v-bind=\"skeletonAttrs\" :loading=\"loading.page\">\n    <onlyoffice-editor :src=\"src\" :config=\"edit"
  },
  {
    "path": "onlyoffice-vue/vue.config.js",
    "chars": 870,
    "preview": "/**\r\n * vue.config.js\r\n * https://cli.vuejs.org/zh/config/\r\n */\r\n\r\n// 默认的接口前缀,在 .env 中设置\r\nconst apiPrefix = `^${process."
  }
]

// ... and 7 more files (download for full content)

About this extraction

This page contains the full source code of the wytxer/demo-onlyoffice GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 72 files (68.6 KB), approximately 22.1k tokens, and a symbol index with 58 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!