This commit is contained in:
Twilight 2024-09-10 16:29:08 +08:00
commit 295f14e9f4
48 changed files with 9762 additions and 0 deletions

View File

@ -0,0 +1,69 @@
{
"globals": {
"Component": true,
"ComponentPublicInstance": true,
"ComputedRef": true,
"EffectScope": true,
"ExtractDefaultPropTypes": true,
"ExtractPropTypes": true,
"ExtractPublicPropTypes": true,
"InjectionKey": true,
"PropType": true,
"Ref": true,
"VNode": true,
"WritableComputedRef": true,
"computed": true,
"createApp": true,
"customRef": true,
"defineAsyncComponent": true,
"defineComponent": true,
"effectScope": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"h": true,
"inject": true,
"isProxy": true,
"isReactive": true,
"isReadonly": true,
"isRef": true,
"markRaw": true,
"nextTick": true,
"onActivated": true,
"onBeforeMount": true,
"onBeforeUnmount": true,
"onBeforeUpdate": true,
"onDeactivated": true,
"onErrorCaptured": true,
"onMounted": true,
"onRenderTracked": true,
"onRenderTriggered": true,
"onScopeDispose": true,
"onServerPrefetch": true,
"onUnmounted": true,
"onUpdated": true,
"provide": true,
"reactive": true,
"readonly": true,
"ref": true,
"resolveComponent": true,
"shallowReactive": true,
"shallowReadonly": true,
"shallowRef": true,
"toRaw": true,
"toRef": true,
"toRefs": true,
"toValue": true,
"triggerRef": true,
"unref": true,
"useAttrs": true,
"useCssModule": true,
"useCssVars": true,
"useRoute": true,
"useRouter": true,
"useSlots": true,
"watch": true,
"watchEffect": true,
"watchPostEffect": true,
"watchSyncEffect": true
}
}

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
wwwroot/*.js
node_modules
typings
dist
.DS_Store
.vscode
.idea
build
yarn.lock

2171
api/swagger.yaml Normal file

File diff suppressed because it is too large Load Diff

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>amber-wails</title>
</head>
<body>
<div id="app"></div>
<script src="./src/main.ts" type="module"></script>
</body>
</html>

50
package.json Normal file
View File

@ -0,0 +1,50 @@
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build2": "vue-tsc --noEmit && vite build",
"build": "vite build",
"preview": "vite preview",
"gen": "openapi-generator-cli generate -i ./api/swagger.yaml -g typescript-axios -o ./src/api"
},
"dependencies": {
"axios": "^1.7.7",
"highlight.js": "^11.10.0",
"js-base64": "^3.7.7",
"lottie-web": "^5.12.2",
"pinia": "^2.2.2",
"pinia-plugin-persistedstate": "^4.0.0",
"vooks": "^0.2.12",
"vue": "^3.2.37",
"vue-router": "^4.0.13"
},
"devDependencies": {
"@babel/types": "^7.18.10",
"@types/node": "^22.5.4",
"@vicons/ionicons5": "^0.12.0",
"@vicons/material": "^0.12.0",
"@vitejs/plugin-vue": "^5.0.5",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.26.0",
"naive-ui": "^2.39.0",
"postcss": "^8.4.45",
"prettier": "^3.3.2",
"tailwindcss": "^3.4.10",
"typescript": "^5.5.2",
"unplugin-auto-import": "^0.18.2",
"unplugin-vue-components": "^0.27.4",
"unplugin-vue-router": "^0.10.7",
"vfonts": "^0.0.3",
"vite": "^5.3.1",
"vite-plugin-pages": "^0.32.3",
"vite-plugin-vue-layouts": "^0.11.0",
"vue": "^3.4.30",
"vue-tsc": "^2.0.22"
}
}

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

117
src/App.vue Normal file
View File

@ -0,0 +1,117 @@
<!--<script setup lang="ts">-->
<!--import DefaultLayout from './layouts/DefaultLayout.vue'-->
<!--// const ipcHandle = () => window.electron.ipcRenderer.send('ping')-->
<!--</script>-->
<!--<template>-->
<!-- <DefaultLayout />-->
<!--</template>-->
<template>
<n-config-provider
:date-locale="dateZhCN"
:hljs="hljs"
:locale="zhCN"
:theme="theme"
:theme-overrides="themeOverrides"
preflight-style-disabled
>
<n-global-style />
<n-loading-bar-provider>
<n-message-provider>
<n-notification-provider :max="3">
<n-dialog-provider>
<!-- &lt;!&ndash; 加载动画 &ndash;&gt;-->
<!-- <transition name="fade">-->
<!-- <div v-show="load_step === 1">-->
<!-- <div class="flex h-screen">-->
<!-- <div class="m-auto text-center">-->
<!-- <Lottie v-if="osThemeRef === 'dark'" :loop="false" name="lae-jump"/>-->
<!-- <Lottie v-else :loop="false" name="lae-jump-black"/>-->
<!-- <n-h3>莱云</n-h3>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </transition>-->
<!-- 页面 -->
<!-- <transition name="fade">
<div v-if="true">
<DefaultLayout />
</div>
</transition> -->
<DefaultLayout />
</n-dialog-provider>
</n-notification-provider>
</n-message-provider>
</n-loading-bar-provider>
</n-config-provider>
</template>
<script setup>
import { computed } from 'vue'
import hljs from 'highlight.js/lib/core'
import ini from 'highlight.js/lib/languages/ini'
import {
darkTheme,
dateZhCN,
NConfigProvider,
NDialogProvider,
NGlobalStyle,
NLoadingBarProvider,
NMessageProvider,
NNotificationProvider,
useOsTheme,
zhCN
} from 'naive-ui'
// import Lottie from "./components/Lottie.vue";
import DefaultLayout from './layouts/DefaultLayout.vue'
import { useUserStore } from './stores/user'
const osThemeRef = useOsTheme()
const theme = computed(() => (osThemeRef.value === 'dark' ? darkTheme : null))
// const load_step = ref(1)
//
// if (process.env.NODE_ENV === 'production') {
// window.onload = () => {
// setTimeout(() => {
// load_step.value = 1
// setTimeout(() => {
// load_step.value = 2
// }, 500)
// }, 250)
// }
// } else {
// load_step.value = 2
// }
hljs.registerLanguage('ini', ini)
//
/**
* js 文件下使用这个做类型提示
* @type import('naive-ui').GlobalThemeOverrides
*/
// const themeOverrides = {
// common: {
// primaryColor: '#ec4b2d',
// primaryColorHover: '#ec4b2d',
// },
// Button: {
// textColor: '#ec4b2d'
// },
//
// }
const themeOverrides = {}
// UserStore
const userStore = useUserStore();
userStore.setupTimer();
</script>

4
src/api/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
wwwroot/*.js
node_modules
typings
dist

1
src/api/.npmignore Normal file
View File

@ -0,0 +1 @@
# empty npmignore to ensure all required files (e.g., in the dist folder) are published by npm

View File

@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md

View File

@ -0,0 +1,9 @@
.gitignore
.npmignore
.openapi-generator-ignore
api.ts
base.ts
common.ts
configuration.ts
git_push.sh
index.ts

View File

@ -0,0 +1 @@
7.8.0

5854
src/api/api.ts Normal file

File diff suppressed because it is too large Load Diff

86
src/api/base.ts Normal file
View File

@ -0,0 +1,86 @@
/* tslint:disable */
/* eslint-disable */
/**
* Leaflow Amber
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import type { Configuration } from './configuration';
// Some imports not used depending on template conditions
// @ts-ignore
import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios';
import globalAxios from 'axios';
export const BASE_PATH = "http://localhost".replace(/\/+$/, "");
/**
*
* @export
*/
export const COLLECTION_FORMATS = {
csv: ",",
ssv: " ",
tsv: "\t",
pipes: "|",
};
/**
*
* @export
* @interface RequestArgs
*/
export interface RequestArgs {
url: string;
options: RawAxiosRequestConfig;
}
/**
*
* @export
* @class BaseAPI
*/
export class BaseAPI {
protected configuration: Configuration | undefined;
constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) {
if (configuration) {
this.configuration = configuration;
this.basePath = configuration.basePath ?? basePath;
}
}
};
/**
*
* @export
* @class RequiredError
* @extends {Error}
*/
export class RequiredError extends Error {
constructor(public field: string, msg?: string) {
super(msg);
this.name = "RequiredError"
}
}
interface ServerMap {
[key: string]: {
url: string,
description: string,
}[];
}
/**
*
* @export
*/
export const operationServerMap: ServerMap = {
}

150
src/api/common.ts Normal file
View File

@ -0,0 +1,150 @@
/* tslint:disable */
/* eslint-disable */
/**
* Leaflow Amber
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import type { Configuration } from "./configuration";
import type { RequestArgs } from "./base";
import type { AxiosInstance, AxiosResponse } from 'axios';
import { RequiredError } from "./base";
/**
*
* @export
*/
export const DUMMY_BASE_URL = 'https://example.com'
/**
*
* @throws {RequiredError}
* @export
*/
export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) {
if (paramValue === null || paramValue === undefined) {
throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`);
}
}
/**
*
* @export
*/
export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) {
if (configuration && configuration.apiKey) {
const localVarApiKeyValue = typeof configuration.apiKey === 'function'
? await configuration.apiKey(keyParamName)
: await configuration.apiKey;
object[keyParamName] = localVarApiKeyValue;
}
}
/**
*
* @export
*/
export const setBasicAuthToObject = function (object: any, configuration?: Configuration) {
if (configuration && (configuration.username || configuration.password)) {
object["auth"] = { username: configuration.username, password: configuration.password };
}
}
/**
*
* @export
*/
export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) {
if (configuration && configuration.accessToken) {
const accessToken = typeof configuration.accessToken === 'function'
? await configuration.accessToken()
: await configuration.accessToken;
object["Authorization"] = "Bearer " + accessToken;
}
}
/**
*
* @export
*/
export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) {
if (configuration && configuration.accessToken) {
const localVarAccessTokenValue = typeof configuration.accessToken === 'function'
? await configuration.accessToken(name, scopes)
: await configuration.accessToken;
object["Authorization"] = "Bearer " + localVarAccessTokenValue;
}
}
function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: any, key: string = ""): void {
if (parameter == null) return;
if (typeof parameter === "object") {
if (Array.isArray(parameter)) {
(parameter as any[]).forEach(item => setFlattenedQueryParams(urlSearchParams, item, key));
}
else {
Object.keys(parameter).forEach(currentKey =>
setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`)
);
}
}
else {
if (urlSearchParams.has(key)) {
urlSearchParams.append(key, parameter);
}
else {
urlSearchParams.set(key, parameter);
}
}
}
/**
*
* @export
*/
export const setSearchParams = function (url: URL, ...objects: any[]) {
const searchParams = new URLSearchParams(url.search);
setFlattenedQueryParams(searchParams, objects);
url.search = searchParams.toString();
}
/**
*
* @export
*/
export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) {
const nonString = typeof value !== 'string';
const needsSerialization = nonString && configuration && configuration.isJsonMime
? configuration.isJsonMime(requestOptions.headers['Content-Type'])
: nonString;
return needsSerialization
? JSON.stringify(value !== undefined ? value : {})
: (value || "");
}
/**
*
* @export
*/
export const toPathString = function (url: URL) {
return url.pathname + url.search + url.hash
}
/**
*
* @export
*/
export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) {
return <T = unknown, R = AxiosResponse<T>>(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs = {...axiosArgs.options, url: (axios.defaults.baseURL ? '' : configuration?.basePath ?? basePath) + axiosArgs.url};
return axios.request<T, R>(axiosRequestArgs);
};
}

110
src/api/configuration.ts Normal file
View File

@ -0,0 +1,110 @@
/* tslint:disable */
/* eslint-disable */
/**
* Leaflow Amber
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export interface ConfigurationParameters {
apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
username?: string;
password?: string;
accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
basePath?: string;
serverIndex?: number;
baseOptions?: any;
formDataCtor?: new () => any;
}
export class Configuration {
/**
* parameter for apiKey security
* @param name security name
* @memberof Configuration
*/
apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
/**
* parameter for basic security
*
* @type {string}
* @memberof Configuration
*/
username?: string;
/**
* parameter for basic security
*
* @type {string}
* @memberof Configuration
*/
password?: string;
/**
* parameter for oauth2 security
* @param name security name
* @param scopes oauth2 scope
* @memberof Configuration
*/
accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
/**
* override base path
*
* @type {string}
* @memberof Configuration
*/
basePath?: string;
/**
* override server index
*
* @type {number}
* @memberof Configuration
*/
serverIndex?: number;
/**
* base options for axios calls
*
* @type {any}
* @memberof Configuration
*/
baseOptions?: any;
/**
* The FormData constructor that will be used to create multipart form data
* requests. You can inject this here so that execution environments that
* do not support the FormData class can still run the generated client.
*
* @type {new () => FormData}
*/
formDataCtor?: new () => any;
constructor(param: ConfigurationParameters = {}) {
this.apiKey = param.apiKey;
this.username = param.username;
this.password = param.password;
this.accessToken = param.accessToken;
this.basePath = param.basePath;
this.serverIndex = param.serverIndex;
this.baseOptions = param.baseOptions;
this.formDataCtor = param.formDataCtor;
}
/**
* Check if the given MIME is a JSON MIME.
* JSON MIME examples:
* application/json
* application/json; charset=UTF8
* APPLICATION/JSON
* application/vnd.company+json
* @param mime - MIME (Multipurpose Internet Mail Extensions)
* @return True if the given MIME is JSON, false otherwise.
*/
public isJsonMime(mime: string): boolean {
const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i');
return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json');
}
}

57
src/api/git_push.sh Normal file
View File

@ -0,0 +1,57 @@
#!/bin/sh
# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
#
# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com"
git_user_id=$1
git_repo_id=$2
release_note=$3
git_host=$4
if [ "$git_host" = "" ]; then
git_host="github.com"
echo "[INFO] No command line input provided. Set \$git_host to $git_host"
fi
if [ "$git_user_id" = "" ]; then
git_user_id="GIT_USER_ID"
echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
fi
if [ "$git_repo_id" = "" ]; then
git_repo_id="GIT_REPO_ID"
echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
fi
if [ "$release_note" = "" ]; then
release_note="Minor update"
echo "[INFO] No command line input provided. Set \$release_note to $release_note"
fi
# Initialize the local directory as a Git repository
git init
# Adds the files in the local repository and stages them for commit.
git add .
# Commits the tracked changes and prepares them to be pushed to a remote repository.
git commit -m "$release_note"
# Sets the new remote
git_remote=$(git remote)
if [ "$git_remote" = "" ]; then # git remote not defined
if [ "$GIT_TOKEN" = "" ]; then
echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git
else
git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git
fi
fi
git pull origin master
# Pushes (Forces) the changes in the local repository up to the remote repository
echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git"
git push origin master 2>&1 | grep -v 'To https'

18
src/api/index.ts Normal file
View File

@ -0,0 +1,18 @@
/* tslint:disable */
/* eslint-disable */
/**
* Leaflow Amber
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export * from "./api";
export * from "./configuration";

93
src/assets/fonts/OFL.txt Normal file
View File

@ -0,0 +1,93 @@
Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com),
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

129
src/auto-imports.d.ts vendored Normal file
View File

@ -0,0 +1,129 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const effectScope: typeof import('vue')['effectScope']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useRoute: typeof import('vue-router/auto')['useRoute']
const useRouter: typeof import('vue-router/auto')['useRouter']
const useSlots: typeof import('vue')['useSlots']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
import('vue')
}
// for vue template auto import
import { UnwrapRef } from 'vue'
declare module 'vue' {
interface GlobalComponents {}
interface ComponentCustomProperties {
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
readonly ref: UnwrapRef<typeof import('vue')['ref']>
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly unref: UnwrapRef<typeof import('vue')['unref']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
readonly useRoute: UnwrapRef<typeof import('vue-router/auto')['useRoute']>
readonly useRouter: UnwrapRef<typeof import('vue-router/auto')['useRouter']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
readonly watch: UnwrapRef<typeof import('vue')['watch']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
}
}

15
src/components.d.ts vendored Normal file
View File

@ -0,0 +1,15 @@
/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
Container: typeof import('./components/Container.vue')['default']
Menu: typeof import('./components/Menu.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
}

View File

@ -0,0 +1,22 @@
<script setup lang="ts">
import router from '../plugins/router';
const currentRoute = computed(() => router.currentRoute.value.name)
</script>
<template>
<!-- <Header style="min-height: var(--header-height)"></Header> -->
<router-view v-slot="{ Component }">
<transition mode="out-in" name="fade">
<div>
<component :is="Component" />
</div>
</transition>
</router-view>
<!-- <router-view></router-view> -->
</template>
<style scoped></style>

57
src/components/Menu.vue Normal file
View File

@ -0,0 +1,57 @@
<template>
<n-menu
:value="currentRoute"
:collapsed="collapsed"
:collapsed-width="64"
:collapsed-icon-size="22"
:options="menuOptions"
/>
</template>
<script setup lang="ts">
import { NIcon, NMenu } from 'naive-ui'
import { Home as HomeIcon } from '@vicons/ionicons5'
import type { MenuOption } from 'naive-ui'
import { RouterLink } from 'vue-router'
import { useRoute } from 'vue-router'
const route = useRoute()
const currentRoute: any = computed(() => route.name)
const collapsed = ref(false)
const menuOptions: MenuOption[] = [
{
label: () =>
h(
RouterLink,
{
to: {
name: '/'
}
},
{ default: () => 'Amber' }
),
key: '/',
icon: renderIcon(HomeIcon)
},
{
label: () =>
h(
RouterLink,
{
to: {
name: '/auth/login'
}
},
{ default: () => 'Login' }
),
key: '/auth/login',
icon: renderIcon(HomeIcon)
}
]
function renderIcon(icon: Component) {
return () => h(NIcon, null, { default: () => h(icon) })
}
</script>

25
src/config/config.ts Normal file
View File

@ -0,0 +1,25 @@
const config = {
app_name: "Amber",
oauth_discovery_url:
"https://auth.leaflow.cn/.well-known/openid-configuration",
oauth_client_id: "90020",
oauth_callback_url: "amber-desktop://auth_callback",
oauth_storage_key: "code_verifier",
oauth_scope: "openid profile",
backend: "http://localhost:8080",
};
// @ts-ignore ...
if (process.env.NODE_ENV === "production") {
config.backend = "https://amber-api.leaflow.cn";
config.oauth_callback_url = "https://amber.leaflow.cn/auth/callback";
config.oauth_client_id = "16";
}
config.backend = "https://amber-api.leaflow.cn";
// config.backend = "https://amber-api.leaflow.cn";
// console.log("api endpoint: " + config.backend);
export default config;

8
src/env.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}

View File

@ -0,0 +1,45 @@
<script setup lang="ts">
import Container from "../components/Container.vue";
import { NLayout, NLayoutContent, NLayoutSider } from "naive-ui";
import { useIsMobile } from "../utils/composables.js";
import Menu from "../components/Menu.vue";
import { useUserStore } from "../stores/user";
import Guest from "../pages/guest/index.vue";
import router from "../plugins/router";
const currentRoute = computed(() => router.currentRoute.value.name);
const userStore = useUserStore();
// import Header from './Header.vue'
const isMobile = useIsMobile();
// const isTablet = useIsTablet()
const menuCollapsed = ref({
left: false,
});
</script>
<template>
<n-layout :position="isMobile ? 'static' : 'absolute'" :has-sider="true">
<n-layout-sider
v-if="userStore.logined"
:collapsed-width="0"
:native-scrollbar="false"
:show-collapsed-content="false"
:width="240"
bordered
collapse-mode="width"
show-trigger="arrow-circle"
class="select-none"
@collapse="menuCollapsed.left = true"
@expand="menuCollapsed.left = false"
>
<Menu></Menu>
</n-layout-sider>
<n-layout-content>
<!-- <Guest v-if="!userStore.logined && currentRoute != '/auth/login'" />
<Container v-else /> -->
<Guest v-if="!userStore.logined && !currentRoute?.startsWith('/auth')" />
<Container v-else />
</n-layout-content>
</n-layout>
</template>

23
src/layouts/Header.vue Normal file
View File

@ -0,0 +1,23 @@
<template>
<n-layout>
<n-layout-header bordered class="layout-header header-height">
<n-grid cols="2" class="header-height">
<n-grid-item class="flex items-center justify-start mr-1.5">
<div>left</div>
</n-grid-item>
<n-grid-item class="flex items-center justify-end mr-1.5">
<div>right</div>
</n-grid-item>
</n-grid>
</n-layout-header>
</n-layout>
</template>
<script setup lang="ts"></script>
<style lang="css" scoped>
.header-height {
min-height: var(--header-height);
}
</style>

32
src/main.ts Normal file
View File

@ -0,0 +1,32 @@
const meta = document.createElement("meta");
meta.name = "naive-ui-style";
document.head.appendChild(meta);
import "./style.css";
import { createApp } from "vue";
import { createPinia } from "pinia";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
import naive from "naive-ui";
// 通用字体
import "vfonts/Lato.css";
// 等宽字体
import "vfonts/FiraCode.css";
import App from "./App.vue";
import router from "./plugins/router";
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
const app = createApp(App);
app.use(pinia);
app.use(naive);
app.use(router);
app.mount("#app");

View File

@ -0,0 +1,52 @@
<template>
<div>
<h1>请稍等片刻</h1>
</div>
</template>
<script setup lang="ts">
import axios from "axios";
import { useUserStore } from "../../stores/user";
import router from "../../plugins/router";
import config from "../../config/config";
const userStore = useUserStore();
axios.get(config.oauth_discovery_url).then((discovery) => {
const localCodeVerifier = localStorage.getItem(config.oauth_storage_key);
const code: any = router.currentRoute.value.query.code;
// code
const q = new URLSearchParams({
client_id: config.oauth_client_id,
grant_type: "authorization_code",
redirect_uri: config.oauth_callback_url,
code_verifier: localCodeVerifier || "",
code: code ?? "",
});
const tokenEndpoint = discovery.data.token_endpoint;
axios
.post(tokenEndpoint, q)
.then((r) => {
userStore.access_token = r.data.access_token;
userStore.refresh_token = r.data.refresh_token;
userStore.login(
r.data.id_token,
r.data.access_token,
r.data.refresh_token,
r.data.expires_in,
);
})
.catch((e) => {
console.log(e);
alert("登录失败");
})
.finally(() => {
// /
router.push("/");
});
});
</script>

View File

@ -0,0 +1,5 @@
<template>
<div>
<h1>请在新窗口中继续登录</h1>
</div>
</template>

59
src/pages/auth/login.vue Normal file
View File

@ -0,0 +1,59 @@
<template>
<div>
<h1>请稍后...</h1>
</div>
</template>
<script async setup lang="ts">
import config from "../../config/config";
import axios from "axios";
import router from "../../plugins/router";
function generateRandomString(length: number) {
let text = "";
const possible =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
async function generateCodeChallenge(codeVerifier: string) {
const digest = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(codeVerifier),
);
return btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/=/g, "")
.replace(/\+/g, "-")
.replace(/\//g, "_");
}
async function go() {
const codeVerifier = generateRandomString(128);
const codeChallenge = await generateCodeChallenge(codeVerifier);
localStorage.setItem(config.oauth_storage_key, codeVerifier);
const query = new URLSearchParams({
client_id: config.oauth_client_id,
redirect_uri: config.oauth_callback_url,
response_type: "code",
scope: config.oauth_scope,
code_challenge: codeChallenge,
code_challenge_method: "S256",
}).toString();
const discovery = await axios.get(config.oauth_discovery_url);
const a = document.createElement('a')
a.href = discovery.data.authorization_endpoint + '?' + query
a.click()
}
go();
</script>

24
src/pages/guest/index.vue Normal file
View File

@ -0,0 +1,24 @@
<template>
<div class="flex items-center align-center justify-center h-full">
<div class="text-center">
<div class="mt-5 !ml-2">
<n-h1> 满身星光不负众望 </n-h1>
<n-p> </n-p>
</div>
<br />
<n-button v-if="!userStore.logined" type="primary" @click="login"> 登录 </n-button>
</div>
</div>
</template>
<script setup lang="ts">
import router from "../../plugins/router";
import { useUserStore } from "../../stores/user";
const userStore = useUserStore();
const login = () => {
router.push("/auth/login");
};
</script>

46
src/pages/index.vue Normal file
View File

@ -0,0 +1,46 @@
<script setup lang="ts">
import { useMessage } from "naive-ui";
import { useUserStore } from "../stores/user";
const message = useMessage();
const userStore = useUserStore();
const options = [
{
label: "滨海湾金沙,新加坡",
key: "marina bay sands",
disabled: true,
},
{
label: "布朗酒店,伦敦",
key: "brown's hotel, london",
},
{
label: "亚特兰蒂斯巴哈马,拿骚",
key: "atlantis nahamas, nassau",
},
{
label: "比佛利山庄酒店,洛杉矶",
key: "the beverly hills hotel, los angeles",
},
];
function handleSelect(key: string | number) {
message.info(String(key));
}
function logout() {
userStore.logout();
}
</script>
<template>
<router-link to="auth/login">go</router-link>
<n-dropdown trigger="hover" :options="options" @select="handleSelect">
<n-button>找个地方休息111</n-button>
</n-dropdown>
<n-button @click="logout()">退出登录</n-button>
</template>
<style scoped></style>

36
src/plugins/api.ts Normal file
View File

@ -0,0 +1,36 @@
// import {
// AssistantApi,
// ChatApi,
// ChatMessageApi,
// ChatPublicApi,
// Configuration,
// PingApi,
// ToolApi,
// } from "@/api";
// import config from "@/config/config";
// import { useUserStore } from "@/stores/user";
//
// const userStore = useUserStore();
//
// const conf = new Configuration();
//
// conf.basePath = config.backend;
// conf.apiKey = () => {
// return "Bearer " + userStore.id_token;
// };
// userStore.$subscribe((mutation, state) => {
// console.log(mutation);
// conf.apiKey = "Bearer " + state.id_token;
// });
// const api = {
// Chat: new ChatApi(conf),
// Assistant: new AssistantApi(conf),
// Ping: new PingApi(conf),
// Tool: new ToolApi(conf),
// ChatMessage: new ChatMessageApi(conf),
// ChatPublic: new ChatPublicApi(conf),
// };
//
// export { api, conf };

5
src/plugins/axios.ts Normal file
View File

@ -0,0 +1,5 @@
import axios from 'axios'
// axios.defaults.adapter = window.axiosHttpAdapter
export default axios

10
src/plugins/router.ts Normal file
View File

@ -0,0 +1,10 @@
import { createRouter, createWebHashHistory } from 'vue-router'
import { routes } from 'vue-router/auto-routes'
import { setupLayouts } from 'virtual:generated-layouts'
const router = createRouter({
history: createWebHashHistory(),
routes: setupLayouts(routes)
})
export default router

31
src/stores/app.ts Normal file
View File

@ -0,0 +1,31 @@
// Utilities
import { defineStore } from "pinia";
export const useAppStore = defineStore("app", {
persist: false,
state: () => ({
navigation_drawer: false,
navigation_items: [
{
icon: "mdi-home",
text: "主页",
to: "/",
},
{
icon: "mdi-assistant",
text: "助理",
to: "/assistants",
},
{
icon: "mdi-tools",
text: "工具",
to: "/tools",
},
{
icon: "mdi-key",
text: "令牌",
to: "/tokens",
},
],
}),
});

102
src/stores/user.ts Normal file
View File

@ -0,0 +1,102 @@
import { defineStore } from "pinia";
import axios from "axios";
import config from "../config/config";
import { Base64 } from "js-base64";
let timer: any = null;
export const useUserStore = defineStore("user", {
persist: true,
state: () => ({
logined: false,
id_token: "",
refresh_token: "",
access_token: "",
expired_at: 0,
user: {
id: 0,
name: "",
email: "",
},
timer: 0,
}),
actions: {
login(
idToken: string,
accessToken: string,
refreshToken: string,
expiredAt: number,
) {
const idTokenParts = idToken.split(".");
const idTokenPayload = JSON.parse(Base64.decode(idTokenParts[1]));
expiredAt = Date.now() + expiredAt * 1000;
this.expired_at = expiredAt;
this.refresh_token = refreshToken;
this.access_token = accessToken;
this.id_token = idToken;
this.user.email = idTokenPayload.email;
this.user.name = idTokenPayload.name;
this.user.id = idTokenPayload.sub;
this.logined = true;
},
checkAndRefresh() {
if (this.logined) {
if (this.expired_at - Date.now() < 60000) {
this.refresh();
}
}
},
setupTimer() {
this.checkAndRefresh();
timer = setInterval(() => {
this.checkAndRefresh();
}, 10 * 1000);
},
async refresh() {
const discovery = await axios.get(config.oauth_discovery_url);
// post /oauth/token to refresh
// 构建 form 参数
const data = new URLSearchParams();
data.set("grant_type", "refresh_token");
data.set("refresh_token", this.refresh_token);
data.set("client_id", config.oauth_client_id);
data.set("scope", config.oauth_scope);
axios
.post(discovery.data.token_endpoint, data)
.then((response) => {
this.login(
response.data.id_token,
response.data.access_token,
response.data.refresh_token,
response.data.expires_in,
);
})
.catch((error) => {
// if 401
if (error.response.status === 401) {
console.log("Refresh token failed");
}
// logout
this.logout();
clearInterval(timer);
});
},
logout() {
this.user = {
id: 0,
name: "",
email: "",
};
this.id_token = "";
this.logined = false;
}
},
});

7
src/style.css Normal file
View File

@ -0,0 +1,7 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
--header-height: 32px;
}

27
src/typed-router.d.ts vendored Normal file
View File

@ -0,0 +1,27 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️
// It's recommended to commit this file.
// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
declare module 'vue-router/auto-routes' {
import type {
RouteRecordInfo,
ParamValue,
ParamValueOneOrMore,
ParamValueZeroOrMore,
ParamValueZeroOrOne,
} from 'vue-router'
/**
* Route name map generated by unplugin-vue-router
*/
export interface RouteNamedMap {
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
'/auth/callback': RouteRecordInfo<'/auth/callback', '/auth/callback', Record<never, never>, Record<never, never>>,
'/auth/continue': RouteRecordInfo<'/auth/continue', '/auth/continue', Record<never, never>, Record<never, never>>,
'/auth/login': RouteRecordInfo<'/auth/login', '/auth/login', Record<never, never>, Record<never, never>>,
'/guest/': RouteRecordInfo<'/guest/', '/guest', Record<never, never>, Record<never, never>>,
}
}

45
src/utils/composables.ts Normal file
View File

@ -0,0 +1,45 @@
import { inject, provide, reactive, toRef, watchEffect } from 'vue'
import { useBreakpoint, useMemo } from 'vooks'
export function useIsMobile() {
const breakpointRef = useBreakpoint()
return useMemo(() => {
return breakpointRef.value === 'xs'
})
}
export function useIsTablet() {
const breakpointRef = useBreakpoint()
return useMemo(() => {
return breakpointRef.value === 's'
})
}
export function useIsSmallDesktop() {
const breakpointRef = useBreakpoint()
return useMemo(() => {
return breakpointRef.value === 'm'
})
}
export const i18n = function (data: any) {
const localeReactive = inject('i18n', null)
return {
// @ts-ignore ...
locale: toRef(localeReactive, 'locale'),
t(key: any) {
// @ts-ignore ...
const { locale } = localeReactive
return data[locale][key]
}
}
}
i18n.provide = function (localeRef: any) {
const localeReactive = reactive({})
watchEffect(() => {
// @ts-ignore ...
localeReactive.locale = localeRef.value
})
provide('i18n', localeReactive)
}

7
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type {DefineComponent} from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

8
tailwind.config.js Normal file
View File

@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};

33
tsconfig.json Normal file
View File

@ -0,0 +1,33 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": [
"ESNext",
"DOM"
],
"skipLibCheck": true,
"types": [
"unplugin-vue-router/client"
]
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}

11
tsconfig.node.json Normal file
View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": [
"vite.config.ts"
]
}

57
vite.config.ts Normal file
View File

@ -0,0 +1,57 @@
import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"
import VueRouter from "unplugin-vue-router/vite"
import Layouts from "vite-plugin-vue-layouts"
import Components from "unplugin-vue-components/vite"
import AutoImport from "unplugin-auto-import/vite"
// import { resolve } from "path";
// const rootPath = new URL(".", import.meta.url).pathname;
import { fileURLToPath, URL } from "node:url"
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
extensions: [".js", ".json", ".jsx", ".mjs", ".ts", ".tsx", ".vue"],
},
plugins: [
VueRouter({
dts: "src/typed-router.d.ts",
routesFolder: [
{
src: "src/pages",
path: "",
// override globals
exclude: (excluded) => excluded,
filePatterns: (filePatterns) => filePatterns,
extensions: (extensions) => extensions,
},
],
}),
vue(),
Layouts(),
AutoImport({
imports: [
"vue",
{
"vue-router/auto": ["useRoute", "useRouter"],
},
],
dts: "src/auto-imports.d.ts",
eslintrc: {
enabled: true,
},
vueTemplate: true,
}),
Components({
dts: "src/components.d.ts",
}),
],
server: {
host: true,
port: 5173,
strictPort: true,
},
})