This commit is contained in:
parent
61ef28c83e
commit
bd27f18e4c
|
@ -0,0 +1 @@
|
|||
cache
|
|
@ -0,0 +1,41 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
|
||||
|
||||
.solid
|
||||
.turbo
|
||||
|
||||
# /dist/
|
||||
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
|
@ -0,0 +1,10 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
// This tells ESLint to load the config from the package `eslint-config-custom`
|
||||
extends: ['@myturborepo/eslint-config-custom'],
|
||||
settings: {
|
||||
next: {
|
||||
rootDir: ['apps/*/'],
|
||||
},
|
||||
},
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
coverage
|
||||
|
||||
# next.js
|
||||
.next/
|
||||
out/
|
||||
build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
**/.env
|
||||
|
||||
# turbo
|
||||
.turbo
|
||||
|
||||
# IDEs
|
||||
.idea
|
||||
|
||||
# nuxt
|
||||
.output
|
||||
|
||||
# histoire
|
||||
.histoire
|
||||
|
||||
# vitepress
|
||||
**/.vitepress/dist/**
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"git": {
|
||||
"commitMessage": "chore: release v${version}"
|
||||
},
|
||||
"github": {
|
||||
"release": true,
|
||||
"releaseName": "v${version}"
|
||||
},
|
||||
"npm": {
|
||||
"publish": false
|
||||
},
|
||||
"plugins": {
|
||||
"@release-it/conventional-changelog": {
|
||||
"preset": "conventionalcommits",
|
||||
"infile": "CHANGELOG.md"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
|
||||
### [0.0.5](https://github.com/gurvan-guss/turborepo-nuxt-boilerplate/compare/0.0.4...0.0.5) (2022-10-18)
|
||||
|
||||
### [0.0.4](https://github.com/gurvan-guss/turborepo-nuxt-boilerplate/compare/0.0.3...0.0.4) (2022-10-18)
|
||||
|
||||
### [0.0.3](https://github.com/gurvan-guss/turborepo-nuxt-boilerplate/compare/0.0.2...0.0.3) (2022-10-18)
|
||||
|
||||
### [0.0.2](https://github.com/gurvan-guss/turborepo-nuxt-boilerplate/compare/0.0.1...0.0.2) (2022-10-18)
|
||||
|
||||
### 0.0.1 (2022-10-18)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "cd: bump to node 18" ([b11ebe4](https://github.com/gurvan-guss/turborepo-nuxt-boilerplate/commit/b11ebe469bf62bfa9642da00facb2d9394d3f09e))
|
|
@ -0,0 +1,15 @@
|
|||
FROM node:20
|
||||
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
|
||||
RUN npm config set registry https://registry.npmmirror.com
|
||||
RUN npm install -g pnpm
|
||||
RUN pnpm config set registry https://registry.npmmirror.com/
|
||||
RUN pnpm i
|
||||
RUN pnpm build:web
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD [ "pnpm" ,"preview:web" ]
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "docs",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vitepress dev pages",
|
||||
"build": "vitepress build pages",
|
||||
"serve": "vitepress serve pages"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@myturborepo/ui": "workspace:*",
|
||||
"vitepress": "1.0.0-beta.1",
|
||||
"vue": "^3.3.4"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"type": "module"
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,22 @@
|
|||
export default {
|
||||
title: 'VitePress',
|
||||
description: 'Just playing around with turborepo',
|
||||
themeConfig: {
|
||||
siteTitle: 'My Custom Title',
|
||||
nav: [
|
||||
{ text: 'Index', link: '/index' },
|
||||
{ text: 'Getting started', link: '/getting-started' },
|
||||
{ text: 'Github', link: 'https://' },
|
||||
],
|
||||
sidebar: [
|
||||
{
|
||||
text: 'Guide',
|
||||
items: [
|
||||
{ text: 'Index', link: '/index' },
|
||||
{ text: 'Getting started', link: '/getting-started' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import DefaultTheme from 'vitepress/theme'
|
||||
import MyButton from '@myturborepo/ui/components/Button.vue'
|
||||
|
||||
export default {
|
||||
...DefaultTheme,
|
||||
enhanceApp({ app }) {
|
||||
app.component('MyButton', MyButton)
|
||||
},
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
# Getting started
|
|
@ -0,0 +1,2 @@
|
|||
# Hello VitePress
|
||||
<MyButton />
|
|
@ -0,0 +1,9 @@
|
|||
node_modules
|
||||
*.log*
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
.output
|
||||
.env
|
||||
dist
|
||||
.netlify
|
|
@ -0,0 +1,42 @@
|
|||
# Nuxt 3 Minimal Starter
|
||||
|
||||
Look at the [nuxt 3 documentation](https://v3.nuxtjs.org) to learn more.
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure to install the dependencies:
|
||||
|
||||
```bash
|
||||
# yarn
|
||||
yarn install
|
||||
|
||||
# npm
|
||||
npm install
|
||||
|
||||
# pnpm
|
||||
pnpm install --shamefully-hoist
|
||||
```
|
||||
|
||||
## Development Server
|
||||
|
||||
Start the development server on http://localhost:3000
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Build the application for production:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
|
||||
```bash
|
||||
npm run preview
|
||||
```
|
||||
|
||||
Checkout the [deployment documentation](https://v3.nuxtjs.org/guide/deploy/presets) for more information.
|
|
@ -0,0 +1,24 @@
|
|||
export interface AccessKeyInfo {
|
||||
AccessKey: string
|
||||
SecretKey: string
|
||||
Expiration: number
|
||||
}
|
||||
|
||||
export interface AccessKeyCreate {
|
||||
// 过期时间
|
||||
Expiration: number
|
||||
}
|
||||
|
||||
export const accessKeyApi = {
|
||||
list: (params: Page.Request) => request.get<Page.Response<AccessKeyInfo>>('access_key/list', params),
|
||||
show: (params: AccessKeyInfo) => request.get<{
|
||||
SecretKey: string
|
||||
}>('access_key/show', params),
|
||||
create: (params: AccessKeyCreate) => request.post('access_key/save', params),
|
||||
delete: (params: AccessKeyInfo) => {
|
||||
return request.post('access_key/change/status', {
|
||||
AccessKey: params.AccessKey,
|
||||
Status: 3,
|
||||
})
|
||||
},
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
export const authApi = {
|
||||
captcha: () => request.get<{
|
||||
imgByte: string
|
||||
uuid: string
|
||||
}>('captcha'),
|
||||
auditors: () => request.get<{
|
||||
Id: string
|
||||
UserName: string
|
||||
}[]>('sys_user_audit_list'),
|
||||
sendCode(params: { Phone?: string; Email?: string }) {
|
||||
return request.post('dep_send_login_code', params)
|
||||
},
|
||||
sendSafeCode(params: { Type?: 'phone' | 'totp' | 'email' }) {
|
||||
return request.post('dep_person/send/check/code', params)
|
||||
},
|
||||
loginSafeCheck(params: {
|
||||
Code: string
|
||||
UserName?: string
|
||||
Phone?: string
|
||||
Email?: string
|
||||
}) {
|
||||
return request.post<{
|
||||
Token: string
|
||||
}>('dep_login_code', params)
|
||||
},
|
||||
loginByDevice(params: {
|
||||
Email?: string
|
||||
Phone?: string
|
||||
UserName?: string
|
||||
Code: string
|
||||
}) {
|
||||
return request.post<{
|
||||
Token: string
|
||||
Email?: string
|
||||
Phone?: string
|
||||
}>('dep_login_code', params)
|
||||
},
|
||||
callbackSSO: (sso: string, params: { Code: string }) => {
|
||||
return request.post<{ Token: string }>(`dep_login/ssocallback/${sso}`, params)
|
||||
},
|
||||
loginByOauth: async (sso: 'ihep' | 'carsipre', state: string) => {
|
||||
const res = await request.get<{
|
||||
RedirectURL: string
|
||||
}>(`dep_login/sso/${sso}`, {
|
||||
state,
|
||||
})
|
||||
window.open(res.RedirectURL)
|
||||
},
|
||||
login(params: {
|
||||
UserName: string
|
||||
Password: string
|
||||
Code: string
|
||||
UUID: string
|
||||
}) {
|
||||
return request.post<{
|
||||
Token: string
|
||||
Email?: string
|
||||
Phone?: string
|
||||
UserName?: string
|
||||
}>('dep_login', params)
|
||||
},
|
||||
register: (params: Partial<ReigsterParams>) => request.post('dep_register', params),
|
||||
postSafeCheck(params: { Code: string; CodeType: string }) {
|
||||
return request.post('dep_person/safe/check', params)
|
||||
},
|
||||
postIsSafe() {
|
||||
return request.post<boolean>('dep_person/is/safe')
|
||||
},
|
||||
}
|
||||
|
||||
export interface ReigsterParams {
|
||||
/**
|
||||
* 验证码
|
||||
*/
|
||||
Code: string
|
||||
/**
|
||||
* Email
|
||||
*/
|
||||
Email: string
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
Password: string
|
||||
/**
|
||||
* 管理用户ID
|
||||
*/
|
||||
SysUserId?: string
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
UserName: string
|
||||
/**
|
||||
* Uuid
|
||||
*/
|
||||
Uuid: string
|
||||
|
||||
RandomPass?: boolean
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
export interface BucketInfo {
|
||||
FileNum: number
|
||||
Mtime: number
|
||||
Name: string
|
||||
Path: string
|
||||
QuotaSize: number
|
||||
TotalSize: number
|
||||
}
|
||||
|
||||
export interface BucketCreateAndEditParams {
|
||||
|
||||
BucketName: string
|
||||
BucketLimitSize: number
|
||||
}
|
||||
|
||||
export interface FileInfo {
|
||||
Name: string
|
||||
FileSize: number
|
||||
Type: string
|
||||
Mtime: number
|
||||
Path: string
|
||||
DownloadUrl: string
|
||||
}
|
||||
|
||||
export interface FileEditParams {
|
||||
OldFileName: string
|
||||
NewFileName: string
|
||||
}
|
||||
|
||||
export interface CreateDirParams {
|
||||
DirName: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回的数据
|
||||
*/
|
||||
export interface FileinfoData {
|
||||
ChunkList?: FileinfoDataChunkList
|
||||
/**
|
||||
* 文件创建时间
|
||||
*/
|
||||
Ctime?: number
|
||||
/**
|
||||
* 文件最后修改时间
|
||||
*/
|
||||
Mtime?: number
|
||||
/**
|
||||
* 文件名
|
||||
*/
|
||||
Name?: string
|
||||
/**
|
||||
* 文件路径
|
||||
*/
|
||||
Path?: string
|
||||
/**
|
||||
* 文件大小
|
||||
*/
|
||||
Size?: number
|
||||
/**
|
||||
* 文件类型
|
||||
*/
|
||||
Type?: string
|
||||
[property: string]: any
|
||||
}
|
||||
|
||||
/**
|
||||
* jwanfs-web-server.server.api.v1.ChunkList
|
||||
*/
|
||||
export interface FileinfoDataChunkList {
|
||||
/**
|
||||
* 分块数量
|
||||
*/
|
||||
Chunks?: FileinfoDataChunkInfo[]
|
||||
/**
|
||||
* 分块数量
|
||||
*/
|
||||
Total?: number
|
||||
[property: string]: any
|
||||
}
|
||||
|
||||
/**
|
||||
* jwanfs-web-server.server.api.v1.ChunkInfo
|
||||
*/
|
||||
export interface FileinfoDataChunkInfo {
|
||||
/**
|
||||
* 分块ID
|
||||
*/
|
||||
ChunkID?: string
|
||||
/**
|
||||
* 分块大小 单位KB
|
||||
*/
|
||||
ChunkSize?: number
|
||||
/**
|
||||
* 数据中心标签
|
||||
*/
|
||||
DataCenterTags?: string[]
|
||||
/**
|
||||
* 内部存储卷地址
|
||||
*/
|
||||
InternalURL?: string[]
|
||||
/**
|
||||
* 服务器标签
|
||||
*/
|
||||
ServerTags?: string[]
|
||||
[property: string]: any
|
||||
}
|
||||
|
||||
export interface RecycledInfo {
|
||||
/**
|
||||
* 删除时间,时间戳精确到毫秒
|
||||
*/
|
||||
DeleteTime: number
|
||||
/**
|
||||
* 文件大小
|
||||
*/
|
||||
FileSize: number
|
||||
/**
|
||||
* 是否是文件夹
|
||||
*/
|
||||
IsDirectory: boolean
|
||||
/**
|
||||
* 是否来自Grpc
|
||||
*/
|
||||
IsFromGrpc: boolean
|
||||
/**
|
||||
* 文件最后修改时间,时间戳精确到秒
|
||||
*/
|
||||
Mtime: number
|
||||
/**
|
||||
* 文件名称
|
||||
*/
|
||||
Name: string
|
||||
/**
|
||||
* 文件夹路径
|
||||
*/
|
||||
Path: string
|
||||
/**
|
||||
* Grpc客户端进程号
|
||||
*/
|
||||
PID: string
|
||||
/**
|
||||
* 回收站有效时间,时间戳精确到秒
|
||||
*/
|
||||
RecycleEffectTime: number
|
||||
/**
|
||||
* 文件状态
|
||||
*/
|
||||
Status: number
|
||||
/**
|
||||
* 文件类型
|
||||
*/
|
||||
Type: string
|
||||
}
|
||||
|
||||
export const bucketApi = {
|
||||
list: (params: Page.Request) => request.get<Page.Response<BucketInfo>>('file_manage/bucket', params),
|
||||
create: (params: BucketCreateAndEditParams) => request.post(`file_manage/bucket/${params.BucketName}`, {
|
||||
BucketLimitSize: params.BucketLimitSize,
|
||||
}),
|
||||
edit: (params: BucketCreateAndEditParams) => request.put(`file_manage/bucket/${params.BucketName}`, {
|
||||
BucketLimitSize: params.BucketLimitSize,
|
||||
}),
|
||||
delete: (BucketName: string) => request.delete(`file_manage/bucket/${BucketName}`),
|
||||
detail: {
|
||||
list: (params: Page.Request, path: string) => request.get<Page.Response<FileInfo>>(`file_manage/file/${path}`, params),
|
||||
delete: (path: string) => request.delete(`file_manage/file/${path}`),
|
||||
edit: (params: FileEditParams, path: string) => request.put(`file_manage/file/${path}/${params.OldFileName}`, params),
|
||||
createDir: (params: CreateDirParams, path: string) => request.post(`file_manage/folder/${path}/${params.DirName}`),
|
||||
upload: (file: File, url: string, token: string) => {
|
||||
const formData = new FormData()
|
||||
formData.append('File', file)
|
||||
formData.append('Size', file.size.toString())
|
||||
return request.upload(`${url}?Token=${token}`, formData, token)
|
||||
},
|
||||
info: (path: string) => request.get<FileinfoData>(`file_manage/fileinfo/${path}`),
|
||||
uploadUrl: (params: {
|
||||
Path: string
|
||||
}) => request.get<{
|
||||
token: string
|
||||
url: string
|
||||
endpoint: string
|
||||
}>('file_manage/get/upload/info', params),
|
||||
download: (params: FileInfo) => {
|
||||
request.get<{
|
||||
Url: string
|
||||
}>(`file_manage/download/${params.Path}`).then((res) => {
|
||||
window.open(res.Url)
|
||||
})
|
||||
// "/file_manage/download"
|
||||
// window.open(`${params.DownloadUrl}`)
|
||||
},
|
||||
downloadUrl: (params: FileInfo) => {
|
||||
return (`${params.DownloadUrl}`)
|
||||
},
|
||||
},
|
||||
recycle: {
|
||||
list: (params: Page.Request & { BucketName: string }) => request.get<Page.Response<RecycledInfo>>('recycle_file/list', params),
|
||||
delete: (params: RecycledInfo) => request.post<{
|
||||
ShareUrl: string
|
||||
}>('recycle_file/delete', params),
|
||||
restore: (params: RecycledInfo) => request.post<{
|
||||
ShareUrl: string
|
||||
}>('recycle_file/restore', params),
|
||||
},
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
export const commonApi = {
|
||||
categorys: () => request.get<CategoryInfo>('public/class'),
|
||||
resources: (params: ResourceRequest & {
|
||||
ShopName?: string
|
||||
}) => request.get<Page.Response<ResourceInfo>>('public/resource', params),
|
||||
resourceDetail: (params: {
|
||||
ID?: string
|
||||
Path?: string
|
||||
Fgw?: string
|
||||
}) => request.get<ResourceInfo>('public/resource/detail', params),
|
||||
storeDetail: (params: {
|
||||
ShopName: string
|
||||
}) => request.get<StoreInfo>('public/shop/detail', params),
|
||||
}
|
||||
|
||||
export interface CategoryInfo {
|
||||
/**
|
||||
* 分类名称
|
||||
*/
|
||||
Class: ClassInfo[]
|
||||
/**
|
||||
* 完整分类
|
||||
*/
|
||||
Tags: TagInfo[]
|
||||
}
|
||||
|
||||
export interface ClassInfo {
|
||||
/**
|
||||
* 分类ID
|
||||
*/
|
||||
ID: string
|
||||
/**
|
||||
* 分类名称
|
||||
*/
|
||||
Name: string
|
||||
}
|
||||
|
||||
export interface TagInfo {
|
||||
/**
|
||||
* 标签ID
|
||||
*/
|
||||
ID: string
|
||||
/**
|
||||
* 标签名称
|
||||
*/
|
||||
Name: string
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
export interface Overview {
|
||||
AllocateBucketSize: number
|
||||
BuketSurplusSize: number
|
||||
FileNum: number
|
||||
MonthFileSize: number
|
||||
QuotaSize: number
|
||||
TotalSize: number
|
||||
CurrentBucketNum: number
|
||||
BucketSurplusQuotaSize: number
|
||||
}
|
||||
|
||||
export interface Storage {
|
||||
Name: string
|
||||
FileNum: number
|
||||
TotalSize: number
|
||||
QuotaSize: number
|
||||
UsagePercent: number
|
||||
DocumentTypeSize: number
|
||||
MusicTypeSize: number
|
||||
PictureTypeSize: number
|
||||
VedioTypeSize: number
|
||||
OtherTypeSize: number
|
||||
}
|
||||
|
||||
export const dashboardApi = {
|
||||
overview() {
|
||||
return request.get<Overview>('dep_dashboard/topic1')
|
||||
},
|
||||
storage(params: {
|
||||
BucketName?: string
|
||||
isAll?: number
|
||||
}) {
|
||||
return request.get<Storage>('dep_dashboard/topic3', params)
|
||||
},
|
||||
storageBucket() {
|
||||
return request.get<
|
||||
{
|
||||
Name: string
|
||||
TotalSize: number
|
||||
QuotaSize: number
|
||||
}[]
|
||||
>('dep_dashboard/topic2')
|
||||
},
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
export interface FilerInfo { Name: string; Address: string; ID: string | number; OSSAddress: string }
|
||||
|
||||
export const filerApi = {
|
||||
list: () => request.get<FilerInfo[]>(
|
||||
'dep_dashboard/filer/list',
|
||||
),
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
export interface LogInfo {
|
||||
/**
|
||||
* 日志类型编号
|
||||
1-上传文件成功
|
||||
2-上传文件失败
|
||||
3-删除文件
|
||||
4-删除桶
|
||||
*/
|
||||
LogType: string
|
||||
/**
|
||||
* 日志类型名称
|
||||
*/
|
||||
LogTypeContent: string
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
Mtime: string
|
||||
/**
|
||||
* 文件名
|
||||
*/
|
||||
ObjectName: string
|
||||
/**
|
||||
* 文件类型
|
||||
*/
|
||||
ObjectType: string
|
||||
|
||||
ObjectSize: number
|
||||
/**
|
||||
* 所属用户
|
||||
*/
|
||||
OwnerID: string
|
||||
}
|
||||
|
||||
export const logApi = {
|
||||
list: (params: Page.Request) => request.get<Page.Response<LogInfo>>('filer_log/list', params),
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
export const baseURL = {
|
||||
v1: '/api/v1/',
|
||||
}
|
||||
|
||||
async function post<T>(method: 'POST' | 'PATCH' | 'PUT', url: string, data?: any, opts: { [k: string]: any } = { headers: {} }) {
|
||||
const headers = opts.headers ?? {}
|
||||
const res = await $fetch<HTTP.Response<T>>(baseURL.v1 + url, {
|
||||
method,
|
||||
body: data,
|
||||
...opts,
|
||||
headers: {
|
||||
UToken: storage.GetItem('UToken'),
|
||||
...headers,
|
||||
},
|
||||
})
|
||||
// 第三方登录跳转至需要注册(信息补充)
|
||||
if (res.Code === 401) {
|
||||
navigateTo(`/login/complete?CarsiCode=${(res as HTTP.Response<T, 401>).Data.Token}`, {
|
||||
replace: true,
|
||||
})
|
||||
}
|
||||
|
||||
if (res.Code === 13) {
|
||||
storage.RemoveItem('UToken')
|
||||
navigateTo('/login')
|
||||
}
|
||||
|
||||
// 安全验证
|
||||
if (res.Code === 60)
|
||||
throw res.Code
|
||||
|
||||
if (res.Code !== 0)
|
||||
throw new Error(res.Msg)
|
||||
|
||||
return res.Data
|
||||
}
|
||||
|
||||
async function get<T>(method: 'DELETE' | 'GET', url: string, data?: any) {
|
||||
const res = await $fetch<HTTP.Response<T>>(baseURL.v1 + url, {
|
||||
method,
|
||||
query: data,
|
||||
// @ts-expect-error
|
||||
headers: {
|
||||
UToken: storage.GetItem('UToken'),
|
||||
},
|
||||
})
|
||||
|
||||
// 第三方登录跳转至需要注册(信息补充)
|
||||
if (res.Code === 401) {
|
||||
navigateTo(`/register?CarsiCode=${(res as HTTP.Response<T, 401>).Data.Token}`, {
|
||||
replace: true,
|
||||
})
|
||||
}
|
||||
|
||||
if (res.Code === 13) {
|
||||
storage.RemoveItem('UToken')
|
||||
navigateTo('/login')
|
||||
}
|
||||
|
||||
// 安全验证
|
||||
if (res.Code === 60)
|
||||
throw res.Code
|
||||
|
||||
if (res.Code !== 0)
|
||||
throw new Error(res.Msg)
|
||||
|
||||
return res.Data
|
||||
}
|
||||
|
||||
export const request = {
|
||||
post: <T>(url: string, data?: any) => post<T>('POST', url, data),
|
||||
patch: <T>(url: string, data?: any) => post<T>('PATCH', url, data),
|
||||
put: <T>(url: string, data?: any) => post<T>('PUT', url, data),
|
||||
get: <T>(url: string, data?: any) => get<T>('GET', url, data),
|
||||
delete: <T>(url: string, data?: any) => get<T>('DELETE', url, data),
|
||||
upload: <T>(url: string, data?: any, token = '') => {
|
||||
// $fetch 上传会报错
|
||||
return fetch(url, {
|
||||
method: 'post',
|
||||
body: data,
|
||||
headers: {
|
||||
token,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.ok)
|
||||
return response.json() as T
|
||||
|
||||
else
|
||||
throw new Error(`${response.status} : ${response.statusText}`)
|
||||
})
|
||||
},
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
import type { FileInfo } from './bucket'
|
||||
|
||||
// 资源属性
|
||||
export interface ResourceInfo {
|
||||
BucketName: string
|
||||
ClassID: string
|
||||
ClassName: string
|
||||
Createtime: number
|
||||
Description: string
|
||||
Document: string
|
||||
EffectiveTime: number
|
||||
Ico: string
|
||||
ID: string
|
||||
IsEnable: boolean
|
||||
Name: string
|
||||
Download: number
|
||||
OwnerID: string
|
||||
DownloadUrl: string
|
||||
ShopID: string
|
||||
ShopName?: string
|
||||
FileInfo: FileInfo[]
|
||||
Tags?: string[]
|
||||
Type?: string
|
||||
}
|
||||
|
||||
// 资源创建
|
||||
export interface ResourceCreate {
|
||||
Type: string
|
||||
Ico: File
|
||||
Background: File
|
||||
Name: string
|
||||
Path: string
|
||||
BucketName: string
|
||||
Description: string
|
||||
IsEnable: boolean
|
||||
ClassID: string
|
||||
Tags: string[]
|
||||
Document: string
|
||||
ShopID: string
|
||||
}
|
||||
|
||||
// 资源修改
|
||||
export interface ResourceUpdate extends Partial<ResourceCreate> {
|
||||
ResourceID: string
|
||||
}
|
||||
|
||||
export interface ResourceRequest {
|
||||
/**
|
||||
* 分类
|
||||
*/
|
||||
ClassName?: string
|
||||
/**
|
||||
* 用户
|
||||
*/
|
||||
DepId?: string
|
||||
/**
|
||||
* 名称过滤
|
||||
*/
|
||||
Name?: string
|
||||
/**
|
||||
* 当前页
|
||||
*/
|
||||
Page?: number
|
||||
/**
|
||||
* 页大小
|
||||
*/
|
||||
PageSize?: number
|
||||
/**
|
||||
* 店铺名称
|
||||
*/
|
||||
ShopName?: string
|
||||
/**
|
||||
* 名称Name,使用大小TotalSize,对象大小FileNum,配额大小QuotaSize,
|
||||
*/
|
||||
SortName?: string
|
||||
/**
|
||||
* 升序 asc,降序desc
|
||||
*/
|
||||
SortOrder?: string
|
||||
/**
|
||||
* 标签
|
||||
*/
|
||||
Tags?: string[]
|
||||
}
|
||||
|
||||
export interface ResourceDashboard {
|
||||
ID: string
|
||||
Name: string
|
||||
ResourceFile: ResourceInfo[]
|
||||
}
|
||||
|
||||
export const resourceApi = {
|
||||
dashboard: () => request.get<Page.Response<ResourceDashboard>>('user/data/resource/dashboard'),
|
||||
list: (params: ResourceRequest & {
|
||||
ShopName?: string
|
||||
}) => request.get<Page.Response<ResourceInfo>>('user/data/resource', params),
|
||||
detail: (params: {
|
||||
ID?: string
|
||||
Path?: string
|
||||
}) => request.get<ResourceInfo>('user/data/resource/detail', params),
|
||||
// 添加资源
|
||||
create: (params: ResourceCreate) => {
|
||||
const data = paramsTool.toFormData(params)
|
||||
return request.post('user/data/resource', data)
|
||||
},
|
||||
download: (params: {
|
||||
DownloadUrl: string
|
||||
}) => {
|
||||
window.open(`${params.DownloadUrl}`)
|
||||
},
|
||||
// 删除资源
|
||||
delete: (data: ResourceInfo) => request.delete('user/data/resource', {
|
||||
ID: data.ID,
|
||||
}),
|
||||
resolution: (src?: string, style: 'st1' | 'st2' | 'st3' = 'st1', refresh?: boolean) => src ? `${src}&style=${style}${refresh ? `×tamp=${new Date().getTime()}` : ''}` : '/null.webp',
|
||||
// 更新资源
|
||||
update: (params: ResourceUpdate) => {
|
||||
const data = paramsTool.toFormData(params)
|
||||
return request.patch('user/data/resource', data)
|
||||
},
|
||||
// 标签列表
|
||||
tags: () => request.get<string[]>('/resource/tags'),
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
export interface ShareInfo {
|
||||
Id: any
|
||||
Name: string
|
||||
ShareUrl: string
|
||||
Status: number
|
||||
ExploreTime: number
|
||||
}
|
||||
|
||||
export interface ShareCreate {
|
||||
Path: string
|
||||
Expire: number
|
||||
ShareFilePassword?: string
|
||||
IsEncrypt: boolean
|
||||
}
|
||||
|
||||
export const shareApi = {
|
||||
list: (params: Page.Request) => request.get<Page.Response<ShareInfo>>('file_share/list', params),
|
||||
create: (params: ShareCreate) => request.post<{ ShareUrl: string }>('file_share/save', params),
|
||||
delete: (params: ShareInfo) => request.post<{ ShareUrl: string }>('file_share/delete', params),
|
||||
shareUrl: (params: ShareInfo) => `${window.location.origin}${baseURL.v1}file_manage/proxyfgw/${params.ShareUrl}`,
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
// 店铺属性
|
||||
export interface StoreInfo {
|
||||
Background: string
|
||||
ClassID: string
|
||||
ClassName: string
|
||||
Createtime: number
|
||||
Ico: string
|
||||
Mark: string
|
||||
Name: string
|
||||
OwnerID: string
|
||||
ShopID: string
|
||||
Tags: string[]
|
||||
}
|
||||
|
||||
// 店铺创建属性
|
||||
export interface StoreCreate {
|
||||
Ico: File
|
||||
ShopName: string
|
||||
Background: File
|
||||
ClassID: string
|
||||
Tags: string[]
|
||||
Mark: string
|
||||
}
|
||||
|
||||
export interface StoreListQuery {
|
||||
/**
|
||||
* 分类
|
||||
*/
|
||||
ClassName?: string
|
||||
/**
|
||||
* 用户
|
||||
*/
|
||||
DepId?: string | number
|
||||
/**
|
||||
* 名称过滤
|
||||
*/
|
||||
Name?: string
|
||||
/**
|
||||
* 当前页
|
||||
*/
|
||||
Page?: number
|
||||
/**
|
||||
* 页大小
|
||||
*/
|
||||
PageSize?: number
|
||||
/**
|
||||
* 名称Name,使用大小TotalSize,对象大小FileNum,配额大小QuotaSize,
|
||||
*/
|
||||
SortName?: string
|
||||
/**
|
||||
* 升序 asc,降序desc
|
||||
*/
|
||||
SortOrder?: string
|
||||
/**
|
||||
* 标签
|
||||
*/
|
||||
Tags?: string[]
|
||||
|
||||
ShopName?: string
|
||||
}
|
||||
|
||||
export const storeApi = {
|
||||
// 店铺列表
|
||||
list: (params: StoreListQuery) => request.get<Page.Response<StoreInfo>>('user/manage/shop', params),
|
||||
detail: (params: {
|
||||
ShopName: string
|
||||
}) => request.get<StoreInfo>('user/manage/shop/detail', params),
|
||||
// 添加店铺
|
||||
create: (params: StoreCreate) => {
|
||||
const data = paramsTool.toFormData(params)
|
||||
return request.post('user/manage/shop', data)
|
||||
},
|
||||
// 删除店铺
|
||||
delete: (params: StoreInfo) => request.delete<StoreInfo>('user/manage/shop', params),
|
||||
// 修改店铺
|
||||
update: (params: StoreCreate & { ShopID: string }) => {
|
||||
const data = paramsTool.toFormData(params)
|
||||
return request.patch('user/manage/shop', data)
|
||||
},
|
||||
official: () => request.get<Page.Response<StoreInfo>>('user/manage/sysshop'),
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
export interface ViewCard {
|
||||
src: any
|
||||
id?: string
|
||||
name?: string
|
||||
star?: boolean
|
||||
backgroundColor?: string
|
||||
[attr: string]: any
|
||||
}
|
||||
|
||||
/*
|
||||
* @Description:
|
||||
* @Version: 2.0
|
||||
* @Author: Yaowen Liu
|
||||
* @Date: 2021-10-14 13:34:56
|
||||
* @LastEditors: Yaowen Liu
|
||||
* @LastEditTime: 2023-09-21 09:23:25
|
||||
*/
|
||||
// import type { ViewCard } from '../lib/types/waterfall'
|
||||
|
||||
/**
|
||||
* 获取随机ID
|
||||
* @param {*} length
|
||||
* @returns
|
||||
*/
|
||||
export function randomID(length = 6) {
|
||||
return Number(Math.random().toString().substr(3, length) + Date.now()).toString(36)
|
||||
}
|
||||
|
||||
const COLORS = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399']
|
||||
// const NAMES = [
|
||||
// '小当家',
|
||||
// '樱木花道',
|
||||
// '木之本樱',
|
||||
// '小可',
|
||||
// '水冰月',
|
||||
// '哆啦A梦',
|
||||
// '大雄',
|
||||
// '项少羽',
|
||||
// '天明',
|
||||
// '月儿',
|
||||
// '石兰',
|
||||
// '夏尔凡多姆海恩',
|
||||
// '塞巴斯蒂安',
|
||||
// '亚伦沃克',
|
||||
// '皮卡丘',
|
||||
// '鸣人',
|
||||
// '宇智波佐助',
|
||||
// '旗木卡卡西',
|
||||
// '喜洋洋',
|
||||
// '灰太狼',
|
||||
// '爱德华',
|
||||
// '阿冈',
|
||||
// '黑崎一护',
|
||||
// '路飞',
|
||||
// '索隆',
|
||||
// '山治',
|
||||
// '恋次',
|
||||
// '越前龙马',
|
||||
// ]
|
||||
|
||||
function getRandomNum(min: number, max: number) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||
}
|
||||
|
||||
function randomColor() {
|
||||
return COLORS[getRandomNum(0, 4)]
|
||||
}
|
||||
|
||||
// function randomName() {
|
||||
// return NAMES[getRandomNum(0, 25)]
|
||||
// }
|
||||
|
||||
// let start = 100
|
||||
// export function getList(pageSize = 10) {
|
||||
// const end = start + pageSize
|
||||
// const list: ViewCard[] = []
|
||||
// for (let i = start; i <= end; i++) {
|
||||
// const successURL = `https://api.mz-moe.cn/img/img${i}.jpg`
|
||||
// // const successURL = `https://images.weserv.nl/?url=https://api.mz-moe.cn/img/img${i}.jpg?timestamp=${Date.now()}`
|
||||
// const errorURL = 'https://api.mz-moe.cn/img/img00000.jpg'
|
||||
// list.push({
|
||||
// id: randomID(),
|
||||
// star: false,
|
||||
// src: {
|
||||
// original: Math.random() < 0.95 ? successURL : errorURL,
|
||||
// },
|
||||
// backgroundColor: randomColor(),
|
||||
// name: randomName(),
|
||||
// })
|
||||
// }
|
||||
// start = end + 1
|
||||
// return list
|
||||
// }
|
||||
|
||||
const website = 'https://www.getphotoblanket.com'
|
||||
// const website = 'https://www.getphotoblanket.com';
|
||||
|
||||
export function getList({ page = 1, pageSize = 20 }) {
|
||||
const url = `${website}/products.json?page=${page}&limit=${pageSize}`
|
||||
return fetch(url)
|
||||
.then(res => res.json())
|
||||
.then(res => res.products).then((res) => {
|
||||
return res.map((item: any) => {
|
||||
return {
|
||||
id: randomID(),
|
||||
star: false,
|
||||
price: item.variants[0].price,
|
||||
src: {
|
||||
original: Math.random() > 0.1 ? item.images[0].src : 'https://www.example.com/non-existent-image.jpg',
|
||||
// original: 'https://tq-alg-public.s3.us-west-2.amazonaws.com/kol/Seraphina_1702987997_0.png',
|
||||
},
|
||||
backgroundColor: randomColor(),
|
||||
name: item.title,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
|
||||
export interface UserSendCode {
|
||||
Phone?: string
|
||||
Email?: string
|
||||
Type: 'phone' | 'email'
|
||||
}
|
||||
|
||||
export const userApi = {
|
||||
info: () => request.get<UserInfo>('dep_person/info'),
|
||||
edit: (params: UserEdit) => request.post('dep_person/update', params),
|
||||
sendCode: (params: UserSendCode) => request.post('dep_person/send/new/info/code', params),
|
||||
qrTotpCode: () => request.get<{
|
||||
Qr: string,
|
||||
TotpSecret: string
|
||||
}>('dep_person/reload/totp'),
|
||||
bindSSO:(sso:string,isBind?:boolean)=>{
|
||||
if(!isBind){
|
||||
return request.post<{ Token: string }>(`dep_login/bindcallback/${sso}`)
|
||||
}else{
|
||||
return request.post<{ Token: string }>(`dep_login/unbindcallback/${sso}`)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
export interface UserEdit {
|
||||
NickName?: string
|
||||
/**
|
||||
* 是否清除双因素认证,1清除
|
||||
*/
|
||||
ClearTotp?: number;
|
||||
/**
|
||||
* 验证码
|
||||
*/
|
||||
Code?: string;
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
Email?: string;
|
||||
/**
|
||||
* 部门账号Id
|
||||
*/
|
||||
Id?: string;
|
||||
/**
|
||||
* 是否安全认证:1-是,2-不是
|
||||
*/
|
||||
IsSafeCheck?: number;
|
||||
/**
|
||||
* 旧密码
|
||||
*/
|
||||
OldPassword?: string;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
Password?: string;
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
Phone?: string;
|
||||
/**
|
||||
* 协议密码
|
||||
*/
|
||||
ProtocolPassword?: string;
|
||||
/**
|
||||
* 双因素认证验证码
|
||||
*/
|
||||
TotpCode?: string;
|
||||
/**
|
||||
* 双因素认证密钥
|
||||
*/
|
||||
TotpSecret?: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export interface UserInfo {
|
||||
CreateTime: number;
|
||||
Email: string;
|
||||
Id: number;
|
||||
IsAdmin: number;
|
||||
NickName: string;
|
||||
Status: number;
|
||||
UpdateTime: number;
|
||||
DepId: number;
|
||||
IsSafeCheck: number;
|
||||
IsTotp: number;
|
||||
Phone: string;
|
||||
status: number;
|
||||
IsBindIHEPEmail: number;
|
||||
IsBindBeiHang: number
|
||||
IsBindCarsiUID:number;
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
<script setup lang="ts">
|
||||
useHead({
|
||||
title: 'NuxTya',
|
||||
meta: [
|
||||
{
|
||||
name: 'NuxTya | Nuxt 3 Starter Template',
|
||||
content:
|
||||
'A Nuxt 3 starter template with TypeScript, Tailwind CSS, Shadcn-vue and Pinia.',
|
||||
},
|
||||
],
|
||||
bodyAttrs: {
|
||||
class: 'nuxt-tya',
|
||||
},
|
||||
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.svg' }],
|
||||
})
|
||||
|
||||
function setScale() {
|
||||
const designWidth = 2560// 设计稿的宽度,根据实际项目调整
|
||||
const designHeight = 1440// 设计稿的高度,根据实际项目调整
|
||||
const scale = document.documentElement.clientWidth / document.documentElement.clientHeight < designWidth / designHeight
|
||||
? (document.documentElement.clientWidth / designWidth)
|
||||
: (document.documentElement.clientHeight / designHeight)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
document.querySelector('#screen').style.transform = `scale(${scale}) translate(-50%)`
|
||||
}
|
||||
onMounted(() => {
|
||||
setScale()
|
||||
})
|
||||
window.onresize = function () {
|
||||
setScale()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="screen-wrapper">
|
||||
<div id="screen">
|
||||
<NuxtLoadingIndicator />
|
||||
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
|
||||
<Sonner />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-image: linear-gradient(145deg, #070a10 10%, #070a10 70%);
|
||||
}
|
||||
|
||||
.screen-wrapper {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.screen-wrapper #screen {
|
||||
display: inline-block;
|
||||
width: 2560px;
|
||||
height: 1440px;
|
||||
overflow: hidden;
|
||||
transform-origin: 0 0;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
}
|
||||
</style>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,154 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
/* add fonts here */
|
||||
/* @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap'); */
|
||||
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--primary: 221.2 83.2% 53.3%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border:214.3 31.8% 91.4%;
|
||||
--input:214.3 31.8% 91.4%;
|
||||
--ring:221.2 83.2% 53.3%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background:222.2 84% 4.9%;
|
||||
--foreground:210 40% 98%;
|
||||
|
||||
--card:222.2 84% 4.9%;
|
||||
--card-foreground:210 40% 98%;
|
||||
|
||||
--popover:222.2 84% 4.9%;
|
||||
--popover-foreground:210 40% 98%;
|
||||
|
||||
--primary:217.2 91.2% 59.8%;
|
||||
--primary-foreground:222.2 47.4% 11.2%;
|
||||
|
||||
--secondary:217.2 32.6% 17.5%;
|
||||
--secondary-foreground:210 40% 98%;
|
||||
|
||||
--muted:217.2 32.6% 17.5%;
|
||||
--muted-foreground:215 20.2% 65.1%;
|
||||
|
||||
--accent:217.2 32.6% 17.5%;
|
||||
--accent-foreground:210 40% 98%;
|
||||
|
||||
--destructive:0 62.8% 30.6%;
|
||||
--destructive-foreground:210 40% 98%;
|
||||
|
||||
--border:217.2 32.6% 17.5%;
|
||||
--input:217.2 32.6% 17.5%;
|
||||
--ring:224.3 76.3% 48%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
/* font-family: 'Poppins', sans-serif; */
|
||||
}
|
||||
|
||||
|
||||
.typography-h1{
|
||||
@apply text-3xl md:text-4xl font-extrabold tracking-tight scroll-m-20 lg:text-5xl dark:text-white
|
||||
}
|
||||
.typography-h2{
|
||||
@apply pb-2 text-3xl font-semibold tracking-tight transition-colors border-b scroll-m-20 first:mt-0 dark:text-white/90;
|
||||
}
|
||||
.typography-h3{
|
||||
@apply text-2xl font-semibold tracking-tight scroll-m-20 dark:text-white;
|
||||
}
|
||||
.typography-h4{
|
||||
@apply text-xl font-semibold tracking-tight scroll-m-20 dark:text-white;
|
||||
}
|
||||
.typography-p{
|
||||
@apply leading-7 [&:not(:first-child)]:mt-6 dark:text-white;
|
||||
}
|
||||
.typography-code{
|
||||
@apply relative rounded bg-gray-500/50 px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold dark:text-white/90;
|
||||
}
|
||||
.typography-lead{
|
||||
@apply text-lg opacity-50 dark:text-white;
|
||||
}
|
||||
.typography-large{
|
||||
@apply text-lg font-semibold dark:text-white;
|
||||
}
|
||||
.typography-small{
|
||||
@apply text-sm font-medium leading-none dark:text-white;
|
||||
}
|
||||
.typography-muted{
|
||||
@apply text-sm opacity-50 dark:text-white;
|
||||
}
|
||||
|
||||
|
||||
[class*=v-md-icon-], [class*=v-md-icon-]:before, [class*=v-md-icon-]:after {
|
||||
font-size: 16px;
|
||||
font-family: v-md-iconfont!important;
|
||||
font-style: normal!important;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
|
||||
.v-md-editor textarea{
|
||||
background:unset;
|
||||
}
|
||||
|
||||
.v-md-editor-preview{
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
|
||||
.overflow-y-auto,
|
||||
.overflow-x-auto,
|
||||
.overflow-auto {
|
||||
@apply scrollbar-thumb-slate-700/70 scrollbar-track-transparent scrollbar-thin;
|
||||
}
|
||||
|
||||
|
||||
.singleLine {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
/* height: 40px; */
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.singleLine span {
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
margin-top: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
<template>
|
||||
<div class="mx-auto w-fit">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class=" h-[133px] w-fit" width="2895" height="133" fill="none" viewBox="0 0 2895 133">
|
||||
<path fill="url(#a)" d="M10 10h2880v116H10z" />
|
||||
<path
|
||||
class=""
|
||||
fill="url(#b)" fill-rule="evenodd"
|
||||
d="M82.818 13.858 2.933 95.613a4 4 0 0 0 .207 5.787l14.971 13.283a4.996 4.996 0 0 0 3.452 1.258l73.13-1.953a50.003 50.003 0 0 0 24.14-6.96l11.321-6.704a51.006 51.006 0 0 1 24.887-7.104L583 84h1278.74l1008.31.5V0H95.165L82.818 13.858Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
fill="url(#c)" fill-rule="evenodd"
|
||||
d="m102.818 23.858-74.751 76.501c-4.391 4.494-4.133 11.746.567 15.916l9.477 8.408a4.996 4.996 0 0 0 3.452 1.258l73.129-1.953a50.004 50.004 0 0 0 24.141-6.96l11.321-6.704a51.011 51.011 0 0 1 24.887-7.104L603 94h1278.74l1008.31.5V10H115.165l-12.347 13.858Z"
|
||||
clip-rule="evenodd" opacity=".7"
|
||||
/>
|
||||
<path
|
||||
stroke="url(#d)"
|
||||
d="M2890.05 95h.5V9.5H114.941l-.149.167-12.339 13.85-80.61 82.497a3.5 3.5 0 0 0 .18 5.064l15.756 13.979a5.494 5.494 0 0 0 3.797 1.384l73.13-1.954a50.504 50.504 0 0 0 24.382-7.028l11.321-6.704a50.499 50.499 0 0 1 24.643-7.035L603 94.5h1278.74l1008.31.5Z"
|
||||
opacity=".488"
|
||||
/>
|
||||
<g opacity=".762">
|
||||
<mask id="f" width="2885" height="123" x="6" y="10" maskUnits="userSpaceOnUse" style="mask-type:alpha">
|
||||
<path fill="url(#e)" fill-opacity=".5" d="M6.5 10h2884v123H6.5z" />
|
||||
</mask>
|
||||
<g mask="url(#f)" class="">
|
||||
<path
|
||||
fill="url(#g)" fill-opacity=".5" fill-rule="evenodd"
|
||||
d="M102.818 23.858 37.696 90.504c-9.632 9.858-9.065 25.768 1.244 34.915.417.37.958.567 1.515.552l74.237-1.983a50.004 50.004 0 0 0 24.141-6.96l11.028-6.53a51.004 51.004 0 0 1 25.523-7.115L603.388 99.5H2890.05V10H115.165l-12.347 13.858Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
fill="url(#h)" fill-rule="evenodd"
|
||||
d="M102.818 23.858 37.696 90.504c-9.632 9.858-9.065 25.768 1.244 34.915.417.37.958.567 1.515.552l74.237-1.983a50.004 50.004 0 0 0 24.141-6.96l11.028-6.53a51.004 51.004 0 0 1 25.523-7.115L603.388 99.5H2890.05V10H115.165l-12.347 13.858Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
fill="url(#i)" fill-rule="evenodd"
|
||||
d="M102.818 23.858 37.696 90.504c-9.632 9.858-9.065 25.768 1.244 34.915.417.37.958.567 1.515.552l74.237-1.983a50.004 50.004 0 0 0 24.141-6.96l11.028-6.53a51.004 51.004 0 0 1 25.523-7.115L603.388 99.5H2890.05V10H115.165l-12.347 13.858Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
fill="url(#j)" fill-rule="evenodd"
|
||||
d="M102.818 23.858 37.696 90.504c-9.632 9.858-9.065 25.768 1.244 34.915.417.37.958.567 1.515.552l74.237-1.983a50.004 50.004 0 0 0 24.141-6.96l11.028-6.53a51.004 51.004 0 0 1 25.523-7.115L603.388 99.5H2890.05V10H115.165l-12.347 13.858Z"
|
||||
clip-rule="evenodd" style="mix-blend-mode:soft-light"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<mask id="l" width="2885" height="123" x="10" y="10" maskUnits="userSpaceOnUse" style="mask-type:alpha">
|
||||
<path fill="url(#k)" d="M10.5 10h2884v123H10.5z" />
|
||||
</mask>
|
||||
<g mask="url(#l)">
|
||||
<path
|
||||
fill="url(#m)" fill-opacity=".5" fill-rule="evenodd"
|
||||
d="m106.818 23.858-79.885 81.755a4 4 0 0 0 .207 5.787l14.971 13.283a4.996 4.996 0 0 0 3.452 1.258l73.129-1.953a50.004 50.004 0 0 0 24.141-6.96l11.028-6.53a51.004 51.004 0 0 1 25.523-7.115L607.388 99.5H2894.05V10H137.725a41.396 41.396 0 0 0-30.907 13.858Z"
|
||||
clip-rule="evenodd" opacity=".478"
|
||||
/>
|
||||
</g>
|
||||
<mask id="o" width="2885" height="123" x="10" y="10" maskUnits="userSpaceOnUse" style="mask-type:alpha">
|
||||
<path fill="url(#n)" d="M10.5 10h2884v123H10.5z" />
|
||||
</mask>
|
||||
<g mask="url(#o)">
|
||||
<path
|
||||
stroke="#DEFCFC" stroke-width="2"
|
||||
d="M134.725 9a42.396 42.396 0 0 0-31.638 14.175l-77.669 79.487c-3.193 3.268-3.005 8.543.413 11.575l12.616 11.194a5.997 5.997 0 0 0 4.143 1.51l73.129-1.954a51.007 51.007 0 0 0 24.624-7.098l11.028-6.531a50 50 0 0 1 25.022-6.975l427.995-3.883H2892.05V9H134.725Z"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="a" x1="10" x2="10" y1="10" y2="126" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#1A2531" />
|
||||
<stop offset="1" stop-color="#0B1016" stop-opacity=".01" />
|
||||
</linearGradient>
|
||||
<linearGradient id="b" x1="-530.728" x2="-530.403" y1="45.685" y2="91.411" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#526A8B" />
|
||||
<stop offset=".43" stop-color="#0D1219" stop-opacity=".785" />
|
||||
<stop offset="1" stop-color="#111821" />
|
||||
</linearGradient>
|
||||
<linearGradient id="c" x1="20" x2="20" y1="10" y2="125.994" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#171F2C" />
|
||||
<stop offset=".43" stop-color="#0D1219" stop-opacity=".785" />
|
||||
<stop offset="1" stop-color="#111821" />
|
||||
</linearGradient>
|
||||
<linearGradient id="d" x1="1990.75" x2="1986.94" y1="25.904" y2="143.261" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff" />
|
||||
<stop offset="1" stop-color="#fff" stop-opacity=".01" />
|
||||
</linearGradient>
|
||||
<linearGradient id="e" x1="1909.39" x2="1911.12" y1="152.221" y2="-13.685" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#313136" />
|
||||
<stop offset="1" stop-color="#161619" stop-opacity=".01" />
|
||||
</linearGradient>
|
||||
<linearGradient id="k" x1="1106.91" x2="1106.97" y1="133.81" y2="46.092" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#313136" />
|
||||
<stop offset="1" stop-color="#161619" stop-opacity=".01" />
|
||||
</linearGradient>
|
||||
<linearGradient id="n" x1="1057.05" x2="1057.08" y1="121.718" y2="39.194" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#313136" />
|
||||
<stop offset="1" stop-color="#161619" stop-opacity=".01" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="g" cx="0" cy="0" r="1"
|
||||
gradientTransform="matrix(-15.79932 138.77654 -5319.5869 -605.62013 188.243 45.762)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#506096" />
|
||||
<stop offset="1" stop-color="#A0A7D7" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="h" cx="0" cy="0" r="1" gradientTransform="matrix(0 156.784 -13149.4 0 716.251 10)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#94CAEB" />
|
||||
<stop offset="1" stop-color="#456182" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="i" cx="0" cy="0" r="1" gradientTransform="scale(9269.85 115.994) rotate(90 -.28 .306)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#80E0FD" />
|
||||
<stop offset="1" stop-color="#456182" stop-opacity=".01" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="j" cx="0" cy="0" r="1" gradientTransform="matrix(0 115.641 -6024.05 0 172.809 20.1)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#44F6D9" />
|
||||
<stop offset="1" stop-color="#A4FFF0" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="m" cx="0" cy="0" r="1" gradientTransform="matrix(0 132.119 -5129.91 0 192.243 45.76)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#68C3D5" />
|
||||
<stop offset="1" stop-color="#A8EDD7" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,28 @@
|
|||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
|
||||
const colorMode = useColorMode()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button variant="ghost">
|
||||
<Icon icon="radix-icons:moon" class="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
<Icon icon="radix-icons:sun" class="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||
<span class="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem @click="colorMode.preference = 'light'">
|
||||
亮色
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="colorMode.preference = 'dark'">
|
||||
暗色
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="colorMode.preference = 'system'">
|
||||
跟随系统
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
|
@ -0,0 +1,15 @@
|
|||
<template>
|
||||
<svg viewBox="0 0 506 1080" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:anim="http://www.w3.org/2000/anim" anim="" anim:transform-origin="50% 50%" anim:duration="1" anim:ease="ease-in-out">
|
||||
<g id="左侧颿¿-黑é€">
|
||||
<rect id="é€" opacity="0.973" width="506" height="1080" transform="matrix(-1 0 0 1 506 0)" fill="url(#paint0_linear_0_9847)" />
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_0_9847" x1="505.702" y1="27.5797" x2="2.1156" y2="25.5704" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0A131C" stop-opacity="0.8" />
|
||||
<stop offset="0.409839" stop-color="#0F1C28" stop-opacity="0.6" />
|
||||
<stop offset="0.679029" stop-color="#101C28" stop-opacity="0.4" />
|
||||
<stop offset="1" stop-color="#101D29" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
|
@ -0,0 +1,52 @@
|
|||
<template>
|
||||
<svg viewBox="0 0 1120 1080" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:anim="http://www.w3.org/2000/anim" anim="" anim:transform-origin="50% 50%" anim:duration="1" anim:ease="ease-in-out">
|
||||
<g id="å³ä¾§é¢æ¿-黑é€">
|
||||
<g id="编组">
|
||||
<rect id="å³ä¾§é¢æ¿-黑é€_2" opacity="0.7" x="234" y="-3" width="1255" height="1080" fill="url(#paint0_linear_0_9849)" />
|
||||
<rect id="å³ä¾§é¢æ¿-黑é€_3" opacity="0.1" x="234" y="-3" width="1255" height="1080" fill="url(#paint1_linear_0_9849)" />
|
||||
<rect id="矩形" x="399" y="-4" width="1090" height="1083" fill="url(#paint2_linear_0_9849)" />
|
||||
</g>
|
||||
<g id="编组 2">
|
||||
<path id="å³ä¾§é¢æ¿-黑é€_4" opacity="0.7" fill-rule="evenodd" clip-rule="evenodd" d="M0 1H1120V1081H0V1Z" fill="url(#paint3_linear_0_9849)" />
|
||||
<path id="å³ä¾§é¢æ¿-黑é€_5" opacity="0.1" fill-rule="evenodd" clip-rule="evenodd" d="M0 1H1120V1081H0V1Z" fill="url(#paint4_linear_0_9849)" />
|
||||
<path id="矩形_2" fill-rule="evenodd" clip-rule="evenodd" d="M76 0H1120V1083H76V0Z" fill="url(#paint5_linear_0_9849)" />
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_0_9849" x1="1494.34" y1="22.0178" x2="239.376" y2="9.59891" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0A131C" />
|
||||
<stop offset="0.886665" stop-color="#0F1C28" stop-opacity="0.8" />
|
||||
<stop offset="0.930356" stop-color="#101C28" stop-opacity="0.6" />
|
||||
<stop offset="1" stop-color="#101D29" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_0_9849" x1="1494.34" y1="22.0178" x2="239.376" y2="9.59891" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0A131C" />
|
||||
<stop offset="0.886665" stop-color="#0F1C28" stop-opacity="0.8" />
|
||||
<stop offset="0.930356" stop-color="#101C28" stop-opacity="0.6" />
|
||||
<stop offset="1" stop-color="#101D29" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_0_9849" x1="1489" y1="-3.20769" x2="400.595" y2="-3.20769" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-opacity="0.35" />
|
||||
<stop offset="0.824744" stop-opacity="0.15" />
|
||||
<stop offset="1" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_0_9849" x1="1124.77" y1="26.0178" x2="4.77502" y2="16.1268" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0A131C" />
|
||||
<stop offset="0.886665" stop-color="#0F1C28" stop-opacity="0.8" />
|
||||
<stop offset="0.930356" stop-color="#101C28" stop-opacity="0.6" />
|
||||
<stop offset="1" stop-color="#101D29" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_0_9849" x1="1124.77" y1="26.0178" x2="4.77502" y2="16.1268" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0A131C" />
|
||||
<stop offset="0.886665" stop-color="#0F1C28" stop-opacity="0.8" />
|
||||
<stop offset="0.930356" stop-color="#101C28" stop-opacity="0.6" />
|
||||
<stop offset="1" stop-color="#101D29" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear_0_9849" x1="1120" y1="0.79231" x2="77.5276" y2="0.79231" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-opacity="0.35" />
|
||||
<stop offset="0.824744" stop-opacity="0.15" />
|
||||
<stop offset="1" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
|
@ -0,0 +1,25 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
withDefaults(defineProps<{
|
||||
src?: string
|
||||
fullScreen?: boolean
|
||||
rounded?: boolean
|
||||
}>(), {
|
||||
src: '',
|
||||
fullScreen: false,
|
||||
rounded: true
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex justify-center items-center overflow-hidden"
|
||||
:class="[fullScreen ? 'h-full w-full' : 'h-[100px] w-[200px]', rounded ? 'rounded-md' : '']"
|
||||
>
|
||||
<img :src="resourceApi.resolution(src,'st3')" alt="background" class=" h-full w-full object-cover">
|
||||
<!-- <img v-else src="/null.webp" alt="background" class=" object-cover h-full w-full dark:invert opacity-25"> -->
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
|
||||
|
||||
withDefaults(defineProps<{
|
||||
src?: string
|
||||
rounded?: boolean
|
||||
shadow?: boolean
|
||||
}>(), {
|
||||
src: '',
|
||||
rounded: true,
|
||||
shadow: false
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<template>
|
||||
<div class="h-[90px] flex justify-center items-center w-[90px] overflow-hidden" :class="[rounded ? 'rounded-md' : '',shadow ? 'shadow-inner' : '',]">
|
||||
<img :src="resourceApi.resolution(src,'st1')" alt="Image" class=" object-cover w-full h-full">
|
||||
<!-- <img v-else src="/null.webp" alt="Image" class=" object-cover w-full h-full dark:invert opacity-55 dark:opacity-20"> -->
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,43 @@
|
|||
<script setup lang="ts">
|
||||
function handleBtnEvent() {
|
||||
|
||||
}
|
||||
|
||||
function handleConfirmEvent() {
|
||||
|
||||
}
|
||||
|
||||
const data = ref({
|
||||
maxDot: 5,
|
||||
imageBase64: '',
|
||||
thumbBase64: '',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPopover>
|
||||
<div class="w-full">
|
||||
<!-- 初始状态 -->
|
||||
<UButton
|
||||
icon="i-solar-shield-keyhole-bold-duotone text-blue-500"
|
||||
variant="ghost"
|
||||
color="cyan"
|
||||
label="请进行人机验证"
|
||||
:trailing="false"
|
||||
size="lg"
|
||||
block
|
||||
@click="handleBtnEvent"
|
||||
/>
|
||||
</div>
|
||||
<template #panel>
|
||||
<Captcha
|
||||
width="300px"
|
||||
height="240px"
|
||||
:max-dot="data.maxDot"
|
||||
:image-base64="data.imageBase64"
|
||||
:thumb-base64="data.thumbBase64"
|
||||
@confirm="handleConfirmEvent"
|
||||
/>
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
|
@ -0,0 +1,277 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
const {
|
||||
value,
|
||||
width = '300px',
|
||||
height = '240px',
|
||||
calcPosType = 'dom',
|
||||
maxDot = 5,
|
||||
imageBase64,
|
||||
thumbBase64,
|
||||
} = defineProps<{
|
||||
value?: boolean
|
||||
width?: string
|
||||
height?: string
|
||||
calcPosType?: string
|
||||
maxDot?: number
|
||||
imageBase64?: string
|
||||
thumbBase64?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void
|
||||
(e: 'success', value: { CaptchaCode: string }): void
|
||||
(e: 'refresh'): void
|
||||
}>()
|
||||
|
||||
|
||||
const { data, refresh } = useAsyncData(API.common.auth.capthcha)
|
||||
|
||||
const toast = useToastHandle()
|
||||
|
||||
const state = reactive({
|
||||
dots: ref<any[]>([]),
|
||||
imageBase64Code: imageBase64,
|
||||
thumbBase64Code: thumbBase64,
|
||||
style: computed(() => {
|
||||
return `width:${width}; height:${height};`
|
||||
}),
|
||||
/**
|
||||
* @Description: 处理关闭事件
|
||||
*/
|
||||
handleCloseEvent() {
|
||||
emit('close')
|
||||
state.dots = []
|
||||
state.imageBase64Code = ''
|
||||
state.thumbBase64Code = ''
|
||||
},
|
||||
/**
|
||||
* @Description: 处理刷新事件
|
||||
*/
|
||||
handleRefreshEvent() {
|
||||
state.dots = []
|
||||
refresh()
|
||||
emit('refresh')
|
||||
},
|
||||
/**
|
||||
* @Description: 处理确认事件
|
||||
*/
|
||||
async handleConfirmEvent() {
|
||||
try {
|
||||
// 验证验证码
|
||||
const res = await API.common.auth.checkCaptcha({
|
||||
UUID: data.value?.UUID,
|
||||
Check: state.dots.map(i => [i.x, i.y].join(',')).join(','),
|
||||
})
|
||||
emit('success', { CaptchaCode: res?.Code ?? '' })
|
||||
} catch (err: any) {
|
||||
toast.error(err.message)
|
||||
state.handleRefreshEvent()
|
||||
}
|
||||
|
||||
},
|
||||
/**
|
||||
* @Description: 处理dot
|
||||
* @param ev
|
||||
*/
|
||||
handleClickPos(ev: any) {
|
||||
if (state.dots.length >= maxDot)
|
||||
return
|
||||
const e = ev || window.event
|
||||
e.preventDefault()
|
||||
const dom = e.currentTarget
|
||||
const { domX, domY } = state.getDomXY(dom)
|
||||
// ===============================================
|
||||
// @notice 如 getDomXY 不准确可尝试使用 calcLocationLeft 或 calcLocationTop
|
||||
// const domX = state.calcLocationLeft(dom)
|
||||
// const domY = state.calcLocationTop(dom)
|
||||
// ===============================================
|
||||
let mouseX = navigator.appName === 'Netscape'
|
||||
? e.pageX
|
||||
: e.x + document.body.offsetTop
|
||||
let mouseY = navigator.appName === 'Netscape'
|
||||
? e.pageY
|
||||
: e.y + document.body.offsetTop
|
||||
if (calcPosType === 'screen') {
|
||||
mouseX = navigator.appName === 'Netscape' ? e.clientX : e.x
|
||||
mouseY = navigator.appName === 'Netscape' ? e.clientY : e.y
|
||||
}
|
||||
// 计算点击的相对位置
|
||||
const xPos = mouseX - domX
|
||||
const yPos = mouseY - domY
|
||||
// 转整形
|
||||
const xp = Number.parseInt(xPos.toString())
|
||||
const yp = Number.parseInt(yPos.toString())
|
||||
|
||||
// 减去点的一半
|
||||
state.dots.push({
|
||||
x: xp - 11,
|
||||
y: yp - 11,
|
||||
index: state.dots.length + 1,
|
||||
})
|
||||
|
||||
return false
|
||||
},
|
||||
/**
|
||||
* @Description: 找到元素的屏幕位置
|
||||
* @param el
|
||||
*/
|
||||
calcLocationLeft(el: any) {
|
||||
let tmp = el.offsetLeft
|
||||
let val = el.offsetParent
|
||||
while (val != null) {
|
||||
tmp += val.offsetLeft
|
||||
val = val.offsetParent
|
||||
}
|
||||
return tmp
|
||||
},
|
||||
/**
|
||||
* @Description: 找到元素的屏幕位置
|
||||
* @param el
|
||||
*/
|
||||
calcLocationTop(el: any) {
|
||||
let tmp = el.offsetTop
|
||||
let val = el.offsetParent
|
||||
while (val != null) {
|
||||
tmp += val.offsetTop
|
||||
val = val.offsetParent
|
||||
}
|
||||
return tmp
|
||||
},
|
||||
/**
|
||||
* @Description: 找到元素的屏幕位置
|
||||
* @param dom
|
||||
*/
|
||||
getDomXY(dom: any) {
|
||||
let x = 0
|
||||
let y = 0
|
||||
if (dom.getBoundingClientRect) {
|
||||
const box = dom.getBoundingClientRect()
|
||||
const D = document.documentElement
|
||||
x = box.left
|
||||
+ Math.max(D.scrollLeft, document.body.scrollLeft)
|
||||
- D.clientLeft
|
||||
y = box.top
|
||||
+ Math.max(D.scrollTop, document.body.scrollTop)
|
||||
- D.clientTop
|
||||
}
|
||||
else {
|
||||
while (dom !== document.body) {
|
||||
x += dom.offsetLeft
|
||||
y += dom.offsetTop
|
||||
dom = dom.offsetParent
|
||||
}
|
||||
}
|
||||
return {
|
||||
domX: x,
|
||||
domY: y,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
effect(() => {
|
||||
if (data.value) {
|
||||
const res = (data?.value)
|
||||
// const res = JSON.parse(data?.value)
|
||||
state.imageBase64Code = res?.BackgroundImage
|
||||
state.thumbBase64Code = res?.CheckImage
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => value, () => {
|
||||
state.dots = []
|
||||
state.imageBase64Code = ''
|
||||
state.thumbBase64Code = ''
|
||||
})
|
||||
watch(() => imageBase64, (val) => {
|
||||
state.dots = []
|
||||
state.imageBase64Code = val
|
||||
})
|
||||
watch(() => thumbBase64, (val) => {
|
||||
state.dots = []
|
||||
state.thumbBase64Code = val
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCard>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-center">
|
||||
<span>
|
||||
请在下图
|
||||
<em>依次</em>
|
||||
点击:
|
||||
</span>
|
||||
<img v-if="state.thumbBase64Code" :src="state.thumbBase64Code" alt=" ">
|
||||
</div>
|
||||
</template>
|
||||
<div class="wg-cap-wrap__body">
|
||||
<img
|
||||
v-if="state.imageBase64Code"
|
||||
class="wg-cap-wrap__picture"
|
||||
:src="state.imageBase64Code"
|
||||
alt=" "
|
||||
@click="state.handleClickPos($event)"
|
||||
>
|
||||
|
||||
<template v-for="(dot, key) in state.dots" :key="key">
|
||||
<div class="wg-cap-wrap__dot" :style="`top: ${dot.y}px; left:${dot.x}px;`">
|
||||
<span>{{ dot.index }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
<!-- <UButton icon="i-solar-close-circle-line-duotone" variant="ghost" color="gray" title="关闭" @click="state.handleCloseEvent" /> -->
|
||||
<UButton
|
||||
icon="i-solar-refresh-circle-line-duotone"
|
||||
variant="ghost"
|
||||
color="gray"
|
||||
title="刷新"
|
||||
@click="state.handleRefreshEvent"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<UButton @click="state.handleConfirmEvent">
|
||||
确认
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.wg-cap-wrap__body {
|
||||
position: relative;
|
||||
display: -webkit-box;
|
||||
display: -moz-box;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
background: #34383e;
|
||||
margin: auto;
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wg-cap-wrap__body .wg-cap-wrap__dot {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
color: #cedffe;
|
||||
background: #3e7cff;
|
||||
border: 2px solid #f7f9fb;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
-webkit-border-radius: 22px;
|
||||
-moz-border-radius: 22px;
|
||||
border-radius: 22px;
|
||||
cursor: default;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,379 @@
|
|||
<template>
|
||||
<div class="wg-cap-btn" :style="style">
|
||||
<div class="wg-cap-btn__inner" :class="activeClass">
|
||||
<!-- wg-cap-active__default wg-cap-active__error wg-cap-active__over wg-cap-active__success -->
|
||||
<template>
|
||||
<div @click="handleBtnEvent" class="wg-cap-state__default">
|
||||
<!-- 初始状态 -->
|
||||
<div class="wg-cap-state__inner">
|
||||
<div class="wg-cap-btn__ico wg-cap-btn__verify">
|
||||
<img
|
||||
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSLlm77lsYJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiCgkgdmlld0JveD0iMCAwIDIwMCAyMDAiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDIwMCAyMDA7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbDojM0U3Q0ZGO30KCS5zdDF7ZmlsbDojRkZGRkZGO30KPC9zdHlsZT4KPGNpcmNsZSBjbGFzcz0ic3QwIiBjeD0iMTAwIiBjeT0iMTAwIiByPSI5Ni4zIi8+CjxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik0xNDAuOCw2NC40bC0zOS42LTExLjloLTIuNEw1OS4yLDY0LjRjLTEuNiwwLjgtMi44LDIuNC0yLjgsNHYyNC4xYzAsMjUuMywxNS44LDQ1LjksNDIuMyw1NC42CgljMC40LDAsMC44LDAuNCwxLjIsMC40YzAuNCwwLDAuOCwwLDEuMi0wLjRjMjYuNS04LjcsNDIuMy0yOC45LDQyLjMtNTQuNlY2OC4zQzE0My41LDY2LjgsMTQyLjMsNjUuMiwxNDAuOCw2NC40eiIvPgo8L3N2Zz4K" />
|
||||
</div>
|
||||
<span class="wg-cap-btn__text">点击按键进行人机验证</span>
|
||||
</div>
|
||||
</div>
|
||||
<div @click="() => false" class="wg-cap-state__check">
|
||||
<!-- 验证状态 -->
|
||||
<div class="wg-cap-state__inner">
|
||||
<div class="wg-cap-btn__ico">
|
||||
<img
|
||||
src="data:image/svg+xml;base64,PHN2ZyB0PSIxNjI3MDU1NTg2NTk0IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjEyMTEiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNMTIwLjI1OTQ1NiA1MTIuMDAxMDIzbS0xMTcuOTIzNzYgMGExMTUuMjM4IDExNS4yMzggMCAxIDAgMjM1Ljg0NzUxOSAwIDExNS4yMzggMTE1LjIzOCAwIDEgMC0yMzUuODQ3NTE5IDBaIiBwLWlkPSIxMjEyIiBmaWxsPSIjZmZhMDAwIj48L3BhdGg+PHBhdGggZD0iTTUxMS45OTk0ODggNTEyLjAwMTAyM20tMTE3LjkyMTcxMyAwYTExNS4yMzYgMTE1LjIzNiAwIDEgMCAyMzUuODQzNDI2IDAgMTE1LjIzNiAxMTUuMjM2IDAgMSAwLTIzNS44NDM0MjYgMFoiIHAtaWQ9IjEyMTMiIGZpbGw9IiNmZmEwMDAiPjwvcGF0aD48cGF0aCBkPSJNOTAzLjczOTUyMSA1MTIuMDAxMDIzbS0xMTcuOTIzNzYgMGExMTUuMjM4IDExNS4yMzggMCAxIDAgMjM1Ljg0NzUxOSAwIDExNS4yMzggMTE1LjIzOCAwIDEgMC0yMzUuODQ3NTE5IDBaIiBwLWlkPSIxMjE0IiBmaWxsPSIjZmZhMDAwIj48L3BhdGg+PC9zdmc+"
|
||||
alt="" />
|
||||
</div>
|
||||
<span class="wg-cap-btn__text">正在进行人机验证...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div @click="handleBtnEvent" class="wg-cap-state__error">
|
||||
<!-- 验证失败状态 -->
|
||||
<div class="wg-cap-state__inner">
|
||||
<div class="wg-cap-btn__ico">
|
||||
<img
|
||||
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAyMDAgMjAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyMDAgMjAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6I0VENDYzMDt9Cjwvc3R5bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xODQsMjYuNkwxMDIuNCwyLjFoLTQuOUwxNiwyNi42Yy0zLjMsMS42LTUuNyw0LjktNS43LDguMnY0OS44YzAsNTIuMiwzMi42LDk0LjcsODcuMywxMTIuNgoJYzAuOCwwLDEuNiwwLjgsMi40LDAuOHMxLjYsMCwyLjQtMC44YzU0LjctMTgsODcuMy01OS42LDg3LjMtMTEyLjZWMzQuN0MxODkuOCwzMS41LDE4Ny4zLDI4LjIsMTg0LDI2LjZ6IE0xMzQuNSwxMjMuMQoJYzMuMSwzLjEsMy4xLDguMiwwLDExLjNjLTEuNiwxLjYtMy42LDIuMy01LjcsMi4zcy00LjEtMC44LTUuNy0yLjNMMTAwLDExMS4zbC0yMy4xLDIzLjFjLTEuNiwxLjYtMy42LDIuMy01LjcsMi4zCgljLTIsMC00LjEtMC44LTUuNy0yLjNjLTMuMS0zLjEtMy4xLTguMiwwLTExLjNMODguNywxMDBMNjUuNSw3Ni45Yy0zLjEtMy4xLTMuMS04LjIsMC0xMS4zYzMuMS0zLjEsOC4yLTMuMSwxMS4zLDBMMTAwLDg4LjcKCWwyMy4xLTIzLjFjMy4xLTMuMSw4LjItMy4xLDExLjMsMGMzLjEsMy4xLDMuMSw4LjIsMCwxMS4zTDExMS4zLDEwMEwxMzQuNSwxMjMuMXoiLz4KPC9zdmc+Cg=="
|
||||
alt="失败" />
|
||||
</div>
|
||||
<span>
|
||||
人机验证失败
|
||||
<em>点击重试</em>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div @click="handleBtnEvent" class="wg-cap-state__over">
|
||||
<!-- 验证次数过多状态 -->
|
||||
<div class="wg-cap-state__inner">
|
||||
<div class="wg-cap-btn__ico">
|
||||
<img
|
||||
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAyMDAgMjAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyMDAgMjAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6I0VENDYzMDt9Cjwvc3R5bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xODQsMjYuNkwxMDIuNCwyLjFoLTQuOUwxNiwyNi42Yy0zLjMsMS42LTUuNyw0LjktNS43LDguMnY0OS44YzAsNTIuMiwzMi42LDk0LjcsODcuMywxMTIuNgoJYzAuOCwwLDEuNiwwLjgsMi40LDAuOHMxLjYsMCwyLjQtMC44YzU0LjctMTgsODcuMy01OS42LDg3LjMtMTEyLjZWMzQuN0MxODkuOCwzMS41LDE4Ny4zLDI4LjIsMTg0LDI2LjZ6IE0xMzQuNSwxMjMuMQoJYzMuMSwzLjEsMy4xLDguMiwwLDExLjNjLTEuNiwxLjYtMy42LDIuMy01LjcsMi4zcy00LjEtMC44LTUuNy0yLjNMMTAwLDExMS4zbC0yMy4xLDIzLjFjLTEuNiwxLjYtMy42LDIuMy01LjcsMi4zCgljLTIsMC00LjEtMC44LTUuNy0yLjNjLTMuMS0zLjEtMy4xLTguMiwwLTExLjNMODguNywxMDBMNjUuNSw3Ni45Yy0zLjEtMy4xLTMuMS04LjIsMC0xMS4zYzMuMS0zLjEsOC4yLTMuMSwxMS4zLDBMMTAwLDg4LjcKCWwyMy4xLTIzLjFjMy4xLTMuMSw4LjItMy4xLDExLjMsMGMzLjEsMy4xLDMuMSw4LjIsMCwxMS4zTDExMS4zLDEwMEwxMzQuNSwxMjMuMXoiLz4KPC9zdmc+Cg=="
|
||||
alt="失败" />
|
||||
</div>
|
||||
<span>
|
||||
点击次数过多
|
||||
<em>点击重试</em>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div @click="() => false" class="wg-cap-state__success">
|
||||
<!-- 验证成功状态 -->
|
||||
<div class="wg-cap-state__inner">
|
||||
<div class="wg-cap-btn__ico">
|
||||
<img
|
||||
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAyMDAgMjAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyMDAgMjAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6IzVFQUEyRjt9Cjwvc3R5bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xODMuMywyNy4yTDEwMi40LDIuOWgtNC45TDE2LjcsMjcuMkMxMy40LDI4LjgsMTEsMzIsMTEsMzUuM3Y0OS40YzAsNTEuOCwzMi40LDkzLjksODYuNiwxMTEuNwoJYzAuOCwwLDEuNiwwLjgsMi40LDAuOGMwLjgsMCwxLjYsMCwyLjQtMC44YzU0LjItMTcuOCw4Ni42LTU5LjEsODYuNi0xMTEuN1YzNS4zQzE4OSwzMiwxODYuNiwyOC44LDE4My4zLDI3LjJ6IE0xNDYuMSw4MS40CglsLTQ4LjUsNDguNWMtMS42LDEuNi0zLjIsMi40LTUuNywyLjRjLTIuNCwwLTQtMC44LTUuNy0yLjRMNjIsMTA1LjdjLTMuMi0zLjItMy4yLTguMSwwLTExLjNjMy4yLTMuMiw4LjEtMy4yLDExLjMsMGwxOC42LDE4LjYKCWw0Mi45LTQyLjljMy4yLTMuMiw4LjEtMy4yLDExLjMsMEMxNDkuNCw3My4zLDE0OS40LDc4LjIsMTQ2LjEsODEuNEwxNDYuMSw4MS40eiIvPgo8L3N2Zz4K"
|
||||
alt="成功" />
|
||||
</div>
|
||||
<span>人机验证已通过</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-dialog v-model:visible="popoverVisible" :close-on-click-modal="false" append-to-body :center="true" title="人机校验"
|
||||
:show-close="false" z-index="999999" width="360px">
|
||||
<CommonCaptcha v-model="popoverVisible" width="300px" height="240px" :max-dot="maxDot" :image-base64="imageBase64"
|
||||
:thumb-base64="thumbBase64" @close="handleCloseEvent" @refresh="handleRefreshEvent"
|
||||
@confirm="handleConfirmEvent" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CommonCaptcha from './captcha.vue'
|
||||
import { defineComponent, watch, toRefs, computed, ref, reactive } from 'vue'
|
||||
export default defineComponent({
|
||||
name: 'CommonCaptchaDialog',
|
||||
components: { CommonCaptcha },
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
validator: value =>
|
||||
['default', 'check', 'error', 'over', 'success'].indexOf(value) > -1,
|
||||
},
|
||||
width: String,
|
||||
height: String,
|
||||
maxDot: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
},
|
||||
imageBase64: String,
|
||||
thumbBase64: String,
|
||||
},
|
||||
setup (props, { emit }) {
|
||||
const state = reactive({
|
||||
popoverVisible: ref(false),
|
||||
captStatus: ref('default'),
|
||||
activeClass: computed(() => {
|
||||
return `wg-cap-active__${state.captStatus}`
|
||||
}),
|
||||
style: computed(() => {
|
||||
return `width:${props.width}; height:${props.height};`
|
||||
}),
|
||||
handleBtnEvent () {
|
||||
setTimeout(() => {
|
||||
state.popoverVisible = true
|
||||
}, 0)
|
||||
},
|
||||
handleRefreshEvent () {
|
||||
state.captStatus = 'check'
|
||||
emit('refresh')
|
||||
},
|
||||
handleConfirmEvent (data) {
|
||||
emit('confirm', data)
|
||||
},
|
||||
handleCloseEvent () {
|
||||
state.popoverVisible = false
|
||||
},
|
||||
})
|
||||
|
||||
watch(
|
||||
() => state.popoverVisible,
|
||||
v => {
|
||||
if (v) {
|
||||
state.captStatus = 'check'
|
||||
emit('refresh')
|
||||
} else if (state.captStatus === 'check') {
|
||||
state.captStatus = props.value
|
||||
}
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => props.value,
|
||||
val => {
|
||||
if (state.captStatus !== 'check') {
|
||||
state.captStatus = val
|
||||
}
|
||||
if (val === 'over' || val === 'success') {
|
||||
setTimeout(() => {
|
||||
state.popoverVisible = false
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => state.captStatus,
|
||||
val => {
|
||||
if (val !== 'check' && props.value !== val) {
|
||||
emit('input', val)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wg-cap-btn {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.wg-cap-btn .wg-cap-btn__inner {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
position: relative;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.wg-cap-btn .wg-cap-state__default,
|
||||
.wg-cap-btn .wg-cap-state__check,
|
||||
.wg-cap-btn .wg-cap-state__error,
|
||||
.wg-cap-btn .wg-cap-state__success,
|
||||
.wg-cap-btn .wg-cap-state__over {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
font-size: 13px;
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
margin: 0;
|
||||
transition: 0.1s;
|
||||
font-weight: 500;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.wg-cap-btn .wg-cap-state__default {
|
||||
color: #3e7cff;
|
||||
border: 1px solid #50a1ff;
|
||||
background: #ecf5ff;
|
||||
box-shadow: 0 0 20px rgba(62, 124, 255, 0.1);
|
||||
-webkit-box-shadow: 0 0 20px rgba(62, 124, 255, 0.1);
|
||||
-moz-box-shadow: 0 0 20px rgba(62, 124, 255, 0.1);
|
||||
}
|
||||
|
||||
.wg-cap-btn .wg-cap-state__check {
|
||||
cursor: default;
|
||||
color: #ffa000;
|
||||
background: #fdf6ec;
|
||||
border: 1px solid #ffbe09;
|
||||
}
|
||||
|
||||
.wg-cap-btn .wg-cap-state__error {
|
||||
color: #ed4630;
|
||||
background: #fef0f0;
|
||||
border: 1px solid #ff5a34;
|
||||
}
|
||||
|
||||
.wg-cap-btn .wg-cap-state__over {
|
||||
color: #ed4630;
|
||||
background: #fef0f0;
|
||||
border: 1px solid #ff5a34;
|
||||
}
|
||||
|
||||
.wg-cap-btn .wg-cap-state__success {
|
||||
color: #5eaa2f;
|
||||
background: #f0f9eb;
|
||||
border: 1px solid #8bc640;
|
||||
}
|
||||
|
||||
.wg-cap-btn .wg-cap-active__default .wg-cap-state__default,
|
||||
.wg-cap-btn .wg-cap-active__error .wg-cap-state__error,
|
||||
.wg-cap-btn .wg-cap-active__over .wg-cap-state__over,
|
||||
.wg-cap-btn .wg-cap-active__success .wg-cap-state__success,
|
||||
.wg-cap-btn .wg-cap-active__check .wg-cap-state__check {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.wg-cap-btn .wg-cap-state__inner {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.wg-cap-btn .wg-cap-state__inner em {
|
||||
padding-left: 5px;
|
||||
color: #3e7cff;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.wg-cap-btn .wg-cap-btn__inner .wg-cap-btn__ico {
|
||||
position: relative;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 12px;
|
||||
font-size: 14px;
|
||||
display: inline-block;
|
||||
/*float: left;*/
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
.wg-cap-btn .wg-cap-btn__inner .wg-cap-btn__ico img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
float: left;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@keyframes ripple {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
5% {
|
||||
opacity: 0.05;
|
||||
}
|
||||
|
||||
20% {
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
65% {
|
||||
opacity: 0.01;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scaleX(2) scaleY(2);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes ripple {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
5% {
|
||||
opacity: 0.05;
|
||||
}
|
||||
|
||||
20% {
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
65% {
|
||||
opacity: 0.01;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scaleX(2) scaleY(2);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.wg-cap-btn .wg-cap-btn__inner .wg-cap-btn__verify::after {
|
||||
background: #409eff;
|
||||
-webkit-border-radius: 50px;
|
||||
-moz-border-radius: 50px;
|
||||
border-radius: 50px;
|
||||
content: '';
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 9;
|
||||
|
||||
animation: ripple 1.3s infinite;
|
||||
-moz-animation: ripple 1.3s infinite;
|
||||
-webkit-animation: ripple 1.3s infinite;
|
||||
animation-delay: 2s;
|
||||
-moz-animation-delay: 2s;
|
||||
-webkit-animation-delay: 2s;
|
||||
}
|
||||
|
||||
.wg-cap-tip {
|
||||
padding: 50px 20px 100px;
|
||||
font-size: 13px;
|
||||
color: #76839b;
|
||||
text-align: center;
|
||||
line-height: 180%;
|
||||
width: 100%;
|
||||
max-width: 680px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,57 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
defineProps<{
|
||||
loading?: boolean
|
||||
}>()
|
||||
|
||||
const open = ref(false)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'verify', value: (res: boolean) => void): void
|
||||
(e: 'success', value: (res: boolean) => void): void
|
||||
}>()
|
||||
|
||||
const { counter, reset, pause, resume } = useInterval(1000, { controls: true, immediate: false })
|
||||
|
||||
const count = computed(() => {
|
||||
const res = 60 - counter.value
|
||||
if (res === 0) {
|
||||
pause()
|
||||
reset()
|
||||
return ''
|
||||
}
|
||||
if (res === 60) {
|
||||
return ''
|
||||
}
|
||||
return `(${res})`
|
||||
})
|
||||
|
||||
const verify = () => {
|
||||
emit('verify', (res) => {
|
||||
if (res) {
|
||||
success()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const success = () => {
|
||||
open.value = false
|
||||
emit('success', (res) => {
|
||||
if (res) {
|
||||
resume()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- <UPopover v-model:open="open" class="h-full" :popper="{ placement: 'top-end' }"> -->
|
||||
<Button variant="secondary" :disabled="counter > 0" :loading="loading" @click.prevent="verify">
|
||||
发送验证码{{ count }}
|
||||
</Button>
|
||||
<!-- <template #panel>
|
||||
<Captcha width="300px" height="240px" :max-dot="5" @success="success" />
|
||||
</template>
|
||||
</UPopover> -->
|
||||
</template>
|
|
@ -0,0 +1,42 @@
|
|||
<script setup lang="ts">
|
||||
import { type EChartsOption, type EChartsType, init, registerMap } from 'echarts'
|
||||
|
||||
import china from '@/assets/china.json'
|
||||
import 'echarts-gl'
|
||||
|
||||
const props = defineProps<{
|
||||
options: EChartsOption
|
||||
}>()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
registerMap('china', china)
|
||||
|
||||
const chartEle = shallowRef<HTMLDivElement>()
|
||||
const chart = shallowRef<EChartsType>()
|
||||
|
||||
let listener: any
|
||||
|
||||
onMounted(() => {
|
||||
chart.value = init(chartEle.value!)
|
||||
|
||||
listener = window.addEventListener('resize', () => {
|
||||
chart.value?.resize()
|
||||
})
|
||||
})
|
||||
|
||||
effect(() => {
|
||||
console.log('chart options update', props.options)
|
||||
props.options && chart.value?.setOption(props.options)
|
||||
})
|
||||
|
||||
onDeactivated(() => {
|
||||
window.removeEventListener('resize', listener)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="height:100%">
|
||||
<div ref="chartEle" style="height: 100%;" />
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,46 @@
|
|||
<script setup lang="ts">
|
||||
import colors from '#tailwind-config/theme/colors'
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
// Computed
|
||||
|
||||
const primaryColors = computed(() => appConfig.ui.colors.filter(color => color !== 'primary').map(color => ({ value: color, text: color, hex: colors[color][colorMode.value === 'dark' ? 400 : 500] })))
|
||||
const primary = computed({
|
||||
get() {
|
||||
return primaryColors.value.find(option => option.value === appConfig.ui.primary)
|
||||
},
|
||||
set(option) {
|
||||
appConfig.ui.primary = option?.value?? 'orange'
|
||||
|
||||
window.localStorage.setItem('nuxt-ui-primary', appConfig.ui.primary)
|
||||
},
|
||||
})
|
||||
|
||||
const grayColors = computed(() => ['slate', 'cool', 'zinc', 'neutral', 'stone'].map(color => ({ value: color, text: color, hex: colors[color][colorMode.value === 'dark' ? 400 : 500] })))
|
||||
const gray = computed({
|
||||
get() {
|
||||
return grayColors.value.find(option => option.value === appConfig.ui.gray)
|
||||
},
|
||||
set(option) {
|
||||
appConfig.ui.gray = option?.value ?? 'slate'
|
||||
|
||||
window.localStorage.setItem('nuxt-ui-gray', appConfig.ui.gray)
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<div class="grid grid-cols-5 gap-px">
|
||||
<ColorPickerPill v-for="color in primaryColors" :key="color.value" :color="color" :selected="primary" @select="primary = color" />
|
||||
</div>
|
||||
|
||||
<hr class="my-2 border-gray-200 dark:border-gray-800">
|
||||
|
||||
<div class="grid grid-cols-5 gap-px">
|
||||
<ColorPickerPill v-for="color in grayColors" :key="color.value" :color="color" :selected="gray" @select="gray = color" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,29 @@
|
|||
<script setup lang="ts">
|
||||
defineProps<{ color: { value: string, hex: string }, selected?: {
|
||||
value: string;
|
||||
text: string;
|
||||
hex: any;
|
||||
} }>()
|
||||
defineEmits(['select'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTooltip :text="color.value" class="capitalize" :open-delay="500">
|
||||
<UButton
|
||||
color="white"
|
||||
square
|
||||
:ui="{
|
||||
color: {
|
||||
white: {
|
||||
solid: 'ring-0 bg-gray-100 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-800',
|
||||
ghost: 'hover:bg-gray-50 dark:hover:bg-gray-800/50',
|
||||
},
|
||||
},
|
||||
}"
|
||||
:variant="color.value === selected?.value ? 'solid' : 'ghost'"
|
||||
@click.stop.prevent="$emit('select')"
|
||||
>
|
||||
<span class="inline-block h-3 w-3 rounded-full" :style="{ backgroundColor: color.hex }" />
|
||||
</UButton>
|
||||
</UTooltip>
|
||||
</template>
|
|
@ -0,0 +1,56 @@
|
|||
<script setup lang="ts">
|
||||
import colors from '#tailwind-config/theme/colors'
|
||||
|
||||
const appConfig = useAppConfig()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
// Computed
|
||||
|
||||
const primaryColors = computed(() => appConfig.ui.colors.filter(color => color !== 'primary').map(color => ({ value: color, text: color, hex: colors[color][colorMode.value === 'dark' ? 400 : 500] })))
|
||||
const primary = computed({
|
||||
get() {
|
||||
return primaryColors.value.find(option => option.value === appConfig.ui.primary)
|
||||
},
|
||||
set(option) {
|
||||
appConfig.ui.primary = option?.value?? 'orange'
|
||||
|
||||
window.localStorage.setItem('nuxt-ui-primary', appConfig.ui.primary)
|
||||
},
|
||||
})
|
||||
|
||||
const grayColors = computed(() => ['slate', 'cool', 'zinc', 'neutral', 'stone'].map(color => ({ value: color, text: color, hex: colors[color][colorMode.value === 'dark' ? 400 : 500] })))
|
||||
const gray = computed({
|
||||
get() {
|
||||
return grayColors.value.find(option => option.value === appConfig.ui.gray)
|
||||
},
|
||||
set(option) {
|
||||
appConfig.ui.gray = option?.value ?? 'slate'
|
||||
|
||||
window.localStorage.setItem('nuxt-ui-gray', appConfig.ui.gray)
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPopover mode="hover" :popper="{ strategy: 'absolute' }" :ui="{ width: 'w-[156px]' }">
|
||||
<template #default="{ open }">
|
||||
<UButton color="gray" variant="ghost" square :class="[open && 'bg-gray-50 dark:bg-gray-800']" aria-label="Color picker">
|
||||
<UIcon name="i-heroicons-swatch-20-solid" class="text-primary-500 dark:text-primary-400 h-5 w-5" />
|
||||
</UButton>
|
||||
</template>
|
||||
|
||||
<template #panel>
|
||||
<div class="p-2">
|
||||
<div class="grid grid-cols-5 gap-px">
|
||||
<ColorPickerPill v-for="color in primaryColors" :key="color.value" :color="color" :selected="primary" @select="primary = color" />
|
||||
</div>
|
||||
|
||||
<hr class="my-2 border-gray-200 dark:border-gray-800">
|
||||
|
||||
<div class="grid grid-cols-5 gap-px">
|
||||
<ColorPickerPill v-for="color in grayColors" :key="color.value" :color="color" :selected="gray" @select="gray = color" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
|
@ -0,0 +1,60 @@
|
|||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
title: string
|
||||
value: number
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-[77px] relative w-[140px]">
|
||||
<div class="w-full text-center text-[#E4F3FF] text-[14px] scale-95 opacity-70">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="bottom-0 h-[50px] flex justify-center items-center w-full absolute text-[24px] text-[#00F8F4]">
|
||||
{{ value }}
|
||||
</div>
|
||||
<svg
|
||||
class="absolute bottom-0" viewBox="0 0 140 75" fill="none" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:anim="http://www.w3.org/2000/anim" anim="" anim:transform-origin="50% 50%" anim:duration="1"
|
||||
anim:ease="ease-in-out"
|
||||
>
|
||||
<g id="1" clip-path="url(#clip0_0_11080)">
|
||||
<path
|
||||
id="BG" opacity="0.39908" fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M3 33.1563L9.02438 25H129.615L137.242 33.1563L137.36 66L129.615 74.3253L9.02438 74.0097L3 66L3 33.1563Z"
|
||||
fill="url(#paint0_radial_0_11080)"
|
||||
/>
|
||||
<path id="路径 41" opacity="0.245359" d="M6.91697 70.52H133.553" stroke="white" stroke-width="0.6" />
|
||||
<path
|
||||
id="路径 31" opacity="0.4" d="M3.23047 32.6844L9.25485 24.6747H129.846L137.591 32.6844"
|
||||
stroke="white"
|
||||
/>
|
||||
<path
|
||||
id="路径 31_2" opacity="0.4" d="M3.23047 65.6747L9.25485 73.6844H129.846L137.591 65.6747"
|
||||
stroke="white"
|
||||
/>
|
||||
<rect id="矩形" y="48" width="4.94438" height="2.7" rx="1" fill="#2EF9CB" />
|
||||
<rect id="矩形_2" x="135" y="48" width="4.94438" height="2.7" rx="1" fill="#2EF9CB" />
|
||||
|
||||
<g id="四边">
|
||||
<path id="路径 32" d="M3.23047 32.831L9.25485 24.6747H15.5446" stroke="#BFE2FB" />
|
||||
<path id="路径 32_2" d="M136.544 32.831L130.52 24.6747H124.23" stroke="#BFE2FB" />
|
||||
<path id="路径 32_3" d="M3.23047 65.6747L9.25485 73.831H15.5446" stroke="#BFE2FB" />
|
||||
<path id="路径 32_4" d="M136.544 65.6747L130.52 73.831H124.23" stroke="#BFE2FB" />
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<radialGradient
|
||||
id="paint0_radial_0_11080" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(66.7454 74.3253) rotate(91.4485) scale(67.9372 416.887)"
|
||||
>
|
||||
<stop stop-color="#67A4E1" />
|
||||
<stop offset="1" stop-color="#67A4E1" stop-opacity="0.01" />
|
||||
</radialGradient>
|
||||
<clipPath id="clip0_0_11080">
|
||||
<rect width="139.944" height="74.6747" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,60 @@
|
|||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
title: string
|
||||
value: number
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-[77px] relative w-[140px]">
|
||||
<div class="w-full text-center text-[#E4F3FF] text-[14px] scale-95 opacity-70">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="bottom-0 h-[50px] flex justify-center items-center w-full absolute text-[24px] text-[#00F8F4]">
|
||||
{{ value }}
|
||||
</div>
|
||||
<svg
|
||||
class="absolute bottom-0" viewBox="0 0 140 75" fill="none" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:anim="http://www.w3.org/2000/anim" anim="" anim:transform-origin="50% 50%" anim:duration="1"
|
||||
anim:ease="ease-in-out"
|
||||
>
|
||||
<g id="1" clip-path="url(#clip0_0_11080)">
|
||||
<path
|
||||
id="BG" opacity="0.39908" fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M3 33.1563L9.02438 25H129.615L137.242 33.1563L137.36 66L129.615 74.3253L9.02438 74.0097L3 66L3 33.1563Z"
|
||||
fill="url(#paint0_radial_0_11080)"
|
||||
/>
|
||||
<path id="路径 41" opacity="0.245359" d="M6.91697 70.52H133.553" stroke="white" stroke-width="0.6" />
|
||||
<path
|
||||
id="路径 31" opacity="0.4" d="M3.23047 32.6844L9.25485 24.6747H129.846L137.591 32.6844"
|
||||
stroke="white"
|
||||
/>
|
||||
<path
|
||||
id="路径 31_2" opacity="0.4" d="M3.23047 65.6747L9.25485 73.6844H129.846L137.591 65.6747"
|
||||
stroke="white"
|
||||
/>
|
||||
<rect id="矩形" y="48" width="4.94438" height="2.7" rx="1" fill="#2EF9CB" />
|
||||
<rect id="矩形_2" x="135" y="48" width="4.94438" height="2.7" rx="1" fill="#2EF9CB" />
|
||||
|
||||
<g id="四边">
|
||||
<path id="路径 32" d="M3.23047 32.831L9.25485 24.6747H15.5446" stroke="#BFE2FB" />
|
||||
<path id="路径 32_2" d="M136.544 32.831L130.52 24.6747H124.23" stroke="#BFE2FB" />
|
||||
<path id="路径 32_3" d="M3.23047 65.6747L9.25485 73.831H15.5446" stroke="#BFE2FB" />
|
||||
<path id="路径 32_4" d="M136.544 65.6747L130.52 73.831H124.23" stroke="#BFE2FB" />
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<radialGradient
|
||||
id="paint0_radial_0_11080" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(66.7454 74.3253) rotate(91.4485) scale(67.9372 416.887)"
|
||||
>
|
||||
<stop stop-color="#67A4E1" />
|
||||
<stop offset="1" stop-color="#67A4E1" stop-opacity="0.01" />
|
||||
</radialGradient>
|
||||
<clipPath id="clip0_0_11080">
|
||||
<rect width="139.944" height="74.6747" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,211 @@
|
|||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
title: string
|
||||
value: number
|
||||
unit: string
|
||||
icon: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class=" w-[196px] h-[75px] relative">
|
||||
<div class="absolute top-0 left-20 opacity-60 text-[14px]">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="absolute top-6 left-[88px] flex justify-center items-center gap-1">
|
||||
<span class=" text-[22px]">{{ value }}</span>
|
||||
<span class="opacity-60 text-[14px]">{{ unit }}</span>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<Iconify :icon="icon" class=" w-[32px] h-[32px] absolute top-[12px] left-[12px]" />
|
||||
</div>
|
||||
<svg
|
||||
viewBox="0 0 196 75" fill="none" class="absolute top-0 left-0 -z-[1]" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:anim="http://www.w3.org/2000/anim" anim="" anim:transform-origin="50% 50%" anim:duration="1"
|
||||
anim:ease="ease-in-out"
|
||||
>
|
||||
<g id="1" clip-path="url(#clip0_0_11033)">
|
||||
|
||||
<path
|
||||
id="矩形"
|
||||
d="M75.5131 27.0529H188.544L173.645 57.7836H60.9809C59.8423 57.7836 59.1188 56.5649 59.6641 55.5653L75.0742 27.3134C75.1618 27.1528 75.3302 27.0529 75.5131 27.0529Z"
|
||||
fill="url(#paint0_radial_0_11033)" fill-opacity="0.2" stroke="url(#paint1_linear_0_11033)"
|
||||
/>
|
||||
<path
|
||||
id="å¡«å……" fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M80.4048 24.1894C80.58 23.8681 80.9167 23.6682 81.2827 23.6682H195.112L179.728 55.399H66.7504C65.2323 55.399 64.2677 53.774 64.9946 52.4413L80.4048 24.1894Z"
|
||||
fill="url(#paint2_linear_0_11033)" fill-opacity="0.5"
|
||||
/>
|
||||
<g id="倒影">
|
||||
<mask
|
||||
id="mask0_0_11033" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="59" y="58" width="106"
|
||||
height="17"
|
||||
>
|
||||
<path
|
||||
id="矩形_2" fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M59.5352 58.2836H164.343V74.6298H59.5352V58.2836Z" fill="url(#paint3_linear_0_11033)"
|
||||
/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_0_11033)">
|
||||
<path
|
||||
id="å¡«å……_2" fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M76.5586 88.5317C76.7338 88.853 77.0705 89.0529 77.4365 89.0529H191.266L175.881 57.3221H62.9042C61.3861 57.3221 60.4215 58.947 61.1484 60.2798L76.5586 88.5317Z"
|
||||
fill="url(#paint4_linear_0_11033)" fill-opacity="0.5"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
id="边框"
|
||||
d="M81.2827 24.1682H194.314L179.414 54.899H66.7504C65.6118 54.899 64.8884 53.6803 65.4336 52.6807L80.8437 24.4288C80.9313 24.2682 81.0997 24.1682 81.2827 24.1682Z"
|
||||
stroke="url(#paint5_linear_0_11033)"
|
||||
/>
|
||||
<g id="编组 4">
|
||||
<g id="座">
|
||||
<g id="底部模糊" opacity="0.87" filter="url(#filter0_f_0_11033)">
|
||||
<path
|
||||
fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M27.9334 39.7012C28.2471 39.5159 28.6367 39.5159 28.9504 39.7012L45.243 49.3224C46.5537 50.0964 46.5537 51.9927 45.243 52.7667L28.9504 62.3879C28.6367 62.5731 28.2471 62.5732 27.9334 62.3879L11.6408 52.7667C10.3301 51.9927 10.3301 50.0964 11.6408 49.3224L27.9334 39.7012Z"
|
||||
fill="url(#paint6_linear_0_11033)"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
id="深层" fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M28.0706 28.5854C28.3843 28.4001 28.7739 28.4001 29.0876 28.5854L53.8416 43.2032C55.1523 43.9772 55.1523 45.8735 53.8416 46.6475L29.0876 61.2654C28.7739 61.4506 28.3843 61.4506 28.0706 61.2654L3.31665 46.6475C2.00594 45.8735 2.00594 43.9772 3.31665 43.2032L28.0706 28.5854Z"
|
||||
fill="url(#paint7_linear_0_11033)" fill-opacity="0.6"
|
||||
/>
|
||||
<path
|
||||
id="基层"
|
||||
d="M28.935 23.0745L53.689 37.6923C54.8031 38.3502 54.8031 39.9621 53.689 40.62L28.935 55.2378C28.7155 55.3675 28.4427 55.3675 28.2232 55.2378L3.4692 40.62C2.3551 39.9621 2.35509 38.3502 3.4692 37.6923L28.2232 23.0745C28.4427 22.9448 28.7155 22.9448 28.935 23.0745Z"
|
||||
fill="url(#paint8_radial_0_11033)" stroke="url(#paint9_linear_0_11033)" stroke-width="0.6"
|
||||
/>
|
||||
</g>
|
||||
<g id="æ¤åœ†å½¢" filter="url(#filter1_df_0_11033)">
|
||||
<ellipse
|
||||
cx="27.9068" cy="36.0023" rx="11.1538" ry="7.88462" fill="url(#paint10_linear_0_11033)"
|
||||
fill-opacity="0.4"
|
||||
/>
|
||||
</g>
|
||||
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_f_0_11033" x="-5.65198" y="23.2526" width="68.1877" height="55.584"
|
||||
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation="8.15485" result="effect1_foregroundBlur_0_11033" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_df_0_11033" x="11.7529" y="25.3994" width="30.3076" height="27.4875"
|
||||
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dx="-1" dy="5" />
|
||||
<feGaussianBlur stdDeviation="2" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.0700061 0 0 0 0 0.128382 0 0 0 0 0.238646 0 0 0 0.5 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_0_11033" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_0_11033" result="shape" />
|
||||
<feGaussianBlur stdDeviation="1.35914" result="effect2_foregroundBlur_0_11033" />
|
||||
</filter>
|
||||
<radialGradient
|
||||
id="paint0_radial_0_11033" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(66.8013 53.1895) rotate(90) scale(14.2635 86.1863)"
|
||||
>
|
||||
<stop stop-color="#466C90" />
|
||||
<stop offset="1" stop-color="#183F64" stop-opacity="0.01" />
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_0_11033" x1="106.274" y1="66.5687" x2="107.89" y2="42.6304"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="white" stop-opacity="0.08" />
|
||||
<stop offset="0.49066" stop-color="white" stop-opacity="0.3" />
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_0_11033" x1="37.4551" y1="6.637" x2="26.2427" y2="80.4089"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#031518" />
|
||||
<stop offset="0.404838" stop-color="#446D9C" />
|
||||
<stop offset="1" stop-color="#2E6376" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint3_linear_0_11033" x1="77.816" y1="58.2836" x2="77.816" y2="68.9275"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#EEEEEE" />
|
||||
<stop offset="1" stop-color="#D8D8D8" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint4_linear_0_11033" x1="33.6089" y1="106.084" x2="22.3965" y2="32.3122"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#031518" />
|
||||
<stop offset="0.404838" stop-color="#446D9C" />
|
||||
<stop offset="1" stop-color="#2E6376" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint5_linear_0_11033" x1="199.904" y1="46.5626" x2="196.423" y2="10.3945"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="white" stop-opacity="0.01" />
|
||||
<stop offset="0.830588" stop-color="white" stop-opacity="0.3" />
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint6_linear_0_11033" x1="19.3075" y1="55.1144" x2="12.6648" y2="73.8499"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#6AD4D1" />
|
||||
<stop offset="1" stop-color="#5E96B8" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint7_linear_0_11033" x1="15.5249" y1="50.7417" x2="6.03154" y2="77.5172"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#1B2D4F" />
|
||||
<stop offset="1" stop-color="#1B2D4F" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="paint8_radial_0_11033" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(28.9127 49.7191) rotate(109.522) scale(28.4087)"
|
||||
>
|
||||
<stop stop-color="#67A4E1" />
|
||||
<stop offset="1" stop-color="#67A4E1" stop-opacity="0.01" />
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
id="paint9_linear_0_11033" x1="12.0455" y1="23.1229" x2="12.8914" y2="54.368"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#DDFFFF" stop-opacity="0.01" />
|
||||
<stop offset="1" stop-color="#DDFFFF" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint10_linear_0_11033" x1="16.7529" y1="28.1177" x2="16.7529" y2="43.8869"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#DDFFFF" stop-opacity="0.01" />
|
||||
<stop offset="1" stop-color="#80BAF3" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint11_linear_0_11033" x1="12.0304" y1="10.0516" x2="12.0304" y2="37.1307"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="white" />
|
||||
<stop offset="1" stop-color="#6AF6FA" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_0_11033">
|
||||
<rect width="194.712" height="74.0144" fill="white" transform="translate(0.400391 0.615356)" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,102 @@
|
|||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
title: string
|
||||
value: number
|
||||
icon: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-[257px] h-[89px] relative">
|
||||
<div class="flex w-full h-full">
|
||||
<div class="flex justify-center items-center w-[82px] h-full">
|
||||
<Iconify :icon="icon" class="w-[50px] h-[50px]" />
|
||||
</div>
|
||||
<div class="flex flex-col items-start pl-[20px] justify-center flex-1 w-full h-full">
|
||||
<div class="text-[#00F8F4] text-[34px] inline-block align-baseline">
|
||||
{{ value }}
|
||||
</div>
|
||||
<div class="text-[#A5D8FC] text-[16px] -mt-3 pl-1">
|
||||
{{ title }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<svg viewBox="0 0 257 89" fill="none" class="absolute top-0 left-0 -z-[1]" xmlns="http://www.w3.org/2000/svg" xmlns:anim="http://www.w3.org/2000/anim" anim="" anim:transform-origin="50% 50%" anim:duration="1" anim:ease="ease-in-out">
|
||||
<g id="box1">
|
||||
<g id="å¡«å……">
|
||||
<g filter="url(#filter0_i_0_11197)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H257V88H0V0Z" fill="#263343" fill-opacity="0.5" />
|
||||
</g>
|
||||
<path d="M0.5 0.5H256.5V87.5H0.5V0.5Z" stroke="#9DA3AF" stroke-opacity="0.5" />
|
||||
</g>
|
||||
<g id="å¡«å……_2" opacity="0.2">
|
||||
<rect width="82" height="88" fill="#577B95" fill-opacity="0.75" />
|
||||
<rect width="82" height="88" fill="url(#paint0_linear_0_11197)" />
|
||||
</g>
|
||||
<g id="高光" opacity="0.45">
|
||||
<rect width="82" height="88" fill="url(#paint1_radial_0_11197)" />
|
||||
<rect width="82" height="88" fill="url(#paint2_radial_0_11197)" />
|
||||
</g>
|
||||
<g id="编组 92">
|
||||
<path id="路径 43" opacity="0.663407" fill-rule="evenodd" clip-rule="evenodd" d="M0 64.2831V88.2831H26L0 64.2831Z" fill="#253645" />
|
||||
<path id="路径 48" opacity="0.0416248" fill-rule="evenodd" clip-rule="evenodd" d="M15 85.2831L35.5479 64.2831L38 85.2831H15Z" fill="#FEFFFF" />
|
||||
<path id="路径 49" opacity="0.0482428" fill-rule="evenodd" clip-rule="evenodd" d="M29 87.2831L52 75.2831L48.029 87.2831H29Z" fill="#FEFFFF" />
|
||||
<path id="路径 58" opacity="0.1" fill-rule="evenodd" clip-rule="evenodd" d="M78.1507 58.2831L68 86.2831L82 72.2831L78.1507 58.2831Z" fill="#FEFFFF" />
|
||||
<path id="路径 59" opacity="0.135917" fill-rule="evenodd" clip-rule="evenodd" d="M59.1277 73.2831L57 87.2831H74L59.1277 73.2831Z" fill="#FEFFFF" />
|
||||
</g>
|
||||
<rect id="边框" opacity="0.66428" x="0.75" y="0.75" width="80.5" height="86.5" stroke="url(#paint3_linear_0_11197)" stroke-width="1.5" />
|
||||
|
||||
<rect id="矩形" width="4" height="4" fill="#FFF9FF" />
|
||||
<rect id="矩形_2" y="84" width="4" height="4" fill="#FFF9FF" />
|
||||
<rect id="矩形_3" x="253" width="4" height="4" fill="#FFF9FF" />
|
||||
<rect id="矩形_4" x="253" y="84" width="4" height="4" fill="#FFF9FF" />
|
||||
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_i_0_11197" x="0" y="0" width="257" height="88" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="2.5" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.577123 0 0 0 0 0.766759 0 0 0 0 0.884641 0 0 0 0.3 0" />
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_0_11197" />
|
||||
</filter>
|
||||
<filter id="filter1_d_0_11197" x="14" y="24" width="52" height="50.0909" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feOffset dy="2" />
|
||||
<feGaussianBlur stdDeviation="1" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_0_11197" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_0_11197" result="shape" />
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_0_11197" x1="0" y1="0" x2="0" y2="88" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#467591" />
|
||||
<stop offset="1" stop-color="#84C3D5" />
|
||||
</linearGradient>
|
||||
<radialGradient id="paint1_radial_0_11197" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(41 -16.5514) rotate(90) scale(79.4466 95.9972)">
|
||||
<stop stop-color="#1D3B53" />
|
||||
<stop offset="0.179646" stop-color="#1E3D55" stop-opacity="0.910177" />
|
||||
<stop offset="1" stop-color="#416C89" stop-opacity="0.5" />
|
||||
</radialGradient>
|
||||
<radialGradient id="paint2_radial_0_11197" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(76.0746 82.7784) rotate(90) scale(79.4466 74.0297)">
|
||||
<stop stop-color="#ACDDE4" />
|
||||
<stop offset="1" stop-color="#305877" stop-opacity="0.01" />
|
||||
</radialGradient>
|
||||
<linearGradient id="paint3_linear_0_11197" x1="-39.4221" y1="47.3867" x2="48.1169" y2="125.818" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FEFFFF" />
|
||||
<stop offset="0.986769" stop-color="#FEFFFF" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_0_11197" x1="16.3103" y1="24.0877" x2="16.3103" y2="69.5826" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" />
|
||||
<stop offset="1" stop-color="#9FC3C4" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_0_11197">
|
||||
<rect width="81" height="62" fill="white" transform="translate(103 12)" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,128 @@
|
|||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
title: string
|
||||
value: number
|
||||
unit: string
|
||||
icon: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-[420px] h-[92px] relative">
|
||||
<div class="flex items-center h-full gap-10 pt-1 pl-8">
|
||||
<div class="opacity-60 text-[14px]">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="text-[24px] text-[#00F8F4]">
|
||||
{{ value }}
|
||||
</div>
|
||||
<div class="opacity-60 text-[14px]">
|
||||
{{ unit }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Iconify :icon="icon" class="absolute top-1/2 right-9 -translate-y-1/2 w-[45px] h-[45px]" />
|
||||
<svg
|
||||
viewBox="0 0 420 92" fill="none" class="absolute top-0 left-0 -z-[1]" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:anim="http://www.w3.org/2000/anim" anim="" anim:transform-origin="50% 50%" anim:duration="1"
|
||||
anim:ease="ease-in-out"
|
||||
>
|
||||
<g id="å›åŒºæ€»äººæ•°" clip-path="url(#clip0_0_10780)">
|
||||
<rect id="矩形" opacity="0.39908" width="420" height="92" rx="46" fill="url(#paint0_linear_0_10780)" />
|
||||
<g id="车ä½_æ ‡å¿—">
|
||||
<circle
|
||||
id="æ¤åœ†å½¢" opacity="0.5" cx="360.745" cy="45.9989" r="35.2706"
|
||||
stroke="url(#paint1_linear_0_10780)"
|
||||
/>
|
||||
<path
|
||||
id="形状" opacity="0.5"
|
||||
d="M383.869 65.3988L383.226 66.1646L383.992 66.8077L384.635 66.0419L383.869 65.3988ZM390.928 45.9989H391.928V44.9989H390.928V45.9989ZM383.103 64.7557L382.337 64.1126L381.694 64.8784L382.46 65.5215L383.103 64.7557ZM389.928 45.9989V44.9989H388.928V45.9989H389.928ZM332.909 34.3088L333.297 33.3869L332.375 32.9993L331.988 33.9212L332.909 34.3088ZM333.831 34.6964L334.753 35.084L335.141 34.1621L334.219 33.7745L333.831 34.6964ZM332.295 52.5294L332.518 53.5042L333.493 53.2814L333.27 52.3065L332.295 52.5294ZM331.321 52.7522L330.346 52.975L330.568 53.9499L331.543 53.7271L331.321 52.7522ZM384.512 64.6331L384.359 64.5044L383.073 66.036L383.226 66.1646L384.512 64.6331ZM383.899 64.1186L383.746 63.9899L382.46 65.5215L382.613 65.6501L383.899 64.1186ZM383.869 65.3988C384.095 65.13 384.316 64.8572 384.532 64.5806L382.957 63.3483C382.755 63.6067 382.548 63.8615 382.337 64.1126L383.869 65.3988ZM386.346 61.9942C386.718 61.3999 387.07 60.7917 387.401 60.1705L385.636 59.2302C385.327 59.8101 384.998 60.3779 384.651 60.9329L386.346 61.9942ZM388.738 57.3075C389.001 56.6583 389.241 55.9979 389.459 55.3272L387.557 54.7097C387.354 55.3356 387.129 55.9519 386.884 56.5578L388.738 57.3075ZM390.275 52.2744C390.42 51.5907 390.541 50.8986 390.638 50.1989L388.658 49.9231C388.567 50.5764 388.453 51.2225 388.318 51.8606L390.275 52.2744ZM390.91 47.0521C390.922 46.7025 390.928 46.3514 390.928 45.9989H388.928C388.928 46.3285 388.923 46.6567 388.911 46.9835L390.91 47.0521ZM389.928 46.9989H390.128V44.9989H389.928V46.9989ZM390.728 46.9989H390.928V44.9989H390.728V46.9989ZM389.928 45.9989C389.928 46.3402 389.922 46.68 389.911 47.0184L391.91 47.087C391.922 46.7258 391.928 46.363 391.928 45.9989H389.928ZM389.648 50.0623C389.554 50.7388 389.436 51.4078 389.296 52.0686L391.253 52.4825C391.402 51.7762 391.528 51.0611 391.629 50.3382L389.648 50.0623ZM388.508 55.0189C388.297 55.667 388.065 56.3052 387.811 56.9326L389.665 57.6823C389.937 57.0117 390.185 56.3294 390.41 55.6365L388.508 55.0189ZM386.519 59.6999C386.199 60.3004 385.859 60.8885 385.499 61.4631L387.194 62.5244C387.578 61.9104 387.942 61.282 388.284 60.6402L386.519 59.6999ZM383.745 63.9643C383.535 64.2319 383.321 64.4957 383.103 64.7557L384.635 66.0419C384.868 65.7642 385.096 65.4824 385.32 65.1965L383.745 63.9643ZM331.988 33.9212C331.828 34.3016 331.675 34.6859 331.53 35.0738L333.403 35.7746C333.539 35.4117 333.682 35.0522 333.831 34.6964L331.988 33.9212ZM330.438 38.6289C330.245 39.4278 330.082 40.2388 329.951 41.0603L331.926 41.3747C332.049 40.606 332.201 39.8473 332.382 39.1L330.438 38.6289ZM329.586 44.7598C329.57 45.1709 329.562 45.584 329.562 45.9989H331.562C331.562 45.61 331.57 45.223 331.585 44.8379L329.586 44.7598ZM329.562 45.9989C329.562 46.47 329.572 46.9387 329.593 47.4049L331.591 47.3161C331.572 46.8795 331.562 46.4404 331.562 45.9989H329.562ZM330.063 51.5964C330.147 52.0596 330.241 52.5192 330.346 52.975L332.295 52.5294C332.198 52.1029 332.11 51.6729 332.031 51.2396L330.063 51.5964ZM331.543 53.7271L331.738 53.6825L331.293 51.7328L331.098 51.7773L331.543 53.7271ZM332.323 53.5488L332.518 53.5042L332.073 51.5545L331.878 51.5991L332.323 53.5488ZM333.27 52.3065C333.176 51.8947 333.091 51.4794 333.015 51.0608L331.047 51.4176C331.128 51.866 331.22 52.3109 331.321 52.7522L333.27 52.3065ZM332.59 47.2712C332.571 46.8495 332.562 46.4254 332.562 45.9989H330.562C330.562 46.4549 330.572 46.9087 330.592 47.3599L332.59 47.2712ZM332.562 45.9989C332.562 45.6233 332.569 45.2496 332.584 44.8777L330.585 44.7996C330.57 45.1975 330.562 45.5973 330.562 45.9989H332.562ZM332.914 41.5332C333.032 40.7908 333.179 40.0582 333.354 39.3365L331.41 38.8655C331.223 39.6387 331.065 40.4236 330.938 41.2189L332.914 41.5332ZM334.34 36.1252C334.471 35.7747 334.609 35.4276 334.753 35.084L332.909 34.3088C332.755 34.677 332.607 35.0489 332.466 35.4244L334.34 36.1252ZM334.219 33.7745L334.034 33.697L333.259 35.5407L333.444 35.6182L334.219 33.7745ZM333.481 33.4645L333.297 33.3869L332.522 35.2306L332.706 35.3081L333.481 33.4645Z"
|
||||
fill="url(#paint2_linear_0_10780)"
|
||||
/>
|
||||
<path
|
||||
id="形状_2"
|
||||
d="M371.334 85.4858L369.39 85.959L369.864 87.9022L371.807 87.429L371.334 85.4858ZM399.915 60.689L401.771 61.4335L402.516 59.5773L400.66 58.8327L399.915 60.689ZM371.097 84.5142L370.624 82.571L368.681 83.0441L369.154 84.9874L371.097 84.5142ZM398.987 60.3167L399.732 58.4604L397.875 57.7159L397.131 59.5721L398.987 60.3167ZM397.096 24.2053L398.815 23.1837L397.794 21.4643L396.074 22.4859L397.096 24.2053ZM400.99 57.6733L400.393 59.5819L402.301 60.1795L402.899 58.2709L400.99 57.6733ZM400.036 57.3745L398.127 56.7768L397.53 58.6855L399.438 59.2831L400.036 57.3745ZM396.236 24.7161L395.215 22.9967L393.495 24.0182L394.517 25.7377L396.236 24.7161ZM371.807 87.429C385.485 84.0984 396.616 74.2871 401.771 61.4335L398.059 59.9444C393.378 71.6138 383.269 80.5213 370.86 83.5426L371.807 87.429ZM369.154 84.9874L369.39 85.959L373.277 85.0126L373.04 84.041L369.154 84.9874ZM397.131 59.5721C392.569 70.9455 382.714 79.627 370.624 82.571L371.57 86.4574C384.931 83.2041 395.806 73.6188 400.843 61.0612L397.131 59.5721ZM400.66 58.8327L399.732 58.4604L398.242 62.1729L399.171 62.5452L400.66 58.8327ZM404.873 45.3005C404.873 37.2265 402.664 29.661 398.815 23.1837L395.376 25.2269C398.868 31.103 400.873 37.9653 400.873 45.3005H404.873ZM402.899 58.2709C404.182 54.1724 404.873 49.8147 404.873 45.3005H400.873C400.873 49.4047 400.245 53.3595 399.082 57.0757L402.899 58.2709ZM399.438 59.2831L400.393 59.5819L401.588 55.7647L400.634 55.4658L399.438 59.2831ZM399.873 45.3005C399.873 49.3022 399.261 53.1563 398.127 56.7768L401.945 57.9721C403.198 53.9692 403.873 49.7122 403.873 45.3005H399.873ZM394.517 25.7377C397.919 31.4635 399.873 38.15 399.873 45.3005H403.873C403.873 37.4112 401.715 30.0215 397.956 23.6945L394.517 25.7377ZM396.074 22.4859L395.215 22.9967L397.258 26.4355L398.117 25.9247L396.074 22.4859Z"
|
||||
fill="url(#paint3_linear_0_10780)"
|
||||
/>
|
||||
<path
|
||||
id="形状_3"
|
||||
d="M350.428 6.51419L352.372 6.04102L351.898 4.0978L349.955 4.57097L350.428 6.51419ZM321.847 31.311L319.991 30.5665L319.246 32.4227L321.102 33.1673L321.847 31.311ZM350.665 7.4858L351.138 9.42902L353.081 8.95585L352.608 7.01263L350.665 7.4858ZM322.775 31.6833L322.03 33.5396L323.887 34.2841L324.631 32.4279L322.775 31.6833ZM324.666 67.7947L322.947 68.8163L323.968 70.5357L325.688 69.5141L324.666 67.7947ZM320.772 34.3267L321.369 32.4181L319.461 31.8205L318.863 33.7291L320.772 34.3267ZM321.726 34.6255L323.635 35.2232L324.232 33.3145L322.324 32.7169L321.726 34.6255ZM325.526 67.2839L326.547 69.0033L328.267 67.9818L327.245 66.2623L325.526 67.2839ZM349.955 4.57097C336.277 7.90163 325.146 17.7129 319.991 30.5665L323.703 32.0556C328.384 20.3862 338.493 11.4787 350.902 8.45741L349.955 4.57097ZM352.608 7.01263L352.372 6.04102L348.485 6.98736L348.722 7.95897L352.608 7.01263ZM324.631 32.4279C329.193 21.0545 339.048 12.373 351.138 9.42902L350.192 5.54258C336.831 8.79591 325.956 18.3812 320.919 30.9388L324.631 32.4279ZM321.102 33.1673L322.03 33.5396L323.519 29.8271L322.591 29.4548L321.102 33.1673ZM316.889 46.6995C316.889 54.7735 319.098 62.339 322.947 68.8163L326.385 66.7731C322.894 60.897 320.889 54.0347 320.889 46.6995H316.889ZM318.863 33.7291C317.58 37.8276 316.889 42.1853 316.889 46.6995H320.889C320.889 42.5953 321.517 38.6405 322.68 34.9243L318.863 33.7291ZM322.324 32.7169L321.369 32.4181L320.174 36.2353L321.128 36.5342L322.324 32.7169ZM321.889 46.6995C321.889 42.6978 322.501 38.8437 323.635 35.2232L319.817 34.0279C318.564 38.0308 317.889 42.2878 317.889 46.6995H321.889ZM327.245 66.2623C323.843 60.5365 321.889 53.85 321.889 46.6995H317.889C317.889 54.5888 320.047 61.9785 323.806 68.3055L327.245 66.2623ZM325.688 69.5141L326.547 69.0033L324.504 65.5645L323.645 66.0753L325.688 69.5141Z"
|
||||
fill="url(#paint4_linear_0_10780)"
|
||||
/>
|
||||
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_f_0_10780" x="330.537" y="23.7524" width="62.4582" height="52.2006"
|
||||
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation="8.15485" result="effect1_foregroundBlur_0_10780" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_0_10780" x1="0.360291" y1="73.2461" x2="419.678" y2="56.3299"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#67A4E1" stop-opacity="0.33" />
|
||||
<stop offset="0.195436" stop-color="#67A4E1" stop-opacity="0.01" />
|
||||
<stop offset="0.535357" stop-color="#67A4E1" stop-opacity="0.01" />
|
||||
<stop offset="1" stop-color="#67A4E1" stop-opacity="0.9" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_0_10780" x1="317.83" y1="30.6611" x2="344.44" y2="79.3588"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#DFF7FF" stop-opacity="0.01" />
|
||||
<stop offset="0.260824" stop-color="#DFF7FF" />
|
||||
<stop offset="0.623539" stop-color="#DFF7FF" />
|
||||
<stop offset="1" stop-color="#DFF7FF" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_0_10780" x1="336.552" y1="15.3195" x2="333.307" y2="57.9034"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#DFF7FF" stop-opacity="0.01" />
|
||||
<stop offset="0.280256" stop-color="#DFF7FF" />
|
||||
<stop offset="0.634379" stop-color="#DFF7FF" />
|
||||
<stop offset="1" stop-color="#DFF7FF" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint3_linear_0_10780" x1="364.727" y1="41.5865" x2="400.264" y2="73.2964"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="white" />
|
||||
<stop offset="0.262294" stop-color="#DFF7FF" />
|
||||
<stop offset="1" stop-color="#DFF7FF" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint4_linear_0_10780" x1="357.035" y1="50.4135" x2="321.498" y2="18.7036"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="white" />
|
||||
<stop offset="0.262294" stop-color="#DFF7FF" />
|
||||
<stop offset="1" stop-color="#DFF7FF" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint5_linear_0_10780" x1="340.699" y1="26.5818" x2="340.699" y2="62.2006"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="white" />
|
||||
<stop offset="1" stop-color="#6AF6FA" stop-opacity="0.2" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint6_linear_0_10780" x1="353.959" y1="53.3313" x2="348.281" y2="69.3448"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#6AD4D1" />
|
||||
<stop offset="1" stop-color="#5E96B8" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_0_10780">
|
||||
<rect width="420" height="92" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,47 @@
|
|||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
title: string
|
||||
value: number
|
||||
icon: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-[96px] h-[66px] relative">
|
||||
<div class="space-y-3 ">
|
||||
<div class="flex items-end gap-3">
|
||||
<Iconify :icon="icon" class="w-[38px] h-[38px] " />
|
||||
<div class="text-[24px] text-shadow">
|
||||
{{ value }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ title }}
|
||||
</div>
|
||||
</div>
|
||||
<svg viewBox="0 0 96 66" class="absolute top-0 left-0 w-full h-full " fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:anim="http://www.w3.org/2000/anim" anim="" anim:transform-origin="50% 50%" anim:duration="1" anim:ease="ease-in-out">
|
||||
<g id="编组 12" clip-path="url(#clip0_0_10738)">
|
||||
<g id="icon_bar" opacity="0.5">
|
||||
<path id="直线 20" d="M0.646341 44.0854H88.5488" stroke="white" stroke-linecap="square" />
|
||||
<circle id="æ¤åœ†å½¢" cx="92.4268" cy="44.0853" r="2.73171" stroke="white" />
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_0_10738" x1="4.02103" y1="0.0608013" x2="4.02103" y2="31.9686" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" />
|
||||
<stop offset="1" stop-color="#6AF6FA" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_0_10738">
|
||||
<rect width="95.6585" height="66" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.text-shadow{
|
||||
text-shadow: 0px 1px 6px #84F8FB;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
error:any
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e:'retry'):Promise<any>
|
||||
}>()
|
||||
|
||||
|
||||
const { isPending, start, stop } = useTimeoutFn(async () => {
|
||||
await emit('retry')
|
||||
}, 3000,{
|
||||
immediate:false
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class=" flex flex-col items-center justify-center gap-2 rounded bg-gray-600/5 py-5">
|
||||
<UIcon name="i-heroicons-exclamation-triangle-16-solid" class=" h-10 w-10 text-rose-500 dark:text-rose-700 " />
|
||||
<p class="max-w-[500px] text-center font-semibold typography-muted">
|
||||
{{ error }}
|
||||
</p>
|
||||
<UButton variant="soft" :loading="isPending" @click="start()">
|
||||
<span class="tracking-[5px]">{{ $t('try-again') }}</span>
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,95 @@
|
|||
<script setup lang="ts">
|
||||
const emit = defineEmits<{
|
||||
(e: 'submit', value: {
|
||||
Token: string
|
||||
Remember: boolean
|
||||
}): void
|
||||
}>()
|
||||
|
||||
const { data, refresh } = useAsyncData(authApi.captcha)
|
||||
|
||||
const schema = z.object({
|
||||
UserName: z.string({
|
||||
required_error: '账号不能为空',
|
||||
}),
|
||||
Password: z.string({
|
||||
required_error: '密码不能为空',
|
||||
}),
|
||||
Code: z.string({
|
||||
required_error: '验证码不能为空',
|
||||
}),
|
||||
Remember: z.boolean().optional(),
|
||||
})
|
||||
|
||||
const onSubmit: any = async (values: ZodInfer<typeof schema>) => {
|
||||
try {
|
||||
const res = await authApi.login({ ...values, UUID: data.value?.uuid ?? '' })
|
||||
// 登录成功
|
||||
if (res.Token) {
|
||||
emit('submit', { ...res, Remember: !!values.Remember })
|
||||
}
|
||||
else {
|
||||
// 需要安全验证
|
||||
// modal.open(Security, {
|
||||
// initial: res,
|
||||
// onSubmit(result) {
|
||||
// emit('submit', { ...result, Remember: event.data.Remember })
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}
|
||||
catch (err: any) {
|
||||
toast.error('登录失败', {
|
||||
description: err.message,
|
||||
})
|
||||
}
|
||||
refresh()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AutoForm
|
||||
class="space-y-4" :schema="schema" :field-config="{
|
||||
UserName: {
|
||||
hideLabel: true,
|
||||
inputProps: {
|
||||
placeholder: '请填写账号',
|
||||
},
|
||||
},
|
||||
Password: {
|
||||
hideLabel: true,
|
||||
inputProps: {
|
||||
type: 'password',
|
||||
placeholder: '请填写密码',
|
||||
},
|
||||
},
|
||||
Code: {
|
||||
hideLabel: true,
|
||||
inputProps: {
|
||||
placeholder: '请填写验证码',
|
||||
},
|
||||
},
|
||||
Remember: {
|
||||
label: '记住登录',
|
||||
inputProps: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}" @submit="onSubmit"
|
||||
>
|
||||
<template #Code="slotProps">
|
||||
<div class="flex items-start gap-3">
|
||||
<AutoFormField v-bind="slotProps" />
|
||||
<Button
|
||||
class="w-[150px] bg-no-repeat" type="button"
|
||||
:style="{ backgroundSize: '100% 100%', backgroundImage: `url(data:image/png;base64,${data?.imgByte})` }"
|
||||
@click="refresh"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<Button type="submit" class="w-full font-bold">
|
||||
登录
|
||||
</Button>
|
||||
</AutoForm>
|
||||
</template>
|
|
@ -0,0 +1,80 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'submit'): void
|
||||
}>()
|
||||
const verifyName = verify.test(
|
||||
// @ts-ignore
|
||||
new RegExp(/^[a-z]{1}(?=.*[a-z])[a-z\d\-]{4,18}[a-z\d]{1}$/)
|
||||
)
|
||||
|
||||
const schema = z.object({
|
||||
UserName: z.string({
|
||||
required_error: '账号不能为空'
|
||||
})
|
||||
.refine(verifyName, '名称应该以6-20位的小写字母组成,允许其中包含数字、连接符(-)')
|
||||
.refine(str => str !== 'root' && str !== 'jwanfs', '不允许起名关键字root、jwanfs'),
|
||||
Email: z.string({
|
||||
required_error: '请填写邮箱'
|
||||
}).email('邮箱格式有误'),
|
||||
Code: z.string({
|
||||
required_error: '验证码不能为空'
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
const { data, refresh } = useAsyncData(authApi.captcha)
|
||||
|
||||
|
||||
const onSubmit: any = async (values: ZodInfer<typeof schema>) => {
|
||||
try {
|
||||
await authApi.register({ ...event, RandomPass: true, Uuid: data.value?.uuid ?? '' })
|
||||
emit('submit')
|
||||
} catch (err: any) {
|
||||
toast.error("操作失败", {
|
||||
description: err.message
|
||||
})
|
||||
}
|
||||
refresh()
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<AutoForm class="space-y-4" :schema="schema" :field-config="{
|
||||
UserName: {
|
||||
hideLabel: true,
|
||||
inputProps: {
|
||||
placeholder: '请填写账号',
|
||||
},
|
||||
},
|
||||
Email: {
|
||||
hideLabel: true,
|
||||
inputProps: {
|
||||
type: 'email',
|
||||
placeholder: '请填写邮箱',
|
||||
},
|
||||
},
|
||||
Code: {
|
||||
hideLabel: true,
|
||||
inputProps: {
|
||||
placeholder: '请填写验证码',
|
||||
},
|
||||
},
|
||||
}" @submit="onSubmit">
|
||||
<template #Code="slotProps">
|
||||
<div class="flex items-start gap-3">
|
||||
<AutoFormField v-bind="slotProps" />
|
||||
<Button class="w-[150px] bg-no-repeat" type="button"
|
||||
:style="{ backgroundSize: '100% 100%', backgroundImage: `url(data:image/png;base64,${data?.imgByte})` }"
|
||||
@click="refresh" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<Button type="submit" class="font-bold w-full">
|
||||
登录
|
||||
</Button>
|
||||
</AutoForm>
|
||||
</template>
|
|
@ -0,0 +1,109 @@
|
|||
<script setup lang="ts">
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'submit', value: {
|
||||
Token: string
|
||||
Remember: boolean
|
||||
}): void
|
||||
}>()
|
||||
|
||||
const schema = z.object({
|
||||
Email: z.string({
|
||||
required_error: '请填写邮箱',
|
||||
}).email('请填写正确的邮箱'),
|
||||
Code: z.string({
|
||||
required_error: '请填写验证码',
|
||||
}).length(4, '请填写4个字符的验证码'),
|
||||
Remember: z.boolean().optional(),
|
||||
})
|
||||
|
||||
const form = useForm({
|
||||
validationSchema: toTypedSchema(schema),
|
||||
})
|
||||
|
||||
const state = reactive({
|
||||
Email: undefined,
|
||||
Code: undefined,
|
||||
Remember: false,
|
||||
})
|
||||
|
||||
// 验证码发送加载
|
||||
const loading = ref(false)
|
||||
|
||||
async function handleSuccessAndSendMSG() {
|
||||
loading.value = true
|
||||
// 验证码验证成功,发送邮件
|
||||
try {
|
||||
await authApi.sendCode({
|
||||
Email: state.Email,
|
||||
})
|
||||
toast.success('验证码发送成功')
|
||||
}
|
||||
catch (err: any) {
|
||||
toast.error(err.message)
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
async function verifyEmail(res: (open: boolean) => void) {
|
||||
try {
|
||||
// 验证邮箱
|
||||
await form.validateField('Email')
|
||||
res(true)
|
||||
}
|
||||
catch (err: any) {
|
||||
if (!err.message.includes('Email'))
|
||||
res(true)
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit: any = async (event: ZodInfer<typeof schema>) => {
|
||||
try {
|
||||
const res = await authApi.loginByDevice(event)
|
||||
emit('submit', { ...res, Remember: !!event.Remember })
|
||||
}
|
||||
catch (err: any) {
|
||||
toast.error('登录失败', {
|
||||
description: err.message,
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AutoForm
|
||||
class="space-y-4" :form="form" :schema="schema" :field-config="{
|
||||
Email: {
|
||||
hideLabel: true,
|
||||
inputProps: {
|
||||
placeholder: '请填写邮箱',
|
||||
},
|
||||
},
|
||||
Code: {
|
||||
hideLabel: true,
|
||||
inputProps: {
|
||||
placeholder: '请填写验证码',
|
||||
},
|
||||
},
|
||||
Remember: {
|
||||
label: '记住登录',
|
||||
inputProps: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}" @submit="onSubmit"
|
||||
>
|
||||
<template #Code="slotProps">
|
||||
<div class="flex items-start gap-3">
|
||||
<AutoFormField v-bind="slotProps" />
|
||||
<CaptchaVerifyButton :loading="loading" @verify="verifyEmail" @success="handleSuccessAndSendMSG" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<Button type="submit" class="w-full font-bold">
|
||||
登录
|
||||
</Button>
|
||||
</AutoForm>
|
||||
</template>
|
|
@ -0,0 +1,111 @@
|
|||
<script setup lang="ts">
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'submit', value: {
|
||||
Token: string
|
||||
Remember: boolean
|
||||
}): void
|
||||
}>()
|
||||
|
||||
const schema = z.object({
|
||||
Phone: z.string({
|
||||
required_error: '请填写手机',
|
||||
}).refine(str => verify.phone(str), '请填写正确的手机号'),
|
||||
Code: z.string({
|
||||
required_error: '请填写验证码',
|
||||
}).length(4, '请填写4个字符的验证码'),
|
||||
Remember: z.boolean().optional(),
|
||||
})
|
||||
|
||||
const form = useForm({
|
||||
validationSchema: toTypedSchema(schema),
|
||||
})
|
||||
|
||||
const state = reactive({
|
||||
Phone: undefined,
|
||||
Code: undefined,
|
||||
Remember: false,
|
||||
})
|
||||
|
||||
// 验证码发送加载
|
||||
const loading = ref(false)
|
||||
|
||||
async function handleSuccessAndSendMSG() {
|
||||
loading.value = true
|
||||
// 验证码验证成功,发送手机
|
||||
try {
|
||||
await authApi.sendCode({
|
||||
Phone: state.Phone,
|
||||
})
|
||||
toast.success('验证码发送成功')
|
||||
}
|
||||
catch (err: any) {
|
||||
toast.error('验证码发送失败', {
|
||||
description: err.message,
|
||||
})
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
async function verifyPhone(res: (open: boolean) => void) {
|
||||
try {
|
||||
// 验证手机
|
||||
await form.validateField('Phone')
|
||||
res(true)
|
||||
}
|
||||
catch (err: any) {
|
||||
if (!err.message.includes('Phone'))
|
||||
res(true)
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit: any = async (event: ZodInfer<typeof schema>) => {
|
||||
try {
|
||||
const res = await authApi.loginByDevice(event)
|
||||
emit('submit', { ...res, Remember: !!event.Remember })
|
||||
}
|
||||
catch (err: any) {
|
||||
toast.error('登录失败', {
|
||||
description: err.message,
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AutoForm
|
||||
class="space-y-4" :form="form" :schema="schema" :field-config="{
|
||||
Phone: {
|
||||
hideLabel: true,
|
||||
inputProps: {
|
||||
placeholder: '请填写手机',
|
||||
},
|
||||
},
|
||||
Code: {
|
||||
hideLabel: true,
|
||||
inputProps: {
|
||||
placeholder: '请填写验证码',
|
||||
},
|
||||
},
|
||||
Remember: {
|
||||
label: '记住登录',
|
||||
inputProps: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}" @submit="onSubmit"
|
||||
>
|
||||
<template #Code="slotProps">
|
||||
<div class="flex items-start gap-3">
|
||||
<AutoFormField v-bind="slotProps" />
|
||||
<CaptchaVerifyButton :loading="loading" @verify="verifyPhone" @success="handleSuccessAndSendMSG" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<Button type="submit" class="w-full font-bold">
|
||||
登录
|
||||
</Button>
|
||||
</AutoForm>
|
||||
</template>
|
|
@ -0,0 +1,118 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'submit'): void
|
||||
}>()
|
||||
const verifyName = verify.test(
|
||||
new RegExp(/^[a-z]{1}(?=.*[a-z])[a-z\d\-]{4,18}[a-z\d]{1}$/)
|
||||
)
|
||||
|
||||
|
||||
const { data: auditors, pending } = useAsyncData(authApi.auditors, {
|
||||
default: () => [],
|
||||
})
|
||||
const enumAuditors = computed(() => auditors.value.map(i => String(i.Id)) ?? [''])
|
||||
|
||||
const schema = computed(() => z.object({
|
||||
UserName: z.string({
|
||||
required_error: '账号不能为空'
|
||||
})
|
||||
.refine(verifyName, '名称应该以6-20位的小写字母组成,允许其中包含数字、连接符(-)')
|
||||
.refine(str => str !== 'root' && str !== 'jwanfs', '不允许起名关键字root、jwanfs'),
|
||||
Email: z.string({
|
||||
required_error: '请填写邮箱'
|
||||
}).email('邮箱格式有误'),
|
||||
Password: z.string({
|
||||
required_error: '密码不能为空'
|
||||
}),
|
||||
Confirm: z.string({
|
||||
required_error: '确认密码不能为空'
|
||||
}),
|
||||
SysUserId: z.enum(enumAuditors.value as [string],{
|
||||
required_error: '请选择审核人'
|
||||
}),
|
||||
Code: z.string({
|
||||
required_error: '验证码不能为空'
|
||||
})
|
||||
}).refine(({ Confirm, Password }) => Confirm === Password, {
|
||||
message: '两次密码输入不一致',
|
||||
path: ['Confirm']
|
||||
}))
|
||||
|
||||
const { data, refresh } = useAsyncData(authApi.captcha)
|
||||
|
||||
|
||||
const toast = useToastHandle()
|
||||
|
||||
async function onSubmit(event: ZodInfer<typeof schema.value>) {
|
||||
try {
|
||||
await authApi.register({ ...event, Uuid: data.value?.uuid ?? '' })
|
||||
emit('submit')
|
||||
} catch (err: any) {
|
||||
toast.error(err.message)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AutoForm class="space-y-4" :schema="schema" :field-config="{
|
||||
UserName: {
|
||||
hideLabel: true,
|
||||
inputProps: {
|
||||
placeholder: '请填写账号',
|
||||
},
|
||||
},
|
||||
Email: {
|
||||
hideLabel: true,
|
||||
inputProps: {
|
||||
type: 'email',
|
||||
placeholder: '请填写邮箱',
|
||||
},
|
||||
},
|
||||
Password: {
|
||||
hideLabel: true,
|
||||
inputProps: {
|
||||
type: 'password',
|
||||
placeholder: '请填写密码',
|
||||
},
|
||||
},
|
||||
SysUserId: {
|
||||
loading: pending,
|
||||
options: auditors.map(item => ({ label: item.UserName, value: item.Id })),
|
||||
hideLabel: true,
|
||||
inputProps: {
|
||||
placeholder: '请选择审核人',
|
||||
},
|
||||
},
|
||||
Confirm: {
|
||||
hideLabel: true,
|
||||
inputProps: {
|
||||
type: 'password',
|
||||
placeholder: '请二次确认密码',
|
||||
},
|
||||
},
|
||||
Code: {
|
||||
hideLabel: true,
|
||||
inputProps: {
|
||||
placeholder: '请填写验证码',
|
||||
},
|
||||
},
|
||||
}" @submit="onSubmit">
|
||||
<template #Code="slotProps">
|
||||
<div class="flex items-start gap-3">
|
||||
<AutoFormField v-bind="slotProps" />
|
||||
<Button class="w-[150px] bg-no-repeat" type="button"
|
||||
:style="{ backgroundSize: '100% 100%', backgroundImage: `url(data:image/png;base64,${data?.imgByte})` }"
|
||||
@click="refresh" />
|
||||
</div>
|
||||
</template>
|
||||
<template #SysUserId="slotProps">
|
||||
<AutoFormField v-bind="slotProps" />
|
||||
</template>
|
||||
|
||||
<Button type="submit" class="font-bold w-full">
|
||||
登录
|
||||
</Button>
|
||||
</AutoForm>
|
||||
</template>
|
|
@ -0,0 +1,141 @@
|
|||
<script setup lang="ts">
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
import type { output } from 'zod'
|
||||
|
||||
|
||||
const model = defineModel({
|
||||
type: Boolean
|
||||
})
|
||||
|
||||
const form = ref()
|
||||
const emit = defineEmits<{
|
||||
(e: 'submit', res: {
|
||||
Token: string;
|
||||
}): void
|
||||
}>()
|
||||
|
||||
|
||||
const { initial } = defineProps<{
|
||||
initial: {
|
||||
Email?: string
|
||||
Phone?: string
|
||||
UserName?: string
|
||||
}
|
||||
}>()
|
||||
|
||||
const schema = z.object({
|
||||
CodeType: z.string({
|
||||
required_error: '请选择认证方式'
|
||||
}),
|
||||
Code: z.string({
|
||||
required_error: '请填写验证码'
|
||||
}).length(4, '请填写4个字符的验证码')
|
||||
})
|
||||
|
||||
type Schema = output<typeof schema>
|
||||
|
||||
const state = reactive({
|
||||
CodeType: undefined,
|
||||
Code: undefined,
|
||||
})
|
||||
|
||||
|
||||
const toast = useToastHandle()
|
||||
// 验证码发送加载
|
||||
const loading = ref(false)
|
||||
|
||||
async function handleSuccessAndSendMSG(res: (open: boolean) => void) {
|
||||
loading.value = true
|
||||
// 验证码验证成功,发送邮件
|
||||
try {
|
||||
|
||||
const params = state.CodeType === 'email' ? {
|
||||
Email: initial.Email,
|
||||
} : {
|
||||
Phone: initial.Phone
|
||||
}
|
||||
|
||||
await authApi.sendCode(params)
|
||||
res(true)
|
||||
toast.success('验证码发送成功')
|
||||
} catch (err: any) {
|
||||
toast.error(err.message)
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
async function verify(res: (open: boolean) => void) {
|
||||
res(true)
|
||||
}
|
||||
|
||||
|
||||
async function onSubmit(event: FormSubmitEvent<Schema>) {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await authApi.loginByDevice({ ...event.data })
|
||||
emit('submit', res)
|
||||
} catch (err: any) {
|
||||
toast.error(err.message)
|
||||
}
|
||||
model.value = false
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
|
||||
|
||||
const options = computed(() => {
|
||||
|
||||
const list = []
|
||||
!!initial.Email &&
|
||||
list.push({
|
||||
id: 'email',
|
||||
name: '邮箱: ' + emailShield(initial.Email)
|
||||
})
|
||||
!!initial.Phone &&
|
||||
list.push({
|
||||
id: 'phone',
|
||||
name: '手机号: ' + phoneShield(initial.Phone)
|
||||
})
|
||||
!!initial.UserName &&
|
||||
list.push({
|
||||
id: 'totp',
|
||||
name: '安全验证器: ' + initial.UserName
|
||||
})
|
||||
return list
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardModal v-model="model" title="安全校验" description="请选择一种方式进行安全校验" :ui="{ width: 'sm:max-w-md' }">
|
||||
<UForm ref="form" :schema="schema" :state="state" class="space-y-4 md:space-y-6" @submit="onSubmit">
|
||||
<UFormGroup name="CodeType">
|
||||
<USelectMenu
|
||||
v-model="state.CodeType"
|
||||
:options="options"
|
||||
placeholder="选择认证方式"
|
||||
value-attribute="id"
|
||||
option-attribute="name"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup name="Code">
|
||||
<UButtonGroup orientation="horizontal" class="w-full">
|
||||
<UInput v-model="state.Code" :maxlength="4" class="flex-1" placeholder="请输入验证码" />
|
||||
<VerifyButton
|
||||
v-if="state.CodeType !== 'totp'"
|
||||
:loading="loading"
|
||||
@verify="verify"
|
||||
@success="handleSuccessAndSendMSG"
|
||||
/>
|
||||
</UButtonGroup>
|
||||
</UFormGroup>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<UButton type="submit" class="px-4">
|
||||
确认
|
||||
</UButton>
|
||||
</div>
|
||||
</UForm>
|
||||
</UDashboardModal>
|
||||
</template>
|
|
@ -0,0 +1,121 @@
|
|||
<script setup lang="ts">
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
import type { output } from 'zod'
|
||||
|
||||
|
||||
const model = defineModel({
|
||||
type: Boolean
|
||||
})
|
||||
|
||||
const form = ref()
|
||||
const emit = defineEmits<{
|
||||
(e: 'submit'): void
|
||||
}>()
|
||||
|
||||
const schema = z.object({
|
||||
CodeType: z.string({
|
||||
required_error: '请选择认证方式'
|
||||
}),
|
||||
Code: z.string({
|
||||
required_error: '请填写验证码'
|
||||
}).length(4, '请填写4个字符的验证码')
|
||||
})
|
||||
|
||||
type Schema = output<typeof schema>
|
||||
|
||||
const state = reactive({
|
||||
CodeType: undefined,
|
||||
Code: undefined,
|
||||
})
|
||||
|
||||
|
||||
const toast = useToastHandle()
|
||||
// 验证码发送加载
|
||||
const loading = ref(false)
|
||||
|
||||
async function handleSuccessAndSendMSG(res: (open: boolean) => void) {
|
||||
loading.value = true
|
||||
// 验证码验证成功,发送邮件
|
||||
try {
|
||||
await authApi.sendSafeCode({
|
||||
Type: state.CodeType!,
|
||||
})
|
||||
res(true)
|
||||
toast.success('验证码发送成功')
|
||||
} catch (err: any) {
|
||||
toast.error(err.message)
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
async function verify(res: (open: boolean) => void) {
|
||||
res(true)
|
||||
}
|
||||
|
||||
|
||||
async function onSubmit(event: FormSubmitEvent<Schema>) {
|
||||
loading.value = true
|
||||
try {
|
||||
await authApi.postSafeCheck({ ...event.data })
|
||||
emit('submit')
|
||||
} catch (err: any) {
|
||||
toast.error(err.message)
|
||||
}
|
||||
model.value = false
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
|
||||
const { userInfo } = useUserStore()
|
||||
|
||||
const options = computed(() => {
|
||||
|
||||
const list = []
|
||||
!!userInfo.Email &&
|
||||
list.push({
|
||||
id: 'email',
|
||||
name: '邮箱: ' + emailShield(userInfo.Email)
|
||||
})
|
||||
!!userInfo.Phone &&
|
||||
list.push({
|
||||
id: 'phone',
|
||||
name: '手机号: ' + phoneShield(userInfo.Phone)
|
||||
})
|
||||
!!userInfo.IsSafeCheck &&
|
||||
list.push({
|
||||
id: 'totp',
|
||||
name: '安全验证器: ' + userInfo.NickName
|
||||
})
|
||||
return list
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDashboardModal v-model="model" title="安全校验" description="请选择一种方式进行安全校验" :ui="{ width: 'sm:max-w-md' }">
|
||||
<UForm ref="form" :schema="schema" :state="state" class="space-y-4 md:space-y-6" @submit="onSubmit">
|
||||
<UFormGroup name="CodeType">
|
||||
<USelectMenu
|
||||
v-model="state.CodeType"
|
||||
:options="options"
|
||||
placeholder="选择认证方式"
|
||||
value-attribute="id"
|
||||
option-attribute="name"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup name="Code">
|
||||
<UButtonGroup orientation="horizontal" class="w-full">
|
||||
<UInput v-model="state.Code" :maxlength="4" class="flex-1" placeholder="请输入验证码" />
|
||||
<VerifyButton v-if="state.CodeType !== 'totp'" :loading="loading" @verify="verify" @success="handleSuccessAndSendMSG" />
|
||||
</UButtonGroup>
|
||||
</UFormGroup>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<UButton type="submit" class="px-4">
|
||||
确认
|
||||
</UButton>
|
||||
</div>
|
||||
</UForm>
|
||||
</UDashboardModal>
|
||||
</template>
|
|
@ -0,0 +1,42 @@
|
|||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
src: string
|
||||
width?: string
|
||||
height?: string
|
||||
icon?: string
|
||||
title?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative overflow-hidden rounded ">
|
||||
<img :src="src" :height="height" :width="width" class="object-cover w-full h-full min-h-[100px] outline-none">
|
||||
<div class="absolute bottom-0 px-2 left-0 flex w-full z-10 justify-between items-center h-[26px]">
|
||||
<div>
|
||||
{{ title }}
|
||||
</div>
|
||||
|
||||
<Iconify v-if="icon" :icon="icon" />
|
||||
</div>
|
||||
<svg viewBox="0 0 210 26" class="absolute bottom-0 w-full " fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:anim="http://www.w3.org/2000/anim" anim="" anim:transform-origin="50% 50%" anim:duration="1" anim:ease="ease-in-out">
|
||||
<g id="accident_tiltle_bg1" opacity="0.577746" filter="url(#filter0_i_0_10627)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.108154 0.51355H209.302V22C209.302 24.2092 207.511 26 205.302 26H4.10816C1.89902 26 0.108154 24.2092 0.108154 22V0.51355Z" fill="url(#paint0_linear_0_10627)" />
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_i_0_10627" x="0.108154" y="0.51355" width="209.194" height="25.4865" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feOffset dy="1" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.654596 0 0 0 0 0.854219 0 0 0 0 1 0 0 0 1 0" />
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_0_10627" />
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_0_10627" x1="0.108154" y1="26" x2="209.302" y2="26" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#09D4BC" />
|
||||
<stop offset="1" stop-color="#233344" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<svg class=" w-[460px] h-[44px] " viewBox="0 0 460 44" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:anim="http://www.w3.org/2000/anim" anim="" anim:transform-origin="50% 50%" anim:duration="1" anim:ease="ease-in-out">
|
||||
<g id="Frame" clip-path="url(#clip0_69_2)">
|
||||
<rect id="å¡«å……" opacity="0.516949" x="82" y="13.3857" width="378" height="27.632" fill="url(#paint0_radial_69_2)" />
|
||||
<rect id="下白线" opacity="0.687887" x="82" y="39.9823" width="377.723" height="1.03544" fill="url(#paint1_linear_69_2)" />
|
||||
<g id="底部模糊" opacity="0.24333" filter="url(#filter0_f_69_2)">
|
||||
<ellipse cx="117.685" cy="34.566" rx="89.6854" ry="9.06599" fill="#2BA6FF" />
|
||||
</g>
|
||||
<g id="矩形" opacity="0.198007">
|
||||
<path d="M259.868 31.5874H447.072V33.1761H250.261H250.083L249.946 33.2881L246.609 36H79.743H79.6177L79.5071 36.0592L66.2898 43.132H21.5738L10.7345 35.0983L10.6018 35H10.4368H0.5V26.5903L13.239 16.5H63.3892H247.842L259.474 31.3951L259.624 31.5874H259.868Z" stroke="url(#paint2_linear_69_2)" />
|
||||
<path d="M259.868 31.5874H447.072V33.1761H250.261H250.083L249.946 33.2881L246.609 36H79.743H79.6177L79.5071 36.0592L66.2898 43.132H21.5738L10.7345 35.0983L10.6018 35H10.4368H0.5V26.5903L13.239 16.5H63.3892H247.842L259.474 31.3951L259.624 31.5874H259.868Z" stroke="url(#paint3_radial_69_2)" />
|
||||
</g>
|
||||
<g id="å¡«å……_2">
|
||||
<path d="M311.386 28.5874L313.227 30.1761H250.261H250.083L249.946 30.2881L246.609 33H79.743H79.6177L79.5071 33.0592L66.2898 40.132H21.5738L10.7345 32.0983L10.6018 32H10.4368H0.5V23.5903L13.239 13.5H63.3892H247.842L259.474 28.3951L259.624 28.5874H259.868H311.386Z" fill="url(#paint4_linear_69_2)" />
|
||||
<path d="M311.386 28.5874L313.227 30.1761H250.261H250.083L249.946 30.2881L246.609 33H79.743H79.6177L79.5071 33.0592L66.2898 40.132H21.5738L10.7345 32.0983L10.6018 32H10.4368H0.5V23.5903L13.239 13.5H63.3892H247.842L259.474 28.3951L259.624 28.5874H259.868H311.386Z" stroke="url(#paint5_linear_69_2)" />
|
||||
<path d="M311.386 28.5874L313.227 30.1761H250.261H250.083L249.946 30.2881L246.609 33H79.743H79.6177L79.5071 33.0592L66.2898 40.132H21.5738L10.7345 32.0983L10.6018 32H10.4368H0.5V23.5903L13.239 13.5H63.3892H247.842L259.474 28.3951L259.624 28.5874H259.868H311.386Z" stroke="url(#paint6_radial_69_2)" />
|
||||
</g>
|
||||
<path id="边框" opacity="0.673995" fill-rule="evenodd" clip-rule="evenodd" d="M13.065 13H63.3892H248.086L259.868 28.0874H311.572L314.572 30.6761H250.261L246.787 33.5H79.743L66.4152 40.632H21.4087L10.4368 32.5H0V23.3485L13.065 13Z" stroke="url(#paint7_radial_69_2)" stroke-width="0.8" />
|
||||
<path id="å³ä¸‹å¡«å……" opacity="0.280772" fill-rule="evenodd" clip-rule="evenodd" d="M80.5067 33.588H247.176L250.261 30.6761H314.572L321 36.5H74.2219L80.5067 33.588Z" fill="url(#paint8_radial_69_2)" />
|
||||
<path id="å³ä¾§çš„边框" opacity="0.537975" d="M314.379 31.1761L319.703 36H76.4904L80.617 34.088H247.176H247.375L247.519 33.9516L250.46 31.1761H314.379Z" stroke="url(#paint9_linear_69_2)" />
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_69_2" x="14.4086" y="11.9086" width="206.554" height="45.3148" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation="6.7957" result="effect1_foregroundBlur_69_2" />
|
||||
</filter>
|
||||
<radialGradient id="paint0_radial_69_2" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(342.17 47.8361) rotate(-90.0012) scale(43.4067 2195.92)">
|
||||
<stop stop-color="#2E7E8B" />
|
||||
<stop offset="1" stop-color="#031518" stop-opacity="0.01" />
|
||||
</radialGradient>
|
||||
<linearGradient id="paint1_linear_69_2" x1="270.862" y1="41.5354" x2="270.867" y2="39.4646" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0.01" />
|
||||
<stop offset="0.50583" stop-color="white" />
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_69_2" x1="-22.8032" y1="37.1017" x2="26.2181" y2="158.013" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4B6160" />
|
||||
<stop offset="1" stop-color="#4B6160" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<radialGradient id="paint3_radial_69_2" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(25.3302 43.632) rotate(90) scale(27.4693 743.408)">
|
||||
<stop stop-color="white" stop-opacity="0.75" />
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.01" />
|
||||
</radialGradient>
|
||||
<linearGradient id="paint4_linear_69_2" x1="0" y1="42.8618" x2="365.343" y2="42.8618" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#031518" />
|
||||
<stop offset="0.373849" stop-color="#446D9C" />
|
||||
<stop offset="1" stop-color="#2E6376" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear_69_2" x1="-16.027" y1="34.1017" x2="44.9086" y2="139.738" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4B6160" />
|
||||
<stop offset="1" stop-color="#4B6160" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<radialGradient id="paint6_radial_69_2" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(17.8031 40.632) rotate(90) scale(27.4693 522.498)">
|
||||
<stop stop-color="white" stop-opacity="0.75" />
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.01" />
|
||||
</radialGradient>
|
||||
<radialGradient id="paint7_radial_69_2" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(222.887 31.9359) rotate(90) scale(27.4693 825.023)">
|
||||
<stop stop-color="white" stop-opacity="0.75" />
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.01" />
|
||||
</radialGradient>
|
||||
<radialGradient id="paint8_radial_69_2" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(274.857 33.588) rotate(88.0875) scale(111.464 6866.67)">
|
||||
<stop stop-color="#8FD4D2" stop-opacity="0.4" />
|
||||
<stop offset="1" stop-color="#8FD4D2" stop-opacity="0.01" />
|
||||
</radialGradient>
|
||||
<linearGradient id="paint9_linear_69_2" x1="448.845" y1="32.7171" x2="448.8" y2="26.0457" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0.08" />
|
||||
<stop offset="0.461587" stop-color="white" stop-opacity="0.3" />
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_69_2">
|
||||
<rect width="460" height="43.632" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
|
@ -0,0 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
title: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-[460px] h-fit relative my-2">
|
||||
<div class="absolute z-10 text-2xl tracking-wide -top-1 left-14 text-cyan-200">
|
||||
{{ title }}
|
||||
</div>
|
||||
<FrameV1HeaderSvg class="absolute " />
|
||||
<div class="px-4 pt-16">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,18 @@
|
|||
<template>
|
||||
<div class="relative w-full h-[211px]">
|
||||
<div class="w-full h-full p-3">
|
||||
<slot />
|
||||
</div>
|
||||
<svg viewBox="0 0 431 211" class="absolute top-0 left-0 " fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:anim="http://www.w3.org/2000/anim" anim="" anim:transform-origin="50% 50%" anim:duration="1" anim:ease="ease-in-out">
|
||||
<g id="框">
|
||||
<path id="矩形" opacity="0.214979" d="M1 0.5H0.5V1V210V210.5H1H430H430.5V210V1V0.5H430H1Z" stroke="#94CAEB" />
|
||||
<g id="高亮边框">
|
||||
<path id="路径 8" d="M1 10.3191V1H10.7924" stroke="#94CAEB" />
|
||||
<path id="路径 8_2" d="M430 10.3191V1H420.208" stroke="#94CAEB" />
|
||||
<path id="路径 8_3" d="M1 200.681V210H10.7924" stroke="#94CAEB" />
|
||||
<path id="路径 8_4" d="M430 200.681V210H420.208" stroke="#94CAEB" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
|
||||
defineProps<{
|
||||
label?:string
|
||||
}>()
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<h1 class="flex items-center gap-1.5 font-semibold text-gray-900 dark:text-white min-w-0">
|
||||
<span class="truncate">
|
||||
<slot>
|
||||
{{ label }}
|
||||
</slot>
|
||||
</span>
|
||||
</h1>
|
||||
</template>
|
|
@ -0,0 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Icon />
|
||||
</template>
|
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<header class="relative flex w-full">
|
||||
<Background class="z-10" />
|
||||
<div class="absolute top-0 left-0 z-10 flex items-end h-full pl-[150px] gap-[100px] pb-[32px]">
|
||||
<div class="pb-[12px]">
|
||||
<h1 class="text-3xl font-bold">
|
||||
农村废弃物特征数据库
|
||||
</h1>
|
||||
<p class="opacity-60">
|
||||
Rural waste characteristics database
|
||||
</p>
|
||||
</div>
|
||||
<Tabs
|
||||
:links="[
|
||||
{ name: '首页', to: '/', icon: 'solar:airbuds-right-bold-duotone' },
|
||||
{ name: '资源化技术', to: '/resource_technology', icon: 'solar:airbuds-right-bold-duotone' },
|
||||
{ name: '政策', to: '/policy', icon: 'solar:airbuds-right-bold-duotone' },
|
||||
{
|
||||
name: '新闻', to: '/news', icon: 'solar:airbuds-right-bold-duotone',
|
||||
},
|
||||
{
|
||||
name: '标准', to: '/standard', icon: 'solar:airbuds-right-bold-duotone',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
|
@ -0,0 +1,74 @@
|
|||
<script setup lang="ts">
|
||||
import Help from '~/components/common/modal/header/help.vue'
|
||||
import Info from '~/components/common/modal/header/info.vue'
|
||||
import Version from '~/components/common/modal/header/version/version.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const infoModal = useModal(Info)
|
||||
const helpModal = useModal(Help)
|
||||
const versionModal = useModal(Version)
|
||||
|
||||
const items = [
|
||||
[{
|
||||
label: 'ben@example.com',
|
||||
slot: 'account',
|
||||
disabled: true,
|
||||
}],
|
||||
[{
|
||||
label: t('menu.setting'),
|
||||
icon: 'i-carbon-settings',
|
||||
click: () => infoModal.open(), // isInfoModal.value = true,
|
||||
}],
|
||||
[{
|
||||
label: t('menu.help'),
|
||||
icon: 'i-carbon-help',
|
||||
click: () => helpModal.open(),
|
||||
}, {
|
||||
label: t('menu.version'),
|
||||
icon: 'i-carbon-account',
|
||||
click: () => versionModal.open(),
|
||||
}],
|
||||
[{
|
||||
label: t('menu.system.user'),
|
||||
icon: 'i-carbon-user-multiple',
|
||||
}, {
|
||||
label: t('menu.system.admin'),
|
||||
icon: 'i-carbon-user-profile',
|
||||
to: '/admin',
|
||||
}, {
|
||||
label: t('menu.system.jwanfs'),
|
||||
icon: 'i-carbon-ibm-cloud-bare-metal-server',
|
||||
}, {
|
||||
label: t('menu.logout'),
|
||||
icon: 'i-heroicons-arrow-left-on-rectangle',
|
||||
to: '/login',
|
||||
}],
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdown
|
||||
:items="items" :ui="{ item: { disabled: 'cursor-text select-text' } }"
|
||||
:popper="{ placement: 'bottom-start' }"
|
||||
>
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" />
|
||||
|
||||
<template #account="{ item }">
|
||||
<div class="text-left">
|
||||
<p>
|
||||
Signed in as
|
||||
</p>
|
||||
<p class="truncate font-medium text-gray-900 dark:text-white">
|
||||
{{ item.label }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item="{ item }">
|
||||
<span class="truncate">{{ item.label }}</span>
|
||||
|
||||
<UIcon :name="item.icon" class="ms-auto h-4 w-4 flex-shrink-0 text-gray-400 dark:text-gray-500" />
|
||||
</template>
|
||||
</UDropdown>
|
||||
</template>
|
|
@ -0,0 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
sider: any[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CommonModal blur placement="right" class="dark">
|
||||
<template #title>
|
||||
<div class="flex items-center justify-between">
|
||||
<CommonLogo dark />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="h-[calc(100vh-200px)] w-[300px] overflow-auto">
|
||||
<CommonLayoutSider static :sider="sider" class="w-full" />
|
||||
</div>
|
||||
</CommonModal>
|
||||
</template>
|
|
@ -0,0 +1,56 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
const { data, error, pending } = useAsyncData(
|
||||
'user.storage', () => dashboardApi.storage({
|
||||
isAll: 1
|
||||
}), {
|
||||
default: () => ({
|
||||
Name: '',
|
||||
FileNum: 0,
|
||||
TotalSize: 0,
|
||||
QuotaSize: 0,
|
||||
UsagePercent: 0,
|
||||
DocumentTypeSize: 0,
|
||||
MusicTypeSize: 0,
|
||||
PictureTypeSize: 0,
|
||||
VedioTypeSize: 0,
|
||||
OtherTypeSize: 0,
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
const size = computed(() => {
|
||||
return [
|
||||
{ name: '文档', value: data.value.DocumentTypeSize, icon: 'i-heroicons-document-text', color: 'indigo' },
|
||||
{ name: '音频', value: data.value.MusicTypeSize, icon: 'i-heroicons-musical-note', color: 'green' },
|
||||
{ name: '图片', value: data.value.PictureTypeSize, icon: 'i-heroicons-photo', color: 'red' },
|
||||
{ name: '视频', value: data.value.VedioTypeSize, icon: 'i-heroicons-film', color: 'blue' },
|
||||
{ name: '其他', value: data.value.OtherTypeSize, icon: 'i-heroicons-document', color: 'gray' },
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UCard class="!bg-gray/5 ">
|
||||
<UMeterGroup :max="data.QuotaSize">
|
||||
<template #indicator>
|
||||
<div class="flex justify-between gap-1.5 text-sm">
|
||||
<p class="text-gray-500 dark:text-gray-400">
|
||||
{{ byteTrans.merticKB(data.TotalSize) }} 已使用
|
||||
</p>
|
||||
<p class="text-gray-500 dark:text-gray-400">
|
||||
{{ byteTrans.merticKB(data.QuotaSize) }} 配额空间
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<UMeter
|
||||
v-for="item in size"
|
||||
:key="item.name"
|
||||
:value="item.value"
|
||||
:color="item.color"
|
||||
:label="item.name"
|
||||
:icon="item.icon"
|
||||
/>
|
||||
</UMeterGroup>
|
||||
</UCard>
|
||||
</template>
|
|
@ -0,0 +1,49 @@
|
|||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
|
||||
defineProps<{
|
||||
sider?: Config['userSider']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col h-full max-h-screen gap-2">
|
||||
<div class="flex h-18 items-center border-b px-4 lg:h-[60px] lg:px-6">
|
||||
<NuxtLink to="/user/data/dashboard" class="flex justify-center w-full">
|
||||
<Logo />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div v-for="item in sider" :key="item.group" class="px-4 mb-8">
|
||||
<div class="pb-4 text-sm font-bold text-gray-500 opacity-40 dark:text-gray-100">
|
||||
{{ item.group }}
|
||||
</div>
|
||||
|
||||
<!-- <UDashboardSidebarLinks :links="item.list" /> -->
|
||||
<div v-for="(link, index) in item.list" :key="index">
|
||||
<NuxtLink :to="link.to" class="block w-full" :target="link.target">
|
||||
<Button
|
||||
:variant="$route.fullPath.includes(link.to) ? 'secondary' : 'ghost'"
|
||||
class="justify-start w-full gap-2 my-1"
|
||||
:trailing="false"
|
||||
>
|
||||
<Icon :icon="link.icon" />
|
||||
{{ link.label }}
|
||||
</Button>
|
||||
</NuxtLink>
|
||||
<div>
|
||||
<slot :name="link.key" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 mt-auto">
|
||||
<div class="w-full md:space-y-4">
|
||||
<LayoutSiderStorage class="hidden xl:block" />
|
||||
<UDivider class="sticky bottom-0" />
|
||||
<!-- ~/components/UserDropdown.vue -->
|
||||
<!-- <LayoutSiderUser /> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,80 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
const { userInfo } = useUserStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const items = [
|
||||
[{
|
||||
label: userInfo?.Email,
|
||||
slot: 'account',
|
||||
}],
|
||||
[{
|
||||
label: t('menu.setting'),
|
||||
icon: 'i-carbon-settings',
|
||||
to: '/user/settings',
|
||||
}],
|
||||
// [{
|
||||
// label: t('menu.help'),
|
||||
// icon: 'i-carbon-help',
|
||||
// to: '/user/settings/help/getting-started',
|
||||
// }, {
|
||||
// label: t('menu.version'),
|
||||
// icon: 'i-carbon-account',
|
||||
// // click: () => versionModal.open(),
|
||||
// }],
|
||||
[
|
||||
// {
|
||||
// label: t('menu.system.user'),
|
||||
// icon: 'i-carbon-user-multiple',
|
||||
// }, {
|
||||
// label: t('menu.system.admin'),
|
||||
// icon: 'i-carbon-user-profile',
|
||||
// to: '/admin',
|
||||
// },
|
||||
// {
|
||||
// label: t('menu.system.jwanfs'),
|
||||
// icon: 'i-carbon-ibm-cloud-bare-metal-server',
|
||||
// },
|
||||
{
|
||||
label: t('menu.logout'),
|
||||
icon: 'i-heroicons-arrow-left-on-rectangle',
|
||||
to: '/login',
|
||||
}],
|
||||
]
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UDropdown mode="hover" :items="items" class="w-full">
|
||||
<template #default="{ open }">
|
||||
<UButton
|
||||
color="gray"
|
||||
variant="ghost"
|
||||
class="w-full"
|
||||
:label="userInfo?.NickName"
|
||||
:class="[open && 'bg-gray-50 dark:bg-gray-800']"
|
||||
>
|
||||
<template #leading>
|
||||
<UAvatar src="" :alt="userInfo?.NickName" size="2xs" />
|
||||
</template>
|
||||
</UButton>
|
||||
</template>
|
||||
|
||||
<template #account>
|
||||
<div class="w-full ">
|
||||
<div class="text-left pb-2">
|
||||
<p>
|
||||
登录于
|
||||
</p>
|
||||
<p class="truncate font-medium text-gray-900 dark:text-white">
|
||||
{{ userInfo?.Email }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<LayoutSiderStorage class="xl:hidden block" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UDropdown>
|
||||
</template>
|
|
@ -0,0 +1,27 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
import { useImage } from '@vueuse/core'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
src?: string
|
||||
}>()
|
||||
|
||||
|
||||
const { isLoading } = useImage({ src: props.src })
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
<template>
|
||||
<AspectRatio :ratio="16 / 9">
|
||||
<UIcon v-if="isLoading" name="i-mdi-loading " class="text-4xl animate-spin" />
|
||||
<img
|
||||
v-else
|
||||
:src="src"
|
||||
alt="Image"
|
||||
class="rounded-md object-cover h-full w-full"
|
||||
>
|
||||
</AspectRatio>
|
||||
</template>
|
|
@ -0,0 +1,6 @@
|
|||
<template>
|
||||
<div class="h-28 w-38">
|
||||
<img class="h-full w-full hidden dark:block" src="/osca_bg.svg" />
|
||||
<img class="block h-full w-full dark:hidden" src="/osca_bg.svg" />
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,40 @@
|
|||
<script setup lang="ts">
|
||||
const { blur } = defineProps<{
|
||||
title: string
|
||||
subtitle?: string
|
||||
hiddenClose?: boolean
|
||||
blur?: boolean
|
||||
}>()
|
||||
const isExternalOpen = defineModel<boolean>()
|
||||
|
||||
const overlayBg = blur ? 'bg-slate-950/10 backdrop-blur-sm' : 'bg-slate-950/10 backdrop-blur-none'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UModal v-model="isExternalOpen" :ui="{ overlay: { background: overlayBg } }">
|
||||
<UCard :ui="{ ring: '', divide: subtitle ? 'divide-y' : 'divide-y-0' }">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="flex justify-between">
|
||||
<h4 class="typography-h4">
|
||||
{{ title }}
|
||||
</h4>
|
||||
</div>
|
||||
<p v-if="subtitle" class="typography-muted">
|
||||
{{ subtitle }}
|
||||
</p>
|
||||
</div>
|
||||
<UButton v-if="!hiddenClose" color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="isExternalOpen = false" />
|
||||
</div>
|
||||
</template>
|
||||
<slot />
|
||||
|
||||
<template #footer>
|
||||
<div class="flex items-center justify-end">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
</UModal>
|
||||
</template>
|
|
@ -0,0 +1,83 @@
|
|||
<script setup lang="tsx">
|
||||
const menus = [
|
||||
{
|
||||
name: '邮箱',
|
||||
value: '1124124@qq.com',
|
||||
icon: 'i-carbon-email',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CommonModal :title="$t('menu.setting')" :subtitle="$t('menu.setting.desc')" prevent-close blur>
|
||||
<div>
|
||||
<InlineComponent :tset="1" />
|
||||
<div class="flex flex-col items-center justify-center gap-2">
|
||||
<UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" size="2xl" alt="Avatar" />
|
||||
<div class="flex items-center">
|
||||
<p class="font-bold typography-lead">
|
||||
admin
|
||||
</p>
|
||||
<UButton icon="i-heroicons-pencil-square" size="sm" variant="soft" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="grid grid-cols-2 w-full gap-2">
|
||||
<UCard v-for="item in menus" :key="item.name" class="col-span-1">
|
||||
<UIcon :name="item.icon" class="float-right h-8 w-8" />
|
||||
<p class="font-bold typography-large">
|
||||
{{ item.name }}
|
||||
</p>
|
||||
<p class="typography-muted">
|
||||
{{ item.value }}
|
||||
</p>
|
||||
</UCard>
|
||||
<UCard class="col-span-1">
|
||||
<div class="i-solar-mailbox-bold-duotone float-right h-8 w-8" />
|
||||
<p class="font-bold typography-large">
|
||||
邮箱
|
||||
</p>
|
||||
<p class="typography-muted">
|
||||
123456789@qq.com
|
||||
</p>
|
||||
<small class="font-bold opacity-50 typography-small">
|
||||
修改
|
||||
</small>
|
||||
</UCard>
|
||||
<UCard class="col-span-1">
|
||||
<div class="i-solar-smartphone-2-bold-duotone float-right h-8 w-8" />
|
||||
<p class="font-bold typography-large">
|
||||
手机
|
||||
</p>
|
||||
<p class="typography-muted">
|
||||
123456789@qq.com
|
||||
</p>
|
||||
<small class="font-bold opacity-50 typography-small">
|
||||
修改
|
||||
</small>
|
||||
</UCard>
|
||||
<UCard class="col-span-1">
|
||||
<div class="i-solar-mailbox-bold-duotone float-right h-8 w-8" />
|
||||
<p class="font-bold typography-large">
|
||||
双因素认证
|
||||
</p>
|
||||
<p class="typography-muted">
|
||||
已开启
|
||||
</p>
|
||||
<UToggle on-icon="i-heroicons-check-20-solid" off-icon="i-heroicons-x-mark-20-solid" size="sm" />
|
||||
</UCard>
|
||||
<UCard class="col-span-1">
|
||||
<div class="i-solar-mailbox-bold-duotone float-right h-8 w-8" />
|
||||
<p class="font-bold typography-large">
|
||||
高能所统一认证
|
||||
</p>
|
||||
<p class="typography-muted">
|
||||
已绑定
|
||||
</p>
|
||||
<UToggle on-icon="i-heroicons-check-20-solid" off-icon="i-heroicons-x-mark-20-solid" size="sm" />
|
||||
</UCard>
|
||||
</div>
|
||||
</template>
|
||||
</CommonModal>
|
||||
</template>
|
|
@ -0,0 +1,35 @@
|
|||
<script setup lang="ts">
|
||||
const items = [{
|
||||
label: 'V1.0.1-12',
|
||||
icon: 'i-heroicons-information-circle',
|
||||
defaultOpen: true,
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.',
|
||||
}, {
|
||||
label: 'V1.0.1-4',
|
||||
icon: 'i-heroicons-arrow-down-tray',
|
||||
disabled: true,
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.',
|
||||
}, {
|
||||
label: 'V1.0.1-0',
|
||||
icon: 'i-heroicons-eye-dropper',
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.',
|
||||
}, {
|
||||
label: 'V1.0.0-0 (大版本)',
|
||||
icon: 'i-heroicons-rectangle-group',
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.',
|
||||
}, {
|
||||
label: 'V0.0.1-4',
|
||||
icon: 'i-heroicons-square-3-stack-3d',
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.',
|
||||
}, {
|
||||
label: 'V0.0.1-1',
|
||||
icon: 'i-heroicons-wrench-screwdriver',
|
||||
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.',
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CommonModal title="功能介绍">
|
||||
<UAccordion :items="items" class="max-w-[500px]" />
|
||||
</CommonModal>
|
||||
</template>
|
|
@ -0,0 +1,10 @@
|
|||
<template>
|
||||
<CommonModal title="反馈">
|
||||
<UTextarea placeholder="Search..." class="min-w-[300px]" />
|
||||
<template #footer>
|
||||
<UButton>
|
||||
提交
|
||||
</UButton>
|
||||
</template>
|
||||
</CommonModal>
|
||||
</template>
|
|
@ -0,0 +1,35 @@
|
|||
<script setup lang="ts">
|
||||
import Feedback from '~/components/common/modal/header/version/feedback.vue'
|
||||
import Feature from '~/components/common/modal/header/version/feature.vue'
|
||||
|
||||
const featureModal = useModal(Feature)
|
||||
const feedbackModal = useModal(Feedback)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CommonModal>
|
||||
<div class="h-[100px] min-w-[300px] flex flex-col items-center justify-center gap-4">
|
||||
<div class="flex justify-between">
|
||||
<h4 class="typography-h4">
|
||||
{{ $t("menu.version") }}
|
||||
</h4>
|
||||
</div>
|
||||
<p class="typography-muted">
|
||||
{{ $t("menu.version.desc") }}
|
||||
</p>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<UButton variant="link" @click="featureModal.open()">
|
||||
功能介绍
|
||||
</UButton>
|
||||
<UDivider orientation="vertical" />
|
||||
<UButton variant="link" @click="feedbackModal.open()">
|
||||
反馈
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</CommonModal>
|
||||
|
||||
<!-- <UserModalHeaderVersionFeedback v-model="isFeedbackOpen" />
|
||||
<UserModalHeaderVersionFeature v-model="isFeatureOpen" /> -->
|
||||
</template>
|
|
@ -0,0 +1,144 @@
|
|||
<script setup lang="ts">
|
||||
import { VueFinalModal } from 'vue-final-modal'
|
||||
|
||||
import { breakpointsTailwind } from '@vueuse/core'
|
||||
|
||||
const { blur, placement = 'center', preventClose, hideOverlay } = defineProps<{
|
||||
title?: string
|
||||
subtitle?: string
|
||||
hiddenClose?: boolean
|
||||
blur?: boolean
|
||||
placement?: 'bottom' | 'center' | 'top' | 'right' | 'left'
|
||||
preventClose?: boolean
|
||||
fullScreen?: boolean
|
||||
blank?: boolean
|
||||
hideOverlay?: boolean
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', modelValue: boolean): void
|
||||
}>()
|
||||
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||
const placementRef = computed(() => {
|
||||
if (placement !== 'center')
|
||||
return placement
|
||||
|
||||
if (breakpoints.smaller('sm').value)
|
||||
return 'bottom'
|
||||
else
|
||||
return 'center'
|
||||
})
|
||||
|
||||
function getInitialValues() {
|
||||
const opts = {
|
||||
teleportTo: 'body',
|
||||
modelValue: false,
|
||||
displayDirective: 'if' as 'if' | 'show' | 'visible',
|
||||
hideOverlay: !!hideOverlay,
|
||||
overlayTransition: 'vfm-fade',
|
||||
contentTransition: 'vfm-fade',
|
||||
clickToClose: !preventClose,
|
||||
escToClose: true,
|
||||
background: 'non-interactive' as 'non-interactive' | 'interactive',
|
||||
lockScroll: true,
|
||||
reserveScrollBarGap: true,
|
||||
swipeToClose: 'none' as 'right' | 'left' | 'none' | 'down' | 'up',
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
const options = ref(getInitialValues())
|
||||
|
||||
effect(() => {
|
||||
switch (placementRef.value) {
|
||||
case 'bottom':
|
||||
options.value.swipeToClose = 'down'
|
||||
options.value.contentTransition = 'vfm-slide-down'
|
||||
break
|
||||
case 'top':
|
||||
options.value.swipeToClose = 'up'
|
||||
options.value.contentTransition = 'vfm-slide-up'
|
||||
break
|
||||
case 'right':
|
||||
options.value.swipeToClose = 'right'
|
||||
options.value.contentTransition = 'vfm-slide-right'
|
||||
break
|
||||
case 'left':
|
||||
options.value.swipeToClose = 'left'
|
||||
options.value.contentTransition = 'vfm-slide-left'
|
||||
break
|
||||
default:
|
||||
options.value.swipeToClose = 'none'
|
||||
options.value.contentTransition = 'vfm-fade'
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
const contentClass = {
|
||||
center: 'absolute top-1/2 left-1/2 -translate-1/2 max-h-screen',
|
||||
bottom: 'absolute bottom-0 w-screen',
|
||||
top: 'absolute top-0 w-screen',
|
||||
right: 'absolute right-0 h-screen',
|
||||
left: 'absolute left-0 h-screen',
|
||||
}
|
||||
|
||||
const cardClass = {
|
||||
center: '',
|
||||
bottom: 'w-screen max-h-[90vh] overflow-y-auto rounded-b-none',
|
||||
top: ' w-screen rounded-t-none max-h-[90vh] overflow-y-auto ',
|
||||
right: ' h-screen rounded-r-none max-w-[90vh] overflow-w-auto ',
|
||||
left: 'h-screen rounded-l-none max-w-[90vh] overflow-w-auto ',
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueFinalModal
|
||||
class="overflow-y-auto"
|
||||
:content-class="[contentClass[placementRef]]"
|
||||
:overlay-class="['bg-black/20 fixed bottom-0 h-screen', blur && 'backdrop-blur-sm ']"
|
||||
:teleport-to="options.teleportTo"
|
||||
:display-directive="options.displayDirective"
|
||||
:hide-overlay="options.hideOverlay"
|
||||
:overlay-transition="options.overlayTransition"
|
||||
:content-transition="options.contentTransition"
|
||||
:click-to-close="options.clickToClose"
|
||||
:esc-to-close="options.escToClose"
|
||||
:background="options.background"
|
||||
:lock-scroll="options.lockScroll"
|
||||
:reserve-scroll-bar-gap="options.reserveScrollBarGap"
|
||||
:swipe-to-close="options.swipeToClose"
|
||||
@update:model-value="val => emit('update:modelValue', val)"
|
||||
>
|
||||
<div :class="[cardClass[placementRef], fullScreen ? 'w-screen h-screen ' : 'md:py-5']">
|
||||
<UCard class="h-full w-full" :ui="{ ring: '', divide: subtitle ? 'divide-y' : 'divide-y-0' }">
|
||||
<template v-if="!blank" #header>
|
||||
<slot name="header">
|
||||
<div class="du flex items-center justify-between">
|
||||
<div>
|
||||
<slot name="title">
|
||||
<div class="flex justify-between">
|
||||
<h4 class="typography-h4">
|
||||
{{ title }}
|
||||
</h4>
|
||||
</div>
|
||||
<p v-if="subtitle" class="typography-muted">
|
||||
{{ subtitle }}
|
||||
</p>
|
||||
</slot>
|
||||
</div>
|
||||
<UButton v-if="!hiddenClose" color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @pointerup="emit('update:modelValue', false)" />
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<template v-if="!blank" #footer>
|
||||
<div class="flex items-center justify-end">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
</div>
|
||||
</VueFinalModal>
|
||||
</template>
|
|
@ -0,0 +1,27 @@
|
|||
<script setup lang="ts">
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', modelValue: boolean): void
|
||||
(e: 'success'): void
|
||||
}>()
|
||||
const selected = ref(false)
|
||||
|
||||
const onSuccess = () => {
|
||||
emit('success')
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ModalBase title="服务条款" prevent-close @update:model-value="val => emit('update:modelValue', val)">
|
||||
<ContentDoc path="/privacy" class="prose-primary my-2 rounded bg-gray/10 p-6 prose dark:prose-invert" />
|
||||
<UCheckbox v-model="selected" name="notifications" label="我同意并接受以上条款服务内容" />
|
||||
|
||||
<template #footer>
|
||||
<UTooltip :popper="{ placement: 'top' }" :text="selected ? '进入系统' : '请先确认您已经同意了条款服务内容'">
|
||||
<UButton size="lg" class="px-10" :disabled="!selected" @click="onSuccess">
|
||||
确认
|
||||
</UButton>
|
||||
</UTooltip>
|
||||
</template>
|
||||
</ModalBase>
|
||||
</template>
|
|
@ -0,0 +1,72 @@
|
|||
<script lang="tsx" setup>
|
||||
const { as = 'h1', title } = defineProps<{
|
||||
icon?: string
|
||||
breadcrumb?: any[]
|
||||
title?: string
|
||||
as?: 'h1' | 'h2' | 'h3' | 'h4'
|
||||
description?: string
|
||||
link?: string
|
||||
}>()
|
||||
|
||||
const space = {
|
||||
h1: 'space-y-3',
|
||||
h2: 'space-y-2',
|
||||
h3: 'space-y-1',
|
||||
h4: 'space-y-0',
|
||||
}
|
||||
|
||||
function Title() {
|
||||
switch (as) {
|
||||
case 'h1':
|
||||
return (
|
||||
<h1 class="typography-h1">
|
||||
{title }
|
||||
</h1>
|
||||
)
|
||||
case 'h2':
|
||||
return (
|
||||
<h2 class="typography-h2">
|
||||
{title }
|
||||
</h2>
|
||||
)
|
||||
case 'h3':
|
||||
return (
|
||||
<h3 class="typography-h3">
|
||||
{title }
|
||||
</h3>
|
||||
)
|
||||
case 'h4':
|
||||
return (
|
||||
<h4 class="typography-h4">
|
||||
{title }
|
||||
</h4>
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center justify-between px-5 my-6">
|
||||
<div class="flex items-center gap-4">
|
||||
<slot name="icon">
|
||||
{{ icon }}
|
||||
</slot>
|
||||
<div :class="space[as]">
|
||||
<slot name="headline">
|
||||
<UBreadcrumb :links="breadcrumb" />
|
||||
</slot>
|
||||
<slot name="title">
|
||||
<Title />
|
||||
</slot>
|
||||
<slot name="description">
|
||||
<p class="-z-1 typography-muted">
|
||||
{{ description }}
|
||||
</p>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,28 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
defineProps<{
|
||||
title?: string
|
||||
badge?: string|number
|
||||
}>()
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<UDashboardToolbar class=" border-0 my-1 mt-2">
|
||||
<template #left>
|
||||
<HeadingH1>
|
||||
{{ title }}
|
||||
</HeadingH1>
|
||||
<UBadge variant="soft">
|
||||
{{ badge }}
|
||||
</UBadge>
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<slot
|
||||
name="right"
|
||||
/>
|
||||
</template>
|
||||
</UDashboardToolbar>
|
||||
</template>
|
|
@ -0,0 +1,137 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
// props: ['dataList'],
|
||||
data() {
|
||||
return {
|
||||
dataList:[{
|
||||
thumb:'https://img.yzcdn.cn/vant/apple-1.jpg',
|
||||
title:'Apple iPhone 12 (A2228) 64GB 黑色 移动联通电信4G手机',
|
||||
subtitle:'Apple iPhone 12 (A2228) 64GB 黑色 移动联通电信4G手机',
|
||||
marketprice:5999,
|
||||
id:1
|
||||
},{
|
||||
thumb:'https://img.yzcdn.cn/vant/apple-2.jpg',
|
||||
title:'Apple iPhone 12 (A2228) 64GB 黑色 移动联通电信4G手机',
|
||||
subtitle:'Apple iPhone 12 (A2228) 64GB 黑色 移动联通电信4G手机',
|
||||
marketprice:5999,
|
||||
id:2
|
||||
},{
|
||||
thumb:'https://img.yzcdn.cn/vant/apple-3.jpg',
|
||||
title:'Apple iPhone 12 (A2228) 64GB 黑色 移动联通电信4G手机',
|
||||
subtitle:'Apple iPhone 12 (A2228) 64GB 黑色 移动联通电信4G手机',
|
||||
marketprice:5999,
|
||||
id:3
|
||||
},{
|
||||
thumb:'https://img.yzcdn.cn/vant/apple-4.jpg',
|
||||
title:'Apple iPhone 12 (A2228) 64GB 黑色 移动联通电信4G手机',
|
||||
subtitle:'Apple iPhone 12 (A2228) 64GB 黑色 移动联通电信4G手机',
|
||||
marketprice:5999,
|
||||
id:4
|
||||
}],
|
||||
calleft: 0,
|
||||
speed:1
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
widthData(){
|
||||
return 240 * Number(this.dataList.length*2)
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.move()
|
||||
},
|
||||
mounted() {
|
||||
const imgBox = document.getElementsByClassName('imgBoxoul')[0]
|
||||
imgBox.innerHTML += imgBox.innerHTML
|
||||
},
|
||||
methods: {
|
||||
//移动
|
||||
move() {
|
||||
this.timer = setInterval(this.starmove, 20)
|
||||
},
|
||||
//开始移动
|
||||
starmove() {
|
||||
this.calleft -= 1.2//*this.speed
|
||||
if (this.calleft <= -1150) {
|
||||
this.calleft = 0
|
||||
}
|
||||
},
|
||||
//鼠标悬停时停止移动
|
||||
stopmove() {
|
||||
clearInterval(this.timer)
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div class="threeImg">
|
||||
<div class="Containt">
|
||||
<ul :style="{'left':calleft + 'px', width: widthData + 'px'} " class="imgBoxoul" @mouseover="stopmove()" @mouseout="move()">
|
||||
<li v-for="(item,index) in dataList" :key="index" @click="gotodetails(item.id)">
|
||||
<img :src="item.thumb">
|
||||
<div class="item-content">
|
||||
<p class="item-title">
|
||||
{{ item.title }}
|
||||
</p>
|
||||
<div class="item-detail line-2">
|
||||
{{ item.subtitle }}
|
||||
</div>
|
||||
<p class="item-price">
|
||||
¥{{ item.marketprice }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
.threeImg {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.threeImg .Containt ul {
|
||||
margin: 0 auto;
|
||||
width: 2400px;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.threeImg .Containt ul li {
|
||||
float: left;
|
||||
width: 220px;
|
||||
height: 350px;
|
||||
margin-right: 20px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.threeImg .Containt ul li img {
|
||||
width: 100%;
|
||||
height: 263px;
|
||||
}
|
||||
|
||||
.Containt {
|
||||
position: relative;
|
||||
padding: 60px 0;
|
||||
overflow-y: auto;
|
||||
width: 1200px;
|
||||
height: 365px;
|
||||
overflow: hidden;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,18 @@
|
|||
<script setup lang="ts">
|
||||
const { toggleDashboardSearch } = useUIState()
|
||||
const { metaSymbol } = useShortcuts()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton color="gray" icon="i-heroicons-magnifying-glass-16-solid" variant="solid" @click="toggleDashboardSearch()">
|
||||
<div class="w-[100px] text-left hidden md:block">
|
||||
{{ $t('search') }}
|
||||
</div>
|
||||
<template #trailing>
|
||||
<div class=" items-center gap-0.5 hidden md:flex">
|
||||
<UKbd>{{ metaSymbol }}</UKbd>
|
||||
<UKbd>K</UKbd>
|
||||
</div>
|
||||
</template>
|
||||
</UButton>
|
||||
</template>
|
|
@ -0,0 +1,45 @@
|
|||
<script setup lang="ts">
|
||||
const { size, unit = 'KB' } = defineProps<{
|
||||
size: number
|
||||
unit?: 'B' | 'KB' | 'MB' | 'GB' | 'TB'
|
||||
}>()
|
||||
|
||||
const colors = {
|
||||
Null: 'slate',
|
||||
B: 'lime',
|
||||
KB: 'emerald',
|
||||
MB: 'sky',
|
||||
GB: 'indigo',
|
||||
TB: 'purple',
|
||||
PB: 'pink',
|
||||
EB: 'rose',
|
||||
ZB: 'orange',
|
||||
YB: 'yellow',
|
||||
}
|
||||
|
||||
type Unit = keyof typeof colors
|
||||
|
||||
const unitFunc = {
|
||||
B:mertic,
|
||||
KB:merticKB,
|
||||
MB:merticMB,
|
||||
GB:merticGB,
|
||||
TB:merticTB,
|
||||
}
|
||||
|
||||
const { color, value } = unitFunc[unit]<{
|
||||
color: string
|
||||
value: string
|
||||
}>(Number(size), (num, unit: Unit) => {
|
||||
return {
|
||||
color: colors[unit],
|
||||
value: num.toFixed(0) + unit,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UBadge :color="color" variant="solid" class="shadow-sm shadow-inner opacity-90">
|
||||
{{ value }}
|
||||
</UBadge>
|
||||
</template>
|
|
@ -0,0 +1,59 @@
|
|||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
title: string
|
||||
value: number
|
||||
max?: number
|
||||
}>()
|
||||
|
||||
const percent = computed(() => {
|
||||
return (props.value / (props.max ?? 100)) * 100
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-[32px] flex items-center justify-between">
|
||||
<div class="w-[140px] opacity-60 truncate">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="relative w-[333px] h-[4px]">
|
||||
<div class="absolute text-[#E4F3FF] text-[15px] -top-2 " :style="`padding-left:${percent}%`">
|
||||
<span class="pl-2">{{ value }}</span>
|
||||
</div>
|
||||
|
||||
<svg
|
||||
viewBox="0 0 333 4" class="h-[4px] absolute top-0 left-0 z-10" :style="`width:${percent}%`" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:anim="http://www.w3.org/2000/anim" anim=""
|
||||
anim:transform-origin="50% 50%" anim:duration="1" anim:ease="ease-in-out"
|
||||
>
|
||||
<rect
|
||||
id="矩形" opacity="0.7" x="0.00170898" y="0.529053" width="333" height="3" rx="1.5"
|
||||
fill="url(#paint0_linear_0_10711)" stroke="url(#paint1_linear_0_10711)"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_0_10711" x1="164.549" y1="0.551191" x2="164.346" y2="7.98045"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#A8DEFF" />
|
||||
<stop offset="1" stop-color="#284257" stop-opacity="0.5" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_0_10711" x1="225.089" y1="1.99811" x2="0.00170898" y2="1.99811"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="white" stop-opacity="0.598517" />
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.01" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<svg
|
||||
viewBox="0 0 333 4" class="absolute top-0 left-0 -z-[1]" fill="none" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:anim="http://www.w3.org/2000/anim" anim="" anim:transform-origin="50% 50%" anim:duration="1"
|
||||
anim:ease="ease-in-out"
|
||||
>
|
||||
<rect id="矩形" opacity="0.7" x="0.00170898" y="0.529053" width="333" height="3" rx="1.5" fill="#2D4D67" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,106 @@
|
|||
<script setup lang="ts">
|
||||
import type {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
} from '@tanstack/vue-table'
|
||||
import {
|
||||
FlexRender,
|
||||
getCoreRowModel,
|
||||
getFacetedRowModel,
|
||||
getFacetedUniqueValues,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useVueTable,
|
||||
} from '@tanstack/vue-table'
|
||||
|
||||
import { ref } from 'vue'
|
||||
import type { Task } from '../data/schema'
|
||||
import DataTablePagination from './DataTablePagination.vue'
|
||||
import DataTableToolbar from './DataTableToolbar.vue'
|
||||
import { valueUpdater } from '@/lib/utils'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/lib/registry/new-york/ui/table'
|
||||
|
||||
interface DataTableProps {
|
||||
columns: ColumnDef<Task, any>[]
|
||||
data: Task[]
|
||||
}
|
||||
const props = defineProps<DataTableProps>()
|
||||
|
||||
const sorting = ref<SortingState>([])
|
||||
const columnFilters = ref<ColumnFiltersState>([])
|
||||
const columnVisibility = ref<VisibilityState>({})
|
||||
const rowSelection = ref({})
|
||||
|
||||
const table = useVueTable({
|
||||
get data() { return props.data },
|
||||
get columns() { return props.columns },
|
||||
state: {
|
||||
get sorting() { return sorting.value },
|
||||
get columnFilters() { return columnFilters.value },
|
||||
get columnVisibility() { return columnVisibility.value },
|
||||
get rowSelection() { return rowSelection.value },
|
||||
},
|
||||
enableRowSelection: true,
|
||||
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
||||
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
|
||||
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
|
||||
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFacetedRowModel: getFacetedRowModel(),
|
||||
getFacetedUniqueValues: getFacetedUniqueValues(),
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<DataTableToolbar :table="table" />
|
||||
<div class="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
|
||||
<TableHead v-for="header in headerGroup.headers" :key="header.id">
|
||||
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.header" :props="header.getContext()" />
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<template v-if="table.getRowModel().rows?.length">
|
||||
<TableRow
|
||||
v-for="row in table.getRowModel().rows"
|
||||
:key="row.id"
|
||||
:data-state="row.getIsSelected() && 'selected'"
|
||||
>
|
||||
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id">
|
||||
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</template>
|
||||
|
||||
<TableRow v-else>
|
||||
<TableCell
|
||||
:colspan="columns.length"
|
||||
class="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<DataTablePagination :table="table" />
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,69 @@
|
|||
<script setup lang="ts">
|
||||
import type { Column } from '@tanstack/vue-table'
|
||||
import type { Task } from '../data/schema'
|
||||
import ArrowDownIcon from '~icons/radix-icons/arrow-down'
|
||||
import ArrowUpIcon from '~icons/radix-icons/arrow-up'
|
||||
import CaretSortIcon from '~icons/radix-icons/caret-sort'
|
||||
import EyeNoneIcon from '~icons/radix-icons/eye-none'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/lib/registry/new-york/ui/dropdown-menu'
|
||||
|
||||
interface DataTableColumnHeaderProps {
|
||||
column: Column<Task, any>
|
||||
title: string
|
||||
}
|
||||
|
||||
defineProps<DataTableColumnHeaderProps>()
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="column.getCanSort()" :class="cn('flex items-center space-x-2', $attrs.class ?? '')">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="-ml-3 h-8 data-[state=open]:bg-accent"
|
||||
>
|
||||
<span>{{ title }}</span>
|
||||
<ArrowDownIcon v-if="column.getIsSorted() === 'desc'" class="ml-2 h-4 w-4" />
|
||||
<ArrowUpIcon v-else-if=" column.getIsSorted() === 'asc'" class="ml-2 h-4 w-4" />
|
||||
<CaretSortIcon v-else class="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem @click="column.toggleSorting(false)">
|
||||
<ArrowUpIcon class="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
Asc
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="column.toggleSorting(true)">
|
||||
<ArrowDownIcon class="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
Desc
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem @click="column.toggleVisibility(false)">
|
||||
<EyeNoneIcon class="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||
Hide
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
<div v-else :class="$attrs.class">
|
||||
{{ title }}
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,136 @@
|
|||
<script setup lang="ts">
|
||||
import type { Column } from '@tanstack/vue-table'
|
||||
import type { Component } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import type { Task } from '../data/schema'
|
||||
import PlusCircledIcon from '~icons/radix-icons/plus-circled'
|
||||
import CheckIcon from '~icons/radix-icons/check'
|
||||
|
||||
import { Badge } from '@/lib/registry/new-york/ui/badge'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from '@/lib/registry/new-york/ui/command'
|
||||
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/lib/registry/new-york/ui/popover'
|
||||
import { Separator } from '@/lib/registry/new-york/ui/separator'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface DataTableFacetedFilter {
|
||||
column?: Column<Task, any>
|
||||
title?: string
|
||||
options: {
|
||||
label: string
|
||||
value: string
|
||||
icon?: Component
|
||||
}[]
|
||||
}
|
||||
|
||||
const props = defineProps<DataTableFacetedFilter>()
|
||||
|
||||
const facets = computed(() => props.column?.getFacetedUniqueValues())
|
||||
const selectedValues = computed(() => new Set(props.column?.getFilterValue() as string[]))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button variant="outline" size="sm" class="h-8 border-dashed">
|
||||
<PlusCircledIcon class="mr-2 h-4 w-4" />
|
||||
{{ title }}
|
||||
<template v-if="selectedValues.size > 0">
|
||||
<Separator orientation="vertical" class="mx-2 h-4" />
|
||||
<Badge
|
||||
variant="secondary"
|
||||
class="rounded-sm px-1 font-normal lg:hidden"
|
||||
>
|
||||
{{ selectedValues.size }}
|
||||
</Badge>
|
||||
<div class="hidden space-x-1 lg:flex">
|
||||
<Badge
|
||||
v-if="selectedValues.size > 2"
|
||||
variant="secondary"
|
||||
class="rounded-sm px-1 font-normal"
|
||||
>
|
||||
{{ selectedValues.size }} selected
|
||||
</Badge>
|
||||
|
||||
<template v-else>
|
||||
<Badge
|
||||
v-for="option in options
|
||||
.filter((option) => selectedValues.has(option.value))"
|
||||
:key="option.value"
|
||||
variant="secondary"
|
||||
class="rounded-sm px-1 font-normal"
|
||||
>
|
||||
{{ option.label }}
|
||||
</Badge>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-[200px] p-0" align="start">
|
||||
<Command
|
||||
:filter-function="(list: DataTableFacetedFilter['options'], term) => list.filter(i => i.label.toLowerCase()?.includes(term)) "
|
||||
>
|
||||
<CommandInput :placeholder="title" />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
v-for="option in options"
|
||||
:key="option.value"
|
||||
:value="option"
|
||||
@select="(e) => {
|
||||
console.log(e.detail.value)
|
||||
const isSelected = selectedValues.has(option.value)
|
||||
if (isSelected) {
|
||||
selectedValues.delete(option.value)
|
||||
}
|
||||
else {
|
||||
selectedValues.add(option.value)
|
||||
}
|
||||
const filterValues = Array.from(selectedValues)
|
||||
column?.setFilterValue(
|
||||
filterValues.length ? filterValues : undefined,
|
||||
)
|
||||
}"
|
||||
>
|
||||
<div
|
||||
:class="cn(
|
||||
'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary',
|
||||
selectedValues.has(option.value)
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'opacity-50 [&_svg]:invisible',
|
||||
)"
|
||||
>
|
||||
<CheckIcon :class="cn('h-4 w-4')" />
|
||||
</div>
|
||||
<component :is="option.icon" v-if="option.icon" class="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
<span>{{ option.label }}</span>
|
||||
<span v-if="facets?.get(option.value)" class="ml-auto flex h-4 w-4 items-center justify-center font-mono text-xs">
|
||||
{{ facets.get(option.value) }}
|
||||
</span>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
|
||||
<template v-if="selectedValues.size > 0">
|
||||
<CommandSeparator />
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
:value="{ label: 'Clear filters' }"
|
||||
class="justify-center text-center"
|
||||
@select="column?.setFilterValue(undefined)"
|
||||
>
|
||||
Clear filters
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</template>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</template>
|
|
@ -0,0 +1,93 @@
|
|||
<script setup lang="ts">
|
||||
import type { Table } from '@tanstack/vue-table'
|
||||
import type { Task } from '../data/schema'
|
||||
import ChevronLeftIcon from '~icons/radix-icons/chevron-left'
|
||||
import ChevronRightIcon from '~icons/radix-icons/chevron-right'
|
||||
import DoubleArrowLeftIcon from '~icons/radix-icons/double-arrow-left'
|
||||
import DoubleArrowRightIcon from '~icons/radix-icons/double-arrow-right'
|
||||
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/lib/registry/new-york/ui/select'
|
||||
|
||||
interface DataTablePaginationProps {
|
||||
table: Table<Task>
|
||||
}
|
||||
defineProps<DataTablePaginationProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center justify-between px-2">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
{{ table.getFilteredSelectedRowModel().rows.length }} of
|
||||
{{ table.getFilteredRowModel().rows.length }} row(s) selected.
|
||||
</div>
|
||||
<div class="flex items-center space-x-6 lg:space-x-8">
|
||||
<div class="flex items-center space-x-2">
|
||||
<p class="text-sm font-medium">
|
||||
Rows per page
|
||||
</p>
|
||||
<Select
|
||||
:model-value="`${table.getState().pagination.pageSize}`"
|
||||
@update:model-value="table.setPageSize"
|
||||
>
|
||||
<SelectTrigger class="h-8 w-[70px]">
|
||||
<SelectValue :placeholder="`${table.getState().pagination.pageSize}`" />
|
||||
</SelectTrigger>
|
||||
<SelectContent side="top">
|
||||
<SelectItem v-for="pageSize in [10, 20, 30, 40, 50]" :key="pageSize" :value="`${pageSize}`">
|
||||
{{ pageSize }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="flex w-[100px] items-center justify-center text-sm font-medium">
|
||||
Page {{ table.getState().pagination.pageIndex + 1 }} of
|
||||
{{ table.getPageCount() }}
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
class="hidden h-8 w-8 p-0 lg:flex"
|
||||
:disabled="!table.getCanPreviousPage()"
|
||||
@click="table.setPageIndex(0)"
|
||||
>
|
||||
<span class="sr-only">Go to first page</span>
|
||||
<DoubleArrowLeftIcon class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="h-8 w-8 p-0"
|
||||
:disabled="!table.getCanPreviousPage()"
|
||||
@click="table.previousPage()"
|
||||
>
|
||||
<span class="sr-only">Go to previous page</span>
|
||||
<ChevronLeftIcon class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="h-8 w-8 p-0"
|
||||
:disabled="!table.getCanNextPage()"
|
||||
@click="table.nextPage()"
|
||||
>
|
||||
<span class="sr-only">Go to next page</span>
|
||||
<ChevronRightIcon class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="hidden h-8 w-8 p-0 lg:flex"
|
||||
:disabled="!table.getCanNextPage()"
|
||||
@click="table.setPageIndex(table.getPageCount() - 1)"
|
||||
>
|
||||
<span class="sr-only">Go to last page</span>
|
||||
<DoubleArrowRightIcon class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,65 @@
|
|||
<script setup lang="ts">
|
||||
import type { Row } from '@tanstack/vue-table'
|
||||
import { computed } from 'vue'
|
||||
import { labels } from '../data/data'
|
||||
import { taskSchema } from '../data/schema'
|
||||
import type { Task } from '../data/schema'
|
||||
import DotsHorizontalIcon from '~icons/radix-icons/dots-horizontal'
|
||||
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/lib/registry/new-york/ui/dropdown-menu'
|
||||
|
||||
interface DataTableRowActionsProps {
|
||||
row: Row<Task>
|
||||
}
|
||||
const props = defineProps<DataTableRowActionsProps>()
|
||||
|
||||
const task = computed(() => taskSchema.parse(props.row.original))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button
|
||||
variant="ghost"
|
||||
class="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
|
||||
>
|
||||
<DotsHorizontalIcon class="h-4 w-4" />
|
||||
<span class="sr-only">Open menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" class="w-[160px]">
|
||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem>Make a copy</DropdownMenuItem>
|
||||
<DropdownMenuItem>Favorite</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>Labels</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuRadioGroup :value="task.label">
|
||||
<DropdownMenuRadioItem v-for="label in labels" :key="label.value" :value="label.value">
|
||||
{{ label.label }}
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
Delete
|
||||
<DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
|
@ -0,0 +1,56 @@
|
|||
<script setup lang="ts">
|
||||
import type { Table } from '@tanstack/vue-table'
|
||||
import { computed } from 'vue'
|
||||
import type { Task } from '../data/schema'
|
||||
|
||||
import { priorities, statuses } from '../data/data'
|
||||
import DataTableFacetedFilter from './DataTableFacetedFilter.vue'
|
||||
import DataTableViewOptions from './DataTableViewOptions.vue'
|
||||
import Cross2Icon from '~icons/radix-icons/cross-2'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||
|
||||
interface DataTableToolbarProps {
|
||||
table: Table<Task>
|
||||
}
|
||||
|
||||
const props = defineProps<DataTableToolbarProps>()
|
||||
|
||||
const isFiltered = computed(() => props.table.getState().columnFilters.length > 0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-1 items-center space-x-2">
|
||||
<Input
|
||||
placeholder="Filter tasks..."
|
||||
:model-value="(table.getColumn('title')?.getFilterValue() as string) ?? ''"
|
||||
class="h-8 w-[150px] lg:w-[250px]"
|
||||
@input="table.getColumn('title')?.setFilterValue($event.target.value)"
|
||||
/>
|
||||
<DataTableFacetedFilter
|
||||
v-if="table.getColumn('status')"
|
||||
:column="table.getColumn('status')"
|
||||
title="Status"
|
||||
:options="statuses"
|
||||
/>
|
||||
<DataTableFacetedFilter
|
||||
v-if="table.getColumn('priority')"
|
||||
:column="table.getColumn('priority')"
|
||||
title="Priority"
|
||||
:options="priorities"
|
||||
/>
|
||||
|
||||
<Button
|
||||
v-if="isFiltered"
|
||||
variant="ghost"
|
||||
class="h-8 px-2 lg:px-3"
|
||||
@click="table.resetColumnFilters()"
|
||||
>
|
||||
Reset
|
||||
<Cross2Icon class="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<DataTableViewOptions :table="table" />
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,57 @@
|
|||
<script setup lang="ts">
|
||||
import type { Table } from '@tanstack/vue-table'
|
||||
import { computed } from 'vue'
|
||||
import type { Task } from '../data/schema'
|
||||
import MixerHorizontalIcon from '~icons/radix-icons/mixer-horizontal'
|
||||
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/lib/registry/new-york/ui/dropdown-menu'
|
||||
|
||||
interface DataTableViewOptionsProps {
|
||||
table: Table<Task>
|
||||
}
|
||||
|
||||
const props = defineProps<DataTableViewOptionsProps>()
|
||||
|
||||
const columns = computed(() => props.table.getAllColumns()
|
||||
.filter(
|
||||
column =>
|
||||
typeof column.accessorFn !== 'undefined' && column.getCanHide(),
|
||||
))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="ml-auto hidden h-8 lg:flex"
|
||||
>
|
||||
<MixerHorizontalIcon class="mr-2 h-4 w-4" />
|
||||
View
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" class="w-[150px]">
|
||||
<DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuCheckboxItem
|
||||
v-for="column in columns"
|
||||
:key="column.id"
|
||||
class="capitalize"
|
||||
:checked="column.getIsVisible()"
|
||||
@update:checked="(value) => column.toggleVisibility(!!value)"
|
||||
>
|
||||
{{ column.id }}
|
||||
</DropdownMenuCheckboxItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
|
@ -0,0 +1,64 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from '@/lib/registry/new-york/ui/avatar'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/lib/registry/new-york/ui/dropdown-menu'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button variant="ghost" class="relative h-8 w-8 rounded-full">
|
||||
<Avatar class="h-9 w-9">
|
||||
<AvatarImage src="/avatars/03.png" alt="@shadcn" />
|
||||
<AvatarFallback>SC</AvatarFallback>
|
||||
</Avatar>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="w-56" align="end">
|
||||
<DropdownMenuLabel class="font-normal flex">
|
||||
<div class="flex flex-col space-y-1">
|
||||
<p class="text-sm font-medium leading-none">
|
||||
shadcn
|
||||
</p>
|
||||
<p class="text-xs leading-none text-muted-foreground">
|
||||
m@example.com
|
||||
</p>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
Profile
|
||||
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
Billing
|
||||
<DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
Settings
|
||||
<DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>New Team</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
Log out
|
||||
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</template>
|
|
@ -0,0 +1,127 @@
|
|||
<script setup lang="ts" generic="T extends { [key: string]: any;}">
|
||||
const { list, columns, menus, refresh, defaultPageSizes = [10, 20, 30, 40] } = defineProps<{
|
||||
defaultPageSizes?: number[]
|
||||
list: T[]
|
||||
pageTotal: number
|
||||
columns: {
|
||||
key: keyof T
|
||||
label?: string
|
||||
sortable?: boolean
|
||||
}[]
|
||||
menus?: (row: T) => any[][]
|
||||
refresh?: () => Promise<any>
|
||||
loading?: boolean
|
||||
searchText?: string
|
||||
error?: any
|
||||
}>()
|
||||
|
||||
defineSlots<{
|
||||
'selected': any
|
||||
'filter': any
|
||||
[k: string]: (scope: { row: T }) => any
|
||||
}>()
|
||||
|
||||
const { query, selected, pageSize, pageIndex, sort } = defineModels<{
|
||||
query: string
|
||||
selected?: T[]
|
||||
pageSize: number
|
||||
pageIndex: number
|
||||
sort?: {
|
||||
clumn: keyof T
|
||||
direction: 'asc' | 'desc'
|
||||
}
|
||||
}>()
|
||||
|
||||
const attrs = useAttrs()
|
||||
|
||||
onMounted(() => {
|
||||
pageSize.value = defaultPageSizes[0]
|
||||
pageIndex.value = 1
|
||||
})
|
||||
|
||||
if (menus) {
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
columns.push({
|
||||
label: '',
|
||||
key: 'opt-actions',
|
||||
})
|
||||
}
|
||||
|
||||
const showColumns = ref<{ key: any; label?: string }[]>([...columns])
|
||||
|
||||
const selectedColumns: any = computed(() => columns.filter(c => showColumns.value.some(sc => sc.key === c.key)))
|
||||
const slots = useSlots()
|
||||
const invoices = [
|
||||
{
|
||||
invoice: 'INV001',
|
||||
paymentStatus: 'Paid',
|
||||
totalAmount: '$250.00',
|
||||
paymentMethod: 'Credit Card',
|
||||
},
|
||||
{
|
||||
invoice: 'INV002',
|
||||
paymentStatus: 'Pending',
|
||||
totalAmount: '$150.00',
|
||||
paymentMethod: 'PayPal',
|
||||
},
|
||||
{
|
||||
invoice: 'INV003',
|
||||
paymentStatus: 'Unpaid',
|
||||
totalAmount: '$350.00',
|
||||
paymentMethod: 'Bank Transfer',
|
||||
},
|
||||
{
|
||||
invoice: 'INV004',
|
||||
paymentStatus: 'Paid',
|
||||
totalAmount: '$450.00',
|
||||
paymentMethod: 'Credit Card',
|
||||
},
|
||||
{
|
||||
invoice: 'INV005',
|
||||
paymentStatus: 'Paid',
|
||||
totalAmount: '$550.00',
|
||||
paymentMethod: 'PayPal',
|
||||
},
|
||||
{
|
||||
invoice: 'INV006',
|
||||
paymentStatus: 'Pending',
|
||||
totalAmount: '$200.00',
|
||||
paymentMethod: 'Bank Transfer',
|
||||
},
|
||||
{
|
||||
invoice: 'INV007',
|
||||
paymentStatus: 'Unpaid',
|
||||
totalAmount: '$300.00',
|
||||
paymentMethod: 'Credit Card',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="w-[100px]">
|
||||
Invoice
|
||||
</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Method</TableHead>
|
||||
<TableHead class="text-right">
|
||||
Amount
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell class="font-medium">
|
||||
INV001
|
||||
</TableCell>
|
||||
<TableCell>Paid</TableCell>
|
||||
<TableCell>Credit Card</TableCell>
|
||||
<TableCell class="text-right">
|
||||
$250.00
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</template>
|
|
@ -0,0 +1,89 @@
|
|||
import type { ColumnDef } from '@tanstack/vue-table'
|
||||
import { h } from 'vue'
|
||||
|
||||
import { labels, priorities, statuses } from '../data/data'
|
||||
import type { Task } from '../data/schema'
|
||||
import DataTableColumnHeader from './DataTableColumnHeader.vue'
|
||||
import DataTableRowActions from './DataTableRowActions.vue'
|
||||
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
||||
import { Badge } from '@/lib/registry/new-york/ui/badge'
|
||||
|
||||
export const columns: ColumnDef<Task>[] = [
|
||||
{
|
||||
id: 'select',
|
||||
header: ({ table }) => h(Checkbox, {
|
||||
'checked': table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate'),
|
||||
'onUpdate:checked': value => table.toggleAllPageRowsSelected(!!value),
|
||||
'ariaLabel': 'Select all',
|
||||
'class': 'translate-y-0.5',
|
||||
}),
|
||||
cell: ({ row }) => h(Checkbox, { 'checked': row.getIsSelected(), 'onUpdate:checked': value => row.toggleSelected(!!value), 'ariaLabel': 'Select row', 'class': 'translate-y-0.5' }),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: 'id',
|
||||
header: ({ column }) => h(DataTableColumnHeader, { column, title: 'Task' }),
|
||||
cell: ({ row }) => h('div', { class: 'w-20' }, row.getValue('id')),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: 'title',
|
||||
header: ({ column }) => h(DataTableColumnHeader, { column, title: 'Title' }),
|
||||
|
||||
cell: ({ row }) => {
|
||||
const label = labels.find(label => label.value === row.original.label)
|
||||
|
||||
return h('div', { class: 'flex space-x-2' }, [
|
||||
label ? h(Badge, { variant: 'outline' }, () => label.label) : null,
|
||||
h('span', { class: 'max-w-[500px] truncate font-medium' }, row.getValue('title')),
|
||||
])
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'status',
|
||||
header: ({ column }) => h(DataTableColumnHeader, { column, title: 'Status' }),
|
||||
|
||||
cell: ({ row }) => {
|
||||
const status = statuses.find(
|
||||
status => status.value === row.getValue('status'),
|
||||
)
|
||||
|
||||
if (!status)
|
||||
return null
|
||||
|
||||
return h('div', { class: 'flex w-[100px] items-center' }, [
|
||||
status.icon && h(status.icon, { class: 'mr-2 h-4 w-4 text-muted-foreground' }),
|
||||
h('span', status.label),
|
||||
])
|
||||
},
|
||||
filterFn: (row, id, value) => {
|
||||
return value.includes(row.getValue(id))
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'priority',
|
||||
header: ({ column }) => h(DataTableColumnHeader, { column, title: 'Priority' }),
|
||||
cell: ({ row }) => {
|
||||
const priority = priorities.find(
|
||||
priority => priority.value === row.getValue('priority'),
|
||||
)
|
||||
|
||||
if (!priority)
|
||||
return null
|
||||
|
||||
return h('div', { class: 'flex items-center' }, [
|
||||
priority.icon && h(priority.icon, { class: 'mr-2 h-4 w-4 text-muted-foreground' }),
|
||||
h('span', {}, priority.label),
|
||||
])
|
||||
},
|
||||
filterFn: (row, id, value) => {
|
||||
return value.includes(row.getValue(id))
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
cell: ({ row }) => h(DataTableRowActions, { row }),
|
||||
},
|
||||
]
|
|
@ -0,0 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
links: {
|
||||
to: string
|
||||
name: string
|
||||
icon: string
|
||||
}[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="inline-flex justify-end ">
|
||||
<TabsItem v-for="(link, i) in links" :key="link.to + i" v-bind="link" />
|
||||
</div>
|
||||
</template>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue