first commit

This commit is contained in:
sunlei 2026-05-08 11:23:15 +08:00
commit f439349933
34 changed files with 3604 additions and 0 deletions

2
.env Normal file
View File

@ -0,0 +1,2 @@
VITE_APP_BASE_API="api.ppmark.cn/chart"
VITE_APP_OSS_DOMAIN="api.ppmark.cn/chart-assets"

4
.env.development Normal file
View File

@ -0,0 +1,4 @@
NODE_ENV = depelopment
VITE_APP_PLAY_GROUND = http://192.168.52.164:5173
VITE_APP_PROXY = "http://192.168.1.206:48085/"

3
.env.production Normal file
View File

@ -0,0 +1,3 @@
NODE_ENV = production
VITE_APP_PLAY_GROUND = http://192.168.1.206:84/
VITE_APP_PROXY = http://192.168.1.206:48085/

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

16
README.md Normal file
View File

@ -0,0 +1,16 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
## Type Support For `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).

8
deploy.json Normal file
View File

@ -0,0 +1,8 @@
{
"host": "192.168.1.206",
"port": "22",
"username": "root",
"password": "3h1admin",
"localDir": "dist",
"remoteDir": "/data/nginx/chart"
}

18
index.html Normal file
View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ShyTemplate - 模版库</title>
<meta name="Keywords" content="echarts,Echarts,gallery,makeapie,make a pie,ppchart,PPChart" />
<meta name="description" content="让图表更简单。PPChart 提供 Echarts 收录、图表制作等服务" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

30
package.json Normal file
View File

@ -0,0 +1,30 @@
{
"name": "kt-template-online-web",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview",
"deploy": "pnpm build && npx shy ftp deploy -f ./deploy"
},
"dependencies": {
"axios": "^1.1.3",
"moment": "^2.29.4",
"monaco-editor": "^0.34.1",
"pinia": "^2.0.23",
"vue": "^3.2.41",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@arco-design/web-vue": "^2.38.1",
"@types/node": "^18.11.9",
"@vitejs/plugin-vue": "^3.2.0",
"sass": "^1.56.0",
"typescript": "^4.6.4",
"unocss": "^0.61.8",
"vite": "^3.2.0",
"vue-tsc": "^1.0.9"
}
}

2628
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

6
public/robots.txt Normal file
View File

@ -0,0 +1,6 @@
User-agent: *
Sitemap: http://ppchart.com/index.html
User-agent: Wandoujia Spider
User-agent: Baiduspider
User-agent: Mediapartners-Google

41
src/App.vue Normal file
View File

@ -0,0 +1,41 @@
<script setup lang="ts">
import { LayoutContent, Layout } from "@arco-design/web-vue";
import Header from "@/modules/header/Header.vue";
</script>
<template>
<Layout class="!h-[calc(100%-60px)] !pt-60px">
<Header></Header>
<LayoutContent class="content">
<router-view></router-view>
</LayoutContent>
</Layout>
</template>
<style lang="scss">
html,
body {
height: 100%;
--color-logo: rgb(85, 26, 139);
--color-logo-1: rgb(237, 221, 249);
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
height: 100%;
}
.content {
background-color: var(--color-bg-5);
height: calc(100% - 60px);
}
body[arco-theme="dark"] {
--color-logo: var(--color-text-1);
--color-logo-1: var(--color-bg-5);
}
</style>

1
src/assets/vue.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

31
src/bus.ts Normal file
View File

@ -0,0 +1,31 @@
// 为保持和vue2版本中使用bus一致emit,on,off前面都加了$
export class Bus {
list: Record<string, Array<(...params: any) => void>> = {}
constructor() {
// 收集订阅信息,调度中心
this.list = {};
}
// 订阅
$on(name: string, fn: (...params: any) => void) {
this.list[name] = this.list[name] || [];
this.list[name].push(fn);
}
// 发布
$emit(name: string, data: any) {
if (this.list[name]) {
this.list[name].forEach((fn) => {
fn(data);
});
}
}
// 取消订阅
$off(name: string) {
if (this.list[name]) {
delete this.list[name];
}
}
}
export default new Bus();

View File

@ -0,0 +1,16 @@
<template>
<a href="/" class="logo"> ShyTemplate </a>
</template>
<style lang="scss">
.logo {
display: flex;
align-items: center;
justify-content: center;
text-decoration: none; // 线
color: var(--color-logo);
width: 100%;
height: 100%;
}
</style>

10
src/config.ts Normal file
View File

@ -0,0 +1,10 @@
import { os } from "@/utils/detect";
const config = (() => ({
isMobileApp: !os.desktop,
axiosBase: "/api",
playground: import.meta.env.VITE_APP_PLAY_GROUND,
}))();
export default config;

16
src/hooks/useModel.ts Normal file
View File

@ -0,0 +1,16 @@
import { ref, watch, getCurrentInstance, ExtractPropTypes } from "vue";
type EmitFn = (event: string, ...args: any[]) => void
function useModel(props: Readonly<ExtractPropTypes<{ [k: string]: StringConstructor }>>, key = "modelValue", emit?: EmitFn) {
const proxy = ref(props[key]);
const _emit = emit || getCurrentInstance()?.emit;
watch(
() => proxy.value,
(v) => _emit && _emit(`update:${key}`, v)
);
return proxy;
}
export default useModel

11
src/main.ts Normal file
View File

@ -0,0 +1,11 @@
import { createApp } from "vue";
import { createPinia } from "pinia";
import "virtual:uno.css";
import App from "@/App.vue";
import { router } from "@/router.js";
import "@arco-design/web-vue/dist/arco.css";
createApp(App).use(router).use(createPinia()).mount("#app");

View File

@ -0,0 +1,13 @@
import { defineStore } from "pinia"
export const useChartListStore = defineStore('chartList', {
state: () => ({ searchValue: '', chartList: [], loading: false }),
getters: {
// double: (state) => state.count * 2,
},
actions: {
increment() {
// this.count++
},
},
})

View File

@ -0,0 +1,228 @@
<script setup lang="ts">
import { DescData, Message, Button, Empty } from "@arco-design/web-vue";
import { onBeforeUnmount, onMounted, reactive, ref } from "vue";
import axios from "axios";
import moment from "moment";
import Bus from "@/bus";
import { Spin, Card, CardGrid, CardMeta, Pagination, Descriptions, Popconfirm } from "@arco-design/web-vue";
import { IconShareInternal, IconDelete } from "@arco-design/web-vue/es/icon";
import config from "@/config";
enum HttpStatus {
OK = 200,
}
const chartData = reactive<any>({
chartList: [] as Array<{
title: string;
image: string;
id: string;
desc: DescData[];
}>,
total: 0,
pageIndex: 1,
loading: false,
type: "",
searchValue: "",
componentType: "",
});
const convertTime = (timeStr: string) => {
return moment(timeStr).format("YYYY-MM-DD");
};
const getData = () => {
chartData.loading = true;
Bus.$emit("search-loading", true);
const params: any = {
pageSize: 20,
pageNo: chartData.pageIndex,
type: chartData.type,
componentType: chartData.componentType,
name: chartData.searchValue,
};
if (chartData.type) {
params.type = chartData.type;
}
axios
.get(`${config.axiosBase}/component/list`, { params })
.then((res) => {
const { code, data, msg } = res.data;
if (code === HttpStatus.OK) {
const { list, total } = data;
chartData.total = total;
chartData.chartList = list.map((item: any) => {
const { id, createTime, componentTypeMsg, name, typeMsg, image } = item;
return {
id,
title: name,
image,
desc: [
{ value: convertTime(createTime), label: "创建时间" },
{ value: componentTypeMsg, label: "组件类型" },
{ value: typeMsg, label: "类型" },
],
};
});
} else {
Message.error(msg || "服务器开小差了,请稍后再试...");
}
})
.finally(() => {
chartData.loading = false;
Bus.$emit("search-loading", false);
});
};
onMounted(() => {
Bus.$on("search", (res: any) => {
Object.keys(res).forEach((key) => {
chartData[key] = res[key];
});
chartData.pageIndex = 1;
getData();
});
});
onBeforeUnmount(() => {
Bus.$off("search-loading");
});
const chartClick = async (params: string) => {
const res = await axios.get(`${config.axiosBase}/component/detail?id=${params}`);
const { template, componentType, type, id, name } = res?.data?.data;
window.open(`${config.playground}/?id=${id}&name=${name}&componentType=${componentType}&type=${type}#${template}`);
};
const pageChange = (pageIndex: number) => {
chartData.pageIndex = pageIndex;
getData();
};
const openTab = async (params: string) => {
const res = await axios.get(`${config.axiosBase}/component/detail?id=${params}`);
const { template, componentType, type, id, name } = res?.data?.data;
const url = `${config.playground}/?id=${id}&name=${name}&componentType=${componentType}&type=${type}#${template}`;
const tempInput = document.createElement("textarea");
tempInput.style.position = "absolute"; //
tempInput.style.opacity = "0"; //
document.body.append(tempInput);
//
tempInput.value = url;
tempInput.select(); //
//
try {
document.execCommand("copy");
} catch (error) {
console.error("Error copying text:", error);
}
//
tempInput.remove();
Message.success("分享链接已复制到剪贴板");
};
const handleAdd = () => {
window.open(`${config.playground}?type=${chartData.type}&componentType=${chartData.componentType}`);
};
const handleRemove = async (id: string) => {
try {
const {
data: { code, msg },
} = await axios.post(`${config.axiosBase}/component/remove?id=${id}`);
if (code !== HttpStatus.OK) return Message.error(msg);
Message.success(msg);
getData();
} catch (error) {
return Message.error(error as any);
}
};
</script>
<template>
<div class="w-full !h-full flex flex-col gap-8px overflow-hidden">
<div class="ml-auto">
<Button type="primary" @click="handleAdd"> 新增 </Button>
</div>
<Spin class="flex-1 overflow-auto" :loading="chartData.loading" tip="加载中,请稍后...">
<Card :bordered="false">
<CardGrid
v-for="(item, index) in chartData.chartList"
class="chart-card"
:key="index"
:style="{
width: `${config.isMobileApp ? 'calc(100% - 16px)' : 'calc(20% - 16px)'}`,
}"
>
<Card class="w-full p-10px box-border" :title="item.title || '-'" hoverable>
<template #extra>
<div class="flex items-center gap-5 justify-center">
<IconShareInternal size="20" @click="openTab(item.id)" />
<Popconfirm content="确定要删除吗?" @before-ok="handleRemove(item.id)">
<IconDelete size="20" />
</Popconfirm>
</div>
</template>
<template #cover>
<div class="h-175px w-full" @click="chartClick(item.id)">
<img :src="item.image" class="w-full h-full object-cover" v-if="item.image" />
<Empty v-else class="w-full h-full flex justify-center items-center flex-col" description="暂无图片" />
</div>
</template>
<CardMeta>
<template #description>
<Descriptions :data="item.desc" layout="inline-vertical" :column="3" @click="chartClick(item.id)" />
</template>
</CardMeta>
</Card>
</CardGrid>
</Card>
</Spin>
<div class="pagination mt-15px">
<Pagination
:total="chartData.total"
show-total
@change="pageChange"
:disabled="chartData.loading"
:page-size="20"
/>
</div>
</div>
</template>
<style lang="scss" scoped>
.pagination {
display: flex;
justify-content: end;
}
:deep(.arco-card) {
background: transparent;
}
:deep(.arco-card-body) {
box-sizing: border-box;
margin: 0 !important;
padding: 0 2px 0 0 !important;
width: 100%;
gap: 16px;
}
.arco-card-grid {
box-shadow: unset !important;
}
::-webkit-scrollbar {
display: none;
}
.chart-card {
background: var(--color-bg-2);
cursor: pointer;
}
</style>

View File

@ -0,0 +1,60 @@
<script setup lang="ts">
import { RadioGroup, Radio } from "@arco-design/web-vue";
import axios from "axios";
import { onMounted, ref } from "vue";
import config from "@/config";
const emits = defineEmits(["change"]);
const handleChange = () => {
emits("change", {
type: type.value,
componentType: componentType.value,
});
};
const typeList = ref<any>([]);
const type = ref<string>("");
const componentTypeList = ref<any>([]);
const componentType = ref("");
const getComponentTypeList = async () => {
const list = (await axios.get(`${config.axiosBase}/dict/getComponentDictByType?type=${type.value}`)).data.data;
list.unshift({ label: "全部", value: "" });
componentTypeList.value = list;
componentType.value = componentTypeList.value[0]?.value;
handleChange();
};
onMounted(async () => {
typeList.value = (await axios.get(`${config.axiosBase}/dict/getDictByKey?dictKey=COMPONENT_TYPE`)).data.data;
type.value = typeList.value[0]?.value;
getComponentTypeList();
});
</script>
<template>
<RadioGroup
type="button"
v-model="type"
@change="getComponentTypeList"
size="large"
class="w-[fit-content] flex flex-wrap"
>
<Radio v-for="item in typeList" :value="item.value" :key="item.value">{{ item.label }}</Radio>
</RadioGroup>
<RadioGroup
type="button"
v-model="componentType"
@change="handleChange"
size="large"
class="w-[fit-content] flex flex-wrap"
>
<Radio v-for="item in componentTypeList" :value="item.value" :key="item.value">
{{ item.label }}
</Radio>
</RadioGroup>
</template>

View File

@ -0,0 +1,107 @@
<script setup lang="ts">
import { useRoute } from "vue-router";
import { InputSearch } from "@arco-design/web-vue";
import { computed, reactive, onBeforeUnmount, onMounted } from "vue";
import Theme from "@/modules/theme/Theme.vue";
import Bus from "@/bus";
import Logo from "@/components/logo/Logo.vue";
import { isSearchEnabled } from "./queries";
//
const pageData = reactive({
loaded: false,
});
const searchData = reactive({
loading: false,
content: "",
});
const route = useRoute();
const showSearch = computed<boolean>(() => isSearchEnabled(pageData.loaded, route.name));
const searchClick = () => {
Bus.$emit("home-search", searchData.content);
};
onMounted(() => {
pageData.loaded = true;
// loading
Bus.$on("search-loading", (loading: boolean) => {
searchData.loading = loading;
});
});
onBeforeUnmount(() => {
Bus.$off("home-search");
Bus.$off("search-loading");
});
</script>
<template>
<div class="nav-bar_wrapper">
<div class="nav-bar_content">
<div class="nav-bar_left">
<Logo></Logo>
</div>
<div class="nav-bar_right">
<div class="menu">
<InputSearch
class="search-input"
v-model="searchData.content"
v-if="showSearch"
placeholder="输入关键词"
button-text="搜索"
search-button
@search="searchClick"
:loading="searchData.loading"
allow-clear
@press-enter="searchClick"
/>
</div>
<Theme class="theme"></Theme>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.nav-bar_wrapper {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
border-bottom: 1px solid var(--color-border);
background-color: var(--color-bg-2);
}
.nav-bar_content {
position: relative;
display: flex;
width: 100%;
height: 60px;
min-height: 60px;
max-height: 60px;
box-sizing: border-box;
z-index: 999;
}
.nav-bar_left {
width: 180px;
font-size: 30px;
}
.nav-bar_right {
height: 100%;
display: flex;
flex: 1;
align-items: center;
padding-right: 1rem;
.menu {
flex: 1;
.search-input {
width: 320px;
}
}
}
</style>

View File

@ -0,0 +1,7 @@
import config from "@/config"
import Home from '@/views/home.vue'
import { RouteRecordName } from "vue-router"
export const isSearchEnabled = (pageLoaded: boolean, routeName?: RouteRecordName | null) => {
return !config.isMobileApp && [Home.name].includes(String(routeName)) && pageLoaded
}

View File

@ -0,0 +1,43 @@
<script setup lang="ts">
import { onMounted, ref, } from "vue";
import { IconSunFill, IconMoonFill } from "@arco-design/web-vue/es/icon";
type ThemeType = 'light' | 'dark'
const size = 24
const theme = ref("light");
const themeChange = (value: ThemeType) => {
theme.value = value;
localStorage.setItem("theme", value);
themeJudge(value);
};
const themeJudge = (theme: ThemeType) => {
if (theme === "dark") {
document.body.setAttribute("arco-theme", "dark");
} else {
document.body.removeAttribute("arco-theme");
}
};
//
onMounted(() => {
//
const themeValue = localStorage.getItem("theme") as (ThemeType | null) || "light";
themeJudge(themeValue);
theme.value = themeValue;
});
</script>
<template>
<IconMoonFill v-if="theme === 'dark'" class="moon theme-icon" :size="size" @click="() => themeChange('light')" />
<IconSunFill v-else class="sun theme-icon" :size="size" @click="() => themeChange('dark')" />
</template>
<style lang="scss" scoped>
.moon {
color: var(--color-text-1)
}
.theme-icon {
cursor: pointer
}
</style>

17
src/router.ts Normal file
View File

@ -0,0 +1,17 @@
import { createRouter, createWebHashHistory } from "vue-router";
import Home from "@/views/home.vue";
const routes = [
{
name: Home.name,
path: "/",
component: () => import("./views/home.vue"),
},
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
export { router };

81
src/style.css Normal file
View File

@ -0,0 +1,81 @@
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

31
src/utils/detect.ts Normal file
View File

@ -0,0 +1,31 @@
const detectOS = () => {
const ua = window.navigator.userAgent;
const detectOther = {
desktop: false,
ios: null as null | RegExpMatchArray,
android: ua.match(/(Android)\s+([\d.]+)/),
tablet: /^(?=.*Android)(?!.*Mobile).*/.test(ua),
ipod: /(iPod).*OS\s([\d_]+)/.test(ua),
ipad: ua.match(/(iPad).*OS\s([\d_]+)/),
iphone: ua.match(/(iPhone\sOS)\s([\d_]+)/),
webkit: /WebKit\/([\d.]+)/.test(ua),
iosVersion: null as null | string,
androidVersion: null as null | string,
blackberry: /^(?=.*BB10; Touch).*Version\/([0-9]+\.[0-9])/.test(ua),
};
detectOther.ios = detectOther.ipad || detectOther.iphone;
detectOther.desktop = !(detectOther.ios || detectOther.android || detectOther.tablet || detectOther.ipod || detectOther.blackberry);
if (detectOther.ios) {
const [iosVersion] = detectOther.ios[2].split('_');
detectOther.iosVersion = iosVersion
}
if (detectOther.android) {
const [androidVersion] = (detectOther.android as RegExpMatchArray)[2].split('.');
detectOther.androidVersion = androidVersion
}
return detectOther;
}
export const os = detectOS();

8
src/utils/transform.ts Normal file
View File

@ -0,0 +1,8 @@
export function utoa(data: string): string {
return btoa(unescape(encodeURIComponent(data)));
}
export function atou(base64: string): string {
return decodeURIComponent(escape(atob(base64)));
}

53
src/views/home.vue Normal file
View File

@ -0,0 +1,53 @@
<script lang="ts">
export default {
name: "home",
};
</script>
<script setup lang="ts">
import ChartTypeGroup from "@/modules/chartList/components/ChartTypeGroup.vue";
import Bus from "@/bus";
import ChartList from "@/modules/chartList/ChartList.vue";
import { onBeforeUnmount, onMounted, ref } from "vue";
import { unref } from "vue";
const tabData = ref<Tab>();
type Tab = {
componentType: string;
type: string;
};
const searchValue = ref("");
const tabChange = (params: Tab) => {
tabData.value = params;
Bus.$emit("search", { ...tabData.value, searchValue: unref(searchValue) });
};
onMounted(() => {
Bus.$on("home-search", (params) => {
searchValue.value = params;
Bus.$emit("search", { ...tabData.value, searchValue: unref(searchValue) });
});
});
onBeforeUnmount(() => {
Bus.$off("search-loading");
});
</script>
<template>
<div class="h-full w-full flex flex-col overflow-hidden gap-8px">
<ChartTypeGroup @change="tabChange"></ChartTypeGroup>
<div class="flex-1 overflow-hidden">
<ChartList />
</div>
</div>
</template>
<style lang="scss">
.content {
padding: 20px 30px;
}
</style>

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

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

38
tsconfig.json Normal file
View File

@ -0,0 +1,38 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"strict": true,
"jsx": "preserve",
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": [
"ESNext",
"DOM"
],
"skipLibCheck": true,
"noEmit": true,
"paths": {
"@/*": [
"./src/*"
],
}
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
],
"exclude": [
"node_modules",
"dist"
],
"references": [
{
"path": "./tsconfig.node.json"
}
],
}

9
tsconfig.node.json Normal file
View File

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

6
uno.config.ts Normal file
View File

@ -0,0 +1,6 @@
// uno.config.ts
import { defineConfig } from "unocss";
export default defineConfig({
// ...UnoCSS options
});

27
vite.config.ts Normal file
View File

@ -0,0 +1,27 @@
import { defineConfig, loadEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
import UnoCSS from "unocss/vite";
export default ({ mode }) => {
const VITE_APP_PROXY: string = loadEnv(mode, process.cwd()).VITE_APP_PROXY;
return defineConfig({
plugins: [vue(), UnoCSS()],
server: {
proxy: {
"/api": {
target: VITE_APP_PROXY,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
resolve: {
alias: {
"@": resolve(__dirname, "src"),
},
},
});
};