Commit 3a573b66 authored by 胡伟's avatar 胡伟

初始

parents

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

> 1%
last 2 versions
not dead
not ie 11
export { default } from '@vben/commitlint-config';
node_modules
.git
.gitignore
*.md
dist
.turbo
dist.zip
root = true
[*]
charset=utf-8
end_of_line=lf
insert_final_newline=true
indent_style=space
indent_size=2
max_line_length = 100
trim_trailing_whitespace = true
quote_type = single
[*.{yml,yaml,json}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
# https://docs.github.com/cn/get-started/getting-started-with-git/configuring-git-to-handle-line-endings
# Automatically normalize line endings (to LF) for all text-based files.
* text=auto eol=lf
# Declare files that will always have CRLF line endings on checkout.
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf
# Denote all files that are truly binary and should not be modified.
*.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary
\ No newline at end of file
[core]
ignorecase = false
node_modules
.DS_Store
dist
dist-ssr
dist.zip
dist.tar
dist.war
.nitro
.output
*-dist.zip
*-dist.tar
*-dist.war
coverage
*.local
**/.vitepress/cache
.cache
.turbo
.temp
dev-dist
.stylelintcache
yarn.lock
package-lock.json
.VSCodeCounter
**/backend-mock/data
# local env files
.env.local
.env.*.local
.eslintcache
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
vite.config.mts.*
vite.config.mjs.*
vite.config.js.*
vite.config.ts.*
# Editor directories and files
.idea
# .vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.history
.cursor
ports:
- port: 5555
onOpen: open-preview
tasks:
- init: npm i -g corepack && pnpm install
command: pnpm run dev:play
registry = "https://registry.npmmirror.com"
public-hoist-pattern[]=lefthook
public-hoist-pattern[]=eslint
public-hoist-pattern[]=prettier
public-hoist-pattern[]=prettier-plugin-tailwindcss
public-hoist-pattern[]=stylelint
public-hoist-pattern[]=*postcss*
public-hoist-pattern[]=@commitlint/*
public-hoist-pattern[]=czg
strict-peer-dependencies=false
auto-install-peers=true
dedupe-peer-dependents=true
dist
dev-dist
.local
.output.js
node_modules
.nvmrc
coverage
CODEOWNERS
.nitro
.output
**/*.svg
**/*.sh
public
.npmrc
*-lock.yaml
export { default } from '@vben/prettier-config';
dist
public
__tests__
coverage
I will implement the "Update Secret Key" feature in both the System and Yunying user management views.
### Plan
1. **Create Component: `apps/web-antd/src/views/yunying/user/components/updateSecret.vue`**
- Create a Modal component that displays the current secret key (read-only) and an "Update" button.
- Implement the `handleUpdate` function to call `update2faApi` with the user's UUID.
- Update the displayed secret key upon successful API response.
2. **Create Component: `apps/web-antd/src/views/system/user/components/updateSecret.vue`**
- Duplicate the component created in step 1 for the system module (following the existing project structure).
3. **Update View: `apps/web-antd/src/views/yunying/user/index.vue`**
- Import the new `UpdateSecret` component.
- Add the `UpdateSecret` component to the template.
- Add a "Update Secret" (更新密钥) button to the table's action column.
- Implement the `handleOpenUpdateSecret` function to open the modal.
4. **Update View: `apps/web-antd/src/views/system/user/index.vue`**
- Apply the same changes as in step 3 to the system user view.
### Implementation Details
- **API Call**: `update2faApi({ uuid: form.uuid })` will be used to update the secret key.
- **UI**: The modal will show the secret key in a read-only input field. Clicking "Update" will refresh this value.
- **Data**: The initial secret key will be populated from the user record if available.
PORT=5320
ACCESS_TOKEN_SECRET=access_token_secret
REFRESH_TOKEN_SECRET=refresh_token_secret
# @vben/backend-mock
## Description
Vben Admin 数据 mock 服务,没有对接任何的数据库,所有数据都是模拟的,用于前端开发时提供数据支持。线上环境不再提供 mock 集成,可自行部署服务或者对接真实数据,由于 `mock.js` 等工具有一些限制,比如上传文件不行、无法模拟复杂的逻辑等,所以这里使用了真实的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。该服务不需要手动启动,已经集成在 vite 插件内,随应用一起启用。
## Running the app
```bash
# development
$ pnpm run start
# production mode
$ pnpm run build
```
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_CODES } from '~/utils/mock-data';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
export default eventHandler((event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const codes =
MOCK_CODES.find((item) => item.username === userinfo.username)?.codes ?? [];
return useResponseSuccess(codes);
});
import { defineEventHandler, readBody, setResponseStatus } from 'h3';
import {
clearRefreshTokenCookie,
setRefreshTokenCookie,
} from '~/utils/cookie-utils';
import { generateAccessToken, generateRefreshToken } from '~/utils/jwt-utils';
import { MOCK_USERS } from '~/utils/mock-data';
import {
forbiddenResponse,
useResponseError,
useResponseSuccess,
} from '~/utils/response';
export default defineEventHandler(async (event) => {
const { password, username } = await readBody(event);
if (!password || !username) {
setResponseStatus(event, 400);
return useResponseError(
'BadRequestException',
'Username and password are required',
);
}
const findUser = MOCK_USERS.find(
(item) => item.username === username && item.password === password,
);
if (!findUser) {
clearRefreshTokenCookie(event);
return forbiddenResponse(event, 'Username or password is incorrect.');
}
const accessToken = generateAccessToken(findUser);
const refreshToken = generateRefreshToken(findUser);
setRefreshTokenCookie(event, refreshToken);
return useResponseSuccess({
...findUser,
accessToken,
});
});
import { defineEventHandler } from 'h3';
import {
clearRefreshTokenCookie,
getRefreshTokenFromCookie,
} from '~/utils/cookie-utils';
import { useResponseSuccess } from '~/utils/response';
export default defineEventHandler(async (event) => {
const refreshToken = getRefreshTokenFromCookie(event);
if (!refreshToken) {
return useResponseSuccess('');
}
clearRefreshTokenCookie(event);
return useResponseSuccess('');
});
import { defineEventHandler } from 'h3';
import {
clearRefreshTokenCookie,
getRefreshTokenFromCookie,
setRefreshTokenCookie,
} from '~/utils/cookie-utils';
import { generateAccessToken, verifyRefreshToken } from '~/utils/jwt-utils';
import { MOCK_USERS } from '~/utils/mock-data';
import { forbiddenResponse } from '~/utils/response';
export default defineEventHandler(async (event) => {
const refreshToken = getRefreshTokenFromCookie(event);
if (!refreshToken) {
return forbiddenResponse(event);
}
clearRefreshTokenCookie(event);
const userinfo = verifyRefreshToken(refreshToken);
if (!userinfo) {
return forbiddenResponse(event);
}
const findUser = MOCK_USERS.find(
(item) => item.username === userinfo.username,
);
if (!findUser) {
return forbiddenResponse(event);
}
const accessToken = generateAccessToken(findUser);
setRefreshTokenCookie(event, refreshToken);
return accessToken;
});
import { eventHandler, setHeader } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse } from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const data = `
{
"code": 0,
"message": "success",
"data": [
{
"id": 123456789012345678901234567890123456789012345678901234567890,
"name": "John Doe",
"age": 30,
"email": "john-doe@demo.com"
},
{
"id": 987654321098765432109876543210987654321098765432109876543210,
"name": "Jane Smith",
"age": 25,
"email": "jane@demo.com"
}
]
}
`;
setHeader(event, 'Content-Type', 'application/json');
return data;
});
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_MENUS } from '~/utils/mock-data';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const menus =
MOCK_MENUS.find((item) => item.username === userinfo.username)?.menus ?? [];
return useResponseSuccess(menus);
});
import { eventHandler, getQuery, setResponseStatus } from 'h3';
import { useResponseError } from '~/utils/response';
export default eventHandler((event) => {
const { status } = getQuery(event);
setResponseStatus(event, Number(status));
return useResponseError(`${status}`);
});
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import {
sleep,
unAuthorizedResponse,
useResponseSuccess,
} from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
await sleep(600);
return useResponseSuccess(null);
});
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import {
sleep,
unAuthorizedResponse,
useResponseSuccess,
} from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
await sleep(1000);
return useResponseSuccess(null);
});
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import {
sleep,
unAuthorizedResponse,
useResponseSuccess,
} from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
await sleep(2000);
return useResponseSuccess(null);
});
import { faker } from '@faker-js/faker';
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
const formatterCN = new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
function generateMockDataList(count: number) {
const dataList = [];
for (let i = 0; i < count; i++) {
const dataItem: Record<string, any> = {
id: faker.string.uuid(),
pid: 0,
name: faker.commerce.department(),
status: faker.helpers.arrayElement([0, 1]),
createTime: formatterCN.format(
faker.date.between({ from: '2021-01-01', to: '2022-12-31' }),
),
remark: faker.lorem.sentence(),
};
if (faker.datatype.boolean()) {
dataItem.children = Array.from(
{ length: faker.number.int({ min: 1, max: 5 }) },
() => ({
id: faker.string.uuid(),
pid: dataItem.id,
name: faker.commerce.department(),
status: faker.helpers.arrayElement([0, 1]),
createTime: formatterCN.format(
faker.date.between({ from: '2023-01-01', to: '2023-12-31' }),
),
remark: faker.lorem.sentence(),
}),
);
}
dataList.push(dataItem);
}
return dataList;
}
const mockData = generateMockDataList(10);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const listData = structuredClone(mockData);
return useResponseSuccess(listData);
});
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
return useResponseSuccess(MOCK_MENU_LIST);
});
import { eventHandler, getQuery } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
const namesMap: Record<string, any> = {};
function getNames(menus: any[]) {
menus.forEach((menu) => {
namesMap[menu.name] = String(menu.id);
if (menu.children) {
getNames(menu.children);
}
});
}
getNames(MOCK_MENU_LIST);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const { id, name } = getQuery(event);
return (name as string) in namesMap &&
(!id || namesMap[name as string] !== String(id))
? useResponseSuccess(true)
: useResponseSuccess(false);
});
import { eventHandler, getQuery } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
const pathMap: Record<string, any> = { '/': 0 };
function getPaths(menus: any[]) {
menus.forEach((menu) => {
pathMap[menu.path] = String(menu.id);
if (menu.children) {
getPaths(menu.children);
}
});
}
getPaths(MOCK_MENU_LIST);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const { id, path } = getQuery(event);
return (path as string) in pathMap &&
(!id || pathMap[path as string] !== String(id))
? useResponseSuccess(true)
: useResponseSuccess(false);
});
import { faker } from '@faker-js/faker';
import { eventHandler, getQuery } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { getMenuIds, MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response';
const formatterCN = new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
const menuIds = getMenuIds(MOCK_MENU_LIST);
function generateMockDataList(count: number) {
const dataList = [];
for (let i = 0; i < count; i++) {
const dataItem: Record<string, any> = {
id: faker.string.uuid(),
name: faker.commerce.product(),
status: faker.helpers.arrayElement([0, 1]),
createTime: formatterCN.format(
faker.date.between({ from: '2022-01-01', to: '2025-01-01' }),
),
permissions: faker.helpers.arrayElements(menuIds),
remark: faker.lorem.sentence(),
};
dataList.push(dataItem);
}
return dataList;
}
const mockData = generateMockDataList(100);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const {
page = 1,
pageSize = 20,
name,
id,
remark,
startTime,
endTime,
status,
} = getQuery(event);
let listData = structuredClone(mockData);
if (name) {
listData = listData.filter((item) =>
item.name.toLowerCase().includes(String(name).toLowerCase()),
);
}
if (id) {
listData = listData.filter((item) =>
item.id.toLowerCase().includes(String(id).toLowerCase()),
);
}
if (remark) {
listData = listData.filter((item) =>
item.remark?.toLowerCase()?.includes(String(remark).toLowerCase()),
);
}
if (startTime) {
listData = listData.filter((item) => item.createTime >= startTime);
}
if (endTime) {
listData = listData.filter((item) => item.createTime <= endTime);
}
if (['0', '1'].includes(status as string)) {
listData = listData.filter((item) => item.status === Number(status));
}
return usePageResponseSuccess(page as string, pageSize as string, listData);
});
import { faker } from '@faker-js/faker';
import { eventHandler, getQuery } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import {
sleep,
unAuthorizedResponse,
usePageResponseSuccess,
} from '~/utils/response';
function generateMockDataList(count: number) {
const dataList = [];
for (let i = 0; i < count; i++) {
const dataItem = {
id: faker.string.uuid(),
imageUrl: faker.image.avatar(),
imageUrl2: faker.image.avatar(),
open: faker.datatype.boolean(),
status: faker.helpers.arrayElement(['success', 'error', 'warning']),
productName: faker.commerce.productName(),
price: faker.commerce.price(),
currency: faker.finance.currencyCode(),
quantity: faker.number.int({ min: 1, max: 100 }),
available: faker.datatype.boolean(),
category: faker.commerce.department(),
releaseDate: faker.date.past(),
rating: faker.number.float({ min: 1, max: 5 }),
description: faker.commerce.productDescription(),
weight: faker.number.float({ min: 0.1, max: 10 }),
color: faker.color.human(),
inProduction: faker.datatype.boolean(),
tags: Array.from({ length: 3 }, () => faker.commerce.productAdjective()),
};
dataList.push(dataItem);
}
return dataList;
}
const mockData = generateMockDataList(100);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
await sleep(600);
const { page, pageSize, sortBy, sortOrder } = getQuery(event);
// 规范化分页参数,处理 string[]
const pageRaw = Array.isArray(page) ? page[0] : page;
const pageSizeRaw = Array.isArray(pageSize) ? pageSize[0] : pageSize;
const pageNumber = Math.max(
1,
Number.parseInt(String(pageRaw ?? '1'), 10) || 1,
);
const pageSizeNumber = Math.min(
100,
Math.max(1, Number.parseInt(String(pageSizeRaw ?? '10'), 10) || 10),
);
const listData = structuredClone(mockData);
// 规范化 query 入参,兼容 string[]
const sortKeyRaw = Array.isArray(sortBy) ? sortBy[0] : sortBy;
const sortOrderRaw = Array.isArray(sortOrder) ? sortOrder[0] : sortOrder;
// 检查 sortBy 是否是 listData 元素的合法属性键
if (
typeof sortKeyRaw === 'string' &&
listData[0] &&
Object.prototype.hasOwnProperty.call(listData[0], sortKeyRaw)
) {
// 定义数组元素的类型
type ItemType = (typeof listData)[0];
const sortKey = sortKeyRaw as keyof ItemType; // 将 sortBy 断言为合法键
const isDesc = sortOrderRaw === 'desc';
listData.sort((a, b) => {
const aValue = a[sortKey] as unknown;
const bValue = b[sortKey] as unknown;
let result = 0;
if (typeof aValue === 'number' && typeof bValue === 'number') {
result = aValue - bValue;
} else if (aValue instanceof Date && bValue instanceof Date) {
result = aValue.getTime() - bValue.getTime();
} else if (typeof aValue === 'boolean' && typeof bValue === 'boolean') {
if (aValue === bValue) {
result = 0;
} else {
result = aValue ? 1 : -1;
}
} else {
const aStr = String(aValue);
const bStr = String(bValue);
const aNum = Number(aStr);
const bNum = Number(bStr);
result =
Number.isFinite(aNum) && Number.isFinite(bNum)
? aNum - bNum
: aStr.localeCompare(bStr, undefined, {
numeric: true,
sensitivity: 'base',
});
}
return isDesc ? -result : result;
});
}
return usePageResponseSuccess(
String(pageNumber),
String(pageSizeNumber),
listData,
);
});
import { defineEventHandler } from 'h3';
export default defineEventHandler(() => 'Test get handler');
import { defineEventHandler } from 'h3';
export default defineEventHandler(() => 'Test post handler');
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
export default eventHandler((event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
return useResponseSuccess({
url: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
});
// return useResponseError("test")
});
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
export default eventHandler((event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
return useResponseSuccess(userinfo);
});
import type { NitroErrorHandler } from 'nitropack';
const errorHandler: NitroErrorHandler = function (error, event) {
event.node.res.end(`[Error Handler] ${error.stack}`);
};
export default errorHandler;
import { defineEventHandler } from 'h3';
import { forbiddenResponse, sleep } from '~/utils/response';
export default defineEventHandler(async (event) => {
event.node.res.setHeader(
'Access-Control-Allow-Origin',
event.headers.get('Origin') ?? '*',
);
if (event.method === 'OPTIONS') {
event.node.res.statusCode = 204;
event.node.res.statusMessage = 'No Content.';
return 'OK';
} else if (
['DELETE', 'PATCH', 'POST', 'PUT'].includes(event.method) &&
event.path.startsWith('/api/system/')
) {
await sleep(Math.floor(Math.random() * 2000));
return forbiddenResponse(event, '演示环境,禁止修改');
}
});
import errorHandler from './error';
process.env.COMPATIBILITY_DATE = new Date().toISOString();
export default defineNitroConfig({
devErrorHandler: errorHandler,
errorHandler: '~/error',
routeRules: {
'/api/**': {
cors: true,
headers: {
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Headers':
'Accept, Authorization, Content-Length, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With',
'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
'Access-Control-Allow-Origin': '*',
'Access-Control-Expose-Headers': '*',
},
},
},
});
{
"name": "@vben/backend-mock",
"version": "0.0.1",
"description": "",
"private": true,
"license": "MIT",
"author": "",
"scripts": {
"build": "nitro build",
"start": "nitro dev"
},
"dependencies": {
"@faker-js/faker": "catalog:",
"jsonwebtoken": "catalog:",
"nitropack": "catalog:"
},
"devDependencies": {
"@types/jsonwebtoken": "catalog:",
"h3": "catalog:"
}
}
import { defineEventHandler } from 'h3';
export default defineEventHandler(() => {
return `
<h1>Hello Vben Admin</h1>
<h2>Mock service is starting</h2>
<ul>
<li><a href="/api/user">/api/user/info</a></li>
<li><a href="/api/menu">/api/menu/all</a></li>
<li><a href="/api/auth/codes">/api/auth/codes</a></li>
<li><a href="/api/auth/login">/api/auth/login</a></li>
<li><a href="/api/upload">/api/upload</a></li>
</ul>
`;
});
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}
{
"extends": "./.nitro/types/tsconfig.json"
}
import type { EventHandlerRequest, H3Event } from 'h3';
import { deleteCookie, getCookie, setCookie } from 'h3';
export function clearRefreshTokenCookie(event: H3Event<EventHandlerRequest>) {
deleteCookie(event, 'jwt', {
httpOnly: true,
sameSite: 'none',
secure: true,
});
}
export function setRefreshTokenCookie(
event: H3Event<EventHandlerRequest>,
refreshToken: string,
) {
setCookie(event, 'jwt', refreshToken, {
httpOnly: true,
maxAge: 24 * 60 * 60, // unit: seconds
sameSite: 'none',
secure: true,
});
}
export function getRefreshTokenFromCookie(event: H3Event<EventHandlerRequest>) {
const refreshToken = getCookie(event, 'jwt');
return refreshToken;
}
import type { EventHandlerRequest, H3Event } from 'h3';
import type { UserInfo } from './mock-data';
import { getHeader } from 'h3';
import jwt from 'jsonwebtoken';
import { MOCK_USERS } from './mock-data';
// TODO: Replace with your own secret key
const ACCESS_TOKEN_SECRET = 'access_token_secret';
const REFRESH_TOKEN_SECRET = 'refresh_token_secret';
export interface UserPayload extends UserInfo {
iat: number;
exp: number;
}
export function generateAccessToken(user: UserInfo) {
return jwt.sign(user, ACCESS_TOKEN_SECRET, { expiresIn: '7d' });
}
export function generateRefreshToken(user: UserInfo) {
return jwt.sign(user, REFRESH_TOKEN_SECRET, {
expiresIn: '30d',
});
}
export function verifyAccessToken(
event: H3Event<EventHandlerRequest>,
): null | Omit<UserInfo, 'password'> {
const authHeader = getHeader(event, 'Authorization');
if (!authHeader?.startsWith('Bearer')) {
return null;
}
const tokenParts = authHeader.split(' ');
if (tokenParts.length !== 2) {
return null;
}
const token = tokenParts[1] as string;
try {
const decoded = jwt.verify(
token,
ACCESS_TOKEN_SECRET,
) as unknown as UserPayload;
const username = decoded.username;
const user = MOCK_USERS.find((item) => item.username === username);
if (!user) {
return null;
}
const { password: _pwd, ...userinfo } = user;
return userinfo;
} catch {
return null;
}
}
export function verifyRefreshToken(
token: string,
): null | Omit<UserInfo, 'password'> {
try {
const decoded = jwt.verify(token, REFRESH_TOKEN_SECRET) as UserPayload;
const username = decoded.username;
const user = MOCK_USERS.find(
(item) => item.username === username,
) as UserInfo;
if (!user) {
return null;
}
const { password: _pwd, ...userinfo } = user;
return userinfo;
} catch {
return null;
}
}
export interface UserInfo {
id: number;
password: string;
realName: string;
roles: string[];
username: string;
homePath?: string;
}
export const MOCK_USERS: UserInfo[] = [
{
id: 0,
password: '123456',
realName: 'Vben',
roles: ['super'],
username: 'vben',
},
{
id: 1,
password: '123456',
realName: 'Admin',
roles: ['admin'],
username: 'admin',
homePath: '/workspace',
},
{
id: 2,
password: '123456',
realName: 'Jack',
roles: ['user'],
username: 'jack',
homePath: '/analytics',
},
];
export const MOCK_CODES = [
// super
{
codes: ['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010'],
username: 'vben',
},
{
// admin
codes: ['AC_100010', 'AC_100020', 'AC_100030'],
username: 'admin',
},
{
// user
codes: ['AC_1000001', 'AC_1000002'],
username: 'jack',
},
];
const dashboardMenus = [
{
meta: {
order: -1,
title: 'page.dashboard.title',
},
name: 'Dashboard',
path: '/dashboard',
redirect: '/analytics',
children: [
{
name: 'Analytics',
path: '/analytics',
component: '/dashboard/analytics/index',
meta: {
affixTab: true,
title: 'page.dashboard.analytics',
},
},
{
name: 'Workspace',
path: '/workspace',
component: '/dashboard/workspace/index',
meta: {
title: 'page.dashboard.workspace',
},
},
],
},
];
const systemMenus = [
{
name: 'System',
path: '/system',
meta: {
icon: 'ion:settings-outline',
title: '系统管理',
},
redirect: '/system/user',
children: [
{
name: 'UserManagement',
path: 'user',
component: '/system/user/index',
meta: {
title: '用户管理',
},
},
],
},
];
const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
const roleWithMenus = {
admin: {
component: '/demos/access/admin-visible',
meta: {
icon: 'mdi:button-cursor',
title: 'demos.access.adminVisible',
},
name: 'AccessAdminVisibleDemo',
path: '/demos/access/admin-visible',
},
super: {
component: '/demos/access/super-visible',
meta: {
icon: 'mdi:button-cursor',
title: 'demos.access.superVisible',
},
name: 'AccessSuperVisibleDemo',
path: '/demos/access/super-visible',
},
user: {
component: '/demos/access/user-visible',
meta: {
icon: 'mdi:button-cursor',
title: 'demos.access.userVisible',
},
name: 'AccessUserVisibleDemo',
path: '/demos/access/user-visible',
},
};
return [
{
meta: {
icon: 'ic:baseline-view-in-ar',
keepAlive: true,
order: 1000,
title: 'demos.title',
},
name: 'Demos',
path: '/demos',
redirect: '/demos/access',
children: [
{
name: 'AccessDemos',
path: '/demosaccess',
meta: {
icon: 'mdi:cloud-key-outline',
title: 'demos.access.backendPermissions',
},
redirect: '/demos/access/page-control',
children: [
{
name: 'AccessPageControlDemo',
path: '/demos/access/page-control',
component: '/demos/access/index',
meta: {
icon: 'mdi:page-previous-outline',
title: 'demos.access.pageAccess',
},
},
{
name: 'AccessButtonControlDemo',
path: '/demos/access/button-control',
component: '/demos/access/button-control',
meta: {
icon: 'mdi:button-cursor',
title: 'demos.access.buttonControl',
},
},
{
name: 'AccessMenuVisible403Demo',
path: '/demos/access/menu-visible-403',
component: '/demos/access/menu-visible-403',
meta: {
authority: ['no-body'],
icon: 'mdi:button-cursor',
menuVisibleWithForbidden: true,
title: 'demos.access.menuVisible403',
},
},
roleWithMenus[role],
],
},
],
},
];
};
export const MOCK_MENUS = [
{
menus: [...dashboardMenus, ...systemMenus, ...createDemosMenus('super')],
username: 'vben',
},
{
menus: [...dashboardMenus, ...systemMenus, ...createDemosMenus('admin')],
username: 'admin',
},
{
menus: [...dashboardMenus, ...systemMenus, ...createDemosMenus('user')],
username: 'jack',
},
];
export const MOCK_MENU_LIST = [
{
id: 1,
name: 'Workspace',
status: 1,
type: 'menu',
icon: 'mdi:dashboard',
path: '/workspace',
component: '/dashboard/workspace/index',
meta: {
icon: 'carbon:workspace',
title: 'page.dashboard.workspace',
affixTab: true,
order: 0,
},
},
{
id: 2,
meta: {
icon: 'carbon:settings',
order: 9997,
title: 'system.title',
badge: 'new',
badgeType: 'normal',
badgeVariants: 'primary',
},
status: 1,
type: 'catalog',
name: 'System',
path: '/system',
children: [
{
id: 201,
pid: 2,
path: '/system/menu',
name: 'SystemMenu',
authCode: 'System:Menu:List',
status: 1,
type: 'menu',
meta: {
icon: 'carbon:menu',
title: 'system.menu.title',
},
component: '/system/menu/list',
children: [
{
id: 20_101,
pid: 201,
name: 'SystemMenuCreate',
status: 1,
type: 'button',
authCode: 'System:Menu:Create',
meta: { title: 'common.create' },
},
{
id: 20_102,
pid: 201,
name: 'SystemMenuEdit',
status: 1,
type: 'button',
authCode: 'System:Menu:Edit',
meta: { title: 'common.edit' },
},
{
id: 20_103,
pid: 201,
name: 'SystemMenuDelete',
status: 1,
type: 'button',
authCode: 'System:Menu:Delete',
meta: { title: 'common.delete' },
},
],
},
{
id: 202,
pid: 2,
path: '/system/dept',
name: 'SystemDept',
status: 1,
type: 'menu',
authCode: 'System:Dept:List',
meta: {
icon: 'carbon:container-services',
title: 'system.dept.title',
},
component: '/system/dept/list',
children: [
{
id: 20_401,
pid: 201,
name: 'SystemDeptCreate',
status: 1,
type: 'button',
authCode: 'System:Dept:Create',
meta: { title: 'common.create' },
},
{
id: 20_402,
pid: 201,
name: 'SystemDeptEdit',
status: 1,
type: 'button',
authCode: 'System:Dept:Edit',
meta: { title: 'common.edit' },
},
{
id: 20_403,
pid: 201,
name: 'SystemDeptDelete',
status: 1,
type: 'button',
authCode: 'System:Dept:Delete',
meta: { title: 'common.delete' },
},
],
},
],
},
{
id: 9,
meta: {
badgeType: 'dot',
order: 9998,
title: 'demos.vben.title',
icon: 'carbon:data-center',
},
name: 'Project',
path: '/vben-admin',
type: 'catalog',
status: 1,
children: [
{
id: 901,
pid: 9,
name: 'VbenDocument',
path: '/vben-admin/document',
component: 'IFrameView',
type: 'embedded',
status: 1,
meta: {
icon: 'carbon:book',
iframeSrc: 'https://doc.vben.pro',
title: 'demos.vben.document',
},
},
{
id: 902,
pid: 9,
name: 'VbenGithub',
path: '/vben-admin/github',
component: 'IFrameView',
type: 'link',
status: 1,
meta: {
icon: 'carbon:logo-github',
link: 'https://github.com/vbenjs/vue-vben-admin',
title: 'Github',
},
},
{
id: 903,
pid: 9,
name: 'VbenAntdv',
path: '/vben-admin/antdv',
component: 'IFrameView',
type: 'link',
status: 0,
meta: {
icon: 'carbon:hexagon-vertical-solid',
badgeType: 'dot',
link: 'https://ant.vben.pro',
title: 'demos.vben.antdv',
},
},
],
},
{
id: 10,
component: '_core/about/index',
type: 'menu',
status: 1,
meta: {
icon: 'lucide:copyright',
order: 9999,
title: 'demos.vben.about',
},
name: 'About',
path: '/about',
},
];
export function getMenuIds(menus: any[]) {
const ids: number[] = [];
menus.forEach((item) => {
ids.push(item.id);
if (item.children && item.children.length > 0) {
ids.push(...getMenuIds(item.children));
}
});
return ids;
}
import type { EventHandlerRequest, H3Event } from 'h3';
import { setResponseStatus } from 'h3';
export function useResponseSuccess<T = any>(data: T) {
return {
code: 0,
data,
error: null,
message: 'ok',
};
}
export function usePageResponseSuccess<T = any>(
page: number | string,
pageSize: number | string,
list: T[],
{ message = 'ok' } = {},
) {
const pageData = pagination(
Number.parseInt(`${page}`),
Number.parseInt(`${pageSize}`),
list,
);
return {
...useResponseSuccess({
items: pageData,
total: list.length,
}),
message,
};
}
export function useResponseError(message: string, error: any = null) {
return {
code: -1,
data: null,
error,
message,
};
}
export function forbiddenResponse(
event: H3Event<EventHandlerRequest>,
message = 'Forbidden Exception',
) {
setResponseStatus(event, 403);
return useResponseError(message, message);
}
export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) {
setResponseStatus(event, 401);
return useResponseError('Unauthorized Exception', 'Unauthorized Exception');
}
export function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export function pagination<T = any>(
pageNo: number,
pageSize: number,
array: T[],
): T[] {
const offset = (pageNo - 1) * Number(pageSize);
return offset + Number(pageSize) >= array.length
? array.slice(offset)
: array.slice(offset, offset + Number(pageSize));
}
# 应用标题
VITE_APP_TITLE=BOM-Robot
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
VITE_APP_NAMESPACE=BOM-Robot
# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key
# public path
VITE_BASE=/
# Basic interface address SPA
VITE_GLOB_API_URL=/api
VITE_VISUALIZER=true
# 端口号
VITE_PORT=5666
VITE_BASE=/
# 接口地址
VITE_GLOB_API_URL=/api
# 是否开启 Nitro Mock服务,true 为开启,false 为关闭
VITE_NITRO_MOCK=true
# 是否打开 devtools,true 为打开,false 为关闭
VITE_DEVTOOLS=false
# 是否注入全局loading
VITE_INJECT_APP_LOADING=true
#本地测试
VITE_GLOB_API_URL=http://192.168.4.2:9989/bomrobot
# VITE_GLOB_API_URL=http://129.226.198.36:9989/bomrobot
\ No newline at end of file
VITE_BASE=/
# 接口地址
VITE_GLOB_API_URL=http://129.226.198.36:9989/bomrobot
# 是否开启压缩,可以设置为 none, brotli, gzip
VITE_COMPRESS=none
# 是否开启 PWA
VITE_PWA=false
# vue-router 的模式
VITE_ROUTER_HISTORY=hash
# 是否注入全局loading
VITE_INJECT_APP_LOADING=true
# 打包后是否生成dist.zip
VITE_ARCHIVER=true
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="renderer" content="webkit" />
<meta name="description" content="A Modern Back-end Management System" />
<meta name="keywords" content="Vben Admin Vue3 Vite" />
<meta name="author" content="Vben" />
<meta
name="viewport"
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
/>
<!-- 由 vite 注入 VITE_APP_TITLE 变量,在 .env 文件内配置 -->
<title><%= VITE_APP_TITLE %></title>
<link rel="icon" href="/favicon.ico" />
<script>
// 生产环境下注入百度统计
if (window._VBEN_ADMIN_PRO_APP_CONF_) {
var _hmt = _hmt || [];
(function () {
var hm = document.createElement('script');
hm.src =
'https://hm.baidu.com/hm.js?b38e689f40558f20a9a686d7f6f33edf';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(hm, s);
})();
}
</script>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
{
"name": "@vben/web-antd",
"version": "5.5.9",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
"type": "git",
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
"directory": "apps/web-antd"
},
"license": "MIT",
"author": {
"name": "vben",
"email": "ann.vben@gmail.com",
"url": "https://github.com/anncwb"
},
"type": "module",
"scripts": {
"build": "pnpm vite build --mode production",
"build:analyze": "pnpm vite build --mode analyze",
"dev": "pnpm vite --mode development",
"prod": "pnpm vite --mode production",
"preview": "vite preview",
"typecheck": "vue-tsc --noEmit --skipLibCheck"
},
"imports": {
"#/*": "./src/*"
},
"dependencies": {
"@ant-design/icons-vue": "^7.0.1",
"@vben/access": "workspace:*",
"@vben/common-ui": "workspace:*",
"@vben/constants": "workspace:*",
"@vben/hooks": "workspace:*",
"@vben/icons": "workspace:*",
"@vben/layouts": "workspace:*",
"@vben/locales": "workspace:*",
"@vben/plugins": "workspace:*",
"@vben/preferences": "workspace:*",
"@vben/request": "workspace:*",
"@vben/stores": "workspace:*",
"@vben/styles": "workspace:*",
"@vben/types": "workspace:*",
"@vben/utils": "workspace:*",
"@vueuse/core": "catalog:",
"ant-design-vue": "catalog:",
"axios": "catalog:",
"crypto-js": "^4.2.0",
"date-fns": "^4.1.0",
"dayjs": "catalog:",
"js-base64": "^3.7.8",
"md5": "^2.3.0",
"pinia": "catalog:",
"qrcode": "catalog:",
"vue": "catalog:",
"vue-router": "catalog:"
},
"devDependencies": {
"@types/qrcode": "catalog:"
}
}
\ No newline at end of file
export { default } from '@vben/tailwind-config/postcss';
/**
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
*/
import type { Component } from 'vue';
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { defineAsyncComponent, defineComponent, h, ref } from 'vue';
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { notification } from 'ant-design-vue';
const AutoComplete = defineAsyncComponent(
() => import('ant-design-vue/es/auto-complete'),
);
const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
const Checkbox = defineAsyncComponent(
() => import('ant-design-vue/es/checkbox'),
);
const CheckboxGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
);
const DatePicker = defineAsyncComponent(
() => import('ant-design-vue/es/date-picker'),
);
const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
const InputNumber = defineAsyncComponent(
() => import('ant-design-vue/es/input-number'),
);
const InputPassword = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.InputPassword),
);
const Mentions = defineAsyncComponent(
() => import('ant-design-vue/es/mentions'),
);
const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
const RadioGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
);
const RangePicker = defineAsyncComponent(() =>
import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
);
const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
const Textarea = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.Textarea),
);
const TimePicker = defineAsyncComponent(
() => import('ant-design-vue/es/time-picker'),
);
const TreeSelect = defineAsyncComponent(
() => import('ant-design-vue/es/tree-select'),
);
const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
componentProps: Recordable<any> = {},
) => {
return defineComponent({
name: component.name,
inheritAttrs: false,
setup: (props: any, { attrs, expose, slots }) => {
const placeholder =
props?.placeholder ||
attrs?.placeholder ||
$t(`ui.placeholder.${type}`);
// 透传组件暴露的方法
const innerRef = ref();
expose(
new Proxy(
{},
{
get: (_target, key) => innerRef.value?.[key],
has: (_target, key) => key in (innerRef.value || {}),
},
),
);
return () =>
h(
component,
{ ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },
slots,
);
},
});
};
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'ApiSelect'
| 'ApiTreeSelect'
| 'AutoComplete'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'DefaultButton'
| 'Divider'
| 'IconPicker'
| 'Input'
| 'InputNumber'
| 'InputPassword'
| 'Mentions'
| 'PrimaryButton'
| 'Radio'
| 'RadioGroup'
| 'RangePicker'
| 'Rate'
| 'Select'
| 'Space'
| 'Switch'
| 'Textarea'
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| BaseFormComponentType;
async function initComponentAdapter() {
const components: Partial<Record<ComponentType, Component>> = {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiSelect: withDefaultPlaceholder(
{
...ApiComponent,
name: 'ApiSelect',
},
'select',
{
component: Select,
loadingSlot: 'suffixIcon',
visibleEvent: 'onDropdownVisibleChange',
modelPropName: 'value',
},
),
ApiTreeSelect: withDefaultPlaceholder(
{
...ApiComponent,
name: 'ApiTreeSelect',
},
'select',
{
component: TreeSelect,
fieldNames: { label: 'label', value: 'value', children: 'children' },
loadingSlot: 'suffixIcon',
modelPropName: 'value',
optionsPropName: 'treeData',
visibleEvent: 'onVisibleChange',
},
),
AutoComplete,
Checkbox,
CheckboxGroup,
DatePicker,
// 自定义默认按钮
DefaultButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
IconPicker: withDefaultPlaceholder(IconPicker, 'select', {
iconSlot: 'addonAfter',
inputComponent: Input,
modelValueProp: 'value',
}),
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
// 自定义主要按钮
PrimaryButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'primary' }, slots);
},
Radio,
RadioGroup,
RangePicker,
Rate,
Select: withDefaultPlaceholder(Select, 'select'),
Space,
Switch,
Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload,
};
// 将组件注册到全局共享状态中
globalShareState.setComponents(components);
// 定义全局共享状态中的消息提示
globalShareState.defineMessage({
// 复制成功消息提示
copyPreferencesSuccess: (title, content) => {
notification.success({
description: content,
message: title,
placement: 'bottomRight',
});
},
});
}
export { initComponentAdapter };
import type {
VbenFormSchema as FormSchema,
VbenFormProps,
} from '@vben/common-ui';
import type { ComponentType } from './component';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
async function initSetupVbenForm() {
setupVbenForm<ComponentType>({
config: {
// ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value',
// 一些组件是 v-model:checked 或者 v-model:fileList
modelPropNameMap: {
Checkbox: 'checked',
Radio: 'checked',
Switch: 'checked',
Upload: 'fileList',
},
},
defineRules: {
// 输入项目必填国际化适配
required: (value, _params, ctx) => {
if (value === undefined || value === null || value.length === 0) {
return $t('ui.formRules.required', [ctx.label]);
}
return true;
},
// 选择项目必填国际化适配
selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) {
return $t('ui.formRules.selectRequired', [ctx.label]);
}
return true;
},
},
});
}
const useVbenForm = useForm<ComponentType>;
export { initSetupVbenForm, useVbenForm, z };
export type VbenFormSchema = FormSchema<ComponentType>;
export type { VbenFormProps };
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import { h } from 'vue';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
import { Button, Image } from 'ant-design-vue';
import { useVbenForm } from './form';
setupVbenVxeTable({
configVxeTable: (vxeUI) => {
vxeUI.setConfig({
grid: {
align: 'center',
border: false,
columnConfig: {
},
minHeight: 180,
formConfig: {
// 全局禁用vxe-table的表单配置,使用formOptions
enabled: false,
},
proxyConfig: {
autoLoad: true,
response: {
result: 'items',
total: 'total',
list: 'items',
},
showActiveMsg: true,
showResponseMsg: false,
},
round: true,
showOverflow: true,
size: 'small',
} as VxeTableGridOptions,
});
// 表格配置项可以用 cellRender: { name: 'CellImage' },
vxeUI.renderer.add('CellImage', {
renderTableDefault(_renderOpts, params) {
const { column, row } = params;
return h(Image, { src: row[column.field] });
},
});
// 表格配置项可以用 cellRender: { name: 'CellLink' },
vxeUI.renderer.add('CellLink', {
renderTableDefault(renderOpts) {
const { props } = renderOpts;
return h(
Button,
{ size: 'small', type: 'link' },
{ default: () => props?.text },
);
},
});
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
// vxeUI.formats.add
},
useVbenForm,
});
export { useVbenVxeGrid };
export type * from '@vben/plugins/vxe-table';
import { baseRequestClient, requestClient } from '#/api/request';
export namespace AuthApi {
/** 登录接口参数 */
export interface LoginParams {
password?: string;
username?: string;
}
/** 登录接口返回值 */
export interface LoginResult {
accessToken: string;
}
export interface RefreshTokenResult {
data: string;
status: number;
}
}
//loginByUm
export async function loginByUm(data: AuthApi.LoginParams) {
const res = await requestClient.post<AuthApi.LoginResult>('/share/UmUser/login/username', data);
return {
accessToken: res.data,
}
}
/**
* 登录
*/
export async function loginApi(data: AuthApi.LoginParams) {
const res = await requestClient.post<AuthApi.LoginResult>('/share/user/login/username', data);
return {
accessToken: res.data,
}
}
/**
* 刷新accessToken
*/
export async function refreshTokenApi() {
return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {
withCredentials: true,
});
}
/**
* 退出登录
*/
export async function logoutApi() {
return baseRequestClient.post('/auth/logout', {
withCredentials: true,
});
}
/**
* 获取用户权限码
*/
export async function getAccessCodesApi() {
// return requestClient.get<string[]>('/auth/codes');
return [];
}
/**
* 返回对象的值:
* secretKey : 密钥
* qrCodeUrl : 二维码图片url
* @param params
* @returns
*/
export async function setup2faApi(params: { username: string }) {
return requestClient.get('/share/api/auth/2fa/setup?username=' + params.username, params);
}
/**
* 验证2fa,返回对象的值:
* data:为token值
* @param params
* @returns
*/
export async function verify2faApi(params) {
const res = await requestClient.post('/share/api/auth/2fa/verify', params);
return {
accessToken: res.data,
}
}
//2fa/update-secret/{uuid}
export async function update2faApi(params) {
return requestClient.post('/share/api/auth/2fa/update-secret/' + params.uuid, params);
}
\ No newline at end of file
import { requestClient } from '#/api/request';
const uploadFile = (formData) => {
return requestClient.post('/auth/FileDetail/upload' ,formData,{
headers: {
'Content-Type': 'multipart/form-data'
}
});
};
//上传图片
const uploadImage = (formData) => {
return requestClient.post('/auth/FileDetail/upload-image' ,formData,{
headers: {
'Content-Type': 'multipart/form-data'
}
});
};
//上传视频
const uploadVideo = (formData) => {
return requestClient.post('/auth/FileDetail/upload-video' ,formData,{
headers: {
'Content-Type': 'multipart/form-data'
}
});
};
//上传Base64图片
const uploadBase64Image = (formData) => {
return requestClient.post('/auth/FileDetail/upload-base64-image' ,formData,{
headers: {
'Content-Type': 'multipart/form-data'
}
});
};
//上传文件后的返回值
// {
// "url": "文件访问地址",
// "md5": "文件MD5值"
// }
// 文件上传相关URI常量
export const base = {
// 通用文件上传
UPLOAD: '/auth/FileDetail/upload',
// 图片上传(带缩略图)
UPLOAD_IMAGE: '/auth/FileDetail/upload-image',
// 视频上传
UPLOAD_VIDEO: '/auth/FileDetail/upload-video',
// Base64图片上传
UPLOAD_BASE64_IMAGE: '/auth/FileDetail/upload-base64-image',
uploadFile,
uploadImage,
uploadVideo,
uploadBase64Image
};
\ No newline at end of file
import { requestClient } from '#/api/request';
async function queryPage(param) {
return requestClient.post('/auth/ChatConfig/queryPage', param);
}
async function queryList(param) {
return requestClient.post('/auth/ChatConfig/queryList', param);
}
async function create(param) {
return requestClient.post('/auth/ChatConfig/addOne', param);
}
async function removeByUid(param) {
return requestClient.post('/auth/ChatConfig/removeByUid/' + param.uuid, param);
}
async function updateByUid(param) {
return requestClient.post('/auth/ChatConfig/updateByUid' , param);
}
async function getById(param) {
return requestClient.post('/auth/ChatConfig/getById', param);
}
async function getByUuid(param) {
return requestClient.post('/auth/ChatConfig/getByUuid', param);
}
export const chatConfig = {
queryPage,
queryList,
create,
removeByUid,
updateByUid,
getById,
getByUuid,
}
\ No newline at end of file
import { requestClient } from '#/api/request';
async function queryPage(param) {
return requestClient.post('/auth/ChatInfo/queryPage', param);
}
async function queryList(param) {
return requestClient.post('/auth/ChatInfo/queryList', param);
}
export const chatInfo = {
queryPage,
queryList,
}
import { requestClient } from '#/api/request';
async function queryPage(param) {
return requestClient.post('/auth/ConfigRelation/queryPage', param);
}
async function queryList(param) {
return requestClient.post('/auth/ConfigRelation/queryList', param);
}
async function create(param) {
return requestClient.post('/auth/ConfigRelation/addOne', param);
}
async function removeByUid(param) {
return requestClient.post('/auth/ConfigRelation/removeByUid/' + param.uuid, param);
}
async function updateByUid(param) {
return requestClient.post('/auth/ConfigRelation/updateByUid' , param);
}
async function getById(param) {
return requestClient.post('/auth/ConfigRelation/getById', param);
}
async function getByUuid(param) {
return requestClient.post('/auth/ConfigRelation/getByUuid', param);
}
export const configRelation = {
queryPage,
queryList,
create,
removeByUid,
updateByUid,
getById,
getByUuid,
}
\ No newline at end of file
import axios from 'axios';
import { useAppConfig } from '@vben/hooks';
const { apiURL: baseURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
import { useAccessStore } from '@vben/stores';
import { message } from 'ant-design-vue';
/**
* 将Blob数据导出为文件
* @param data Blob数据
* @param fileName 文件名
*/
export const exportFileByData = (data: Blob, fileName: string = 'export.xlsx') => {
// 创建临时下载链接
const downloadUrl = window.URL.createObjectURL(data);
// 创建隐藏的a标签触发下载
const link = document.createElement('a');
link.style.display = 'none';
link.href = downloadUrl;
link.download = fileName;
document.body.appendChild(link);
link.click();
// 清理资源
window.URL.revokeObjectURL(downloadUrl);
document.body.removeChild(link);
};
/**
* 导出文件
* @param url 接口URL
* @param params 请求参数
* @param fileName 文件名
* @returns Promise
*/
export const exportFile = async (
url: string,
params: any,
fileName: string = 'export.xlsx'
): Promise<void> => {
debugger;
try {
const accessStore = useAccessStore();
const token = accessStore.accessToken;
const fullUrl = url.startsWith('http') ? url : `${baseURL}${url}`;
const response = await axios({
method: 'post',
url: fullUrl,
data: params,
responseType: 'blob',
headers: {
'Content-Type': 'application/json',
Authorization: token
}
});
exportFileByData(response.data, fileName);
} catch (error: any) {
console.error('导出失败:', error);
// 处理JSON格式的错误信息
if (error.response?.data?.type?.includes('json')) {
try {
const reader = new FileReader();
reader.onload = () => {
try {
const errData = JSON.parse(reader.result as string);
const errMsg = errData.message || '导出失败,请稍后重试';
console.error('导出错误信息:', errMsg);
message.error(`导出失败: ${errMsg}`);
} catch (parseError) {
message.error('导出失败: 解析错误信息失败');
}
};
reader.readAsText(error.response.data);
} catch (readerError) {
message.error('导出失败: 无法读取错误信息');
}
} else {
message.error('文件下载失败,请检查网络连接或联系管理员');
}
}
};
import { requestClient, fullUrl } from '#/api/request';
/**
* 获取资金池总资金
*/
async function getTotalFund() {
return requestClient.post('/auth/FundPoolService/getTotalFund');
}
/**
* 获取资金池详情
*/
async function getFundPoolDetail() {
return requestClient.post('/auth/FundPoolService/getFundPoolDetail');
}
/**
* 申请资金
* @param param 申请资金参数
*/
async function applyForBalance(param) {
return requestClient.post('/auth/FundPoolService/applyForBalance', param);
}
/**
* 重置资金池缓存
*/
async function resetCache() {
return requestClient.post('/auth/FundPoolService/resetCache');
}
/**
* 清空资金池缓存
*/
async function clearFundPoolCache() {
return requestClient.post('/auth/FundPoolService/clearFundPoolCache');
}
export const fundPoolService = {
getTotalFund,
getFundPoolDetail,
applyForBalance,
resetCache,
clearFundPoolCache
}
\ No newline at end of file
import { requestClient } from '#/api/request';
async function queryPage(param) {
return requestClient.post('/auth/GiftConfig/queryPage', param);
}
async function queryList(param) {
return requestClient.post('/auth/GiftConfig/queryList', param);
}
async function create(param) {
return requestClient.post('/auth/GiftConfig/addOne', param);
}
async function removeByUid(param) {
return requestClient.post('/auth/GiftConfig/removeByUid/' + param.uuid, param);
}
async function updateByUid(param) {
return requestClient.post('/auth/GiftConfig/updateByUid' , param);
}
async function getById(param) {
return requestClient.post('/auth/GiftConfig/getById', param);
}
async function getByUuid(param) {
return requestClient.post('/auth/GiftConfig/getByUuid', param);
}
export const giftConfig = {
queryPage,
queryList,
create,
removeByUid,
updateByUid,
getById,
getByUuid,
}
\ No newline at end of file
import { requestClient, fullUrl } from '#/api/request';
/**
* 新增数据
* @param param 新增参数
*/
async function addOne(param) {
return requestClient.post('/auth/GiftRelation/addOne', param);
}
/**
* 批量新增数据
* @param param 新增参数
*/
async function addBatch(param) {
return requestClient.post('/auth/GiftRelation/addBatch', param);
}
/**
* 更新数据
* @param param 更新参数
*/
async function updateByUid(param) {
return requestClient.post('/auth/GiftRelation/updateByUid/' + param.uuid, param);
}
/**
* 删除数据
* @param param 删除参数
*/
async function removeByUid(param) {
return requestClient.post('/auth/GiftRelation/removeByUid/' + param.uuid, param);
}
/**
* 批量删除数据
* @param param 批量删除参数
*/
async function removeBatchByUid(param) {
return requestClient.post('/auth/GiftRelation/removeBatchByUid', param);
}
/**
* 查询指定数据
* @param param 查询参数
*/
async function getOneByUid(param) {
return requestClient.post('/auth/GiftRelation/getOneByUid/' + param.uuid, param);
}
/**
* 分页查询数据
* @param param 分页查询参数
*/
async function queryPage(param) {
return requestClient.post('/auth/GiftRelation/queryPage', param);
}
/**
* 查询所有数据
* @param param 查询参数
*/
async function queryList(param) {
return requestClient.post('/auth/GiftRelation/queryList', param);
}
/**
* 查询总数
* @param param 查询参数
*/
async function count(param) {
return requestClient.post('/auth/GiftRelation/count', param);
}
export const giftRelation = {
addOne,
addBatch,
updateByUid,
removeByUid,
removeBatchByUid,
getOneByUid,
queryPage,
queryList,
count
}
\ No newline at end of file
import { requestClient, fullUrl } from '#/api/request';
/**
* 获取礼物列表
* @param param 查询参数,包含roomId(可选)
*/
async function getGiftList(param) {
return requestClient.post('/auth/GiftService/getGiftList', param);
}
/**
* 根据ID获取礼物信息
* @param param 查询参数,包含giftId
*/
async function getGiftDetail(param) {
return requestClient.post('/auth/GiftService/getGiftDetail', param);
}
/**
* 赠送礼物
* @param param 赠送礼物参数
*/
async function sendGift(param) {
return requestClient.post('/auth/GiftService/sendGift', param);
}
/**
* 重置礼物缓存
*/
async function resetCache() {
return requestClient.post('/auth/GiftService/resetCache');
}
/**
* 清空礼物缓存
*/
async function clearGiftCache() {
return requestClient.post('/auth/GiftService/clearGiftCache');
}
export const giftService = {
getGiftList,
getGiftDetail,
sendGift,
resetCache,
clearGiftCache
}
\ No newline at end of file
import { requestClient } from '#/api/request';
async function getRobotStatistics() {
return requestClient.post('/auth/HomePage/getRobotStatistics');
}
async function getConsumptionStatistics(param) {
return requestClient.post('/auth/HomePage/getConsumptionStatistics', param);
}
async function getLiveRoomStatistics(param) {
return requestClient.post('/auth/HomePage/getLiveRoomStatistics', param);
}
async function getChatActivityStatistics(param) {
return requestClient.post('/auth/HomePage/getChatActivityStatistics', param);
}
export const homePage = {
getRobotStatistics,
getConsumptionStatistics,
getLiveRoomStatistics,
getChatActivityStatistics,
}
export * from './auth';
export * from './menu';
export * from './user';
export * from './role';
export * from './base';
export * from './export';
export * from './robotAccount';
export * from './robotFee';
export * from './robotGroup';
export * from './robotRunningInfo';
export * from './configRelation';
export * from './giftConfig';
export * from './chatConfig';
export * from './chatInfo';
export * from './robotConsumptionRecord';
export * from './robotRunRecord';
export * from './rRunningConfig';
export * from './userRobotRelation';
export * from './giftService';
export * from './fundPoolService';
export * from './liveService';
export * from './liveRoomRelation';
export * from './giftRelation';
export * from './rRunningConfigGlobal';
export * from './messageTemplate';
export * from './takeover';
export * from './homePage';
import { requestClient, fullUrl } from '#/api/request';
/**
* 新增数据
* @param param 新增参数
*/
async function addOne(param) {
return requestClient.post('/auth/LiveRoomRelation/addOne', param);
}
/**
* 批量新增数据
* @param param 新增参数
*/
async function addBatch(param) {
return requestClient.post('/auth/LiveRoomRelation/addBatch', param);
}
/**
* 根据uid更新数据
* @param param 更新参数,包含uuid字段
*/
async function updateByUid(param) {
return requestClient.post('/auth/LiveRoomRelation/updateByUid/' + param.uuid, param);
}
/**
* 根据uid删除数据
* @param param 删除参数,包含uuid字段
*/
async function removeByUid(param) {
return requestClient.post('/auth/LiveRoomRelation/removeByUid/' + param.uuid, param);
}
/**
* 批量删除数据
* @param param 批量删除参数,包含uids字段
*/
async function removeBatchByUid(param) {
return requestClient.post('/auth/LiveRoomRelation/removeBatchByUid', param);
}
/**
* 根据uid查询指定数据
* @param param 查询参数,包含uuid字段
*/
async function getOneByUid(param) {
return requestClient.post('/auth/LiveRoomRelation/getOneByUid/' + param.uuid, param);
}
/**
* 分页查询数据
* @param param 分页查询参数
*/
async function queryPage(param) {
return requestClient.post('/auth/LiveRoomRelation/queryPage', param);
}
/**
* 查询所有数据
* @param param 查询参数
*/
async function queryList(param) {
return requestClient.post('/auth/LiveRoomRelation/queryList', param);
}
/**
* 查询总数
* @param param 查询参数
*/
async function count(param) {
return requestClient.post('/auth/LiveRoomRelation/count', param);
}
/**
* 导出全部数据URL
*/
function exportAllUrl(param) {
return fullUrl('/auth/LiveRoomRelation/export', param);
}
/**
* 导出部分数据URL
*/
function exportPartUrl(param) {
return fullUrl('/auth/LiveRoomRelation/export', param);
}
/**
* 导出全部数据
*/
async function exportAll(param) {
return requestClient.post('/auth/LiveRoomRelation/export', param);
}
/**
* 导出部分数据
*/
async function exportPart(param) {
return requestClient.post('/auth/LiveRoomRelation/exportPart', param);
}
export const liveRoomRelation = {
addOne,
addBatch,
updateByUid,
removeByUid,
removeBatchByUid,
getOneByUid,
queryPage,
queryList,
count,
exportAllUrl,
exportPartUrl,
exportAll,
exportPart
}
\ No newline at end of file
import { requestClient, fullUrl } from '#/api/request';
/**
* 获取直播间列表
*/
async function getLiveRoomList() {
return requestClient.post('/auth/LiveService/getLiveRoomList');
}
/**
* 根据ID获取直播间信息
* @param param 查询参数,包含roomId
*/
async function getLiveRoomById(param) {
return requestClient.post('/auth/LiveService/getLiveRoomById', param);
}
/**
* 根据名称获取直播间信息
* @param param 查询参数,包含roomName
*/
async function getLiveRoomByName(param) {
return requestClient.post('/auth/LiveService/getLiveRoomByName', param);
}
/**
* 重置直播间缓存
*/
async function resetCache() {
return requestClient.post('/auth/LiveService/resetCache');
}
/**
* 清除指定直播间缓存
* @param param 查询参数,包含roomId
*/
async function clearLiveRoomCache(param) {
return requestClient.post('/auth/LiveService/clearLiveRoomCache', param);
}
export const liveService = {
getLiveRoomList,
getLiveRoomById,
getLiveRoomByName,
resetCache,
clearLiveRoomCache
}
\ No newline at end of file
import { requestClient } from '#/api/request';
async function queryPageNextChildren(param) {
return requestClient.post('/auth/menu/queryPageNextChildren', param);
}
async function queryVisible(param) {
return requestClient.post('/auth/menu/queryVisible', param);
}
async function queryByRole(param) {
return requestClient.post('/auth/menu/queryByRole/' + param.roleUid, param);
}
async function addOne(param) {
return requestClient.post('/auth/menu/addOne', param);
}
async function removeByUid(param) {
return requestClient.post('/auth/menu/removeByUid/' + param.uid, param);
}
async function updateByUid(param) {
return requestClient.post('/auth/menu/updateByUid/' + param.uid, param);
}
export const menu = {
queryPageNextChildren,
queryVisible,
queryByRole,
addOne,
removeByUid,
updateByUid,
}
\ No newline at end of file
import { requestClient } from '#/api/request';
async function queryPage(param) {
return requestClient.post('/auth/MessageTemplate/queryPage', param);
}
async function queryList(param) {
return requestClient.post('/auth/MessageTemplate/queryList', param);
}
async function create(param) {
return requestClient.post('/auth/MessageTemplate/addOne', param);
}
async function removeByUid(param) {
return requestClient.post('/auth/MessageTemplate/removeByUid/' + param.uuid, param);
}
async function updateByUid(param) {
return requestClient.post('/auth/MessageTemplate/updateByUid/' + param.uuid, param);
}
async function getById(param) {
return requestClient.post('/auth/MessageTemplate/getById', param);
}
async function getByUuid(param) {
return requestClient.post('/auth/MessageTemplate/getByUuid', param);
}
export const messageTemplate = {
queryPage,
queryList,
create,
removeByUid,
updateByUid,
getById,
getByUuid,
}
\ No newline at end of file
import { requestClient } from '#/api/request';
async function queryPage(param) {
return requestClient.post('/auth/RunningConfig/queryPage', param);
}
async function queryList(param) {
return requestClient.post('/auth/RunningConfig/queryList', param);
}
async function create(param) {
return requestClient.post('/auth/RunningConfig/addOne', param);
}
async function removeByUid(param) {
return requestClient.post('/auth/RunningConfig/removeByUid/'+param.uuid, param);
}
async function updateByUid(param) {
return requestClient.post('/auth/RunningConfig/updateByUid', param);
}
async function getById(param) {
return requestClient.post('/auth/RunningConfig/getById', param);
}
async function getByUuid(param) {
return requestClient.post('/auth/RunningConfig/getByUuid', param);
}
export const rRunningConfig = {
queryPage,
queryList,
create,
removeByUid,
updateByUid,
getById,
getByUuid,
}
\ No newline at end of file
import { requestClient } from '#/api/request';
async function updateBy(param) {
return requestClient.post('/auth/RRunningConfigGlobal/updateBy', param);
}
async function getOneBy(param) {
return requestClient.get('/auth/RRunningConfigGlobal/getOneBy', param);
}
export const rRunningConfigGlobal = {
updateBy,
getOneBy,
}
\ No newline at end of file
import { requestClient ,fullUrl } from '#/api/request';
async function queryPage(param) {
return requestClient.post('/auth/RobotAccount/queryPage', param);
}
async function queryList(param) {
return requestClient.post('/auth/RobotAccount/queryList', param);
}
async function create(param) {
return requestClient.post('/auth/RobotAccount/addOne', param);
}
async function removeByUid(param) {
return requestClient.post('/auth/RobotAccount/removeByUid/' + param.uuid, param);
}
async function updateByUid(param) {
return requestClient.post('/auth/RobotAccount/updateByUid/' + param.uuid, param);
}
/**
* 更新多个账号状态
*/
async function updateByUids(param) {
return requestClient.post('/auth/RobotAccount/updateByUids', param);
}
async function getById(param) {
return requestClient.post('/auth/RobotAccount/getById', param);
}
async function getByUuid(param) {
return requestClient.post('/auth/RobotAccount/getByUuid', param);
}
async function isValidAll(param) {
return requestClient.post('/auth/RobotAccount/isValidAll', param);
}
async function isValid(param) {
return requestClient.post('/auth/RobotAccount/isValid/' + param.uuid, param);
}
async function isValidNot(param) {
return requestClient.post('/auth/RobotAccount/isValidNot', param);
}
function importUrl(param){
return fullUrl('/auth/RobotAccount/import', param);
}
function importBatchUpdateUrl(param){
return fullUrl('/auth/RobotAccount/importBatchUpdate', param);
}
function dwonloadTemplateUrl(param){
return fullUrl('/auth/RobotAccount/dwonloadTemplate', param);
}
function exportAllUrl(param){
return fullUrl('/auth/RobotAccount/export', param);
}
function exportPartUrl(param){
return fullUrl('/auth/RobotAccount/export', param);
}
async function importExcel(param){
return requestClient.post('/auth/RobotAccount/import', param);
}
/**
* 批量更新账号状态
*/
async function importBatchUpdate(param){
return requestClient.post('/auth/RobotAccount/importBatchUpdate', param);
}
async function dwonloadTemplate(param){
return requestClient.post('/auth/RobotAccount/dwonloadTemplate', param);
}
async function exportAll(param){
return requestClient.post('/auth/RobotAccount/export', param);
}
async function exportPart(param){
return requestClient.post('/auth/RobotAccount/exportPart', param);
}
export const robotAccount = {
queryPage,
queryList,
create,
removeByUid,
updateByUid,
updateByUids,
getById,
getByUuid,
importUrl,
importBatchUpdateUrl,
dwonloadTemplateUrl,
exportAllUrl,
exportPartUrl,
importExcel,
importBatchUpdate,
dwonloadTemplate,
exportAll,
exportPart,
isValidAll,
isValid,
isValidNot
}
\ No newline at end of file
import { requestClient ,fullUrl } from '#/api/request';
/**
* 分页查询消费记录
*/
export function queryPage(params) {
return requestClient.post(
'/auth/RobotConsumptionRecord/queryPage',
params
);
}
/**
* 查询消费记录列表
*/
export function queryList(params) {
return requestClient.post(
'/auth/RobotConsumptionRecord/queryList',
params
);
}
/**
* 创建消费记录
*/
export function create(params) {
return requestClient.post(
'/auth/RobotConsumptionRecord/addOne',
params
);
}
/**
* 根据UUID删除消费记录
*/
export function removeByUid(uuid: string) {
return requestClient.post<boolean>(
'/auth/RobotConsumptionRecord/removeByUid',
{ uuid }
);
}
/**
* 根据UUID更新消费记录
*/
export function updateByUid(params) {
return requestClient.post(
'/auth/RobotConsumptionRecord/updateByUid',
params
);
}
/**
* 根据ID查询消费记录
*/
export function getById(id: number) {
return requestClient.post(
'/auth/RobotConsumptionRecord/getById',
{ id }
);
}
/**
* 根据UUID查询消费记录
*/
export function getByUuid(uuid: string) {
return requestClient.post(
'/auth/RobotConsumptionRecord/getByUuid',
{ uuid }
);
}
/**
* 导出全部数据URL
*/
function exportAllUrl(param) {
return fullUrl('/auth/RobotConsumptionRecord/export', param);
}
/**
* 导出部分数据URL
*/
function exportPartUrl(param) {
return fullUrl('/auth/RobotConsumptionRecord/export', param);
}
/**
* 下载模板URL
*/
function downloadTemplateUrl(param) {
return fullUrl('/auth/RobotConsumptionRecord/downloadTemplate', param);
}
/**
* 导出全部数据
*/
async function exportAll(param) {
return requestClient.post('/auth/RobotConsumptionRecord/export', param);
}
/**
* 导出部分数据
*/
async function exportPart(param) {
return requestClient.post('/auth/RobotConsumptionRecord/exportPart', param);
}
/**
* 下载模板
*/
async function downloadTemplate(param) {
return requestClient.post('/auth/RobotConsumptionRecord/downloadTemplate', param);
}
export const robotConsumptionRecord = {
queryPage,
queryList,
create,
removeByUid,
updateByUid,
getById,
getByUuid,
exportAllUrl,
exportPartUrl,
downloadTemplateUrl,
exportAll,
exportPart,
downloadTemplate
};
export default robotConsumptionRecord;
\ No newline at end of file
import { requestClient } from '#/api/request';
async function queryPage(param) {
return requestClient.post('/auth/RobotFee/queryPage', param);
}
async function queryList(param) {
return requestClient.post('/auth/RobotFee/queryList', param);
}
async function create(param) {
return requestClient.post('/auth/RobotFee/addOne', param);
}
async function removeByUid(param) {
return requestClient.post('/auth/RobotFee/removeByUid/' + param.uuid, param);
}
async function updateByUid(param) {
return requestClient.post('/auth/RobotFee/updateByUid/' + param.uuid, param);
}
async function getById(param) {
return requestClient.post('/auth/RobotFee/getById', param);
}
async function getByUuid(param) {
return requestClient.post('/auth/RobotFee/getByUuid', param);
}
/**
* 资金池统计数据
* @param param
* @returns
* 返回数据,data的值为:
* {
* "totalPool": 0.0,
* "totalAllocatedAmount": 0.0,
* "usedAmount": 0.0,
* "allocatedAmount": 0.0,
* "availableAmount": 0.0
* }
*/
async function getFeeStatistics(param) {
return requestClient.post('/auth/RobotFee/getFeeStatistics', param);
}
export const robotFee = {
queryPage,
queryList,
create,
removeByUid,
updateByUid,
getById,
getByUuid,
getFeeStatistics
}
\ No newline at end of file
import { requestClient } from '#/api/request';
async function queryPage(param) {
return requestClient.post('/auth/RobotGroup/queryPage', param);
}
async function queryList(param) {
return requestClient.post('/auth/RobotGroup/queryList', param);
}
async function create(param) {
return requestClient.post('/auth/RobotGroup/addOne', param);
}
async function removeByUid(param) {
return requestClient.post('/auth/RobotGroup/removeByUid/' + param.uuid, param);
}
async function updateByUid(param) {
return requestClient.post('/auth/RobotGroup/updateByUid/' + param.uuid, param);
}
async function getById(param) {
return requestClient.post('/auth/RobotGroup/getById', param);
}
async function getByUuid(param) {
return requestClient.post('/auth/RobotGroup/getByUuid', param);
}
export const robotGroup = {
queryPage,
queryList,
create,
removeByUid,
updateByUid,
getById,
getByUuid,
}
\ No newline at end of file
import { requestClient ,fullUrl } from '#/api/request';
async function queryPage(param) {
return requestClient.post('/auth/RobotRunRecord/queryPage', param);
}
async function queryList(param) {
return requestClient.post('/auth/RobotRunRecord/queryList', param);
}
async function create(param) {
return requestClient.post('/auth/RobotRunRecord/addOne', param);
}
async function removeByUid(param) {
return requestClient.post('/auth/RobotRunRecord/removeByUid/' + param.uuid, param);
}
async function updateByUid(param) {
return requestClient.post('/auth/RobotRunRecord/updateByUid/' + param.uuid, param);
}
async function getById(param) {
return requestClient.post('/auth/RobotRunRecord/getById', param);
}
async function getByUuid(param) {
return requestClient.post('/auth/RobotRunRecord/getByUuid', param);
}
/**
* 导出全部数据URL
*/
function exportAllUrl(param) {
return fullUrl('/auth/RobotRunRecord/export', param);
}
/**
* 导出部分数据URL
*/
function exportPartUrl(param) {
return fullUrl('/auth/RobotRunRecord/export', param);
}
/**
* 下载模板URL
*/
function downloadTemplateUrl(param) {
return fullUrl('/auth/RobotRunRecord/downloadTemplate', param);
}
/**
* 导出全部数据
*/
async function exportAll(param) {
return requestClient.post('/auth/RobotRunRecord/export', param);
}
/**
* 导出部分数据
*/
async function exportPart(param) {
return requestClient.post('/auth/RobotRunRecord/exportPart', param);
}
/**
* 下载模板
*/
async function downloadTemplate(param) {
return requestClient.post('/auth/RobotRunRecord/downloadTemplate', param);
}
export const robotRunRecord = {
queryPage,
queryList,
create,
removeByUid,
updateByUid,
getById,
getByUuid,
exportAllUrl,
exportPartUrl,
downloadTemplateUrl,
exportAll,
exportPart,
downloadTemplate,
}
\ No newline at end of file
import { requestClient ,fullUrl } from '#/api/request';
async function queryPage(param) {
return requestClient.post('/auth/RobotRunningInfo/queryPage', param);
}
async function queryList(param) {
return requestClient.post('/auth/RobotRunningInfo/queryList', param);
}
async function create(param) {
return requestClient.post('/auth/RobotRunningInfo/addOne', param);
}
async function removeByUid(param) {
return requestClient.post('/auth/RobotRunningInfo/removeByUid/' + param.uuid, param);
}
async function updateByUid(param) {
return requestClient.post('/auth/RobotRunningInfo/updateByUid/' + param.uuid, param);
}
async function getById(param) {
return requestClient.post('/auth/RobotRunningInfo/getById', param);
}
async function getByUuid(param) {
return requestClient.post('/auth/RobotRunningInfo/getByUuid', param);
}
/**
* 导出全部数据URL
*/
function exportAllUrl(param) {
return fullUrl('/auth/RobotRunningInfo/export', param);
}
/**
* 导出部分数据URL
*/
function exportPartUrl(param) {
return fullUrl('/auth/RobotRunningInfo/export', param);
}
/**
* 下载模板URL
*/
function downloadTemplateUrl(param) {
return fullUrl('/auth/RobotRunningInfo/downloadTemplate', param);
}
/**
* 导出全部数据
*/
async function exportAll(param) {
return requestClient.post('/auth/RobotRunningInfo/export', param);
}
/**
* 导出部分数据
*/
async function exportPart(param) {
return requestClient.post('/auth/RobotRunningInfo/exportPart', param);
}
/**
* 下载模板
*/
async function downloadTemplate(param) {
return requestClient.post('/auth/RobotRunningInfo/downloadTemplate', param);
}
export const robotRunningInfo = {
queryPage,
queryList,
create,
removeByUid,
updateByUid,
getById,
getByUuid,
exportAllUrl,
exportPartUrl,
downloadTemplateUrl,
exportAll,
exportPart,
downloadTemplate,
}
\ No newline at end of file
import { requestClient } from '#/api/request';
/**
* 分页查询
* @param param
*/
async function queryPage(param) {
return requestClient.post('/auth/role/queryPage', param);
}
/**
* 查询当前用户可见角色
* @param param
*/
async function queryVisible(param) {
return requestClient.post('/auth/role/queryVisible', param);
}
/**
* 查询用户关联角色
* @param param
*/
async function queryRelationByUserUid(param) {
return requestClient.post('/auth/role/queryRelationByUserUid/' + param.uid, param);
}
/**
* 分配菜单
*/
async function assignMenuPermissionAndRemoveOld(param) {
return requestClient.post('/auth/role/assignMenuPermissionAndRemoveOld', param);
}
/**
* 创建角色
* @param param
*/
async function addOne(param) {
return requestClient.post('/auth/role/addOne', param);
}
/**
* 删除角色
* @param param
*/
async function removeByUid(param) {
return requestClient.post('/auth/role/removeByUid/' + param.uid, param);
}
/**
* 更新数据
* @param param
*/
async function updateByUid(param) {
return requestClient.post('/auth/role/updateByUid/' + param.uid, param);
}
export const role = {
queryPage,
queryVisible,
queryRelationByUserUid,
assignMenuPermissionAndRemoveOld,
addOne,
removeByUid,
updateByUid,
}
\ No newline at end of file
import { requestClient } from '#/api/request';
/**
* 接管相关接口定义 - 根据RobotManualControlAuthController同步
*/
/**
* 接管机器人
*/
export async function takeOverApi(params: { robotUuid: string; durationMinutes?: number }) {
return requestClient.post('/auth/RobotManualControl/takeOver', params);
}
/**
* 释放机器人
*/
export async function releaseApi(params: { robotUuid: string }) {
return requestClient.post('/auth/RobotManualControl/release', params);
}
/**
* 机器人登录
*/
export async function robotLoginApi(params: { robotUuid: string }) {
return requestClient.post('/auth/RobotManualControl/login', params);
}
/**
* 机器人登出
*/
export async function robotLogoutApi(params: { robotUuid: string }) {
return requestClient.post('/auth/RobotManualControl/logout', params);
}
/**
* 获取直播间在线列表
*/
export async function getLiveRoomsApi(params) {
return requestClient.post('/auth/RobotManualControl/liveRooms', params);
}
/**
* 进入直播间
*/
export async function enterLiveRoomApi(params: { robotUuid: string; roomId: number }) {
return requestClient.post('/auth/RobotManualControl/enterRoom', params);
}
/**
* 退出直播间
*/
export async function quitLiveRoomApi(params: { robotUuid: string }) {
return requestClient.post('/auth/RobotManualControl/quitRoom', params);
}
/**
* 点赞直播间
*/
export async function likeApi(params: { robotUuid: string }) {
return requestClient.post('/auth/RobotManualControl/like', params);
}
/**
* 关注主播
*/
export async function followApi(params: { robotUuid: string }) {
return requestClient.post('/auth/RobotManualControl/follow', params);
}
/**
* 获取礼物列表
*/
export async function getGiftListApi(params: { robotUuid: string }) {
return requestClient.post('/auth/RobotManualControl/giftList', params);
}
/**
* 送礼
*/
export async function sendGiftApi(params: { robotUuid: string; giftId: number }) {
return requestClient.post('/auth/RobotManualControl/sendGift', params);
}
/**
* 获取机器人余额
*/
export async function getFeeApi(params: { robotUuid: string }) {
return requestClient.post('/auth/RobotManualControl/getFee', params);
}
/**
* 发送聊天消息
*/
export async function sendMessageApi(params: { robotUuid: string; message: string }) {
return requestClient.post('/auth/RobotManualControl/sendMessage', params);
}
/**
* 获取聊天消息
* @param params
* @param robotUuid 机器人UUID
* @param skip 跳过的消息数量
* @param limit 获取的消息数量
*/
export async function getMessagesApi(params: { robotUuid: string; skip?: number; limit?: number }) {
return requestClient.post('/auth/RobotManualControl/getMessages', params);
}
/**
* 接管接口集合
*/
export const takeoverApi = {
takeOver: takeOverApi,
release: releaseApi,
robotLogin: robotLoginApi,
robotLogout: robotLogoutApi,
getLiveRooms: getLiveRoomsApi,
enterLiveRoom: enterLiveRoomApi,
quitLiveRoom: quitLiveRoomApi,
like: likeApi,
follow: followApi,
getGiftList: getGiftListApi,
sendGift: sendGiftApi,
sendMessage: sendMessageApi,
getFee: getFeeApi,
getMessages: getMessagesApi,
};
\ No newline at end of file
import type { UserInfo } from '@vben/types';
import { requestClient } from '#/api/request';
/**
* 获取用户信息
*/
export async function getUserInfoApi(params: {getRoles?: boolean, getMenus?: boolean}) {
//修改不要使用json,使用formdata格式发送请求:'/auth/user/query/current'
const formData = new FormData();
formData.append('getRoles', params.getRoles?.toString() || 'false');
formData.append('getMenus', params.getMenus?.toString() || 'false');
return requestClient.request<UserInfo>('/auth/user/query/current', {
method: 'POST',
data: formData,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
}
async function queryPage(param) {
return requestClient.post('/auth/user/queryPage', param);
}
async function queryList(param) {
return requestClient.post('/auth/user/queryList', param);
}
async function create(param) {
return requestClient.post('/auth/user/create', param);
}
//createYunying
async function createYunying(param) {
return requestClient.post('/auth/user/createYunying', param);
}
async function assignOrRemoveRole(param){
return requestClient.post('/auth/user/assignOrRemoveRole', param)
}
async function removeByUid(param) {
return requestClient.post('/auth/user/removeByUid/' + param.uid, param);
}
async function updateByUid(param) {
return requestClient.post('/auth/user/update/' + param.uid, param);
}
export const user = {
queryPage,
queryList,
create,
assignOrRemoveRole,
removeByUid,
updateByUid,
createYunying,
}
import { requestClient } from '#/api/request';
async function getUserInfoByRobot(param) {
return requestClient.post('/auth/UserRobotRelation/getUserInfoByRobot', param);
}
async function getRobotList(param) {
return requestClient.post('/auth/UserRobotRelation/getRobotList' , param);
}
async function queryList(param) {
return requestClient.post('/auth/UserRobotRelation/queryList', param);
}
async function clear(param) {
return requestClient.post('/auth/UserRobotRelation/clear', param);
}
async function assignRobot(param) {
return requestClient.post('/auth/UserRobotRelation/assignRobot', param);
}
export const userRobotRelation = {
getUserInfoByRobot,
getRobotList,
queryList,
clear,
assignRobot
}
\ No newline at end of file
export * from './core';
/**
* 该文件可自行根据业务逻辑进行调整
*/
import type { RequestClientOptions } from '@vben/request';
import { useAppConfig } from '@vben/hooks';
import { preferences } from '@vben/preferences';
import {
authenticateResponseInterceptor,
defaultResponseInterceptor,
errorMessageResponseInterceptor,
RequestClient,
} from '@vben/request';
import { useAccessStore } from '@vben/stores';
import { message } from 'ant-design-vue';
import { useAuthStore } from '#/store';
import { refreshTokenApi } from './core';
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
const client = new RequestClient({
...options,
baseURL,
});
/**
* 重新认证逻辑
*/
async function doReAuthenticate() {
console.warn('Access token or refresh token is invalid or expired. ');
const accessStore = useAccessStore();
const authStore = useAuthStore();
accessStore.setAccessToken(null);
if (
preferences.app.loginExpiredMode === 'modal' &&
accessStore.isAccessChecked
) {
accessStore.setLoginExpired(true);
} else {
await authStore.logout();
}
}
/**
* 刷新token逻辑
*/
async function doRefreshToken() {
const accessStore = useAccessStore();
const resp = await refreshTokenApi();
const newToken = resp.data;
accessStore.setAccessToken(newToken);
return newToken;
}
function formatToken(token: null | string) {
// return token ? `Bearer ${token}` : null;
return token || '';
}
// 请求头处理
client.addRequestInterceptor({
fulfilled: async (config) => {
const accessStore = useAccessStore();
config.headers.Authorization = formatToken(accessStore.accessToken);
config.headers['Accept-Language'] = preferences.app.locale;
return config;
},
});
// 处理返回的响应数据格式
client.addResponseInterceptor(
defaultResponseInterceptor({
codeField: 'code',
dataField: (data) => data,
successCode: '00000',
}),
);
// token过期的处理
client.addResponseInterceptor(
authenticateResponseInterceptor({
client,
doReAuthenticate,
doRefreshToken,
enableRefreshToken: preferences.app.enableRefreshToken,
formatToken,
}),
);
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
client.addResponseInterceptor(
errorMessageResponseInterceptor((msg: string, error) => {
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
// 当前mock接口返回的错误字段是 error 或者 message
if(error?.data?.code === 'A0004'){
message.error('登录过期,请重新登录');
// 跳转到登录页
doReAuthenticate();
return;
}
const responseData = error?.response?.data ?? {};
const errorMessage = responseData?.msg ?? responseData?.error ?? responseData?.message ?? '';
// 如果没有错误信息,则会根据状态码进行提示
message.error(errorMessage || msg);
}),
);
return client;
}
export const requestClient = createRequestClient(apiURL, {
responseReturn: 'data',
});
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
/**
* 路径url参数替换
* @param url
* @param params
*/
export function fullUrl(url: string, params: object) {
return apiURL + url.replace(/\{(.*?)\}/g, (match: any, key: string) => params[key.trim()]);
}
/**
* 路径url参数替换
* @param url
* @param params
*/
export function pathUrl(url: string, params: object) {
return url.replace(/\{(.*?)\}/g, (match: any, key: string) => params[key.trim()]);
}
<script lang="ts" setup>
import { computed } from 'vue';
import { useAntdDesignTokens } from '@vben/hooks';
import { preferences, usePreferences } from '@vben/preferences';
import { App, ConfigProvider, theme } from 'ant-design-vue';
import { antdLocale } from '#/locales';
defineOptions({ name: 'App' });
const { isDark } = usePreferences();
const { tokens } = useAntdDesignTokens();
const tokenTheme = computed(() => {
const algorithm = isDark.value
? [theme.darkAlgorithm]
: [theme.defaultAlgorithm];
// antd 紧凑模式算法
if (preferences.app.compact) {
algorithm.push(theme.compactAlgorithm);
}
return {
algorithm,
token: tokens,
};
});
</script>
<template>
<ConfigProvider :locale="antdLocale" :theme="tokenTheme">
<App>
<RouterView />
</App>
</ConfigProvider>
</template>
import { createApp, watchEffect } from 'vue';
import { registerAccessDirective } from '@vben/access';
import { registerLoadingDirective } from '@vben/common-ui/es/loading';
import { preferences } from '@vben/preferences';
import { initStores } from '@vben/stores';
import '@vben/styles';
import '@vben/styles/antd';
import { useTitle } from '@vueuse/core';
import { $t, setupI18n } from '#/locales';
import { initComponentAdapter } from './adapter/component';
import { initSetupVbenForm } from './adapter/form';
import App from './app.vue';
import { router } from './router';
async function bootstrap(namespace: string) {
// 初始化组件适配器
await initComponentAdapter();
// 初始化表单组件
await initSetupVbenForm();
// // 设置弹窗的默认配置
// setDefaultModalProps({
// fullscreenButton: false,
// });
// // 设置抽屉的默认配置
// setDefaultDrawerProps({
// zIndex: 1020,
// });
const app = createApp(App);
// 注册v-loading指令
registerLoadingDirective(app, {
loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令
spinning: 'spinning',
});
// 国际化 i18n 配置
await setupI18n(app);
// 配置 pinia-tore
await initStores(app, { namespace });
// 安装权限指令
registerAccessDirective(app);
// 初始化 tippy
const { initTippy } = await import('@vben/common-ui/es/tippy');
initTippy(app);
// 配置路由及路由守卫
app.use(router);
// 配置Motion插件
const { MotionPlugin } = await import('@vben/plugins/motion');
app.use(MotionPlugin);
// 动态更新标题
watchEffect(() => {
if (preferences.app.dynamicTitle) {
const routeTitle = router.currentRoute.value.meta?.title;
const pageTitle =
(routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
useTitle(pageTitle);
}
});
app.mount('#app');
}
export { bootstrap };
<template>
<div>
<AModal v-model:open="data.isShowModal" title="流程操作" width="30%" @cancel="_close" @ok="handleConfirm">
<AForm ref="formRef" :model="form" :rules="rules" :label-col="{ span: 8 }" labelAlign="left"
:labelWrap="true">
<ARow :gutter="24">
<ACol :span="12">
<AFormItem label="审核结果" name="dataCenterName">
<!-- 单选框,通过,不通过 -->
<ARadioGroup v-model:value="form.status" buttonStyle="solid">
<ARadio :value="3">通过</ARadio>
<ARadio :value="4">不通过</ARadio>
</ARadioGroup>
</AFormItem>
</ACol>
<ACol :span="24">
<AFormItem :label-col="{ span: 4 }" :label="$t('page.inventory_inboundapplication.notes')" name="notes">
<ATextarea v-model:value="form.notes" :rows="3" allowClear
:placeholder="$t('common.placeholderIn')"/>
</AFormItem>
</ACol>
</ARow>
</AForm>
</AModal>
</div>
</template>
<script setup lang="ts">
import {reactive} from "vue";
import {$t} from "@/locales";
let form = reactive({
status: 3,
notes: '',
})
const data = reactive({
isShowModal: false,
resolve: null,
reject: null,
})
function handleConfirm() {
//回调
data.resolve ? data.resolve({...form}) : '';
_close();
}
function _open() {
data.isShowModal = true;
return new Promise((resolve, reject) => {
data.resolve = resolve;
data.reject = reject;
});
}
function _close() {
data.isShowModal = false;
form = reactive({
status: 3,
notes: '',
})
data.reject ? data.reject() : '';
}
defineExpose({_open, _close});
</script>
<style scoped>
</style>
<template>
<div>
<Modal v-model:open="data.isShowModal" title="确认提示" width="30%" @cancel="_close" @ok="handleConfirm">
<div>
<h4>{{ data.title }}</h4>
</div>
<div v-if="data.confirmText" style="margin-top: 10px">
<div>
<span>请输入红色字体内容进行确认:</span>
<span style="font-size: 12px;color: red">{{ data.confirmText }}</span>
</div>
<div style="margin-top: 20px">
<Input v-model:value="data.inputText" allowClear :placeholder="data.confirmText"/>
</div>
</div>
</Modal>
</div>
</template>
<script setup lang="ts">
import {reactive} from "vue";
import {Modal, Input} from 'ant-design-vue';
const data = reactive({
isShowModal: false,
inputText: '',
title: '确认提示',
confirmText: null,
resolve: null,
reject: null,
})
function handleConfirm() {
//值判断
if (data.confirmText && data.confirmText != data.inputText) {
return;
}
//回调
data.resolve ? data.resolve() : '';
_close();
}
function _open(title, confirmText) {
data.title = title || data.title;
data.confirmText = confirmText;
data.isShowModal = true;
return new Promise((resolve, reject) => {
data.resolve = resolve;
data.reject = reject;
});
}
function _close() {
data.inputText = '';
data.isShowModal = false;
data.reject ? data.reject() : '';
}
defineExpose({_open, _close});
</script>
<style scoped>
</style>
<template>
<div>
<Modal v-model:open="data.isShowModal" title="删除确认" width="30%" @cancel="_close" @ok="handleConfirm">
<div>
<h4>{{ data.title }}</h4>
</div>
<div v-if="data.deleteText" style="margin-top: 10px">
<div>
<span>请输入红色字体内容以确认删除数据:</span>
<span style="font-size: 12px;color: red">{{ data.deleteText }}</span>
</div>
<div style="margin-top: 20px">
<Input v-model:value="data.inputText" allowClear :placeholder="data.deleteText"/>
</div>
</div>
</Modal>
</div>
</template>
<script setup lang="ts">
import {reactive} from "vue";
import {Modal, Input} from 'ant-design-vue';
const data = reactive({
isShowModal: false,
inputText: '',
title: '确认删除?',
deleteText: null,
resolve: null,
reject: null,
})
function handleConfirm() {
//值判断
if (data.deleteText && data.deleteText != data.inputText) {
return;
}
//回调
data.resolve ? data.resolve() : '';
_close();
}
function _open(title, deleteText) {
data.title = title || data.title;
data.deleteText = deleteText;
data.isShowModal = true;
return new Promise((resolve, reject) => {
data.resolve = resolve;
data.reject = reject;
});
}
function _close() {
data.inputText = '';
data.isShowModal = false;
data.reject ? data.reject() : '';
}
defineExpose({_open, _close});
</script>
<style scoped>
</style>
<template>
<ASelect
allow-clear
showSearch
:filterOption="filterWrapper"
v-model:value="internalValue"
mode="multiple"
:placeholder="placeholder"
:style="style"
:options="transformedOptions"
@blur="handleBlur"
>
<template #dropdownRender="{ menuNode: menu }">
<div style="padding: 4px 8px; border-bottom: 1px solid #f0f0f0">
<ASpace>
<AButton size="small" type="link" @click.stop="handleSelectAll">全选</AButton>
<AButton size="small" type="link" @click.stop="handleInvertSelect">反选</AButton>
<AButton size="small" type="link" @click.stop="handleClearSelect">清空</AButton>
</ASpace>
</div>
<v-nodes :vnodes="menu"/>
</template>
</ASelect>
</template>
<script setup lang="ts">
import {selectFilterOption} from "@/utils/common";
import {defineComponent, computed, watch, ref} from 'vue';
const VNodes = defineComponent({
props: {
vnodes: {
type: Object,
required: true,
},
},
render() {
return this.vnodes;
},
});
const props = defineProps({
modelValue: {
type: Array,
default: () => []
},
options: {
type: Array,
required: true
},
placeholder: {
type: String,
default: '请选择'
},
style: {
type: Object,
default: () => ({width: '180px'})
},
valueField: {
type: String,
default: 'value'
},
labelField: {
type: String,
default: 'label'
}
});
const emit = defineEmits(['update:modelValue' ,'change']);
// 使用 ref 管理内部值,确保响应式更新
const internalValue = ref(props.modelValue);
// 监听父组件传入的值变化
watch(() => props.modelValue, (newVal) => {
internalValue.value = newVal;
});
// 监听内部值变化并通知父组件
watch(internalValue, (newVal) => {
emit('update:modelValue', newVal);
emit('change' ,newVal)
});
let filters = {}
const filterWrapper = (input: string, option: any)=>{
let b = selectFilterOption(input ,option);
if(b){
filters[option.value] = option;
}else{
//删除
delete filters[option.value];
}
return b;
}
const handleBlur = ()=>{
filters = {};
}
// 转换后的选项 - 添加原始值引用
const transformedOptions = computed(() => {
return props.options && props.options.length
? props.options.map(item => ({
value: item[props.valueField],
label: item[props.labelField],
text: item[props.labelField],
raw: item // 保留原始引用
}))
: [];
});
// 全选
function handleSelectAll() {
if(filters && Object.keys( filters).length > 0){
internalValue.value = Object.keys( filters);
return;
}
internalValue.value = props.options.map(item => item[props.valueField]);
}
// 反选
function handleInvertSelect() {
const currentSelected = internalValue.value || [];
if(filters && Object.keys( filters).length > 0){
internalValue.value = Object.keys( filters).filter(value => !currentSelected.includes(value));
return;
}
const allValues = props.options.map(item => item[props.valueField]);
internalValue.value = allValues.filter(value => !currentSelected.includes(value));
}
// 清空选择
function handleClearSelect() {
internalValue.value = [];
}
</script>
export const REG_USER_NAME = /^[\u4E00-\u9FA5a-zA-Z0-9_-]{4,16}$/;
/** Phone reg */
export const REG_PHONE =
/^[1](([3][0-9])|([4][01456789])|([5][012356789])|([6][2567])|([7][0-8])|([8][0-9])|([9][012356789]))[0-9]{8}$/;
/**
* Password reg
*
* 6-18 characters, including letters, numbers, and underscores
*/
export const REG_PWD = /^\w{5,18}$/;
/** Email reg */
export const REG_EMAIL = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
/** Six digit code reg */
export const REG_CODE_SIX = /^\d{6}$/;
/** Four digit code reg */
export const REG_CODE_FOUR = /^\d{4}$/;
/** Url reg */
export const REG_URL =
/(((^https?:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
/**
* 中文
*/
export const REG_CHINESE = /^[\u4e00-\u9fa5]+$/;
/**
* 数值
*/
export const REG_NUMBER = /^-?\d+$/;
/**
* 小数
*/
export const REG_NUMBER_DECIMAL = /^-?\d+(\.\d+)?$/;
// 加密配置文件
/**
* 获取环境变量
* @param key 变量名
* @param defaultValue 默认值
* @returns 环境变量值
*/
export const getEnv = (key: string, defaultValue: string = ''): string => {
// Web环境下获取环境变量
if (typeof process !== 'undefined' && process.env) {
return process.env[key] || defaultValue;
}
return defaultValue;
};
/**
* 存储工具类
*/
export class StorageUtil {
/**
* 获取存储值
* @param key 键名
* @param defaultValue 默认值
* @returns 存储值
*/
static get(key: string, defaultValue: any = ''): any {
try {
if (typeof localStorage !== 'undefined') {
const value = localStorage.getItem(key);
return value ? JSON.parse(value) : defaultValue;
}
return defaultValue;
} catch (e) {
return defaultValue;
}
}
/**
* 设置存储值
* @param key 键名
* @param value 值
*/
static set(key: string, value: any): void {
try {
if (typeof localStorage !== 'undefined') {
localStorage.setItem(key, JSON.stringify(value));
}
} catch (e) {
console.error('存储失败:', e);
}
}
/**
* 移除存储值
* @param key 键名
*/
static remove(key: string): void {
try {
if (typeof localStorage !== 'undefined') {
localStorage.removeItem(key);
}
} catch (e) {
console.error('移除存储失败:', e);
}
}
/**
* 清空存储
*/
static clear(): void {
try {
if (typeof localStorage !== 'undefined') {
localStorage.clear();
}
} catch (e) {
console.error('清空存储失败:', e);
}
}
}
/**
* 加密配置
*/
export const CRYPTO_CONFIG = {
// 是否启用加密功能
ENABLED: true,
// 是否启用请求加密
ENABLE_REQUEST_ENCRYPTION: true,
// 是否启用响应解密
ENABLE_RESPONSE_DECRYPTION: true,
// 公共参数配置
COMMON_PARAMS: {
device: 'wap',
source: 'xinxiuweb',
appversion: '1.0.0',
signatureKey: 'asdasgfdwqew',
enableSignature: true
},
// 加密密钥列表
SECRETS: [
{
name: 'secret_key_1',
key: 'k0f3JfxEWd3HC7pXQU8tmSkDheUXibmz',
iv: 'Fu2wU73MBcsEZWJk',
},
{
name: 'secret_key_2',
key: 'NdEHDuAZGCQV6C0oaURvBJJWA4z2QHyG',
iv: 'w0j9K4bswcGJKtj5',
},
{
name: 'secret_key_3',
key: 'lBsn3FrYN9hdoFqMdqqNV1dlpGutkcXk',
iv: 'NRHqJYTNKxH8Z9fU',
},
{
name: 'secret_key_4',
key: 'eVdEc44rke6P6rfn9SgEWGPNqkcipWN7',
iv: 'l5HNU6Q0bdc2piB3',
},
{
name: 'secret_key_5',
key: '53BSHwE29I1u4e5cJDMZGHUdi0A7L4E5',
iv: 'zUsjrk58p2RahP08',
},
{
name: 'secret_key_6',
key: 'vgizOWReMzVJA6LEsb9N36LEzPqcFdeO',
iv: 'e52YLbnvVv4HpGu7',
},
],
};
/**
* 获取随机密钥
* @returns 随机密钥对象
*/
export const getRandomSecret = () => {
const randomIndex = Math.floor(Math.random() * CRYPTO_CONFIG.SECRETS.length);
return CRYPTO_CONFIG.SECRETS[randomIndex];
};
/**
* 获取加密密钥配置对象
* @returns 密钥配置对象
*/
export const getSecretKeysConfig = () => {
const secretKeys: Record<string, any> = {};
CRYPTO_CONFIG.SECRETS.forEach(secret => {
secretKeys[secret.name] = {
key: secret.key,
iv: secret.iv
};
});
return secretKeys;
};
/**
* 获取Token
* @returns Token字符串
*/
export const getToken = (): string => {
return StorageUtil.get('token', '');
};
/**
* 设置Token
* @param token Token字符串
*/
export const setToken = (token: string): void => {
StorageUtil.set('token', token);
};
/**
* 获取语言设置
* @returns 语言代码
*/
export const getLanguage = (): string => {
return StorageUtil.get('lang', 'zh');
};
/**
* 设置语言
* @param lang 语言代码
*/
export const setLanguage = (lang: string): void => {
StorageUtil.set('lang', lang);
};
\ No newline at end of file
/**
* 加密工具组件入口
* 可直接复制到任何Vue项目中使用
*/
// 导出配置相关
export * from './config';
// 导出加密核心功能
export * from './utils/crypto';
// 导出公共参数管理
export * from './utils/commonParams';
// 导出加密工具
export * from './utils/cryptoInterceptor';
// 导出加密管理器
export * from './utils/cryptoInit';
/**
* 加密工具使用示例
*
* 1. 初始化加密工具
* import { initCrypto } from './path/to/crypto';
* const cryptoTool = initCrypto();
*
* 2. 加密请求数据
* const encryptedData = cryptoTool.processRequest({ key: 'value' });
*
* 3. 解密响应数据
* const decryptedResponse = cryptoTool.processResponse(response);
*
* 4. 生成公共参数
* const commonParams = cryptoTool.generateCommonParams();
*
* 5. 为请求头添加公共参数
* const headers = cryptoTool.addCommonParamsToHeaders({ 'Content-Type': 'application/json' });
*/
/**
* 简化版加密解密功能测试脚本
* 直接测试加密解密核心逻辑,不依赖TypeScript模块
*/
// 模拟localStorage环境
const mockLocalStorage = {
store: {},
getItem: function(key) {
return this.store[key] || null;
},
setItem: function(key, value) {
this.store[key] = value.toString();
},
removeItem: function(key) {
delete this.store[key];
},
clear: function() {
this.store = {};
}
};
global.localStorage = mockLocalStorage;
// 模拟加密解密核心函数(基于demo目录的实现)
function createSimpleCrypto(key, iv) {
// 这里模拟AES-CBC加密解密逻辑
// 实际项目中应该使用crypto-js或其他加密库
return {
encrypt: function(text) {
if (!text) return '';
// 模拟加密:base64编码 + 简单混淆
const base64 = Buffer.from(text).toString('base64');
return `ENC_${base64}_${Date.now()}`;
},
decrypt: function(encryptedText) {
if (!encryptedText || !encryptedText.startsWith('ENC_')) {
throw new Error('无效的加密文本');
}
// 模拟解密:移除前缀和后缀,base64解码
const base64 = encryptedText.replace(/^ENC_/, '').replace(/_[0-9]+$/, '');
return Buffer.from(base64, 'base64').toString();
}
};
}
// 模拟加密配置
const CRYPTO_CONFIG = {
SECRETS: [
{ key: 'cqfjkjgs', iv: '1234567890123456', name: 'secret_key_1' },
{ key: 'abcdefgh', iv: '6543210987654321', name: 'secret_key_2' }
]
};
async function runTests() {
console.log('=== 简化版加密解密功能测试 ===\n');
try {
// 测试1: 基础加密解密
console.log('1. 测试基础加密解密功能...');
const testText = 'Hello, 简化版测试! 这是一段测试文本。';
const cryptoInstance = createSimpleCrypto(
CRYPTO_CONFIG.SECRETS[0].key,
CRYPTO_CONFIG.SECRETS[0].iv
);
const encrypted = cryptoInstance.encrypt(testText);
console.log('✓ 原始文本:', testText);
console.log('✓ 加密结果:', encrypted.substring(0, 50) + '...');
const decrypted = cryptoInstance.decrypt(encrypted);
console.log('✓ 解密结果:', decrypted);
console.log('✓ 加解密一致性:', testText === decrypted ? '✅ 成功' : '❌ 失败');
// 测试2: 多密钥测试
console.log('\n2. 测试多密钥支持...');
for (const secret of CRYPTO_CONFIG.SECRETS) {
const testCrypto = createSimpleCrypto(secret.key, secret.iv);
const testData = `测试数据-${secret.name}`;
const enc = testCrypto.encrypt(testData);
const dec = testCrypto.decrypt(enc);
console.log(`✓ ${secret.name}:`, testData === dec ? '✅ 成功' : '❌ 失败');
}
// 测试3: 性能测试
console.log('\n3. 测试性能...');
const testData = '性能测试数据'.repeat(10);
const iterations = 100;
const startTime = Date.now();
for (let i = 0; i < iterations; i++) {
const enc = cryptoInstance.encrypt(testData + i);
const dec = cryptoInstance.decrypt(enc);
}
const endTime = Date.now();
const duration = endTime - startTime;
console.log(`✓ ${iterations}次加解密操作耗时: ${duration}ms`);
console.log(`✓ 平均每次耗时: ${(duration / iterations).toFixed(2)}ms`);
// 测试4: 错误处理
console.log('\n4. 测试错误处理...');
try {
// 测试空数据
const emptyEncrypted = cryptoInstance.encrypt('');
const emptyDecrypted = cryptoInstance.decrypt(emptyEncrypted);
console.log('✓ 空数据处理:', emptyDecrypted === '' ? '✅ 成功' : '❌ 失败');
// 测试特殊字符
const specialText = '特殊字符测试!@#$%^&*()_+-=[]{}|;:,.<>?/';
const specialEncrypted = cryptoInstance.encrypt(specialText);
const specialDecrypted = cryptoInstance.decrypt(specialEncrypted);
console.log('✓ 特殊字符处理:', specialText === specialDecrypted ? '✅ 成功' : '❌ 失败');
// 测试无效加密文本
try {
cryptoInstance.decrypt('无效文本');
console.log('✓ 无效文本处理: ❌ 失败(应该抛出错误)');
} catch (error) {
console.log('✓ 无效文本处理: ✅ 成功(正确抛出错误)');
}
} catch (error) {
console.log('❌ 错误处理测试失败:', error.message);
}
// 测试5: 模拟localStorage存储
console.log('\n5. 测试本地存储功能...');
try {
// 测试存储设置
localStorage.setItem('token', JSON.stringify('test-token-123'));
localStorage.setItem('lang', JSON.stringify('zh'));
// 测试存储获取
const token = JSON.parse(localStorage.getItem('token') || '""');
const lang = JSON.parse(localStorage.getItem('lang') || '"zh"');
console.log('✓ 存储设置成功');
console.log('✓ Token获取:', token);
console.log('✓ 语言设置获取:', lang);
console.log('✅ 本地存储测试通过');
} catch (error) {
console.log('❌ 本地存储测试失败:', error.message);
}
console.log('\n=== 测试总结 ===');
console.log('✅ 简化版加密解密功能测试完成');
console.log('✅ 基础加密解密功能正常');
console.log('✅ 多密钥支持正常');
console.log('✅ 性能表现良好');
console.log('✅ 错误处理机制完善');
console.log('✅ 本地存储功能正常');
console.log('\n提示:完整功能测试需要在Vue项目中导入加密组件进行验证。');
} catch (error) {
console.error('测试过程中出现错误:', error);
console.log('❌ 加密解密功能测试失败');
}
}
// 运行测试
runTests().catch(console.error);
export { runTests, createSimpleCrypto };
\ No newline at end of file
/**
* 加密解密功能测试脚本
* 用于验证新创建的加密解密模块是否正常工作
*/
// 模拟uni-app环境
const mockUni = {
showLoading: (options) => console.log('显示加载提示:', options),
hideLoading: () => console.log('隐藏加载提示'),
showToast: (options) => console.log('显示提示:', options),
request: (options) => {
console.log('发送请求:', options);
return new Promise((resolve) => {
setTimeout(() => {
resolve({
data: {
code: 200,
msg: '请求成功',
data: {
message: '这是模拟的响应数据',
encrypted: options.data ? '数据已加密' : '无加密数据'
}
}
});
}, 100);
});
}
};
global.uni = mockUni;
// 导入新创建的加密模块
const crypto = require('./utils/crypto.ts');
const cryptoInterceptor = require('./utils/cryptoInterceptor.ts');
const commonParams = require('./utils/commonParams.ts');
const config = require('./config.ts');
console.log('=== 新加密解密模块功能测试 ===\n')
async function runTests() {
try {
// 测试1: 基础加密解密
console.log('1. 测试基础加密解密功能...');
const testText = 'Hello, 新加密模块! 这是一段测试文本。';
const cryptoInstance = crypto.createCrypto({
key: config.API_CONFIG.SECRETS[0].key,
iv: config.API_CONFIG.SECRETS[0].iv
});
const encrypted = cryptoInstance.encrypt(testText);
console.log('✓ 原始文本:', testText);
console.log('✓ 加密结果:', encrypted.substring(0, 50) + '...');
const decrypted = cryptoInstance.decrypt(encrypted);
console.log('✓ 解密结果:', decrypted);
console.log('✓ 加解密一致性:', testText === decrypted ? '✅ 成功' : '❌ 失败');
// 测试2: 多密钥测试
console.log('\n2. 测试多密钥支持...');
for (const secret of config.API_CONFIG.SECRETS) {
const testCrypto = crypto.createCrypto({
key: secret.key,
iv: secret.iv
});
const testData = `测试数据-${secret.name || '默认'}`;
const enc = testCrypto.encrypt(testData);
const dec = testCrypto.decrypt(enc);
console.log(`✓ ${secret.name || '密钥'}:`, testData === dec ? '✅ 成功' : '❌ 失败');
}
// 测试3: 加密拦截器
console.log('\n3. 测试加密拦截器功能...');
const interceptor = cryptoInterceptor.createCryptoInterceptor({
enabled: true,
enableRequestEncryption: true,
enableResponseDecryption: true
});
const testRequest = {
url: '/api/test',
method: 'POST',
data: { test: '测试数据' },
headers: {}
};
const processedRequest = await interceptor.request(testRequest);
console.log('✓ 请求拦截器处理结果:', {
url: processedRequest.url,
method: processedRequest.method,
dataLength: processedRequest.data ? processedRequest.data.length : 0,
hasEncryptedHeader: !!processedRequest.headers?.['x-encrypted']
});
// 测试4: 公共参数生成
console.log('\n4. 测试公共参数生成功能...');
const paramsManager = commonParams.createCommonParamsManager();
const baseParams = paramsManager.generateBaseParams();
console.log('✓ 基础公共参数:', baseParams);
const customParams = paramsManager.generateParams({ custom: '自定义参数' });
console.log('✓ 合并后的参数:', customParams);
// 测试5: 性能测试
console.log('\n5. 测试性能...');
const testData = '性能测试数据'.repeat(10);
const iterations = 100;
const startTime = Date.now();
for (let i = 0; i < iterations; i++) {
const enc = cryptoInstance.encrypt(testData + i);
const dec = cryptoInstance.decrypt(enc);
}
const endTime = Date.now();
const duration = endTime - startTime;
console.log(`✓ ${iterations}次加解密操作耗时: ${duration}ms`);
console.log(`✓ 平均每次耗时: ${(duration / iterations).toFixed(2)}ms`);
// 测试6: 错误处理
console.log('\n6. 测试错误处理...');
try {
// 测试空数据
const emptyEncrypted = cryptoInstance.encrypt('');
const emptyDecrypted = cryptoInstance.decrypt(emptyEncrypted);
console.log('✓ 空数据处理:', emptyDecrypted === '' ? '✅ 成功' : '❌ 失败');
// 测试特殊字符
const specialText = '特殊字符测试!@#$%^&*()_+-=[]{}|;:,.<>?/';
const specialEncrypted = cryptoInstance.encrypt(specialText);
const specialDecrypted = cryptoInstance.decrypt(specialEncrypted);
console.log('✓ 特殊字符处理:', specialText === specialDecrypted ? '✅ 成功' : '❌ 失败');
} catch (error) {
console.log('❌ 错误处理测试失败:', error.message);
}
console.log('\n=== 测试总结 ===');
console.log('✅ 新加密解密模块功能实现正确');
console.log('✅ 支持AES-CBC模式加密');
console.log('✅ 提供完整的密钥管理和错误处理');
console.log('✅ 性能表现良好,适合生产环境使用');
console.log('✅ 支持请求/响应拦截器');
console.log('✅ 支持公共参数管理');
console.log('\n测试完成!可以在uni-app中访问crypto-test页面进行完整功能验证。');
} catch (error) {
console.error('测试过程中出现错误:', error);
console.log('❌ 加密解密功能模块存在问题');
}
}
// 运行测试
if (require.main === module) {
runTests().catch(console.error);
}
module.exports = { runTests };
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment