Compare commits

106 Commits

Author SHA1 Message Date
a6a6198eaa fix(优化) 2026-08-26 14:01:04 +08:00
zxr
9fbfe54f92 Merge branch 'main' of https://git.apinb.com/ops/front 2026-08-24 22:06:19 +08:00
zxr
fb9c5b9255 fix 2026-08-24 22:04:01 +08:00
73247053dd fix(优化) 2026-08-24 22:02:55 +08:00
zxr
695755546f fix: 机柜图形化 2026-08-20 18:06:05 +08:00
zxr
43ce3433ce fix: 机柜使用率浮点精度 2026-08-19 19:27:12 +08:00
zxr
3e60e699ce fix 2026-08-18 11:11:46 +08:00
zxr
5cbfb36fbf fix 2026-08-17 18:28:58 +08:00
zxr
87ec4dff70 fix 2026-08-10 20:55:56 +08:00
zxr
46559fe955 docs: 清理旧文档 2026-08-07 17:36:24 +08:00
zxr
c550369a4b Merge branch 'feature/report-pdf' 2026-08-04 14:23:26 +08:00
zxr
a9c2a499ce feat: 接入资产文件上传服务 2026-08-04 01:02:40 +08:00
zxr
24dd0c17da fix: 修复历史报表交互状态 2026-08-04 01:02:17 +08:00
zxr
913e9e63ad fix: 卸载时终止历史报告状态提交 2026-08-03 22:14:19 +08:00
zxr
3abdc187fb fix: 隔离历史报告列表请求状态 2026-08-03 22:06:35 +08:00
zxr
809dc883f5 feat: 建立历史报告档案中心 2026-08-03 21:52:48 +08:00
zxr
7c505f8169 fix: 补齐报表文件名安全边界 2026-08-03 21:42:30 +08:00
zxr
3654ccb9db fix: 安全处理报表下载文件名 2026-08-03 21:30:36 +08:00
zxr
195aa7ccff feat: 增加报表 PDF 下载 2026-08-03 21:13:07 +08:00
zxr
7353c4eede fix: 资产相关页面微调 2026-08-01 16:24:52 +08:00
zxr
dbda9f76fb refactor: 统一知识库接口公共类型 2026-07-22 04:55:36 +08:00
zxr
c2c5afa82d fix: 避免文档收藏响应串写 2026-07-22 04:19:54 +08:00
zxr
f5c7ebc59c fix: 收紧知识库页面交互状态 2026-07-22 04:01:25 +08:00
zxr
98e1baf1bb feat: 整合知识库管理页面 2026-07-22 03:26:22 +08:00
zxr
51f938fbfc fix: 防止 FAQ 表单重复提交 2026-07-22 02:54:26 +08:00
zxr
e43ada5c7f fix: 修正 FAQ 表单与分类交互 2026-07-22 02:46:50 +08:00
zxr
f2f4390ac2 feat: 增加知识库 FAQ 管理页面 2026-07-22 02:30:50 +08:00
zxr
52a0346c4c fix: 统一前端菜单类型判断 2026-07-22 00:00:19 +08:00
zxr
4b9a87638c feat: 兼容前端按钮权限数据 2026-07-21 23:51:18 +08:00
zxr
b4dba77229 fix: preserve alert configuration intent 2026-07-21 22:15:59 +08:00
zxr
7e61f330dc fix: restrict dynamic rule operators 2026-07-21 21:21:47 +08:00
zxr
8c1772c784 fix: guard alert policy user response types 2026-07-21 16:18:26 +08:00
zxr
c9da54829d fix: 移除告警客户端身份字段 2026-07-21 08:33:33 +08:00
zxr
db767de8bb feat: expose alert suppression configuration 2026-07-21 04:58:48 +08:00
zxr
9cbacf5998 feat: configure static and dynamic alert rules 2026-07-20 21:59:57 +08:00
zxr
947fa2f9b9 fix: 消息展示修正 2026-07-15 13:06:09 +08:00
zxr
e5df404aaa fix: 标签栏图标修正;概览页展示数据切换;消息通知异常显示修复 2026-07-15 12:40:38 +08:00
zxr
8a6ec3ef96 fix: 更换logo及oid自定义弹窗修改 2026-07-14 10:13:20 +08:00
zxr
922f7dfc4b Merge branch 'feat/snmp-oid-table-editor' 2026-07-05 23:30:41 +08:00
zxr
ee101984f0 refactor: reuse snmp oid editor in storage form 2026-07-05 23:06:18 +08:00
zxr
3f4d50b395 refactor: reuse snmp oid editor in security form 2026-07-05 23:04:21 +08:00
zxr
58365870ef refactor: reuse snmp oid editor in device collect form 2026-07-05 23:02:09 +08:00
zxr
aa6db97457 fix: validate snmp oid json field types 2026-07-05 22:18:52 +08:00
zxr
f5d0bae89b feat: add shared snmp oid editor 2026-07-05 22:13:39 +08:00
zxr
cc6ea1acc6 fix snmp链路测试 2026-07-05 21:21:53 +08:00
zxr
7a20329594 fix: validate advanced snmp oid json rows 2026-07-05 18:35:01 +08:00
zxr
83b713890b fix: preserve advanced snmp oid json fields 2026-07-05 18:31:47 +08:00
zxr
c3f38f5e2a fix: preserve snmp oid validation row numbers 2026-07-05 18:22:25 +08:00
zxr
c6d0df6fd2 fix: normalize blank snmp oid json 2026-07-05 18:18:47 +08:00
zxr
93e8f3349f feat: validate snmp oid table rows 2026-07-05 18:15:45 +08:00
zxr
fc5d653433 feat: add snmp oid table editor ui 2026-07-05 18:07:51 +08:00
zxr
fafdb0d830 feat: initialize snmp oid editor state 2026-07-05 18:00:17 +08:00
zxr
99a5f8bd16 fix: trim parsed snmp oid rows 2026-07-05 17:54:23 +08:00
zxr
9f1c3eb660 feat: add snmp oid editor state helpers 2026-07-05 17:48:49 +08:00
zxr
1ad0323715 fix 大屏管理 2026-07-03 15:48:23 +08:00
zxr
b01a0f0979 fix 报表数据无法查看与下载 2026-07-03 11:09:16 +08:00
zxr
8f3dd3e43e 新增 业务拓扑相关 2026-06-29 21:47:46 +08:00
zxr
2107f77081 修复 : 数据为空的时候,创建按钮不显示 2026-06-29 10:42:36 +08:00
zxr
fcaad4b3ae 修复 空数据时页面组件不显示 2026-06-29 10:09:28 +08:00
zxr
cd5e8b5f2d 任务执行1-19 2026-06-26 12:51:19 +08:00
zxr
18531bfcac fix 2026-06-24 15:12:18 +08:00
zxr
d104dd8df9 fix 2026-05-02 17:21:08 +08:00
zxr
bd61f722a2 Merge branch 'main' of https://git.apinb.com/ops/front 2026-05-02 17:20:20 +08:00
zxr
27e1f335a6 fix 2026-05-02 17:08:10 +08:00
026cff306a feat 2026-05-02 09:59:29 +08:00
1af4075e9b feat 2026-05-02 09:59:06 +08:00
zxr
ea3e60c17c fix 2026-04-30 18:55:32 +08:00
zxr
ea9531313e fix 2026-04-28 17:24:36 +08:00
zxr
f7b34c5d60 fix 2026-04-28 17:22:06 +08:00
zxr
39150be726 Merge branch 'main' of https://git.apinb.com/ops/front 2026-04-27 19:26:48 +08:00
zxr
25e8eafc69 fix 2026-04-27 19:26:44 +08:00
e569571462 feat 2026-04-19 21:44:17 +08:00
7f209b5fef feat 2026-04-19 20:57:48 +08:00
zxr
d9a0470ecf fix 2026-04-19 14:24:03 +08:00
zxr
39d3254b8d Merge branch 'main' of https://git.apinb.com/ops/front 2026-04-18 16:26:28 +08:00
zxr
0a12f54132 fix 2026-04-18 16:26:24 +08:00
9afaa4b775 feat 2026-04-18 15:07:05 +08:00
878cbea9a5 feat 2026-04-18 14:23:06 +08:00
d5df671ff6 feat 2026-04-16 22:38:21 +08:00
ba933457d4 feat 2026-04-16 22:18:26 +08:00
zxr
01139f2874 fix 2026-04-16 18:59:33 +08:00
zxr
cb8bd05ff7 fix 2026-04-15 21:41:47 +08:00
zxr
55170bceb0 fix 2026-04-14 17:42:31 +08:00
zxr
8db158c390 fix 2026-04-14 16:17:31 +08:00
b8a7ba1cbb feat 2026-04-13 23:27:09 +08:00
e84cb75dda feat 2026-04-13 23:18:15 +08:00
zxr
f030f9c5c9 fix 2026-04-13 20:57:41 +08:00
zxr
003c552238 fix 2026-04-12 16:40:33 +08:00
zxr
1dcab7af96 fix 2026-04-12 15:01:46 +08:00
5f4111aeb1 feat 2026-04-11 22:22:07 +08:00
c72af0bfa7 feat 2026-04-11 21:23:10 +08:00
68a320b6c2 Merge branch 'main' of https://git.apinb.com/ops/front 2026-04-11 21:08:39 +08:00
a0ca86d98d feat 2026-04-11 21:08:34 +08:00
zxr
dc31084d87 Merge branch 'main' of https://git.apinb.com/ops/front 2026-04-11 20:03:10 +08:00
zxr
8b3bd98e68 fix 2026-04-11 20:03:06 +08:00
af7c652ed4 feat 2026-04-11 13:54:34 +08:00
ac74bc20ec feat 2026-04-09 22:14:39 +08:00
zxr
70a7d89b4f fix 2026-04-09 19:58:45 +08:00
zxr
86bf1dd9b3 fix 2026-04-09 13:52:10 +08:00
2ceb18cbee feat 2026-04-09 00:02:42 +08:00
ac759da246 feat 2026-04-08 23:14:40 +08:00
720c6fbe74 feat 2026-04-07 21:52:39 +08:00
ea07322257 feat 2026-04-07 21:35:33 +08:00
33d7460ea0 feat 2026-04-05 16:14:23 +08:00
9936b2b92c Merge branch 'main' of https://git.apinb.com/ops/front 2026-04-05 15:51:39 +08:00
13dda2dc6d feat 2026-04-05 15:47:06 +08:00
467 changed files with 3020084 additions and 23341 deletions

View File

@@ -6,11 +6,18 @@ VITE_APP_WORKSPACE=ops
# 应用标题
VITE_APP_TITLE=标准管理系统
VITE_APP_DESCRIPTION="default standard template"
VITE_USE_MOCK=false
# API 基础URL
VITE_API_BASE_URL=https://ops-api.apinb.com
# 开发环境通过 Vite 同源代理访问 API避免浏览器跨域
VITE_API_BASE_URL=/api
VITE_API_PROXY_TARGET=https://ops-api.apinb.com
# 海康 WebSDK 开发代理(按实际摄像头/NVR 地址填写,不要提交账号密码)
VITE_HIKVISION_PROXY_TARGET=http://192.168.1.101:80
VITE_HIKVISION_WS_PROXY_TARGET=
# Logs 本地调试地址(仅 logs 模块使用)
VITE_LOGS_API_BASE_URL=http://127.0.0.1:12440
# 应用版本
VITE_APP_VERSION=1.0.0

View File

@@ -5,6 +5,7 @@ VITE_APP_WORKSPACE=ops
VITE_APP_TITLE=标准管理系统
VITE_APP_DESCRIPTION="default standard template"
VITE_USE_MOCK=false
# API 基础URL
VITE_API_BASE_URL=https://ops-api.apinb.com

133
.kilo/README.md Normal file
View File

@@ -0,0 +1,133 @@
# Kilo Configuration for Vue Admin Project
This directory contains the Kilo AI assistant configuration for this Vue 3 + Arco Design admin project.
## Directory Structure
```
.kilo/
├── agent/ # Agent mode rules
│ ├── rules-code.md # Code writing rules
│ ├── rules-architect.md # Architecture rules
│ ├── rules-debug.md # Debugging rules
│ └── rules-ask.md # Q&A rules
├── command/ # Slash commands
│ ├── new-page.md # Create new page module
│ ├── new-api.md # Create new API module
│ ├── new-component.md # Create new component
│ ├── lint-fix.md # Fix lint issues
│ └── type-check.md # Check TypeScript types
├── skill/ # Skill definitions
│ └── vue-admin-dev.md # Vue admin development skill
├── templates/ # Code templates
│ ├── api-module.ts # API module template
│ ├── columns.ts # Table columns template
│ ├── search-form.ts # Search form template
│ ├── index.vue # Main page template
│ ├── FormDialog.vue # Form dialog template
│ └── Detail.vue # Detail view template
├── logs/ # Log files
└── node_modules/ # Dependencies
```
## Configuration Files
### kilo.json
Main configuration file with:
- Agent settings (default mode, allowed modes)
- Command directory path
- Rules directory path
- Skills directory path
- Hooks (beforeFileWrite, afterTaskComplete)
- Context settings (maxTokens, include/exclude patterns)
- Logging configuration
### AGENTS.md
Project-level guidance file in root directory with:
- Build commands
- Architecture notes
- Module structure patterns
- Code style requirements
- Import order
- Debug tips
## Usage
### Commands
```
/new-page <module-name> <category> # Create new page module
/new-api <module-name> [base-path] # Create new API module
/new-component <name> [type] # Create new component
/lint-fix [path] # Fix lint issues
/type-check # Check TypeScript types
```
### Skills
Load the vue-admin-dev skill for comprehensive development guidance:
```
/skill vue-admin-dev
```
## Key Rules
### Storage
Always use `SafeStorage` with `AppStorageKey` enum - never use localStorage directly.
### API Choice
- Use `request.ts` for workspace-aware APIs (most ops APIs)
- Use `interceptor.ts` for Bearer token APIs expecting `code: 20000`
### API Response Format
Standard response from `request.ts`: `{code: 0, details: {data: [...], total: number}}`
- Success code is `0`, NOT `200` or `20000`
- Access data via `response.details.data`
- Access total via `response.details.total`
### Comments
Add JSDoc comments for interfaces and functions:
```ts
/** 服务器类型 */
export interface ServerItem { ... }
/** 获取服务器列表(分页) */
export const fetchServerList = (params?: ServerListParams) => { ... }
```
### useRequest Hook
Does NOT work in async functions. Use `.bind(null, params)` pattern in setup scope.
### Dynamic Routes
Routes loaded from server via permission guard. Don't modify `isMenuLoading`/`isMenuLoaded` flags.
### Code Style
- No semicolons
- Single quotes
- Print width: 140 characters
- JSDoc comments for types and functions
## Templates
Templates use placeholder syntax:
- `{{Module}}` - PascalCase module name (e.g., `UrlDevice`)
- `{{module}}` - lowercase module name (e.g., `url-device`)
- `{{ModuleTitle}}` - Chinese title (e.g., `URL监控设备`)
- `{{ModuleName}}` - Vue component name (e.g., `UrlDeviceManagement`)
Replace these placeholders when generating new modules.

View File

@@ -0,0 +1,68 @@
# Architect Mode Rules (Non-Obvious Only)
## Architecture Overview
- Vue 3 SPA with dynamic route loading from server
- Pinia stores: `app`, `user`, `tab-bar` (see [`src/store/`](src/store/))
- Two-layer route guard: user login check + permission check
## Dynamic Route System
- Server menu fetched in [`app store`](src/store/modules/app/index.ts) via `fetchServerMenuConfig()`
- Routes registered dynamically in [`permission.ts`](src/router/guard/permission.ts)
- Uses `isMenuLoading`/`isMenuLoaded` flags to prevent duplicate loads
## API Architecture
- Two axios instances for different auth patterns:
- [`request.ts`](src/api/request.ts): Workspace header + custom token format
- [`interceptor.ts`](src/api/interceptor.ts): Bearer token + code 20000 validation
- Choose based on backend API requirements
## API Response Format
- Standard response structure: `{code: 0, details: {data: [...], total: number}}`
- Success code is `0`, NOT `200` or `20000`
- `request.ts` returns `response.data` directly
- Access list data via `response.details.data`
- Access total count via `response.details.total`
## State Management
- [`SafeStorage`](src/utils/safeStorage.ts) required for all localStorage access
- Supports TTL expiry and type-safe keys via enum
## Component Patterns
- Global components registered in [`components/index.ts`](src/components/index.ts)
- ECharts modules manually imported for bundle size optimization
- Use `SearchTable` composite component for list pages
## Directory Structure
```
src/
api/ # API layer (ops/ for business, module/ for auth)
components/ # Global reusable components
hooks/ # Composition functions
router/ # Route config and guards
store/ # Pinia stores
utils/ # Utility functions
views/ops/ # Main business modules
pages/
dc/ # Data center management
monitor/ # Monitoring modules
netarch/ # Network architecture
report/ # Reports
system-settings/ # System config
```
## Module Creation Checklist
1. Create API file in `src/api/ops/`
2. Create page directory in `src/views/ops/pages/`
3. Create `config/columns.ts` for table config
4. Create `config/search-form.ts` for search config
5. Create main `index.vue` with SearchTable
6. Create `components/FormDialog.vue` for CRUD
7. Create `components/Detail.vue` for detail view

47
.kilo/agent/rules-ask.md Normal file
View File

@@ -0,0 +1,47 @@
# Ask Mode Rules (Non-Obvious Only)
## Project Structure
- Vue 3 + Arco Design admin template with Pinia state management
- Vite config files located in `config/` directory (not root)
## Key Directories
- `src/api/` - API layer with two axios instances
- `src/views/ops/` - Main business modules (kb, netarch, asset, etc.)
- `src/router/guard/` - Route guards including dynamic menu loading
- `src/store/modules/app/` - App store with server menu fetching
## Documentation References
- Arco Design Vue: https://arco.design/vue
- Vue Flow (for topology): https://vueflow.dev/
## API Patterns
- Use [`request.ts`](src/api/request.ts) for workspace-aware requests
- Use [`interceptor.ts`](src/api/interceptor.ts) for standard Bearer token auth
## API Response Format
- Standard response: `{code: 0, details: {...}}`
- Success code is `0`, NOT `200` or `20000`
- Access data via `response.details.data`
## Component Libraries
- Arco Design Vue components: `<a-button>`, `<a-table>`, `<a-form>`, etc.
- Global components: `SearchTable`, `SearchForm`, `DataTable`, `Chart`
## Code Style
- No semicolons
- Single quotes
- Print width: 140 characters
- Path alias: `@/``src/`
## Build Commands
- `pnpm dev` - Start dev server
- `pnpm build` - Production build
- `pnpm lint` - Run ESLint + Prettier

85
.kilo/agent/rules-code.md Normal file
View File

@@ -0,0 +1,85 @@
# Code Mode Rules (Non-Obvious Only)
## API Layer
- Two axios instances exist: [`request.ts`](src/api/request.ts) (custom with workspace header) and [`interceptor.ts`](src/api/interceptor.ts) (global with Bearer token). Choose based on whether you need workspace support.
- [`request.ts`](src/api/request.ts) returns `response.data` directly (no nesting)
## Storage
- Always use [`SafeStorage`](src/utils/safeStorage.ts) with `AppStorageKey` enum - never use localStorage directly. Supports TTL via third parameter.
## useRequest Hook
- [`useRequest()`](src/hooks/request.ts) invokes API immediately - does NOT work in async functions. Pass params via `.bind(null, params)` pattern.
## Dynamic Routes
- Routes loaded from server in [`permission.ts`](src/router/guard/permission.ts) using `isMenuLoading`/`isMenuLoaded` flags. Don't modify these flags manually.
## Vue Component Structure
- Use `<script lang="ts" setup>` for main logic
- Add `<script lang="ts">` block with `export default { name: 'ComponentName' }` for component naming
- Style: `<style scoped lang="less">`
## Page Module Pattern
```
src/views/ops/pages/{module}/
index.vue # Main page component
components/
FormDialog.vue # CRUD form dialog
Detail.vue # Detail drawer/view
QuickConfigDialog.vue # Quick config dialog (optional)
config/
columns.ts # Table columns config
search-form.ts # Search form config
```
## API File Pattern
```
src/api/ops/{module}.ts
- Export interfaces for types
- Export fetch functions using `request.get/post/put/patch/delete`
- Name functions: `fetch{Resource}List`, `fetch{Resource}Detail`, `create{Resource}`, `update{Resource}`, `delete{Resource}`
```
## Import Order
1. Vue core (ref, reactive, computed, etc.)
2. Third-party (arco-design, axios, etc.)
3. Local components
4. Local configs
5. Local APIs
6. Types
## Comments (JSDoc Style)
- Add JSDoc comments for interfaces, types, and functions
- Interface comment format: `/** {{TypeName}} */`
- Function comment format: `/** {{Function description}} */`
- Example:
```ts
/** 服务器类型 */
export interface ServerItem { ... }
/** 获取服务器列表(分页) */
export const fetchServerList = (params?: ServerListParams) => { ... }
```
## API Response Format
- Standard response: `{code: 0, details: {data: [...], total: number}}`
- Success code is `0`, NOT `200` or `20000`
- Access data via `response.details.data` and total via `response.details.total`
## Code Style (Prettier)
- No semicolons
- Single quotes
- Trailing commas (es5)
- Print width: 140 characters
- Path alias: `@/` → `src/`

View File

@@ -0,0 +1,60 @@
# Debug Mode Rules (Non-Obvious Only)
## API Response Codes
- [`request.ts`](src/api/request.ts) returns `response.data` directly, expects `{code: 0, details: ...}`
- [`interceptor.ts`](src/api/interceptor.ts) expects `code: 20000` for success
- Token expiry codes: 50008, 50012, 50014 trigger logout modal (interceptor.ts)
- Token expiry status: 401 or error "Token has expired" (request.ts)
## Token Storage
- Tokens stored via [`SafeStorage`](src/utils/safeStorage.ts) with key `AppStorageKey.TOKEN`.
- Token expiry redirects to `/auth/login` (not `/login`).
## Route Loading Issues
- If routes not loading, check `isMenuLoading`/`isMenuLoaded` flags in [`permission.ts`](src/router/guard/permission.ts).
- Server menu fetched via [`fetchServerMenuConfig()`](src/store/modules/app/index.ts).
## Environment
- Dev config: `.env.development`, Prod config: `.env.production`
- API base URL: `VITE_API_BASE_URL`, Workspace: `VITE_APP_WORKSPACE`
## Common Issues
### useRequest Not Working in Async
- useRequest invokes API immediately - use in setup scope only
- For async contexts, call API directly without useRequest
### Menu Not Loading
- Check console for `[Permission Guard]` logs
- Verify `menuFromServer` setting in app store
- Check `fetchServerMenuConfig()` response
### Token Expiry
- Response `status: 401` or `error: 'Token has expired'` triggers logout
- Clear storage via `SafeStorage.clearAppStorage()`
## Debug Commands
```bash
# Check lint errors
pnpm lint
# Type check
pnpm build
# Dev server with logs
pnpm dev
```
## Browser DevTools
- Vue DevTools for component state
- Network tab for API calls
- Console for `[Permission Guard]` logs

27
.kilo/command/lint-fix.md Normal file
View File

@@ -0,0 +1,27 @@
# /lint-fix Command
Run linting and auto-fix code style issues.
## Usage
```
/lint-fix [path]
```
## Parameters
- `path`: Optional file or directory path (defaults to current working directory)
## What This Command Does
1. Runs `pnpm lint:eslint --fix` to fix ESLint issues
2. Runs `pnpm lint:prettier --write` to fix Prettier formatting
3. Reports any remaining issues that need manual fixing
## Example
```
/lint-fix src/views/ops/pages/dc/url-harvest/
```
Fixes lint issues in the url-harvest module.

40
.kilo/command/new-api.md Normal file
View File

@@ -0,0 +1,40 @@
# /new-api Command
Create a new API module with standard interfaces and functions.
## Usage
```
/new-api <module-name> [base-path]
```
## Parameters
- `module-name`: The API module name (e.g., `url-device`, `server`, `alert`)
- `base-path`: Optional API base path (defaults to `/DC-Control/v1/{module-name}`)
## What This Command Does
1. Creates API file: `src/api/ops/{module-name}.ts`
2. Generates standard interfaces:
- `{Module}Item` - Single item type
- `{Module}ListResponse` - List response type
- `{Module}ListParams` - Query parameters
- `{Module}CreateData` - Create payload
- `{Module}UpdateData` - Update payload
3. Generates standard functions:
- `fetch{Module}List` - GET list with pagination
- `fetch{Module}Detail` - GET single item
- `create{Module}` - POST create
- `update{Module}` - PUT update
- `delete{Module}` - DELETE remove
## Example
```
/new-api alert-template /Alert/v1/templates
```
Creates `src/api/ops/alert-template.ts` with full CRUD API functions.

View File

@@ -0,0 +1,33 @@
# /new-component Command
Create a new global reusable component.
## Usage
```
/new-component <component-name> [type]
```
## Parameters
- `component-name`: The component name (e.g., `status-badge`, `time-picker`)
- `type`: Optional component type (dialog, drawer, form, display, chart)
## What This Command Does
1. Creates component file: `src/components/{component-name}/index.vue`
2. Generates template following Arco Design patterns:
- Props definition with TypeScript types
- Emits definition with typed events
- Scoped styles with Less
3. Optionally registers in `src/components/index.ts` for global use
## Example
```
/new-component status-badge display
```
Creates a display component for showing status with color-coded badges.

51
.kilo/command/new-page.md Normal file
View File

@@ -0,0 +1,51 @@
# /new-page Command
Create a new page module with standard structure.
## Usage
```
/new-page <module-name> <category>
```
## Parameters
- `module-name`: The module name (e.g., `url-harvest`, `server`, `database`)
- `category`: The category directory (e.g., `dc`, `monitor`, `netarch`, `report`, `system-settings`)
## What This Command Does
1. Creates directory structure:
```
src/views/ops/pages/{category}/{module-name}/
index.vue
components/
FormDialog.vue
Detail.vue
config/
columns.ts
search-form.ts
```
2. Creates API file:
```
src/api/ops/{module-name}.ts
```
3. Generates template code following project patterns:
- Main page with SearchTable component
- CRUD dialog with form validation
- Detail drawer for viewing records
- Table columns configuration
- Search form configuration
- API interfaces and functions
## Example
```
/new-page asset dc
```
Creates `src/views/ops/pages/dc/asset/` with full CRUD functionality.

View File

@@ -0,0 +1,23 @@
# /type-check Command
Run TypeScript type checking on the project.
## Usage
```
/type-check
```
## What This Command Does
1. Runs `vue-tsc --noEmit` to check TypeScript types
2. Reports type errors with file locations and line numbers
3. Helps catch type issues before build
## Example
```
/type-check
```
Checks all TypeScript files for type errors.

115
.kilo/package-lock.json generated Normal file
View File

@@ -0,0 +1,115 @@
{
"name": ".kilo",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@kilocode/plugin": "7.2.14"
}
},
"node_modules/@kilocode/plugin": {
"version": "7.2.14",
"resolved": "https://registry.npmjs.org/@kilocode/plugin/-/plugin-7.2.14.tgz",
"integrity": "sha512-mS+WA9HZIBH2qQ9ARA+v0q4MdQTSdfOvKbe4AOSkjP+P5hVA70OM/UVM9DVcvmjSOxU+wuUxmOy+j/EQIrgFmw==",
"license": "MIT",
"dependencies": {
"@kilocode/sdk": "7.2.14",
"zod": "4.1.8"
},
"peerDependencies": {
"@opentui/core": ">=0.1.97",
"@opentui/solid": ">=0.1.97"
},
"peerDependenciesMeta": {
"@opentui/core": {
"optional": true
},
"@opentui/solid": {
"optional": true
}
}
},
"node_modules/@kilocode/sdk": {
"version": "7.2.14",
"resolved": "https://registry.npmjs.org/@kilocode/sdk/-/sdk-7.2.14.tgz",
"integrity": "sha512-Naz83lFrsbavuDp6UwxRuglOaSNvRBsZfcRNvb7RpWYAwbuJP0dBdhpXj6uO3ta5qxeQ2JzxKNC9Ffz+LCLLDg==",
"license": "MIT",
"dependencies": {
"cross-spawn": "7.0.6"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/zod": {
"version": "4.1.8",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

View File

@@ -0,0 +1,219 @@
# Vue Admin Development Skill
## Overview
This skill provides comprehensive guidance for developing features in this Vue 3 + Arco Design admin project.
## Core Patterns
### 1. Page Module Structure
Every page module follows this pattern:
```
src/views/ops/pages/{category}/{module}/
├── index.vue # Main page (SearchTable + CRUD)
├── components/
│ ├── FormDialog.vue # Create/Edit dialog
│ ├── Detail.vue # Detail view
│ └── QuickConfigDialog.vue # Optional quick config
└── config/
├── columns.ts # Table columns config
└── search-form.ts # Search form config
```
### 2. API Module Structure
```
src/api/ops/{module}.ts
├── /** {{Module}}类型 */
├── Interfaces (Item, ListResponse, ListParams, CreateData, UpdateData)
├── /** 获取{{Module}}列表(分页) */
├── fetch{Module}List(params)
├── /** 获取{{Module}}详情 */
├── fetch{Module}Detail(id)
├── /** 创建{{Module}} */
├── create{Module}(data)
├── /** 更新{{Module}} */
├── update{Module}(id, data)
└── /** 删除{{Module}} */
└── delete{Module}(id)
```
### 3. API Response Handling
```ts
// Standard response: {code: 0, details: {...}}
const response = await fetchList(params)
if (response && response.code === 0 && response.details) {
tableData.value = response.details.data || []
pagination.total = response.details.total || 0
}
```
src/api/ops/{module}.ts
├── Interfaces (Item, ListResponse, ListParams, CreateData, UpdateData)
├── fetch{Module}List(params)
├── fetch{Module}Detail(id)
├── create{Module}(data)
├── update{Module}(id, data)
└── delete{Module}(id)
````
### 3. Component Template
```vue
<template>
<div class="container">
<search-table ...>
<template #toolbar-left>
<!-- Action buttons -->
</template>
<template #column-slot="{ record }">
<!-- Custom column rendering -->
</template>
</search-table>
<FormDialog v-model:visible="..." />
<a-drawer v-model:visible="...">
<Detail :record="..." />
</a-drawer>
</div>
</template>
<script lang="ts" setup>
// Imports in order: Vue → Third-party → Components → Configs → APIs → Types
</script>
<script lang="ts">
export default { name: 'ModuleName' }
</script>
<style scoped lang="less"></style>
````
## Critical Rules
### Storage
```ts
// CORRECT
SafeStorage.set(AppStorageKey.TOKEN, token, 3600000)
SafeStorage.get(AppStorageKey.USER_INFO)
// WRONG
localStorage.setItem('token', token)
```
### API Choice
```ts
// Workspace-aware APIs (most ops APIs)
import { request } from '@/api/request'
request.post('/DC-Control/v1/servers', data)
// Response: {code: 0, details: {data: [...], total: number}}
// Standard Bearer token APIs
import axios from 'axios' // Uses interceptor.ts defaults
// Response: {code: 20000, data: {...}}
```
### Comments (JSDoc Style)
```ts
/** 服务器类型 */
export interface ServerItem {
id: number
name: string
}
/** 获取服务器列表(分页) */
export const fetchServerList = (params?: ServerListParams) => {
return request.get<ServerListResponse>('/DC-Control/v1/servers', { params })
}
```
### API Choice
```ts
// Workspace-aware APIs (most ops APIs)
import { request } from '@/api/request'
request.post('/DC-Control/v1/servers', data)
// Standard Bearer token APIs
import axios from 'axios' // Uses interceptor.ts defaults
```
### useRequest Hook
```ts
// CORRECT - Use in setup scope
const { loading, response } = useRequest(fetchList.bind(null, params))
// WRONG - Use in async function
async function loadData() {
const { loading, response } = useRequest(fetchList()) // Doesn't work!
}
```
### Dynamic Routes
```ts
// Routes auto-loaded by permission guard
// Don't manually add routes except in menu config
// Don't modify isMenuLoading/isMenuLoaded flags
```
## Quick Reference
### Arco Design Components
- Table: `<a-table>` with columns, pagination, slots
- Form: `<a-form>` with `<a-form-item>` and validation
- Dialog: `<a-modal>` with v-model:visible
- Drawer: `<a-drawer>` for side panels
- Message: `Message.success/error/info/warning()`
- Modal: `Modal.confirm/info/error()`
### Common Slots
- `#toolbar-left` - Left toolbar buttons
- `#toolbar-right` - Right toolbar buttons
- `#actions` - Row actions column
- `#enabled` - Status toggle column
- `#{fieldName}` - Custom field rendering
### Import Order
1. Vue: `ref, reactive, computed, watch, onMounted`
2. Arco: `Message, Modal` + Icons
3. Components: `SearchTable, FormDialog`
4. Configs: `searchFormConfig, columns`
5. APIs: `fetchList, createItem, updateItem`
6. Types: `Item, ListParams, CreateData`
## Workflow
### Creating New Module
1. Run `/new-api {module}` to create API
2. Run `/new-page {module} {category}` to create page
3. Adjust columns and search-form configs
4. Customize FormDialog fields
5. Add Detail view content
6. Test with `pnpm dev`
### Adding Feature to Existing Module
1. Check existing patterns in similar modules
2. Add API function if needed
3. Update columns/search-form config
4. Add component or slot if needed
5. Run `pnpm lint` after changes
## Debugging
- Console logs prefixed with `[Permission Guard]` for route issues
- Network tab for API debugging
- Vue DevTools for state inspection
- `pnpm lint` for code issues

View File

@@ -0,0 +1,77 @@
<template>
<div class="detail-container">
<a-descriptions :column="2" bordered>
<a-descriptions-item label="ID">{{ record.id }}</a-descriptions-item>
<a-descriptions-item label="名称">{{ record.name }}</a-descriptions-item>
<a-descriptions-item label="描述" :span="2">{{ record.description || '-' }}</a-descriptions-item>
<a-descriptions-item label="启用状态">
<a-tag :color="record.enabled ? 'green' : 'gray'">
{{ record.enabled ? '已启用' : '已禁用' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="创建时间">{{ formatTime(record.created_at) }}</a-descriptions-item>
<a-descriptions-item label="更新时间">{{ formatTime(record.updated_at) }}</a-descriptions-item>
</a-descriptions>
<div class="actions">
<a-space>
<a-button type="primary" @click="handleEdit">
<template #icon>
<icon-edit />
</template>
编辑
</a-button>
<a-button status="danger" @click="handleDelete">
<template #icon>
<icon-delete />
</template>
删除
</a-button>
</a-space>
</div>
</div>
</template>
<script lang="ts" setup>
import { IconEdit, IconDelete } from '@arco-design/web-vue/es/icon'
import type { {{Module}}Item } from '@/api/ops/{{module}}'
const props = defineProps<{
record: {{Module}}Item
}>()
const emit = defineEmits<{
(e: 'edit'): void
(e: 'delete'): void
}>()
const formatTime = (time?: string) => {
if (!time) return '-'
const date = new Date(time)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
const handleEdit = () => {
emit('edit')
}
const handleDelete = () => {
emit('delete')
}
</script>
<style scoped lang="less">
.detail-container {
.actions {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid var(--color-border);
}
}
</style>

View File

@@ -0,0 +1,107 @@
<template>
<a-modal
v-model:visible="visible"
:title="isEdit ? '编辑{{ModuleTitle}}' : '新增{{ModuleTitle}}'"
:width="600"
:mask-closable="false"
unmount-on-close
@ok="handleOk"
@cancel="handleCancel"
>
<a-form ref="formRef" :model="formData" :rules="rules" layout="vertical">
<a-form-item field="name" label="名称" required>
<a-input v-model="formData.name" placeholder="请输入名称" :max-length="100" />
</a-form-item>
<a-form-item field="description" label="描述">
<a-textarea v-model="formData.description" placeholder="请输入描述" :max-length="500" :auto-size="{ minRows: 3, maxRows: 5 }" />
</a-form-item>
<a-form-item field="enabled" label="启用状态">
<a-switch v-model="formData.enabled" />
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, computed, watch } from 'vue'
import { Message } from '@arco-design/web-vue'
import type { {{Module}}Item, {{Module}}CreateData, {{Module}}UpdateData } from '@/api/ops/{{module}}'
import { create{{Module}}, update{{Module}} } from '@/api/ops/{{module}}'
const props = defineProps<{
visible: boolean
record: {{Module}}Item | null
}>()
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'success'): void
}>()
const formRef = ref()
const isEdit = computed(() => !!props.record)
const formData = ref<{{Module}}CreateData>({
name: '',
description: '',
enabled: true,
})
const rules = {
name: [
{ required: true, message: '请输入名称' },
{ max_length: 100, message: '名称不能超过100个字符' },
],
}
watch(
() => props.visible,
(val) => {
if (val && props.record) {
formData.value = {
name: props.record.name,
description: props.record.description || '',
enabled: props.record.enabled,
}
} else if (val) {
formData.value = {
name: '',
description: '',
enabled: true,
}
}
}
)
const handleOk = async () => {
try {
const valid = await formRef.value?.validate()
if (valid) return
if (isEdit.value && props.record) {
const res = await update{{Module}}(props.record.id, formData.value as {{Module}}UpdateData)
if (res && res.code === 0) {
Message.success('更新成功')
emit('success')
emit('update:visible', false)
}
} else {
const res = await create{{Module}}(formData.value)
if (res && res.code === 0) {
Message.success('创建成功')
emit('success')
emit('update:visible', false)
}
}
} catch (error) {
console.error('操作失败:', error)
Message.error('操作失败')
}
}
const handleCancel = () => {
emit('update:visible', false)
}
</script>
<style scoped lang="less"></style>

View File

@@ -0,0 +1,66 @@
import { request } from '@/api/request'
/** {{Module}}类型 */
export interface {{Module}}Item {
id: number
created_at: string
updated_at: string
name: string
description?: string
enabled: boolean
}
/** {{Module}}列表响应 */
export interface {{Module}}ListResponse {
total: number
page: number
page_size: number
data: {{Module}}Item[]
}
/** {{Module}}列表请求参数 */
export interface {{Module}}ListParams {
page?: number
size?: number
keyword?: string
enabled?: boolean
}
/** 创建{{Module}}请求参数 */
export interface {{Module}}CreateData {
name: string
description?: string
enabled?: boolean
}
/** 更新{{Module}}请求参数 */
export interface {{Module}}UpdateData {
name?: string
description?: string
enabled?: boolean
}
/** 获取{{Module}}列表(分页) */
export const fetch{{Module}}List = (params?: {{Module}}ListParams) => {
return request.get<{{Module}}ListResponse>('/DC-Control/v1/{{module}}s', { params })
}
/** 获取{{Module}}详情 */
export const fetch{{Module}}Detail = (id: number) => {
return request.get<{{Module}}Item>(`/DC-Control/v1/{{module}}s/${id}`)
}
/** 创建{{Module}} */
export const create{{Module}} = (data: {{Module}}CreateData) => {
return request.post<{ message: string; id: number }>('/DC-Control/v1/{{module}}s', data)
}
/** 更新{{Module}} */
export const update{{Module}} = (id: number, data: {{Module}}UpdateData) => {
return request.put<{ message: string }>(`/DC-Control/v1/{{module}}s/${id}`, data)
}
/** 删除{{Module}} */
export const delete{{Module}} = (id: number) => {
return request.delete<{ message: string }>(`/DC-Control/v1/{{module}}s/${id}`)
}

View File

@@ -0,0 +1,39 @@
export const columns = [
{
dataIndex: 'id',
title: 'ID',
width: 80,
slotName: 'id',
},
{
dataIndex: 'name',
title: '名称',
width: 150,
},
{
dataIndex: 'description',
title: '描述',
width: 200,
ellipsis: true,
tooltip: true,
},
{
dataIndex: 'enabled',
title: '启用状态',
width: 100,
slotName: 'enabled',
},
{
dataIndex: 'created_at',
title: '创建时间',
width: 180,
slotName: 'created_at',
},
{
dataIndex: 'actions',
title: '操作',
width: 180,
fixed: 'right' as const,
slotName: 'actions',
},
]

235
.kilo/templates/index.vue Normal file
View File

@@ -0,0 +1,235 @@
<template>
<div class="container">
<search-table
:form-model="formModel"
:form-items="formItems"
:data="tableData"
:columns="columns"
:loading="loading"
:pagination="pagination"
title="{{ModuleTitle}}管理"
search-button-text="查询"
reset-button-text="重置"
@update:form-model="handleFormModelUpdate"
@search="handleSearch"
@reset="handleReset"
@page-change="handlePageChange"
@refresh="handleRefresh"
>
<template #toolbar-left>
<a-button type="primary" @click="handleAdd">
<template #icon>
<icon-plus />
</template>
新增
</a-button>
</template>
<template #id="{ record }">
{{ record.id }}
</template>
<template #enabled="{ record }">
<a-tag :color="record.enabled ? 'green' : 'gray'">
{{ record.enabled ? '已启用' : '已禁用' }}
</a-tag>
</template>
<template #created_at="{ record }">
{{ formatTime(record.created_at) }}
</template>
<template #actions="{ record }">
<a-space>
<a-button type="text" size="small" @click="handleDetail(record)">
<template #icon>
<icon-eye />
</template>
详情
</a-button>
<a-button type="text" size="small" @click="handleEdit(record)">
<template #icon>
<icon-edit />
</template>
编辑
</a-button>
<a-button type="text" size="small" status="danger" @click="handleDelete(record)">
<template #icon>
<icon-delete />
</template>
删除
</a-button>
</a-space>
</template>
</search-table>
<FormDialog v-model:visible="formDialogVisible" :record="currentRecord" @success="handleFormSuccess" />
<a-drawer v-model:visible="detailVisible" :width="600" title="{{ModuleTitle}}详情" :footer="false" unmount-on-close>
<Detail v-if="currentRecord" :record="currentRecord" @edit="handleDetailEdit" @delete="handleDetailDelete" />
</a-drawer>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from 'vue'
import { Message, Modal } from '@arco-design/web-vue'
import { IconPlus, IconEdit, IconDelete, IconEye } from '@arco-design/web-vue/es/icon'
import type { FormItem } from '@/components/search-form/types'
import SearchTable from '@/components/search-table/index.vue'
import FormDialog from './components/FormDialog.vue'
import Detail from './components/Detail.vue'
import { searchFormConfig } from './config/search-form'
import { columns as columnsConfig } from './config/columns'
import { fetch{{Module}}List, delete{{Module}}, type {{Module}}Item, type {{Module}}ListParams } from '@/api/ops/{{module}}'
const loading = ref(false)
const tableData = ref<{{Module}}Item[]>([])
const formDialogVisible = ref(false)
const detailVisible = ref(false)
const currentRecord = ref<{{Module}}Item | null>(null)
const formModel = ref({
keyword: '',
enabled: undefined as boolean | undefined,
})
const pagination = reactive({
current: 1,
pageSize: 20,
total: 0,
})
const formItems = computed<FormItem[]>(() => searchFormConfig)
const columns = computed(() => columnsConfig)
const formatTime = (time?: string) => {
if (!time) return '-'
const date = new Date(time)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
const fetchData = async () => {
loading.value = true
try {
const params: {{Module}}ListParams = {
page: pagination.current,
size: pagination.pageSize,
keyword: formModel.value.keyword,
enabled: formModel.value.enabled,
}
const response: any = await fetch{{Module}}List(params)
if (response && response.code === 0 && response.details) {
tableData.value = response.details?.data || []
pagination.total = response.details?.total || 0
} else {
tableData.value = []
pagination.total = 0
}
} catch (error) {
console.error('获取列表失败:', error)
Message.error('获取列表失败')
tableData.value = []
pagination.total = 0
} finally {
loading.value = false
}
}
const handleSearch = () => {
pagination.current = 1
fetchData()
}
const handleFormModelUpdate = (value: any) => {
formModel.value = value
}
const handleReset = () => {
formModel.value = {
keyword: '',
enabled: undefined,
}
pagination.current = 1
fetchData()
}
const handlePageChange = (current: number) => {
pagination.current = current
fetchData()
}
const handleRefresh = () => {
fetchData()
Message.success('数据已刷新')
}
const handleAdd = () => {
currentRecord.value = null
formDialogVisible.value = true
}
const handleEdit = (record: {{Module}}Item) => {
currentRecord.value = record
formDialogVisible.value = true
}
const handleDetail = (record: {{Module}}Item) => {
currentRecord.value = record
detailVisible.value = true
}
const handleDetailEdit = () => {
detailVisible.value = false
formDialogVisible.value = true
}
const handleDetailDelete = () => {
detailVisible.value = false
if (currentRecord.value) {
handleDelete(currentRecord.value)
}
}
const handleFormSuccess = () => {
fetchData()
}
const handleDelete = (record: {{Module}}Item) => {
Modal.confirm({
title: '确认删除',
content: `确认删除 "${record.name}" 吗?`,
onOk: async () => {
try {
const res = await delete{{Module}}(record.id)
if (res && res.code === 0) {
Message.success('删除成功')
fetchData()
}
} catch (error) {
console.error('删除失败:', error)
Message.error('删除失败')
}
},
})
}
fetchData()
</script>
<script lang="ts">
export default {
name: '{{ModuleName}}',
}
</script>
<style scoped lang="less">
.container {
margin-top: 20px;
}
</style>

View File

@@ -0,0 +1,22 @@
import type { FormItem } from '@/components/search-form/types'
export const searchFormConfig: FormItem[] = [
{
field: 'keyword',
label: '关键词',
type: 'input',
placeholder: '请输入名称',
span: 6,
},
{
field: 'enabled',
label: '启用状态',
type: 'select',
placeholder: '请选择启用状态',
options: [
{ label: '已启用', value: true },
{ label: '已禁用', value: false },
],
span: 6,
},
]

115
.kilocode/package-lock.json generated Normal file
View File

@@ -0,0 +1,115 @@
{
"name": ".kilocode",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@kilocode/plugin": "7.2.14"
}
},
"node_modules/@kilocode/plugin": {
"version": "7.2.14",
"resolved": "https://registry.npmjs.org/@kilocode/plugin/-/plugin-7.2.14.tgz",
"integrity": "sha512-mS+WA9HZIBH2qQ9ARA+v0q4MdQTSdfOvKbe4AOSkjP+P5hVA70OM/UVM9DVcvmjSOxU+wuUxmOy+j/EQIrgFmw==",
"license": "MIT",
"dependencies": {
"@kilocode/sdk": "7.2.14",
"zod": "4.1.8"
},
"peerDependencies": {
"@opentui/core": ">=0.1.97",
"@opentui/solid": ">=0.1.97"
},
"peerDependenciesMeta": {
"@opentui/core": {
"optional": true
},
"@opentui/solid": {
"optional": true
}
}
},
"node_modules/@kilocode/sdk": {
"version": "7.2.14",
"resolved": "https://registry.npmjs.org/@kilocode/sdk/-/sdk-7.2.14.tgz",
"integrity": "sha512-Naz83lFrsbavuDp6UwxRuglOaSNvRBsZfcRNvb7RpWYAwbuJP0dBdhpXj6uO3ta5qxeQ2JzxKNC9Ffz+LCLLDg==",
"license": "MIT",
"dependencies": {
"cross-spawn": "7.0.6"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/zod": {
"version": "4.1.8",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

View File

@@ -1,25 +1,34 @@
# Architect Mode Rules (Non-Obvious Only)
## Development Constraints
- Code is not deployed yet; do not spend effort preserving backward compatibility with old code.
## Architecture Overview
- Vue 3 SPA with dynamic route loading from server
- Pinia stores: `app`, `user`, `tab-bar` (see [`src/store/`](src/store/))
- Two-layer route guard: user login check + permission check
## Dynamic Route System
- Server menu fetched in [`app store`](src/store/modules/app/index.ts) via `fetchServerMenuConfig()`
- Routes registered dynamically in [`permission.ts`](src/router/guard/permission.ts)
- Uses `isMenuLoading`/`isMenuLoaded` flags to prevent duplicate loads
## API Architecture
- Two axios instances for different auth patterns:
- [`request.ts`](src/api/request.ts): Workspace header + custom token format
- [`interceptor.ts`](src/api/interceptor.ts): Bearer token + code 20000 validation
- Choose based on backend API requirements
## State Management
- [`SafeStorage`](src/utils/safeStorage.ts) required for all localStorage access
- Supports TTL expiry and type-safe keys via enum
## Component Patterns
- Global components registered in [`components/index.ts`](src/components/index.ts)
- ECharts modules manually imported for bundle size optimization
- ECharts modules manually imported for bundle size optimization

View File

@@ -1,19 +1,23 @@
# Ask Mode Rules (Non-Obvious Only)
## Project Structure
- Vue 3 + Arco Design admin template with Pinia state management
- Vite config files located in `config/` directory (not root)
## Key Directories
- `src/api/` - API layer with two axios instances
- `src/views/ops/` - Main business modules (kb, netarch, asset, etc.)
- `src/router/guard/` - Route guards including dynamic menu loading
- `src/store/modules/app/` - App store with server menu fetching
## Documentation References
- Arco Design Vue: https://arco.design/vue
- Vue Flow (for topology): https://vueflow.dev/
## API Patterns
- Use [`request.ts`](src/api/request.ts) for workspace-aware requests
- Use [`interceptor.ts`](src/api/interceptor.ts) for standard Bearer token auth
- Use [`interceptor.ts`](src/api/interceptor.ts) for standard Bearer token auth

View File

@@ -1,13 +1,21 @@
# Code Mode Rules (Non-Obvious Only)
## Development Constraints
- Code is not deployed yet; do not spend effort preserving backward compatibility with old code.
## API Layer
- Two axios instances exist: [`request.ts`](src/api/request.ts) (custom with workspace header) and [`interceptor.ts`](src/api/interceptor.ts) (global with Bearer token). Choose based on whether you need workspace support.
## Storage
- Always use [`SafeStorage`](src/utils/safeStorage.ts) with `AppStorageKey` enum - never use localStorage directly. Supports TTL via third parameter.
## useRequest Hook
- [`useRequest()`](src/hooks/request.ts) invokes API immediately - does NOT work in async functions. Pass params via `.bind(null, params)` pattern.
## Dynamic Routes
- Routes loaded from server in [`permission.ts`](src/router/guard/permission.ts) using `isMenuLoading`/`isMenuLoaded` flags. Don't modify these flags manually.
- Routes loaded from server in [`permission.ts`](src/router/guard/permission.ts) using `isMenuLoading`/`isMenuLoaded` flags. Don't modify these flags manually.

View File

@@ -1,17 +1,21 @@
# Debug Mode Rules (Non-Obvious Only)
## API Response Codes
- [`interceptor.ts`](src/api/interceptor.ts) expects `code: 20000` for success. Other codes trigger error messages.
- Token expiry codes: 50008, 50012, 50014 trigger logout modal.
## Token Storage
- Tokens stored via [`SafeStorage`](src/utils/safeStorage.ts) with key `AppStorageKey.TOKEN`.
- Token expiry redirects to `/auth/login` (not `/login`).
## Route Loading Issues
- If routes not loading, check `isMenuLoading`/`isMenuLoaded` flags in [`permission.ts`](src/router/guard/permission.ts).
- Server menu fetched via [`fetchServerMenuConfig()`](src/store/modules/app/index.ts).
## Environment
- Dev config: `.env.development`, Prod config: `.env.production`
- API base URL: `VITE_API_BASE_URL`, Workspace: `VITE_APP_WORKSPACE`
- API base URL: `VITE_API_BASE_URL`, Workspace: `VITE_APP_WORKSPACE`

File diff suppressed because it is too large Load Diff

161
AGENTS.md
View File

@@ -3,34 +3,191 @@
This file provides guidance to agents when working with code in this repository.
## Build Commands
- `pnpm dev` - Start dev server (config: `config/vite.config.dev.ts`)
- `pnpm build` - Production build (config: `config/vite.config.prod.ts`)
- `pnpm lint` - Run ESLint + Prettier
- `pnpm lint:eslint --fix` - Fix ESLint issues
- `pnpm lint:prettier --write` - Fix Prettier formatting
- No test framework configured
## Critical Architecture Notes
### Development Constraints
- Code is not deployed yet; do not spend effort preserving backward compatibility with old code.
### Vite Config Location
Config files are in `config/` directory, NOT root. All vite commands reference `./config/vite.config.*.ts`.
### Two Axios Instances
- [`src/api/request.ts`](src/api/request.ts) - Custom instance with workspace header support and `needWorkspace` param
- [`src/api/request.ts`](src/api/request.ts) - Custom instance with workspace header support and `needWorkspace` param. Returns `response.data` directly.
- [`src/api/interceptor.ts`](src/api/interceptor.ts) - Global instance with Bearer token and code 20000 validation
- Use `request.ts` for most ops APIs that need workspace header
- Use `interceptor.ts` pattern for APIs expecting `code: 20000` response format
### API Response Format
Standard response from `request.ts`: `{code: 0, details: {data: [...], total: number}}`
- Success code is `0`, NOT `200` or `20000`
- Access list data via `response.details.data`
- Access total count via `response.details.total`
```ts
const response = await fetchList(params)
if (response && response.code === 0 && response.details) {
tableData.value = response.details.data || []
pagination.total = response.details.total || 0
}
```
### Storage Pattern
Use [`SafeStorage`](src/utils/safeStorage.ts) instead of localStorage directly. Supports TTL expiry and type-safe keys via `AppStorageKey` enum.
```ts
// Set with optional TTL (milliseconds)
SafeStorage.set(AppStorageKey.TOKEN, token, 3600000)
// Get
SafeStorage.get(AppStorageKey.USER_INFO)
// Clear all app storage
SafeStorage.clearAppStorage()
```
### Dynamic Route Loading
Routes are loaded from server via [`fetchServerMenuConfig()`](src/store/modules/app/index.ts) with permission guard in [`permission.ts`](src/router/guard/permission.ts). Uses flags `isMenuLoading`/`isMenuLoaded` to prevent duplicate loads.
- Do NOT manually modify `isMenuLoading`/`isMenuLoaded` flags
- Routes are registered dynamically via `router.addRoute()`
- Menu data comes from `userPmn` API and transformed via `buildTree` + `transformMenuToRoutes`
### useRequest Hook Limitation
[`useRequest()`](src/hooks/request.ts) does NOT work in async functions - it immediately invokes the API. Use `.bind(null, params)` to pass arguments.
```ts
// CORRECT - Use in setup scope with bind
const { loading, response } = useRequest(fetchList.bind(null, { page: 1 }))
// WRONG - Use in async function
async function loadData() {
const { loading, response } = useRequest(fetchList()) // Won't work!
}
```
## Standard Module Structure
### Page Module Pattern
```
src/views/ops/pages/{category}/{module}/
├── index.vue # Main page component (SearchTable)
├── components/
│ ├── FormDialog.vue # Create/Edit form dialog
│ ├── Detail.vue # Detail drawer/view
│ └── QuickConfigDialog.vue # Optional quick config dialog
└── config/
├── columns.ts # Table columns configuration
└── search-form.ts # Search form configuration
```
### API Module Pattern
```
src/api/ops/{module}.ts
├── {Module}Item # Single item interface
├── {Module}ListResponse # List response interface
├── {Module}ListParams # Query parameters interface
├── {Module}CreateData # Create payload interface
├── {Module}UpdateData # Update payload interface
├── fetch{Module}List() # GET list with pagination
├── fetch{Module}Detail() # GET single item
├── create{Module}() # POST create
├── update{Module}() # PUT update
├── patch{Module}() # PATCH partial update
└── delete{Module}() # DELETE remove
```
### Component Template Pattern
```vue
<template>
<div class="container">
<!-- SearchTable with slots -->
</div>
</template>
<script lang="ts" setup>
// Import order: Vue → Third-party → Components → Configs → APIs → Types
import { ref, reactive, computed } from 'vue'
import { Message, Modal } from '@arco-design/web-vue'
import SearchTable from '@/components/search-table/index.vue'
import FormDialog from './components/FormDialog.vue'
import { columns } from './config/columns'
import { fetchList } from '@/api/ops/module'
</script>
<script lang="ts">
export default { name: 'ModuleName' }
</script>
<style scoped lang="less"></style>
```
## Code Style
- No semicolons (Prettier enforced)
- Single quotes, trailing commas (es5)
- Print width: 140 characters
- Path alias: `@/``src/`
- **Add JSDoc comments** for interfaces, types, and functions
```ts
/** 服务器类型 */
export interface ServerItem { ... }
/** 获取服务器列表(分页) */
export const fetchServerList = (params?: ServerListParams) => { ... }
```
## Vue/i18n Aliases Required
Vite config includes aliases for `vue-i18n/dist/vue-i18n.cjs.js` and `vue/dist/vue.esm-bundler.js` - don't remove these.
Vite config includes aliases for `vue-i18n/dist/vue-i18n.cjs.js` and `vue/dist/vue.esm-bundler.js` - don't remove these.
## Key Components
- [`SearchTable`](src/components/search-table/index.vue) - Composite search + table component
- [`SearchForm`](src/components/search-form/index.vue) - Search form with dynamic fields
- [`DataTable`](src/components/data-table/index.vue) - Enhanced table with toolbar
- [`Chart`](src/components/chart/index.vue) - ECharts wrapper component
## Import Order
1. Vue core (ref, reactive, computed, watch, onMounted)
2. Third-party (Message, Modal, icons from Arco Design)
3. Global components (SearchTable, DataTable)
4. Local components (FormDialog, Detail)
5. Config files (columns, searchForm)
6. API functions (fetchList, createItem)
7. Type interfaces (Item, ListParams)
## API Response Handling
- `request.ts` returns `response.data` directly (no `.data.data` nesting)
- Success code: `0` (NOT `200` or `20000`)
- Access data: `response.details.data`
- Access total: `response.details.total`
- `interceptor.ts` expects `code: 20000` for success
- Token expiry codes: 50008, 50012, 50014 trigger logout (interceptor.ts)
- Status 401 or error "Token has expired" redirects to `/auth/login` (request.ts)
## Debug Tips
- Console logs prefixed with `[Permission Guard]` for route loading issues
- Check `isMenuLoading`/`isMenuLoaded` flags if routes not appearing
- Network tab for API debugging
- Vue DevTools for component state inspection
- Run `pnpm lint` after changes to catch issues

View File

@@ -1,196 +0,0 @@
# 官网首页和帮助中心页面说明
## 新增页面
本次为系统添加了两个全新的对外页面,无需登录即可访问:
### 1. 官网首页 (`/home`)
**功能特点:**
- 🎨 现代化的渐变背景设计
- 📱 响应式布局,适配各种设备
- 🎯 清晰的功能特性展示区域
- 📊 数据中心关键指标展示
- 🔗 快速导航到登录系统和帮助中心
- 💬 完整的页脚信息
**主要板块:**
1. **顶部导航栏** - 包含 Logo、导航链接和登录按钮
2. **Banner 区域** - 醒目的标题和行动号召按钮
3. **功能特性** - 6 个核心功能卡片展示
4. **数据中心** - 关键运行指标统计
5. **行动号召** - 引导用户登录或查看帮助
6. **页脚** - 版权信息和快速链接
**访问方式:**
- 直接访问:`http://your-domain/#/home`
- 根路径自动重定向到首页
### 2. 帮助中心页面 (`/help`)
**功能特点:**
- 🔍 文档搜索功能
- 📑 左侧分类导航菜单
- ❓ 常见问题 FAQ折叠面板
- 📖 使用指南卡片
- 📥 操作手册下载
- 📞 联系方式展示
**主要内容:**
1. **常见问题** - 8 个典型问题的问答
2. **使用指南** - 6 个分类的详细教程
- 快速入门
- 仪表盘
- 告警管理
- 数据中心
- 报表分析
- 系统设置
3. **操作手册** - 可下载的文档资料
4. **联系我们** - 电话、邮件、在线客服
**访问方式:**
- 直接访问:`http://your-domain/#/help`
- 从首页导航点击"帮助中心"
## 技术实现
### 文件结构
```
src/
├── views/
│ ├── home/ # 官网首页
│ │ └── index.vue
│ └── help/ # 帮助中心
│ └── index.vue
├── layout/
│ └── standalone-layout.vue # 独立布局(无侧边栏)
└── router/
├── index.ts # 路由配置
└── constants.ts # 路由常量(白名单)
```
### 路由配置
```typescript
// 根路径配置
{
path: '/',
name: 'root',
component: () => import('@/layout/standalone-layout.vue'),
redirect: '/home',
children: [
{
path: '/home',
name: 'Home',
component: () => import('@/views/home/index.vue'),
meta: { requiresAuth: false }
},
{
path: '/help',
name: 'Help',
component: () => import('@/views/help/index.vue'),
meta: { requiresAuth: false }
}
]
}
```
### 权限控制
- 两个页面都设置了 `requiresAuth: false`,无需登录即可访问
- 已添加到路由白名单 `WHITE_LIST`
- 使用独立的 `standalone-layout`,不显示系统的导航栏和侧边栏
## 自定义内容
### 修改公司信息
在两个页面的页脚部分,可以修改:
- 版权信息
- 联系方式(电话、邮箱)
- 公司地址
### 添加更多帮助文档
编辑 `help/index.vue` 中的数据:
```typescript
// 添加 FAQ
const faqs = ref([
{
question: '您的问题',
answer: '您的答案',
category: '分类'
}
])
// 添加使用指南
const guides = ref([
{
id: 1,
title: '指南标题',
description: '描述',
icon: '图标',
category: '分类',
categoryName: '分类名称',
updatedAt: '更新日期'
}
])
```
### 修改配色方案
页面使用了 Arco Design 的设计令牌Design Tokens可以通过修改主题变量来调整配色
```less
// 主要颜色
rgb(var(--primary-6)) // 主色调
rgb(var(--success-6)) // 成功色
rgb(var(--warning-6)) // 警告色
```
## 登录页面优化
在登录页面右上角添加了"访问官网首页"按钮,方便用户在登录前了解系统功能。
## 下一步建议
1. **添加真实数据** - 将演示数据替换为实际的帮助文档
2. **集成客服系统** - 接入第三方在线客服工具
3. **添加视频教学** - 在帮助中心增加视频教程
4. **SEO 优化** - 添加 meta 标签、title 等 SEO 元素
5. **多语言支持** - 如果需要,可以添加国际化支持
6. **访问统计** - 集成网站分析工具如百度统计、Google Analytics
## 运行项目
```bash
# 安装依赖
pnpm install
# 启动开发服务器
pnpm dev
# 构建生产版本
pnpm build
```
## 浏览器兼容性
- ✅ Chrome (推荐)
- ✅ Firefox
- ✅ Edge
- ✅ Safari
- ⚠️ IE11需要额外 polyfill
## 注意事项
1. 确保 logo 文件路径正确(`@/assets/logo.svg`
2. 如果部署在子目录,需要调整路由的 base 配置
3. 如需启用 SSR 或 SEO建议使用 Vue Router 的 history 模式
4. 生产环境部署时,确保服务器正确处理 hash 路由
---
如有任何问题或需要进一步的技术支持,请联系开发团队。

View File

@@ -1,86 +0,0 @@
# 快速启动指南
## 🎉 新增功能
已成功为您的智能运维管理系统添加了两个全新的对外页面:
### ✅ 已完成的工作
1. **官网首页** (`/#/home`)
- 现代化的 UI 设计
- 功能特性展示6 个核心功能)
- 数据中心关键指标
- 响应式布局
2. **帮助中心** (`/#/help`)
- 常见问题 FAQ
- 使用指南分类
- 操作手册下载
- 联系方式展示
3. **登录页面优化**
- 添加"访问官网首页"按钮
4. **路由配置**
- 无需登录即可访问
- 独立布局(无侧边栏)
- 根路径自动重定向到首页
## 🚀 立即查看效果
### 方式一:直接访问 URL
在浏览器中访问:
```
http://localhost:5173/#/home
http://localhost:5173/#/help
```
### 方式二:从登录页跳转
访问登录页面,点击右上角的"首页"图标即可跳转到官网首页。
## 📝 下一步自定义建议
### 1. 修改 Logo
替换 `src/assets/logo.svg` 文件为您公司的 logo
### 2. 更新联系信息
编辑以下文件中的联系方式:
- `src/views/home/index.vue` - 页脚联系方式
- `src/views/help/index.vue` - 联系我们板块
### 3. 添加真实的帮助文档
编辑 `src/views/help/index.vue` 中的:
```typescript
const faqs = ref([...]) // 常见问题
const guides = ref([...]) // 使用指南
const manuals = ref([...]) // 操作手册
```
### 4. 调整配色方案
页面使用了 Arco Design 主题色,可以通过 CSS 变量调整:
```less
rgb(var(--primary-6)) // 主色调
```
## 💡 提示
- 所有页面都已经适配移动端,可以在手机上查看效果
- 页面使用了 Vue Router 的 hash 模式,部署简单
- 无需后端支持,纯静态页面,可直接部署
## 📦 技术栈
- Vue 3 + TypeScript
- Arco Design 组件库
- Vite 构建工具
- Vue Router 路由管理
---
享受您的新页面!🎊

View File

@@ -0,0 +1,47 @@
# Front - OPS 前端
`front` 是 OPS 平台的统一前端入口,基于 Vue 3、Vite、Arco Design Vue、Pinia 和 Vue Router。页面以动态菜单为主RBAC 菜单接口不可用时使用本地菜单数据兜底。
## 当前代码入口
| 类型 | 路径 |
| --- | --- |
| 应用入口 | `front/src/main.ts` |
| 路由入口 | `front/src/router/index.ts` |
| 静态路由 | `front/src/router/routes/modules/` |
| 动态菜单转换 | `front/src/router/menu-data.ts` |
| 本地菜单兜底 | `front/src/router/local-menu-items.ts``front/src/router/local-menu-flat.ts` |
| 页面目录 | `front/src/views/` |
| API 模块 | `front/src/api/module/` |
| 构建配置 | `front/config/` |
## 主要页面域
| 页面域 | 目录 |
| --- | --- |
| 首页与概览 | `front/src/views/home``front/src/views/ops/pages/overview` |
| 数据中心与资源 | `front/src/views/ops/pages/dc``datacenter``resource-context` |
| 告警与治理 | `front/src/views/ops/pages/alert``governance` |
| 工单闭环 | `front/src/views/ops/pages/feedback` |
| 知识库 | `front/src/views/ops/pages/kb` |
| 报表与自动化 | `front/src/views/ops/pages/report``automation` |
| 业务系统与拓扑 | `front/src/views/ops/pages/business-system``business-topology` |
| 大屏 | `front/src/views/ops/pages/big-screen``front/src/views/visualization` |
| 日志管理 | `front/src/views/ops/pages/log-mgmt` |
| 系统设置 | `front/src/views/ops/pages/system-settings` |
## 常用命令
```powershell
cd D:\work\ops\front
pnpm.cmd install
pnpm.cmd dev
pnpm.cmd build
```
## 相关文档
- `front/PAGES_README.md`:页面清单和路由说明。
- `front/QUICK_START.md`:本地启动说明。
- `front/docs/`:页面设计、适配验收和部分接口对接文档。
- `docs/模块文档索引.md`:全工作区模块边界与文档入口。

View File

@@ -0,0 +1,376 @@
# 资产设备源地址前端对接文档
## 1. 变更说明
设备资产新增可选字段 `source_address`,界面名称为“源地址”。该字段适用于所有设备分类,可用于保存 RTSP、HTTP、HTTPS、HLS 等形式的播放源地址。
| 属性 | 内容 |
| --- | --- |
| JSON 字段 | `source_address` |
| TypeScript 类型 | `string` |
| 是否必填 | 否 |
| 最大长度 | 1000 个字符 |
| 协议限制 | 无 |
| 数据库字段 | `assets_assets.source_address` |
| 数据库类型 | `varchar(1000)` |
前端不需要根据设备分类判断是否展示该字段。
## 2. 接口公共约定
资产接口基础路径:
```text
/Assets/v1/asset
```
请求头:
```http
Authorization: Bearer <token>
Content-Type: application/json
```
统一响应结构:
```ts
interface ApiResponse<T> {
code: number
message: string
details: T
timeseq: number
}
```
`code === 0` 表示请求成功,业务数据位于 `details`
## 3. 前端类型
在资产表单类型中增加 `source_address`
```ts
export interface AssetForm {
id?: number
asset_name: string
asset_code: string
source_address?: string
}
```
当前项目已经在 `src/api/ops/asset.ts``AssetForm` 中加入该字段。其他以资产对象为基础的详情、列表和批量导入数据可以直接复用该字段。
## 4. 创建设备资产
### 4.1 接口
```http
POST /Assets/v1/asset/create
```
### 4.2 请求示例
```json
{
"asset_name": "东门监控摄像头",
"asset_code": "CAMERA-001",
"category_id": 12,
"model": "DS-2CD3T",
"manufacturer": "海康威视",
"serial_number": "SN20260817001",
"source_address": "rtsp://192.168.1.100:554/Streaming/Channels/101",
"status": "in_use"
}
```
未填写源地址时可以省略 `source_address`,也可以传空字符串:
```json
{
"asset_name": "普通设备",
"asset_code": "DEVICE-001",
"source_address": ""
}
```
### 4.3 成功响应
```json
{
"code": 0,
"message": "",
"details": {
"id": 101,
"asset_name": "东门监控摄像头",
"asset_code": "CAMERA-001",
"source_address": "rtsp://192.168.1.100:554/Streaming/Channels/101",
"status": "in_use"
},
"timeseq": 1786932000000
}
```
### 4.4 调用方式
```ts
import { createAsset, type AssetForm } from '@/api/ops/asset'
const data: AssetForm = {
asset_name: '东门监控摄像头',
asset_code: 'CAMERA-001',
source_address: 'rtsp://192.168.1.100:554/Streaming/Channels/101',
status: 'in_use',
}
const response = await createAsset(data)
if (response.code !== 0) {
throw new Error(response.message || '创建设备失败')
}
```
## 5. 更新设备资产
### 5.1 接口
```http
PUT /Assets/v1/asset/update
```
### 5.2 请求示例
```json
{
"id": 101,
"asset_name": "东门监控摄像头",
"asset_code": "CAMERA-001",
"source_address": "https://video.example.com/live/camera-001.m3u8",
"status": "in_use"
}
```
更新接口需要同时传入:
- `id`
- `asset_name`
- `asset_code`
`source_address` 仍为选填字段。传入非空值时更新源地址;不传时保留原值。
当前后端使用 GORM 结构体更新,空字符串属于零值,因此传入 `source_address: ""` 不会清空已有源地址。前端暂时不要把“传空字符串”作为清空操作。
### 5.3 调用方式
```ts
import { updateAsset, type AssetForm } from '@/api/ops/asset'
const data: AssetForm = {
id: 101,
asset_name: '东门监控摄像头',
asset_code: 'CAMERA-001',
source_address: 'https://video.example.com/live/camera-001.m3u8',
status: 'in_use',
}
const response = await updateAsset(data)
if (response.code !== 0) {
throw new Error(response.message || '更新设备失败')
}
```
## 6. 查询设备详情
### 6.1 接口
```http
GET /Assets/v1/asset/detail/{id}
```
### 6.2 成功响应
```json
{
"code": 0,
"message": "",
"details": {
"id": 101,
"asset_name": "东门监控摄像头",
"asset_code": "CAMERA-001",
"source_address": "rtsp://192.168.1.100:554/Streaming/Channels/101",
"status": "in_use"
},
"timeseq": 1786932000000
}
```
未填写源地址时,`source_address` 返回空字符串。
### 6.3 表单回填
当前编辑页面使用详情对象整体回填:
```ts
const response = await fetchAssetDetail(deviceId)
if (response.code === 0) {
form.value = { ...response.details } as AssetForm
}
```
`source_address` 会随其他资产字段自动回填,不需要单独请求。
## 7. 分页查询设备资产
### 7.1 接口
```http
POST /Assets/v1/asset/list
```
### 7.2 请求示例
```json
{
"page": 1,
"page_size": 20,
"keyword": "",
"status": "in_use"
}
```
### 7.3 响应示例
```json
{
"code": 0,
"message": "",
"details": {
"total": 1,
"page": 1,
"page_size": 20,
"data": [
{
"id": 101,
"asset_name": "东门监控摄像头",
"asset_code": "CAMERA-001",
"source_address": "rtsp://192.168.1.100:554/Streaming/Channels/101"
}
]
},
"timeseq": 1786932000000
}
```
以下接口返回的资产对象同样包含 `source_address`
```http
GET /Assets/v1/asset/page
GET /Assets/v1/asset/all
GET /Assets/v1/asset/export
```
## 8. 批量导入
### 8.1 接口
```http
POST /Assets/v1/asset/bulk_import
```
### 8.2 请求示例
```json
{
"dry_run": false,
"items": [
{
"asset_name": "西门监控摄像头",
"asset_code": "CAMERA-002",
"source_address": "rtsp://192.168.1.101:554/live",
"status": "in_use"
}
]
}
```
每个 `items` 元素都可以单独填写或省略 `source_address`
## 9. 表单接入
### 9.1 默认值
```ts
const form = ref<AssetForm>({
asset_name: '',
asset_code: '',
source_address: '',
})
```
### 9.2 表单组件
```vue
<a-form-item label="源地址" field="source_address">
<a-input
v-model="form.source_address"
placeholder="请输入源地址"
:max-length="1000"
allow-clear
/>
</a-form-item>
```
该字段不设置 `required` 规则,也不校验具体协议格式。
### 9.3 提交
新增和编辑直接复用现有资产提交逻辑:
```ts
const data = { ...form.value }
if (isEdit.value && deviceId.value) {
await updateAsset({ ...data, id: deviceId.value })
} else {
await createAsset(data)
}
```
## 10. 详情展示
```vue
<a-descriptions-item label="源地址" :span="2">
<span class="source-address">{{ device?.source_address || '-' }}</span>
</a-descriptions-item>
```
长地址需要允许换行:
```css
.source-address {
word-break: break-all;
}
```
## 11. 播放功能使用建议
获取播放地址时直接读取资产对象的 `source_address`
```ts
const sourceAddress = record.source_address?.trim()
if (!sourceAddress) {
Message.warning('该设备未配置源地址')
return
}
```
前端不要自动补全协议、转换大小写或修改地址内容。具体播放器需要支持对应的流媒体协议,例如浏览器不能直接原生播放 RTSP 地址。
## 12. 对接检查项
- 新增设备时可以不填写源地址。
- 新增设备时填写源地址后可以正常保存。
- 编辑设备时能够正确回填源地址。
- 编辑设备时修改源地址后可以正常保存。
- 详情接口和详情弹窗能够显示源地址。
- 列表接口返回的每条资产数据包含 `source_address`
- 超长地址能够在详情区域正常换行。
- 接口失败时优先显示响应中的 `message`

View File

@@ -1,24 +1,132 @@
import { mergeConfig } from 'vite'
import type { IncomingMessage } from 'node:http'
import { loadEnv, mergeConfig, type ConfigEnv, type ProxyOptions } from 'vite'
// import eslint from 'vite-plugin-eslint'
import baseConfig from './vite.config.base'
export default mergeConfig(
{
mode: 'development',
server: {
open: true,
host: '0.0.0.0',
fs: {
strict: true,
},
const HIKVISION_PROXY_TIMEOUT = 8000
const proxyTarget = (port: number) => ({
target: `http://127.0.0.1:${port}`,
changeOrigin: true,
})
/** 读取海康 WebSDK 写入的代理目标 Cookie。 */
function getProxyCookie(request: IncomingMessage, name: string): string {
const cookie = request.headers.cookie || ''
const matched = cookie.match(new RegExp(`(?:^|;\\s*)${name}=([^;]+)`))
if (!matched) return ''
try {
return decodeURIComponent(matched[1])
} catch (_error) {
return ''
}
}
/** 从 SDK Cookie 解析 WebSocket 端口,并限制只能代理到已配置的录像机。 */
function resolveHikvisionWebSocketTarget(request: IncomingMessage, allowedHost: string, fallbackTarget: string): string {
const secureAddress = getProxyCookie(request, 'webVideoCtrlProxyWss')
const address = secureAddress || getProxyCookie(request, 'webVideoCtrlProxyWs')
if (!address) return fallbackTarget
try {
const target = new URL(`${secureAddress ? 'wss' : 'ws'}://${address}`)
return target.hostname.toLowerCase() === allowedHost.toLowerCase() ? target.toString() : fallbackTarget
} catch (_error) {
return fallbackTarget
}
}
/** 创建开发环境 Vite 配置。 */
export default ({ mode }: ConfigEnv) => {
const env = loadEnv(mode, process.cwd(), '')
const apiProxyTarget = env.VITE_API_PROXY_TARGET || 'https://ops-api.apinb.com'
const proxy: Record<string, ProxyOptions> = {
'/api': {
target: apiProxyTarget,
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
plugins: [
// eslint({
// cache: false,
// include: ['src/**/*.ts', 'src/**/*.tsx', 'src/**/*.vue'],
// exclude: ['node_modules'],
// }),
],
},
baseConfig
)
'/rbac2': proxyTarget(10001),
'/Alert': proxyTarget(12427),
'/alert': proxyTarget(12427),
'/DC-Control': proxyTarget(3031),
'/dc-control': proxyTarget(3031),
'/dc-network': proxyTarget(12429),
'/DC-Hardware': proxyTarget(12450),
'/dc-hardware': proxyTarget(12450),
'/dc-host': proxyTarget(9030),
'/dc-middleware': proxyTarget(12428),
'/dc-database': proxyTarget(12580),
'/Feedback': proxyTarget(12432),
'/feedback': proxyTarget(12432),
'/Assets': proxyTarget(12430),
'/assets': proxyTarget(12430),
'/Logs': proxyTarget(12440),
'/logs': proxyTarget(12440),
'/Kb': proxyTarget(12434),
'/kb': proxyTarget(12434),
'/Mgt': proxyTarget(12436),
'/mgt': proxyTarget(12436),
'/Visual': proxyTarget(12438),
'/visual': proxyTarget(12438),
}
if (env.VITE_HIKVISION_PROXY_TARGET) {
const hikvisionHttpTarget = new URL(env.VITE_HIKVISION_PROXY_TARGET)
const hikvisionWebSocketTarget =
env.VITE_HIKVISION_WS_PROXY_TARGET || `${hikvisionHttpTarget.protocol === 'https:' ? 'wss' : 'ws'}://${hikvisionHttpTarget.host}`
proxy['/ISAPI'] = {
target: env.VITE_HIKVISION_PROXY_TARGET,
changeOrigin: true,
secure: false,
timeout: HIKVISION_PROXY_TIMEOUT,
proxyTimeout: HIKVISION_PROXY_TIMEOUT,
}
proxy['/SDK'] = {
target: env.VITE_HIKVISION_PROXY_TARGET,
changeOrigin: true,
secure: false,
timeout: HIKVISION_PROXY_TIMEOUT,
proxyTimeout: HIKVISION_PROXY_TIMEOUT,
}
proxy['/webSocketVideoCtrlProxy'] = {
target: hikvisionWebSocketTarget,
changeOrigin: true,
secure: false,
ws: true,
timeout: HIKVISION_PROXY_TIMEOUT,
proxyTimeout: HIKVISION_PROXY_TIMEOUT,
router: env.VITE_HIKVISION_WS_PROXY_TARGET
? undefined
: (request) => resolveHikvisionWebSocketTarget(request, hikvisionHttpTarget.hostname, hikvisionWebSocketTarget),
rewrite: (path) => path.replace(/^\/webSocketVideoCtrlProxy/, '/'),
}
}
return mergeConfig(
{
mode: 'development',
server: {
open: true,
host: '0.0.0.0',
headers: {
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Resource-Policy': 'cross-origin',
},
fs: {
strict: true,
},
proxy,
},
plugins: [
// eslint({
// cache: false,
// include: ['src/**/*.ts', 'src/**/*.tsx', 'src/**/*.vue'],
// exclude: ['node_modules'],
// }),
],
},
baseConfig
)
}

24
dev/components.d.ts vendored Normal file
View File

@@ -0,0 +1,24 @@
/* eslint-disable */
// @ts-nocheck
// biome-ignore lint: disable
// oxlint-disable
// ------
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
import { GlobalComponents } from 'vue'
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
}
// For TSX support
declare global {
const RouterLink: typeof import('vue-router')['RouterLink']
const RouterView: typeof import('vue-router')['RouterView']
}

View File

@@ -2,7 +2,7 @@
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="shortcut icon" type="image/x-icon" href="https://unpkg.byted-static.com/latest/byted/arco-config/assets/favicon.ico" />
<link rel="icon" type="image/svg+xml" href="%BASE_URL%favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>智能运维管理系统</title>
</head>

28
kilo.json Normal file
View File

@@ -0,0 +1,28 @@
{
"agents": {
"default": "code",
"allowed": ["code", "architect", "ask", "debug"]
},
"commands": {
"dir": ".kilo/command"
},
"rules": {
"dir": ".kilo/agent"
},
"skills": {
"dir": ".kilo/skill"
},
"hooks": {
"beforeFileWrite": "pnpm lint --fix",
"afterTaskComplete": "pnpm lint"
},
"context": {
"maxTokens": 8000,
"includePatterns": ["src/**/*.ts", "src/**/*.vue", "src/**/*.tsx"],
"excludePatterns": ["node_modules", "dist", ".git"]
},
"logging": {
"level": "info",
"file": ".kilo/logs/kilo.log"
}
}

15673
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,7 @@
"dependencies": {
"@arco-design/web-vue": "^2.57.0",
"@tabler/icons-vue": "^3.40.0",
"@tweenjs/tween.js": "18.6.0",
"@vue-flow/background": "^1.3.2",
"@vue-flow/controls": "^1.1.3",
"@vue-flow/core": "^1.48.2",
@@ -33,6 +34,7 @@
"pinia": "^3.0.4",
"query-string": "^9.3.1",
"sortablejs": "^1.15.7",
"three": "0.115.0",
"uuid": "^13.0.0",
"vue": "^3.5.29",
"vue-echarts": "^8.0.1",
@@ -72,7 +74,6 @@
"rollup": "^4.59.0",
"rollup-plugin-visualizer": "^7.0.1",
"stylelint": "^17.4.0",
"stylelint-config-prettier": "^9.0.5",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-config-recommended-vue": "^1.6.1",
"stylelint-config-standard": "^40.0.0",

3351
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

5
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,5 @@
allowBuilds:
esbuild: true
less: true
unrs-resolver: true
vue-demi: true

4
public/favicon.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="33" height="33">
<path d="M0 240.941176a240.941176 240.941176 0 0 1 240.941176-240.941176h542.117648a240.941176 240.941176 0 0 1 240.941176 240.941176v542.117648a240.941176 240.941176 0 0 1-240.941176 240.941176H240.941176a240.941176 240.941176 0 0 1-240.941176-240.941176V240.941176z" fill="#287BF7"></path>
<path d="M340.931765 388.969412a120.621176 120.621176 0 1 0 115.260235 209.799529l70.776471 97.490824a241.091765 241.091765 0 1 1 82.522353-283.587765c15.751529 39.936 14.516706 83.546353 13.763764 110.290824-0.150588 4.879059-0.271059 9.216-0.271059 12.860235 0 22.648471 5.812706 48.519529 18.221177 66.831059 10.24 15.058824 26.654118 28.310588 61.108706 28.310588 31.804235 0 49.152-10.721882 59.331764-22.40753 11.264-12.950588 18.974118-33.370353 18.974118-59.452235 0-18.642824-9.396706-39.574588-27.166118-53.067294-16.022588-12.137412-43.550118-21.594353-86.768941-8.734118l-34.42447-115.440941c75.926588-22.618353 144.112941-9.637647 193.987764 28.190118 48.128 36.502588 74.842353 93.093647 74.842353 149.082353 0 48.489412-14.245647 99.087059-48.609882 138.541176-35.418353 40.719059-87.341176 63.728941-150.166588 63.728941-75.294118 0-128.662588-33.792-160.768-81.106823-29.936941-44.062118-39.032471-95.864471-39.032471-134.475294 0-9.396706 0.150588-17.588706 0.240941-24.847059 0.421647-25.057882 0.632471-38.972235-5.330823-54.091294a120.621176 120.621176 0 0 0-156.491294-67.915294z" fill="#FFFFFF"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -0,0 +1,962 @@
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>:04.08.2026 13:21:15
newmtl Mat3d66_10994759_12_33214
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.3686 0.3686 0.3686
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Mat3d66_10994759_11_76789
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 1.0000 1.0000 1.0000
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka maps\Map__107_Noise.tga
map_Kd maps\Map__107_Noise.tga
map_bump -bm 0.2000 maps\Map__106_Noise.tga
newmtl Mat3d66_10994759_30_54055
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.6667 0.0000 0.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka maps\3d66_17938251_color_VR-<2D><>ɫ.tga
map_Kd maps\3d66_17938251_color_VR-<2D><>ɫ.tga
map_bump -bm 0.0500 maps\3d66_17938251_color_VR-<2D><>ɫ.tga
newmtl Mat3d66_11807706_26_30152
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0196 0.0196 0.0196
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_bump maps\3d66Model-11807706-files-51.png
bump maps\3d66Model-11807706-files-51.png
newmtl Mat3d66_10994759_29_37910
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.6078 0.0000 0.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Mat3d66_10994759_22_2dad4012
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.1373 0.1373 0.1373
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Mat3d66_10994759_34_15870
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.6392 0.6392 0.6392
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka maps\3d66Model-10994759-files-1.jpg
map_Kd maps\3d66Model-10994759-files-1.jpg
newmtl Mat3d66_10994759_22_24012
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0039 0.0039 0.0039
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Mat3d66_10994759_37_59834
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0000 0.0000 0.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl wire_198225087
Ns 32
d 1
Tr 0
Tf 1 1 1
illum 2
Ka 0.7765 0.8824 0.3412
Kd 0.7765 0.8824 0.3412
Ks 0.3500 0.3500 0.3500
newmtl Mat3d66_10994759_35_21635
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0588 0.0588 0.0588
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl wire_115115115
Ns 32
d 1
Tr 0
Tf 1 1 1
illum 2
Ka 0.4510 0.4510 0.4510
Kd 0.4510 0.4510 0.4510
Ks 0.3500 0.3500 0.3500
newmtl Mat3d66_10994759_25_25429
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.8549 0.9098 0.9412
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka maps\3d66Model-10994759-files-13.jpg
map_Kd maps\3d66Model-10994759-files-13.jpg
map_bump maps\3d66Model-10994759-files-13.jpg
bump maps\3d66Model-10994759-files-13.jpg
newmtl wire_000000000
Ns 32
d 1
Tr 0
Tf 1 1 1
illum 2
Ka 0.0000 0.0000 0.0000
Kd 0.0000 0.0000 0.0000
Ks 0.3500 0.3500 0.3500
newmtl 3d66_VRayMtl_23563919_021
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.7529 0.7647 0.7294
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 3d66_VRayMtl_23563919_022
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.5961 0.6039 0.5765
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 3d66_VRayMtl_23563919_018
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.6314 0.0392 0.0392
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 3d66_VRayMtl_23563919_019
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0000 0.0000 0.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 3d66_VRayMtl_23563919_020
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0275 0.0275 0.0275
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl <20><>ҵ<EFBFBD><D2B5><EFBFBD>Ұ<EFBFBD>ɫ<EFBFBD>߹<EFBFBD><DFB9><EFBFBD><E2BBAC>ƺ<EFBFBD><C6BA>_Ϳ<5F><CDBF>_VR
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.3922 0.3922 0.3922
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka maps\3d66Mat-14871359-maps-3.jpg
map_Kd maps\3d66Mat-14871359-maps-3.jpg
map_bump -bm 0.0150 maps\<5C><>ͼ__123_Noise.tga
newmtl <20>ִ<EFBFBD><D6B4><EFBFBD>ɫ<EFBFBD>ƹ<EFBFBD><C6B9><EFBFBD><E2BBAC><EFBFBD><EFBFBD>_<EFBFBD><5F>_VR
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0196 0.0196 0.0196
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Mat3d66_11807706_4_39763
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0196 0.0196 0.0196
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Mat3d66_11807706_5_89594
Ns 41.0000
Ni 1.5000
d 0.8000
Tr 0.2000
Tf 0.8000 0.8000 0.8000
illum 2
Ka 0.9725 0.0000 0.0000
Kd 0.9725 0.0000 0.0000
Ks 0.3960 0.3960 0.3960
Ke 0.0000 0.0000 0.0000
newmtl wire_225198087
Ns 32
d 1
Tr 0
Tf 1 1 1
illum 2
Ka 0.8824 0.7765 0.3412
Kd 0.8824 0.7765 0.3412
Ks 0.3500 0.3500 0.3500
newmtl Mat3d66_11807706_7_95731
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.5333 0.5294 0.5255
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Mat3d66_11807706_8_21117
Ns 76.0000
Ni 1.5000
d 0.4000
Tr 0.6000
Tf 0.4000 0.4000 0.4000
illum 2
Ka 0.1608 0.1569 0.1843
Kd 0.1608 0.1569 0.1843
Ks 0.7920 0.7920 0.7920
Ke 0.0000 0.0000 0.0000
map_refl maps\Map__614327418_VR-<2D><>ͼ.tga
newmtl Mat3d66_11807706_9_83349
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0863 0.0745 0.0667
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Mat3d66_11807706_10_61673
Ns 76.0000
Ni 1.5000
d 0.0500
Tr 0.9500
Tf 0.0500 0.0500 0.0500
illum 2
Ka 0.1608 0.1569 0.1843
Kd 0.1608 0.1569 0.1843
Ks 0.7920 0.7920 0.7920
Ke 0.0000 0.0000 0.0000
map_refl maps\Map__614327418_VR-<2D><>ͼ.tga
newmtl Mat3d66_11807706_11_67368
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0863 0.0745 0.0667
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka maps\3d66Model-11807706-files-3.jpg
map_Kd maps\3d66Model-11807706-files-3.jpg
newmtl 22___Default
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0039 0.0039 0.0039
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Mat3d66_11807706_29_27585
Ns 38.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5882 0.5882 0.5882
Kd 0.5882 0.5882 0.5882
Ks 0.1800 0.1800 0.1800
Ke 0.0706 0.0706 0.0706
map_Ka maps\3d66Model-11807706-files-60.jpg
map_Kd maps\3d66Model-11807706-files-60.jpg
map_bump maps\3d66Model-11807706-files-60.jpg
bump maps\3d66Model-11807706-files-60.jpg
newmtl Mat3d66_11807706_120_47020
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.5000 0.5000 0.5000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka maps\Map__1_<31><5F>ɫУ<C9AB><D0A3>.tga
map_Kd maps\Map__1_<31><5F>ɫУ<C9AB><D0A3>.tga
map_bump maps\Map__1_<31><5F>ɫУ<C9AB><D0A3>.tga
newmtl wire_010140195
Ns 32
d 1
Tr 0
Tf 1 1 1
illum 2
Ka 0.0392 0.5490 0.7647
Kd 0.0392 0.5490 0.7647
Ks 0.3500 0.3500 0.3500
newmtl Mat3d66_11807706_28_26251
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.8941 0.8941 0.8941
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Mat3d66_15185da96_31_5088
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0275 0.0275 0.0235
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Mat3d66_10994759_49_69999
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.1373 0.1373 0.1373
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Mat3d66_10994759_31_26985
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0392 0.0392 0.0392
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_bump maps\3d66Model-10994759-files-15.JPG
bump maps\3d66Model-10994759-files-15.JPG
newmtl 24___Default
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.5000 0.5000 0.5000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka maps\t7.jpg
map_Kd maps\t7.jpg
newmtl 3d66_VRayMtl_20092954_028
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.9216 0.9216 0.9216
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 3d66_VRayMtl_20092954_034
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.1176 0.1176 0.1176
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Mat3d66_7566050_1_94391
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.9020 0.9020 0.9020
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Mat3d66_1518596_31_5088
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.6471 0.6235 0.6000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 3d66_VRayMtl_23605226_030
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.5882 0.5882 0.5882
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 3d66_VRayMtl_23605226_031
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.5000 0.5000 0.5000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka maps\3d66Model-23605226-files-005.png
map_Kd maps\3d66Model-23605226-files-005.png
map_bump maps\3d66_VRayNormalMap_23605226_023_VR-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ.tga
newmtl Mat3d66_7859414_4_42689
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.3529 0.3529 0.3529
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Mat3d66_7859414_3_41489
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.8627 0.8549 0.8471
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Mat3d66_7859414_7_21754
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.1765 0.1765 0.1765
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Mat3d66_7859414_5_93165
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.8235 0.8196 0.8157
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl wire_135006006
Ns 32
d 1
Tr 0
Tf 1 1 1
illum 2
Ka 0.5294 0.0235 0.0235
Kd 0.5294 0.0235 0.0235
Ks 0.3500 0.3500 0.3500
newmtl 3d66_VRayMtl_22568095_046
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0000 0.0000 0.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 3d66_VRayMtl_22568095_047
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0275 0.0275 0.0275
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 3d66_VRayMtl_22568095_045
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.6314 0.0392 0.0392
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 3d66_VRayMtl_22568095_048
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.7529 0.7647 0.7294
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 3d66_VRayMtl_22568095_049
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.5961 0.6039 0.5765
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 3d66_VRayMtl_22568095_052
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0510 0.0510 0.0510
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 3d66_VRayMtl_22568095_069
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 3d66_VRayMtl_22568095_070
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.7176 0.7882 0.8510
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 3d66_VRayMtl_22568095_071
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0000 0.0000 0.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 3d66_VRayMtl_22568095_074
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.5000 0.5000 0.5000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka maps\3d66_Bricks_22568095_021_Tiles.tga
map_Kd maps\3d66_Bricks_22568095_021_Tiles.tga
newmtl 3d66_VRayMtl_22568095_025
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0902 0.0902 0.0902
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 3d66_Standardmaterial_22568095_004
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5882 0.5882 0.5882
Kd 0.5882 0.5882 0.5882
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 3d66_VRayMtl_22568095_066
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0392 0.0392 0.0392
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl 17___Default
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.5000 0.5000 0.5000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka maps\t3.jpg
map_Kd maps\t3.jpg
newmtl 18___Default
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.5000 0.5000 0.5000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka maps\t5.jpg
map_Kd maps\t5.jpg
newmtl 16___Default
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.5000 0.5000 0.5000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka maps\t2.jpg
map_Kd maps\t2.jpg
newmtl 15___Default
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.5000 0.5000 0.5000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka maps\t1.jpg
map_Kd maps\t1.jpg
newmtl Mat3d66_14662737_11_91260
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka maps\3d66Model-14662737-files-5.jpg
map_Kd maps\3d66Model-14662737-files-5.jpg
newmtl Mat3d66_14662737_12_48868
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 1.0000 1.0000 1.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka maps\3d66Model-14662737-files-5.jpg
map_Kd maps\3d66Model-14662737-files-5.jpg
newmtl wire_154185229
Ns 32
d 1
Tr 0
Tf 1 1 1
illum 2
Ka 0.6039 0.7255 0.8980
Kd 0.6039 0.7255 0.8980
Ks 0.3500 0.3500 0.3500
newmtl Glass
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0000 0.0000 0.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Mat3d66_8099876_13_28564
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.4667 0.4510 0.4353
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Material__53
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0000 0.0000 0.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl Material__52
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.1451 0.1451 0.1451
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka maps\3d66Model-8099876-files-4.jpg
map_Kd maps\3d66Model-8099876-files-4.jpg
newmtl Material__109
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.0000 0.0000 0.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl <20><><EFBFBD>ݧ<EFBFBD><DDA7>ާڧߧڧ<DFA7>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD>ݧڧ<DDA7><DAA7><EFBFBD><EFBFBD>ӧѧߧߧ<DFA7><DFA7><EFBFBD>
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.1872 0.1872 0.1872
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
newmtl <20><><EFBFBD><EFBFBD><EFBFBD>ݧ<EFBFBD>
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5880 0.5880 0.5880
Kd 0.7529 0.7529 0.7529
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_d maps\Map__19_Falloff.tga
newmtl wire_154215229
Ns 32
d 1
Tr 0
Tf 1 1 1
illum 2
Ka 0.6039 0.8431 0.8980
Kd 0.6039 0.8431 0.8980
Ks 0.3500 0.3500 0.3500

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,621 @@
(function() {
var Nr = 10;
// convert two-dimensional indicies to one-dim array indices
var I00 = 0;
var I01 = 1;
var I02 = 2;
var I03 = 3;
var I10 = 4;
var I11 = 5;
var I12 = 6;
var I13 = 7;
var I20 = 8;
var I21 = 9;
var I22 = 10;
var I23 = 11;
var I30 = 12;
var I31 = 13;
var I32 = 14;
var I33 = 15;
// S-Box substitution table
var S_enc = new Array(
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16);
// inverse S-Box for decryptions
var S_dec = new Array(
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38,
0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87,
0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d,
0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2,
0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16,
0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda,
0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a,
0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea,
0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85,
0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89,
0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20,
0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31,
0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d,
0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0,
0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26,
0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d);
function cvt_hex8 (val) {
var vh = (val>>>4)&0x0f;
return vh.toString(16) + (val&0x0f).toString(16);
}
function cvt_byte (str) {
// get the first hex digit
var val1 = str.charCodeAt(0);
// do some error checking
if (val1 >= 48 && val1 <= 57) {
// have a valid digit 0-9
val1 -= 48;
} else if (val1 >= 65 && val1 <= 70) {
// have a valid digit A-F
val1 -= 55;
} else if (val1 >= 97 && val1 <= 102) {
// have a valid digit A-F
val1 -= 87;
} else {
// not 0-9 or A-F, complain
console.log( str.charAt(1)+" is not a valid hex digit" );
return -1;
}
// get the second hex digit
var val2 = str.charCodeAt(1);
// do some error checking
if ( val2 >= 48 && val2 <= 57 ) {
// have a valid digit 0-9
val2 -= 48;
} else if ( val2 >= 65 && val2 <= 70 ) {
// have a valid digit A-F
val2 -= 55;
} else if ( val2 >= 97 && val2 <= 102 ) {
// have a valid digit A-F
val2 -= 87;
} else {
// not 0-9 or A-F, complain
console.log( str.charAt(2)+" is not a valid hex digit" );
return -1;
}
// all is ok, return the value
return val1*16 + val2;
}
// conversion function for non-constant subscripts
// assume subscript range 0..3
function I(x,y) {
return (x*4) + y;
}
// remove spaces from input
function remove_spaces(instr) {
var i;
var outstr = "";
for(i=0; i<instr.length; i++) {
if ( instr.charAt(i) != " " )
// not a space, include it
outstr += instr.charAt(i);
}
return outstr;
}
// get the message to encrypt/decrypt or the key
// return as a 16-byte array
function get_value(str, isASCII) {
var dbyte = new Array(16);
var i;
var val; // one hex digit
if (isASCII) {
// check length of data
if (str.length > 16) {
console.log("is too long, using the first 16 ASCII characters" );
}
// have ASCII data
// 16 characters?
if (str.length >= 16) {
// 16 or more characters
for(i=0; i<16; i++) {
dbyte[i] = str.charCodeAt(i);
}
} else {
// less than 16 characters - fill with NULLs
for(i=0; i<str.length; i++) {
dbyte[i] = str.charCodeAt(i);
}
for( i=str.length; i<16; i++) {
dbyte[i] = 0;
}
}
} else {
// have hex data - remove any spaces they used, then convert
//str = remove_spaces(str);
// check length of data
if ( str.length != 32 ) {
//console.log("\tget_value:\tstr = " + str + "\tisASCII = " + isASCII); //isASCII = false
console.log("length wrong: Is " + str.length + " hex digits, but must be 128 bits (32 hex digits)");
dbyte[0] = -1;
return dbyte;
}
for( i=0; i<16; i++ ) {
// isolate and convert this substring
dbyte[i] = cvt_byte( str.substr(i*2,2) );
if( dbyte[i] < 0 ) {
// have an error
dbyte[0] = -1;
return dbyte;
}
}
}
// return successful conversion
return dbyte;
}
//do the AES GF(2**8) multiplication
// do this by the shift-and-"add" approach
function aes_mul(a, b) {
var res = 0;
while(a > 0) {
if((a&1) != 0)
res = res ^ b; // "add" to the result
a >>>= 1; // shift a to get next higher-order bit
b <<= 1; // shift multiplier also
}
// now reduce it modulo x**8 + x**4 + x**3 + x + 1
var hbit = 0x10000; // bit to test if we need to take action
var modulus = 0x11b00; // modulus - XOR by this to change value
while(hbit >= 0x100) {
if ((res & hbit) != 0) {
res ^= modulus; // XOR with the modulus
}
// prepare for the next loop
hbit >>= 1;
modulus >>= 1;
}
return res;
}
// apply the S-box substitution to the key expansion
function SubWord(word_ary) {
var i;
for(i=0; i<16; i++) {
word_ary[i] = S_enc[word_ary[i]];
}
return word_ary;
}
// rotate the bytes in a word
function RotWord(word_ary) {
return new Array(word_ary[1], word_ary[2], word_ary[3], word_ary[0]);
}
// calculate the first item Rcon[i] = { x^(i-1), 0, 0, 0 }
// note we only return the first item
function Rcon(exp) {
var val = 2;
var result = 1;
// remember to calculate x^(exp-1)
exp--;
// process the exponent using normal shift and multiply
while ( exp > 0 )
{
if ( (exp & 1) != 0 )
result = aes_mul( result, val );
// square the value
val = aes_mul( val, val );
// move to the next bit
exp >>= 1;
}
return result;
}
// round key generation
// return a byte array with the expanded key information
function key_expand( key )
{
var temp = new Array(4);
var i, j;
var w = new Array(4*(Nr+1));
// copy initial key stuff
for( i=0; i<16; i++ )
{
w[i] = key[i];
}
// generate rest of key schedule using 32-bit words
i = 4;
while ( i < 4*(Nr+1)) // blocksize * ( rounds + 1 )
{
// copy word W[i-1] to temp
for( j=0; j<4; j++ )
temp[j] = w[(i-1)*4+j];
if ( i % 4 == 0)
{
// temp = SubWord(RotWord(temp)) ^ Rcon[i/4];
temp = RotWord( temp );
temp = SubWord( temp );
temp[0] ^= Rcon( i>>>2 );
}
// word = word ^ temp
for( j=0; j<4; j++ )
w[i*4+j] = w[(i-4)*4+j] ^ temp[j];
i++;
}
return w;
}
// do S-Box substitution
function SubBytes(state, Sbox)
{
var i;
for( i=0; i<16; i++ )
state[i] = Sbox[ state[i] ];
return state;
}
// shift each row as appropriate
function ShiftRows(state)
{
var t0, t1, t2, t3;
// top row (row 0) isn't shifted
// next row (row 1) rotated left 1 place
t0 = state[I10];
t1 = state[I11];
t2 = state[I12];
t3 = state[I13];
state[I10] = t1;
state[I11] = t2;
state[I12] = t3;
state[I13] = t0;
// next row (row 2) rotated left 2 places
t0 = state[I20];
t1 = state[I21];
t2 = state[I22];
t3 = state[I23];
state[I20] = t2;
state[I21] = t3;
state[I22] = t0;
state[I23] = t1;
// bottom row (row 3) rotated left 3 places
t0 = state[I30];
t1 = state[I31];
t2 = state[I32];
t3 = state[I33];
state[I30] = t3;
state[I31] = t0;
state[I32] = t1;
state[I33] = t2;
return state;
}
// inverset shift each row as appropriate
function InvShiftRows(state)
{
var t0, t1, t2, t3;
// top row (row 0) isn't shifted
// next row (row 1) rotated left 1 place
t0 = state[I10];
t1 = state[I11];
t2 = state[I12];
t3 = state[I13];
state[I10] = t3;
state[I11] = t0;
state[I12] = t1;
state[I13] = t2;
// next row (row 2) rotated left 2 places
t0 = state[I20];
t1 = state[I21];
t2 = state[I22];
t3 = state[I23];
state[I20] = t2;
state[I21] = t3;
state[I22] = t0;
state[I23] = t1;
// bottom row (row 3) rotated left 3 places
t0 = state[I30];
t1 = state[I31];
t2 = state[I32];
t3 = state[I33];
state[I30] = t1;
state[I31] = t2;
state[I32] = t3;
state[I33] = t0;
return state;
}
// process column info
function MixColumns(state)
{
var col;
var c0, c1, c2, c3;
for( col=0; col<4; col++ )
{
c0 = state[I(0,col)];
c1 = state[I(1,col)];
c2 = state[I(2,col)];
c3 = state[I(3,col)];
// do mixing, and put back into array
state[I(0,col)] = aes_mul(2,c0) ^ aes_mul(3,c1) ^ c2 ^ c3;
state[I(1,col)] = c0 ^ aes_mul(2,c1) ^ aes_mul(3,c2) ^ c3;
state[I(2,col)] = c0 ^ c1 ^ aes_mul(2,c2) ^ aes_mul(3,c3);
state[I(3,col)] = aes_mul(3,c0) ^ c1 ^ c2 ^ aes_mul(2,c3);
}
return state;
}
// inverse process column info
function InvMixColumns(state)
{
var col;
var c0, c1, c2, c3;
for( col=0; col<4; col++ )
{
c0 = state[I(0,col)];
c1 = state[I(1,col)];
c2 = state[I(2,col)];
c3 = state[I(3,col)];
// do inverse mixing, and put back into array
state[I(0,col)] = aes_mul(0x0e,c0) ^ aes_mul(0x0b,c1)
^ aes_mul(0x0d,c2) ^ aes_mul(0x09,c3);
state[I(1,col)] = aes_mul(0x09,c0) ^ aes_mul(0x0e,c1)
^ aes_mul(0x0b,c2) ^ aes_mul(0x0d,c3);
state[I(2,col)] = aes_mul(0x0d,c0) ^ aes_mul(0x09,c1)
^ aes_mul(0x0e,c2) ^ aes_mul(0x0b,c3);
state[I(3,col)] = aes_mul(0x0b,c0) ^ aes_mul(0x0d,c1)
^ aes_mul(0x09,c2) ^ aes_mul(0x0e,c3);
}
return state;
}
// insert subkey information
function AddRoundKey( state, w, base )
{
var col;
for( col=0; col<4; col++ )
{
state[I(0,col)] ^= w[base+col*4];
state[I(1,col)] ^= w[base+col*4+1];
state[I(2,col)] ^= w[base+col*4+2];
state[I(3,col)] ^= w[base+col*4+3];
}
return state;
}
// return a transposed array
function transpose( msg )
{
var row, col;
var state = new Array( 16 );
for( row=0; row<4; row++ )
for( col=0; col<4; col++ )
state[I(row,col)] = msg[I(col,row)];
return state;
}
// final AES state
var AES_output = new Array(16);
// format AES output
// -- uses the global array DES_output
function format_AES_output(bASCII)
{
var i;
var bits;
var str="";
// what type of data do we have to work with?
if (bASCII)
{
// convert each set of bits back to ASCII
for( i=0; i<16; i++ )
str += String.fromCharCode( AES_output[i] );
}
else
{
// output hexdecimal data (insert spaces)
str = cvt_hex8( AES_output[0] );
for( i=1; i<16; i++ )
{
str += "" + cvt_hex8( AES_output[i] );
}
}
return str;
}
// do encrytion
function aes_encrypt(str, key, bASCII)
{
//console.log(" aes_encrypt:\tstr = " + str + "\tkey = " + key + "\t bASCII = " + bASCII);
var w = new Array( 4*(Nr+1) ); // subkey information
var state = new Array( 16 ); // working state
var round;
//accumulated_output_info = "";
// get the message from the user
// also check if it is ASCII or hex
var msg = get_value(str, bASCII);
// problems??
if ( msg[0] < 0 )
{
return;
}
// get the key from the user
var key = get_value(key, false);
// problems??
if ( key[0] < 0 )
{
return;
}
// expand the key
w = key_expand( key );
// initial state = message in columns (transposed from what we input)
state = transpose( msg );
// display the round key - Transpose due to the way it is stored/used
state = AddRoundKey(state, w, 0);
for( round=1; round<Nr; round++ )
{
state = SubBytes(state, S_enc);
state = ShiftRows(state);
state = MixColumns(state);
// display the round key - Transpose due to the way it is stored/used
// note here the spec uses 32-bit words, we are using bytes, so an extra *4
state = AddRoundKey(state, w, round*4*4);
}
SubBytes(state, S_enc);
ShiftRows(state);
AddRoundKey(state, w, Nr*4*4);
// process output
AES_output = transpose( state );
var szOutput = format_AES_output(!bASCII);
return szOutput;
}
// do decryption
function aes_decrypt(str, key, bASCII)
{
//console.log(" aes_decrypt:\tstr = " + str + "\tkey = " + key + "\tbASCII = " + bASCII);
var w = new Array( 4*(Nr+1) ); // subkey information
var state = new Array( 16 ); // working state
var round;
//accumulated_output_info = "";
// get the message from the user
// also check if it is ASCII or hex
var msg = get_value(str, bASCII);
// problems??
if ( msg[0] < 0 )
{
return;
}
// get the key from the user
var key = get_value(key, false);
// problems??
if ( key[0] < 0 )
{
return;
}
// expand the key
w = key_expand( key );
// initial state = message
state = transpose( msg );
// display the round key - Transpose due to the way it is stored/used
state = AddRoundKey(state, w, Nr*4*4);
for( round=Nr-1; round>=1; round-- )
{
state = InvShiftRows(state);
state = SubBytes(state, S_dec);
// display the round key - Transpose due to the way it is stored/used
// note here the spec uses 32-bit words, we are using bytes, so an extra *4
state = AddRoundKey(state, w, round*4*4);
state = InvMixColumns(state);
}
InvShiftRows(state);
SubBytes(state, S_dec);
AddRoundKey(state, w, 0);
// process output
AES_output = transpose( state );
var szOutput = format_AES_output(!bASCII);
return szOutput;
}
window.aes_encrypt = aes_encrypt;
window.aes_decrypt = aes_decrypt;
window.console = window.console || {
log: function() {}
};
}());

View File

@@ -0,0 +1,106 @@
var dbits,canary=244837814094590,j_lm=(canary&16777215)==15715070;function BigInteger(a,b,c){a!=null&&("number"==typeof a?this.fromNumber(a,b,c):b==null&&"string"!=typeof a?this.fromString(a,256):this.fromString(a,b))}function nbi(){return new BigInteger(null)}function am1(a,b,c,d,e,g){for(;--g>=0;){var h=b*this[a++]+c[d]+e,e=Math.floor(h/67108864);c[d++]=h&67108863}return e}
function am2(a,b,c,d,e,g){var h=b&32767;for(b>>=15;--g>=0;){var f=this[a]&32767,o=this[a++]>>15,p=b*f+o*h,f=h*f+((p&32767)<<15)+c[d]+(e&1073741823),e=(f>>>30)+(p>>>15)+b*o+(e>>>30);c[d++]=f&1073741823}return e}function am3(a,b,c,d,e,g){var h=b&16383;for(b>>=14;--g>=0;){var f=this[a]&16383,o=this[a++]>>14,p=b*f+o*h,f=h*f+((p&16383)<<14)+c[d]+e,e=(f>>28)+(p>>14)+b*o;c[d++]=f&268435455}return e}
j_lm&&navigator.appName=="Microsoft Internet Explorer"?(BigInteger.prototype.am=am2,dbits=30):j_lm&&navigator.appName!="Netscape"?(BigInteger.prototype.am=am1,dbits=26):(BigInteger.prototype.am=am3,dbits=28);BigInteger.prototype.DB=dbits;BigInteger.prototype.DM=(1<<dbits)-1;BigInteger.prototype.DV=1<<dbits;var BI_FP=52;BigInteger.prototype.FV=Math.pow(2,BI_FP);BigInteger.prototype.F1=BI_FP-dbits;BigInteger.prototype.F2=2*dbits-BI_FP;var BI_RM="0123456789abcdefghijklmnopqrstuvwxyz",BI_RC=[],rr,vv;
rr="0".charCodeAt(0);for(vv=0;vv<=9;++vv)BI_RC[rr++]=vv;rr="a".charCodeAt(0);for(vv=10;vv<36;++vv)BI_RC[rr++]=vv;rr="A".charCodeAt(0);for(vv=10;vv<36;++vv)BI_RC[rr++]=vv;function int2char(a){return BI_RM.charAt(a)}function intAt(a,b){var c=BI_RC[a.charCodeAt(b)];return c==null?-1:c}function bnpCopyTo(a){for(var b=this.t-1;b>=0;--b)a[b]=this[b];a.t=this.t;a.s=this.s}function bnpFromInt(a){this.t=1;this.s=a<0?-1:0;a>0?this[0]=a:a<-1?this[0]=a+DV:this.t=0}
function nbv(a){var b=nbi();b.fromInt(a);return b}
function bnpFromString(a,b){var c;if(b==16)c=4;else if(b==8)c=3;else if(b==256)c=8;else if(b==2)c=1;else if(b==32)c=5;else if(b==4)c=2;else{this.fromRadix(a,b);return}this.s=this.t=0;for(var d=a.length,e=!1,g=0;--d>=0;){var h=c==8?a[d]&255:intAt(a,d);h<0?a.charAt(d)=="-"&&(e=!0):(e=!1,g==0?this[this.t++]=h:g+c>this.DB?(this[this.t-1]|=(h&(1<<this.DB-g)-1)<<g,this[this.t++]=h>>this.DB-g):this[this.t-1]|=h<<g,g+=c,g>=this.DB&&(g-=this.DB))}if(c==8&&(a[0]&128)!=0)this.s=-1,g>0&&(this[this.t-1]|=(1<<
this.DB-g)-1<<g);this.clamp();e&&BigInteger.ZERO.subTo(this,this)}function bnpClamp(){for(var a=this.s&this.DM;this.t>0&&this[this.t-1]==a;)--this.t}
function bnToString(a){if(this.s<0)return"-"+this.negate().toString(a);if(a==16)a=4;else if(a==8)a=3;else if(a==2)a=1;else if(a==32)a=5;else if(a==64)a=6;else if(a==4)a=2;else return this.toRadix(a);var b=(1<<a)-1,c,d=!1,e="",g=this.t,h=this.DB-g*this.DB%a;if(g-- >0){if(h<this.DB&&(c=this[g]>>h)>0)d=!0,e=int2char(c);for(;g>=0;)h<a?(c=(this[g]&(1<<h)-1)<<a-h,c|=this[--g]>>(h+=this.DB-a)):(c=this[g]>>(h-=a)&b,h<=0&&(h+=this.DB,--g)),c>0&&(d=!0),d&&(e+=int2char(c))}return d?e:"0"}
function bnNegate(){var a=nbi();BigInteger.ZERO.subTo(this,a);return a}function bnAbs(){return this.s<0?this.negate():this}function bnCompareTo(a){var b=this.s-a.s;if(b!=0)return b;var c=this.t,b=c-a.t;if(b!=0)return b;for(;--c>=0;)if((b=this[c]-a[c])!=0)return b;return 0}function nbits(a){var b=1,c;if((c=a>>>16)!=0)a=c,b+=16;if((c=a>>8)!=0)a=c,b+=8;if((c=a>>4)!=0)a=c,b+=4;if((c=a>>2)!=0)a=c,b+=2;a>>1!=0&&(b+=1);return b}
function bnBitLength(){return this.t<=0?0:this.DB*(this.t-1)+nbits(this[this.t-1]^this.s&this.DM)}function bnpDLShiftTo(a,b){var c;for(c=this.t-1;c>=0;--c)b[c+a]=this[c];for(c=a-1;c>=0;--c)b[c]=0;b.t=this.t+a;b.s=this.s}function bnpDRShiftTo(a,b){for(var c=a;c<this.t;++c)b[c-a]=this[c];b.t=Math.max(this.t-a,0);b.s=this.s}
function bnpLShiftTo(a,b){var c=a%this.DB,d=this.DB-c,e=(1<<d)-1,g=Math.floor(a/this.DB),h=this.s<<c&this.DM,f;for(f=this.t-1;f>=0;--f)b[f+g+1]=this[f]>>d|h,h=(this[f]&e)<<c;for(f=g-1;f>=0;--f)b[f]=0;b[g]=h;b.t=this.t+g+1;b.s=this.s;b.clamp()}
function bnpRShiftTo(a,b){b.s=this.s;var c=Math.floor(a/this.DB);if(c>=this.t)b.t=0;else{var d=a%this.DB,e=this.DB-d,g=(1<<d)-1;b[0]=this[c]>>d;for(var h=c+1;h<this.t;++h)b[h-c-1]|=(this[h]&g)<<e,b[h-c]=this[h]>>d;d>0&&(b[this.t-c-1]|=(this.s&g)<<e);b.t=this.t-c;b.clamp()}}
function bnpSubTo(a,b){for(var c=0,d=0,e=Math.min(a.t,this.t);c<e;)d+=this[c]-a[c],b[c++]=d&this.DM,d>>=this.DB;if(a.t<this.t){for(d-=a.s;c<this.t;)d+=this[c],b[c++]=d&this.DM,d>>=this.DB;d+=this.s}else{for(d+=this.s;c<a.t;)d-=a[c],b[c++]=d&this.DM,d>>=this.DB;d-=a.s}b.s=d<0?-1:0;d<-1?b[c++]=this.DV+d:d>0&&(b[c++]=d);b.t=c;b.clamp()}
function bnpMultiplyTo(a,b){var c=this.abs(),d=a.abs(),e=c.t;for(b.t=e+d.t;--e>=0;)b[e]=0;for(e=0;e<d.t;++e)b[e+c.t]=c.am(0,d[e],b,e,0,c.t);b.s=0;b.clamp();this.s!=a.s&&BigInteger.ZERO.subTo(b,b)}function bnpSquareTo(a){for(var b=this.abs(),c=a.t=2*b.t;--c>=0;)a[c]=0;for(c=0;c<b.t-1;++c){var d=b.am(c,b[c],a,2*c,0,1);if((a[c+b.t]+=b.am(c+1,2*b[c],a,2*c+1,d,b.t-c-1))>=b.DV)a[c+b.t]-=b.DV,a[c+b.t+1]=1}a.t>0&&(a[a.t-1]+=b.am(c,b[c],a,2*c,0,1));a.s=0;a.clamp()}
function bnpDivRemTo(a,b,c){var d=a.abs();if(!(d.t<=0)){var e=this.abs();if(e.t<d.t)b!=null&&b.fromInt(0),c!=null&&this.copyTo(c);else{c==null&&(c=nbi());var g=nbi(),h=this.s,a=a.s,f=this.DB-nbits(d[d.t-1]);f>0?(d.lShiftTo(f,g),e.lShiftTo(f,c)):(d.copyTo(g),e.copyTo(c));d=g.t;e=g[d-1];if(e!=0){var o=e*(1<<this.F1)+(d>1?g[d-2]>>this.F2:0),p=this.FV/o,o=(1<<this.F1)/o,q=1<<this.F2,n=c.t,k=n-d,j=b==null?nbi():b;g.dlShiftTo(k,j);c.compareTo(j)>=0&&(c[c.t++]=1,c.subTo(j,c));BigInteger.ONE.dlShiftTo(d,
j);for(j.subTo(g,g);g.t<d;)g[g.t++]=0;for(;--k>=0;){var l=c[--n]==e?this.DM:Math.floor(c[n]*p+(c[n-1]+q)*o);if((c[n]+=g.am(0,l,c,k,0,d))<l){g.dlShiftTo(k,j);for(c.subTo(j,c);c[n]<--l;)c.subTo(j,c)}}b!=null&&(c.drShiftTo(d,b),h!=a&&BigInteger.ZERO.subTo(b,b));c.t=d;c.clamp();f>0&&c.rShiftTo(f,c);h<0&&BigInteger.ZERO.subTo(c,c)}}}}function bnMod(a){var b=nbi();this.abs().divRemTo(a,null,b);this.s<0&&b.compareTo(BigInteger.ZERO)>0&&a.subTo(b,b);return b}function Classic(a){this.m=a}
function cConvert(a){return a.s<0||a.compareTo(this.m)>=0?a.mod(this.m):a}function cRevert(a){return a}function cReduce(a){a.divRemTo(this.m,null,a)}function cMulTo(a,b,c){a.multiplyTo(b,c);this.reduce(c)}function cSqrTo(a,b){a.squareTo(b);this.reduce(b)}Classic.prototype.convert=cConvert;Classic.prototype.revert=cRevert;Classic.prototype.reduce=cReduce;Classic.prototype.mulTo=cMulTo;Classic.prototype.sqrTo=cSqrTo;
function bnpInvDigit(){if(this.t<1)return 0;var a=this[0];if((a&1)==0)return 0;var b=a&3,b=b*(2-(a&15)*b)&15,b=b*(2-(a&255)*b)&255,b=b*(2-((a&65535)*b&65535))&65535,b=b*(2-a*b%this.DV)%this.DV;return b>0?this.DV-b:-b}function Montgomery(a){this.m=a;this.mp=a.invDigit();this.mpl=this.mp&32767;this.mph=this.mp>>15;this.um=(1<<a.DB-15)-1;this.mt2=2*a.t}
function montConvert(a){var b=nbi();a.abs().dlShiftTo(this.m.t,b);b.divRemTo(this.m,null,b);a.s<0&&b.compareTo(BigInteger.ZERO)>0&&this.m.subTo(b,b);return b}function montRevert(a){var b=nbi();a.copyTo(b);this.reduce(b);return b}
function montReduce(a){for(;a.t<=this.mt2;)a[a.t++]=0;for(var b=0;b<this.m.t;++b){var c=a[b]&32767,d=c*this.mpl+((c*this.mph+(a[b]>>15)*this.mpl&this.um)<<15)&a.DM,c=b+this.m.t;for(a[c]+=this.m.am(0,d,a,b,0,this.m.t);a[c]>=a.DV;)a[c]-=a.DV,a[++c]++}a.clamp();a.drShiftTo(this.m.t,a);a.compareTo(this.m)>=0&&a.subTo(this.m,a)}function montSqrTo(a,b){a.squareTo(b);this.reduce(b)}function montMulTo(a,b,c){a.multiplyTo(b,c);this.reduce(c)}Montgomery.prototype.convert=montConvert;
Montgomery.prototype.revert=montRevert;Montgomery.prototype.reduce=montReduce;Montgomery.prototype.mulTo=montMulTo;Montgomery.prototype.sqrTo=montSqrTo;function bnpIsEven(){return(this.t>0?this[0]&1:this.s)==0}function bnpExp(a,b){if(a>4294967295||a<1)return BigInteger.ONE;var c=nbi(),d=nbi(),e=b.convert(this),g=nbits(a)-1;for(e.copyTo(c);--g>=0;)if(b.sqrTo(c,d),(a&1<<g)>0)b.mulTo(d,e,c);else var h=c,c=d,d=h;return b.revert(c)}
function bnModPowInt(a,b){var c;c=a<256||b.isEven()?new Classic(b):new Montgomery(b);return this.exp(a,c)}BigInteger.prototype.copyTo=bnpCopyTo;BigInteger.prototype.fromInt=bnpFromInt;BigInteger.prototype.fromString=bnpFromString;BigInteger.prototype.clamp=bnpClamp;BigInteger.prototype.dlShiftTo=bnpDLShiftTo;BigInteger.prototype.drShiftTo=bnpDRShiftTo;BigInteger.prototype.lShiftTo=bnpLShiftTo;BigInteger.prototype.rShiftTo=bnpRShiftTo;BigInteger.prototype.subTo=bnpSubTo;
BigInteger.prototype.multiplyTo=bnpMultiplyTo;BigInteger.prototype.squareTo=bnpSquareTo;BigInteger.prototype.divRemTo=bnpDivRemTo;BigInteger.prototype.invDigit=bnpInvDigit;BigInteger.prototype.isEven=bnpIsEven;BigInteger.prototype.exp=bnpExp;BigInteger.prototype.toString=bnToString;BigInteger.prototype.negate=bnNegate;BigInteger.prototype.abs=bnAbs;BigInteger.prototype.compareTo=bnCompareTo;BigInteger.prototype.bitLength=bnBitLength;BigInteger.prototype.mod=bnMod;BigInteger.prototype.modPowInt=bnModPowInt;
BigInteger.ZERO=nbv(0);BigInteger.ONE=nbv(1);function bnClone(){var a=nbi();this.copyTo(a);return a}function bnIntValue(){if(this.s<0)if(this.t==1)return this[0]-this.DV;else{if(this.t==0)return-1}else if(this.t==1)return this[0];else if(this.t==0)return 0;return(this[1]&(1<<32-this.DB)-1)<<this.DB|this[0]}function bnByteValue(){return this.t==0?this.s:this[0]<<24>>24}function bnShortValue(){return this.t==0?this.s:this[0]<<16>>16}
function bnpChunkSize(a){return Math.floor(Math.LN2*this.DB/Math.log(a))}function bnSigNum(){return this.s<0?-1:this.t<=0||this.t==1&&this[0]<=0?0:1}function bnpToRadix(a){a==null&&(a=10);if(this.signum()==0||a<2||a>36)return"0";var b=this.chunkSize(a),b=Math.pow(a,b),c=nbv(b),d=nbi(),e=nbi(),g="";for(this.divRemTo(c,d,e);d.signum()>0;)g=(b+e.intValue()).toString(a).substr(1)+g,d.divRemTo(c,d,e);return e.intValue().toString(a)+g}
function bnpFromRadix(a,b){this.fromInt(0);b==null&&(b=10);for(var c=this.chunkSize(b),d=Math.pow(b,c),e=!1,g=0,h=0,f=0;f<a.length;++f){var o=intAt(a,f);o<0?a.charAt(f)=="-"&&this.signum()==0&&(e=!0):(h=b*h+o,++g>=c&&(this.dMultiply(d),this.dAddOffset(h,0),h=g=0))}g>0&&(this.dMultiply(Math.pow(b,g)),this.dAddOffset(h,0));e&&BigInteger.ZERO.subTo(this,this)}
function bnpFromNumber(a,b,c){if("number"==typeof b)if(a<2)this.fromInt(1);else{this.fromNumber(a,c);this.testBit(a-1)||this.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,this);for(this.isEven()&&this.dAddOffset(1,0);!this.isProbablePrime(b);)this.dAddOffset(2,0),this.bitLength()>a&&this.subTo(BigInteger.ONE.shiftLeft(a-1),this)}else{var c=[],d=a&7;c.length=(a>>3)+1;b.nextBytes(c);d>0?c[0]&=(1<<d)-1:c[0]=0;this.fromString(c,256)}}
function bnToByteArray(){var a=this.t,b=[];b[0]=this.s;var c=this.DB-a*this.DB%8,d,e=0;if(a-- >0){if(c<this.DB&&(d=this[a]>>c)!=(this.s&this.DM)>>c)b[e++]=d|this.s<<this.DB-c;for(;a>=0;)if(c<8?(d=(this[a]&(1<<c)-1)<<8-c,d|=this[--a]>>(c+=this.DB-8)):(d=this[a]>>(c-=8)&255,c<=0&&(c+=this.DB,--a)),(d&128)!=0&&(d|=-256),e==0&&(this.s&128)!=(d&128)&&++e,e>0||d!=this.s)b[e++]=d}return b}function bnEquals(a){return this.compareTo(a)==0}function bnMin(a){return this.compareTo(a)<0?this:a}
function bnMax(a){return this.compareTo(a)>0?this:a}function bnpBitwiseTo(a,b,c){var d,e,g=Math.min(a.t,this.t);for(d=0;d<g;++d)c[d]=b(this[d],a[d]);if(a.t<this.t){e=a.s&this.DM;for(d=g;d<this.t;++d)c[d]=b(this[d],e);c.t=this.t}else{e=this.s&this.DM;for(d=g;d<a.t;++d)c[d]=b(e,a[d]);c.t=a.t}c.s=b(this.s,a.s);c.clamp()}function op_and(a,b){return a&b}function bnAnd(a){var b=nbi();this.bitwiseTo(a,op_and,b);return b}function op_or(a,b){return a|b}
function bnOr(a){var b=nbi();this.bitwiseTo(a,op_or,b);return b}function op_xor(a,b){return a^b}function bnXor(a){var b=nbi();this.bitwiseTo(a,op_xor,b);return b}function op_andnot(a,b){return a&~b}function bnAndNot(a){var b=nbi();this.bitwiseTo(a,op_andnot,b);return b}function bnNot(){for(var a=nbi(),b=0;b<this.t;++b)a[b]=this.DM&~this[b];a.t=this.t;a.s=~this.s;return a}function bnShiftLeft(a){var b=nbi();a<0?this.rShiftTo(-a,b):this.lShiftTo(a,b);return b}
function bnShiftRight(a){var b=nbi();a<0?this.lShiftTo(-a,b):this.rShiftTo(a,b);return b}function lbit(a){if(a==0)return-1;var b=0;(a&65535)==0&&(a>>=16,b+=16);(a&255)==0&&(a>>=8,b+=8);(a&15)==0&&(a>>=4,b+=4);(a&3)==0&&(a>>=2,b+=2);(a&1)==0&&++b;return b}function bnGetLowestSetBit(){for(var a=0;a<this.t;++a)if(this[a]!=0)return a*this.DB+lbit(this[a]);return this.s<0?this.t*this.DB:-1}function cbit(a){for(var b=0;a!=0;)a&=a-1,++b;return b}
function bnBitCount(){for(var a=0,b=this.s&this.DM,c=0;c<this.t;++c)a+=cbit(this[c]^b);return a}function bnTestBit(a){var b=Math.floor(a/this.DB);return b>=this.t?this.s!=0:(this[b]&1<<a%this.DB)!=0}function bnpChangeBit(a,b){var c=BigInteger.ONE.shiftLeft(a);this.bitwiseTo(c,b,c);return c}function bnSetBit(a){return this.changeBit(a,op_or)}function bnClearBit(a){return this.changeBit(a,op_andnot)}function bnFlipBit(a){return this.changeBit(a,op_xor)}
function bnpAddTo(a,b){for(var c=0,d=0,e=Math.min(a.t,this.t);c<e;)d+=this[c]+a[c],b[c++]=d&this.DM,d>>=this.DB;if(a.t<this.t){for(d+=a.s;c<this.t;)d+=this[c],b[c++]=d&this.DM,d>>=this.DB;d+=this.s}else{for(d+=this.s;c<a.t;)d+=a[c],b[c++]=d&this.DM,d>>=this.DB;d+=a.s}b.s=d<0?-1:0;d>0?b[c++]=d:d<-1&&(b[c++]=this.DV+d);b.t=c;b.clamp()}function bnAdd(a){var b=nbi();this.addTo(a,b);return b}function bnSubtract(a){var b=nbi();this.subTo(a,b);return b}
function bnMultiply(a){var b=nbi();this.multiplyTo(a,b);return b}function bnSquare(){var a=nbi();this.squareTo(a);return a}function bnDivide(a){var b=nbi();this.divRemTo(a,b,null);return b}function bnRemainder(a){var b=nbi();this.divRemTo(a,null,b);return b}function bnDivideAndRemainder(a){var b=nbi(),c=nbi();this.divRemTo(a,b,c);return[b,c]}function bnpDMultiply(a){this[this.t]=this.am(0,a-1,this,0,0,this.t);++this.t;this.clamp()}
function bnpDAddOffset(a,b){if(a!=0){for(;this.t<=b;)this[this.t++]=0;for(this[b]+=a;this[b]>=this.DV;)this[b]-=this.DV,++b>=this.t&&(this[this.t++]=0),++this[b]}}function NullExp(){}function nNop(a){return a}function nMulTo(a,b,c){a.multiplyTo(b,c)}function nSqrTo(a,b){a.squareTo(b)}NullExp.prototype.convert=nNop;NullExp.prototype.revert=nNop;NullExp.prototype.mulTo=nMulTo;NullExp.prototype.sqrTo=nSqrTo;function bnPow(a){return this.exp(a,new NullExp)}
function bnpMultiplyLowerTo(a,b,c){var d=Math.min(this.t+a.t,b);c.s=0;for(c.t=d;d>0;)c[--d]=0;var e;for(e=c.t-this.t;d<e;++d)c[d+this.t]=this.am(0,a[d],c,d,0,this.t);for(e=Math.min(a.t,b);d<e;++d)this.am(0,a[d],c,d,0,b-d);c.clamp()}function bnpMultiplyUpperTo(a,b,c){--b;var d=c.t=this.t+a.t-b;for(c.s=0;--d>=0;)c[d]=0;for(d=Math.max(b-this.t,0);d<a.t;++d)c[this.t+d-b]=this.am(b-d,a[d],c,0,0,this.t+d-b);c.clamp();c.drShiftTo(1,c)}
function Barrett(a){this.r2=nbi();this.q3=nbi();BigInteger.ONE.dlShiftTo(2*a.t,this.r2);this.mu=this.r2.divide(a);this.m=a}function barrettConvert(a){if(a.s<0||a.t>2*this.m.t)return a.mod(this.m);else if(a.compareTo(this.m)<0)return a;else{var b=nbi();a.copyTo(b);this.reduce(b);return b}}function barrettRevert(a){return a}
function barrettReduce(a){a.drShiftTo(this.m.t-1,this.r2);if(a.t>this.m.t+1)a.t=this.m.t+1,a.clamp();this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3);for(this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2);a.compareTo(this.r2)<0;)a.dAddOffset(1,this.m.t+1);for(a.subTo(this.r2,a);a.compareTo(this.m)>=0;)a.subTo(this.m,a)}function barrettSqrTo(a,b){a.squareTo(b);this.reduce(b)}function barrettMulTo(a,b,c){a.multiplyTo(b,c);this.reduce(c)}Barrett.prototype.convert=barrettConvert;
Barrett.prototype.revert=barrettRevert;Barrett.prototype.reduce=barrettReduce;Barrett.prototype.mulTo=barrettMulTo;Barrett.prototype.sqrTo=barrettSqrTo;
function bnModPow(a,b){var c=a.bitLength(),d,e=nbv(1),g;if(c<=0)return e;else d=c<18?1:c<48?3:c<144?4:c<768?5:6;g=c<8?new Classic(b):b.isEven()?new Barrett(b):new Montgomery(b);var h=[],f=3,o=d-1,p=(1<<d)-1;h[1]=g.convert(this);if(d>1){c=nbi();for(g.sqrTo(h[1],c);f<=p;)h[f]=nbi(),g.mulTo(c,h[f-2],h[f]),f+=2}for(var q=a.t-1,n,k=!0,j=nbi(),c=nbits(a[q])-1;q>=0;){c>=o?n=a[q]>>c-o&p:(n=(a[q]&(1<<c+1)-1)<<o-c,q>0&&(n|=a[q-1]>>this.DB+c-o));for(f=d;(n&1)==0;)n>>=1,--f;if((c-=f)<0)c+=this.DB,--q;if(k)h[n].copyTo(e),
k=!1;else{for(;f>1;)g.sqrTo(e,j),g.sqrTo(j,e),f-=2;f>0?g.sqrTo(e,j):(f=e,e=j,j=f);g.mulTo(j,h[n],e)}for(;q>=0&&(a[q]&1<<c)==0;)g.sqrTo(e,j),f=e,e=j,j=f,--c<0&&(c=this.DB-1,--q)}return g.revert(e)}
function bnGCD(a){var b=this.s<0?this.negate():this.clone(),a=a.s<0?a.negate():a.clone();if(b.compareTo(a)<0)var c=b,b=a,a=c;var c=b.getLowestSetBit(),d=a.getLowestSetBit();if(d<0)return b;c<d&&(d=c);d>0&&(b.rShiftTo(d,b),a.rShiftTo(d,a));for(;b.signum()>0;)(c=b.getLowestSetBit())>0&&b.rShiftTo(c,b),(c=a.getLowestSetBit())>0&&a.rShiftTo(c,a),b.compareTo(a)>=0?(b.subTo(a,b),b.rShiftTo(1,b)):(a.subTo(b,a),a.rShiftTo(1,a));d>0&&a.lShiftTo(d,a);return a}
function bnpModInt(a){if(a<=0)return 0;var b=this.DV%a,c=this.s<0?a-1:0;if(this.t>0)if(b==0)c=this[0]%a;else for(var d=this.t-1;d>=0;--d)c=(b*c+this[d])%a;return c}
function bnModInverse(a){var b=a.isEven();if(this.isEven()&&b||a.signum()==0)return BigInteger.ZERO;for(var c=a.clone(),d=this.clone(),e=nbv(1),g=nbv(0),h=nbv(0),f=nbv(1);c.signum()!=0;){for(;c.isEven();){c.rShiftTo(1,c);if(b){if(!e.isEven()||!g.isEven())e.addTo(this,e),g.subTo(a,g);e.rShiftTo(1,e)}else g.isEven()||g.subTo(a,g);g.rShiftTo(1,g)}for(;d.isEven();){d.rShiftTo(1,d);if(b){if(!h.isEven()||!f.isEven())h.addTo(this,h),f.subTo(a,f);h.rShiftTo(1,h)}else f.isEven()||f.subTo(a,f);f.rShiftTo(1,
f)}c.compareTo(d)>=0?(c.subTo(d,c),b&&e.subTo(h,e),g.subTo(f,g)):(d.subTo(c,d),b&&h.subTo(e,h),f.subTo(g,f))}if(d.compareTo(BigInteger.ONE)!=0)return BigInteger.ZERO;if(f.compareTo(a)>=0)return f.subtract(a);if(f.signum()<0)f.addTo(a,f);else return f;return f.signum()<0?f.add(a):f}
var lowprimes=[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,
733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997],lplim=67108864/lowprimes[lowprimes.length-1];
function bnIsProbablePrime(a){var b,c=this.abs();if(c.t==1&&c[0]<=lowprimes[lowprimes.length-1]){for(b=0;b<lowprimes.length;++b)if(c[0]==lowprimes[b])return!0;return!1}if(c.isEven())return!1;for(b=1;b<lowprimes.length;){for(var d=lowprimes[b],e=b+1;e<lowprimes.length&&d<lplim;)d*=lowprimes[e++];for(d=c.modInt(d);b<e;)if(d%lowprimes[b++]==0)return!1}return c.millerRabin(a)}
function bnpMillerRabin(a){var b=this.subtract(BigInteger.ONE),c=b.getLowestSetBit();if(c<=0)return!1;var d=b.shiftRight(c),a=a+1>>1;if(a>lowprimes.length)a=lowprimes.length;for(var e=nbi(),g=0;g<a;++g){e.fromInt(lowprimes[Math.floor(Math.random()*lowprimes.length)]);var h=e.modPow(d,this);if(h.compareTo(BigInteger.ONE)!=0&&h.compareTo(b)!=0){for(var f=1;f++<c&&h.compareTo(b)!=0;)if(h=h.modPowInt(2,this),h.compareTo(BigInteger.ONE)==0)return!1;if(h.compareTo(b)!=0)return!1}}return!0}
BigInteger.prototype.chunkSize=bnpChunkSize;BigInteger.prototype.toRadix=bnpToRadix;BigInteger.prototype.fromRadix=bnpFromRadix;BigInteger.prototype.fromNumber=bnpFromNumber;BigInteger.prototype.bitwiseTo=bnpBitwiseTo;BigInteger.prototype.changeBit=bnpChangeBit;BigInteger.prototype.addTo=bnpAddTo;BigInteger.prototype.dMultiply=bnpDMultiply;BigInteger.prototype.dAddOffset=bnpDAddOffset;BigInteger.prototype.multiplyLowerTo=bnpMultiplyLowerTo;BigInteger.prototype.multiplyUpperTo=bnpMultiplyUpperTo;
BigInteger.prototype.modInt=bnpModInt;BigInteger.prototype.millerRabin=bnpMillerRabin;BigInteger.prototype.clone=bnClone;BigInteger.prototype.intValue=bnIntValue;BigInteger.prototype.byteValue=bnByteValue;BigInteger.prototype.shortValue=bnShortValue;BigInteger.prototype.signum=bnSigNum;BigInteger.prototype.toByteArray=bnToByteArray;BigInteger.prototype.equals=bnEquals;BigInteger.prototype.min=bnMin;BigInteger.prototype.max=bnMax;BigInteger.prototype.and=bnAnd;BigInteger.prototype.or=bnOr;
BigInteger.prototype.xor=bnXor;BigInteger.prototype.andNot=bnAndNot;BigInteger.prototype.not=bnNot;BigInteger.prototype.shiftLeft=bnShiftLeft;BigInteger.prototype.shiftRight=bnShiftRight;BigInteger.prototype.getLowestSetBit=bnGetLowestSetBit;BigInteger.prototype.bitCount=bnBitCount;BigInteger.prototype.testBit=bnTestBit;BigInteger.prototype.setBit=bnSetBit;BigInteger.prototype.clearBit=bnClearBit;BigInteger.prototype.flipBit=bnFlipBit;BigInteger.prototype.add=bnAdd;BigInteger.prototype.subtract=bnSubtract;
BigInteger.prototype.multiply=bnMultiply;BigInteger.prototype.divide=bnDivide;BigInteger.prototype.remainder=bnRemainder;BigInteger.prototype.divideAndRemainder=bnDivideAndRemainder;BigInteger.prototype.modPow=bnModPow;BigInteger.prototype.modInverse=bnModInverse;BigInteger.prototype.pow=bnPow;BigInteger.prototype.gcd=bnGCD;BigInteger.prototype.isProbablePrime=bnIsProbablePrime;BigInteger.prototype.square=bnSquare;
(function(a,b,c,d,e,g,h){function f(a){var b,d,e=this,g=a.length,f=0,h=e.i=e.j=e.m=0;e.S=[];e.c=[];for(g||(a=[g++]);f<c;)e.S[f]=f++;for(f=0;f<c;f++)b=e.S[f],h=h+b+a[f%g]&c-1,d=e.S[h],e.S[f]=d,e.S[h]=b;e.g=function(a){var b=e.S,d=e.i+1&c-1,g=b[d],f=e.j+g&c-1,h=b[f];b[d]=h;b[f]=g;for(var k=b[g+h&c-1];--a;)d=d+1&c-1,g=b[d],f=f+g&c-1,h=b[f],b[d]=h,b[f]=g,k=k*c+b[g+h&c-1];e.i=d;e.j=f;return k};e.g(c)}function o(a,b,c,d,e){c=[];e=typeof a;if(b&&e=="object")for(d in a)if(d.indexOf("S")<5)try{c.push(o(a[d],
b-1))}catch(g){}return c.length?c:a+(e!="string"?"\x00":"")}function p(a,b,d,e){a+="";for(e=d=0;e<a.length;e++){var g=b,f=e&c-1,h=(d^=b[e&c-1]*19)+a.charCodeAt(e);g[f]=h&c-1}a="";for(e in b)a+=String.fromCharCode(b[e]);return a}b.seedrandom=function(q,n){var k=[],j,q=p(o(n?[q,a]:arguments.length?q:[(new Date).getTime(),a,window],3),k);j=new f(k);p(j.S,a);b.random=function(){for(var a=j.g(d),b=h,f=0;a<e;)a=(a+f)*c,b*=c,f=j.g(1);for(;a>=g;)a/=2,b/=2,f>>>=1;return(a+f)/b};return q};h=b.pow(c,d);e=b.pow(2,
e);g=e*2;p(b.random(),a)})([],Math,256,6,52);function SeededRandom(){}function SRnextBytes(a){var b;for(b=0;b<a.length;b++)a[b]=Math.floor(Math.random()*256)}SeededRandom.prototype.nextBytes=SRnextBytes;function Arcfour(){this.j=this.i=0;this.S=[]}function ARC4init(a){var b,c,d;for(b=0;b<256;++b)this.S[b]=b;for(b=c=0;b<256;++b)c=c+this.S[b]+a[b%a.length]&255,d=this.S[b],this.S[b]=this.S[c],this.S[c]=d;this.j=this.i=0}
function ARC4next(){var a;this.i=this.i+1&255;this.j=this.j+this.S[this.i]&255;a=this.S[this.i];this.S[this.i]=this.S[this.j];this.S[this.j]=a;return this.S[a+this.S[this.i]&255]}Arcfour.prototype.init=ARC4init;Arcfour.prototype.next=ARC4next;function prng_newstate(){return new Arcfour}var rng_psize=256,rng_state,rng_pool,rng_pptr;
function rng_seed_int(a){rng_pool[rng_pptr++]^=a&255;rng_pool[rng_pptr++]^=a>>8&255;rng_pool[rng_pptr++]^=a>>16&255;rng_pool[rng_pptr++]^=a>>24&255;rng_pptr>=rng_psize&&(rng_pptr-=rng_psize)}function rng_seed_time(){rng_seed_int((new Date).getTime())}
if(rng_pool==null){rng_pool=[];rng_pptr=0;var t;if(navigator.appName=="Netscape"&&navigator.appVersion<"5"&&window.crypto){var z=window.crypto.random(32);for(t=0;t<z.length;++t)rng_pool[rng_pptr++]=z.charCodeAt(t)&255}for(;rng_pptr<rng_psize;)t=Math.floor(65536*Math.random()),rng_pool[rng_pptr++]=t>>>8,rng_pool[rng_pptr++]=t&255;rng_pptr=0;rng_seed_time()}
function rng_get_byte(){if(rng_state==null){rng_seed_time();rng_state=prng_newstate();rng_state.init(rng_pool);for(rng_pptr=0;rng_pptr<rng_pool.length;++rng_pptr)rng_pool[rng_pptr]=0;rng_pptr=0}return rng_state.next()}function rng_get_bytes(a){var b;for(b=0;b<a.length;++b)a[b]=rng_get_byte()}function SecureRandom(){}SecureRandom.prototype.nextBytes=rng_get_bytes;
function SHA256(a){function b(a,b){var c=(a&65535)+(b&65535);return(a>>16)+(b>>16)+(c>>16)<<16|c&65535}function c(a,b){return a>>>b|a<<32-b}a=function(a){for(var a=a.replace(/\r\n/g,"\n"),b="",c=0;c<a.length;c++){var h=a.charCodeAt(c);h<128?b+=String.fromCharCode(h):(h>127&&h<2048?b+=String.fromCharCode(h>>6|192):(b+=String.fromCharCode(h>>12|224),b+=String.fromCharCode(h>>6&63|128)),b+=String.fromCharCode(h&63|128))}return b}(a);return function(a){for(var b="",c=0;c<a.length*4;c++)b+="0123456789abcdef".charAt(a[c>>
2]>>(3-c%4)*8+4&15)+"0123456789abcdef".charAt(a[c>>2]>>(3-c%4)*8&15);return b}(function(a,e){var g=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,
2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],h=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225],f=Array(64),o,p,q,n,k,j,l,m,s,r,u,w;a[e>>5]|=128<<24-e%32;a[(e+64>>9<<4)+15]=e;for(s=0;s<a.length;s+=16){o=h[0];p=h[1];q=h[2];n=h[3];
k=h[4];j=h[5];l=h[6];m=h[7];for(r=0;r<64;r++)f[r]=r<16?a[r+s]:b(b(b(c(f[r-2],17)^c(f[r-2],19)^f[r-2]>>>10,f[r-7]),c(f[r-15],7)^c(f[r-15],18)^f[r-15]>>>3),f[r-16]),u=b(b(b(b(m,c(k,6)^c(k,11)^c(k,25)),k&j^~k&l),g[r]),f[r]),w=b(c(o,2)^c(o,13)^c(o,22),o&p^o&q^p&q),m=l,l=j,j=k,k=b(n,u),n=q,q=p,p=o,o=b(u,w);h[0]=b(o,h[0]);h[1]=b(p,h[1]);h[2]=b(q,h[2]);h[3]=b(n,h[3]);h[4]=b(k,h[4]);h[5]=b(j,h[5]);h[6]=b(l,h[6]);h[7]=b(m,h[7])}return h}(function(a){for(var b=[],c=0;c<a.length*8;c+=8)b[c>>5]|=(a.charCodeAt(c/
8)&255)<<24-c%32;return b}(a),a.length*8))}var sha256={hex:function(a){return SHA256(a)}};
function SHA1(a){function b(a,b){return a<<b|a>>>32-b}function c(a){var b="",c,d;for(c=7;c>=0;c--)d=a>>>c*4&15,b+=d.toString(16);return b}var d,e,g=Array(80),h=1732584193,f=4023233417,o=2562383102,p=271733878,q=3285377520,n,k,j,l,m,a=function(a){for(var a=a.replace(/\r\n/g,"\n"),b="",c=0;c<a.length;c++){var d=a.charCodeAt(c);d<128?b+=String.fromCharCode(d):(d>127&&d<2048?b+=String.fromCharCode(d>>6|192):(b+=String.fromCharCode(d>>12|224),b+=String.fromCharCode(d>>6&63|128)),b+=String.fromCharCode(d&
63|128))}return b}(a);n=a.length;var s=[];for(d=0;d<n-3;d+=4)e=a.charCodeAt(d)<<24|a.charCodeAt(d+1)<<16|a.charCodeAt(d+2)<<8|a.charCodeAt(d+3),s.push(e);switch(n%4){case 0:d=2147483648;break;case 1:d=a.charCodeAt(n-1)<<24|8388608;break;case 2:d=a.charCodeAt(n-2)<<24|a.charCodeAt(n-1)<<16|32768;break;case 3:d=a.charCodeAt(n-3)<<24|a.charCodeAt(n-2)<<16|a.charCodeAt(n-1)<<8|128}for(s.push(d);s.length%16!=14;)s.push(0);s.push(n>>>29);s.push(n<<3&4294967295);for(a=0;a<s.length;a+=16){for(d=0;d<16;d++)g[d]=
s[a+d];for(d=16;d<=79;d++)g[d]=b(g[d-3]^g[d-8]^g[d-14]^g[d-16],1);e=h;n=f;k=o;j=p;l=q;for(d=0;d<=19;d++)m=b(e,5)+(n&k|~n&j)+l+g[d]+1518500249&4294967295,l=j,j=k,k=b(n,30),n=e,e=m;for(d=20;d<=39;d++)m=b(e,5)+(n^k^j)+l+g[d]+1859775393&4294967295,l=j,j=k,k=b(n,30),n=e,e=m;for(d=40;d<=59;d++)m=b(e,5)+(n&k|n&j|k&j)+l+g[d]+2400959708&4294967295,l=j,j=k,k=b(n,30),n=e,e=m;for(d=60;d<=79;d++)m=b(e,5)+(n^k^j)+l+g[d]+3395469782&4294967295,l=j,j=k,k=b(n,30),n=e,e=m;h=h+e&4294967295;f=f+n&4294967295;o=o+k&4294967295;
p=p+j&4294967295;q=q+l&4294967295}m=c(h)+c(f)+c(o)+c(p)+c(q);return m.toLowerCase()}
var sha1={hex:function(a){return SHA1(a)}},MD5=function(a){function b(a,b){var c,d,e,f,g;e=a&2147483648;f=b&2147483648;c=a&1073741824;d=b&1073741824;g=(a&1073741823)+(b&1073741823);return c&d?g^2147483648^e^f:c|d?g&1073741824?g^3221225472^e^f:g^1073741824^e^f:g^e^f}function c(a,c,d,e,f,g,h){a=b(a,b(b(c&d|~c&e,f),h));return b(a<<g|a>>>32-g,c)}function d(a,c,d,e,f,g,h){a=b(a,b(b(c&e|d&~e,f),h));return b(a<<g|a>>>32-g,c)}function e(a,c,d,e,f,g,h){a=b(a,b(b(c^d^e,f),h));return b(a<<g|a>>>32-g,c)}function g(a,
c,d,e,f,g,h){a=b(a,b(b(d^(c|~e),f),h));return b(a<<g|a>>>32-g,c)}function h(a){var b="",c="",d;for(d=0;d<=3;d++)c=a>>>d*8&255,c="0"+c.toString(16),b+=c.substr(c.length-2,2);return b}var f=[],o,p,q,n,k,j,l,m,a=function(a){for(var a=a.replace(/\r\n/g,"\n"),b="",c=0;c<a.length;c++){var d=a.charCodeAt(c);d<128?b+=String.fromCharCode(d):(d>127&&d<2048?b+=String.fromCharCode(d>>6|192):(b+=String.fromCharCode(d>>12|224),b+=String.fromCharCode(d>>6&63|128)),b+=String.fromCharCode(d&63|128))}return b}(a),
f=function(a){var b,c=a.length;b=c+8;for(var d=((b-b%64)/64+1)*16,e=Array(d-1),f=0,g=0;g<c;)b=(g-g%4)/4,f=g%4*8,e[b]|=a.charCodeAt(g)<<f,g++;e[(g-g%4)/4]|=128<<g%4*8;e[d-2]=c<<3;e[d-1]=c>>>29;return e}(a);k=1732584193;j=4023233417;l=2562383102;m=271733878;for(a=0;a<f.length;a+=16)o=k,p=j,q=l,n=m,k=c(k,j,l,m,f[a+0],7,3614090360),m=c(m,k,j,l,f[a+1],12,3905402710),l=c(l,m,k,j,f[a+2],17,606105819),j=c(j,l,m,k,f[a+3],22,3250441966),k=c(k,j,l,m,f[a+4],7,4118548399),m=c(m,k,j,l,f[a+5],12,1200080426),l=c(l,
m,k,j,f[a+6],17,2821735955),j=c(j,l,m,k,f[a+7],22,4249261313),k=c(k,j,l,m,f[a+8],7,1770035416),m=c(m,k,j,l,f[a+9],12,2336552879),l=c(l,m,k,j,f[a+10],17,4294925233),j=c(j,l,m,k,f[a+11],22,2304563134),k=c(k,j,l,m,f[a+12],7,1804603682),m=c(m,k,j,l,f[a+13],12,4254626195),l=c(l,m,k,j,f[a+14],17,2792965006),j=c(j,l,m,k,f[a+15],22,1236535329),k=d(k,j,l,m,f[a+1],5,4129170786),m=d(m,k,j,l,f[a+6],9,3225465664),l=d(l,m,k,j,f[a+11],14,643717713),j=d(j,l,m,k,f[a+0],20,3921069994),k=d(k,j,l,m,f[a+5],5,3593408605),
m=d(m,k,j,l,f[a+10],9,38016083),l=d(l,m,k,j,f[a+15],14,3634488961),j=d(j,l,m,k,f[a+4],20,3889429448),k=d(k,j,l,m,f[a+9],5,568446438),m=d(m,k,j,l,f[a+14],9,3275163606),l=d(l,m,k,j,f[a+3],14,4107603335),j=d(j,l,m,k,f[a+8],20,1163531501),k=d(k,j,l,m,f[a+13],5,2850285829),m=d(m,k,j,l,f[a+2],9,4243563512),l=d(l,m,k,j,f[a+7],14,1735328473),j=d(j,l,m,k,f[a+12],20,2368359562),k=e(k,j,l,m,f[a+5],4,4294588738),m=e(m,k,j,l,f[a+8],11,2272392833),l=e(l,m,k,j,f[a+11],16,1839030562),j=e(j,l,m,k,f[a+14],23,4259657740),
k=e(k,j,l,m,f[a+1],4,2763975236),m=e(m,k,j,l,f[a+4],11,1272893353),l=e(l,m,k,j,f[a+7],16,4139469664),j=e(j,l,m,k,f[a+10],23,3200236656),k=e(k,j,l,m,f[a+13],4,681279174),m=e(m,k,j,l,f[a+0],11,3936430074),l=e(l,m,k,j,f[a+3],16,3572445317),j=e(j,l,m,k,f[a+6],23,76029189),k=e(k,j,l,m,f[a+9],4,3654602809),m=e(m,k,j,l,f[a+12],11,3873151461),l=e(l,m,k,j,f[a+15],16,530742520),j=e(j,l,m,k,f[a+2],23,3299628645),k=g(k,j,l,m,f[a+0],6,4096336452),m=g(m,k,j,l,f[a+7],10,1126891415),l=g(l,m,k,j,f[a+14],15,2878612391),
j=g(j,l,m,k,f[a+5],21,4237533241),k=g(k,j,l,m,f[a+12],6,1700485571),m=g(m,k,j,l,f[a+3],10,2399980690),l=g(l,m,k,j,f[a+10],15,4293915773),j=g(j,l,m,k,f[a+1],21,2240044497),k=g(k,j,l,m,f[a+8],6,1873313359),m=g(m,k,j,l,f[a+15],10,4264355552),l=g(l,m,k,j,f[a+6],15,2734768916),j=g(j,l,m,k,f[a+13],21,1309151649),k=g(k,j,l,m,f[a+4],6,4149444226),m=g(m,k,j,l,f[a+11],10,3174756917),l=g(l,m,k,j,f[a+2],15,718787259),j=g(j,l,m,k,f[a+9],21,3951481745),k=b(k,o),j=b(j,p),l=b(l,q),m=b(m,n);return(h(k)+h(j)+h(l)+
h(m)).toLowerCase()};function parseBigInt(a,b){return new BigInteger(a,b)}function linebrk(a,b){for(var c="",d=0;d+b<a.length;)c+=a.substring(d,d+b)+"\n",d+=b;return c+a.substring(d,a.length)}function byte2Hex(a){return a<16?"0"+a.toString(16):a.toString(16)}
function pkcs1pad2(a,b){if(b<a.length+11)throw"Message too long for RSA (n="+b+", l="+a.length+")";for(var c=[],d=a.length-1;d>=0&&b>0;){var e=a.charCodeAt(d--);e<128?c[--b]=e:e>127&&e<2048?(c[--b]=e&63|128,c[--b]=e>>6|192):(c[--b]=e&63|128,c[--b]=e>>6&63|128,c[--b]=e>>12|224)}c[--b]=0;d=new SecureRandom;for(e=[];b>2;){for(e[0]=0;e[0]==0;)d.nextBytes(e);c[--b]=e[0]}c[--b]=2;c[--b]=0;return new BigInteger(c)}
function RSAKey(){this.n=null;this.e=0;this.coeff=this.dmq1=this.dmp1=this.q=this.p=this.d=null}function RSASetPublic(a,b){a!=null&&b!=null&&a.length>0&&b.length>0?(this.n=parseBigInt(a,16),this.e=parseInt(b,16)):alert("Invalid RSA public key")}function RSADoPublic(a){return a.modPowInt(this.e,this.n)}function RSAEncrypt(a){a=pkcs1pad2(a,this.n.bitLength()+7>>3);if(a==null)return null;a=this.doPublic(a);if(a==null)return null;a=a.toString(16);return(a.length&1)==0?a:"0"+a}
RSAKey.prototype.doPublic=RSADoPublic;RSAKey.prototype.setPublic=RSASetPublic;RSAKey.prototype.encrypt=RSAEncrypt;function pkcs1unpad2(a,b){for(var c=a.toByteArray(),d=0;d<c.length&&c[d]==0;)++d;if(c.length-d!=b-1||c[d]!=2)return null;for(++d;c[d]!=0;)if(++d>=c.length)return null;for(var e="";++d<c.length;){var g=c[d]&255;g<128?e+=String.fromCharCode(g):g>191&&g<224?(e+=String.fromCharCode((g&31)<<6|c[d+1]&63),++d):(e+=String.fromCharCode((g&15)<<12|(c[d+1]&63)<<6|c[d+2]&63),d+=2)}return e}
function RSASetPrivate(a,b,c){a!=null&&b!=null&&a.length>0&&b.length>0?(this.n=parseBigInt(a,16),this.e=parseInt(b,16),this.d=parseBigInt(c,16)):alert("Invalid RSA private key")}
function RSASetPrivateEx(a,b,c,d,e,g,h,f){a!=null&&b!=null&&a.length>0&&b.length>0?(this.n=parseBigInt(a,16),this.e=parseInt(b,16),this.d=parseBigInt(c,16),this.p=parseBigInt(d,16),this.q=parseBigInt(e,16),this.dmp1=parseBigInt(g,16),this.dmq1=parseBigInt(h,16),this.coeff=parseBigInt(f,16)):alert("Invalid RSA private key")}
function RSAGenerate(a,b){var c=new SeededRandom,d=a>>1;this.e=parseInt(b,16);for(var e=new BigInteger(b,16);;){for(;;)if(this.p=new BigInteger(a-d,1,c),this.p.subtract(BigInteger.ONE).gcd(e).compareTo(BigInteger.ONE)==0&&this.p.isProbablePrime(10))break;for(;;)if(this.q=new BigInteger(d,1,c),this.q.subtract(BigInteger.ONE).gcd(e).compareTo(BigInteger.ONE)==0&&this.q.isProbablePrime(10))break;if(this.p.compareTo(this.q)<=0){var g=this.p;this.p=this.q;this.q=g}var g=this.p.subtract(BigInteger.ONE),
h=this.q.subtract(BigInteger.ONE),f=g.multiply(h);if(f.gcd(e).compareTo(BigInteger.ONE)==0){this.n=this.p.multiply(this.q);this.d=e.modInverse(f);this.dmp1=this.d.mod(g);this.dmq1=this.d.mod(h);this.coeff=this.q.modInverse(this.p);break}}}
function RSADoPrivate(a){if(this.p==null||this.q==null)return a.modPow(this.d,this.n);for(var b=a.mod(this.p).modPow(this.dmp1,this.p),a=a.mod(this.q).modPow(this.dmq1,this.q);b.compareTo(a)<0;)b=b.add(this.p);return b.subtract(a).multiply(this.coeff).mod(this.p).multiply(this.q).add(a)}function RSADecrypt(a){a=this.doPrivate(parseBigInt(a,16));return a==null?null:pkcs1unpad2(a,this.n.bitLength()+7>>3)}RSAKey.prototype.doPrivate=RSADoPrivate;RSAKey.prototype.setPrivate=RSASetPrivate;
RSAKey.prototype.setPrivateEx=RSASetPrivateEx;RSAKey.prototype.generate=RSAGenerate;RSAKey.prototype.decrypt=RSADecrypt;var _RSASIGN_DIHEAD=[];_RSASIGN_DIHEAD.sha1="3021300906052b0e03021a05000414";_RSASIGN_DIHEAD.sha256="3031300d060960864801650304020105000420";var _RSASIGN_HASHHEXFUNC=[];_RSASIGN_HASHHEXFUNC.sha1=sha1.hex;_RSASIGN_HASHHEXFUNC.sha256=sha256.hex;
function _rsasign_getHexPaddedDigestInfoForString(a,b,c){b/=4;for(var a=(0,_RSASIGN_HASHHEXFUNC[c])(a),c="00"+_RSASIGN_DIHEAD[c]+a,a="",b=b-4-c.length,d=0;d<b;d+=2)a+="ff";return sPaddedMessageHex="0001"+a+c}function _rsasign_signString(a,b){var c=_rsasign_getHexPaddedDigestInfoForString(a,this.n.bitLength(),b);return this.doPrivate(parseBigInt(c,16)).toString(16)}
function _rsasign_signStringWithSHA1(a){a=_rsasign_getHexPaddedDigestInfoForString(a,this.n.bitLength(),"sha1");return this.doPrivate(parseBigInt(a,16)).toString(16)}function _rsasign_signStringWithSHA256(a){a=_rsasign_getHexPaddedDigestInfoForString(a,this.n.bitLength(),"sha256");return this.doPrivate(parseBigInt(a,16)).toString(16)}function _rsasign_getDecryptSignatureBI(a,b,c){var d=new RSAKey;d.setPublic(b,c);return d.doPublic(a)}
function _rsasign_getHexDigestInfoFromSig(a,b,c){return _rsasign_getDecryptSignatureBI(a,b,c).toString(16).replace(/^1f+00/,"")}function _rsasign_getAlgNameAndHashFromHexDisgestInfo(a){for(var b in _RSASIGN_DIHEAD){var c=_RSASIGN_DIHEAD[b],d=c.length;if(a.substring(0,d)==c)return[b,a.substring(d)]}return[]}
function _rsasign_verifySignatureWithArgs(a,b,c,d){b=_rsasign_getHexDigestInfoFromSig(b,c,d);c=_rsasign_getAlgNameAndHashFromHexDisgestInfo(b);if(c.length==0)return!1;b=c[1];a=(0,_RSASIGN_HASHHEXFUNC[c[0]])(a);return b==a}function _rsasign_verifyHexSignatureForMessage(a,b){var c=parseBigInt(a,16);return _rsasign_verifySignatureWithArgs(b,c,this.n.toString(16),this.e.toString(16))}
function _rsasign_verifyString(a,b){var b=b.replace(/[ \n]+/g,""),c=this.doPublic(parseBigInt(b,16)).toString(16).replace(/^1f+00/,""),d=_rsasign_getAlgNameAndHashFromHexDisgestInfo(c);if(d.length==0)return!1;c=d[1];d=(0,_RSASIGN_HASHHEXFUNC[d[0]])(a);return c==d}RSAKey.prototype.signString=_rsasign_signString;RSAKey.prototype.signStringWithSHA1=_rsasign_signStringWithSHA1;RSAKey.prototype.signStringWithSHA256=_rsasign_signStringWithSHA256;RSAKey.prototype.verifyString=_rsasign_verifyString;
RSAKey.prototype.verifyHexSignatureForMessage=_rsasign_verifyHexSignatureForMessage;
var aes=function(){var a={Sbox:[99,124,119,123,242,107,111,197,48,1,103,43,254,215,171,118,202,130,201,125,250,89,71,240,173,212,162,175,156,164,114,192,183,253,147,38,54,63,247,204,52,165,229,241,113,216,49,21,4,199,35,195,24,150,5,154,7,18,128,226,235,39,178,117,9,131,44,26,27,110,90,160,82,59,214,179,41,227,47,132,83,209,0,237,32,252,177,91,106,203,190,57,74,76,88,207,208,239,170,251,67,77,51,133,69,249,2,127,80,60,159,168,81,163,64,143,146,157,56,245,188,182,218,33,16,255,243,210,205,12,19,236,
95,151,68,23,196,167,126,61,100,93,25,115,96,129,79,220,34,42,144,136,70,238,184,20,222,94,11,219,224,50,58,10,73,6,36,92,194,211,172,98,145,149,228,121,231,200,55,109,141,213,78,169,108,86,244,234,101,122,174,8,186,120,37,46,28,166,180,198,232,221,116,31,75,189,139,138,112,62,181,102,72,3,246,14,97,53,87,185,134,193,29,158,225,248,152,17,105,217,142,148,155,30,135,233,206,85,40,223,140,161,137,13,191,230,66,104,65,153,45,15,176,84,187,22],ShiftRowTab:[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11]};a.Init=
function(){a.Sbox_Inv=Array(256);for(var b=0;b<256;b++)a.Sbox_Inv[a.Sbox[b]]=b;a.ShiftRowTab_Inv=Array(16);for(b=0;b<16;b++)a.ShiftRowTab_Inv[a.ShiftRowTab[b]]=b;a.xtime=Array(256);for(b=0;b<128;b++)a.xtime[b]=b<<1,a.xtime[128+b]=b<<1^27};a.Done=function(){delete a.Sbox_Inv;delete a.ShiftRowTab_Inv;delete a.xtime};a.ExpandKey=function(b){var c=b.length,d,e=1;switch(c){case 16:d=176;break;case 24:d=208;break;case 32:d=240;break;default:alert("my.ExpandKey: Only key lengths of 16, 24 or 32 bytes allowed!")}for(var g=
c;g<d;g+=4){var h=b.slice(g-4,g);if(g%c==0){if(h=[a.Sbox[h[1]]^e,a.Sbox[h[2]],a.Sbox[h[3]],a.Sbox[h[0]]],(e<<=1)>=256)e^=283}else c>24&&g%c==16&&(h=[a.Sbox[h[0]],a.Sbox[h[1]],a.Sbox[h[2]],a.Sbox[h[3]]]);for(var f=0;f<4;f++)b[g+f]=b[g+f-c]^h[f]}};a.Encrypt=function(b,c){var d=c.length;a.AddRoundKey(b,c.slice(0,16));for(var e=16;e<d-16;e+=16)a.SubBytes(b,a.Sbox),a.ShiftRows(b,a.ShiftRowTab),a.MixColumns(b),a.AddRoundKey(b,c.slice(e,e+16));a.SubBytes(b,a.Sbox);a.ShiftRows(b,a.ShiftRowTab);a.AddRoundKey(b,
c.slice(e,d))};a.Decrypt=function(b,c){var d=c.length;a.AddRoundKey(b,c.slice(d-16,d));a.ShiftRows(b,a.ShiftRowTab_Inv);a.SubBytes(b,a.Sbox_Inv);for(d-=32;d>=16;d-=16)a.AddRoundKey(b,c.slice(d,d+16)),a.MixColumns_Inv(b),a.ShiftRows(b,a.ShiftRowTab_Inv),a.SubBytes(b,a.Sbox_Inv);a.AddRoundKey(b,c.slice(0,16))};a.SubBytes=function(a,c){for(var d=0;d<16;d++)a[d]=c[a[d]]};a.AddRoundKey=function(a,c){for(var d=0;d<16;d++)a[d]^=c[d]};a.ShiftRows=function(a,c){for(var d=[].concat(a),e=0;e<16;e++)a[e]=d[c[e]]};
a.MixColumns=function(b){for(var c=0;c<16;c+=4){var d=b[c+0],e=b[c+1],g=b[c+2],h=b[c+3],f=d^e^g^h;b[c+0]^=f^a.xtime[d^e];b[c+1]^=f^a.xtime[e^g];b[c+2]^=f^a.xtime[g^h];b[c+3]^=f^a.xtime[h^d]}};a.MixColumns_Inv=function(b){for(var c=0;c<16;c+=4){var d=b[c+0],e=b[c+1],g=b[c+2],h=b[c+3],f=d^e^g^h,o=a.xtime[f],p=a.xtime[a.xtime[o^d^g]]^f;f^=a.xtime[a.xtime[o^e^h]];b[c+0]^=p^a.xtime[d^e];b[c+1]^=f^a.xtime[e^g];b[c+2]^=p^a.xtime[g^h];b[c+3]^=f^a.xtime[h^d]}};return a}(),cryptico=function(){var a={};aes.Init();
a.b256to64=function(a){var c,d,e,g="",h=0,f=0,o=a.length;for(e=0;e<o;e++)d=a.charCodeAt(e),f==0?(g+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(d>>2&63),c=(d&3)<<4):f==1?(g+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(c|d>>4&15),c=(d&15)<<2):f==2&&(g+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(c|d>>6&3),h+=1,g+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(d&63)),h+=1,f+=1,f==3&&
(f=0);f>0&&(g+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(c),g+="=");f==1&&(g+="=");return g};a.b64to256=function(a){var c,d,e="",g=0,h=0,f=a.length;for(d=0;d<f;d++)c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(a.charAt(d)),c>=0&&(g&&(e+=String.fromCharCode(h|c>>6-g&255)),g=g+2&7,h=c<<g&255);return e};a.b16to64=function(a){var c,d,e="";a.length%2==1&&(a="0"+a);for(c=0;c+3<=a.length;c+=3)d=parseInt(a.substring(c,c+3),16),e+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(d>>
6)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(d&63);c+1==a.length?(d=parseInt(a.substring(c,c+1),16),e+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(d<<2)):c+2==a.length&&(d=parseInt(a.substring(c,c+2),16),e+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(d>>2)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt((d&3)<<4));for(;(e.length&3)>0;)e+="=";return e};a.b64to16=function(a){var c="",
d,e=0,g;for(d=0;d<a.length;++d){if(a.charAt(d)=="=")break;v="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(a.charAt(d));v<0||(e==0?(c+=int2char(v>>2),g=v&3,e=1):e==1?(c+=int2char(g<<2|v>>4),g=v&15,e=2):e==2?(c+=int2char(g),c+=int2char(v>>2),g=v&3,e=3):(c+=int2char(g<<2|v>>4),c+=int2char(v&15),e=0))}e==1&&(c+=int2char(g<<2));return c};a.string2bytes=function(a){for(var c=[],d=0;d<a.length;d++)c.push(a.charCodeAt(d));return c};a.bytes2string=function(a){for(var c="",d=0;d<
a.length;d++)c+=String.fromCharCode(a[d]);return c};a.blockXOR=function(a,c){for(var d=Array(16),e=0;e<16;e++)d[e]=a[e]^c[e];return d};a.blockIV=function(){var a=new SecureRandom,c=Array(16);a.nextBytes(c);return c};a.pad16=function(a){var c=a.slice(0),d=(16-a.length%16)%16;for(i=a.length;i<a.length+d;i++)c.push(0);return c};a.depad=function(a){for(a=a.slice(0);a[a.length-1]==0;)a=a.slice(0,a.length-1);return a};a.encryptAESCBC=function(b,c){var d=c.slice(0);aes.ExpandKey(d);for(var e=a.string2bytes(b),
e=a.pad16(e),g=a.blockIV(),h=0;h<e.length/16;h++){var f=e.slice(h*16,h*16+16),o=g.slice(h*16,h*16+16),f=a.blockXOR(o,f);aes.Encrypt(f,d);g=g.concat(f)}d=a.bytes2string(g);return a.b256to64(d)};a.decryptAESCBC=function(b,c){var d=c.slice(0);aes.ExpandKey(d);for(var b=a.b64to256(b),e=a.string2bytes(b),g=[],h=1;h<e.length/16;h++){var f=e.slice(h*16,h*16+16),o=e.slice((h-1)*16,(h-1)*16+16);aes.Decrypt(f,d);f=a.blockXOR(o,f);g=g.concat(f)}g=a.depad(g);return a.bytes2string(g)};a.wrap60=function(a){for(var c=
"",d=0;d<a.length;d++)d%60==0&&d!=0&&(c+="\n"),c+=a[d];return c};a.generateAESKey=function(){var a=Array(16);(new SecureRandom).nextBytes(a);return a};a.generateRSAKey=function(a,c){Math.seedrandom(sha256.hex(a));var d=new RSAKey;d.generate(c,"10001");return d};a.publicKeyString=function(b){return pubkey=b.n.toString(16)};a.publicKeyID=function(a){return MD5(a)};a.publicKeyFromString=function(b){var b=b.split("|")[0],c=new RSAKey;c.setPublic(b,"10001");return c};a.encrypt=function(b,
c,d){var e="";try{var h=a.publicKeyFromString(c);e+=h.encrypt(b)+"?"}catch(f){return{status:"Invalid public key"}};return{status:"success",cipher:e}};a.decrypt=function(b,c){var d=b.split("?"),e=c.decrypt(d[0]);return{status:"success",plaintext:e,signature:"unsigned"}};return a}();

View File

@@ -0,0 +1,35 @@
/*
CryptoJS v3.1.2
code.google.com/p/crypto-js
(c) 2009-2013 by Jeff Mott. All rights reserved.
code.google.com/p/crypto-js/wiki/License
*/
var CryptoJS=CryptoJS||function(u,p){var d={},l=d.lib={},s=function(){},t=l.Base={extend:function(a){s.prototype=this;var c=new s;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}},
r=l.WordArray=t.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=p?c:4*a.length},toString:function(a){return(a||v).stringify(this)},concat:function(a){var c=this.words,e=a.words,j=this.sigBytes;a=a.sigBytes;this.clamp();if(j%4)for(var k=0;k<a;k++)c[j+k>>>2]|=(e[k>>>2]>>>24-8*(k%4)&255)<<24-8*((j+k)%4);else if(65535<e.length)for(k=0;k<a;k+=4)c[j+k>>>2]=e[k>>>2];else c.push.apply(c,e);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<<
32-8*(c%4);a.length=u.ceil(c/4)},clone:function(){var a=t.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],e=0;e<a;e+=4)c.push(4294967296*u.random()|0);return new r.init(c,a)}}),w=d.enc={},v=w.Hex={stringify:function(a){var c=a.words;a=a.sigBytes;for(var e=[],j=0;j<a;j++){var k=c[j>>>2]>>>24-8*(j%4)&255;e.push((k>>>4).toString(16));e.push((k&15).toString(16))}return e.join("")},parse:function(a){for(var c=a.length,e=[],j=0;j<c;j+=2)e[j>>>3]|=parseInt(a.substr(j,
2),16)<<24-4*(j%8);return new r.init(e,c/2)}},b=w.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var e=[],j=0;j<a;j++)e.push(String.fromCharCode(c[j>>>2]>>>24-8*(j%4)&255));return e.join("")},parse:function(a){for(var c=a.length,e=[],j=0;j<c;j++)e[j>>>2]|=(a.charCodeAt(j)&255)<<24-8*(j%4);return new r.init(e,c)}},x=w.Utf8={stringify:function(a){try{return decodeURIComponent(escape(b.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return b.parse(unescape(encodeURIComponent(a)))}},
q=l.BufferedBlockAlgorithm=t.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=x.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,e=c.words,j=c.sigBytes,k=this.blockSize,b=j/(4*k),b=a?u.ceil(b):u.max((b|0)-this._minBufferSize,0);a=b*k;j=u.min(4*a,j);if(a){for(var q=0;q<a;q+=k)this._doProcessBlock(e,q);q=e.splice(0,a);c.sigBytes-=j}return new r.init(q,j)},clone:function(){var a=t.clone.call(this);
a._data=this._data.clone();return a},_minBufferSize:0});l.Hasher=q.extend({cfg:t.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){q.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(b,e){return(new a.init(e)).finalize(b)}},_createHmacHelper:function(a){return function(b,e){return(new n.HMAC.init(a,
e)).finalize(b)}}});var n=d.algo={};return d}(Math);
(function(){var u=CryptoJS,p=u.lib.WordArray;u.enc.Base64={stringify:function(d){var l=d.words,p=d.sigBytes,t=this._map;d.clamp();d=[];for(var r=0;r<p;r+=3)for(var w=(l[r>>>2]>>>24-8*(r%4)&255)<<16|(l[r+1>>>2]>>>24-8*((r+1)%4)&255)<<8|l[r+2>>>2]>>>24-8*((r+2)%4)&255,v=0;4>v&&r+0.75*v<p;v++)d.push(t.charAt(w>>>6*(3-v)&63));if(l=t.charAt(64))for(;d.length%4;)d.push(l);return d.join("")},parse:function(d){var l=d.length,s=this._map,t=s.charAt(64);t&&(t=d.indexOf(t),-1!=t&&(l=t));for(var t=[],r=0,w=0;w<
l;w++)if(w%4){var v=s.indexOf(d.charAt(w-1))<<2*(w%4),b=s.indexOf(d.charAt(w))>>>6-2*(w%4);t[r>>>2]|=(v|b)<<24-8*(r%4);r++}return p.create(t,r)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})();
(function(u){function p(b,n,a,c,e,j,k){b=b+(n&a|~n&c)+e+k;return(b<<j|b>>>32-j)+n}function d(b,n,a,c,e,j,k){b=b+(n&c|a&~c)+e+k;return(b<<j|b>>>32-j)+n}function l(b,n,a,c,e,j,k){b=b+(n^a^c)+e+k;return(b<<j|b>>>32-j)+n}function s(b,n,a,c,e,j,k){b=b+(a^(n|~c))+e+k;return(b<<j|b>>>32-j)+n}for(var t=CryptoJS,r=t.lib,w=r.WordArray,v=r.Hasher,r=t.algo,b=[],x=0;64>x;x++)b[x]=4294967296*u.abs(u.sin(x+1))|0;r=r.MD5=v.extend({_doReset:function(){this._hash=new w.init([1732584193,4023233417,2562383102,271733878])},
_doProcessBlock:function(q,n){for(var a=0;16>a;a++){var c=n+a,e=q[c];q[c]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360}var a=this._hash.words,c=q[n+0],e=q[n+1],j=q[n+2],k=q[n+3],z=q[n+4],r=q[n+5],t=q[n+6],w=q[n+7],v=q[n+8],A=q[n+9],B=q[n+10],C=q[n+11],u=q[n+12],D=q[n+13],E=q[n+14],x=q[n+15],f=a[0],m=a[1],g=a[2],h=a[3],f=p(f,m,g,h,c,7,b[0]),h=p(h,f,m,g,e,12,b[1]),g=p(g,h,f,m,j,17,b[2]),m=p(m,g,h,f,k,22,b[3]),f=p(f,m,g,h,z,7,b[4]),h=p(h,f,m,g,r,12,b[5]),g=p(g,h,f,m,t,17,b[6]),m=p(m,g,h,f,w,22,b[7]),
f=p(f,m,g,h,v,7,b[8]),h=p(h,f,m,g,A,12,b[9]),g=p(g,h,f,m,B,17,b[10]),m=p(m,g,h,f,C,22,b[11]),f=p(f,m,g,h,u,7,b[12]),h=p(h,f,m,g,D,12,b[13]),g=p(g,h,f,m,E,17,b[14]),m=p(m,g,h,f,x,22,b[15]),f=d(f,m,g,h,e,5,b[16]),h=d(h,f,m,g,t,9,b[17]),g=d(g,h,f,m,C,14,b[18]),m=d(m,g,h,f,c,20,b[19]),f=d(f,m,g,h,r,5,b[20]),h=d(h,f,m,g,B,9,b[21]),g=d(g,h,f,m,x,14,b[22]),m=d(m,g,h,f,z,20,b[23]),f=d(f,m,g,h,A,5,b[24]),h=d(h,f,m,g,E,9,b[25]),g=d(g,h,f,m,k,14,b[26]),m=d(m,g,h,f,v,20,b[27]),f=d(f,m,g,h,D,5,b[28]),h=d(h,f,
m,g,j,9,b[29]),g=d(g,h,f,m,w,14,b[30]),m=d(m,g,h,f,u,20,b[31]),f=l(f,m,g,h,r,4,b[32]),h=l(h,f,m,g,v,11,b[33]),g=l(g,h,f,m,C,16,b[34]),m=l(m,g,h,f,E,23,b[35]),f=l(f,m,g,h,e,4,b[36]),h=l(h,f,m,g,z,11,b[37]),g=l(g,h,f,m,w,16,b[38]),m=l(m,g,h,f,B,23,b[39]),f=l(f,m,g,h,D,4,b[40]),h=l(h,f,m,g,c,11,b[41]),g=l(g,h,f,m,k,16,b[42]),m=l(m,g,h,f,t,23,b[43]),f=l(f,m,g,h,A,4,b[44]),h=l(h,f,m,g,u,11,b[45]),g=l(g,h,f,m,x,16,b[46]),m=l(m,g,h,f,j,23,b[47]),f=s(f,m,g,h,c,6,b[48]),h=s(h,f,m,g,w,10,b[49]),g=s(g,h,f,m,
E,15,b[50]),m=s(m,g,h,f,r,21,b[51]),f=s(f,m,g,h,u,6,b[52]),h=s(h,f,m,g,k,10,b[53]),g=s(g,h,f,m,B,15,b[54]),m=s(m,g,h,f,e,21,b[55]),f=s(f,m,g,h,v,6,b[56]),h=s(h,f,m,g,x,10,b[57]),g=s(g,h,f,m,t,15,b[58]),m=s(m,g,h,f,D,21,b[59]),f=s(f,m,g,h,z,6,b[60]),h=s(h,f,m,g,C,10,b[61]),g=s(g,h,f,m,j,15,b[62]),m=s(m,g,h,f,A,21,b[63]);a[0]=a[0]+f|0;a[1]=a[1]+m|0;a[2]=a[2]+g|0;a[3]=a[3]+h|0},_doFinalize:function(){var b=this._data,n=b.words,a=8*this._nDataBytes,c=8*b.sigBytes;n[c>>>5]|=128<<24-c%32;var e=u.floor(a/
4294967296);n[(c+64>>>9<<4)+15]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360;n[(c+64>>>9<<4)+14]=(a<<8|a>>>24)&16711935|(a<<24|a>>>8)&4278255360;b.sigBytes=4*(n.length+1);this._process();b=this._hash;n=b.words;for(a=0;4>a;a++)c=n[a],n[a]=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360;return b},clone:function(){var b=v.clone.call(this);b._hash=this._hash.clone();return b}});t.MD5=v._createHelper(r);t.HmacMD5=v._createHmacHelper(r)})(Math);
(function(){var u=CryptoJS,p=u.lib,d=p.Base,l=p.WordArray,p=u.algo,s=p.EvpKDF=d.extend({cfg:d.extend({keySize:4,hasher:p.MD5,iterations:1}),init:function(d){this.cfg=this.cfg.extend(d)},compute:function(d,r){for(var p=this.cfg,s=p.hasher.create(),b=l.create(),u=b.words,q=p.keySize,p=p.iterations;u.length<q;){n&&s.update(n);var n=s.update(d).finalize(r);s.reset();for(var a=1;a<p;a++)n=s.finalize(n),s.reset();b.concat(n)}b.sigBytes=4*q;return b}});u.EvpKDF=function(d,l,p){return s.create(p).compute(d,
l)}})();
CryptoJS.lib.Cipher||function(u){var p=CryptoJS,d=p.lib,l=d.Base,s=d.WordArray,t=d.BufferedBlockAlgorithm,r=p.enc.Base64,w=p.algo.EvpKDF,v=d.Cipher=t.extend({cfg:l.extend(),createEncryptor:function(e,a){return this.create(this._ENC_XFORM_MODE,e,a)},createDecryptor:function(e,a){return this.create(this._DEC_XFORM_MODE,e,a)},init:function(e,a,b){this.cfg=this.cfg.extend(b);this._xformMode=e;this._key=a;this.reset()},reset:function(){t.reset.call(this);this._doReset()},process:function(e){this._append(e);return this._process()},
finalize:function(e){e&&this._append(e);return this._doFinalize()},keySize:4,ivSize:4,_ENC_XFORM_MODE:1,_DEC_XFORM_MODE:2,_createHelper:function(e){return{encrypt:function(b,k,d){return("string"==typeof k?c:a).encrypt(e,b,k,d)},decrypt:function(b,k,d){return("string"==typeof k?c:a).decrypt(e,b,k,d)}}}});d.StreamCipher=v.extend({_doFinalize:function(){return this._process(!0)},blockSize:1});var b=p.mode={},x=function(e,a,b){var c=this._iv;c?this._iv=u:c=this._prevBlock;for(var d=0;d<b;d++)e[a+d]^=
c[d]},q=(d.BlockCipherMode=l.extend({createEncryptor:function(e,a){return this.Encryptor.create(e,a)},createDecryptor:function(e,a){return this.Decryptor.create(e,a)},init:function(e,a){this._cipher=e;this._iv=a}})).extend();q.Encryptor=q.extend({processBlock:function(e,a){var b=this._cipher,c=b.blockSize;x.call(this,e,a,c);b.encryptBlock(e,a);this._prevBlock=e.slice(a,a+c)}});q.Decryptor=q.extend({processBlock:function(e,a){var b=this._cipher,c=b.blockSize,d=e.slice(a,a+c);b.decryptBlock(e,a);x.call(this,
e,a,c);this._prevBlock=d}});b=b.CBC=q;q=(p.pad={}).Pkcs7={pad:function(a,b){for(var c=4*b,c=c-a.sigBytes%c,d=c<<24|c<<16|c<<8|c,l=[],n=0;n<c;n+=4)l.push(d);c=s.create(l,c);a.concat(c)},unpad:function(a){a.sigBytes-=a.words[a.sigBytes-1>>>2]&255}};d.BlockCipher=v.extend({cfg:v.cfg.extend({mode:b,padding:q}),reset:function(){v.reset.call(this);var a=this.cfg,b=a.iv,a=a.mode;if(this._xformMode==this._ENC_XFORM_MODE)var c=a.createEncryptor;else c=a.createDecryptor,this._minBufferSize=1;this._mode=c.call(a,
this,b&&b.words)},_doProcessBlock:function(a,b){this._mode.processBlock(a,b)},_doFinalize:function(){var a=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){a.pad(this._data,this.blockSize);var b=this._process(!0)}else b=this._process(!0),a.unpad(b);return b},blockSize:4});var n=d.CipherParams=l.extend({init:function(a){this.mixIn(a)},toString:function(a){return(a||this.formatter).stringify(this)}}),b=(p.format={}).OpenSSL={stringify:function(a){var b=a.ciphertext;a=a.salt;return(a?s.create([1398893684,
1701076831]).concat(a).concat(b):b).toString(r)},parse:function(a){a=r.parse(a);var b=a.words;if(1398893684==b[0]&&1701076831==b[1]){var c=s.create(b.slice(2,4));b.splice(0,4);a.sigBytes-=16}return n.create({ciphertext:a,salt:c})}},a=d.SerializableCipher=l.extend({cfg:l.extend({format:b}),encrypt:function(a,b,c,d){d=this.cfg.extend(d);var l=a.createEncryptor(c,d);b=l.finalize(b);l=l.cfg;return n.create({ciphertext:b,key:c,iv:l.iv,algorithm:a,mode:l.mode,padding:l.padding,blockSize:a.blockSize,formatter:d.format})},
decrypt:function(a,b,c,d){d=this.cfg.extend(d);b=this._parse(b,d.format);return a.createDecryptor(c,d).finalize(b.ciphertext)},_parse:function(a,b){return"string"==typeof a?b.parse(a,this):a}}),p=(p.kdf={}).OpenSSL={execute:function(a,b,c,d){d||(d=s.random(8));a=w.create({keySize:b+c}).compute(a,d);c=s.create(a.words.slice(b),4*c);a.sigBytes=4*b;return n.create({key:a,iv:c,salt:d})}},c=d.PasswordBasedCipher=a.extend({cfg:a.cfg.extend({kdf:p}),encrypt:function(b,c,d,l){l=this.cfg.extend(l);d=l.kdf.execute(d,
b.keySize,b.ivSize);l.iv=d.iv;b=a.encrypt.call(this,b,c,d.key,l);b.mixIn(d);return b},decrypt:function(b,c,d,l){l=this.cfg.extend(l);c=this._parse(c,l.format);d=l.kdf.execute(d,b.keySize,b.ivSize,c.salt);l.iv=d.iv;return a.decrypt.call(this,b,c,d.key,l)}})}();
(function(){for(var u=CryptoJS,p=u.lib.BlockCipher,d=u.algo,l=[],s=[],t=[],r=[],w=[],v=[],b=[],x=[],q=[],n=[],a=[],c=0;256>c;c++)a[c]=128>c?c<<1:c<<1^283;for(var e=0,j=0,c=0;256>c;c++){var k=j^j<<1^j<<2^j<<3^j<<4,k=k>>>8^k&255^99;l[e]=k;s[k]=e;var z=a[e],F=a[z],G=a[F],y=257*a[k]^16843008*k;t[e]=y<<24|y>>>8;r[e]=y<<16|y>>>16;w[e]=y<<8|y>>>24;v[e]=y;y=16843009*G^65537*F^257*z^16843008*e;b[k]=y<<24|y>>>8;x[k]=y<<16|y>>>16;q[k]=y<<8|y>>>24;n[k]=y;e?(e=z^a[a[a[G^z]]],j^=a[a[j]]):e=j=1}var H=[0,1,2,4,8,
16,32,64,128,27,54],d=d.AES=p.extend({_doReset:function(){for(var a=this._key,c=a.words,d=a.sigBytes/4,a=4*((this._nRounds=d+6)+1),e=this._keySchedule=[],j=0;j<a;j++)if(j<d)e[j]=c[j];else{var k=e[j-1];j%d?6<d&&4==j%d&&(k=l[k>>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255]):(k=k<<8|k>>>24,k=l[k>>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255],k^=H[j/d|0]<<24);e[j]=e[j-d]^k}c=this._invKeySchedule=[];for(d=0;d<a;d++)j=a-d,k=d%4?e[j]:e[j-4],c[d]=4>d||4>=j?k:b[l[k>>>24]]^x[l[k>>>16&255]]^q[l[k>>>
8&255]]^n[l[k&255]]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._keySchedule,t,r,w,v,l)},decryptBlock:function(a,c){var d=a[c+1];a[c+1]=a[c+3];a[c+3]=d;this._doCryptBlock(a,c,this._invKeySchedule,b,x,q,n,s);d=a[c+1];a[c+1]=a[c+3];a[c+3]=d},_doCryptBlock:function(a,b,c,d,e,j,l,f){for(var m=this._nRounds,g=a[b]^c[0],h=a[b+1]^c[1],k=a[b+2]^c[2],n=a[b+3]^c[3],p=4,r=1;r<m;r++)var q=d[g>>>24]^e[h>>>16&255]^j[k>>>8&255]^l[n&255]^c[p++],s=d[h>>>24]^e[k>>>16&255]^j[n>>>8&255]^l[g&255]^c[p++],t=
d[k>>>24]^e[n>>>16&255]^j[g>>>8&255]^l[h&255]^c[p++],n=d[n>>>24]^e[g>>>16&255]^j[h>>>8&255]^l[k&255]^c[p++],g=q,h=s,k=t;q=(f[g>>>24]<<24|f[h>>>16&255]<<16|f[k>>>8&255]<<8|f[n&255])^c[p++];s=(f[h>>>24]<<24|f[k>>>16&255]<<16|f[n>>>8&255]<<8|f[g&255])^c[p++];t=(f[k>>>24]<<24|f[n>>>16&255]<<16|f[g>>>8&255]<<8|f[h&255])^c[p++];n=(f[n>>>24]<<24|f[g>>>16&255]<<16|f[h>>>8&255]<<8|f[k&255])^c[p++];a[b]=q;a[b+1]=s;a[b+2]=t;a[b+3]=n},keySize:8});u.AES=p._createHelper(d)})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
"use strict";var Module={};var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=(info,receiveInstance)=>{var module=Module["wasmModule"];Module["wasmModule"]=null;var instance=new WebAssembly.Instance(module,info);return receiveInstance(instance)};self.onunhandledrejection=e=>{throw e.reason??e};function handleMessage(e){try{if(e.data.cmd==="load"){let messageQueue=[];self.onmessage=e=>messageQueue.push(e);self.startWorker=instance=>{Module=instance;postMessage({"cmd":"loaded"});for(let msg of messageQueue){handleMessage(msg)}self.onmessage=handleMessage};Module["wasmModule"]=e.data.wasmModule;for(const handler of e.data.handlers){Module[handler]=(...args)=>{postMessage({cmd:"callHandler",handler:handler,args:args})}}Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob=="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}JSPlayerModule(Module)}else if(e.data.cmd==="run"){Module["__emscripten_thread_init"](e.data.pthread_ptr,0,0,1);Module["__emscripten_thread_mailbox_await"](e.data.pthread_ptr);Module["establishStackSpace"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInitTLS();if(!initializedJS){Module["__embind_initialize_bindings"]();initializedJS=true}try{Module["invokeEntryPoint"](e.data.start_routine,e.data.arg)}catch(ex){if(ex!="unwind"){throw ex}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["__emscripten_thread_exit"](-1)}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="checkMailbox"){if(initializedJS){Module["checkMailbox"]()}}else if(e.data.cmd){err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){if(Module["__emscripten_thread_crashed"]){Module["__emscripten_thread_crashed"]()}throw ex}}self.onmessage=handleMessage;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
"use strict";var Module={};var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=(info,receiveInstance)=>{var module=Module["wasmModule"];Module["wasmModule"]=null;var instance=new WebAssembly.Instance(module,info);return receiveInstance(instance)};self.onunhandledrejection=e=>{throw e.reason??e};function handleMessage(e){try{if(e.data.cmd==="load"){let messageQueue=[];self.onmessage=e=>messageQueue.push(e);self.startWorker=instance=>{Module=instance;postMessage({"cmd":"loaded"});for(let msg of messageQueue){handleMessage(msg)}self.onmessage=handleMessage};Module["wasmModule"]=e.data.wasmModule;for(const handler of e.data.handlers){Module[handler]=(...args)=>{postMessage({cmd:"callHandler",handler:handler,args:args})}}Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob=="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}JSPlayerModule(Module)}else if(e.data.cmd==="run"){Module["__emscripten_thread_init"](e.data.pthread_ptr,0,0,1);Module["__emscripten_thread_mailbox_await"](e.data.pthread_ptr);Module["establishStackSpace"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInitTLS();if(!initializedJS){Module["__embind_initialize_bindings"]();initializedJS=true}try{Module["invokeEntryPoint"](e.data.start_routine,e.data.arg)}catch(ex){if(ex!="unwind"){throw ex}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["__emscripten_thread_exit"](-1)}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="checkMailbox"){if(initializedJS){Module["checkMailbox"]()}}else if(e.data.cmd){err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){if(Module["__emscripten_thread_crashed"]){Module["__emscripten_thread_crashed"]()}throw ex}}self.onmessage=handleMessage;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,240 @@
importScripts('libSystemTransform.js');
const RECORDRTP = 0; //录制一份未经过转封装的码流原始数据,用于定位问题
let dataType = 1;
// 字母字符串转byte数组
function stringToBytes (str) {
var ch;
var st;
var re = [];
for (var i = 0; i < str.length; i++) {
ch = str.charCodeAt(i); // get char
st = []; // set up "stack"
do {
st.push(ch & 0xFF); // push byte to stack
ch = ch >> 8; // shift value down by 1 byte
}
while (ch);
// add stack contents to result
// done because chars have "wrong" endianness
re = re.concat(st.reverse());
}
// return an array of bytes
return re;
}
// 转封装库回调函数
self.STCallBack = function (fileIndex, indexLen, data, dataLen) {
//stFrameInfo的类型见DETAIL_FRAME_INFO
let stFrameInfo = Module._GetDetialFrameInfo();
let nIsMp4Index = stFrameInfo.nIsMp4Index;
//console.log("FrameType is " , stFrameInfo);
//console.log("nIsMp4Index is " + nIsMp4Index);
//debugger
var pData = null;
pData = new Uint8Array(dataLen);
pData.set(Module.HEAPU8.subarray(data, data + dataLen));
if (dataType === 1) {
postMessage({ type: "outputData",
buf: pData.buffer,
dType: 1,
frameInfo: stFrameInfo }, [pData.buffer]);
dataType = 2;
} else {
if (nIsMp4Index) {
postMessage({ type: "outputData",
buf: pData.buffer,
dType: 6,
frameInfo: stFrameInfo }, [pData.buffer]); //6索引类型
} else {
postMessage({ type: "outputData",
buf: pData.buffer,
dType: 2,
frameInfo: stFrameInfo }, [pData.buffer]); //2:码流
}
}
//stFrameInfo的类型见DETAIL_FRAME_INFO
//let stFrameInfo = Module._GetDetialFrameInfo();
//let stFrameType = stFrameInfo.nFrameType;
//let nFrameNum = stFrameInfo.nFrameNum;
//let nTimeStamp = stFrameInfo.nTimeStamp;
//let nIsMp4Index = stFrameInfo.nIsMp4Index;
//console.log("FrameType is " + stFrameType);
//console.log("nIsMp4Index is " + nIsMp4Index);
};
// self.Module = { memoryInitializerRequest: loadMemInitFile(), TOTAL_MEMORY: 128*1024*1024 };
// importScripts('SystemTransform.js');
self.Module['onRuntimeInitialized'] = function () {
postMessage({type: "loaded"});
};
onmessage = function (e) {
var data = e.data;
if ("create" === data.type) {
if (RECORDRTP) {
postMessage({ type: "created" });
postMessage({ type: "outputData",
buf: data.buf,
dType: 1 }, [data.buf]);
} else {
var iHeadLen = data.len;
var pHead = Module._malloc(iHeadLen);
if (pHead === null) {
console.log("inputdata malloc failed!!!");
return -1;
}
var iTransType = data.packType;//目标格式
var iRet = 0;
var buf = new Uint8Array(data.buf);
//PS流(只有ps支持探测)并且编码格式异常正常是265和26411位和10位 不可能全是0全0 并且是ps就探测策略的情况下使用探测策略
if (buf[9] === 0 && buf[8] === 2 && buf[11] === 0 && buf[10] === 0) {
iRet = Module._CreatHandle(0, iTransType, iHeadLen); //用探测的策略
} else {
self.writeArrayToMemory(buf, pHead);
iRet = Module._CreatHandle(pHead, iTransType, iHeadLen);
//-2147483645代表的是参数错误此种情况大概率发生在头信息错误例如大华设备的情况此时用 无头探测的策略
//其他情况 按海康标准处理流程,不要做任何特殊处理
if (iRet == -2147483645) {
iRet = Module._CreatHandle(0, iTransType, iHeadLen); //失败了,用探测的策略再试一次
}
}
if (iRet != 0) {
if (iRet == -2147483647) {
postMessage({ type: "outputData",
dType: 1501 }); //标记为格式不支持
} else {
postMessage({ type: "outputData",
dType: 1501 }); //转封装创建失败,也同样提示码流格式不支持,如果后续要细化再区分
}
console.log("_CreatHandle failed!" + iRet);
} else {
if (data.options && typeof data.options.pKeyData !== "undefined" && data.options.pKeyData !== null) {
if ((2 === iTransType && "" === data.options.pKeyData)) {
//转ps的时候如果密码是空是允许的即使码流加密了导出加密后的码流就行
//此时不要设置密码否则反而会提示密码错误
} else {
var secretInfo = data.options;
var keyLen = secretInfo.nKeyLen;
var pKeyData = Module._malloc(keyLen);
if (pKeyData === null) {
console.log("setEncryptKey malloc failed!!!");
return -1;
}
var nKeySize = secretInfo.pKeyData.length;
var bufData = stringToBytes(secretInfo.pKeyData);
let inputData = new Uint8Array(bufData);
Module.writeArrayToMemory(inputData, pKeyData);
inputData = null;
iRet = Module._SysTransSetEncryptKey(secretInfo.nKeyType, pKeyData, keyLen, nKeySize);
if (iRet != 0) {
console.log("_SysTransSetEncryptKey failed!");
}
if (pKeyData != null) {
Module._free(pKeyData);
pKeyData = null;
}
}
}
//带samplingParam参数代表需要 用到 音频替换功能
if (data.options && typeof data.options.samplingParam !== "undefined") {
var oParam = data.options.samplingParam;
var nCapacityType = 1; //写死1 代表 剔除音频
var nType = 3; //写死3 代表 修改输出目标的海康头配置,内部包含视频参数、音频参数
var nAudioEnable = 1; //音频参数修改使能开关0=不启用1=启用
var nAudioFormat = oParam.iAudioType; //音频编码类型 对应关系参考海康媒体头规范PCM 0x7001 G711_U 0x7110 G711_A 0x7111 AAC 0x2001
var nAudioChannels = oParam.iChannel; //音频通道数直接设置为
var nAudioBitsPerSample = oParam.iAudioBitWidth; //音频位样率
var nAudioSamplesrate = oParam.iAudioSamplingRate; //音频采样率
var nAudioBitrate = oParam.iAudioBitRate; //音频比特率
iRet = Module._SysTransConfig(nCapacityType, nType, nAudioEnable, nAudioFormat,
nAudioChannels, nAudioBitsPerSample, nAudioSamplesrate, nAudioBitrate);
if (iRet != 0) {
console.log("_SysTransConfig Failed:" + iRet);
}
} else {
iRet = Module._SysTransConfig(128, 0, 0, 0, 0, 0, 0, 0); //nCapacityType = 0x00000080 代表开启私有信息回调 解决转mp4后私有信息丢失问题
if (iRet != 0) {
console.log("_SysTransConfig Failed:" + iRet);
}
}
iRet = Module._SysTransRegisterDataCallBack();
if (iRet != 0) {
console.log("_SysTransRegisterDataCallBack Failed:" + iRet);
}
iRet = Module._SysTransStart(null, null);
if (iRet != 0) {
console.log("_SysTransStart Failed:" + iRet);
}
postMessage({type: "created"});
}
if (pHead != null) {
Module._free(pHead);
pHead = null;
}
}
} else if ("inputData" === data.type) {
if (RECORDRTP) {
var aFileData = new Uint8Array(data.buf); // 拷贝一份
var iBufferLen = aFileData.length;
var szBufferLen = iBufferLen.toString(16);
if (szBufferLen.length === 1) {
szBufferLen = "000" + szBufferLen;
} else if (szBufferLen.length === 2) {
szBufferLen = "00" + szBufferLen;
} else if (szBufferLen.length === 3) {
szBufferLen = "0" + szBufferLen;
}
var aData = [0, 0, parseInt(szBufferLen.substring(0, 2), 16), parseInt(szBufferLen.substring(2, 4), 16)];
for (var iIndex = 0, iDataLength = aFileData.length; iIndex < iDataLength; iIndex++) {
aData[iIndex + 4] = aFileData[iIndex];
}
var dataUint8 = new Uint8Array(aData);
postMessage({type: "outputData",
buf: dataUint8.buffer,
dType: 2});
} else {
let inputMode = 0; //代表输入原始数据
if (data.samplingParam) {
iRet = Module._SysTransInputAudioPara(5, data.samplingParam.iChannel, data.samplingParam.iAudioBitWidth,
data.samplingParam.iAudioSamplingRate, data.samplingParam.iTimeStamp, data.samplingParam.iAudioBitRate); //参数含义和_SysTransConfig类似
if (iRet != 0) {
console.log("_SysTransInputAudioPara Failed:" + iRet);
}
inputMode = 2; //输入替换的音频
}
var pInputDataBuf = Module._malloc(data.len);
var idataLen = data.len;
self.writeArrayToMemory(new Uint8Array(data.buf), pInputDataBuf);
// 输入数据每次最多2m
let pp = Module._SysTransInputData(inputMode, pInputDataBuf, idataLen);
if (pp == -2147483627) {
//-2147483627 对应十六进制的80000015
postMessage({ type: "outputData",
dType: 1500 }); //标记为密码错误
} else if (pp == -2147483647) {
postMessage({ type: "outputData",
dType: 1501 }); //标记为格式不支持
} else if (pp != 0) {
console.log("InputData Failed:" + pp);
}
Module._free(pInputDataBuf);
}
} else if ("release" === data.type) {
var iRet = Module._SysTransStop();
if (iRet != 0) {
console.log("_SysTransStop failed!");
}
Module._SysTransRelease();
if (iRet != 0) {
console.log("_SysTransRelease failed!");
}
close();
}
};

File diff suppressed because one or more lines are too long

View File

@@ -1,26 +0,0 @@
import { AxiosProgressEvent } from "axios";
import { request } from "@/api/request";
/** 上传文件 */
const FtsUpload = (data: FormData, onUploadProgress?: (progress: number) => void) => {
data.append('provider', 'local');
data.append('bucket', 'visual');
return request.post(
`/fts/v1/uploader`,
data,
{
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: onUploadProgress ? (progressEvent: AxiosProgressEvent) => {
if (progressEvent.total) {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
onUploadProgress(percentCompleted);
}
} : undefined
}
);
};
export default FtsUpload;

View File

@@ -1,93 +1,86 @@
import { request } from "@/api/request";
import { request } from '@/api/request'
import type { KbReply } from './types'
/** 分类类型 */
/** 公共分类状态。 */
export type CategoryStatus = 'active' | 'inactive'
/** 公共分类。 */
export interface Category {
id: number;
created_at: string;
updated_at: string;
name: string;
description: string;
type: string;
icon: string;
color: string;
parent_id: number;
level: number;
path: string;
sort_order: number;
status: string;
creator_id: number;
creator_name: string;
doc_count: number;
faq_count: number;
metadata: string | null;
remarks: string;
id: number
created_at: string
updated_at: string
name: string
description: string
type: 'general'
icon: string
color: string
parent_id: number
level: 1 | 2 | 3
path: string
sort_order: number
status: CategoryStatus
creator_id: number
creator_name: string
doc_count: number
faq_count: number
metadata: string
remarks: string
}
/** API响应包装类型 */
export interface ApiResponse<T = any> {
code: number;
message: string;
data: T;
/** 公共分类树节点;详情字段需通过详情接口获取。 */
export interface CategoryTreeNode {
id: number
name: string
description: string
type: 'general'
icon: string
color: string
parent_id: number
level: 1 | 2 | 3
path: string
sort_order: number
status: CategoryStatus
doc_count: number
faq_count: number
children: CategoryTreeNode[]
}
/** 创建分类请求参数 */
/** 创建分类参数 */
export interface CreateCategoryParams {
name: string;
description?: string;
type?: string;
icon?: string;
color?: string;
parent_id?: number;
sort_order?: number;
remarks?: string;
name: string
description: string
icon: string
color: string
parent_id: number
sort_order: number
status: CategoryStatus
remarks: string
}
/** 更新分类请求参数 */
export interface UpdateCategoryParams {
id: number;
name?: string;
description?: string;
icon?: string;
color?: string;
sort_order?: number;
status?: string;
remarks?: string;
/** 更新分类参数 */
export interface UpdateCategoryParams extends CreateCategoryParams {
id: number
}
/** 获取分类列表参数 */
/** 分类列表参数 */
export interface FetchCategoryListParams {
type?: string;
parent_id?: number;
parent_id?: number
}
/** 创建分类 */
export const createCategory = (data: CreateCategoryParams) => {
return request.post<ApiResponse<Category>>("/Kb/v1/category/create", data);
};
/** 创建公共分类 */
export const createCategory = (data: CreateCategoryParams) => request.post<KbReply<Category>>('/Kb/v1/category/create', data)
/** 更新分类 */
export const updateCategory = (data: UpdateCategoryParams) => {
return request.post<ApiResponse<Category>>("/Kb/v1/category/update", data);
};
/** 更新公共分类 */
export const updateCategory = (data: UpdateCategoryParams) => request.post<KbReply<Category>>('/Kb/v1/category/update', data)
/** 删除分类 */
export const deleteCategory = (id: number) => {
return request.delete<ApiResponse<string>>(`/Kb/v1/category/${id}`);
};
/** 删除公共分类 */
export const deleteCategory = (id: number) => request.delete<KbReply<string>>(`/Kb/v1/category/${id}`)
/** 获取分类详情 */
export const fetchCategoryDetail = (id: number) => {
return request.get<ApiResponse<Category>>(`/Kb/v1/category/${id}`);
};
/** 获取公共分类详情 */
export const fetchCategoryDetail = (id: number) => request.get<KbReply<Category>>(`/Kb/v1/category/${id}`)
/** 获取分类列表 */
export const fetchCategoryList = (params?: FetchCategoryListParams) => {
return request.get<ApiResponse<Category[]>>("/Kb/v1/category/list", { params });
};
/** 获取公共分类列表 */
export const fetchCategoryList = (params?: FetchCategoryListParams) => request.get<KbReply<Category[]>>('/Kb/v1/category/list', { params })
/** 获取分类树 */
export const fetchCategoryTree = (type?: string) => {
return request.get<ApiResponse<Category[]>>("/Kb/v1/category/tree", {
params: type ? { type } : undefined,
});
};
/** 获取完整公共分类树,包含停用节点。 */
export const fetchCategoryTree = () => request.get<KbReply<CategoryTreeNode[]>>('/Kb/v1/category/tree')

View File

@@ -1,218 +1,118 @@
import { request } from "@/api/request";
import { request } from '@/api/request'
import type { KbReply } from './types'
/** 文档状态 */
export type DocumentStatus = 'draft' | 'published' | 'reviewed' | 'rejected';
/** 文档状态 */
export type DocumentStatus = 'draft' | 'published' | 'reviewed' | 'rejected'
/** 文档类型。 */
export type DocumentType = 'common' | 'guide' | 'solution' | 'troubleshoot' | 'process' | 'technical'
/** 文档列表范围。 */
export type DocumentScope = 'my' | 'all'
/** 文档类型 */
export type DocumentType = 'common' | 'guide' | 'solution' | 'troubleshoot' | 'process' | 'technical';
/** 文档接口类型 */
/** 文档资源。 */
export interface Document {
id: number;
created_at: string;
updated_at: string;
doc_no: string;
title: string;
description: string;
content: string;
type: DocumentType;
status: DocumentStatus;
category_id: number;
sub_category: string;
author_id: number;
author_name: string;
reviewer_id: number;
reviewer_name: string;
reviewed_at: string | null;
published_at: string | null;
publisher_id: number;
view_count: number;
like_count: number;
comment_count: number;
download_count: number;
version: string;
version_notes: string;
tags: string;
attachments: string | null;
related_docs: string | null;
metadata: string | null;
keywords: string;
remarks: string;
is_favorited?: boolean;
id: number
created_at: string
updated_at: string
doc_no: string
title: string
description: string
content: string
type: DocumentType
status: DocumentStatus
category_id: number
sub_category: string
author_id: number
author_name: string
reviewer_id: number
reviewer_name: string
reviewed_at: string | null
published_at: string | null
publisher_id: number
view_count: number
like_count: number
comment_count: number
download_count: number
version: string
version_notes: string
tags: string
attachments: string
related_docs: string
detection_point_ids: string
metadata: string
keywords: string
remarks: string
is_favorited: boolean
}
/** API响应包装类型 */
export interface ApiResponse<T = any> {
code: number;
message: string;
data: T;
/** 文档分页结果。 */
export interface DocumentPage {
total: number
page: number
page_size: number
data: Document[]
}
/** 分页响应类型 */
export interface PaginatedResponse<T> {
total: number;
page: number;
page_size: number;
data: T[];
}
/** 创建文档请求参数 */
/** 文档创建字段。 */
export interface CreateDocumentParams {
title: string;
description?: string;
content: string;
type?: DocumentType;
category_id?: number;
sub_category?: string;
keywords?: string;
tags?: string;
remarks?: string;
title: string
description: string
content: string
type: DocumentType
category_id: number
sub_category: string
keywords: string
tags: string
detection_point_ids: string
remarks: string
}
/** 更新文档请求参数 */
export interface UpdateDocumentParams {
id: number;
title?: string;
description?: string;
content?: string;
type?: DocumentType;
category_id?: number;
sub_category?: string;
keywords?: string;
tags?: string;
remarks?: string;
/** 文档编辑字段。 */
export interface UpdateDocumentParams extends CreateDocumentParams {
id: number
}
/** 获取文档列表参数 */
/** 文档列表参数 */
export interface FetchDocumentListParams {
page?: number;
page_size?: number;
keyword?: string;
type?: DocumentType;
status?: DocumentStatus;
category_id?: number;
page?: number
page_size?: number
scope?: DocumentScope
keyword?: string
type?: DocumentType
status?: DocumentStatus
category_id?: number
}
/** 文档类型选项 */
export const documentTypeOptions = [
export const documentTypeOptions: Array<{ label: string; value: DocumentType }> = [
{ label: '通用文档', value: 'common' },
{ label: '操作指南', value: 'guide' },
{ label: '解决方案', value: 'solution' },
{ label: '故障排查', value: 'troubleshoot' },
{ label: '流程规范', value: 'process' },
{ label: '技术文档', value: 'technical' },
];
]
/** 文档状态选项 */
export const documentStatusOptions = [
{ label: '草稿', value: 'draft' },
{ label: '已发布', value: 'published' },
{ label: '已审核', value: 'reviewed' },
{ label: '未通过审核', value: 'rejected' },
];
/** 创建文档草稿。 */
export const createDocument = (data: CreateDocumentParams) => request.post<KbReply<Document>>('/Kb/v1/document/create', data)
/** 获取文档状态文本 */
export const getDocumentStatusText = (status: DocumentStatus): string => {
const statusMap: Record<DocumentStatus, string> = {
draft: '草稿',
published: '已发布',
reviewed: '已审核',
rejected: '未通过审核',
};
return statusMap[status] || status;
};
/** 更新文档。 */
export const updateDocument = (data: UpdateDocumentParams) => request.post<KbReply<Document>>('/Kb/v1/document/update', data)
/** 获取文档状态颜色 */
export const getDocumentStatusColor = (status: DocumentStatus): string => {
const colorMap: Record<DocumentStatus, string> = {
draft: 'gray',
published: 'blue',
reviewed: 'green',
rejected: 'red',
};
return colorMap[status] || 'gray';
};
/** 删除文档并移入回收站。 */
export const deleteDocument = (id: number) => request.delete<KbReply<string>>(`/Kb/v1/document/${id}`)
/** 获取文档类型文本 */
export const getDocumentTypeText = (type: DocumentType): string => {
const typeMap: Record<DocumentType, string> = {
common: '通用文档',
guide: '操作指南',
solution: '解决方案',
troubleshoot: '故障排查',
process: '流程规范',
technical: '技术文档',
};
return typeMap[type] || type;
};
/** 获取文档详情。 */
export const fetchDocumentDetail = (id: number) => request.get<KbReply<Document>>(`/Kb/v1/document/${id}`)
/** 创建文档 */
export const createDocument = (data: CreateDocumentParams) => {
return request.post<ApiResponse<Document>>("/Kb/v1/document/create", data);
};
/** 获取指定可见范围的文档列表。 */
export const fetchDocumentList = (params: FetchDocumentListParams) => request.get<KbReply<DocumentPage>>('/Kb/v1/document/list', { params })
/** 更新文档 */
export const updateDocument = (data: UpdateDocumentParams) => {
return request.post<ApiResponse<Document>>("/Kb/v1/document/update", data);
};
/** 提交文档审核。 */
export const publishDocument = (id: number) => request.post<KbReply<string>>('/Kb/v1/document/publish', { id })
/** 删除文档(移入回收站) */
export const deleteDocument = (id: number) => {
return request.delete<ApiResponse<string>>(`/Kb/v1/document/${id}`);
};
/** 收藏文档。 */
export const favoriteDocument = (id: number) =>
request.post<KbReply<unknown>>('/Kb/v1/favorite/collect', { resource_type: 'document', resource_id: id })
/** 获取文档详情 */
export const fetchDocumentDetail = (id: number) => {
return request.get<ApiResponse<Document>>(`/Kb/v1/document/${id}`);
};
/** 获取文档列表 */
export const fetchDocumentList = (params?: FetchDocumentListParams) => {
return request.get<ApiResponse<PaginatedResponse<Document>>>("/Kb/v1/document/list", { params });
};
/** 发布文档 */
export const publishDocument = (id: number) => {
return request.post<ApiResponse<string>>("/Kb/v1/document/publish", { id });
};
/** 移入回收站 */
export const moveToTrash = (resourceId: number, resourceType: string) => {
return request.post<ApiResponse<string>>("/Kb/v1/trash/move", {
resource_id: resourceId,
resource_type: resourceType,
});
};
/** 获取我的文档列表(由我创建的所有文档) */
export const fetchMyDocumentList = (params?: FetchDocumentListParams) => {
return request.get<ApiResponse<PaginatedResponse<Document>>>("/Kb/v1/review/publish/list", { params });
};
/** 获取已审核通过的文档列表 */
export const fetchApprovedDocumentList = (params?: FetchDocumentListParams) => {
return request.get<ApiResponse<PaginatedResponse<Document>>>("/Kb/v1/review/approved/list", { params });
};
/** 收藏文档 */
export const favoriteDocument = (id: number, remarks?: string) => {
return request.post<ApiResponse<string>>("/Kb/v1/favorite/collect", {
resource_type: 'document',
resource_id: id,
remarks,
});
};
/** 取消收藏文档 */
export const unfavoriteDocument = (id: number) => {
return request.post<ApiResponse<string>>("/Kb/v1/favorite/uncollect", {
resource_type: 'document',
resource_id: id,
});
};
/** 下载文档 */
export const downloadDocument = (id: number) => {
return request.get<Blob>(`/Kb/v1/document/${id}/download`, { responseType: 'blob' });
};
/** 取消收藏文档。 */
export const unfavoriteDocument = (id: number) =>
request.post<KbReply<string>>('/Kb/v1/favorite/uncollect', { resource_type: 'document', resource_id: id })

177
src/api/kb/faq.ts Normal file
View File

@@ -0,0 +1,177 @@
import { request } from '@/api/request'
import type { KbReply } from './types'
export { fetchCategoryTree as fetchGeneralCategoryTree } from './category'
export type { CategoryTreeNode } from './category'
export type { KbReply } from './types'
/** FAQ 审核状态。 */
export type FaqStatus = 'draft' | 'published' | 'reviewed' | 'rejected'
/** FAQ 优先级。 */
export type FaqPriority = 'low' | 'medium' | 'high'
/** FAQ 列表可见范围。 */
export type FaqScope = 'my' | 'all'
/** FAQ 资源。 */
export interface Faq {
id: number
created_at: string
updated_at: string
faq_no: string
question: string
answer: string
status: FaqStatus
priority: FaqPriority
category_id: number
sub_category: string
problem_type: string
solution: string
process_steps: string
prerequisites: string
author_id: number
author_name: string
reviewer_id: number
reviewer_name: string
reviewed_at: string | null
published_at: string | null
view_count: number
use_count: number
helpful_count: number
useless_count: number
tags: string
related_faqs: string
related_docs: string
related_links: string
detection_point_ids: string
attachments: string
keywords: string
applicable_scope: string
remarks: string
is_favorited: boolean
}
/** FAQ 分页结果。 */
export interface FaqPage {
total: number
page: number
page_size: number
data: Faq[]
}
/** FAQ 列表查询参数。 */
export interface FaqListParams {
page?: number
page_size?: number
scope?: FaqScope
keyword?: string
category_id?: number
status?: FaqStatus
priority?: FaqPriority
problem_type?: string
}
/** FAQ 创建和编辑字段。 */
export interface FaqFormData {
question: string
answer: string
priority: FaqPriority
category_id: number
sub_category: string
problem_type: string
solution: string
process_steps: string
prerequisites: string
keywords: string
tags: string
detection_point_ids: string
applicable_scope: string
remarks: string
}
/** FAQ 编辑请求。 */
export interface UpdateFaqData extends FaqFormData {
id: number
}
/** FAQ 状态筛选项。 */
export const faqStatusOptions: Array<{ label: string; value: FaqStatus }> = [
{ label: '草稿', value: 'draft' },
{ label: '待审核', value: 'published' },
{ label: '已审核', value: 'reviewed' },
{ label: '已拒绝', value: 'rejected' },
]
/** FAQ 优先级筛选项。 */
export const faqPriorityOptions: Array<{ label: string; value: FaqPriority }> = [
{ label: '低', value: 'low' },
{ label: '中', value: 'medium' },
{ label: '高', value: 'high' },
]
/** 常用问题类型;接口仍允许自由字符串。 */
export const faqProblemTypeOptions = [
{ label: '故障', value: '故障' },
{ label: '咨询', value: '咨询' },
{ label: '请求', value: '请求' },
{ label: '其他', value: '其他' },
]
const faqStatusMeta: Record<FaqStatus, { text: string; color: string }> = {
draft: { text: '草稿', color: 'gray' },
published: { text: '待审核', color: 'orange' },
reviewed: { text: '已审核', color: 'green' },
rejected: { text: '已拒绝', color: 'red' },
}
const faqPriorityMeta: Record<FaqPriority, { text: string; color: string }> = {
low: { text: '低', color: 'gray' },
medium: { text: '中', color: 'blue' },
high: { text: '高', color: 'orange' },
}
/** 获取 FAQ 状态文案。 */
export const getFaqStatusText = (status: FaqStatus | string) => faqStatusMeta[status as FaqStatus]?.text || status || '-'
/** 获取 FAQ 状态标签颜色。 */
export const getFaqStatusColor = (status: FaqStatus | string) => faqStatusMeta[status as FaqStatus]?.color || 'gray'
/** 获取 FAQ 优先级文案。 */
export const getFaqPriorityText = (priority: FaqPriority | string) => faqPriorityMeta[priority as FaqPriority]?.text || priority || '-'
/** 获取 FAQ 优先级标签颜色。 */
export const getFaqPriorityColor = (priority: FaqPriority | string) => faqPriorityMeta[priority as FaqPriority]?.color || 'gray'
/** 获取 FAQ 列表。 */
export const fetchFaqList = (params: FaqListParams) =>
request.get<KbReply<FaqPage>>('/Kb/v1/faq/list', {
params,
})
/** 获取 FAQ 详情。 */
export const fetchFaqDetail = (id: number) => request.get<KbReply<Faq>>(`/Kb/v1/faq/${id}`)
/** 创建 FAQ 草稿。 */
export const createFaq = (data: FaqFormData) => request.post<KbReply<Faq>>('/Kb/v1/faq/create', data)
/** 更新 FAQ。 */
export const updateFaq = (data: UpdateFaqData) => request.post<KbReply<Faq>>('/Kb/v1/faq/update', data)
/** 提交 FAQ 审核。 */
export const publishFaq = (id: number) => request.post<KbReply<string>>('/Kb/v1/faq/publish', { id })
/** 删除 FAQ 并移入回收站。 */
export const deleteFaq = (id: number) => request.delete<KbReply<string>>(`/Kb/v1/faq/${id}`)
/** 收藏 FAQ。 */
export const favoriteFaq = (id: number) =>
request.post<KbReply<unknown>>('/Kb/v1/favorite/collect', {
resource_type: 'faq',
resource_id: id,
})
/** 取消收藏 FAQ。 */
export const unfavoriteFaq = (id: number) =>
request.post<KbReply<string>>('/Kb/v1/favorite/uncollect', {
resource_type: 'faq',
resource_id: id,
})

View File

@@ -1,86 +1,49 @@
import { request } from '@/api/request';
import { request } from '@/api/request'
import type { Document } from './document'
import type { Faq } from './faq'
import type { KbReply } from './types'
/** 资源类型 */
export type ResourceType = 'document' | 'faq';
export type ResourceType = 'document' | 'faq'
/** 收藏记录接口 */
/** 收藏记录 */
export interface Favorite {
id: number;
created_at: string;
resource_type: ResourceType;
resource_id: number;
resource_name: string;
remarks: string;
is_deleted: boolean;
resource_data?: any;
id: number
created_at: string
resource_type: ResourceType
resource_id: number
resource_name: string
remarks: string
is_deleted: boolean
resource_data?: Document | Faq
}
/** 收藏列表响应 */
export interface FavoriteListResponse {
total: number;
page: number;
page_size: number;
data: Favorite[];
export interface FavoritePage {
total: number
page: number
page_size: number
data: Favorite[]
}
/** 获取收藏列表参数 */
export interface FetchFavoriteListParams {
page?: number;
page_size?: number;
resource_type?: ResourceType;
page?: number
page_size?: number
resource_type?: ResourceType
}
/** 收藏请求参数 */
export interface CollectParams {
resource_type: ResourceType;
resource_id: number;
remarks?: string;
resource_type: ResourceType
resource_id: number
remarks?: string
}
/** 取消收藏参数 */
export interface UncollectParams {
resource_type: ResourceType;
resource_id: number;
}
export type UncollectParams = Omit<CollectParams, 'remarks'>
/** API响应包装类型 */
export interface ApiResponse<T = any> {
code: number;
message: string;
data: T;
}
/** 获取收藏列表。 */
export const fetchFavoriteList = (params: FetchFavoriteListParams = {}) =>
request.get<KbReply<FavoritePage>>('/Kb/v1/favorite/list', { params })
/**
* 获取收藏列表
*/
export async function fetchFavoriteList(
params: FetchFavoriteListParams = {}
): Promise<ApiResponse<FavoriteListResponse>> {
return request.get<ApiResponse<FavoriteListResponse>>('/Kb/v1/favorite/list', {
params,
});
}
/** 收藏资源。 */
export const collectResource = (data: CollectParams) => request.post<KbReply<Favorite>>('/Kb/v1/favorite/collect', data)
/**
* 收藏资源
*/
export async function collectResource(
data: CollectParams
): Promise<ApiResponse<Favorite>> {
return request.post<ApiResponse<Favorite>>('/Kb/v1/favorite/collect', data);
}
/**
* 取消收藏
*/
export async function uncollectResource(
data: UncollectParams
): Promise<ApiResponse<string>> {
return request.post<ApiResponse<string>>('/Kb/v1/favorite/uncollect', data);
}
/** 资源类型选项 */
export const resourceTypeOptions = [
{ label: '文档', value: 'document' },
{ label: 'FAQ', value: 'faq' },
];
/** 取消收藏。 */
export const uncollectResource = (data: UncollectParams) => request.post<KbReply<string>>('/Kb/v1/favorite/uncollect', data)

68
src/api/kb/knowledge.ts Normal file
View File

@@ -0,0 +1,68 @@
import { request } from '@/api/request'
export interface DetectionPoint {
id: number
resource_category: string
metric_code: string
alert_rule_id: number
fault_type: string
knowledge_type: 'document' | 'faq'
knowledge_id: number
knowledge_title: string
enabled: boolean
remarks: string
}
export interface KnowledgeRecommendation {
knowledge_type: 'document' | 'faq'
knowledge_id: number
title: string
status: string
resource_category: string
metric_code: string
alert_rule_id: number
fault_type: string
detection_point_id: number
score: number
reason: string
}
export interface IncidentKnowledgeRelation {
id: number
incident_id: number
ticket_id: number
knowledge_type: 'document' | 'faq'
knowledge_id: number
knowledge_title: string
relation_type: string
source: string
score: number
reason: string
created_at: string
}
export const fetchKnowledgeRecommendations = (params: {
resource_category?: string
metric_code?: string
alert_rule_id?: number
fault_type?: string
limit?: number
}) => request.get('/Kb/v1/knowledge/recommend', { params })
export const fetchIncidentKnowledge = (incidentId: number) => request.get(`/Kb/v1/knowledge/incidents/${incidentId}/relations`)
export const fetchTicketKnowledge = (ticketId: number) => request.get('/Kb/v1/knowledge/relations', { params: { ticket_id: ticketId } })
export const relateIncidentKnowledge = (data: {
incident_id?: number
ticket_id?: number
knowledge_type: 'document' | 'faq'
knowledge_id: number
knowledge_title?: string
relation_type?: string
source?: string
score?: number
reason?: string
}) => request.post('/Kb/v1/knowledge/relations', data)
export const createPostmortemDraft = (data: Record<string, any>) => request.post('/Kb/v1/knowledge/postmortem/draft', data)

View File

@@ -1,173 +1,71 @@
import { request } from "@/api/request";
import { request } from '@/api/request'
import type { Document } from './document'
import type { Faq } from './faq'
import type { KbReply } from './types'
export type ReviewStatsResourceType = "all" | "document" | "faq";
export type ReviewResourceType = 'all' | 'document' | 'faq'
/** 审核统计接口返回的 data 字段 */
export interface ReviewStatsPayload {
need_my_review_document?: number;
need_my_review_faq?: number;
need_my_review_total?: number;
need_my_review_unreviewed_document?: number;
need_my_review_unreviewed_faq?: number;
need_my_review_unreviewed_total?: number;
}
/** 文档资源类型 */
export interface DocumentResource {
id: number;
created_at: string;
updated_at: string;
doc_no: string;
title: string;
description: string;
content: string;
type: string;
status: string;
category_id: number;
sub_category: string;
author_id: number;
author_name: string;
reviewer_id: number;
reviewer_name: string;
reviewed_at: string | null;
published_at: string | null;
publisher_id: number;
view_count: number;
like_count: number;
comment_count: number;
download_count: number;
version: string;
version_notes: string;
tags: string;
attachments: string | null;
related_docs: string | null;
metadata: string | null;
keywords: string;
remarks: string;
}
/** FAQ资源类型 */
export interface FaqResource {
id: number;
created_at: string;
updated_at: string;
faq_no: string;
question: string;
answer: string;
status: string;
priority: string;
category_id: number;
sub_category: string;
problem_type: string;
solution: string;
process_steps: string;
prerequisites: string;
author_id: number;
author_name: string;
reviewer_id: number;
reviewer_name: string;
reviewed_at: string | null;
published_at: string | null;
view_count: number;
use_count: number;
helpful_count: number;
useless_count: number;
tags: string;
related_faqs: string | null;
related_docs: string | null;
related_links: string | null;
attachments: string | null;
keywords: string;
applicable_scope: string;
remarks: string;
}
/** 审核列表项resource_type 为 all 时) */
/** 统一审核列表项。 */
export interface ReviewListItem {
type: "document" | "faq";
resource: DocumentResource | FaqResource;
type: 'document' | 'faq'
resource: Document | Faq
}
/** 分页响应类型 */
export interface PaginatedResponse<T> {
total: number;
page: number;
page_size: number;
data: T[];
/** 审核分页结果。 */
export interface ReviewPage {
total: number
page: number
page_size: number
data: ReviewListItem[]
}
/** API响应包装类型 */
export interface ApiResponse<T = any> {
code: number;
message: string;
data: T;
/** 当前审核人的待审数量。 */
export interface ReviewStats {
need_my_review_document: number
need_my_review_faq: number
need_my_review_total: number
need_my_review_unreviewed_document: number
need_my_review_unreviewed_faq: number
need_my_review_unreviewed_total: number
}
/** 获取审核列表参数 */
/** 审核列表参数 */
export interface FetchReviewListParams {
page?: number;
page_size?: number;
resource_type?: ReviewStatsResourceType;
page?: number
page_size?: number
resource_type?: ReviewResourceType
}
/** 审核通过参数 */
export interface ApproveParams {
resource_type: "document" | "faq";
id: number;
resource_type: 'document' | 'faq'
id: number
}
/** 审核拒绝参数 */
export interface RejectParams {
resource_type: "document" | "faq";
id: number;
reason?: string;
export interface RejectParams extends ApproveParams {
reason: string
}
/** 按当前登录用户统计需要本人审核的数量(不含本人为作者的稿件) */
export const fetchReviewStats = (params?: { resource_type?: ReviewStatsResourceType }) =>
request.get("/Kb/v1/review/stats", params ? { params } : undefined);
/** 获取当前用户可审核的待审资源。 */
export const fetchReviewList = (params: FetchReviewListParams) => request.get<KbReply<ReviewPage>>('/Kb/v1/review/list', { params })
/** 获取待审核列表 */
export const fetchReviewList = (params?: FetchReviewListParams) =>
request.get<ApiResponse<PaginatedResponse<ReviewListItem | DocumentResource | FaqResource>>>("/Kb/v1/review/list", { params });
/** 获取当前审核人的待审统计。 */
export const fetchReviewStats = (params: { resource_type?: ReviewResourceType } = {}) =>
request.get<KbReply<ReviewStats>>('/Kb/v1/review/stats', { params })
/** 审核通过 */
export const approveReview = (data: ApproveParams) =>
request.post<ApiResponse<string>>("/Kb/v1/review/approve", data);
/** 审核通过 */
export const approveReview = (data: ApproveParams) => request.post<KbReply<string>>('/Kb/v1/review/approve', data)
/** 审核拒绝 */
export const rejectReview = (data: RejectParams) =>
request.post<ApiResponse<string>>("/Kb/v1/review/reject", data);
/** 审核拒绝 */
export const rejectReview = (data: RejectParams) => request.post<KbReply<string>>('/Kb/v1/review/reject', data)
/** 获取文档详情 */
export const fetchDocumentDetail = (id: number) =>
request.get<ApiResponse<DocumentResource>>(`/Kb/v1/document/${id}`);
/** 获取FAQ详情 */
export const fetchFaqDetail = (id: number) =>
request.get<ApiResponse<FaqResource>>(`/Kb/v1/faq/${id}`);
/** 资源类型选项 */
export const resourceTypeOptions = [
export const resourceTypeOptions: Array<{ label: string; value: ReviewResourceType }> = [
{ label: '全部', value: 'all' },
{ label: '文档', value: 'document' },
{ label: 'FAQ', value: 'faq' },
];
]
/** 获取资源类型文 */
export const getResourceTypeText = (type: string): string => {
const typeMap: Record<string, string> = {
document: '文档',
faq: 'FAQ',
};
return typeMap[type] || type;
};
/** 获取审核资源类型文案。 */
export const getResourceTypeText = (type: string) => ({ document: '文档', faq: 'FAQ' })[type] || type
/** 获取资源类型颜色 */
export const getResourceTypeColor = (type: string): string => {
const colorMap: Record<string, string> = {
document: 'arcoblue',
faq: 'green',
};
return colorMap[type] || 'gray';
};
/** 获取审核资源类型颜色 */
export const getResourceTypeColor = (type: string) => ({ document: 'arcoblue', faq: 'green' })[type] || 'gray'

View File

@@ -1,88 +1,53 @@
import { request } from "@/api/request";
import { request } from '@/api/request'
import type { KbReply } from './types'
/** API响应包装类型 */
export interface ApiResponse<T = any> {
code: number;
message: string;
data: T;
}
export type TrashResourceType = 'document' | 'faq'
/** 分页响应类型 */
export interface PaginatedResponse<T> {
total: number;
page: number;
page_size: number;
data: T[];
}
/** 回收站记录 */
/** 回收站记录。 */
export interface TrashRecord {
id: number;
created_at: string;
updated_at: string;
resource_type: 'document' | 'faq';
resource_id: number;
resource_name: string;
deleted_by: number;
deleted_name: string;
deleted_time: string;
delete_reason: string;
original_data: string;
remarks: string;
id: number
created_at: string
updated_at: string
resource_type: TrashResourceType
resource_id: number
resource_name: string
deleted_by: number
deleted_name: string
deleted_time: string
delete_reason: string
original_data: string
remarks: string
}
export interface TrashPage {
total: number
page: number
page_size: number
data: TrashRecord[]
}
/** 获取回收站列表参数 */
export interface FetchTrashListParams {
page?: number;
page_size?: number;
resource_type?: 'document' | 'faq';
page?: number
page_size?: number
resource_type?: TrashResourceType
}
/** 恢复资源请求参数 */
export interface RestoreTrashParams {
id: number;
}
/** 彻底删除请求参数 */
export interface DeleteTrashParams {
id: number;
}
/** 资源类型选项 */
export const resourceTypeOptions = [
export const resourceTypeOptions: Array<{ label: string; value: TrashResourceType }> = [
{ label: '文档', value: 'document' },
{ label: '常见问题', value: 'faq' },
];
{ label: 'FAQ', value: 'faq' },
]
/** 获取资源类型文 */
export const getResourceTypeText = (type: string): string => {
const typeMap: Record<string, string> = {
document: '文档',
faq: '常见问题',
};
return typeMap[type] || type;
};
/** 获取回收站资源类型文案。 */
export const getResourceTypeText = (type: string) => ({ document: '文档', faq: 'FAQ' })[type] || type
/** 获取资源类型颜色 */
export const getResourceTypeColor = (type: string): string => {
const colorMap: Record<string, string> = {
document: 'blue',
faq: 'green',
};
return colorMap[type] || 'gray';
};
/** 获取回收站资源类型颜色 */
export const getResourceTypeColor = (type: string) => ({ document: 'blue', faq: 'green' })[type] || 'gray'
/** 获取回收站列表 */
export const fetchTrashList = (params?: FetchTrashListParams) => {
return request.get<ApiResponse<PaginatedResponse<TrashRecord>>>("/Kb/v1/trash/list", { params });
};
/** 获取回收站列表 */
export const fetchTrashList = (params: FetchTrashListParams) => request.get<KbReply<TrashPage>>('/Kb/v1/trash/list', { params })
/** 恢复资源 */
export const restoreTrash = (data: RestoreTrashParams) => {
return request.post<ApiResponse<string>>("/Kb/v1/trash/restore", data);
};
/** 恢复资源 */
export const restoreTrash = (id: number) => request.post<KbReply<string>>('/Kb/v1/trash/restore', { id })
/** 彻底删除 */
export const deleteTrash = (data: DeleteTrashParams) => {
return request.post<ApiResponse<string>>("/Kb/v1/trash/delete", data);
};
/** 彻底删除资源。 */
export const deleteTrash = (id: number) => request.post<KbReply<string>>('/Kb/v1/trash/delete', { id })

7
src/api/kb/types.ts Normal file
View File

@@ -0,0 +1,7 @@
/** 知识库服务统一响应。 */
export interface KbReply<T> {
code: number
message: string
details: T
timeseq: number
}

View File

@@ -1,4 +1,5 @@
import axios from 'axios'
import { request } from '@/api/request'
export interface MessageRecord {
id: number
@@ -10,19 +11,93 @@ export interface MessageRecord {
time: string
status: 0 | 1
messageType?: number
deliveryStatus?: string
channelType?: string
attemptCount?: number
lastError?: string
incidentId?: number
alertRecordId?: number
nextRetryAt?: string
occurrenceCount?: number
firstOccurredAt?: string
lastOccurredAt?: string
lastDeliveryId?: number
}
export type MessageListType = MessageRecord[]
export function queryMessageList() {
return axios.post<MessageListType>('/api/message/list')
interface ApiResponse<T> {
code?: number
message?: string
data?: T
details?: T
}
/** 通知投递历史 */
export interface NotificationDeliveryRecord {
id: number
incident_id: number
alert_record_id: number
channel_id: number
channel_type: string
target: string
subject: string
content: string
status: string
attempt_count: number
next_retry_at?: string
last_error?: string
trace_id?: string
delivered_at?: string
created_at?: string
updated_at?: string
}
/** 投递历史查询参数 */
export interface NotificationDeliveryQuery {
page?: number
page_size?: number
keyword?: string
incident_id?: number
status?: string
channel_type?: string
}
/** 分页结果 */
export interface PageResult<T> {
total: number
page: number
page_size: number
data: T[]
}
function unwrapData<T>(response: ApiResponse<T> | T): T {
const wrapped = response as ApiResponse<T>
return (wrapped.details ?? wrapped.data ?? response) as T
}
/** 查询站内消息列表 */
export async function queryMessageList() {
const response = await request.get<ApiResponse<MessageListType>>('/Alert/v1/message/list')
return { data: unwrapData<MessageListType>(response) || [] }
}
interface MessageStatus {
ids: number[]
}
/** 标记站内消息已读 */
export function setMessageStatus(data: MessageStatus) {
return axios.post<MessageListType>('/api/message/read', data)
return request.post<ApiResponse<boolean>>('/Alert/v1/message/read', data)
}
/** 查询通知投递历史 */
export function queryNotificationDeliveryHistory(params?: NotificationDeliveryQuery) {
return request.get<ApiResponse<PageResult<NotificationDeliveryRecord>>>('/Alert/v1/notification/deliveries', { params: params || {} })
}
/** 人工补发通知 */
export function resendNotificationDelivery(id: number) {
return request.post<ApiResponse<{ delivery_id: number; status: string }>>(`/Alert/v1/notification/deliveries/${id}/resend`)
}
export interface ChatRecord {

View File

@@ -1,35 +1,35 @@
import { request } from "@/api/request";
import { request } from '@/api/request'
/** 批次列表 */
export const fetchLoanBatch = (data: { page: number, size: number }) => request.post("/factor/v1/fetch/loan_batch", data);
export const fetchLoanBatch = (data: { page: number; size: number }) => request.post('/factor/v1/fetch/loan_batch', data)
/** 上报列表 */
export const fetchTransferWatch = (data: { page: number, size: number }) => request.post("/factor/v1/fetch/transfer_watch", data);
export const fetchTransferWatch = (data: { page: number; size: number }) => request.post('/factor/v1/fetch/transfer_watch', data)
/** 回盘列表 */
export const fetchTransferDown = (data: { page: number, size: number }) => request.post("/factor/v1/fetch/transfer_down", data);
export const fetchTransferDown = (data: { page: number; size: number }) => request.post('/factor/v1/fetch/transfer_down', data)
/** 放款数据列表 */
export const fetchDataFile = (data: { page: number, size: number }) => request.post("/factor/v1/fetch/data_file", data);
export const fetchDataFile = (data: { page: number; size: number }) => request.post('/factor/v1/fetch/data_file', data)
/** 回盘数据列表 */
export const fetchDataReply = (data: { page: number, size: number }) => request.post("/factor/v1/fetch/data_reply", data);
export const fetchDataReply = (data: { page: number; size: number }) => request.post('/factor/v1/fetch/data_reply', data)
/** 贷款支付数据列表 */
export const fetchLoanPayment = (data: { page: number, size: number, status?: number }) => request.post("/factor/v1/fetch/loan_payment", data);
export const fetchLoanPayment = (data: { page: number; size: number; status?: number }) =>
request.post('/factor/v1/fetch/loan_payment', data)
/** 获取日报数据 */
export const fetchOverviewReports = (data: { page: number, size: number }) => request.post("/factor/v1/reports/overview", data);
export const fetchOverviewReports = (data: { page: number; size: number }) => request.post('/factor/v1/reports/overview', data)
/** 获取日报数据 */
export const fetchDailyReports = (data: { page: number, size: number }) => request.post("/factor/v1/reports/days", data);
export const fetchDailyReports = (data: { page: number; size: number }) => request.post('/factor/v1/reports/days', data)
/** 获取周报数据 */
export const fetchWeeklyReports = (data: { page: number, size: number }) => request.post("/factor/v1/reports/weeks", data);
export const fetchWeeklyReports = (data: { page: number; size: number }) => request.post('/factor/v1/reports/weeks', data)
/** 获取月报数据 */
export const fetchMonthlyReports = (data: { page: number, size: number }) => request.post("/factor/v1/reports/months", data);
export const fetchMonthlyReports = (data: { page: number; size: number }) => request.post('/factor/v1/reports/months', data)
/** 获取批次详情 */
export const getLoanBatchDetail = (data: { batch_id: number }) => request.post(`/factor/v1/get/batch`,data);
export const getLoanBatchDetail = (data: { batch_id: number }) => request.post(`/factor/v1/get/batch`, data)

View File

@@ -1,43 +1,44 @@
import { request } from "@/api/request";
import { request } from '@/api/request'
export interface MenuItem {
id?: number;
identity?: string;
code?: string;
description?: string;
menu_icon?: string;
menu_path?: string;
parent_id?: number | null;
title?: string;
title_en?: string;
type?: number;
sort_key?: number;
order?: number; // 排序字段(用于本地排序)
app_id?: number;
created_at?: string;
children?: MenuItem[];
is_web_page?: boolean; // 是否为网页
web_url?: string; // 嵌入的网页URL
component?: string; // 路由文件地址
is_new_tab?: boolean; // 是否在新窗口打开
is_full?: boolean; // 单独的页面,不包含菜单
hide_menu?: boolean; // 隐藏菜单栏的菜单
id?: number
identity?: string
code?: string
description?: string
menu_icon?: string
menu_path?: string
parent_id?: number | null
title?: string
title_en?: string
type?: number
sort_key?: number
order?: number // 排序字段(用于本地排序)
app_id?: number
created_at?: string
children?: MenuItem[]
is_web_page?: boolean // 是否为网页
web_url?: string // 嵌入的网页URL
component?: string // 路由文件地址
is_new_tab?: boolean // 是否在新窗口打开
is_full?: boolean // 单独的页面,不包含菜单
hide_menu?: boolean // 隐藏菜单栏的菜单
}
/** 获取菜单 */
export const fetchMenu = (data: { page: number, size: number }) => request.post("/rbac2/v1/pmn/fetch", data, { needWorkspace: true });
export const fetchMenu = (data: { page: number; size: number }) => request.post('/rbac2/v1/pmn/fetch', data, { needWorkspace: true })
/** 创建菜单 */
export const createMenu = (data: MenuItem) => request.post("/rbac2/v1/pmn/create", data, { needWorkspace: true });
export const createMenu = (data: MenuItem) => request.post('/rbac2/v1/pmn/create', data, { needWorkspace: true })
/** 修改菜单 */
export const modifyMenu = (data: MenuItem) => request.post("/rbac2/v1/pmn/modify", data, { needWorkspace: true });
export const modifyMenu = (data: MenuItem) => request.post('/rbac2/v1/pmn/modify', data, { needWorkspace: true })
/** 删除菜单 */
export const deleteMenu = (data: { id: MenuItem['id'] }) => request.post("/rbac2/v1/pmn/del", data, { needWorkspace: true });
export const deleteMenu = (data: { id: MenuItem['id'] }) => request.post('/rbac2/v1/pmn/del', data, { needWorkspace: true })
/** 更新菜单排序 */
export const updateMenuOrder = (data: { pmn_id: number, sort_key: number }[]) =>
request.post("/rbac2/v1/pmn/sort", data);
export const updateMenuOrder = (data: { pmn_id: number; sort_key: number }[]) => request.post('/rbac2/v1/pmn/sort', data)
/** 用户-给指定用户设置权限 */
export const userPmn = (data: { code: string; list: { id: number, pmn_id: number }[] }) => request.post("/rbac2/v1/pmn/user_pmn", data, { needWorkspace: true });
export const userPmn = (data: { code: string; list: { id: number; pmn_id: number }[] }) =>
request.post('/rbac2/v1/pmn/user_pmn', data, { needWorkspace: true })
/** 用户-给指定用户设置权限 */
export const userSetPmn = (data: { list: { id: number, pmn_id: number }[] }) => request.post("/rbac2/v1/pmn/user/set_pmn", data, { needWorkspace: true });
export const userSetPmn = (data: { list: { id: number; pmn_id: number }[] }) =>
request.post('/rbac2/v1/pmn/user/set_pmn', data, { needWorkspace: true })

Some files were not shown because too many files have changed in this diff Show More