目录

  1. 为什么要在 Node.js 中使用 TypeScript
  2. 环境与依赖安装

  3. 编译与运行方式对比

  4. tsconfig.json 详解

  5. 项目示例:从零搭建 Node+TS

  6. 调试 TypeScript in Node.js

  7. 常见问题与解决方案
  8. 总结与最佳实践

为什么要在 Node.js 中使用 TypeScript

  1. 静态类型检查

    • TypeScript 在编译阶段就能发现常见的类型错误,避免运行时抛出“undefined is not a function”之类的错误。
  2. 更好的 IDE 支持

    • 类型提示、自动补全、重构跳转(Go To Definition)等功能,让编写 Node.js 代码更高效。
  3. 渐进式 Adoption

    • 可以增量地把 JavaScript 文件改为 .ts,配合 allowJscheckJs 选项,就能逐步引入类型定义。
  4. 面向大型项目

    • 随着项目规模增长,模块划分和接口契约更复杂,TS 的类型系统有助于维护可读性和可维护性。

环境与依赖安装

2.1 全局与项目依赖

全局安装(可选)

  • 在命令行中安装 TypeScript 编译器和 ts-node:

    npm install -g typescript ts-node
    • tsc:TypeScript 编译器
    • ts-node:可以直接在 Node.js 环境中运行 .ts 文件,无需手动编译

项目本地安装(推荐)

在项目根目录执行:

npm init -y
npm install --save-dev typescript ts-node nodemon @types/node
  • typescript:TS 编译器
  • ts-node:启动时动态编译并执行 .ts
  • nodemon:文件变化时自动重新启动
  • @types/node:Node.js 内置模块的类型定义

查看依赖:

npm list --depth=0

2.2 初始化 tsconfig.json

在项目根目录运行:

npx tsc --init

会生成一个默认的 tsconfig.json。初版内容类似:

{
  "compilerOptions": {
    "target": "ES2018",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

接下来我们会在第 4 节进行详细解读,并根据实际需求进行调整。


编译与运行方式对比

Node.js 运行 TypeScript 主要有两种思路:实时编译执行预先编译再运行。下面逐一说明优劣和示例。

3.1 直接使用 ts-node 运行

  • 优点:启动简单、无需手动编译,适合开发阶段快速迭代。
  • 缺点:启动速度稍慢、对生产环境不推荐(性能损耗),不产出纯 JavaScript 代码。

示例

假设有 src/index.ts

// src/index.ts
import http from 'http';

const PORT = process.env.PORT || 3000;

const server = http.createServer((req, res) => {
  res.end('Hello TypeScript on Node.js!');
});

server.listen(PORT, () => {
  console.log(`Server listening on http://localhost:${PORT}`);
});

package.json 中添加脚本:

{
  "scripts": {
    "dev": "ts-node src/index.ts"
  }
}

然后启动:

npm run dev

控制台输出:

Server listening on http://localhost:3000

3.2 预先编译再用 node 运行

  • 优点:可生成干净的 JS 输出,适合生产环境部署;更快启动。
  • 缺点:需要维护编译与运行之间的命令链,稍微麻烦些。

步骤

  1. tsconfig.json 中指定输出目录
    例如:

    {
      "compilerOptions": {
        "outDir": "dist",
        "rootDir": "src",
        "target": "ES2018",
        "module": "commonjs",
        "strict": true,
        "esModuleInterop": true
      }
    }
  2. 编译命令
    package.json 增加:

    {
      "scripts": {
        "build": "tsc",
        "start": "npm run build && node dist/index.js"
      }
    }
  3. 运行

    npm run start
    • tsc 会将 src/*.ts 编译到 dist/*.js
    • Node.js 执行编译后的 dist/index.js

3.3 ESModule 模式下的 TypeScript

如果想使用 ESModule (import/export) 而非 CommonJS (require),需要做以下调整:

  1. package.json 中指定:

    {
      "type": "module"
    }
  2. tsconfig.json 中设置

    {
      "compilerOptions": {
        "module": "ES2020",
        "target": "ES2020",
        "moduleResolution": "node",
        "outDir": "dist",
        "rootDir": "src",
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "skipLibCheck": true
      }
    }
  3. 文件后缀

    • 在代码里引用时,要加上文件后缀 .js(编译后是 .js)。
    • 示例:import { foo } from './utils.js';

示例 src/index.ts

import http from 'http';
import { greet } from './utils.js';

const PORT = process.env.PORT || 3000;

const server = http.createServer((req, res) => {
  res.end(greet('TypeScript'));
});

server.listen(PORT, () => {
  console.log(`Server listening on http://localhost:${PORT}`);
});

示例 src/utils.ts

export function greet(name: string): string {
  return `Hello, ${name}!`;
}

编译与运行

npm run build
node --experimental-specifier-resolution=node dist/index.js
在较新版本的 Node.js(≥16)中,通常不需要 --experimental-specifier-resolution=node,只要文件后缀正确即可。

3.4 Hot Reload:nodemonts-node-dev

开发阶段通常希望在源代码修改后自动重启服务,可选择两种常用工具:

  1. nodemon + ts-node

    • nodemon.json 配置:

      {
        "watch": ["src"],
        "ext": "ts,js,json",
        "ignore": ["dist"],
        "exec": "ts-node src/index.ts"
      }
    • 启动:npx nodemon
  2. ts-node-dev

    • 安装:npm install --save-dev ts-node-dev
    • 脚本:

      {
        "scripts": {
          "dev": "ts-node-dev --respawn --transpile-only src/index.ts"
        }
      }
    • 启动:npm run dev
    • 相比 nodemonts-node-dev 带有更快的增量重编译与内存缓存。

tsconfig.json 详解

tsconfig.json 是 TypeScript 编译器的核心配置文件,下面对常用选项进行解释,并给出完整示例。

4.1 常用编译选项示例

{
  "compilerOptions": {
    /* 指定 ECMAScript 目标版本 */
    "target": "ES2019",             // 可选 ES3, ES5, ES6/ES2015, ES2017, ES2019, ES2020...

    /* 指定模块系统 */
    "module": "commonjs",           // 可选 commonjs, es2015, es2020, esnext

    /* 输出目录与输入目录 */
    "rootDir": "src",               // 源代码根目录
    "outDir": "dist",               // 编译输出目录

    /* 开启严格模式 */
    "strict": true,                 // 严格类型检查,包含下面所有选项

    /* 各类严格检查 */
    "noImplicitAny": true,          // 禁止隐式 any
    "strictNullChecks": true,       // 严格的 null 检查
    "strictFunctionTypes": true,    // 函数参数双向协变检查
    "strictBindCallApply": true,    // 严格的 bind/call/apply 检查
    "strictPropertyInitialization": true, // 类属性初始化检查
    "noImplicitThis": true,         // 检查 this 的隐式 any
    "alwaysStrict": true,           // 禁用严格模式下的保留字(js 严格模式)

    /* 兼容性与交互 */
    "esModuleInterop": true,        // 允许默认导入非 ES 模块
    "allowSyntheticDefaultImports": true, // 允许从没有默认导出的模块中默认导入
    "moduleResolution": "node",      // 模块解析策略(node 或 classic)
    "allowJs": false,               // 若为 true,会编译 .js 文件
    "checkJs": false,               // 若为 true,检查 .js 文件中的类型

    /* SourceMap 支持,用于调试 */
    "sourceMap": true,              // 生成 .js.map 文件
    "inlineSources": true,          // 将源代码嵌入到 SourceMap

    /* 路径映射与别名 */
    "baseUrl": ".",                 // 相对路径基准
    "paths": {                      // 别名配置
      "@utils/*": ["src/utils/*"],
      "@models/*": ["src/models/*"]
    },

    /* 库文件 */
    "lib": ["ES2019", "DOM"],       // 在 TypeScript 中引入的全局类型声明文件

    /* 构建优化 */
    "incremental": true,            // 开启增量编译
    "skipLibCheck": true,           // 跳过声明文件的类型检查,加速编译
    "forceConsistentCasingInFileNames": true // 文件名大小写一致
  },
  "include": ["src"],               // 包含的文件或目录
  "exclude": ["node_modules", "dist"] // 排除的目录
}

解析

  • target:设为 ES2019 或更高可以使用现代 JS 特性(如 Object.fromEntries)。
  • module:在 CommonJS 环境下请使用 commonjs,若要输出 ES Module,改为 es2020
  • esModuleInterop:与 Babel/webpack 联动更方便,允许 import fs from 'fs' 而不是 import * as fs from 'fs'
  • sourceMap + inlineSources:用于调试,使得在 VSCode 中能准确定位到 .ts 源文件。
  • paths:结合 baseUrl 可自定义模块别名,减少相对路径导入的冗长。

4.2 Paths 与 Module Resolution

当你在代码里写:

import { helper } from '@utils/helper';

需要在 tsconfig.json 中配置:

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

这样,TypeScript 编译器在解析 @utils/helper 时会映射到 src/utils/helper.ts。运行时需要配合 module-alias 或在编译后通过构建工具(Webpack、tsc-alias)替换路径。


4.3 示意图:模块解析流程

                    import x from '@models/user'
                                │
                                ▼
                   ┌─────────────────────────┐
                   │  TypeScript 编译器解析  │
                   └─────────────────────────┘
                                │ (paths 配置)
                                ▼
             @models/user  ───>  src/models/user.ts
                                │
                                ▼
                   ┌─────────────────────────┐
                   │  输出 JavaScript 文件    │
                   │ dist/models/user.js     │
                   └─────────────────────────┘
                                │
                                ▼
                   ┌─────────────────────────┐
                   │  Node.js 加载 dist/...   │
                   └─────────────────────────┘
  • “@models/user” → 映射至 “src/models/user.ts”
  • 编译后输出至 “dist/models/user.js”,Node.js 直接加载即可

项目示例:从零搭建 Node+TS

下面演示一个完整的示例项目,从目录结构到关键代码,一步步搭建一个简单的用户认证 API。

5.1 目录结构

my-typescript-node-app/
├── src/
│   ├── config/
│   │   └── default.ts
│   ├── controllers/
│   │   └── auth.controller.ts
│   ├── services/
│   │   └── auth.service.ts
│   ├── models/
│   │   └── user.model.ts
│   ├── utils/
│   │   └── jwt.util.ts
│   ├── middleware/
│   │   └── auth.middleware.ts
│   ├── index.ts
│   └── app.ts
├── tsconfig.json
├── package.json
└── .env

5.2 关键文件详解

5.2.1 tsconfig.json

{
  "compilerOptions": {
    "target": "ES2019",
    "module": "commonjs",
    "rootDir": "src",
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true,
    "moduleResolution": "node",
    "sourceMap": true,
    "baseUrl": ".",
    "paths": {
      "@models/*": ["src/models/*"],
      "@utils/*": ["src/utils/*"]
    },
    "skipLibCheck": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

5.2.2 .env

PORT=4000
JWT_SECRET=MySuperSecretKey

5.2.3 src/config/default.ts

// src/config/default.ts
import dotenv from 'dotenv';
dotenv.config();

export default {
  port: process.env.PORT || 3000,
  jwtSecret: process.env.JWT_SECRET || 'default_secret'
};

5.2.4 src/models/user.model.ts

// src/models/user.model.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity('users')
export class User {
  @PrimaryGeneratedColumn()
  id!: number;

  @Column({ unique: true })
  username!: string;

  @Column()
  password!: string; // 已经 bcrypt hash 过

  @Column()
  email!: string;
}

5.2.5 src/utils/jwt.util.ts

// src/utils/jwt.util.ts
import jwt from 'jsonwebtoken';
import config from '../config/default';

export function signToken(payload: object): string {
  return jwt.sign(payload, config.jwtSecret, { expiresIn: '1h' });
}

export function verifyToken(token: string): any {
  return jwt.verify(token, config.jwtSecret);
}

5.2.6 src/services/auth.service.ts

// src/services/auth.service.ts
import { getRepository } from 'typeorm';
import bcrypt from 'bcrypt';
import { User } from '@models/user.model';
import { signToken } from '@utils/jwt.util';

export class AuthService {
  static async register(username: string, password: string, email: string) {
    const repo = getRepository(User);
    const existing = await repo.findOne({ where: { username } });
    if (existing) {
      throw new Error('用户名已存在');
    }
    const hash = await bcrypt.hash(password, 10);
    const user = repo.create({ username, password: hash, email });
    const saved = await repo.save(user);
    return saved;
  }

  static async login(username: string, password: string) {
    const repo = getRepository(User);
    const user = await repo.findOne({ where: { username } });
    if (!user) throw new Error('用户不存在');
    const match = await bcrypt.compare(password, user.password);
    if (!match) throw new Error('密码错误');
    const token = signToken({ id: user.id, username: user.username });
    return { token, user };
  }
}

5.2.7 src/controllers/auth.controller.ts

// src/controllers/auth.controller.ts
import { Request, Response } from 'express';
import { AuthService } from '../services/auth.service';

export class AuthController {
  static async register(req: Request, res: Response) {
    try {
      const { username, password, email } = req.body;
      const user = await AuthService.register(username, password, email);
      res.status(201).json({ success: true, data: user });
    } catch (err: any) {
      res.status(400).json({ success: false, message: err.message });
    }
  }

  static async login(req: Request, res: Response) {
    try {
      const { username, password } = req.body;
      const result = await AuthService.login(username, password);
      res.json({ success: true, data: result });
    } catch (err: any) {
      res.status(400).json({ success: false, message: err.message });
    }
  }
}

5.2.8 src/middleware/auth.middleware.ts

// src/middleware/auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { verifyToken } from '@utils/jwt.util';

export function authMiddleware(req: Request, res: Response, next: NextFunction) {
  const header = req.headers.authorization;
  if (!header) {
    return res.status(401).json({ success: false, message: '缺少令牌' });
  }
  const token = header.split(' ')[1];
  try {
    const payload = verifyToken(token);
    (req as any).user = payload;
    next();
  } catch {
    res.status(401).json({ success: false, message: '无效或过期的令牌' });
  }
}

5.2.9 src/app.ts

// src/app.ts
import express from 'express';
import 'reflect-metadata';
import { createConnection } from 'typeorm';
import config from './config/default';
import { User } from '@models/user.model';
import { AuthController } from './controllers/auth.controller';
import { authMiddleware } from './middleware/auth.middleware';

export async function createApp() {
  // 1. 初始化数据库连接
  await createConnection({
    type: 'sqlite',
    database: 'database.sqlite',
    entities: [User],
    synchronize: true,
    logging: false
  });

  // 2. 创建 Express 实例
  const app = express();
  app.use(express.json());

  // 3. 公共路由
  app.post('/register', AuthController.register);
  app.post('/login', AuthController.login);

  // 4. 受保护路由
  app.get('/profile', authMiddleware, (req, res) => {
    // (req as any).user 包含 token 中的 payload
    res.json({ success: true, data: (req as any).user });
  });

  return app;
}

5.2.10 src/index.ts

// src/index.ts
import config from './config/default';
import { createApp } from './app';

async function bootstrap() {
  const app = await createApp();
  app.listen(config.port, () => {
    console.log(`Server running at http://localhost:${config.port}`);
  });
}

bootstrap().catch((err) => {
  console.error('启动失败:', err);
});

5.3 示例业务代码运行方式

  1. 安装依赖

    npm install express typeorm sqlite3 bcrypt jsonwebtoken @types/express @types/jsonwebtoken
  2. 开发模式

    npx ts-node src/index.ts
  3. 编译后运行

    npm run build   # tsc
    node dist/index.js

测试流程:

  • 注册

    curl -X POST http://localhost:4000/register \
      -H "Content-Type: application/json" \
      -d '{"username":"alice","password":"pass123","email":"alice@example.com"}'
  • 登录

    curl -X POST http://localhost:4000/login \
      -H "Content-Type: application/json" \
      -d '{"username":"alice","password":"pass123"}'

    返回:

    {
      "success": true,
      "data": {
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
        "user": { "id":1,"username":"alice", ... }
      }
    }
  • 访问受保护接口

    curl http://localhost:4000/profile \
      -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

调试 TypeScript in Node.js

6.1 生成 Source Map

已在 tsconfig.json 中开启:

"sourceMap": true,
"inlineSources": true

编译后会在 dist/ 目录看到 .js 与对应的 .js.map。这样在调试器里就能映射到 .ts 文件。

6.2 在 VSCode 中断点调试

  1. .vscode/launch.json 添加:

    {
      "version": "0.2.0",
      "configurations": [
        {
          "type": "node",
          "request": "launch",
          "name": "Debug TS",
          "runtimeArgs": ["-r", "ts-node/register"],
          "args": ["${workspaceFolder}/src/index.ts"],
          "cwd": "${workspaceFolder}",
          "protocol": "inspector",
          "env": {
            "NODE_ENV": "development",
            "PORT": "4000"
          },
          "sourceMaps": true,
          "console": "integratedTerminal"
        }
      ]
    }
  2. 设置断点

    • src/ 目录下打开任何 .ts 文件,点击行号左侧即可设置断点。
    • 在 Debug 面板选择 “Debug TS” 并启动,代码会在 TS 源文件层面断点。

常见问题与解决方案

  1. Cannot use import statement outside a module

    • 检查 package.json 是否包含 "type": "module" 或者将 tsconfig.jsonmodule 改为 commonjs
  2. 模块解析失败 (Cannot find module '@models/user.model')

    • 确认 tsconfig.jsonpathsbaseUrl 配置正确,并在编译后使用 tsconfig-pathstsc-alias
  3. Property 'foo' does not exist on type 'Request'

    • 需要扩展类型定义,例如:

      // src/types/express.d.ts
      import { Request } from 'express';
      
      declare module 'express-serve-static-core' {
        interface Request {
          user?: any;
        }
      }

      并在 tsconfig.jsoninclude 加入 src/types/**/*.ts

  4. ts-node 性能慢

    • 可以加上 --transpile-only 跳过类型检查:

      ts-node --transpile-only src/index.ts
    • 或使用 ts-node-dev

      npx ts-node-dev --respawn --transpile-only src/index.ts
  5. 生产环境如何部署 TS 项目

    • 一般先运行 npm run buildtsc),再启动编译后的 dist/index.js;避免在生产环境使用 ts-node,因为它没有预编译,性能较差,也不利于故障排查。

总结与最佳实践

  1. 增量迁移

    • 如果已有纯 JS 项目,可在 tsconfig.json 中开启 allowJscheckJs,逐步将 .js 改为 .ts
  2. 严格模式

    • 开启 strict,配置更自由和安全,有助于在编译时捕获更多潜在错误。
  3. 模块别名

    • 配合 paths 与对应的运行时替换工具(tsconfig-pathsmodule-alias),避免相对路径过于冗长。
  4. 分层结构

    • 将业务逻辑分为 controllersservicesmodels,中间件与工具代码放在独立目录,提高可维护性。
  5. 调试与日志

    • 开启 sourceMap,在开发环境使用 VSCode 或 Chrome DevTools 调试。
    • 引入 winstonpino 等日志库,并根据 NODE\_ENV 切换不同级别输出。
  6. 编译产物管理

    • .gitignore 中忽略 dist/node_modules/
    • 定期清理 dist/,执行 tsc --build --clean

通过以上配置与示例,你可以轻松在 Node.js 中运行 TypeScript 代码,从开发到生产部署都能保障类型安全与高效。

2024-08-27

在TypeScript中,类型断言提供了一种明确告诉编译器变量的类型的方法。你可以使用 as 关键字或者 <类型> 的形式来进行类型断言。

例如,假设你有一个 value 变量,它可能是 string 类型也可能是 number 类型。你可以在知道它是 string 类型的时候进行类型断言:




let value: string | number;
 
// 使用 as 关键字断言
let strValue1 = (value as string).toUpperCase();
 
// 使用 <> 形式断言
let strValue2 = (<string>value).toUpperCase();

请注意,类型断言并不会改变运行时的行为,它只是提供给TypeScript编译器一个类型信息。如果你断言了一个变量是一个不正确的类型,在运行时可能会抛出错误。因此,使用类型断言时需要确保断言的类型是正确的。

2024-08-27

在Cocos Creator中封装一个对象池,可以通过以下步骤实现:

  1. 创建一个对象池类,包含对象数组和对象的创建与回收方法。
  2. 初始化对象池,设置对象的创建函数。
  3. 实现get方法来获取池中的对象,如果池中没有可用对象,则创建一个新的对象。
  4. 实现put方法将对象放回池中,可以进行复位处理,例如重置节点的位置和状态。

以下是一个简单的对象池封装示例:




cc.Class({
    extends: cc.Component,
    
    // 初始化对象池
    initPool: function(classConstructor, initCount) {
        this.classConstructor = classConstructor;
        this.pool = [];
        for (let i = 0; i < initCount; i++) {
            let obj = new classConstructor();
            this.pool.push(obj);
        }
    },
    
    // 获取对象
    get: function() {
        if (this.pool.length === 0) {
            return new this.classConstructor();
        }
        return this.pool.pop();
    },
    
    // 回收对象
    put: function(obj) {
        if (obj instanceof this.classConstructor) {
            // 这里可以添加复位对象状态的代码
            // obj.reset();
            this.pool.push(obj);
        }
    }
});

使用对象池时,首先需要实例化这个类,并调用initPool方法来设置对象的类型和初始化数量。使用get方法获取对象,使用put方法回收对象。




let objectPool = new ObjectPool();
objectPool.initPool(MyClass, 10); // MyClass是对象的类
 
let obj = objectPool.get();
// ... 使用obj
objectPool.put(obj); // 使用完毕后回收

这样就实现了一个简单的对象池封装,可以有效管理和复用游戏中的对象,减少频繁的实例化与销毁,提高性能。

2024-08-27

以下是一个简化的代码示例,展示了如何在Vue 3和TypeScript中结合ECharts绘制一个射线图的中国地图,并添加了轮播tooltip功能。




<template>
  <div ref="mapChart" style="width: 600px; height: 400px;"></div>
</template>
 
<script lang="ts">
import { defineComponent, ref, onMounted, watch } from 'vue';
import * as echarts from 'echarts';
 
export default defineComponent({
  name: 'MapChart',
  setup() {
    const mapChart = ref<HTMLElement | null>(null);
    let chartInstance: echarts.ECharts | null = null;
 
    onMounted(() => {
      chartInstance = echarts.init(mapChart.value as HTMLElement);
      const option = {
        tooltip: {
          trigger: 'item',
          formatter: '{b}',
          axisPointer: {
            type: 'line',
            lineStyle: {
              color: 'rgba(0,0,0,0.2)',
              width: 1,
              type: 'solid'
            }
          }
        },
        geo: {
          map: 'china',
          roam: true,
          label: {
            emphasis: {
              show: true
            }
          },
          itemStyle: {
            normal: {
              areaColor: '#323c48',
              borderColor: '#111'
            },
            emphasis: {
              areaColor: '#2a333d'
            }
          }
        },
        series: [
          {
            type: 'lines',
            coordinateSystem: 'geo',
            data: [
              {
                coords: [
                  [116.405285, 39.904989], // 起点经纬度
                  [121.472644, 31.231706]  // 终点经纬度
                ],
                name: 'Line 1',
                value: 0
              }
            ],
            lineStyle: {
              width: 1,
              opacity: 1,
              color: 'rgb(223,0,0)'
            },
            effect: {
              show: true,
              period: 6,
              trailLength: 0.7,
              color: 'rgb(223,0,0)',
              symbolSize: 3
            }
          }
        ]
      };
 
      chartInstance?.setOption(option);
    });
 
    watch(() => chartInstance, (newInstance) => {
      if (newInstance) {
        setInterval(() => {
          const dataLen = newInstance.getOption().series[0].data.length;
          // 假设我们有一个动态的数组来更新tooltip信息
          const tooltips = ['Info 1', 'Info 2', 'Info 3'];
          const currentTooltip = tooltips[(dataLen - 1) % tooltips.length];
 
          newInstance.setOption({
            series: [
2024-08-27



import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { Ammo } from './ammo-es.js';
 
// 设置场景、相机和渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
 
// 创建物理世界
const collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration();
const dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
const broadphase = new Ammo.btDbvtBroadphase();
const solver = new Ammo.btSequentialImpulseConstraintSolver();
const softBodySolver = new Ammo.btDefaultSoftBodySolver();
const world = new Ammo.btSoftRigidDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration, softBodySolver);
world.setGravity(new Ammo.btVector3(0, -9.81, 0));
 
// 创建一个球体并添加物理属性
const sphereGeometry = new THREE.SphereGeometry(1, 20, 20);
const sphereMaterial = new THREE.MeshPhongMaterial({ color: 0xffffff });
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
const sphereMass = 10;
const sphereShape = new Ammo.btSphereShape(1);
const sphereLocalInertia = new Ammo.btVector3(0, 0, 0);
sphereShape.calculateLocalInertia(sphereMass, sphereLocalInertia);
const sphereMotionState = new Ammo.btDefaultMotionState(
  new Ammo.btTransform(new Ammo.btQuaternion(0, 0, 0, 1), new Ammo.btVector3(0, 10, 0))
);
const sphereRigidBody = new Ammo.btRigidBody(
  new Ammo.btRigidBodyConstructionInfo(sphereMass, sphereMotionState, sphereShape, sphereLocalInertia)
);
sphereRigidBody.setRestitution(1); // 弹性
world.addRigidBody(sphereRigidBody);
 
// 创建投篮棒并添加物理属性
const cylinderGeometry = new THREE.CylinderGeometry(0.5, 0.5, 2, 32);
const cylinderMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
const cylinder = new THREE.Mesh(cylinderGeometry, cylinderMaterial);
const cylinderMass = 0.1;
const cylinderShape = new Ammo.btCylinderShape(new Ammo.btVector3(1, 1, 1));
const cylinderLocalInertia = new Ammo.btVector3(0, 0, 0);
cylinderShape.calculateLocalInertia(cylinderMass, cylinderLocalInertia);
const cylinderMotionState = new Ammo.btDefaultMotionState(
  new Ammo.btTransform(
    new Ammo.btQuaternion(0, 0, 0, 1),
    new Ammo.btVector3(5, 10, 0)
  )
);
const cylinderRigidBody = new
2024-08-27

在Vue项目中使用TypeScript需要以下步骤:

  1. 确保你的项目已经支持TypeScript。如果还没有安装typescript,可以通过npm或yarn安装:

    
    
    
    npm install -g typescript
  2. 在项目中安装TypeScript支持:

    
    
    
    npm install --save-dev typescript
  3. 创建一个tsconfig.json文件,该文件定义了TypeScript编译选项:

    
    
    
    npx tsc --init
  4. 安装vue类型定义文件和vue-class-component装饰器支持:

    
    
    
    npm install --save-dev @vue/cli-plugin-typescript @vue/cli-plugin-babel
    npm install --save-dev vue-class-component
  5. 修改vue项目中的<script>标签,使其可以支持TypeScript:

    
    
    
    <script lang="ts">
    import Vue from 'vue';
    export default Vue.extend({
      // Options
    });
    </script>
  6. <script>标签中编写TypeScript代码。

以下是一个简单的Vue组件示例,使用TypeScript编写:




<template>
  <div>{{ message }}</div>
</template>
 
<script lang="ts">
import Vue from 'vue';
 
export default Vue.extend({
  data() {
    return {
      message: 'Hello, Vue with TypeScript!'
    };
  }
});
</script>
 
<style scoped>
div {
  color: blue;
}
</style>

这个组件在<template>中显示一条消息,并在<script>标签中使用TypeScript编写。当你在Vue CLI创建的项目中这样配置后,就可以使用TypeScript来编写Vue应用了。

2024-08-27

"DEJA\_VU3D - Cesium功能集 之 106-鹰眼地图" 这个问题似乎是指在Cesium中集成3D视觉飞行的功能,或者是一个特定的地图服务。Cesium是一个用于世界级3D地图的开源库,可以用于创建交互式的Web地图。

要在Cesium中集成3D视觉飞行的功能,你可以使用Cesium的Viewer类,并添加一个实体(Entity),该实体可以是一个模拟的飞机或其他飞行物,然后通过设置实体的属性,比如位置、速度、方向等,来模拟飞行。

下面是一个简单的示例代码,展示如何在Cesium中添加一个飞行的实体:




// 首先,确保你已经在HTML中引入了Cesium.js库
 
// 创建Cesium Viewer实例
const viewer = new Cesium.Viewer('cesiumContainer');
 
// 定义飞行路线(这里只是一个简单的路线,实际应用中可能需要更复杂的路线)
const property = new Cesium.SampledPositionProperty();
property.addSample(Cesium.JulianDate.now(), Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883));
property.addSample(Cesium.JulianDate.addSeconds(Cesium.JulianDate.now(), 3600, new Cesium.JulianDate()), Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883));
 
// 创建飞行的实体
const entity = viewer.entities.add({
    position: property,
    model: {
        uri: 'path/to/your/model/file.gltf', // 飞行模型的路径
        minimumPixelSize: 128,
        maximumScale: 20000
    },
    path: {
        resolution: 1,
        material: new Cesium.PolylineGlowMaterialProperty({
            color: Cesium.Color.WHITE,
            power: 0.15,
            threshold: 0.5
        }),
        width: 10
    }
});
 
// 如果需要,可以添加更多的属性来模拟飞行,例如速度、旋转等

在这个例子中,我们创建了一个实体,并给它设置了一个路径,路径将会显示为一个发光的蓝色线条跟随实体。实体的模型是一个从.gltf文件中加载的模型,这个模型应该是一个飞机或者其他可以飞行的模型。

请注意,这只是一个简化的示例,实际的应用可能需要更复杂的代码来处理动画、路径计算、用户交互等。此外,确保你有适当的飞行模型的权限和访问路径,以及适当的Cesium.js库和WebGL支持。

2024-08-27

在Vue中使用Element UI的<el-table>组件时,可以通过highlight-current-row属性来启用行高亮,并且使用@current-change事件来获取行数据。下面是一个简单的例子:




<template>
  <el-table
    :data="tableData"
    highlight-current-row
    @current-change="handleCurrentChange"
  >
    <el-table-column
      prop="date"
      label="日期"
      width="180">
    </el-table-column>
    <el-table-column
      prop="name"
      label="姓名"
      width="180">
    </el-table-column>
    <el-table-column
      prop="address"
      label="地址">
    </el-table-column>
  </el-table>
</template>
 
<script>
export default {
  data() {
    return {
      tableData: [{
        date: '2016-05-02',
        name: '王小虎',
        address: '上海市普陀区金沙江路 1518 弄'
      }, {
        date: '2016-05-04',
        name: '李小虎',
        address: '上海市普陀区金沙江路 1517 弄'
      }, {
        date: '2016-05-01',
        name: '赵小虎',
        address: '上海市普陀区金沙江路 1519 弄'
      }, {
        date: '2016-05-03',
        name: '孙小虎',
        address: '上海市普陀区金沙江路 1516 弄'
      }]
    };
  },
  methods: {
    handleCurrentChange(val) {
      if (val) {
        console.log('选中的行数据:', val);
      } else {
        console.log('取消选中');
      }
    }
  }
};
</script>

在这个例子中,highlight-current-row属性使得当前选中的行有高亮效果。@current-change事件在选中的行数据发生变化时触发,并且会将新选中的行数据作为参数传递给handleCurrentChange方法。如果用户取消选中,则传递给方法的参数为null

2024-08-27

在Vue 3中使用Less并且想要使用:deep选择器来穿透组件边界时,可能会遇到:deep不生效的问题。这可能是由于以下原因造成的:

  1. 确保你正确地在Vue 3项目中使用了Scoped CSS。在单文件组件(.vue文件)中,<style>标签默认是Scoped的。
  2. 确保你使用了正确的语法来指定:deep选择器。在Vue 3中,:deep选择器的语法是::v-deep或者>>>
  3. 如果你在JavaScript中动态创建了样式,确保你使用了正确的选择器。

下面是一个简单的例子,展示如何在Vue 3组件中使用:deep选择器:




<template>
  <div class="parent">
    <child-component>
      <!-- 使用 :deep 选择器来选中 child-component 内的 .inner 元素 -->
      <:deep(.inner)> {
        color: red;
      }
    </child-component>
  </div>
</template>
 
<script>
import ChildComponent from './ChildComponent.vue';
 
export default {
  components: {
    ChildComponent
  }
};
</script>
 
<style scoped>
.parent {
  :deep(.inner) {
    color: blue;
  }
}
</style>

在这个例子中,ChildComponent可能有一个类名为.inner的元素。在父组件中,我们使用:deep(.inner)选择器来确保我们的样式能够应用到子组件内部的.inner元素上,无论它是否是Scoped的。

如果你在使用Less并且:deep不生效,请检查以上几点,并确保你的Vue 3项目配置正确。如果问题依然存在,可以查看官方文档或者社区支持来获取帮助。