Showing preview only (246K chars total). Download the full file or copy to clipboard to get everything.
Repository: lin-xin/vue-manage-system
Branch: master
Commit: 6a7019ec1a74
Files: 76
Total size: 228.2 KB
Directory structure:
gitextract_omct5e3d/
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── LICENSE
├── README.md
├── README_EN.md
├── auto-imports.d.ts
├── components.d.ts
├── index.html
├── package.json
├── public/
│ ├── mock/
│ │ ├── role.json
│ │ ├── table.json
│ │ └── user.json
│ └── template.xlsx
├── src/
│ ├── App.vue
│ ├── api/
│ │ └── index.ts
│ ├── assets/
│ │ └── css/
│ │ ├── icon.css
│ │ └── main.css
│ ├── components/
│ │ ├── countup.vue
│ │ ├── header.vue
│ │ ├── menu.ts
│ │ ├── sidebar.vue
│ │ ├── table-custom.vue
│ │ ├── table-detail.vue
│ │ ├── table-edit.vue
│ │ ├── table-search.vue
│ │ └── tabs.vue
│ ├── main.ts
│ ├── router/
│ │ └── index.ts
│ ├── store/
│ │ ├── permiss.ts
│ │ ├── sidebar.ts
│ │ ├── tabs.ts
│ │ └── theme.ts
│ ├── types/
│ │ ├── form-option.ts
│ │ ├── menu.ts
│ │ ├── role.ts
│ │ ├── table.ts
│ │ └── user.ts
│ ├── utils/
│ │ ├── china.ts
│ │ ├── index.ts
│ │ └── request.ts
│ ├── views/
│ │ ├── chart/
│ │ │ ├── echarts.vue
│ │ │ ├── options.ts
│ │ │ └── schart.vue
│ │ ├── dashboard.vue
│ │ ├── element/
│ │ │ ├── calendar.vue
│ │ │ ├── carousel.vue
│ │ │ ├── form.vue
│ │ │ ├── statistic.vue
│ │ │ ├── steps.vue
│ │ │ ├── tabs.vue
│ │ │ ├── tour.vue
│ │ │ ├── upload.vue
│ │ │ └── watermark.vue
│ │ ├── home.vue
│ │ ├── pages/
│ │ │ ├── 403.vue
│ │ │ ├── 404.vue
│ │ │ ├── editor.vue
│ │ │ ├── icon.vue
│ │ │ ├── login.vue
│ │ │ ├── markdown.vue
│ │ │ ├── register.vue
│ │ │ ├── reset-pwd.vue
│ │ │ ├── theme.vue
│ │ │ └── ucenter.vue
│ │ ├── system/
│ │ │ ├── menu.vue
│ │ │ ├── role-permission.vue
│ │ │ ├── role.vue
│ │ │ └── user.vue
│ │ └── table/
│ │ ├── basetable.vue
│ │ ├── export.vue
│ │ ├── import.vue
│ │ └── table-editor.vue
│ └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: https://lin-xin.github.io/images/weixin.jpg
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2016-2023 vue-manage-system
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# vue-manage-system
<a href="https://github.com/lin-xin/vue-manage-system/releases">
<img src="https://img.shields.io/github/release/lin-xin/vue-manage-system.svg" alt="GitHub release">
</a>
<a href="https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
</a>
基于 Vue3 + pinia + Element Plus 的后台管理系统解决方案。[线上演示](https://lin-xin.github.io/example/vue-manage-system/)
> Vue2 版本请看 [tag-V4.2.0](https://github.com/lin-xin/vue-manage-system/tree/V4.2.0),带后台功能请看 [tsrpc-manage-system](https://github.com/lin-xin/tsrpc-manage-system)
[文档地址](https://lin-xin.github.io/example/vuems-doc/)
[English document](https://github.com/lin-xin/manage-system/blob/master/README_EN.md)
## 赞助商
### 好问
[<img src="https://static.bestqa.net/logo/bestqa_haowen.png" width="220" height="100">](https://www.bestqa.net/home/index.html)
专业问卷服务,一对一客服,按需定制
## 支持作者
请作者喝杯咖啡吧!(微信号:linxin_20)

## 前言
该方案作为一套多功能的后台框架模板,适用于绝大部分的后台管理系统开发。基于 Vue3 + pinia + typescript,引用 Element Plus 组件库,方便开发。实现逻辑简单,适合外包项目,快速交付。
## 功能
- [x] Element Plus
- [x] vite 3
- [x] pinia
- [x] typescript
- [x] 登录/注册
- [x] Dashboard
- [x] 表格/表单
- [x] 图表 :bar_chart:
- [x] 富文本/markdown 编辑器
- [x] 图片拖拽/裁剪上传
- [x] 权限管理
- [x] 三级菜单
- [x] 自定义图标
- [x] 主题切换
## 安装步骤
> 因为使用 vite3,node 版本需要 14.18+
```
git clone https://github.com/lin-xin/vue-manage-system.git // 把模板下载到本地
cd vue-manage-system // 进入模板目录
npm install // 安装项目依赖,等待安装完成之后,安装失败可用 cnpm 或 yarn
// 运行
npm run dev
// 执行构建命令,生成的dist文件夹放在服务器下即可访问
npm run build
```
## 项目截图
### 首页

### 登录

## License
[MIT](https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE)
================================================
FILE: README_EN.md
================================================
# vue-manage-system
<a href="https://github.com/vuejs/vue">
<img src="https://img.shields.io/badge/vue-2.6.10-brightgreen.svg" alt="vue">
</a>
<a href="https://github.com/ElemeFE/element">
<img src="https://img.shields.io/badge/element--ui-2.8.2-brightgreen.svg" alt="element-ui">
</a>
<a href="https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
</a>
<a href="https://github.com/lin-xin/vue-manage-system/releases">
<img src="https://img.shields.io/github/release/lin-xin/vue-manage-system.svg" alt="GitHub release">
</a>
<a href="https://lin-xin.gitee.io/example/work/#/donate">
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg" alt="donate">
</a>
The web management system solution based on Vue3 and ElementPlus。[live demo](https://lin-xin.gitee.io/example/work/)
Please check the version of vue2 in [tag V4.2.0](https://github.com/lin-xin/vue-manage-system/tree/V4.2.0)
## Donation

## Preface
The scheme as a set of multi-function background frame templates, suitable for most of the WEB management system development. Convenient development fast simple good components based on Vue3 and ElementPlus. Color separation of color style, support manual switch themes, and it is convenient to use a custom theme color.
## Function
- [x] Element-UI
- [x] Login/Logout
- [x] Dashboard
- [x] Table
- [x] Tabs
- [x] From
- [x] Chart :bar_chart:
- [x] Editor
- [x] Markdown
- [x] Upload pictures by clipping or dragging
- [x] Permission
- [x] Three level menu
- [x] Custom icon
## Installation steps
git clone https://github.com/lin-xin/vue-manage-system.git // Clone templates
cd vue-manage-system // Enter template directory
npm install // Installation dependency
## Local development
npm run dev
## Constructing production
// Constructing project
npm run build
## Component description and presentation
### vue-schart
Vue.js wrapper for sChart.js. Github : [vue-schart](https://github.com/lin-xin/vue-schart#/)
```html
<template>
<div>
<schart class="wrapper" canvasId="myCanvas" :options="options"></schart>
</div>
</template>
<script setup>
import { ref } from 'vue';
import Schart from "vue-schart"; // 导入Schart组件
const options = ref({
type: "bar",
title: {
text: "最近一周各品类销售图",
},
labels: ["周一", "周二", "周三", "周四", "周五"],
datasets: [
{
label: "家电",
data: [234, 278, 270, 190, 230],
},
{
label: "百货",
data: [164, 178, 190, 135, 160],
},
{
label: "食品",
data: [144, 198, 150, 235, 120],
},
],
})
</script>
<style>
.wrapper {
width: 7rem;
height: 5rem;
}
</style>
```
## Screenshot
### Default theme

### Login

## License
[MIT](https://github.com/lin-xin/vue-manage-system/blob/master/LICENSE)
================================================
FILE: auto-imports.d.ts
================================================
// Generated by 'unplugin-auto-import'
export {}
declare global {
}
================================================
FILE: components.d.ts
================================================
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
Countup: typeof import('./src/components/countup.vue')['default']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElButton: typeof import('element-plus/es')['ElButton']
ElCalendar: typeof import('element-plus/es')['ElCalendar']
ElCard: typeof import('element-plus/es')['ElCard']
ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCascader: typeof import('element-plus/es')['ElCascader']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol']
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
ElCountdown: typeof import('element-plus/es')['ElCountdown']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRate: typeof import('element-plus/es')['ElRate']
ElResult: typeof import('element-plus/es')['ElResult']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSlider: typeof import('element-plus/es')['ElSlider']
ElSpace: typeof import('element-plus/es')['ElSpace']
ElStatistic: typeof import('element-plus/es')['ElStatistic']
ElStep: typeof import('element-plus/es')['ElStep']
ElSteps: typeof import('element-plus/es')['ElSteps']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTimeline: typeof import('element-plus/es')['ElTimeline']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTour: typeof import('element-plus/es')['ElTour']
ElTourStep: typeof import('element-plus/es')['ElTourStep']
ElTransfer: typeof import('element-plus/es')['ElTransfer']
ElUpload: typeof import('element-plus/es')['ElUpload']
ElWatermark: typeof import('element-plus/es')['ElWatermark']
Header: typeof import('./src/components/header.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Sidebar: typeof import('./src/components/sidebar.vue')['default']
TableCustom: typeof import('./src/components/table-custom.vue')['default']
TableDetail: typeof import('./src/components/table-detail.vue')['default']
TableEdit: typeof import('./src/components/table-edit.vue')['default']
TableSearch: typeof import('./src/components/table-search.vue')['default']
Tabs: typeof import('./src/components/tabs.vue')['default']
}
}
================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>vue-manage-system后台管理系统</title>
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_830376_92o68tc95je.css">
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<!-- built files will be auto injected -->
</body>
</html>
================================================
FILE: package.json
================================================
{
"name": "vue-manage-system",
"version": "5.5.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"serve": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "*",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.6.3",
"countup.js": "^2.8.0",
"echarts": "^5.5.0",
"echarts-wordcloud": "^2.1.0",
"element-plus": "^2.6.3",
"md-editor-v3": "^2.11.2",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
"vue": "^3.4.5",
"vue-cropper": "1.1.1",
"vue-echarts": "^6.6.9",
"vue-router": "^4.2.5",
"vue-schart": "^2.0.0",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^3.0.0",
"@vue/compiler-sfc": "^3.1.2",
"typescript": "^4.6.4",
"unplugin-auto-import": "^0.11.2",
"unplugin-vue-components": "^0.22.4",
"vite": "^3.0.0",
"vite-plugin-vue-setup-extend": "^0.4.0",
"vue-tsc": "^0.38.4"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
================================================
FILE: public/mock/role.json
================================================
{
"list": [
{
"id": 1,
"name": "管理员",
"key": "admin",
"status": true,
"permiss": [
"0",
"1",
"11",
"12",
"13",
"2",
"21",
"22",
"23",
"24",
"3",
"31",
"32",
"33",
"331",
"332",
"4",
"41",
"42",
"5"
]
},
{
"id": 2,
"name": "普通用户",
"key": "user",
"status": true,
"permiss": [
"0",
"1",
"11",
"12",
"13"
]
}
],
"pageTotal": 2
}
================================================
FILE: public/mock/table.json
================================================
{
"list": [
{
"id": 1,
"name": "张三",
"money": 123,
"address": "广东省东莞市长安镇",
"state": true,
"date": "2019-11-1",
"thumb": "https://lin-xin.gitee.io/images/post/wms.png"
},
{
"id": 2,
"name": "李四",
"money": 456,
"address": "广东省广州市白云区",
"state": true,
"date": "2019-10-11",
"thumb": "https://lin-xin.gitee.io/images/post/node3.png"
},
{
"id": 3,
"name": "王五",
"money": 789,
"address": "湖南省长沙市",
"state": false,
"date": "2019-11-11",
"thumb": "https://lin-xin.gitee.io/images/post/parcel.png"
},
{
"id": 4,
"name": "赵六",
"money": 1011,
"address": "福建省厦门市鼓浪屿",
"state": true,
"date": "2019-10-20",
"thumb": "https://lin-xin.gitee.io/images/post/notice.png"
}
],
"pageTotal": 4
}
================================================
FILE: public/mock/user.json
================================================
{
"list": [
{
"id": 1,
"name": "张三",
"password": "123",
"email": "123@qq.com",
"phone": "12345678944",
"date": "2024-01-01",
"role": "管理员"
},
{
"id": 2,
"name": "李四",
"password": "123",
"email": "1234@qq.com",
"phone": "12345678945",
"date": "2024-01-01",
"role": "普通用户"
}
],
"pageTotal": 2
}
================================================
FILE: src/App.vue
================================================
<template>
<el-config-provider :locale="zhCn">
<router-view />
</el-config-provider>
</template>
<script setup lang="ts">
import { ElConfigProvider } from 'element-plus';
import zhCn from 'element-plus/es/locale/lang/zh-cn';
import { useThemeStore } from './store/theme';
const theme = useThemeStore();
theme.initTheme();
</script>
<style>
@import './assets/css/main.css';
</style>
================================================
FILE: src/api/index.ts
================================================
import request from '../utils/request';
export const fetchData = () => {
return request({
url: './mock/table.json',
method: 'get'
});
};
export const fetchUserData = () => {
return request({
url: './mock/user.json',
method: 'get'
});
};
export const fetchRoleData = () => {
return request({
url: './mock/role.json',
method: 'get'
});
};
================================================
FILE: src/assets/css/icon.css
================================================
[class*=" el-icon-lx"],
[class^=el-icon-lx] {
font-family: lx-iconfont !important;
}
================================================
FILE: src/assets/css/main.css
================================================
* {
margin: 0;
padding: 0;
outline: 0 !important;
}
body {
font-family: 'PingFang SC', 'Helvetica Neue', Helvetica, 'microsoft yahei', arial, STHeiTi, sans-serif;
}
a {
text-decoration: none;
}
i {
font-style: normal;
}
.container {
padding: 30px;
background: #fff;
border: 1px solid #ddd;
border-radius: 5px;
}
.el-table th {
background-color: #f5f7fa !important;
}
.plugins-tips {
padding: 20px 10px;
margin-bottom: 20px;
background: #eef1f6;
}
.plugins-tips a {
color: var(--el-color-primary);
}
.el-button + .el-tooltip {
margin-left: 10px;
}
.mgb20 {
margin-bottom: 20px;
}
.mgb10 {
margin-bottom: 10px;
}
.mr10 {
margin-right: 10px;
}
.move-enter-active,
.move-leave-active {
transition: opacity 0.1s ease;
}
.move-enter-from,
.move-leave-to {
opacity: 0;
}
.el-time-panel__content::after,
.el-time-panel__content::before {
margin-top: -7px;
}
.el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default) {
padding-bottom: 0;
}
[hidden] {
display: none !important;
}
.flex-center {
display: flex;
justify-content: center;
align-items: center;
}
:root {
--header-bg-color: #242f42;
--header-text-color: #fff;
--active-color: var(--el-color-primary);
}
================================================
FILE: src/components/countup.vue
================================================
<template>
<span ref="countRef"></span>
</template>
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue';
import { CountUp } from 'countup.js';
const props = defineProps({
end: {
type: Number,
required: true,
},
options: {
type: Object,
default: () => ({}),
required: false,
},
});
const countRef = ref<any>(null);
let countUp: any;
onMounted(() => {
countUp = new CountUp(countRef.value, props.end, props.options);
if (countUp.error) {
console.error(countUp.error);
return;
}
countUp.start();
});
watch(() => props.end, (newVal) => {
if (countUp) {
countUp.update(newVal);
}
});
</script>
================================================
FILE: src/components/header.vue
================================================
<template>
<div class="header">
<!-- 折叠按钮 -->
<div class="header-left">
<img class="logo" src="../assets/img/logo.svg" alt="" />
<div class="web-title">后台管理系统</div>
<div class="collapse-btn" @click="collapseChage">
<el-icon v-if="sidebar.collapse">
<Expand />
</el-icon>
<el-icon v-else>
<Fold />
</el-icon>
</div>
</div>
<div class="header-right">
<div class="header-user-con">
<div class="btn-icon" @click="router.push('/theme')">
<el-tooltip effect="dark" content="设置主题" placement="bottom">
<i class="el-icon-lx-skin"></i>
</el-tooltip>
</div>
<div class="btn-icon" @click="router.push('/ucenter')">
<el-tooltip
effect="dark"
:content="message ? `有${message}条未读消息` : `消息中心`"
placement="bottom"
>
<i class="el-icon-lx-notice"></i>
</el-tooltip>
<span class="btn-bell-badge" v-if="message"></span>
</div>
<div class="btn-icon" @click="setFullScreen">
<el-tooltip effect="dark" content="全屏" placement="bottom">
<i class="el-icon-lx-full"></i>
</el-tooltip>
</div>
<!-- 用户头像 -->
<el-avatar class="user-avator" :size="30" :src="imgurl" />
<!-- 用户名下拉菜单 -->
<el-dropdown class="user-name" trigger="click" @command="handleCommand">
<span class="el-dropdown-link">
{{ username }}
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<a href="https://github.com/lin-xin/vue-manage-system" target="_blank">
<el-dropdown-item>项目仓库</el-dropdown-item>
</a>
<a href="https://lin-xin.gitee.io/example/vuems-doc/" target="_blank">
<el-dropdown-item>官方文档</el-dropdown-item>
</a>
<el-dropdown-item command="user">个人中心</el-dropdown-item>
<el-dropdown-item divided command="loginout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue';
import { useSidebarStore } from '../store/sidebar';
import { useRouter } from 'vue-router';
import imgurl from '../assets/img/img.jpg';
const username: string | null = localStorage.getItem('vuems_name');
const message: number = 2;
const sidebar = useSidebarStore();
// 侧边栏折叠
const collapseChage = () => {
sidebar.handleCollapse();
};
onMounted(() => {
if (document.body.clientWidth < 1500) {
collapseChage();
}
});
// 用户名下拉菜单选择事件
const router = useRouter();
const handleCommand = (command: string) => {
if (command == 'loginout') {
localStorage.removeItem('vuems_name');
router.push('/login');
} else if (command == 'user') {
router.push('/ucenter');
}
};
const setFullScreen = () => {
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
document.body.requestFullscreen.call(document.body);
}
};
</script>
<style scoped>
.header {
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
width: 100%;
height: 70px;
color: var(--header-text-color);
background-color: var(--header-bg-color);
border-bottom: 1px solid #ddd;
}
.header-left {
display: flex;
align-items: center;
padding-left: 20px;
height: 100%;
}
.logo {
width: 35px;
}
.web-title {
margin: 0 40px 0 10px;
font-size: 22px;
}
.collapse-btn {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
padding: 0 10px;
cursor: pointer;
opacity: 0.8;
font-size: 22px;
}
.collapse-btn:hover {
opacity: 1;
}
.header-right {
float: right;
padding-right: 50px;
}
.header-user-con {
display: flex;
height: 70px;
align-items: center;
}
.btn-fullscreen {
transform: rotate(45deg);
margin-right: 5px;
font-size: 24px;
}
.btn-icon {
position: relative;
width: 30px;
height: 30px;
text-align: center;
cursor: pointer;
display: flex;
align-items: center;
color: var(--header-text-color);
margin: 0 5px;
font-size: 20px;
}
.btn-bell-badge {
position: absolute;
right: 4px;
top: 0px;
width: 8px;
height: 8px;
border-radius: 4px;
background: #f56c6c;
color: var(--header-text-color);
}
.user-avator {
margin: 0 10px 0 20px;
}
.el-dropdown-link {
color: var(--header-text-color);
cursor: pointer;
display: flex;
align-items: center;
}
.el-dropdown-menu__item {
text-align: center;
}
</style>
================================================
FILE: src/components/menu.ts
================================================
import { Menus } from '@/types/menu';
export const menuData: Menus[] = [
{
id: '0',
title: '系统首页',
index: '/dashboard',
icon: 'Odometer',
},
{
id: '1',
title: '系统管理',
index: '1',
icon: 'HomeFilled',
children: [
{
id: '11',
pid: '1',
index: '/system-user',
title: '用户管理',
},
{
id: '12',
pid: '1',
index: '/system-role',
title: '角色管理',
},
{
id: '13',
pid: '1',
index: '/system-menu',
title: '菜单管理',
},
],
},
{
id: '2',
title: '组件',
index: '2-1',
icon: 'Calendar',
children: [
{
id: '21',
pid: '3',
index: '/form',
title: '表单',
},
{
id: '22',
pid: '3',
index: '/upload',
title: '上传',
},
{
id: '23',
pid: '2',
index: '/carousel',
title: '走马灯',
},
{
id: '24',
pid: '2',
index: '/calendar',
title: '日历',
},
{
id: '25',
pid: '2',
index: '/watermark',
title: '水印',
},
{
id: '26',
pid: '2',
index: '/tour',
title: '分布引导',
},
{
id: '27',
pid: '2',
index: '/steps',
title: '步骤条',
},
{
id: '28',
pid: '2',
index: '/statistic',
title: '统计',
},
{
id: '29',
pid: '3',
index: '29',
title: '三级菜单',
children: [
{
id: '291',
pid: '29',
index: '/editor',
title: '富文本编辑器',
},
{
id: '292',
pid: '29',
index: '/markdown',
title: 'markdown编辑器',
},
],
},
],
},
{
id: '3',
title: '表格',
index: '3',
icon: 'Calendar',
children: [
{
id: '31',
pid: '3',
index: '/table',
title: '基础表格',
},
{
id: '32',
pid: '3',
index: '/table-editor',
title: '可编辑表格',
},
{
id: '33',
pid: '3',
index: '/import',
title: '导入Excel',
},
{
id: '34',
pid: '3',
index: '/export',
title: '导出Excel',
},
],
},
{
id: '4',
icon: 'PieChart',
index: '4',
title: '图表',
children: [
{
id: '41',
pid: '4',
index: '/schart',
title: 'schart图表',
},
{
id: '42',
pid: '4',
index: '/echarts',
title: 'echarts图表',
},
],
},
{
id: '5',
icon: 'Guide',
index: '/icon',
title: '图标',
permiss: '5',
},
{
id: '7',
icon: 'Brush',
index: '/theme',
title: '主题',
},
{
id: '6',
icon: 'DocumentAdd',
index: '6',
title: '附加页面',
children: [
{
id: '61',
pid: '6',
index: '/ucenter',
title: '个人中心',
},
{
id: '62',
pid: '6',
index: '/login',
title: '登录',
},
{
id: '63',
pid: '6',
index: '/register',
title: '注册',
},
{
id: '64',
pid: '6',
index: '/reset-pwd',
title: '重设密码',
},
{
id: '65',
pid: '6',
index: '/403',
title: '403',
},
{
id: '66',
pid: '6',
index: '/404',
title: '404',
},
],
},
];
================================================
FILE: src/components/sidebar.vue
================================================
<template>
<div class="sidebar">
<el-menu
class="sidebar-el-menu"
:default-active="onRoutes"
:collapse="sidebar.collapse"
:background-color="sidebar.bgColor"
:text-color="sidebar.textColor"
router
>
<template v-for="item in menuData">
<template v-if="item.children">
<el-sub-menu :index="item.index" :key="item.index" v-permiss="item.id">
<template #title>
<el-icon>
<component :is="item.icon"></component>
</el-icon>
<span>{{ item.title }}</span>
</template>
<template v-for="subItem in item.children">
<el-sub-menu
v-if="subItem.children"
:index="subItem.index"
:key="subItem.index"
v-permiss="item.id"
>
<template #title>{{ subItem.title }}</template>
<el-menu-item
v-for="(threeItem, i) in subItem.children"
:key="i"
:index="threeItem.index"
>
{{ threeItem.title }}
</el-menu-item>
</el-sub-menu>
<el-menu-item v-else :index="subItem.index" v-permiss="item.id">
{{ subItem.title }}
</el-menu-item>
</template>
</el-sub-menu>
</template>
<template v-else>
<el-menu-item :index="item.index" :key="item.index" v-permiss="item.id">
<el-icon>
<component :is="item.icon"></component>
</el-icon>
<template #title>{{ item.title }}</template>
</el-menu-item>
</template>
</template>
</el-menu>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useSidebarStore } from '../store/sidebar';
import { useRoute } from 'vue-router';
import { menuData } from '@/components/menu';
const route = useRoute();
const onRoutes = computed(() => {
return route.path;
});
const sidebar = useSidebarStore();
</script>
<style scoped>
.sidebar {
display: block;
position: absolute;
left: 0;
top: 70px;
bottom: 0;
overflow-y: scroll;
}
.sidebar::-webkit-scrollbar {
width: 0;
}
.sidebar-el-menu:not(.el-menu--collapse) {
width: 250px;
}
.sidebar-el-menu {
min-height: 100%;
}
</style>
================================================
FILE: src/components/table-custom.vue
================================================
<template>
<div>
<div class="table-toolbar" v-if="hasToolbar">
<div class="table-toolbar-left">
<slot name="toolbarBtn"></slot>
</div>
<div class="table-toolbar-right flex-center">
<template v-if="multipleSelection.length > 0">
<el-tooltip effect="dark" content="删除选中" placement="top">
<el-icon class="columns-setting-icon" @click="delSelection(multipleSelection)">
<Delete />
</el-icon>
</el-tooltip>
<el-divider direction="vertical" />
</template>
<el-tooltip effect="dark" content="刷新" placement="top">
<el-icon class="columns-setting-icon" @click="refresh">
<Refresh />
</el-icon>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip effect="dark" content="列设置" placement="top">
<el-dropdown :hide-on-click="false" size="small" trigger="click">
<el-icon class="columns-setting-icon">
<Setting />
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="c in columns">
<el-checkbox v-model="c.visible" :label="c.label" />
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-tooltip>
</div>
</div>
<el-table class="mgb20" :style="{ width: '100%' }" border :data="tableData" :row-key="rowKey"
@selection-change="handleSelectionChange" table-layout="auto">
<template v-for="item in columns" :key="item.prop">
<el-table-column v-if="item.visible" :prop="item.prop" :label="item.label" :width="item.width"
:type="item.type" :align="item.align || 'center'">
<template #default="{ row, column, $index }" v-if="item.type === 'index'">
{{ getIndex($index) }}
</template>
<template #default="{ row, column, $index }" v-if="!item.type">
<slot :name="item.prop" :rows="row" :index="$index">
<template v-if="item.prop == 'operator'">
<el-button type="warning" size="small" :icon="View" @click="viewFunc(row)">
查看
</el-button>
<el-button type="primary" size="small" :icon="Edit" @click="editFunc(row)">
编辑
</el-button>
<el-button type="danger" size="small" :icon="Delete" @click="handleDelete(row)">
删除
</el-button>
</template>
<span v-else-if="item.formatter">
{{ item.formatter(row[item.prop]) }}
</span>
<span v-else>
{{ row[item.prop] }}
</span>
</slot>
</template>
</el-table-column>
</template>
</el-table>
<el-pagination v-if="hasPagination" :current-page="currentPage" :page-size="pageSize" :background="true"
:layout="layout" :total="total" @current-change="handleCurrentChange" />
</div>
</template>
<script setup lang="ts">
import { toRefs, PropType, ref } from 'vue'
import { Delete, Edit, View, Refresh } from '@element-plus/icons-vue';
import { ElMessageBox } from 'element-plus';
const props = defineProps({
// 表格相关
tableData: {
type: Array,
default: []
},
columns: {
type: Array as PropType<any[]>,
default: []
},
rowKey: {
type: String,
default: 'id'
},
hasToolbar: {
type: Boolean,
default: true
},
// 分页相关
hasPagination: {
type: Boolean,
default: true
},
total: {
type: Number,
default: 0
},
currentPage: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 10
},
layout: {
type: String,
default: 'total, prev, pager, next'
},
delFunc: {
type: Function,
default: () => { }
},
viewFunc: {
type: Function,
default: () => { }
},
editFunc: {
type: Function,
default: () => { }
},
delSelection: {
type: Function,
default: () => { }
},
refresh: {
type: Function,
default: () => { }
},
changePage: {
type: Function,
default: () => { }
}
})
let {
tableData,
columns,
rowKey,
hasToolbar,
hasPagination,
total,
currentPage,
pageSize,
layout,
} = toRefs(props)
columns.value.forEach((item) => {
if (item.visible === undefined) {
item.visible = true
}
})
// 当选择项发生变化时会触发该事件
const multipleSelection = ref([])
const handleSelectionChange = (selection: any[]) => {
multipleSelection.value = selection
}
// 当前页码变化的事件
const handleCurrentChange = (val: number) => {
props.changePage(val)
}
const handleDelete = (row) => {
ElMessageBox.confirm('确定要删除吗?', '提示', {
type: 'warning'
})
.then(async () => {
props.delFunc(row);
})
.catch(() => { });
};
const getIndex = (index: number) => {
return index + 1 + (currentPage.value - 1) * pageSize.value
}
</script>
<style scoped>
.table-toolbar {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 10px;
}
.columns-setting-icon {
display: block;
font-size: 18px;
cursor: pointer;
color: #676767;
}
</style>
<style>
.table-header .cell {
color: #333;
}
</style>
================================================
FILE: src/components/table-detail.vue
================================================
<template>
<el-descriptions :title="title" :column="column" border>
<el-descriptions-item v-for="item in list" :span="item.span">
<template #label> {{ item.label }} </template>
<slot :name="item.prop" :rows="row">
{{ item.value || row[item.prop] }}
</slot>
</el-descriptions-item>
</el-descriptions>
</template>
<script lang="ts" setup>
const props = defineProps({
data: {
type: Object,
required: true,
}
});
const { row, title, column = 2, list } = props.data;
</script>
================================================
FILE: src/components/table-edit.vue
================================================
<template>
<el-form ref="formRef" :model="form" :rules="rules" :label-width="options.labelWidth">
<el-row>
<el-col :span="options.span" v-for="item in options.list">
<el-form-item :label="item.label" :prop="item.prop">
<!-- 文本框、数字框、下拉框、日期框、开关、上传 -->
<el-input v-if="item.type === 'input'" v-model="form[item.prop]" :disabled="item.disabled"
:placeholder="item.placeholder" clearable></el-input>
<el-input-number v-else-if="item.type === 'number'" v-model="form[item.prop]"
:disabled="item.disabled" controls-position="right"></el-input-number>
<el-select v-else-if="item.type === 'select'" v-model="form[item.prop]" :disabled="item.disabled"
:placeholder="item.placeholder" clearable>
<el-option v-for="opt in item.opts" :label="opt.label" :value="opt.value"></el-option>
</el-select>
<el-date-picker v-else-if="item.type === 'date'" type="date" v-model="form[item.prop]"
:value-format="item.format"></el-date-picker>
<el-switch v-else-if="item.type === 'switch'" v-model="form[item.prop]"
:active-value="item.activeValue" :inactive-value="item.inactiveValue"
:active-text="item.activeText" :inactive-text="item.inactiveText"></el-switch>
<el-upload v-else-if="item.type === 'upload'" class="avatar-uploader" action="#"
:show-file-list="false" :on-success="handleAvatarSuccess">
<img v-if="form[item.prop]" :src="form[item.prop]" class="avatar" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
<slot :name="item.prop" v-else>
</slot>
</el-form-item>
</el-col>
</el-row>
<el-form-item>
<el-button type="primary" @click="saveEdit(formRef)">保 存</el-button>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { FormOption } from '@/types/form-option';
import { FormInstance, FormRules, UploadProps } from 'element-plus';
import { PropType, ref } from 'vue';
const { options, formData, edit, update } = defineProps({
options: {
type: Object as PropType<FormOption>,
required: true
},
formData: {
type: Object,
required: true
},
edit: {
type: Boolean,
required: false
},
update: {
type: Function,
required: true
}
});
const form = ref({ ...(edit ? formData : {}) });
const rules: FormRules = options.list.map(item => {
if (item.required) {
return { [item.prop]: [{ required: true, message: `${item.label}不能为空`, trigger: 'blur' }] };
}
return {};
}).reduce((acc, cur) => ({ ...acc, ...cur }), {});
const formRef = ref<FormInstance>();
const saveEdit = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(valid => {
if (!valid) return false;
update(form.value);
});
};
const handleAvatarSuccess: UploadProps['onSuccess'] = (response, uploadFile) => {
form.value.thumb = URL.createObjectURL(uploadFile.raw!);
};
</script>
<style>
.avatar-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
</style>
================================================
FILE: src/components/table-search.vue
================================================
<template>
<div class="search-container">
<el-form ref="searchRef" :model="query" :inline="true">
<el-form-item :label="item.label" :prop="item.prop" v-for="item in options">
<!-- 文本框、下拉框、日期框 -->
<el-input v-if="item.type === 'input'" v-model="query[item.prop]" :disabled="item.disabled"
:placeholder="item.placeholder" clearable></el-input>
<el-select v-else-if="item.type === 'select'" v-model="query[item.prop]" :disabled="item.disabled"
:placeholder="item.placeholder" clearable>
<el-option v-for="opt in item.opts" :label="opt.label" :value="opt.value"></el-option>
</el-select>
<el-date-picker v-else-if="item.type === 'date'" type="date" v-model="query[item.prop]"
:value-format="item.format"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="search">搜索</el-button>
<el-button :icon="Refresh" @click="resetForm(searchRef)">重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { FormInstance } from 'element-plus';
import { Search, Refresh } from '@element-plus/icons-vue';
import { PropType, ref } from 'vue';
import { FormOptionList } from '@/types/form-option';
const props = defineProps({
query: {
type: Object,
required: true
},
options: {
type: Array as PropType<Array<FormOptionList>>,
required: true
},
search: {
type: Function,
default: () => { }
}
});
const searchRef = ref<FormInstance>();
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
props.search();
}
</script>
<style scoped>
.search-container {
padding: 20px 30px 0;
background-color: #fff;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 5px
}
</style>
================================================
FILE: src/components/tabs.vue
================================================
<template>
<div class="tabs-container">
<el-tabs v-model="activePath" class="tabs" type="card" closable @tab-click="clickTabls" @tab-remove="closeTabs">
<el-tab-pane
v-for="item in tabs.list"
:key="item.path"
:label="item.title"
:name="item.path"
@click="setTags(item)"
></el-tab-pane>
</el-tabs>
<div class="Tabs-close-box">
<el-dropdown @command="handleTags">
<el-button size="small" type="primary" plain>
标签选项
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu size="small">
<el-dropdown-item command="other">关闭其他</el-dropdown-item>
<el-dropdown-item command="current">关闭当前</el-dropdown-item>
<el-dropdown-item command="all">关闭所有</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { useTabsStore } from '../store/tabs';
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
const activePath = ref(route.fullPath);
const tabs = useTabsStore();
// 设置标签
const setTags = (route: any) => {
const isExist = tabs.list.some((item) => {
return item.path === route.fullPath;
});
if (!isExist) {
tabs.setTabsItem({
name: route.name,
title: route.meta.title,
path: route.fullPath,
});
}
};
setTags(route);
onBeforeRouteUpdate((to) => {
setTags(to);
});
// 关闭全部标签
const closeAll = () => {
tabs.clearTabs();
router.push('/');
};
// 关闭其他标签
const closeOther = () => {
const curItem = tabs.list.filter((item) => {
return item.path === route.fullPath;
});
tabs.closeTabsOther(curItem);
};
const handleTags = (command: string) => {
switch (command) {
case 'current':
// 关闭当前页面的标签页
tabs.closeCurrentTag({
$router: router,
$route: route,
});
break;
case 'all':
closeAll();
break;
case 'other':
closeOther();
break;
}
};
const clickTabls = (item: any) => {
router.push(item.props.name);
};
const closeTabs = (path: string) => {
const index = tabs.list.findIndex((item) => item.path === path);
tabs.delTabsItem(index);
const item = tabs.list[index] || tabs.list[index - 1];
router.push(item ? item.path : '/');
};
watch(
() => route.fullPath,
(newVal, oldVal) => {
activePath.value = newVal;
}
);
</script>
<style scss>
.tabs-container {
position: relative;
overflow: hidden;
background: #fff;
padding: 2px 120px 0 0;
}
.tabs {
.el-tabs__header {
margin-bottom: 0;
}
.el-tabs__nav {
height: 28px;
}
.el-tabs__nav-next,
.el-tabs__nav-prev {
line-height: 32px;
}
&.el-tabs {
--el-tabs-header-height: 28px;
}
}
.Tabs-close-box {
position: absolute;
right: 0;
top: 0;
box-sizing: border-box;
padding-top: 1px;
text-align: center;
width: 110px;
height: 30px;
background: #fff;
box-shadow: -3px 0 15px 3px rgba(0, 0, 0, 0.1);
z-index: 10;
}
</style>
================================================
FILE: src/main.ts
================================================
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
import App from './App.vue';
import router from './router';
import { usePermissStore } from './store/permiss';
import 'element-plus/dist/index.css';
import './assets/css/icon.css';
const app = createApp(App);
app.use(createPinia());
app.use(router);
// 注册elementplus图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
// 自定义权限指令
const permiss = usePermissStore();
app.directive('permiss', {
mounted(el, binding) {
if (binding.value && !permiss.key.includes(String(binding.value))) {
el['hidden'] = true;
}
},
});
app.mount('#app');
================================================
FILE: src/router/index.ts
================================================
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import { usePermissStore } from '../store/permiss';
import Home from '../views/home.vue';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
const routes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/dashboard',
},
{
path: '/',
name: 'Home',
component: Home,
children: [
{
path: '/dashboard',
name: 'dashboard',
meta: {
title: '系统首页',
noAuth: true,
},
component: () => import(/* webpackChunkName: "dashboard" */ '../views/dashboard.vue'),
},
{
path: '/system-user',
name: 'system-user',
meta: {
title: '用户管理',
permiss: '11',
},
component: () => import(/* webpackChunkName: "system-user" */ '../views/system/user.vue'),
},
{
path: '/system-role',
name: 'system-role',
meta: {
title: '角色管理',
permiss: '12',
},
component: () => import(/* webpackChunkName: "system-role" */ '../views/system/role.vue'),
},
{
path: '/system-menu',
name: 'system-menu',
meta: {
title: '菜单管理',
permiss: '13',
},
component: () => import(/* webpackChunkName: "system-menu" */ '../views/system/menu.vue'),
},
{
path: '/table',
name: 'basetable',
meta: {
title: '基础表格',
permiss: '31',
},
component: () => import(/* webpackChunkName: "table" */ '../views/table/basetable.vue'),
},
{
path: '/table-editor',
name: 'table-editor',
meta: {
title: '可编辑表格',
permiss: '32',
},
component: () => import(/* webpackChunkName: "table-editor" */ '../views/table/table-editor.vue'),
},
{
path: '/schart',
name: 'schart',
meta: {
title: 'schart图表',
permiss: '41',
},
component: () => import(/* webpackChunkName: "schart" */ '../views/chart/schart.vue'),
},
{
path: '/echarts',
name: 'echarts',
meta: {
title: 'echarts图表',
permiss: '42',
},
component: () => import(/* webpackChunkName: "echarts" */ '../views/chart/echarts.vue'),
},
{
path: '/icon',
name: 'icon',
meta: {
title: '图标',
permiss: '5',
},
component: () => import(/* webpackChunkName: "icon" */ '../views/pages/icon.vue'),
},
{
path: '/ucenter',
name: 'ucenter',
meta: {
title: '个人中心',
},
component: () => import(/* webpackChunkName: "ucenter" */ '../views/pages/ucenter.vue'),
},
{
path: '/editor',
name: 'editor',
meta: {
title: '富文本编辑器',
permiss: '291',
},
component: () => import(/* webpackChunkName: "editor" */ '../views/pages/editor.vue'),
},
{
path: '/markdown',
name: 'markdown',
meta: {
title: 'markdown编辑器',
permiss: '292',
},
component: () => import(/* webpackChunkName: "markdown" */ '../views/pages/markdown.vue'),
},
{
path: '/export',
name: 'export',
meta: {
title: '导出Excel',
permiss: '34',
},
component: () => import(/* webpackChunkName: "export" */ '../views/table/export.vue'),
},
{
path: '/import',
name: 'import',
meta: {
title: '导入Excel',
permiss: '33',
},
component: () => import(/* webpackChunkName: "import" */ '../views/table/import.vue'),
},
{
path: '/theme',
name: 'theme',
meta: {
title: '主题设置',
permiss: '7',
},
component: () => import(/* webpackChunkName: "theme" */ '../views/pages/theme.vue'),
},
{
path: '/calendar',
name: 'calendar',
meta: {
title: '日历',
permiss: '24',
},
component: () => import(/* webpackChunkName: "calendar" */ '../views/element/calendar.vue'),
},
{
path: '/watermark',
name: 'watermark',
meta: {
title: '水印',
permiss: '25',
},
component: () => import(/* webpackChunkName: "watermark" */ '../views/element/watermark.vue'),
},
{
path: '/carousel',
name: 'carousel',
meta: {
title: '走马灯',
permiss: '23',
},
component: () => import(/* webpackChunkName: "carousel" */ '../views/element/carousel.vue'),
},
{
path: '/tour',
name: 'tour',
meta: {
title: '分步引导',
permiss: '26',
},
component: () => import(/* webpackChunkName: "tour" */ '../views/element/tour.vue'),
},
{
path: '/steps',
name: 'steps',
meta: {
title: '步骤条',
permiss: '27',
},
component: () => import(/* webpackChunkName: "steps" */ '../views/element/steps.vue'),
},
{
path: '/form',
name: 'forms',
meta: {
title: '表单',
permiss: '21',
},
component: () => import(/* webpackChunkName: "form" */ '../views/element/form.vue'),
},
{
path: '/upload',
name: 'upload',
meta: {
title: '上传',
permiss: '22',
},
component: () => import(/* webpackChunkName: "upload" */ '../views/element/upload.vue'),
},
{
path: '/statistic',
name: 'statistic',
meta: {
title: '统计',
permiss: '28',
},
component: () => import(/* webpackChunkName: "statistic" */ '../views/element/statistic.vue'),
},
],
},
{
path: '/login',
meta: {
title: '登录',
noAuth: true,
},
component: () => import(/* webpackChunkName: "login" */ '../views/pages/login.vue'),
},
{
path: '/register',
meta: {
title: '注册',
noAuth: true,
},
component: () => import(/* webpackChunkName: "register" */ '../views/pages/register.vue'),
},
{
path: '/reset-pwd',
meta: {
title: '重置密码',
noAuth: true,
},
component: () => import(/* webpackChunkName: "reset-pwd" */ '../views/pages/reset-pwd.vue'),
},
{
path: '/403',
meta: {
title: '没有权限',
noAuth: true,
},
component: () => import(/* webpackChunkName: "403" */ '../views/pages/403.vue'),
},
{
path: '/404',
meta: {
title: '找不到页面',
noAuth: true,
},
component: () => import(/* webpackChunkName: "404" */ '../views/pages/404.vue'),
},
{ path: '/:path(.*)', redirect: '/404' },
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
router.beforeEach((to, from, next) => {
NProgress.start();
const role = localStorage.getItem('vuems_name');
const permiss = usePermissStore();
if (!role && to.meta.noAuth !== true) {
next('/login');
} else if (typeof to.meta.permiss == 'string' && !permiss.key.includes(to.meta.permiss)) {
// 如果没有权限,则进入403
next('/403');
} else {
next();
}
});
router.afterEach(() => {
NProgress.done();
});
export default router;
================================================
FILE: src/store/permiss.ts
================================================
import { defineStore } from 'pinia';
interface ObjectList {
[key: string]: string[];
}
export const usePermissStore = defineStore('permiss', {
state: () => {
const defaultList: ObjectList = {
admin: [
'0',
'1',
'11',
'12',
'13',
'2',
'21',
'22',
'23',
'24',
'25',
'26',
'27',
'28',
'29',
'291',
'292',
'3',
'31',
'32',
'33',
'34',
'4',
'41',
'42',
'5',
'7',
'6',
'61',
'62',
'63',
'64',
'65',
'66',
],
user: ['0', '1', '11', '12', '13'],
};
const username = localStorage.getItem('vuems_name');
console.log(username);
return {
key: (username == 'admin' ? defaultList.admin : defaultList.user) as string[],
defaultList,
};
},
actions: {
handleSet(val: string[]) {
this.key = val;
},
},
});
================================================
FILE: src/store/sidebar.ts
================================================
import { defineStore } from 'pinia';
export const useSidebarStore = defineStore('sidebar', {
state: () => {
return {
collapse: false,
bgColor: localStorage.getItem('sidebar-bg-color') || '#324157',
textColor: localStorage.getItem('sidebar-text-color') || '#bfcbd9'
};
},
getters: {},
actions: {
handleCollapse() {
this.collapse = !this.collapse;
},
setBgColor(color: string) {
this.bgColor = color;
localStorage.setItem('sidebar-bg-color', color);
},
setTextColor(color: string) {
this.textColor = color;
localStorage.setItem('sidebar-text-color', color);
}
}
});
================================================
FILE: src/store/tabs.ts
================================================
import { defineStore } from 'pinia';
interface ListItem {
name: string;
path: string;
title: string;
}
export const useTabsStore = defineStore('tabs', {
state: () => {
return {
list: <ListItem[]>[]
};
},
getters: {
show: state => {
return state.list.length > 0;
},
nameList: state => {
return state.list.map(item => item.name);
}
},
actions: {
delTabsItem(index: number) {
this.list.splice(index, 1);
},
setTabsItem(data: ListItem) {
this.list.push(data);
},
clearTabs() {
this.list = [];
},
closeTabsOther(data: ListItem[]) {
this.list = data;
},
closeCurrentTag(data: any) {
for (let i = 0, len = this.list.length; i < len; i++) {
const item = this.list[i];
if (item.path === data.$route.fullPath) {
if (i < len - 1) {
data.$router.push(this.list[i + 1].path);
} else if (i > 0) {
data.$router.push(this.list[i - 1].path);
} else {
data.$router.push('/');
}
this.list.splice(i, 1);
break;
}
}
}
}
});
================================================
FILE: src/store/theme.ts
================================================
import { mix, setProperty } from '@/utils';
import { defineStore } from 'pinia';
export const useThemeStore = defineStore('theme', {
state: () => {
return {
primary: '',
success: '',
warning: '',
danger: '',
info: '',
headerBgColor: '#242f42',
headerTextColor: '#fff',
};
},
getters: {},
actions: {
initTheme() {
['primary', 'success', 'warning', 'danger', 'info'].forEach((type) => {
const color = localStorage.getItem(`theme-${type}`) || '';
if (color) {
this.setPropertyColor(color, type); // 设置主题色
}
});
const headerBgColor = localStorage.getItem('header-bg-color');
headerBgColor && this.setHeaderBgColor(headerBgColor);
const headerTextColor = localStorage.getItem('header-text-color');
headerTextColor && this.setHeaderTextColor(headerTextColor);
},
resetTheme() {
['primary', 'success', 'warning', 'danger', 'info'].forEach((type) => {
this.setPropertyColor('', type); // 重置主题色
});
},
setPropertyColor(color: string, type: string = 'primary') {
this[type] = color;
setProperty(`--el-color-${type}`, color);
localStorage.setItem(`theme-${type}`, color);
this.setThemeLight(type);
},
setThemeLight(type: string = 'primary') {
[3, 5, 7, 8, 9].forEach((v) => {
setProperty(`--el-color-${type}-light-${v}`, mix('#ffffff', this[type], v / 10));
});
setProperty(`--el-color-${type}-dark-2`, mix('#ffffff', this[type], 0.2));
},
setHeaderBgColor(color: string) {
this.headerBgColor = color;
setProperty('--header-bg-color', color);
localStorage.setItem(`header-bg-color`, color);
},
setHeaderTextColor(color: string) {
this.headerTextColor = color;
setProperty('--header-text-color', color);
localStorage.setItem(`header-text-color`, color);
}
}
});
================================================
FILE: src/types/form-option.ts
================================================
export interface FormOption {
list: FormOptionList[];
labelWidth?: number | string;
span?: number;
}
export interface FormOptionList {
prop: string;
label: string;
type: string;
placeholder?: string;
disabled?: boolean;
opts?: any[];
format?: string;
activeValue?: any;
inactiveValue?: any;
activeText?: string;
inactiveText?: string;
required?: boolean;
}
================================================
FILE: src/types/menu.ts
================================================
export interface Menus {
id: string;
pid?: string;
icon?: string;
index: string;
title: string;
permiss?: string;
children?: Menus[];
}
================================================
FILE: src/types/role.ts
================================================
export interface Role {
id: number;
name: string;
key: string;
status: boolean;
permiss: string[]
}
================================================
FILE: src/types/table.ts
================================================
export interface TableItem {
id: number;
name: string;
thumb: string;
money: number;
state: string;
date: string;
address: string;
}
================================================
FILE: src/types/user.ts
================================================
export interface User {
id: number;
name: string;
password: string;
email: string;
phone: string;
role: string;
date: string;
}
export interface Register {
username: string;
password: string;
email: string;
}
================================================
FILE: src/utils/china.ts
================================================
let chinaMap:any = {"type":"FeatureCollection","features":[{"id":"710000","geometry":{"type":"MultiPolygon","coordinates":[["@@°Ü¯Û","@@ƛĴÕƊÉɼģºðʀ\\ƎsÆNŌÔĚänÜƤɊĂǀĆĴĤNJŨxĚĮǂƺòƌâÔ®ĮXŦţƸZûÐƕƑGđ¨ĭMó·ęcëƝɉlÝƯֹÅŃ^Ó·śŃNjƏďíåɛGɉ¿IċããF¥ĘWǬÏĶñÄ","@@\\p|WoYG¿¥Ij@","@@
¡@V^RqBbAnTXeQr©C","@@ÆEEkWqë I"]],"encodeOffsets":[[[122886,24033],[123335,22980],[122375,24193],[122518,24117],[124427,22618]]]},"properties":{"cp":[121.509062,25.044332],"name":"台湾","childNum":5}},{"id":"130000","geometry":{"type":"MultiPolygon","coordinates":[["@@\\aM`ǽÓnUK
Ĝēs¤©yrý§uģcJ»eIP]ªrºc_ħ²G¼s`jΟnüsÂľP","@@U`Ts¿mÄ","@@FOhđ©OiÃ`ww^ÌkÑH«ƇǤŗĺtFu
{Z}Ö@U´
ʚLg®¯Oı°Ãw ^VbÉsmA
ê]]w§RRl£ŭuwNÁ`ÇFēÝčȻuT¡Ĺ¯Õ¯sŗő£YªhVƍ£ƅnëYNgq¼ś¿µı²UºÝUąąŖóxV@tƯJ]eR¾fe|rHA|h~Ėƍl§ÏjVë` ØoÅbbx³^zÃͶSj®AyÂhðk`«P˵EFÛ¬Y¨Ļrõqi¼Wi°§Ð±²°`[À|ĠO@ÆxO\\ta\\p_Zõ^û{ġȧXýĪÓjùÎRb^λj{íděYfíÙTymńŵōHim½éŅaVcř§ax¹XŻácWU£ôãºQ¨÷Ñws¥qEHÙ|šYQoŕÇyáĂ£MðoťÊP¡mWO¡v{ôvîēÜISpÌhp¨ jdeŔQÖjX³àĈ[n`Yp@UcM`RKhEbpŞlNut®EtqnsÁgAiúoHqCXhfgu~ÏWP½¢G^}¯ÅīGCÑ^ãziMáļMTÃƘrMc|O_¯Ŏ´|morDkO\\mĆJfl@c̬¢aĦtRıÒXòë¬WP{ŵǫƝ
īÛ÷ąV×qƥV¿aȉd³BqPBmaËđŻģmÅ®V¹d^KKonYg¯XhqaLdu¥ÍpDž¡KąÅkĝęěhq}HyÃ]¹ǧ£
Í÷¿qágPmoei¤o^á¾ZEY^
Ný{nOl±Í@Mċèk§daNaÇį¿]øRiiñEūiDZàUtėGyl}ÓM}jpEC~¡FtoQiHkk{ILgĽxqÈƋÄdeVDJj£J|ÅdzÂFt~KŨ¸IÆv|¢r}èonb}`RÎÄn°ÒdÞ²^®lnÐèĄlðÓ×]ªÆ}LiñÖ`^°Ç¶p®đDcŋ`ZÔ¶êqvFÆN®ĆTH®¦O¾IbÐã´BĐɢŴÆíȦpĐÞXR·nndO¤OÀĈƒQgµFo|gȒęSWb©osx|hYhgŃfmÖĩnºTÌSp¢dYĤ¶UĈjlǐpäðëx³kÛfw²Xjz~ÂqbTÑěŨ@|oMzv¢ZrÃVw¬ŧˏf°ÐTªqs{S¯r æÝl¼ÖĞ džiGĘJ¼lr}~K¨ŸƐÌWö¼Þ°nÞoĦL|C~D©|q]SvKÑcwpÏÏĿćènĪWlĄkT}¬Tp~®Hgd˒ĺBVtEÀ¢ôPĎƗè@~kü\\rÊĔÖæW_§¼F´©òDòjYÈrbĞāøŀG{ƀ|¦ðrb|ÀH`pʞkvGpuARhÞÆǶgĘTǼƹS£¨¡ù³ŘÍ]¿ÂyôEP xX¶¹ÜO¡gÚ¡IwÃé¦ÅBÏ|ǰ
N«úmH¯âbęU~xĈbȒ{^xÖlD¸dɂ~"]],"encodeOffsets":[[[120023,41045],[121616,39981],[122102,42307]]]},"properties":{"cp":[114.502461,38.045474],"name":"河北","childNum":3}},{"id":"140000","geometry":{"type":"Polygon","coordinates":["@@ħÜ_ªlìwGkÛÃǏokćiµVZģ¡coTS˹ĪmnÕńehZg{gtwªpXaĚThȑp{¶Eh®RćƑP¿£PmcªaJyý{ýȥoÅîɡųAďä³aÏJ½¥PGąSMsWz½µÛYÓŖgxoOkĒCoȵ]¯_²ÕjāK~©ÅØ^ÔkïçămÏk]±cݯÑÃmQÍ~_apm
~ç¡qu{JÅŧ·Ls}EyÁÆcI{¤IiCfUcƌÃp§]ě«vD@¡SÀµMÅwuYY¡DbÑc¡h×]nkoQdaMç~eDÛtT©±@¥ù@É¡ZcW|WqOJmĩl«ħşvOÓ«IqăV¥D[mI~Ó¢cehiÍ]Ɠ~ĥqX·eƷn±}v[ěďŕ]_œ`¹§ÕōIo©bs^}Ét±ū«³p£ÿ¥WÑxçÁ«h×u×¥ř¾dÒ{ºvĴÎêÌɊ²¶ü¨|ÞƸµȲLLúÉƎ¤ϊęĔV`_bªS^|dzY|dz¥pZbÆ£¶ÒK}tĦÔņƠPYznÍvX¶Ěn ĠÔzý¦ª÷ÑĸÙUȌ¸dòÜJð´ìúNM¬XZ´¤ŊǸ_tldI{¦ƀðĠȤ¥NehXnYGR° ƬDj¬¸|CĞKqºfƐiĺ©ª~ĆOQª ¤@ìǦɌ²æBÊTĞHƘÁĪËĖĴŞȀÆÿȄlŤĒötνî¼ĨXh|ªM¤ÐzÞĩÒSrao³"],"encodeOffsets":[[117016,41452]]},"properties":{"cp":[112.549248,37.857014],"name":"山西","childNum":1}},{"id":"150000","geometry":{"type":"MultiPolygon","coordinates":[["@@ǪƫÌÛM
Ă[`ÕCn}¶Vc
ês¯PqFB
|S³C|kñHdiÄ¥sʼnÅ
PóÑÑE^ÅPpy_YtShQ·aHwsOnʼnÃs©iqjUSiº]ïW«gW¡ARëśijĘ
ů`çõh]y»ǃǛҤxÒm~zf}pf|ÜroÈzrKÈĵSƧżĠu~è¬vîS¼ĂhĖMÈÄw\\fŦ°W ¢¾luŸDw\\Ŗĝ","@@GVu»Aylßí¹ãe]Eāò³C¹ð¾²iÒAdkò^P²CǜңDŽ z¼g^èöŰ_IJĕê}gÁnUI«m
]jvV¼euhwqAaW_µj
»çjioQR¹ēÃßt@r³[ÛlćË^ÍÉáGOUÛOB±XkŹ£k|e]olkVͼÕqtaÏõjgÁ£§U^RLËnX°ÇBz^~wfvypV ¯ƫĉ˭ȫƗŷɿÿĿƑ˃ĝÿÃǃßËőó©ǐȍŒĖM×ÍEyxþp]ÉvïèvƀnÂĴÖ@V~Ĉ³MEĸÅĖtējyÄDXÄxGQuv_i¦aBçw˛wD©{tāmQ{EJ§KPśƘƿ¥@sCTÉ}ɃwƇy±gÑ}T[÷kÐ禫
SÒ¥¸ëBX½HáŵÀğtSÝÂa[ƣ°¯¦Pï¡]£ġÒk®G²èQ°óMq}EóƐÇ\\@áügQÍu¥FTÕ¿Jû]|mvāÎYua^WoÀa·ząÒot×¶CLƗi¯¤mƎHNJ¤îìɾŊìTdåwsRÖgĒųúÍġäÕ}Q¶¿A[¡{d×uQAMxVvMOmăl«ct[wº_ÇÊjb£ĦS_éQZ_lwgOiýe`YYJq¥IÁdz£ÙË[ÕªuƏ³ÍTs·bÁĽäė[b[ŗfãcn¥îC¿÷µ[ŏÀQōĉm¿Á^£mJVmL[{Ï_£F¥Ö{ŹA}
×Wu©ÅaųijƳhB{·TQqÙIķËZđ©Yc|M¡
LeVUóK_QWk_ĥ¿ãZ»X\\ĴuUèlG®ěłTĠğDŃGÆÍz]±
ŭ©Å]ÅÐ}UË¥©TċïxgckfWgi\\ÏĒ¥HkµEë{»ÏetcG±ahUiñiWsɁ·cCÕk]wȑ|ća}w
VaĚá G°ùnM¬¯{ÈÐÆA¥ÄêJxÙ¢hP¢ÛºµwWOóFÁz^ÀŗÎú´§¢T¤ǻƺSėǵhÝÅQgvBHouʝl_o¿Ga{ïq{¥|ſĿHĂ÷aĝÇqZñiñC³ª
»E`¨åXēÕqÉû[l}ç@čƘóO¿¡FUsAʽīccocÇS}£IS~ălkĩXçmĈ
ŀÐoÐdxÒuL^T{r@¢ÍĝKén£kQyÅõËXŷƏL§~}kq»IHėDžjĝ»ÑÞoå°qTt|r©ÏS¯·eŨĕx«È[eM¿yupN~¹ÏyN£{©għWí»Í¾səšDž_ÃĀɗ±ąijĉʍŌŷSÉA±åǥɋ@ë£R©ąP©}ĹªƏj¹erLDĝ·{i«ƫC½ÉshVz
GS|úþXgp{ÁX¿ć{ƱȏñZáĔyoÁhA}ŅĆfdʼn_¹Y°ėǩÑ¡H¯¶oMQqð¡Ë|Ñ`ƭŁX½·óÛxğįÅcQs«tȋDžFù^it«Č¯[hAi©á¥ÇĚ×l|¹y¯Kȝqgů{ñǙµïċĹzŚȭ¶¡oŽäÕG\\ÄT¿Òõr¯LguÏYęRƩɷŌO\\İТæ^Ŋ IJȶȆbÜGĝ¬¿ĚVĎgª^íu½jÿĕęjık@Ľ]ėl¥ËĭûÁėéV©±ćn©ȇÍq¯½YÃÔʼnÉNÑÅÝy¹NqáʅDǡËñƁYÅy̱os§ȋµʽǘǏƬɱàưN¢ƔÊuľýľώȪƺɂļxZĈ}ÌʼnŪĺœĭFЛĽ̅ȣͽÒŵìƩÇϋÿȮǡŏçƑůĕ~ǼȳÐUfdIxÿ\\G zâɏÙOº·pqy£@qþ@Ǟ˽IBäƣzsÂZÁàĻdñ°ŕzéØűzșCìDȐĴĺf®Àľưø@ɜÖÞKĊŇƄ§͑těï͡VAġÑÑ»d³öǍÝXĉĕÖ{þĉu¸ËʅğU̎éhɹƆ̗̮ȘNJ֥ड़ࡰţાíϲäʮW¬®ҌeרūȠkɬɻ̼ãüfƠSצɩςåȈHϚÎKdzͲOðÏȆƘ¼CϚǚ࢚˼ФÔ¤ƌĞ̪Qʤ´¼mȠJˀƲÀɠmɆDŽĜƠ´ǠN~ʢĜ¶ƌĆĘźʆȬ˪ĚǏĞGȖƴƀj`ĢçĶāàŃºēĢĖćYÀŎüôQÐÂŎŞdžŞêƖoˆDĤÕºÑǘÛˤ³̀gńƘĔÀ^ªƂ`ªt¾äƚêĦ¼ÐĔǎ¨Ȕ»͠^ˮÊȦƤøxRrŜH¤¸ÂxDÄ|ø˂˜ƮЬɚwɲFjĔ²Äw°dždÀÉ_ĸdîàŎjÊêTЪŌŜWÈ|tqĢUB~´°ÎFCU¼pĀēƄN¦¾O¶łKĊOjĚj´ĜYp{¦SĚÍ\\TתV÷Ší¨ÅDK°ßtŇĔK¨ǵÂcḷ̌ĚǣȄĽFlġUĵŇȣFʉɁMğįʏƶɷØŭOǽ«ƽū¹Ʊő̝Ȩ§ȞʘĖiɜɶʦ}¨֪ࠜ̀ƇǬ¹ǨE˦ĥªÔêFxúQEr´Wrh¤Ɛ \\talĈDJÜ|[Pll̚¸ƎGú´P¬W¦^¦H]prRn|or¾wLVnÇIujkmon£cX^Bh`¥V¦U¤¸}xRj[^xN[~ªxQ[`ªHÆÂExx^wN¶Ê|¨ìMrdYpoRzNyÀDs~bcfÌ`L¾n|¾T°c¨È¢ar¤`[|òDŞĔöxElÖdHÀI`Ď\\Àì~ÆR¼tf¦^¢ķ¶eÐÚMptgjɡČÅyġLûŇV®ÄÈƀϰP|ªVVªj¬ĚÒêp¬E|ŬÂ_~¼rƐK f{ĘFĒƌXưăkÃĄ}nµo×q£çkX{uĩ«āíÓUŅÝVUŌ]Ť¥lyň[oi{¦Lĸ
Ħ^ôâJ¨^UZðÚĒL¿Ìf£K£ʺoqNwğc`uetOj×°KJ±qÆġmĚŗos¬
qehqsuH{¸kH¡
ÊRǪÇƌbȆ¢´äÜ¢NìÉʖ¦â©Ɨؗ"]],"encodeOffsets":[[[128500,52752],[127089,51784]]]},"properties":{"cp":[111.670801,40.818311],"name":"内蒙古","childNum":2}},{"id":"210000","geometry":{"type":"MultiPolygon","coordinates":[["@@L@@s]","@@MnNm","@@dc","@@eÀC@b","@@f
XwkbrÄ`qg","@@^jtWQ","@@~ Y[c","@@I`ĖN^_¿ZÁM","@@Ïxnj{q_×^Gigp","@@iX¶B
Y","@@Y
Z","@@L_yG`b","@@^WqCTZ","@@\\[§t|]","@@m`p[","@@@é^BntaÊU]x ¯ÄPIJ°hʙK³VÕ@Y~|EvĹsǦL^pòŸÒG Ël]xxÄ_fT¤Ď¤cPC¨¸TVjbgH²sdÎdHt`B²¬GJję¶[ÐhjeXdlwhðSȦªVÊÏÆZÆŶ®²^ÎyÅHńĚDMħĜŁHkçvV[ij¼WYÀäĦ`XlR`ôLUVfK¢{NZdĒªYĸÌÚJRr¸SA|ƴgŴĴÆbvªØX~źB|¦ÕE¤Ð`\\|KUnnI]¤ÀÂĊnŎR®Ő¿¶\\ÀøíDm¦ÎbŨabaĘ\\ľã¸atÎSƐ´©v\\ÖÚÌǴ¤Â¨JKrZ_ZfjþhPkx`YRIjJcVf~sCN¤ EhæmsHy¨SðÑÌ\\\\ĐRÊwS¥fqŒßýáĞÙÉÖ[^¯ǤŲê´\\¦¬ĆPM¯£»uïpùzExanµyoluqe¦W^£ÊL}ñrkqWňûPUP¡ôJoo·U}£[·¨@XĸDXmÛݺGUCÁª½{íĂ^cjk¶Ã[q¤LÉö³cux«|Zd²BWÇ®Yß½ve±ÃCý£W{Ú^q^sÑ·¨ËMr¹·C¥GDrí@wÕKţëV·i}xËÍ÷i©ĝɝǡ]{c±OW³Ya±_ç©HĕoƫŇqr³Lys[ñ³¯OSďOMisZ±ÅFC¥Pq{Ã[Pg}\\¿ghćO
k^ĩÃXaĕËĥMoEqqZûěʼn³F¦oĵhÕP{¯~TÍlªNßYÐ{Ps{ÃVUeĎwk±ʼnVÓ½ŽJãÇÇ»Jm°dhcÀffdF~ĀeĖd`sx² ®EĦ¦dQÂd^~ăÔH¦\\LKpĄVez¤NP ǹÓRÆąJSha[¦´ÂghwmBШźhI|VV|p] ¼èNä¶ÜBÖ¼L`¼bØæKVpoúNZÞÒKxpw|ÊEMnzEQIZZNBčÚFÜçmĩWĪñtÞĵÇñZ«uD±|ƏlǗw·±PmÍada CLǑkùó¡³Ï«QaċÏOÃ¥ÕđQȥċƭy³ÁA"]],"encodeOffsets":[[[123686,41445],[126019,40435],[124393,40128],[126117,39963],[125322,40140],[126686,40700],[126041,40374],[125584,40168],[125509,40217],[125453,40165],[125362,40214],[125280,40291],[125774,39997],[125976,40496],[125822,39993],[122731,40949]]]},"properties":{"cp":[123.429096,41.796767],"name":"辽宁","childNum":16}},{"id":"220000","geometry":{"type":"Polygon","coordinates":["@@ñr½ÉKāGÁ¤ia Éȹ`\\xs¬dĆkNnuNUwNx¶c¸|\\¢
GªóĄ~RãÖÎĢùđŴÕhQxtcæëSɽʼníëlj£ƍG£nj°KƘµDsØÑpyƸ®¿bXp]vbÍZuĂ{n^IüÀSÖ¦EvRÎûh@â[ƏÈô~FNr¯ôçR±HÑlĢ^¤¢OðætxsŒ]ÞÁTĠs¶¿âÆGW¾ìA¦·TѬè¥ÏÐJ¨¼ÒÖ¼ƦɄxÊ~StD@Ă¼Ŵ¡jlºWvÐzƦZвCH AxiukdGgetqmcÛ£Ozy¥cE}|
¾cZ
k¿uŐã[oxGikfeäT@
SUwpiÚFM©£è^Ú`@v¶eňf heP¶täOlÃUgÞzŸU`l}ÔÆUvØ_Ō¬Öi^ĉi§²ÃB~¡ĈÚEgc|DC_Ȧm²rBx¼MÔ¦ŮdĨÃâYxƘDVÇĺĿg¿cwÅ\\¹¥Yĭl¤OvLjM_a W`zļMž·\\swqÝSAqŚij¯°kRē°wx^ĐkǂÒ\\]nrĂ}²ĊŲÒøãh·M{yMzysěnĒġV·°G³¼XÀ¤¹i´o¤ŃÈ`ÌDzÄUĞd\\iÖmÈBĤÜɲDEh LG¾ƀľ{WaYÍÈĢĘÔRîĐj}ÇccjoUb½{h§Ǿ{KƖµÎ÷GĄØŜçưÌs«lyiē«`å§H¥Ae^§GK}iã\\c]v©ģZmÃ|[M}ģTɟĵÂÂ`ÀçmFK¥ÚíÁbX³ÌQÒHof{]ept·GŋĜYünĎųVY^ydõkÅZW«WUa~U·SbwGçǑiW^qFuNĝ·EwUtW·Ýďæ©PuqEzwAVXRãQ`©GY
YhcUGorBd}ģÉb¡·µMicF«Yƅ»
é\\ɹ~ǙG³mØ©BšuT§Ĥ½¢Ã_ýL¡ûsT\\rke\\PnwAKy}ywdSefµ]UhĿD@mÿvaÙNSkCun
cÿ`lWėVâ¦÷~^fÏ~vwHCį`xqTlW«ï¸skmßEGqd¯R
©Ý¯¯S\\cZ¹iűƏCuƍÓXoR}M^o£
R}oªUF
uuXHlEÅÏ©¤ßgXþ¤D²ÄufàÀXXȱAc{Yw¬dvõ´KÊ£\\rµÄlidā]|DÂVH¹Þ®ÜWnCķ W§@\\¸~¤Vp¸póIO¢VOŇürXql~òÉK]¤¥Xrfkvzpm¶bwyFoúv𼤠N°ąO¥«³[éǣű]°Õ\\ÚÊĝôîŇÔaâBYlďQ[ Ë[ïÒ¥RI|`j]P"],"encodeOffsets":[[126831,44503]]},"properties":{"cp":[125.3245,43.886841],"name":"吉林","childNum":1}},{"id":"230000","geometry":{"type":"MultiPolygon","coordinates":[["@@UµNÿ¥īèçHÍøƕ¶Lǽ|g¨|a¾pVidd~ÈiíďÓQġėÇZÎXb½|ſÃH½KFgɱCģÛÇAnjÕc[VĝDZÃËÇ_ £ń³pj£º¿»WH´¯U¸đĢmtĜyzzNN|g¸÷äűѱĉā~mq^[ǁÑďlw]¯xQĔ¯l°řĴrBÞTxr[tޏĻN_yX`biNKu
P£kZĮ¦[ºxÆÀdhĹŀUÈƗCwáZħÄŭcÓ¥»NAw±qȥnD`{ChdÙFć}¢A±Äj¨]ĊÕjŋ«×`VuÓÅ~_kŷVÝyhVkÄãPsOµfgeŇ
µf@u_Ù ÙcªNªÙEojVxT@ãSefjlwH\\pŏäÀvlY½d{F~¦dyz¤PÜndsrhfHcvlwjF£G±DÏƥYyÏu¹XikĿ¦ÏqƗǀOŜ¨LI|FRĂn sª|C˜zxAè¥bfudTrFWÁ¹Am|ĔĕsķÆF´N}ć
UÕ@Áijſmuçuð^ÊýowFzØÎĕNőǏȎôªÌŒDŽàĀÄ˄ĞŀƒʀĀƘŸˮȬƬĊ°Uzouxe]}
AyÈW¯ÌmKQ]Īºif¸ÄX|sZt|½ÚUÎ lk^p{f¤lºlÆW A²PVÜPHÊâ]ÎĈÌÜk´\\@qàsĔÄQºpRij¼èi`¶bXrBgxfv»uUi^v~J¬mVp´£´VWrnP½ì¢BX¬hðX¹^TjVriªjtŊÄmtPGx¸bgRsT`ZozÆO]ÒFôÒOÆŊvÅpcGêsx´DR{AEOr°x|íb³Wm~DVjºéNNËܲɶGxŷCSt}]ûōSmtuÇÃĕNāg»íT«u}ç½BĵÞʣ¥ëÊ¡MÛ³ãȅ¡ƋaǩÈÉQG¢·lG|tvgrrf«ptęŘnÅĢrI²¯LiØsPf_vĠdxM prʹL¤¤eËÀđKïÙVY§]Ióáĥ]ķK¥j|pŇ\\kzţ¦šnņäÔVĂîά|vW®l¤èØrxm¶ă~lÄƯĄ̈́öȄEÔ¤ØQĄĄ»ƢjȦOǺ¨ìSŖÆƬyQv`cwZSÌ®ü±DŽ]ŀç¬B¬©ńzƺŷɄeeOĨSfm ĊƀP̎ēz©ĊÄÕÊmgÇsJ¥ƔŊśæÎÑqv¿íUOµªÂnĦÁ_½ä@êí
£P}Ġ[@gġ}gɊ×ûÏWXá¢užƻÌsNͽƎÁ§čŐAēeL³àydl¦ĘVçŁpśdžĽĺſÊQíÜçÛġÔsĕ¬Ǹ¯YßċġHµ ¡eå`ļrĉŘóƢFìĎWøxÊkƈdƬv|I|·©NqńRŀ¤éeŊŀàŀU²ŕƀBQ£Ď}L¹Îk@©ĈuǰųǨÚ§ƈnTËÇéƟÊcfčŤ^XmHĊĕË«W·ċëx³ǔķÐċJāwİ_ĸȀ^ôWr°oú¬Ħ
ŨK~ȰCĐ´Ƕ£fNÎèâw¢XnŮeÂÆĶ¾¾xäLĴĘlļO¤ÒĨA¢Êɚ¨®ØCÔ ŬGƠƦYĜĘÜƬDJg_ͥœ@čŅĻA¶¯@wÎqC½Ĉ»NăëKďÍQÙƫ[«ÃígßÔÇOÝáWñuZ¯ĥŕā¡ÑķJu¤E 寰WKɱ_d_}}vyõu¬ï¹ÓU±½@gÏ¿rýDg
Cdµ°MFYxw¿CG£Rƛ½Õ{]L§{qqą¿BÇƻğëܭNJË|c²}Fµ}ÙRsÓpg±QNqǫŋRwŕnéÑÉK«SeYR
ŋ@{¤SJ}D Ûǖ֍]gr¡µŷjqWÛham³~S«Ü[","@@ƨĶTLÇyqpÇÛqe{~oyen}s`qiXGù]Ëp½©lÉÁp]Þñ´FĂ^fäîºkàz¼BUv¬D"]],"encodeOffsets":[[[134456,44547],[127123,51780]]]},"properties":{"cp":[126.642464,45.756967],"name":"黑龙江","childNum":2}},{"id":"320000","geometry":{"type":"Polygon","coordinates":["@@Õg^vÁbnÀ`Jnĝ¬òM¶ĘTÖŒbe¦¦{¸ZâćNp©Hp|`mjhSEb\\afv`sz^lkljÄtg¤D¾X¿À|ĐiZȀåB·î}GL¢õcßjayBFµÏC^ĭcÙt¿sğH]j{s©HM¢QnDÀ©DaÜÞ·jgàiDbPufjDk`dPOîhw¡ĥ¥GP²ĐobºrYî¶aHŢ´ ]´rılw³r_{£DB_Ûdåuk|Ũ¯F Cºyr{XFye³Þċ¿ÂkĭB¿MvÛpm`rÚã@ƹhågËÖƿxnlč¶Åì½Ot¾dJlVJĂǀŞqvnO^JZż·Q}êÍÅmµÒ]ƍ¦Dq}¬R^èĂ´ŀĻĊIÔtIJyQŐĠMNtR®òLhĚs©»}OÓGZz¶A\\jĨFäOĤHYJvÞHNiÜaĎÉnFQlNM¤B´ĄNöɂtpŬdZÅglmuÇUšŞÚb¤uŃJŴu»¹ĄlȖħŴw̌ŵ²ǹǠ͛hĭłƕrçü±Yrřl¥i`ã__¢ćSÅr[Çq^ùzWmOĈaŐÝɞï²ʯʊáĘijĒǭPħ͍ôƋÄÄÍīçÛɈǥ£ÛmY`ó£Z«§°Ó³QafusNıDž_k}¢m[ÝóDµ¡RLčiXyÅNïă¡¸iĔÏNÌķoıdōîåŤûHcs}~Ûwbù¹£¦ÓCtOPrE^ÒogĉIµÛÅʹK
¤½phMú`mR¸¦PƚgÉLRs`£¯ãhD¨|³¤C"],"encodeOffsets":[[121451,32518]]},"properties":{"cp":[118.767413,32.041544],"name":"江苏","childNum":1}},{"id":"330000","geometry":{"type":"MultiPolygon","coordinates":[["@@jX^n
","@@sfdM","@@qP\\xz[_i","@@o\\VzRZ}mECy","@@R¢FX}°[m]","@@Cb\\}","@@e|v\\laus","@@v~s{","@@QxÂF©}","@@¹nvÞs©m","@@rQgYIh","@@bi«ZX","@@p[}ILd","@@À¿|","@@¹dnb
","@@rS}[Kl","@@g~h}","@@FlCk","@@ůTG°ĄLHm°UF","@@OdRe","@@v[u\\","@@FjâL~wyoo~sµLZ","@@¬e¹aH","@@\\nÔ¡q]L³ë\\ÿ®QÌ","@@ÊA©]ª","@@Kxv{","@@@hlIk_","@@pWcrxp","@@Md|_iA","@@¢
X£½z\\ðpN","@@hlÜ[LykAvyfw^E ","@@fp¤MusH","@@®_ma~LÁ¬`","@@@°¡mÛGĕ¨§Ianá[ýƤjfæÐNäGp","@@iMt\\","@@Zc[b","@@X®±GrưZæĉm","@@Z~dOSo|A¿qZv","@@@`EN£p","@@|s","@@@nDi","@@n
a£¾uYL¯Qª
mĉÅdMgÇjcº«ę¬K´B«Âącoċ\\xK`cįŧ«®á[~ıxu·ÅKsËÉc¢Ù\\ĭƛëbf¹ģSĜkáƉÔĈZB{aMµfzʼnfÓÔŹŁƋǝÊĉ{ğč±g³ne{çií´S¬\\ßðK¦w\\iqªĭiAuAµ_W¥ƣO\\lċĢttC¨£t`PZäuXßBsĻyekOđġĵHuXBµ]×\\°®¬F¢¾pµ¼kŘó¬Wät¸|@L¨¸µrºù³Ù~§WIZW®±Ð¨ÒÉx`²pĜrOògtÁZ{üÙ[|ûKwsPlU[}¦Rvn`hsª^nQ´ĘRWb_ rtČFIÖkĦPJ¶ÖÀÖJĈĄTĚòC ²@Pú
Øz©PCÈÚDZhŖl¬â~nm¨f©iļ«mntqÒTÜÄjL®EÌFª²iÊxبIÈhhst[Ôx}dtüGæţŔïĬaĸpMËÐj碷ðĄÆMzjWKĎ¢Q¶À_ê_@ıi«pZgf¤Nrq]§ĂN®«H±yƳí¾×ŊďŀĐÏŴǝĂíÀBŖÕªÁŐTFqĉ¯³ËCĕģi¨hÜ·ñt»¯Ï","@@ºwZRkĕWK "]],"encodeOffsets":[[[125785,31436],[125729,31431],[125513,31380],[125329,30690],[125223,30438],[125115,30114],[124815,29155],[124419,28746],[124095,28635],[124005,28609],[125000,30713],[125111,30698],[125078,30682],[125150,30684],[124014,28103],[125008,31331],[125411,31468],[125329,31479],[125369,31139],[125626,30916],[125417,30956],[125254,30976],[125199,30997],[125095,31058],[125083,30915],[124885,31015],[125218,30798],[124867,30838],[124755,30788],[124802,30809],[125267,30657],[125218,30578],[125200,30562],[125192,30787],[124968,30474],[125167,30396],[125115,30363],[124955,29879],[124714,29781],[124762,29462],[124325,28754],[124863,30077],[125366,31477]]]},"properties":{"cp":[120.153576,30.287459],"name":"浙江","childNum":43}},{"id":"340000","geometry":{"type":"MultiPolygon","coordinates":[["@@^iuLV\\","@@e©Edh","@@´CE¶zAXêeödK¡~H¸íæAȽd{ďÅÀ½W®£ChÃsikkly]_teu[bFaTign{]GqªoĈMYá|·¥f¥őaSÕėNµñĞ«Im_m¿Âa]uĜp
Z_§{Cäg¤°r[_YjÆOdý[I[á·¥Q_nùgL¾mzˆDÜÆ¶ĊJhpc¹O]iŠ]¥ jtsggDÑ¡w×jÉ©±EFËKiÛÃÕYv
sm¬njĻª§emná}k«ŕgđ²ÙDÇ¤í¡ªOy×Où±@DñSęćăÕIÕ¿IµĥOlJÕÍRÍ|JìĻÒåyķrĕq§ÄĩsWÆßF¶X®¿mw
RIÞfßoG³¾©uyHį{Ɓħ¯AFnuP
ÍÔzVdàôº^Ðæd´oG¤{S¬ćxã}ŧ×Kǥĩ«ÕOEзÖdÖsƘѨ[Û^Xr¢¼§xvÄÆµ`K§ tÒ´Cvlo¸fzŨð¾NY´ı~ÉĔē
ßúLÃÃ_ÈÏ|]ÂÏHlg`ben¾¢pUh~ƴ˶_r sĄ~cƈ]|r c~`¼{À{ȒiJjz`îÀT¥Û³
]u}f
ïQl{skloNdjäËzDvčoQďHI¦rbrHĖ~BmlNRaĥTX\\{fÁKÁ®TLÂÄMtÊgĀDĄXƔvDcÎJbt[¤D@®hh~kt°ǾzÖ@¾ªdbYhüóV´ŮŒ¨Üc±r@J|àuYÇÔG·ĚąĐlŪÚpSJ¨ĸLvÞcPæķŨ®mÐálsgd×mQ¨ųƩޤIÎs°KZpĄ|XwWdϵmkǀwÌÕæhºgBĝâqÙĊzÖgņtÀÁĂÆáhEz|WzqD¹°Eŧl{ævÜcA`¤C`|´qxIJkq^³³GšµbíZ
¹qpa±ď OH¦Ħx¢gPícOl_iCveaOjCh߸iÝbÛªCC¿mRV§¢A|tbkĜEÀtîm^g´fÄ"]],"encodeOffsets":[[[121722,32278],[119475,30423],[121606,33646]]]},"properties":{"cp":[117.283042,31.86119],"name":"安徽","childNum":3}},{"id":"350000","geometry":{"type":"MultiPolygon","coordinates":[["@@zht´}[","@@aj^~ĆGå","@@edH
se","@@@vPGsyQ","@@sBzddW[O","@@S¨Qy","@@NVucW","@@qptB@q","@@¸[iu","@@Q\\pD[_","@@jSwUappI","@@eXª~","@@AjvFoo","@@fT_Çí\\v|ba¦jZÆy|®","@@IjLg","@@wJIx«¼AoNe{M¥","@@K±¡ÓČ~N¾","@@k¡¹Eh~c®uDqZì¡I~Māe£bN¨gZý¡a±Öcp©PhI¢Qq
ÇGj|¥U g[Ky¬ŏv@OptÉEF\\@ åA¬V{XģĐBy
cpě
¼³Ăp·¤¥ohqqÚ¡ŅLs^á§qlÀhH¨MCe»åÇGD¥zPO£čÙkJA¼ßėuĕeûÒiÁŧS[¡Uûŗ½ùěcݧSùĩąSWó«íęACµeRåǃRCÒÇZÍ¢ź±^dlstjD¸ZpuÔâÃH¾oLUêÃÔjjēò´ĄWƛ
^Ñ¥Ħ@ÇòmOw¡õyJyD}¢ďÑÈġfZda©º²z£NjD°Ötj¶¬ZSÎ~¾c°¶ÐmxO¸¢Pl´SL|¥AȪĖMņIJg®áIJČĒü` QF¬h|ĂJ@zµ |ê³È ¸UÖŬŬÀCtrĸr]ðM¤ĶIJHtÏ AĬkvsq^aÎbvdfÊòSD´Z^xPsĂrvƞŀjJd×ŘÉ ®AΦĤdxĆqAZRÀMźnĊ»İÐZ YXæJyĊ²·¶q§·K@·{sXãô«lŗ¶»o½E¡«¢±¨Y®Ø¶^AvWĶGĒĢPlzfļtàAvWYãO_¤sD§ssČġ[kƤPX¦`¶®BBvĪjv©jx[L¥àï[F
¼ÍË»ğV`«Ip}ccÅĥZEãoP
´B@D¸m±z«Ƴ¿å³BRضWlâþäą`]Z£Tc ĹGµ¶Hm@_©k¾xĨôȉðX«½đCIbćqK³ÁÄš¬OAwã»aLʼnËĥW[ÂGIÂNxij¤D¢îĎÎB§°_JGs¥E@
¤uć
PåcuMuw¢BI¿]zG¹guĮI"]],"encodeOffsets":[[[123250,27563],[122541,27268],[123020,27189],[122916,27125],[122887,26845],[122808,26762],[122568,25912],[122778,26197],[122515,26757],[122816,26587],[123388,27005],[122450,26243],[122578,25962],[121255,25103],[120987,24903],[122339,25802],[121042,25093],[122439,26024]]]},"properties":{"cp":[119.306239,26.075302],"name":"福建","childNum":18}},{"id":"360000","geometry":{"type":"Polygon","coordinates":["@@ÖP¬ǦĪØLŨä~Ĉw«|TH£pc³Ïå¹]ĉđxe{ÎÓvOEm°BƂĨİ|Gvz½ª´HàpeJÝQxnÀWEµàXÅĪt¨ÃĖrÄwÀFÎ|Ă¡WÕ¸cf¥XaęST±m[r«_gmQu~¥V\\OkxtL E¢Ú^~ýØkbēqoě±_Êw§Ñ²ÏƟė¼mĉŹ¿NQ
YBąrwģcÍ¥BŗÊcØiIƝĿuqtāwO]³YCñTeÉcaubÍ]trluī
BÐGsĵıN£ï^ķqsq¿DūūVÕ·´Ç{éĈýÿOER_đûIċâJhŅıNȩĕB
¦K{Tk³¡OP·wnµÏd¯}½TÍ«YiµÕsC¯iM¤¦¯P|ÿUHvhe¥oFTuõ\\OSsMòđƇiaºćXĊĵà·çhƃ÷Ç{ígu^đgm[ÙxiIN¶Õ»lđÕwZSÆv©_ÈëJbVkĔVÀ¤P¾ºÈMÖxlò~ªÚàGĂ¢B±ÌKyñ`w²¹·
`gsÙfIěxŕeykpudjuTfb·hh¿Jd[\\LáƔĨƐAĈepÀÂMD~ņªe^\\^§ý©j×cZبzdÒa¶lÒJìõ`oz÷@¤u޸´ôęöY¼HČƶajlÞƩ¥éZ[|h}^U ¥pĄžƦO lt¸Æ Q\\aÆ|CnÂOjtĚĤdÈF`¶@Ðë ¦ōÒ¨SêvHĢÛ@[Æ
QoxHW[ŰîÀt¦DŽ~NĠ¢lĄtZoCƞÔºCxrpČNpj¢{f_Y`_eq®Aot`@oDXfkp¨|s¬\\DÄSfè©Hn¬
^DhÆyøJhØxĢĀLÊƠPżċĄwĮ¶"],"encodeOffsets":[[118923,30536]]},"properties":{"cp":[115.892151,28.676493],"name":"江西","childNum":1}},{"id":"370000","geometry":{"type":"MultiPolygon","coordinates":[["@@Xjd]mE","@@itnq","@@Dl@k","@@TGw","@@K¬U","@@Wd`c","@@PtMs","@@LnXlc","@@ppVu]Qn","@@cdzAU_","@@udRhnCE
","@@oIpP","@@M{ĿčwbxƨîKÎMĮ]ZF½Y]â£ph¶¨râøÀÎǨ¤^ºÄGz~grĚĜlĞÆLĆdž¢Îo¦cvKbgr°WhmZp L]LºcUÆnżĤÌĒbAnrOA´ȊcÀbƦUØrĆUÜøĬƞŶǬĴóò_Ä«ªdÎÉnb²ĦhņBĖįĦåXćì@L¯´ywƕCéõė ƿ¸lµZæyj|BíÂKNNnoƈfÈMZwnŐNàúÄsTJULîVjǎ¾ĒØDz²XPn±ŴPè¸ŔLƔÜƺ_TüÃĤBBċÈöA´faM¨{«M`¶d¡ôÖ°mȰBÔjj´PM|c^d¤u¤Û´ä«ƢfPk¶Môl]Lb}su^ke{lC
MrDÇ]NÑFsmoõľHyGă{{çrnÓEƕZGª¹Fj¢ÿ©}ÌCǷë¡ąuhÛ¡^KxC`C\\bÅxì²ĝÝ¿_NīCȽĿåB¥¢·IŖÕy\\¹kxãČáKµË¤ÁçFQ¡KtŵƋ]CgÏAùSedcÚźuYfyMmhUWpSyGwMPqŀÁ¼zK¶GY§Ë@´śÇµƕBm@IogZ¯uTMx}CVKï{éƵP_K«pÛÙqċtkkù]gTğwoɁsMõ³ăAN£MRkmEÊčÛbMjÝGu
IZGPģãħE[iµBEuDPÔ~ª¼ęt]ûG§¡QMsğNPŏįzs£Ug{đJĿļā³]ç«Qr~¥CƎÑ^n¶ÆéÎR~ݏYI] PumŝrƿIā[xedzL¯v¯s¬ÁY
~}
ťuŁgƋpÝĄ_ņī¶ÏSR´ÁP~¿Cyċßdwk´SsX|t`Ä ÈðAªìÎT°¦Dda^lĎDĶÚY°`ĪŴǒàŠv\\ebZHŖR¬ŢƱùęOÑM³FÛaj"]],"encodeOffsets":[[[123806,39303],[123821,39266],[123742,39256],[123702,39203],[123649,39066],[123847,38933],[123580,38839],[123894,37288],[123043,36624],[123344,38676],[123522,38857],[123628,38858],[118267,36772]]]},"properties":{"cp":[117.000923,36.675807],"name":"山东","childNum":13}},{"id":"410000","geometry":{"type":"MultiPolygon","coordinates":[["@@dXD}~Hgq~ÔN~zkĘHVsDzßjŬŢ`Pûàl¢\\ÀEhİgÞē X¼`khÍLùµP³swIÓzeŠĠð´E®ÚPtºIŊʺL«šŕQGYfa[şußǑĩų_Z¯ĵÙčC]kbc¥CS¯ëÍB©ïÇÃ_{sWTt³xlàcČzÀD}ÂOQ³ÐTĬµƑпŸghłŦv~}ÂZ«¤lPÇ£ªÝŴÅR§ØnhctâknÏľŹUÓÝdKuķI§oTũÙďkęĆH¸Ó\\Ä¿PcnS{wBIvÉĽ[GqµuŇôYgûZca©@½Õǽys¯}lgg@C\\£asIdÍuCQñ[L±ęk·ţb¨©kK»KC²òGKmĨS`UQnk}AGēsqaJ¥ĐGRĎpCuÌy ã iMcplk|tRkðev~^´¦ÜSí¿_iyjI|ȑ|¿_»d}q^{Ƈdă}tqµ`ŷ飩V¡om½ZÙÏÁRD|JOÈpÀRsI{ùÓjuµ{t}uËRivGçJFjµåkWê´MÂHewixGw½Yŷpµú³XU½ġyłåkÚwZX·l¢Á¢KzOÎÎjc¼htoDHr
|J½}JZ_¯iPq{tę½ĕ¦Zpĵø«kQ
Ť]MÛfaQpě±ǽ¾]uFu÷nčįADp}AjmcEÇaª³o³ÆÍSƇĈÙDIzçñİ^KNiÞñ[aA²zzÌ÷D|[íijgfÕÞd®|`Ć~oĠƑô³ŊD×°¯CsøÂ«ìUMhTº¨¸ǝêWÔDruÂÇZ£ĆPZW~ØØv¬gèÂÒw¦X¤Ā´oŬ¬²Ês~]®tªapŎJ¨Öº_ŔfŐ\\Đ\\Ĝu~m²Ƹ¸fWĦrƔ}Î^gjdfÔ¡J}\\n C¦þWxªJRÔŠu¬ĨĨmFdM{\\d\\YÊ¢ú@@¦ª²SÜsC}fNècbpRmlØ^gd¢aÒ¢CZZxvƶN¿¢T@uC¬^ĊðÄn|lIlXhun[","@@hzUq"]],"encodeOffsets":[[[116744,37216],[116480,33048]]]},"properties":{"cp":[113.665412,34.757975],"name":"河南","childNum":2}},{"id":"420000","geometry":{"type":"MultiPolygon","coordinates":[["@@ASd","@@ls{d","@@¾«}{ra®pîÃ\\{øCËyyB±b\\òÝjKL ]ĎĽÌJyÚCƈćÎT´Å´pb©ÈdFin~BCo°BĎÃømv®E^vǾ½Ĝ²RobÜeN^ĺ£R¬lĶ÷YoĖ¥Ě¾|sOr°jY`~I¾®I{GqpCgyl{£ÍÍyPL¡¡¸kWxYlÙæŁĢz¾V´W¶ùŸo¾ZHxjwfxGNÁ³Xéæl¶EièIH ujÌQ~v|sv¶Ôi|ú¢FhQsğ¦SiŠBgÐE^ÁÐ{čnOÂÈUÎóĔÊēIJ}Z³½Mŧïeyp·uk³DsѨL¶_Åuèw»¡WqÜ]\\Ò§tƗcÕ¸ÕFÏǝĉăxŻČƟOKÉġÿ×wg÷IÅzCg]m«ªGeçÃTC«[t§{loWeC@ps_Bprf_``Z|ei¡oċMqow¹DƝÓDYpûsYkıǃ}s¥ç³[§cY§HK«Qy]¢wwö¸ïx¼ņ¾Xv®ÇÀµRĠÐHM±cÏdƒǍũȅȷ±DSyúĝ£ŤĀàtÖÿï[îb\\}pĭÉI±Ñy
¿³x¯No|¹HÏÛmjúË~TuęjCöAwě¬Rđl¯ ÑbŇTĿ_[IčĄʿnM¦ğ\\É[T·k¹©oĕ@A¾wya¥Y\\¥Âaz¯ãÁ¡k¥ne£ÛwE©Êō¶˓uoj_U¡cF¹[WvP©whuÕyBF`RqJUw\\i¡{jEPïÿ½fć
QÑÀQ{°fLÔ~wXgītêݾĺHd³fJd]HJ²
EoU¥HhwQsƐ»Xmg±çve]DmÍPoCc¾_hhøYrŊU¶eD°Č_N~øĹĚ·`z]Äþp¼
äÌQv\\rCé¾TnkžŐÚÜa¼ÝƆ̶Ûo
d
ĔňТJqPb ¾|J¾fXƐîĨ_Z¯À}úƲN_ĒÄ^ĈaŐyp»CÇÄKñL³ġM²wrIÒŭxjb[n«øæà ^²h¯ÚŐªÞ¸Y²ĒVø}Ā^İ´LÚm¥ÀJÞ{JVųÞŃx×sxxƈē ģMřÚðòIfĊŒ\\Ʈ±ŒdʧĘDvČ_Àæ~Dċ´A®µ¨ØLV¦êHÒ¤"]],"encodeOffsets":[[[113712,34000],[115612,30507],[113649,34054]]]},"properties":{"cp":[114.298572,30.584355],"name":"湖北","childNum":3}},{"id":"430000","geometry":{"type":"MultiPolygon","coordinates":[["@@nFZw","@@ãÆá½ÔXrCO
ËRïÿĩTooQyÓ[ŅBE¬ÎÓXaį§Ã¸G °ITxpúxÚij¥Ï̾edÄ©ĸG
àGhM¤Â_U}Ċ}¢pczfþg¤ÇôAV","@@ȴÚĖÁĐiOĜ«BxDõĚivSÌ}iùÜnкG{p°M°yÂÒzJ²Ì ÂcXëöüiáÿñőФùTz²CȆȸǎŪƑÐc°dPÎğ˶[Ƚu¯½WM¡ÉB·rínZÒ `¨GA¾\\pēXhÃRCüWGġu
Té§ŎÑ©êLM³}_EÇģc®ęisÁPDmÅ{b[RÅs·kPŽƥóRoOV~]{g\\êYƪ¦kÝbiċƵGZ»Ěõ
ó·³vŝ£ø@pyö_ëIkѵbcѧy
×dYتiþUjҳC}ÁN»hĻħƏâƓKA·³CQ±µ§¿AUƑ¹AtćOwD]JUÖgk¯b£ylZFËѱH}EbóľA¡»Ku¦·³åş¥ùBD^{ÌC´¦ŷJ£^[ª¿ğ|ƅ
N
skóā¹¿ï]ă~÷O§@Vm¡Qđ¦¢Ĥ{ºjÔª¥nf´~Õo×ÛąGû¥cÑ[Z¶ŨβSÊǔƐƀAÚŌ¦Qؼrŭ«}NÏürʬmjr@ĘrTW SsdHzƓ^ÇÂyUi¯DÅYlŹu{hT}mĉ¹¥ěDÿë©ıÓ[Oº£¥ótł¹MÕƪ`P
DiÛU¾ÅâìUñBÈ£ýhedy¡oċ`pfmjP~kZa
ZsÐd°wj§@Ĵ®w~^kÀÅKvNmX\\¨aŃqvíó¿F¤¡@ũÑVw}S@j}¾«pĂrªg àÀ²NJ¶¶Dô
K|^ª°LX¾ŴäPα£EXd^¶IJÞÜ~u¸ǔMRhsR
e`ÄofIÔ\\Ø ićymnú¨cj ¢»GČìƊÿШXeĈ¾Oð Fi ¢|[jVxrIQ_EzAN¦zLU`cªxOTu RLĪpUĪȴ^ŎµªÉFx
Üf¤ºgIJèy°Áb[¦Zb¦z½xBĖ@ªpºjS´rVźOd©ʪiĎăJP`"]],"encodeOffsets":[[[115640,30489],[112577,27316],[114113,30649]]]},"properties":{"cp":[112.982279,28.19409],"name":"湖南","childNum":3}},{"id":"440000","geometry":{"type":"MultiPolygon","coordinates":[["@@QdAsa","@@lxDRm","@@sbhNLo","@@Ă ý","@@WltOY[","@@Kr]S","@@e~AS}","@@I|Mym","@@Û³LS²Q","@@nvºBë¥cÕº","@@zdÛJm","@@°³","@@a yAª¸ËJIxØ@ĀHÉÕZofo
o","@@sŗÃÔėAƁZÄ ~°ČPºb","@@¶ÝÌvmĞh¹Ĺ","@@HdSjĒ¢D}war
u«ZqadY{K","@@el\\LqqO","@@~rMmX","@@f^E","@@øPªoj÷ÍÝħXČx°Q¨ıXJp","@@gÇƳmxatfu","@@EÆC½","@@¸B_¶ekWvSivc}p}Ăº¾NĎyj¦Èm th_®Ä}»âUzL˲Aā¡ßH©Ùñ}wkNÕ¹ÇO½¿£ēUlaUìIǪ`uTÅxYĒÖ¼kÖµMjJÚwn\\hĒv]îh|ÈƄøèg¸Ķß ĉĈWb¹ƀdéĘNTtP[öSvrCZaGubo´ŖÒÇĐ~¡zCI
özx¢PnÈñ @ĥÒ¦]ƜX³ăĔñiiÄÓVépKG½ÄÓávYoC·sitiaÀyŧΡÈYDÑům}ý|m[węõĉZÅxUO}÷N¹³ĉo_qtăqwµŁYÙǝŕ¹tïÛUïmRCº
ĭ|µÕÊK½Rē ó]GªęAxNqSF|ām¡diď×YïYWªʼnOeÚtĐ«zđ¹T
āúEáÎÁWwíHcòßÎſ¿Çdğ·ùT×Çūʄ¡XgWÀLJğ·¿ÃOj YÇ÷Sğ³kzőõmĝ[³¡VÙæÅöM̳¹pÁaËýý©D©ÜJŹƕģGą¤{Ùū
ÇO²«BƱéAÒĥ¡«BhlmtÃPµyU¯ucd·w_bŝcīímGOGBȅŹãĻFŷŽŕ@Óoo¿ē±ß}}ÓF÷tIJWÈCőâUâǙIğʼn©IijE×
Á³AĥDĈ±ÌÜÓĨ£L]ĈÙƺZǾĆĖMĸĤfÎĵlŨnÈĐtFFĤêk¶^k°f¶g}®Faf`vXŲxl¦ÔÁ²¬Ð¦pqÊ̲iXØRDÎ}Ä@ZĠsx®AR~®ETtĄZƈfŠŠHâÒÐAµ\\S¸^wĖkRzalŜ|E¨ÈNĀňZTpBh£\\ĎƀuXĖtKL¶G|»ĺEļĞ~ÜĢÛĊrOÙîvd]n¬VÊĜ°RÖpMƀ¬HbwEÀ©\\
¤]ŸI®¥D³|Ë]CúAЦ
æ´¥¸Lv¼¢ĽBaôF~®²GÌÒEYzk¤°ahlVÕI^CxĈPsBƒºVÀB¶¨R²´D","@@OR"]],"encodeOffsets":[[[117381,22988],[116552,22934],[116790,22617],[116973,22545],[116444,22536],[116931,22515],[116496,22490],[116453,22449],[113301,21439],[118726,21604],[118709,21486],[113210,20816],[115482,22082],[113171,21585],[113199,21590],[115232,22102],[115739,22373],[115134,22184],[113056,21175],[119573,21271],[119957,24020],[115859,22356],[116680,26053],[116561,22649]]]},"properties":{"cp":[113.280637,23.125178],"name":"广东","childNum":24}},{"id":"450000","geometry":{"type":"MultiPolygon","coordinates":[["@@H TI¡U","@@Ɣ_LÊFZg
čPkini«qÇczÍY®¬Ů»qR×ō©DÕ§ƙǃŵTÉĩ±ıdÑnYYIJvNĆĆØÜ Öp}e³¦m©iÓ|¹ħņ|ª¦QF¢Â¬ʖovg¿em^ucäāmÇÖåB¡Õçĝ}FϼĹ{µHKsLSđƃrč¤[AgoSŇYMÿ§Ç{FśbkylQxĕ]T·¶[B
ÑÏGáşşƇe
ăYSsFQ}BwtYğÃ@~
CÍQ ×Wj˱rÉ¥oÏ ±«ÓÂ¥kwWűue_bE~µh¯ecl¯Ïr¯EģJğ}w³Ƈē`ãògK_ÛsUʝćğ¶höO¤Ǜn³c`¡yię[ďĵűMę§]XÎ_íÛ]éÛUćİÕBƣ±
dy¹T^dûÅÑŦ·PĻþÙ`K¦
¢ÍeĥR¿³£[~äu¼dltW¸oRM¢ď\\z}Æzdvň{ÎXF¶°Â_ÒÂÏL©ÖTmu¼ãlīkiqéfA·Êµ\\őDc¥ÝFyÔćcűH_hLÜêĺШc}rn`½Ì@¸¶ªVLhŒ\\Ţĺk~Ġið°|gtTĭĸ^xvKVGréAébUuMJVÃO¡
qĂXËSģãlýà_juYÛÒBG^éÖ¶§EGÅzěƯ¤EkN[kdåucé¬dnYpAyČ{`]þ±X\\ÞÈk¡ĬjàhÂƄ¢Hè ŔâªLĒ^Öm¶ħĊAǦė¸zÚGn£¾rªŀÜt¬@ÖÚSx~øOŒŶÐÂæȠ\\ÈÜObĖw^oÞLf¬°bI lTØBÌF£Ć¹gñĤaYt¿¤VSñK¸¤nM¼JE±½¸ñoÜCƆæĪ^ĚQÖ¦^f´QüÜÊz¯lzUĺš@ìp¶n]sxtx¶@~ÒĂJb©gk{°~c°`Ô¬rV\\la¼¤ôá`¯¹LCÆbxEræOv[H[~|aB£ÖsºdAĐzNÂðsÞÆ
Ĥªbab`ho¡³F«èVZs\\\\ÔRzpp®SĪº¨ÖºN
ijd`a¦¤F³¢@`¢ĨĀìhYvlĆº¦Ċ~nS|gźv^kGÆÀè·"]],"encodeOffsets":[[[111707,21520],[113706,26955]]]},"properties":{"cp":[108.320004,22.82402],"name":"广西","childNum":2}},{"id":"460000","geometry":{"type":"Polygon","coordinates":["@@¦Ŝil¢XƦƞòïè§ŞCêɕrŧůÇąĻõ·ĉ³œ̅kÇm@ċȧŧĥĽʉƅſȓÒ˦ŝE}ºƑ[ÍĜȋ gÎfǐÏĤ¨êƺ\\Ɔ¸ĠĎvʄȀоjNðĀÒRZdžzÐĊ¢DÀɘZ"],"encodeOffsets":[[112750,20508]]},"properties":{"cp":[110.33119,20.031971],"name":"海南","childNum":1}},{"id":"510000","geometry":{"type":"MultiPolygon","coordinates":[["@@LqSn","@@ĆOìÛÐ@ĞǔNY{¤Á§d
i´ezÝúØãwIþËQǦÃqÉSJ»ĂéʔõÔƁİlƞ¹§ĬqtÀƄmÀêErĒtD®ċæcQE®³^ĭ¥©l}äQtoŖÜqÆkµªÔĻĴ¡@Ċ°B²Èw^^RsºT£ڿQPJvÄz^Đ¹Æ¯fLà´GC²dtĀRt¼¤ĦOðğfÔðDŨŁĞƘïPÈ®âbMüÀXZ ¸£@Å»»QÉ]dsÖ×_Í_ÌêŮPrĔĐÕGĂeZÜîĘqBhtO ¤tE[h|YÔZśÎs´xº±Uñt|OĩĠºNbgþJy^dÂY Į]Řz¦gC³R`Āz¢Aj¸CL¤RÆ»@Ŏk\\Ç´£YW}z@Z}öoû¶]´^NÒ}èNªPÍy¹`S°´ATeVamdUĐwʄvĮÕ\\uÆŗ¨Yp¹àZÂmWh{á}WØǍÉüwga§ßAYrÅÂQĀÕ¬LŐý®Xøxª½Ű¦¦[þ`ÜUÖ´òrÙŠ°²ÄkijnDX{U~ET{ļº¦PZcjF²Ė@pg¨B{u¨ŦyhoÚD®¯¢ WòàFΤ¨GDäz¦kŮPġqË¥À]eâÚ´ªKxīPÖ|æ[xäJÞĥsNÖ½I¬nĨY´®ÐƐmDŝuäđđEb
ee_v¡}ìęNJē}qÉåT¯µRs¡M@}ůaa¯wvƉåZw\\Z{åû`[±oiJDŦ]ĕãïrG réÏ·~ąSfy×Í·ºſƽĵȁŗūmHQ¡Y¡®ÁÃ×t«T¤JJJyJÈ`Ohߦ¡uËhIyCjmÿw
ZG
TiSsOB²fNmsPa{M{õE^Hj}gYpaeu¯oáwHjÁ½M¡pMuåmni{fk\\oÎqCwEZ¼KĝAy{m÷LwO×SimRI¯rKõBS«sFe]fµ¢óY_ÆPRcue°Cbo×bd£ŌIHgtrnyPt¦foaXďxlBowz_{ÊéWiêEGhܸºuFĈIxf®Y½ĀǙ]¤EyF²ċw¸¿@g¢§RGv»áW`ÃĵJwi]t¥wO½a[×]`ÃiüL¦LabbTÀåc}ÍhÆh®BHî|îºÉk¤Sy£ia©taį·Ɖ`ō¥UhO
ĝLk}©Fos´JmµlŁu
ønÑJWΪYÀïAetTŅÓGË«bo{ıwodƟ½OġܵxàNÖ¾P²§HKv¾]|BÆåoZ`¡Ø`ÀmºĠ~ÌЧnÇ
¿¤]wğ@srğu~Io[é±¹ ¿ſđÓ@qg¹zƱřaí°KtǤV»Ã[ĩǭƑ^ÇÓ@áťsZÏÅĭƋěpwDóÖáŻneQËq·GCœýS]x·ýq³OÕ¶Qzßti{řáÍÇWŝŭñzÇWpç¿JXĩè½cFÂLiVjx}\\NŇĖ¥GeJA¼ÄHfÈu~¸Æ«dE³ÉMA|bÒ
ćhG¬CMõƤąAvüVéŀ_V̳ĐwQj´·ZeÈÁ¨X´Æ¡Qu·»ÕZ³ġqDoy`L¬gdp°şp¦ėìÅĮZ°Iähzĵf²å ĚÑKpIN|Ñz]ń
·FU×é»R³MÉ»GM«kiér}Ã`¹ăÞmÈnÁîRǀ³ĜoİzŔwǶVÚ£À]ɜ»ĆlƂ²Ġ
þTº·àUȞÏʦ¶I«dĽĢdĬ¿»Ĕ×h\\c¬ä²GêëĤł¥ÀǿżÃÆMº}BÕĢyFVvwxBèĻĒ©Ĉt@Ğû¸£B¯¨ˋäßkķ½ªôNÔ~t¼Ŵu^s¼{TA¼ø°¢İªDè¾Ň¶ÝJ®Z´ğ~Sn|ªWÚ©òzPOȸbð¢|øĞA"]],"encodeOffsets":[[[108815,30935],[100197,35028]]]},"properties":{"cp":[104.065735,30.659462],"name":"四川","childNum":2}},{"id":"520000","geometry":{"type":"MultiPolygon","coordinates":[["@@G\\lY£cj","@@q|mc¯vÏV","@@hÑ£IsNgßHHªķÃh_¹¡ĝħń¦uÙùgS¯JH|sÝÅtÁïyMDč»eÕtA¤{b\\}G®u\\åPFqwÅaD
K°ºâ_£ùbµmÁÛĹM[q|hlaªāI}ѵ@swtwm^oµD鼊yVky°ÉûÛR
³e¥]RÕěħ[ƅåÛDpJiVÂF²I
»mN·£LbÒYbWsÀbpkiTZĄă¶Hq`
ĥ_J¯ae«KpÝx]aĕÛPÇȟ[ÁåŵÏő÷Pw}TÙ@Õs«ĿÛq©½m¤ÙH·yǥĘĉBµĨÕnđ]K©œáGçş§ÕßgǗĦTèƤƺ{¶ÉHÎd¾ŚÊ·OÐjXWrãLyzÉAL¾ę¢bĶėy_qMĔąro¼hĊw¶øV¤w²Ĉ]ÊKx|`ź¦ÂÈdrcÈbe¸`I¼čTF´¼Óýȃr¹ÍJ©k_șl³´_pĐ`oÒh¶pa^ÓĔ}D»^Xy`d[Kv
JPhèhCrĂĚÂ^Êƌ wZLĠ£ÁbrzOIlMMĪŐžËr×ÎeŦtw|¢mKjSǘňĂStÎŦEtqFT¾E쬬ôxÌO¢ K³ŀºäYPVgŎ¦Ŋm޼VZwVlz¤
£Tl®ctĽÚó{GAÇge~Îd¿æaSba¥KKûj®_Ä^\\ؾbP®¦x^sxjĶI_Ä Xâ¼Hu¨Qh¡À@Ëô}±GNìĎlT¸
`V~R°tbÕĊ`¸úÛtÏFDu[MfqGH·¥yAztMFe|R_GkChZeÚ°tov`xbDnÐ{E}ZèxNEÞREn[Pv@{~rĆAB§EO¿|UZ~ìUf¨J²ĂÝÆsªB`s¶fvö¦Õ~dÔq¨¸º»uù[[§´sb¤¢zþF¢Æ
ÀhÂW\\ıËIÝo±ĭŠ£þÊs}¡R]ěDg´VG¢j±®èºÃmpU[Á뺰rÜbNu¸}º¼`niºÔXĄ¤¼ÔdaµÁ_Ã
ftQQgR·Ǔv}Ý×ĵ]µWc¤F²OĩųãW½¯K©
]{LóµCIµ±Mß¿h©āq¬o½~@i~TUxð´ĐhwÀEîôuĶb[§nWuMÆJl½]vuıµb"]],"encodeOffsets":[[[112158,27383],[112105,27474],[112095,27476]]]},"properties":{"cp":[106.713478,26.578343],"name":"贵州","childNum":3}},{"id":"530000","geometry":{"type":"Polygon","coordinates":["@@[ùx½}ÑRHYīĺûsÍniEoã½Ya²ė{c¬ĝgĂsAØÅwďõzFjw}«Dx¿}Uũlê@HÅF¨ÇoJ´Ónũuą¡Ã¢pÒÅØ TF²xa²ËXcÊlHîAßËŁkŻƑŷÉ©hWæßUËs¡¦}teèÆ¶StÇÇ}Fd£jĈZĆÆ¤Tč\\D}O÷£U§~ŃGåŃDĝ¸Tsd¶¶Bª¤u¢ŌĎo~t¾ÍŶÒtD¦ÚiôözØX²ghįh½Û±¯ÿm·zR¦Ɵ`ªŊÃh¢rOÔ´£Ym¼èêf¯ŪĽncÚbw\\zlvWªâ ¦gmĿBĹ£¢ƹřbĥkǫßeeZkÙIKueT»sVesbaĕ ¶®dNĄÄpªy¼³BE®lGŭCǶwêżĔÂepÍÀQƞpC¼ŲÈAÎô¶RäQ^Øu¬°_Èôc´¹ò¨P΢hlϦ´ĦÆ´sâÇŲPnÊD^¯°Upv}®BP̪jǬxSöwlfòªvqĸ|`HviļndĜĆhňem·FyÞqóSᝳX_ĞçêtryvL¤§z¦c¦¥jnŞklD¤øz½ĜàĂŧMÅ|áƆàÊcðÂFÜáŢ¥\\\\ºİøÒÐJĴîD¦zK²ǏÎEh~CDhMn^ÌöÄ©ČZÀaüfɭyœpį´ěFűk]Ôě¢qlÅĆÙa¶~ÄqêljN¬¼HÊNQ´ê¼VظE^ŃÒyM{JLoÒęæe±Ķygã¯JYÆĭĘëo¥Šo¯hcK«z_prC´ĢÖY¼ v¸¢RÅW³Â§fǸYi³xR´ďUË`êĿUûuĆBƣöNDH«ĈgÑaB{ÊNF´¬c·Åv}eÇÃGB»If¦HňĕM
~[iwjUÁKE¾dĪçWIèÀoÈXòyŞŮÈXâÎŚj|àsRyµÖPr´þ ¸^wþTDŔHr¸RÌmfżÕâCôoxĜƌÆĮÐYtâŦÔ@]ÈǮƒ\\μģUsȯLbîƲŚºyhr@ĒÔƀÀ²º\\êpJ}ĠvqtĠ@^xÀ£È¨mËÏğ}n¹_¿¢×Y_æpÅA^{½Lu¨GO±Õ½ßM¶wÁĢÛPƢ¼pcIJx|ap̬HÐŊSfsðBZ¿©XÏÒKk÷Eû¿S
rEFsÕūkóVǥʼniTL¡n{uxţÏhôŝ¬ğōNNJkyPaqÂğ¤K®YxÉƋÁ]āęDqçgOgILu\\_gz]W¼~CÔē]bµogpÑ_oď`´³Țkl`IªºÎȄqÔþ»E³ĎSJ»_f·adÇqÇc¥Á_Źw{L^ɱćxU£µ÷xgĉp»ĆqNē`rĘzaĵĚ¡K½ÊBzyäKXqiWPÏɸ½řÍcÊG|µƕƣGË÷k°_^ý|_zċBZocmø¯hhcæ\\lMFlư£ĜÆyHF¨µêÕ]HA
àÓ^it `þßäkĤÎT~Wlÿ¨ÔPzUCNVv [jâôDôď[}z¿msSh¯{jïğl}šĹ[őgK©U·µË@¾m_~q¡f¹
ÅË^»f³ø}Q¡Ö˳gͱ^Ç
\\ëÃA_¿bWÏ[¶ƛé£F{īZgm@|kHǭƁć¦UĔť×ëǟ
eċ¼ȡȘÏíBÉ£āĘPªij¶ʼnÿy©nď£G¹¡I±LÉĺÑdĉÜW¥}gÁ{aqÃ¥aıęÏZÁ`"],"encodeOffsets":[[104636,22969]]},"properties":{"cp":[102.712251,25.040609],"name":"云南","childNum":1}},{"id":"540000","geometry":{"type":"Polygon","coordinates":["@@ÂhľxŖxÒVºÅâAĪÝȆµę¯Ňa±r_w~uSÕňqOj]ɄQ
£Z
UDûoY»©M[L¼qãË{VÍçWVi]ë©Ä÷àyƛhÚU°adcQ~Mx¥caÛcSyFÖkuRýq¿ÔµQĽ³aG{¿FµëªéĜÿª@¬·K·àariĕĀ«V»ŶĴūgèLǴŇƶaftèBŚ£^âǐÝ®M¦ÁǞÿ¬LhJ¾óƾƺcxwf]Y
´¦|QLn°adĊ
\\¨oǀÍŎ´ĩĀd`tÊQŞŕ|¨C^©Ĉ¦¦ÎJĊ{ëĎjª²rÐl`¼Ą[t|¦Stè¾PÜK¸dƄı]s¤î_v¹ÎVòŦj£Əsc¬_Ğ´|٦Av¦w`ăaÝaa¢e¤ı²©ªSªÈMĄwÉØŔì@T¤Ę\\õª@þo´xA sÂtŎKzó²Çȵ¢r^nĊƬ×üG¢³ {âĊ]G~bÀgVjzlhǶfOfdªB]pjTOtĊn¤}®¦Č¥d¢¼»ddY¼t¢eȤJ¤}Ǿ¡°§¤AÐlc@ĝsªćļđAçwxUuzEÖġ~AN¹ÄÅȀݦ¿ģŁéì±H
ãd«g[ؼēÀcīľġ¬cJµ
ÐʥVȝ¸ßS¹ý±ğkƁ¼ą^ɛ¤Ûÿb[}¬ōõÃ]ËNm®g@Bg}ÍF±ǐyL¥íCIijÏ÷Ñį[¹¦[âšEÛïÁÉdƅß{âNÆāŨß¾ě÷yC£k´ÓH@¹TZ¥¢į·ÌAЧ®Zc
v½Z¹|ÅWZqgW|ieZÅYVÓqdqbc²R@c¥Rã»GeeƃīQ}J[ÒK
¬Ə|oėjġĠÑN¡ð¯EBčnwôɍėª²CλŹġǝʅįĭạ̃ūȹ]ΓͧgšsgȽóϧµǛęgſ¶ҍć`ĘąŌJÞä¤rÅň¥ÖÁUětęuůÞiĊÄÀ\\Æs¦ÓRb|Â^řÌkÄŷ¶½÷f±iMÝ@ĥ°G¬ÃM¥n£Øąğ¯ß§aëbéüÑOčk£{\\eµª×MÉfm«Ƒ{Å×Gŏǩãy³©WÑăû··Qòı}¯ãIéÕÂZ¨īès¶ZÈsæĔTŘvgÌsN@îá¾ó@ÙwU±ÉT廣TđWxq¹Zobs[ׯcĩvėŧ³BM|¹kªħ¥TzNYnÝßpęrñĠĉRS~½ěVVµõ«M££µBĉ¥áºae~³AuĐh`ܳç@BÛïĿa©|z²Ý¼D£àč²ŸIûI āóK¥}rÝ_Á´éMaň¨~ªSĈ½½KÙóĿeƃÆB·¬ën×W|Uº}LJrƳlŒµ`bÔ`QÐÓ@s¬ñIÍ@ûws¡åQÑßÁ`ŋĴ{ĪTÚÅTSijYo|Ç[ǾµMW¢ĭiÕØ¿@Mh
pÕ]jéò¿OƇĆƇpêĉâlØwěsǩĵ¸c
bU¹ř¨WavquSMzeo_^gsÏ·¥Ó@~¯¿RiīB\\qTGªÇĜçPoÿfñòą¦óQīÈáPābß{ZŗĸIæÅhnszÁCËìñÏ·ąĚÝUm®óL·ăUÈíoù´Êj°ŁŤ_uµ^°ìÇ@tĶĒ¡ÆM³Ģ«İĨÅ®ğRāðggheÆ¢zÊ©Ô\\°ÝĎz~ź¤PnMĪÖB£kné§żćĆKǰ¼L¶èâz¨u¦¥LDĘz¬ýÎmĘd¾ßFzhg²Fy¦ĝ¤ċņbÎ@yĄæm°NĮZRÖíJ²öLĸÒ¨Y®ƌÐVàtt_ÚÂyĠz]ŢhzĎ{ÂĢXc|ÐqfO¢¤ögÌHNPKŖUú´xx[xvĐCûĀìÖT¬¸^}Ìsòd´_KgžLĴ
ÀBon|H@Êx¦BpŰŌ¿fµƌA¾zLjRx¶FkĄźRzŀ~¶[´HnªVƞuĒȨƎcƽÌm¸ÁÈM¦x͊ëÀxdžBú^´W£dkɾĬpw˂ØɦļĬIŚÊnŔa¸~J°îlɌxĤÊÈðhÌ®gT´øàCÀ^ªerrƘd¢İP|Ė ŸWªĦ^¶´ÂLaT±üWƜǀRÂŶUńĖ[QhlLüAÜ\\qRĄ©"],"encodeOffsets":[[90849,37210]]},"properties":{"cp":[91.132212,29.660361],"name":"西藏","childNum":1}},{"id":"610000","geometry":{"type":"Polygon","coordinates":["@@¸ÂW¢xRFq§uF@N¢XLRMº[ğȣſï|¥Jkc`sʼnǷ£Y³WN«ùMëï³ÛIg÷±mTșÚÒķø©þ¥yÓğęmWµÎumZyOŅƟĥÓ~sÑL¤µaÅ
Y¦ocyZ{y c]{Ta©`U_Ěē£ωÊƍKùK¶ȱÝƷ§{û»ÅÁȹÍéuij|¹cÑdìUYOuFÕÈYvÁCqÓTǢí§·S¹NgV¬ë÷Át°DدC´ʼnƒópģ}ąiEË
FéGU¥×K
§¶³BČ}C¿åċ`wġB·¤őcƭ²ő[Å^axwQO
ñJÙïŚĤNĔwƇÄńwĪo[_KÓª³ÙnKÇěÿ]ďă_d©·©Ýŏ°Ù®g]±ß×¥¬÷m\\iaǑkěX{¢|ZKlçhLtŇîŵœè[É@ƉĄEtƇϳħZ«mJ
×¾MtÝĦ£IwÄå\\Õ{OwĬ©LÙ³ÙTª¿^¦rÌĢŭO¥lãyC§HÍ£ßEñX¡°ÙCgpťzb`wIvA|¥hoĕ@E±iYd¥OÿµÇvPW|mCĴŜǂÒW¶¸AĜh^Wx{@¬F¸¡ķn£P|ªĴ@^ĠĈæbÔc¶lYi
^MicϰÂ[ävï¶gv@ÀĬ·lJ¸sn|¼u~a]ÆÈtŌºJpþ£KKf~¦UbyäIĺãnÔ¿^ŵMThĠܤko¼Ŏìąǜh`[tRd²IJ_XPrɲlXiL§à¹H°Ȧqº®QCbAŌJ¸ĕÚ³ĺ§ `d¨YjiZvRĺ±öVKkjGȊÄePĞZmļKÀ[`ösìhïÎoĬdtKÞ{¬èÒÒBÔpIJÇĬJŊ¦±J«[©ārHµàåVKe§|P²ÇÓ·vUzgnN¾yI@oHĆÛķhxen¡QQ±ƝJǖRbzy¸ËÐl¼EºpĤ¼x¼½~Ğà@ÚüdK^mÌSjp²ȮµûGĦ}Ħðǚ¶òƄjɂz°{ºØkÈęâ¦jªBg\\ċ°s¬]jú EȌdž¬stRÆdĠİwܸôW¾ƮłÒ_{Ìû¼jº¹¢GǪÒ¯ĘZ`ºŊecņą~BÂgzpâēòYƲȐĎ"],"encodeOffsets":[[113634,40474]]},"properties":{"cp":[108.948024,34.263161],"name":"陕西","childNum":1}},{"id":"620000","geometry":{"type":"MultiPolygon","coordinates":[["@@Vu_^","@@ųEĠtt~nkh`Q¦ÅÄÜdwAb×ĠąJ¤DüègĺqBqj°lI¡Ĩ¶ĖIHdjÎB°aZ¢KJO[|A£Dx}NìHUnrk kp¼Y kMJn[aGáÚÏ[½rc}aQxOgsPMnUsncZ
sKúvAtÞġ£®ĀYKdnFw¢JE°Latf`¼h¬we|Æbj}GA·~W`¢MC¤tL©IJ°qdfObÞĬ¹ttu`^ZúE`[@Æsîz®¡CƳƜG²R¢RmfwĸgÜą G@pzJM½mhVy¸uÈÔO±¨{LfæU¶ßGĂq\\ª¬²I¥IʼnÈīoıÓÑAçÑ|«LÝcspīðÍg
të_õ\\ĉñLYnĝgRǡÁiHLlõUĹ²uQjYi§Z_c¨´ĹĖÙ·ŋI
aBDR¹ȥr¯GºßK¨jWkɱOqWij\\aQ\\sg_ĆǛōëp»£lğÛgSŶN®À]ÓämĹãJaz¥V}Le¤Lýo¹IsŋÅÇ^bz
³tmEÁ´a¹cčecÇNĊãÁ\\č¯dNj]jZµkÓdaćå]ğij@ ©O{¤ĸm¢E·®«|@Xwg]A챝XǁÑdzªcwQÚŝñsÕ³ÛV_ý¥\\ů¥©¾÷w©WÕÊĩhÿÖÁRo¸V¬âDb¨hûxÊ×nj~Zâg|XÁnßYoº§ZÅŘv[ĭÖʃuďxcVbnUSf
B¯³_TzºÎO©çMÑ~M³]µ^püµÄY~y@X~¤Z³[Èōl@®Å¼£QK·Di¡ByÿQ_´D¥hŗy^ĭÁZ]cIzýah¹MĪğPs{ò²Vw¹t³ŜË[Ñ}X\\gsF£sPAgěp×ëfYHāďÖqēŭOÏëdLü\\it^c®Rʺ¶¢H°mrY£B¹čIoľu¶uI]vģSQ{UŻÅ}QÂ|̰ƅ¤ĩŪU ęĄÌZÒ\\v²PĔ»ƢNHĂyAmƂwVm`]ÈbH`Ì¢²ILvĜH®¤Dlt_¢JJÄämèÔDëþgºƫaʎÌrêYi~ ÎݤNpÀA¾Ĕ¼b
ð÷®üszMzÖĖQdȨýv§Tè|ªHþa¸|Ð ƒwKĢx¦ivr^ÿ ¸l öæfƟĴ·PJv}n\\h¹¶v·À|\\ƁĚN´ĜçèÁz]ġ¤²¨QÒŨTIlªťØ}¼˗ƦvÄùØE«FïËIqōTvāÜŏíÛßÛVj³âwGăÂíNOPìyV³ʼnĖýZso§HÑiYw[ß\\X¦¥c]ÔƩÜ·«jÐqvÁ¦m^ċ±R¦ƈťĚgÀ»IïĨʗƮ°ƝĻþÍAƉſ±tÍEÕÞāNUÍ¡\\ſčåÒʻĘm ƭÌŹöʥëQ¤µÇcƕªoIýIÉ_mkl³ăƓ¦j¡YzŇi}Msßõīʋ }ÁVm_[n}eıUĥ¼ªI{ΧDÓƻėojqYhĹT©oūĶ£]ďxĩǑMĝq`B´ƃ˺Чç~²ņj@¥@đ´ί}ĥtPńǾV¬ufÓÉCtÓ̻
¹£G³]ƖƾŎĪŪĘ̖¨ʈĢƂlɘ۪üºňUðǜȢƢż̌ȦǼĤŊɲĖÂKqĘʼn¼ĔDzņɾªǀÞĈĂD½ĄĎÌŗĞrôñnN¼â¾ʄľԆ|DŽ֦ज़ȗlj̘̭ɺƅêgV̍ʆĠ·ÌĊv|ýĖÕWĊǎÞ´õ¼cÒÒBĢ͢UĜð͒s¨ňƃLĉÕÝ@ɛƯ÷¿ĽĹeȏijëCȚDŲyê×Ŗyò¯ļcÂßY
tÁƤyAã˾J@ǝrý@¤
rz¸oP¹ɐÚyáHĀ[Jw
cVeȴÏ»ÈĖ}ƒŰŐèȭǢόĀƪÈŶë;Ñ̆ȤМľĮEŔĹŊũ~ËUă{ĻƹɁύȩþĽvĽƓÉ@ēĽɲßǐƫʾǗĒpäWÐxnsÀ^ƆwW©¦cÅ¡Ji§vúF¶¨c~c¼īeXǚ\\đ¾JwÀďksãAfÕ¦L}waoZD½Ml«]eÒÅaɲáo½FõÛ]ĻÒ¡wYR£¢rvÓ®y®LFLzĈôe]gx}|KK}xklL]c¦£fRtív¦PŨ£","@@M T¥"]],"encodeOffsets":[[[108619,36299],[108594,36341],[108600,36306]]]},"properties":{"cp":[103.823557,36.058039],"name":"甘肃","childNum":3}},{"id":"630000","geometry":{"type":"MultiPolygon","coordinates":[["@@InJo","@@CƽOŃĦsΰ~dz¦@@Ņi±è}ШƄ˹A³r_ĞǒNĪĐw¤^ŬĵªpĺSZgrpiƼĘÔ¨C|ÍJ©Ħ»®VIJ~f\\m `UnÂ~ʌĬàöNt~ňjy¢ZiƔ¥Ąk´nl`JÊJþ©pdƖ®È£¶ìRʦźõƮËnʼėæÑƀĎ[¢VÎĂMÖÝÎF²sƊƀÎBļýƞ¯ʘƭðħ¼Jh¿ŦęΌƇ¥²Q]Č¥nuÂÏri¸¬ƪÛ^Ó¦d¥[Wà
x\\ZjÒ¨GtpþYŊĕ´zUOëPîMĄÁxH´áiÜUàîÜŐĂÛSuŎrJð̬EFÁú×uÃÎkrĒ{V}İ«O_ÌËĬ©ÓŧSRѱ§Ģ£^ÂyèçěM³Ƃę{[¸¿u
ºµ[gt£¸OƤĿéYõ·kĀq]juw¥DĩƍõÇPéĽG©ã¤G
uȧþRcÕĕNyyûtøï»a½ē¿BMoį£Íj}éZËqbʍƬh¹ìÿÓAçãnIáI`ks£CGěUy×Cy
@¶ʡÊBnāzGơMē¼±O÷õJËĚăVĪũƆ£¯{ËL½ÌzżVR|ĠTbuvJvµhĻĖHAëáa
OÇðñęNw
œľ·LmI±íĠĩPÉ×®ÿscB³±JKßĊ«`
ađ»·QAmOVţéÿ¤¹SQt]]Çx±¯A@ĉij¢Óļ©l¶ÅÛrŕspãRk~¦ª]Į´FRådČsCqđéFn¿ÅƃmÉx{W©ºƝºįkÕƂƑ¸wWūЩÈF£\\tÈ¥ÄRÈýÌJ lGr^×äùyÞ³fjc¨£ÂZ|ǓMĝÏ@ëÜőRĝ÷¡{aïȷPu°ËXÙ{©TmĠ}Y³ÞIňµç½©C¡į÷¯B»|St»]vųs»}MÓ ÿʪƟǭA¡fs»PY¼c¡»¦cċ¥£~msĉPSi^o©AecPeǵkgyUi¿h}aHĉ^|á´¡HØûÅ«ĉ®]m¡qċ¶±ÈyôōLÁstB®wn±ă¥HSòė£Së@לÊăxÇN©©T±ª£IJ¡fb®Þbb_Ą¥xu¥B{łĝ³«`dƐt¤ťiñÍUuºí`£^tƃIJc·ÛLO½sç¥Ts{ă\\_»kϱq©čiìĉ|ÍI¥ć¥]ª§D{ŝŖÉR_sÿc³ĪōƿΧp[ĉc¯bKmR¥{³Ze^wx¹dƽŽôIg §Mĕ ƹĴ¿ǣÜÍ]Ý]snåA{eƭ`ǻŊĿ\\ijŬűYÂÿ¬jĖqßb¸L«¸©@ěĀ©ê¶ìÀEH|´bRľÓ¶rÀQþvl®ÕETzÜdb hw¤{LRdcb¯ÙVgƜßzÃôì®^jUèXÎ|UäÌ»rK\\ªN¼pZCüVY¤ɃRi^rPŇTÖ}|br°qňb̰ªiƶGQ¾²x¦PmlŜ[Ĥ¡ΞsĦÔÏâ\\ªÚŒU\\f
¢N²§x|¤§xĔsZPòʛ²SÐqF`ªVÞŜĶƨVZÌL`¢dŐIqr\\oäõF礻Ŷ×h¹]ClÙ\\¦ďÌį¬řtTӺƙgQÇÓHţĒ´ÃbEÄlbʔC|CŮkƮ[ʼ¬ň´KŮÈΰÌζƶlðļATUvdTGº̼ÔsÊDÔveMg"]],"encodeOffsets":[[[105308,37219],[95370,40081]]]},"properties":{"cp":[101.778916,36.623178],"name":"青海","childNum":2}},{"id":"640000","geometry":{"type":"Polygon","coordinates":["@@KëÀęĞ«Oęȿȕı]ʼn¡åįÕÔ«ǴõƪĚQÐZhv K°öqÀÑS[ÃÖHƖčËnL]ûc
Ùß@ĝ¾}w»»oģF¹»kÌÏ·{zP§B¢íyÅt@@á]Yv_ssģ¼ißĻL¾ġsKD£¡N_
X¸}B~HaiÅf{«x»ge_bsKF¯¡IxmELcÿZ¤ĢÝsuBLùtYdmVtNmtOPhRw~bd
¾qÐ\\âÙH\\bImlNZ»loqlVmGā§~QCw¤{A\\PKNY¯bFkC¥sks_Ã\\ă«¢ħkJi¯rrAhĹûç£CUĕĊ_ÔBixÅÙĄnªÑaM~ħpOu¥sîeQ¥¤^dkKwlL~{L~hw^ófćKyEKzuÔ¡qQ¤xZÑ¢^ļöܾEp±âbÊÑÆ^fk¬
NC¾YpxbK~¥eÖäBlt¿Đx½I[ĒǙWf»Ĭ}d§dµùEuj¨IÆ¢¥dXªƅx¿]mtÏwßRĶX¢͎vÆzƂZò®ǢÌʆCrâºMÞzÆMÒÊÓŊZľr°Î®Ȉmª²ĈUªĚîøºĮ¦ÌĘk^FłĬhĚiĀ˾iİbjË"],"encodeOffsets":[[109366,40242]]},"properties":{"cp":[106.278179,38.46637],"name":"宁夏","childNum":1}},{"id":"650000","geometry":{"type":"Polygon","coordinates":["@@QØĔ²X¨~ǘBºjʐߨvKƔX¨vĊO÷¢i@~cĝe_«E}QxgɪëÏÃ@sÅyXoŖ{ô«ŸuX
êÎf`C¹ÂÿÐGĮÕĞXŪōŸMźÈƺQèĽôe|¿ƸJR¤ĘEjcUóº¯Ĩ_ŘÁMª÷Ð¥OéÈ¿ÖğǤǷÂFÒzÉx[]Ĥĝœ¦EP}ûƥé¿İƷTėƫœŕƅƱB»Đ±ēO
¦E}`cȺrĦáŖuÒª«IJπdƺÏØZƴwʄ¤ĖGĐǂZĶèH¶}ÚZצʥĪï|ÇĦMŔ»İĝLjì¥Βba¯¥ǕǚkĆŵĦɑĺƯxūД̵nơʃĽá½M»òmqóŘĝč˾ăC
ćāƿÝɽ©DZŅ»ēėŊLrÁ®ɱĕģʼnǻ̋ȥơŻǛȡVï¹Ň۩ûkɗġƁ§ʇė̕ĩũƽō^ƕUv£ƁQïƵkŏ½ΉÃŭdzLŇʻ«ƭ\\lŭD{ʓDkaFÃÄa³ŤđÔGRÈƚhSӹŚsİ«ĐË[¥ÚDkº^Øg¼ŵ¸£EÍöůʼnT¡c_ËKYƧUśĵÝU_©rETÏʜ±OñtYwē¨{£¨uM³x½şL©Ùá[ÓÐĥ Νtģ¢\\śnkOw¥±T»ƷFɯàĩÞáB¹Æ
ÑUwŕĽw]kE½Èå~Æ÷QyěCFmĭZīŵVÁƿQƛûXS²b½KϽĉS©ŷXĕ{ĕK·¥Ɨcqq©f¿]ßDõU³hgËÇïģÉɋwk¯í}I·œbmÉřīJɥĻˁ×xoɹīlc
¤³Xù]DžA¿w͉ì¥wÇN·ÂËnƾƍdǧđ®ƝvUm©³G\\}µĿQyŹlăµEwLJQ½yƋBe¶ŋÀůo¥AÉw@{Gpm¿AijŽKLh³`ñcËtW±»ÕSëüÿďDu\\wwwù³VLŕOMËGh£õP¡erÏd{ġWÁ
č|yšg^ğyÁzÙs`s|ÉåªÇ}m¢Ń¨`x¥ù^}Ì¥H«YªƅAйn~ź¯f¤áÀzgÇDIÔ´AňĀÒ¶ûEYospõD[{ù°]uJqU|Soċxţ[õÔĥkŋÞŭZ˺óYËüċrw ÞkrťË¿XGÉbřaDü·Ē÷Aê[ÄäI®BÕĐÞ_¢āĠpÛÄȉĖġDKwbmÄNôfƫVÉvidzHQµâFùœ³¦{YGd¢ĚÜO {Ö¦ÞÍÀP^bƾl[vt×ĈÍE˨¡Đ~´î¸ùÎhuè`¸HÕŔVºwĠââWò@{ÙNÝ´ə²ȕn{¿¥{l÷eé^eďXj©î\\ªÑòÜìc\\üqÕ[Č¡xoÂċªbØø|¶ȴZdÆÂońéG\\¼C°ÌÆn´nxÊOĨŪƴĸ¢¸òTxÊǪMīĞÖŲÃɎOvʦƢ~FRěò¿ġ~åŊúN¸qĘ[Ĕ¶ÂćnÒPĒÜvúĀÊbÖ{Äî¸~Ŕünp¤ÂH¾ĄYÒ©ÊfºmÔĘcDoĬMŬS¤s²ʘÚžȂVŦ èW°ªB|IJXŔþÈJĦÆæFĚêYĂªĂ]øªŖNÞüAfɨJ¯ÎrDDĤ`mz\\§~D¬{vJ«lµĂb¤pŌŰNĄ¨ĊXW|ų ¿¾ɄĦƐMTòP÷fØĶK¢ȝ˔Sô¹òEð`Ɩ½ǒÂň×äı§ĤƝ§C~¡hlåǺŦŞkâ~}FøàIJaĞfƠ¥Ŕd®U¸źXv¢aƆúŪtŠųƠjdƺƺÅìnrh\\ĺ¯äɝĦ]èpĄ¦´LƞĬ´ƤǬ˼Ēɸ¤rºǼ²¨zÌPðŀbþ¹ļD¢¹\\ĜÑŚ¶ZƄ³âjĦoâȴLÊȮĐĚăÀêZǚŐ¤qȂ\\L¢ŌİfÆs|zºeªÙæ§{Ā´ƐÚ¬¨Ĵà²łhʺKÞºÖTiƢ¾ªì°`öøu®Ê¾ãÖ"],"encodeOffsets":[[88824,50096]]},"properties":{"cp":[87.617733,43.792818],"name":"新疆","childNum":1}},{"id":"110000","geometry":{"type":"Polygon","coordinates":["@@RºaYÕQaúÍÔiþĩȨWĢü|Ėu[qb[swP@ÅğP¿{\\¯Y²·Ñ¨j¯X\\¯MSvU¯YIŕY{[fkVÁûtŷmiÍt_H»Ĩ±d`¹{bw
Yr³S]§§o¹qGtm_SŧoaFLgQN_dV@Zom_ć\\ßW´ÕiœRcfi
o§ËgToÛJíĔóu
|wP¤XnO¢ÉŦ¯pNÄā¤zâŖÈRpŢZÚ{GrFt¦Òx§ø¹RóäV¤XdżâºWbwڍUd®bêņ¾jnŎGŃŶnzÚScîĚZen¬"],"encodeOffsets":[[119421,42013]]},"properties":{"cp":[116.405285,39.904989],"name":"北京","childNum":1}},{"id":"120000","geometry":{"type":"Polygon","coordinates":["@@ŬgX§Ü«E
¶F̬O_ïlÁgz±AXeµÄĵ{¶]gitgIj·¥ì_iU¨ÐƎk}ĕ{gBqGf{¿aU^fIư³õ{YıëNĿk©ïËZukāAīlĕĥs¡bġ«@dekąI[nlPqCnp{ō³°`{PNdƗqSÄĻNNâyj]äÒD ĬH°Æ]~¡HO¾X}ÐxgpgWrDGpù^LrzWxZ^¨´T\\|~@IzbĤjeĊªz£®ĔvěLmV¾Ô_ÈNW~zbĬvG²ZmDM~~"],"encodeOffsets":[[120237,41215]]},"properties":{"cp":[117.190182,39.125596],"name":"天津","childNum":1}},{"id":"310000","geometry":{"type":"MultiPolygon","coordinates":[["@@ɧư¬EpƸÁx]","@@©²","@@MA","@@QpªKWT
§¨","@@bŝÕÕEȣÚƥêImɇǦèÜĠÚÄÓŴ·ʌÇ","@@Sô¤r]ìƬįǜûȬɋŭ×^sYɍDŋŽąñCG²«ªč@h_p¯A{oloY¬j@IJ`gQÚpptǀ^MIJvtbe´Rh@oj¨","@@ÆLH{a}Eo¦"]],"encodeOffsets":[[[124702,32062],[124547,32200],[124808,31991],[124726,32110],[124903,32376],[124065,32166],[124870,31965]]]},"properties":{"cp":[121.472644,31.231706],"name":"上海","childNum":7}},{"id":"500000","geometry":{"type":"Polygon","coordinates":["@@TÂÛ`Ùƅően½SêqDu[Rå͹÷eXÍy¸_ĺę}÷`M¯ċfCVµqʼn÷Zgg^d½pDOÎCn^uf²ènh¼WtƏxRGg¦
pVFI±G^Ic´ecGĹÞ½sëÆNä̤KÓe¯|R¸§LÜkPoïƭNï¶}Gywdiù©nkĈzj@Óc£»Wă¹Óf§c[µo·Ó|MvÛaq½«è\\ÂoVnÓØÍ²«bq¿ehCĜ^Q~ Évýş¤²ĮpEĶyhsŊwH½¿gÅ¡ýE¡ya£³t\\¨\\vú¹¼©·Ñr_oÒý¥et³]Et©uÖ¥±ă©KVeë]}wVPÀFA¨ąB}qTjgRemfFmQFÝ
MyùnÑAmÑCawu_p¯sfÛ_gI_pNysB¦zG¸rHeN\\CvEsÐñÚkcDÖĉsaQ¯}_UzÁē}^R Äd^ÍĸZ¾·¶`wećJE¹vÛ·HgéFXjÉê`|ypxkAwWĐpb¥eOsmzwqChóUQl¥F^lafanòsrEvfQdÁUVfÎvÜ^eftET¬ôA\\¢sJnQTjPØxøK|nBzĞ»LY
FDxÓvr[ehľvN¢o¾NiÂxGpâ¬zbfZo~hGi]öF||NbtOMn eA±tPTLjpYQ|SHYĀxinzDJÌg¢và¥Pg_ÇzIIII£®S¬Øs쥨^LnGIJļIJƤjÎƀƾ¹¸ØÎezĆT¸}êÐqHðqĖä¥^CÆIj²p
\\_ æüY|[YxƊæu°xb®
Űb@~¢NQt°¶Sæ Ê~rljĔëĚ¢~uf`faĔJåĊnÔ]jƎćÊ@£¾a®£Ű{ŶĕFègLk{Y|¡ĜWƔtƬJÑxq±ĢN´òKLÈüD|s`ŋć]Ã`đMùƱ¿~Y°ħ`ƏíW½eI½{aOIrÏ¡ĕŇapµÜƃġ²"],"encodeOffsets":[[111728,31311]]},"properties":{"cp":[106.504962,29.533155],"name":"重庆","childNum":1}},{"id":"810000","geometry":{"type":"MultiPolygon","coordinates":[["@@AlFi","@@mp","@@EpHo","@@rMUwAS¬]","@@ea¢pl¸Eõ¹hj[]ÔCÎ@lj¡uBX
´AI¹
[yDU]W`çwZkmc
MpÅv}IoJlcafŃK°ä¬XJmÐ đhI®æÔtSHnEÒrÄc"]],"encodeOffsets":[[[117111,23002],[117072,22876],[117045,22887],[116882,22747],[116975,23082]]]},"properties":{"cp":[114.173355,22.320048],"name":"香港","childNum":5}},{"id":"820000","geometry":{"type":"Polygon","coordinates":["@@áw{Îr"],"encodeOffsets":[[116285,22746]]},"properties":{"cp":[113.54909,22.198951],"name":"澳门","childNum":1}}],"UTF8Encoding":true}
export default chinaMap;
================================================
FILE: src/utils/index.ts
================================================
export const setProperty = (prop: string, val: any, dom = document.documentElement) => {
dom.style.setProperty(prop, val);
};
export const mix = (color1: string, color2: string, weight: number = 0.5): string => {
let color = '#';
for (let i = 0; i <= 2; i++) {
const c1 = parseInt(color1.substring(1 + i * 2, 3 + i * 2), 16);
const c2 = parseInt(color2.substring(1 + i * 2, 3 + i * 2), 16);
const c = Math.round(c1 * weight + c2 * (1 - weight));
color += c.toString(16).padStart(2, '0');
}
return color;
};
================================================
FILE: src/utils/request.ts
================================================
import axios, { AxiosInstance, AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
const service: AxiosInstance = axios.create({
timeout: 5000
});
service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
return config;
},
(error: AxiosError) => {
console.log(error);
return Promise.reject();
}
);
service.interceptors.response.use(
(response: AxiosResponse) => {
if (response.status === 200) {
return response;
} else {
Promise.reject();
}
},
(error: AxiosError) => {
console.log(error);
return Promise.reject();
}
);
export default service;
================================================
FILE: src/views/chart/echarts.vue
================================================
<template>
<div class="container">
<div class="plugins-tips">
vue-echarts:Apache ECharts™ 的 Vue.js 组件。 访问地址:
<a href="https://github.com/ecomfe/vue-echarts" target="_blank">vue-echarts</a>
</div>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">柱状图</div>
</template>
<v-chart class="schart" :option="barOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">折线图</div>
</template>
<v-chart class="schart" :option="lineOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">饼状图</div>
</template>
<v-chart class="schart" :option="pieOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">环形图</div>
</template>
<v-chart class="schart" :option="ringOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">词云图</div>
</template>
<v-chart class="schart" :option="wordOptions" />
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">地图</div>
</template>
<v-chart class="schart" :option="mapOptions" />
</el-card>
</div>
</template>
<script setup lang="ts" name="echarts">
import { registerMap, use } from 'echarts/core';
import { BarChart, LineChart, PieChart, MapChart } from 'echarts/charts';
import {
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent,
VisualMapComponent,
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import VChart from 'vue-echarts';
import 'echarts-wordcloud';
import { barOptions, lineOptions, pieOptions, ringOptions, wordOptions, mapOptions } from './options';
import chinaMap from '@/utils/china';
use([
CanvasRenderer,
BarChart,
GridComponent,
LineChart,
PieChart,
MapChart,
TooltipComponent,
LegendComponent,
TitleComponent,
VisualMapComponent,
]);
registerMap('china', chinaMap);
</script>
<style scoped>
.schart {
width: 100%;
height: 400px;
}
.content-title {
font-weight: 400;
font-size: 22px;
color: #1f2f3d;
}
</style>
================================================
FILE: src/views/chart/options.ts
================================================
import { graphic } from 'echarts/core';
export const barOptions = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: {
type: 'value',
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
},
color: ['#009688', '#f44336'],
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
},
{
data: [180, 230, 190, 120, 110, 230, 235],
type: 'bar',
},
],
};
export const lineOptions = {
tooltip: {
trigger: 'axis',
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: {
type: 'value',
},
color: ['#009688', '#f44336'],
series: [
{
name: 'Email',
type: 'line',
stack: 'Total',
areaStyle: {},
smooth: true,
data: [120, 132, 101, 134, 90, 230, 210],
},
{
name: 'Union Ads',
type: 'line',
stack: 'Total',
areaStyle: {},
smooth: true,
data: [220, 182, 191, 234, 290, 330, 310],
},
],
};
export const pieOptions = {
title: {
text: 'Referer of a Website',
subtext: 'Fake Data',
left: 'center',
},
tooltip: {
trigger: 'item',
},
legend: {
orient: 'vertical',
left: 'left',
},
series: [
{
name: 'Access From',
type: 'pie',
radius: '50%',
data: [
{ value: 1048, name: 'Search Engine' },
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
};
export const wordOptions = {
series: [
{
type: 'wordCloud',
rotationRange: [0, 0],
autoSize: {
enable: true,
minSize: 14,
},
textStyle: {
fontFamily: '微软雅黑,sans-serif',
color: function () {
return (
'rgb(' +
[
Math.round(Math.random() * 160),
Math.round(Math.random() * 160),
Math.round(Math.random() * 160),
].join(',') +
')'
);
},
},
data: [
{
name: 'Vue',
value: 10000,
},
{
name: 'React',
value: 9000,
},
{
name: '图表',
value: 4000,
},
{
name: '产品',
value: 7000,
},
{
name: 'vue-manage-system',
value: 2000,
},
{
name: 'element-plus',
value: 6000,
},
{
name: '管理系统',
value: 5000,
},
{
name: '前端',
value: 4000,
},
{
name: '测试',
value: 3000,
},
{
name: '后端',
value: 8000,
},
{
name: '软件开发',
value: 6000,
},
{
name: '程序员',
value: 4000,
},
],
},
],
};
export const ringOptions = {
tooltip: {
trigger: 'item',
},
legend: {
top: '5%',
left: 'center',
},
series: [
{
name: 'Access From',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
},
label: {
show: false,
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: 'bold',
},
},
labelLine: {
show: false,
},
data: [
{ value: 1048, name: 'Search Engine' },
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' },
],
},
],
};
export const dashOpt1 = {
xAxis: {
type: 'category',
boundaryGap: false,
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: {
type: 'value',
},
grid: {
top: '2%',
left: '2%',
right: '3%',
bottom: '2%',
containLabel: true,
},
color: ['#009688', '#f44336'],
series: [
{
type: 'line',
areaStyle: {
color: new graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(0, 150, 136,0.8)',
},
{
offset: 1,
color: 'rgba(0, 150, 136,0.2)',
},
]),
},
smooth: true,
data: [120, 132, 301, 134, 90, 230, 210],
},
{
type: 'line',
smooth: true,
data: [220, 122, 191, 234, 190, 130, 310],
},
],
};
export const dashOpt2 = {
legend: {
bottom: '1%',
left: 'center',
},
color: ['#3f51b5', '#009688', '#f44336', '#00bcd4', '#1ABC9C'],
series: [
{
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
},
data: [
{ value: 1048, name: '数码' },
{ value: 735, name: '食品' },
{ value: 580, name: '母婴' },
{ value: 484, name: '家电' },
{ value: 300, name: '运动' },
],
},
],
};
export const mapOptions = {
tooltip: {
trigger: 'item',
},
geo: {
map: 'china',
roam: false,
emphasis: {
label: {
show: false,
},
},
},
visualMap: {
show: false,
min: 0,
max: 100,
realtime: false,
calculable: false,
inRange: {
color: ['#d2e0f5', '#71A9FF'],
},
},
series: [
{
geoIndex: 0,
name: '地域分布',
type: 'map',
coordinateSystem: 'geo',
map: 'china',
data: [
{ name: '北京', value: 100 },
{ name: '上海', value: 100 },
{ name: '广东', value: 100 },
{ name: '浙江', value: 90 },
{ name: '江西', value: 80 },
{ name: '山东', value: 70 },
{ name: '广西', value: 60 },
{ name: '河南', value: 50 },
{ name: '河南', value: 40 },
{ name: '青海', value: 70 },
{ name: '河南', value: 30 },
{ name: '黑龙江', value: 20 },
{ name: '新疆', value: 20 },
{ name: '云南', value: 20 },
{ name: '甘肃', value: 20 },
],
},
],
};
================================================
FILE: src/views/chart/schart.vue
================================================
<template>
<div class="container">
<div class="plugins-tips">
vue-schart:vue.js封装sChart.js的图表组件。 访问地址:
<a href="https://github.com/lin-xin/vue-schart" target="_blank">vue-schart</a>
</div>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">柱状图</div>
</template>
<schart class="schart" canvasId="bar" :options="options1"></schart>
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">折线图</div>
</template>
<schart class="schart" canvasId="line" :options="options2"></schart>
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">饼状图</div>
</template>
<schart class="schart" canvasId="pie" :options="options3"></schart>
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">环形图</div>
</template>
<schart class="schart" canvasId="ring" :options="options4"></schart>
</el-card>
</div>
</template>
<script setup lang="ts" name="schart">
import Schart from 'vue-schart';
const options1 = {
type: 'bar',
title: {
text: '最近一周各品类销售图'
},
colorList: ["#3f51b5", "#009688", "#f44336", "#00bcd4", "#1ABC9C"],
labels: ['周一', '周二', '周三', '周四', '周五'],
datasets: [
{
label: '家电',
// fillColor: 'rgba(241, 49, 74, 0.5)',
data: [234, 278, 270, 190, 230]
},
{
label: '百货',
data: [164, 178, 190, 135, 160]
},
{
label: '食品',
data: [144, 198, 150, 235, 120]
}
]
};
const options2 = {
type: 'line',
title: {
text: '最近几个月各品类销售趋势图'
},
colorList: ["#3f51b5", "#009688", "#f44336", "#00bcd4", "#1ABC9C"],
labels: ['6月', '7月', '8月', '9月', '10月'],
datasets: [
{
label: '家电',
data: [234, 278, 270, 190, 230]
},
{
label: '百货',
data: [164, 178, 150, 135, 160]
},
{
label: '食品',
data: [114, 138, 200, 235, 190]
}
]
};
const options3 = {
type: 'pie',
title: {
text: '服装品类销售饼状图'
},
legend: {
position: 'left'
},
colorList: ["#2196f3", '#673ab7', "#009688", "#1ABC9C", "#3f51b5", "#f44336", "#00bcd4"],
labels: ['T恤', '牛仔裤', '连衣裙', '毛衣', '七分裤', '短裙', '羽绒服'],
datasets: [
{
data: [334, 278, 190, 235, 260, 200, 141]
}
]
};
const options4 = {
type: 'ring',
title: {
text: '环形三等分'
},
showValue: false,
legend: {
position: 'bottom',
bottom: 40
},
colorList: ["#3f51b5", "#009688", "#f44336", "#00bcd4", "#1ABC9C"],
labels: ['vue', 'react', 'angular'],
datasets: [
{
data: [500, 500, 500]
}
]
};
</script>
<style scoped>
.schart {
width: 100%;
height: 400px;
}
.content-title {
font-weight: 400;
font-size: 22px;
color: #1f2f3d;
}
</style>
================================================
FILE: src/views/dashboard.vue
================================================
<template>
<div>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg1">
<User />
</el-icon>
<div class="card-content">
<countup class="card-num color1" :end="6666" />
<div>用户访问量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg2">
<ChatDotRound />
</el-icon>
<div class="card-content">
<countup class="card-num color2" :end="168" />
<div>系统消息</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg3">
<Goods />
</el-icon>
<div class="card-content">
<countup class="card-num color3" :end="8888" />
<div>商品数量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg4">
<ShoppingCartFull />
</el-icon>
<div class="card-content">
<countup class="card-num color4" :end="568" />
<div>今日订单量</div>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mgb20">
<el-col :span="18">
<el-card shadow="hover">
<div class="card-header">
<p class="card-header-title">订单动态</p>
<p class="card-header-desc">最近一周订单状态,包括订单成交量和订单退货量</p>
</div>
<v-chart class="chart" :option="dashOpt1" />
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<div class="card-header">
<p class="card-header-title">品类分布</p>
<p class="card-header-desc">最近一个月销售商品的品类情况</p>
</div>
<v-chart class="chart" :option="dashOpt2" />
</el-card>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="7">
<el-card shadow="hover" :body-style="{ height: '400px' }">
<div class="card-header">
<p class="card-header-title">时间线</p>
<p class="card-header-desc">最新的销售动态和活动信息</p>
</div>
<el-timeline>
<el-timeline-item v-for="(activity, index) in activities" :key="index" :color="activity.color">
<div class="timeline-item">
<div>
<p>{{ activity.content }}</p>
<p class="timeline-desc">{{ activity.description }}</p>
</div>
<div class="timeline-time">{{ activity.timestamp }}</div>
</div>
</el-timeline-item>
</el-timeline>
</el-card>
</el-col>
<el-col :span="10">
<el-card shadow="hover" :body-style="{ height: '400px' }">
<div class="card-header">
<p class="card-header-title">渠道统计</p>
<p class="card-header-desc">最近一个月的订单来源统计</p>
</div>
<v-chart class="map-chart" :option="mapOptions" />
</el-card>
</el-col>
<el-col :span="7">
<el-card shadow="hover" :body-style="{ height: '400px' }">
<div class="card-header">
<p class="card-header-title">排行榜</p>
<p class="card-header-desc">销售商品的热门榜单Top5</p>
</div>
<div>
<div class="rank-item" v-for="(rank, index) in ranks">
<div class="rank-item-avatar">{{ index + 1 }}</div>
<div class="rank-item-content">
<div class="rank-item-top">
<div class="rank-item-title">{{ rank.title }}</div>
<div class="rank-item-desc">销量:{{ rank.value }}</div>
</div>
<el-progress
:show-text="false"
striped
:stroke-width="10"
:percentage="rank.percent"
:color="rank.color"
/>
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts" name="dashboard">
import countup from '@/components/countup.vue';
import { use, registerMap } from 'echarts/core';
import { BarChart, LineChart, PieChart, MapChart } from 'echarts/charts';
import {
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent,
VisualMapComponent,
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import VChart from 'vue-echarts';
import { dashOpt1, dashOpt2, mapOptions } from './chart/options';
import chinaMap from '@/utils/china';
use([
CanvasRenderer,
BarChart,
GridComponent,
LineChart,
PieChart,
TooltipComponent,
LegendComponent,
TitleComponent,
VisualMapComponent,
MapChart,
]);
registerMap('china', chinaMap);
const activities = [
{
content: '收藏商品',
description: 'xxx收藏了你的商品,就是不买',
timestamp: '30分钟前',
color: '#00bcd4',
},
{
content: '用户评价',
description: 'xxx给了某某商品一个差评,吐血啊',
timestamp: '55分钟前',
color: '#1ABC9C',
},
{
content: '订单提交',
description: 'xxx提交了订单,快去收钱吧',
timestamp: '1小时前',
color: '#3f51b5',
},
{
content: '退款申请',
description: 'xxx申请了仅退款,又要亏钱了',
timestamp: '15小时前',
color: '#f44336',
},
{
content: '商品上架',
description: '运营专员瞒着你上架了一辆飞机',
timestamp: '1天前',
color: '#009688',
},
];
const ranks = [
{
title: '手机',
value: 10000,
percent: 80,
color: '#f25e43',
},
{
title: '电脑',
value: 8000,
percent: 70,
color: '#00bcd4',
},
{
title: '相机',
value: 6000,
percent: 60,
color: '#64d572',
},
{
title: '衣服',
value: 5000,
percent: 55,
color: '#e9a745',
},
{
title: '书籍',
value: 4000,
percent: 50,
color: '#009688',
},
];
</script>
<style>
.card-body {
display: flex;
align-items: center;
height: 100px;
padding: 0;
}
</style>
<style scoped>
.card-content {
flex: 1;
text-align: center;
font-size: 14px;
color: #999;
padding: 0 20px;
}
.card-num {
font-size: 30px;
}
.card-icon {
font-size: 50px;
width: 100px;
height: 100px;
text-align: center;
line-height: 100px;
color: #fff;
}
.bg1 {
background: #2d8cf0;
}
.bg2 {
background: #64d572;
}
.bg3 {
background: #f25e43;
}
.bg4 {
background: #e9a745;
}
.color1 {
color: #2d8cf0;
}
.color2 {
color: #64d572;
}
.color3 {
color: #f25e43;
}
.color4 {
color: #e9a745;
}
.chart {
width: 100%;
height: 400px;
}
.card-header {
padding-left: 10px;
margin-bottom: 20px;
}
.card-header-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 5px;
}
.card-header-desc {
font-size: 14px;
color: #999;
}
.timeline-item {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
color: #000;
}
.timeline-time,
.timeline-desc {
font-size: 12px;
color: #787878;
}
.rank-item {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.rank-item-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: #f2f2f2;
text-align: center;
line-height: 40px;
margin-right: 10px;
}
.rank-item-content {
flex: 1;
}
.rank-item-top {
display: flex;
justify-content: space-between;
align-items: center;
color: #343434;
margin-bottom: 10px;
}
.rank-item-desc {
font-size: 14px;
color: #999;
}
.map-chart {
width: 100%;
height: 350px;
}
</style>
================================================
FILE: src/views/element/calendar.vue
================================================
<template>
<div class="container">
<el-calendar v-model="value">
<template #date-cell="{ data }">
<div>{{ data.date.getDate() }}</div>
<div class="notes-container" v-if="notes[data.day.toString()]">
<div class="notes" v-for="note in notes[data.day.toString()]">
<span :class="note.status === 1 ? 'text-success' : 'text-danger'"></span>
<div class="note-title">{{ note.title }}</div>
</div>
</div>
</template>
</el-calendar>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const today = new Date();
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
const value = ref(today);
const todayDate = today.toISOString().slice(0, 10);
const yesterdayDate = yesterday.toISOString().slice(0, 10);
const notes: any = {
[todayDate]: [
{ title: '吃饭', status: 1 },
{ title: '睡觉', status: 0 },
{ title: '吃饭', status: 1 },
{ title: '睡觉', status: 0 },
{ title: '吃饭', status: 1 },
{ title: '睡觉', status: 0 },
],
[yesterdayDate]: [{ title: '参加会议', status: 0 }],
};
</script>
<style scoped>
.notes-container {
height: 60px;
overflow-y: auto;
}
.notes-container::-webkit-scrollbar {
width: 0;
}
.notes {
display: flex;
align-items: center;
width: 100%;
font-size: 12px;
}
.notes:hover {
background-color: #eee;
}
.note-title {
flex: 1;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.notes span {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 5px;
}
.text-success {
background-color: #5cb85c;
}
.text-danger {
background-color: #d9534f;
}
</style>
================================================
FILE: src/views/element/carousel.vue
================================================
<template>
<div>
<el-card class="mgb20">
<template #header>基础用法</template>
<el-carousel height="400px">
<el-carousel-item v-for="item in 4" :key="item">
<h3>{{ item }}</h3>
</el-carousel-item>
</el-carousel>
</el-card>
<el-row :gutter="20">
<el-col :span="12">
<el-card class="mgb20">
<template #header>轮播图</template>
<el-carousel height="300px">
<el-carousel-item v-for="item in imgs" :key="item">
<el-image class="carousel-img" :src="item" fit="cover" />
</el-carousel-item>
</el-carousel>
</el-card>
</el-col>
<el-col :span="12">
<el-card class="mgb20">
<template #header>卡片模式</template>
<el-carousel height="300px" type="card">
<el-carousel-item v-for="item in imgs" :key="item">
<el-image class="carousel-img" :src="item" fit="cover" />
</el-carousel-item>
</el-carousel>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script lang="ts" setup>
const imgs = [
'https://cdn.pixabay.com/photo/2017/08/07/08/23/sea-2601374_640.jpg',
'https://cdn.pixabay.com/photo/2020/02/11/10/24/lake-4839058_640.jpg',
'https://cdn.pixabay.com/photo/2024/02/21/08/06/coast-8587004_640.jpg',
'https://cdn.pixabay.com/photo/2023/07/29/10/21/grasshopper-8156626_640.jpg',
];
</script>
<style scoped>
.el-carousel__item h3 {
color: #475669;
line-height: 400px;
margin: 0;
text-align: center;
}
.el-carousel__item:nth-child(2n) {
background-color: #99a9bf;
}
.el-carousel__item:nth-child(2n + 1) {
background-color: #d3dce6;
}
.carousel-img {
width: 100%;
height: 100%;
}
</style>
================================================
FILE: src/views/element/form.vue
================================================
<template>
<div class="container">
<el-radio-group class="mgb20" v-model="labelPosition">
<el-radio-button value="left">Left</el-radio-button>
<el-radio-button value="right">Right</el-radio-button>
<el-radio-button value="top">Top</el-radio-button>
</el-radio-group>
<el-form ref="formRef" :rules="rules" :model="form" label-width="120px" :label-position="labelPosition">
<el-row :gutter="50">
<el-col :span="10">
<el-form-item label="文本框" prop="name">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="数字框" prop="num">
<el-input-number v-model="form.num" :min="1" :max="10" />
</el-form-item>
<el-form-item label="日期选择" prop="date">
<el-date-picker type="date" placeholder="选择日期" v-model="form.date"></el-date-picker>
</el-form-item>
<el-form-item label="时间选择" prop="time">
<el-time-picker placeholder="选择时间" v-model="form.time">
</el-time-picker>
</el-form-item>
<el-form-item label="选择器" prop="region">
<el-select v-model="form.region" placeholder="请选择">
<el-option key="小明" label="小明" value="小明"></el-option>
<el-option key="小红" label="小红" value="小红"></el-option>
<el-option key="小白" label="小白" value="小白"></el-option>
</el-select>
</el-form-item>
<el-form-item label="城市级联" prop="options">
<el-cascader :options="options" v-model="form.options"></el-cascader>
</el-form-item>
<el-form-item label="文本框" prop="desc">
<el-input type="textarea" rows="5" v-model="form.desc"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="评分" prop="rate">
<el-rate v-model="form.rate" allow-half />
</el-form-item>
<el-form-item label="滑块" prop="num">
<el-slider v-model="form.num" :step="1" show-stops :max="10" />
</el-form-item>
<el-form-item label="开关" prop="delivery">
<el-switch v-model="form.delivery"></el-switch>
</el-form-item>
<el-form-item label="颜色选择" prop="color">
<el-color-picker v-model="form.color" />
</el-form-item>
<el-form-item label="多选框" prop="type">
<el-checkbox-group v-model="form.type">
<el-checkbox label="小明" value="小明" name="type"></el-checkbox>
<el-checkbox label="小红" value="小红" name="type"></el-checkbox>
<el-checkbox label="小白" value="小白" name="type"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="单选框" prop="resource">
<el-radio-group v-model="form.resource">
<el-radio label="小明" value="小明"></el-radio>
<el-radio label="小红" value="小红"></el-radio>
<el-radio label="小白" value="小白"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="穿梭框" prop="transfer">
<el-transfer v-model="form.transfer" :data="transferData" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item>
<el-button type="primary" @click="onSubmit(formRef)">表单提交</el-button>
<el-button @click="onReset(formRef)">重置表单</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</template>
<script setup lang="ts" name="forms">
import { reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
import type { FormInstance, FormProps, FormRules } from 'element-plus';
const labelPosition = ref<FormProps['labelPosition']>('right')
const options = [
{
value: 'guangdong',
label: '广东省',
children: [
{
value: 'guangzhou',
label: '广州市',
children: [
{
value: 'tianhe',
label: '天河区',
},
{
value: 'haizhu',
label: '海珠区',
},
],
},
{
value: 'dongguan',
label: '东莞市',
children: [
{
value: 'changan',
label: '长安镇',
},
{
value: 'humen',
label: '虎门镇',
},
],
},
],
},
{
value: 'hunan',
label: '湖南省',
children: [
{
value: 'changsha',
label: '长沙市',
children: [
{
value: 'yuelu',
label: '岳麓区',
},
],
},
],
},
];
const rules: FormRules = {
name: [{ required: true, message: '请输入表单名称', trigger: 'blur' }],
};
const formRef = ref<FormInstance>();
const form = reactive({
name: '',
region: '',
date: '',
time: '',
delivery: true,
type: ['小明'],
resource: '小红',
desc: '',
options: [],
color: '',
num: 1,
rate: 0,
transfer: [],
});
const generateData = () => {
const data = []
for (let i = 1; i <= 15; i++) {
data.push({
key: i,
label: `Option ${i}`,
disabled: i % 4 === 0,
})
}
return data
}
const transferData = ref(generateData())
// 提交
const onSubmit = (formEl: FormInstance | undefined) => {
// 表单校验
if (!formEl) return;
formEl.validate((valid) => {
if (valid) {
console.log(form);
ElMessage.success('提交成功!');
} else {
return false;
}
});
};
// 重置
const onReset = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
};
</script>
================================================
FILE: src/views/element/statistic.vue
================================================
<template>
<div>
<el-card class="mgb20" shadow="hover">
<template #header>基础用法</template>
<el-row>
<el-col :span="6" style="text-align: center">
<el-statistic title="Daily active users" :value="268500" />
</el-col>
<el-col :span="6" style="text-align: center">
<el-statistic :value="138">
<template #title>
<div style="display: inline-flex; align-items: center">
Ratio of men to women
</div>
</template>
<template #suffix>/100</template>
</el-statistic>
</el-col>
<el-col :span="6" style="text-align: center">
<el-statistic title="数字滚动" :value="outputValue" />
</el-col>
<el-col :span="6" style="text-align: center">
<el-countdown title="倒计时" :value="value" />
</el-col>
</el-row>
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>CountUp.js</template>
<div class="plugins-tips">
countup.js:用于快速创建以更有趣的方式显示数字数据的动画。 访问地址:
<a href="https://github.com/inorganik/countUp.js" target="_blank">countUp.js</a>
</div>
<el-row>
<el-col :span="8" style="text-align: center">
<p>基础用法</p>
<countup class="countup" :end="6666" />
</el-col>
<el-col :span="8" style="text-align: center">
<p>具体配置</p>
<countup class="countup" :end="8888.5" :options="options" />
</el-col>
<el-col :span="8" style="text-align: center">
<p>更新数值</p>
<countup class="countup" :end="value1" />
</el-col>
</el-row>
</el-card>
<el-card class="mgb20" shadow="never">
<template #header>统计卡片</template>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon color1">
<User />
</el-icon>
<div class="card-content text-right">
<el-statistic title="日活跃用户量" :value="268500" />
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon color2">
<ChatDotRound />
</el-icon>
<div class="card-content text-right">
<el-statistic title="系统消息" :value="16800" />
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon color3">
<Goods />
</el-icon>
<div class="card-content text-right">
<el-statistic title="商品数量" :value="8888" />
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon color4">
<ShoppingCartFull />
</el-icon>
<div class="card-content text-right">
<el-statistic title="今日订单量" :value="56888" />
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<div class="card-content text-left">
<el-statistic :value-style="{ color: '#2d8cf0' }" title="日活跃用户量" :value="268500" />
</div>
<el-icon class="card-icon color1">
<User />
</el-icon>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<div class="card-content text-left">
<el-statistic :value-style="{ color: '#64d572' }" title="系统消息" :value="16800" />
</div>
<el-icon class="card-icon color2">
<ChatDotRound />
</el-icon>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<div class="card-content text-left">
<el-statistic :value-style="{ color: '#f25e43' }" title="商品数量" :value="8888" />
</div>
<el-icon class="card-icon color3">
<Goods />
</el-icon>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<div class="card-content text-left">
<el-statistic :value-style="{ color: '#e9a745' }" title="今日订单量" :value="56888" />
</div>
<el-icon class="card-icon color4">
<ShoppingCartFull />
</el-icon>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg1">
<User />
</el-icon>
<div class="card-content">
<countup class="card-num color1" :end="6666" />
<div>用户访问量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg2">
<ChatDotRound />
</el-icon>
<div class="card-content">
<countup class="card-num color2" :end="168" />
<div>系统消息</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg3">
<Goods />
</el-icon>
<div class="card-content">
<countup class="card-num color3" :end="8888" />
<div>商品数量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body">
<el-icon class="card-icon bg4">
<ShoppingCartFull />
</el-icon>
<div class="card-content">
<countup class="card-num color4" :end="568" />
<div>今日订单量</div>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" class="mgb20">
<el-col :span="6">
<el-card shadow="hover" body-class="card-body bg1">
<el-icon class="card-icon ">
<User />
</el-icon>
<div class="card-content color0">
<countup class="card-num" :end="6666" />
<div>用户访问量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body bg2">
<el-icon class="card-icon">
<ChatDotRound />
</el-icon>
<div class="card-content color0">
<countup class="card-num" :end="168" />
<div>系统消息</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body bg3">
<el-icon class="card-icon">
<Goods />
</el-icon>
<div class="card-content color0">
<countup class="card-num " :end="8888" />
<div>商品数量</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" body-class="card-body bg4">
<el-icon class="card-icon">
<ShoppingCartFull />
</el-icon>
<div class="card-content color0">
<countup class="card-num " :end="568" />
<div>今日订单量</div>
</div>
</el-card>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { useTransition } from '@vueuse/core'
import countup from '@/components/countup.vue';
const source = ref(0)
const outputValue = useTransition(source, {
duration: 1500,
})
source.value = 172000
const value = ref(Date.now() + 1000 * 60 * 60 * 7)
const value1 = ref(1000);
setTimeout(() => {
value1.value = 8000;
}, 5000);
const options = {
startVal: 1000,
decimalPlaces: 2,
duration: 5,
useGrouping: false,
prefix: '$',
separator: ',',
decimal: '.',
suffix: '',
}
</script>
<style>
.card-body {
display: flex;
align-items: center;
height: 100px;
padding: 0;
}
.bg1 {
background: #2d8cf0;
}
.bg2 {
background: #64d572;
}
.bg3 {
background: #f25e43;
}
.bg4 {
background: #e9a745;
}
</style>
<style scoped>
.countup {
font-size: 24px;
}
.card-content {
flex: 1;
text-align: center;
font-size: 14px;
color: #999;
padding: 0 20px;
}
.card-num {
font-size: 30px;
}
.card-icon {
font-size: 50px;
width: 100px;
height: 100px;
text-align: center;
line-height: 100px;
color: #fff;
}
.color0 {
color: #fff;
}
.color1 {
color: #2d8cf0;
}
.color2 {
color: #64d572;
}
.color3 {
color: #f25e43;
}
.color4 {
color: #e9a745;
}
.text-right {
text-align: right;
}
.text-left {
text-align: left;
}
</style>
================================================
FILE: src/views/element/steps.vue
================================================
<template>
<div class="container">
<div class="step-div" v-if="step === 0">
<p>输入注册时的邮箱,我们会发送验证码到您的邮箱</p>
<el-input placeholder="请输入邮箱"></el-input>
<el-button class="step-btn" type="primary" @click="step++">下一步</el-button>
</div>
<div class="step-div" v-else-if="step === 1">
<p>验证码已发送至您的邮箱,请输入验证码</p>
<el-input placeholder="请输入验证码"></el-input>
<el-button class="step-btn" type="primary" @click="step++">下一步</el-button>
</div>
<div class="step-div" v-else-if="step === 2">
<p>请输入6位以上密码</p>
<el-input placeholder="请输入新密码"></el-input>
<el-button class="step-btn" type="primary" @click="step++">保存</el-button>
</div>
<div v-else>
<el-result icon="success" title="保存成功" sub-title="请退出后重新登录"></el-result>
</div>
<el-steps class="step-style" :active="step" align-center finish-status="success">
<el-step title="Step 1" description="填写邮箱" />
<el-step title="Step 2" description="填写验证码" />
<el-step title="Step 3" description="修改密码" />
</el-steps>
<el-steps class="step-style" :active="step" finish-status="success" simple>
<el-step title="填写邮箱" />
<el-step title="填写验证码" />
<el-step title="修改密码" />
</el-steps>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const step = ref(0)
</script>
<style scoped>
.step-div {
max-width: 500px;
margin: 0 auto;
}
.step-div p {
margin-bottom: 20px;
color: #787878;
}
.step-btn {
display: block;
width: 100%;
margin: 20px 0;
}
.step-style {
max-width: 800px;
margin: 40px auto;
}
</style>
================================================
FILE: src/views/element/tabs.vue
================================================
<template>
<el-tabs v-model="message" type="card">
<el-tab-pane :label="`未读消息(${state.unread.length})`" name="first">
<el-table :data="state.unread" :show-header="false" style="width: 100%">
<el-table-column>
<template #default="scope">
<span class="message-title">{{ scope.row.title }}</span>
</template>
</el-table-column>
<el-table-column prop="date" width="180"></el-table-column>
<el-table-column width="120">
<template #default="scope">
<el-button size="small" @click="handleRead(scope.$index)">标为已读</el-button>
</template>
</el-table-column>
</el-table>
<div class="handle-row">
<el-button type="primary">全部标为已读</el-button>
</div>
</el-tab-pane>
<el-tab-pane :label="`已读消息(${state.read.length})`" name="second">
<template v-if="message === 'second'">
<el-table :data="state.read" :show-header="false" style="width: 100%">
<el-table-column>
<template #default="scope">
<span class="message-title">{{ scope.row.title }}</span>
</template>
</el-table-column>
<el-table-column prop="date" width="180"></el-table-column>
<el-table-column width="120">
<template #default="scope">
<el-button type="danger" size="small" @click="handleDel(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="handle-row">
<el-button type="danger">删除全部</el-button>
</div>
</template>
</el-tab-pane>
<el-tab-pane :label="`回收站(${state.recycle.length})`" name="third">
<template v-if="message === 'third'">
<el-table :data="state.recycle" :show-header="false" style="width: 100%">
<el-table-column>
<template #default="scope">
<span class="message-title">{{ scope.row.title }}</span>
</template>
</el-table-column>
<el-table-column prop="date" width="180"></el-table-column>
<el-table-column width="120">
<template #default="scope">
<el-button size="small" @click="handleRestore(scope.$index)">还原</el-button>
</template>
</el-table-column>
</el-table>
<div class="handle-row">
<el-button type="danger">清空回收站</el-button>
</div>
</template>
</el-tab-pane>
</el-tabs>
</template>
<script setup lang="ts" name="tabs">
import { ref, reactive } from 'vue';
const message = ref('first');
const state = reactive({
unread: [
{
date: '2018-04-19 20:00:00',
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
},
{
date: '2018-04-19 21:00:00',
title: '今晚12点整发大红包,先到先得'
}
],
read: [
{
date: '2018-04-19 20:00:00',
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
}
],
recycle: [
{
date: '2018-04-19 20:00:00',
title: '【系统通知】该系统将于今晚凌晨2点到5点进行升级维护'
}
]
});
const handleRead = (index: number) => {
const item = state.unread.splice(index, 1);
state.read = item.concat(state.read);
};
const handleDel = (index: number) => {
const item = state.read.splice(index, 1);
state.recycle = item.concat(state.recycle);
};
const handleRestore = (index: number) => {
const item = state.recycle.splice(index, 1);
state.read = item.concat(state.read);
};
</script>
<style>
.message-title {
cursor: pointer;
color: var(--el-color-primary);
}
.handle-row {
margin-top: 30px;
}
</style>
================================================
FILE: src/views/element/tour.vue
================================================
<template>
<div class="container">
<el-button type="primary" @click="open = true">开始引导</el-button>
<el-divider />
<el-space>
<el-button ref="ref1">上传</el-button>
<el-button ref="ref2" type="primary">保存</el-button>
<el-button ref="ref3" :icon="MoreFilled" />
</el-space>
<el-tour v-model="open">
<el-tour-step :target="ref1?.$el" title="上传文件">
<img style="width: 120px" src="../../assets/img/img.jpg" alt="tour.png" />
<div>点击这里选择文件</div>
</el-tour-step>
<el-tour-step :target="ref2?.$el" title="保存" description="点击进行上传" />
<el-tour-step :target="ref3?.$el" title="更多操作" description="点击查看更多操作" />
</el-tour>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { MoreFilled } from '@element-plus/icons-vue'
const ref1 = ref()
const ref2 = ref()
const ref3 = ref()
const open = ref(false)
</script>
================================================
FILE: src/views/element/upload.vue
================================================
<template>
<div class="container">
<div class="content-title">支持拖拽</div>
<div class="plugins-tips">
Element Plus自带上传组件。 访问地址:
<a href="https://element-plus.org/zh-CN/component/upload.html" target="_blank">Element Plus Upload</a>
</div>
<el-upload class="upload-demo" drag action="http://jsonplaceholder.typicode.com/api/posts/" multiple
:on-change="handle">
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
将文件拖到此处,或
<em>点击上传</em>
</div>
</el-upload>
<div class="content-title">支持裁剪</div>
<div class="plugins-tips">
vue-cropper:一个简单的vue图片裁剪插件。 访问地址:
<a href="https://github.com/xyxiao001/vue-cropper" target="_blank">vue-cropper</a>。 示例请查看
<router-link to="/ucenter">个人中心-我的头像</router-link>
</div>
</div>
</template>
<script setup lang="ts">
const handle = (rawFile: any) => {
console.log(rawFile);
};
</script>
<style scoped>
.content-title {
font-weight: 400;
line-height: 50px;
margin: 10px 0;
font-size: 22px;
color: #1f2f3d;
}
.upload-demo {
width: 360px;
}
</style>
================================================
FILE: src/views/element/watermark.vue
================================================
<template>
<div class="container">
<el-row :gutter="20">
<el-col :span="18">
<el-watermark :content="config.content" :font="config.font" :z-index="config.zIndex"
:rotate="config.rotate" :gap="config.gap" :offset="config.offset">
<div style="height: 600px" />
</el-watermark>
</el-col>
<el-col :span="6">
<el-form class="form" :model="config" label-position="top" label-width="50px">
<el-form-item label="Content">
<el-input v-model="config.content" />
</el-form-item>
<el-form-item label="Color">
<el-color-picker v-model="config.font.color" show-alpha />
</el-form-item>
<el-form-item label="FontSize">
<el-slider v-model="config.font.fontSize" />
</el-form-item>
<el-form-item label="zIndex">
<el-slider v-model="config.zIndex" />
</el-form-item>
<el-form-item label="Rotate">
<el-slider v-model="config.rotate" :min="-180" :max="180" />
</el-form-item>
<el-form-item label="Gap">
<el-space>
<el-input-number v-model="config.gap[0]" controls-position="right" />
<el-input-number v-model="config.gap[1]" controls-position="right" />
</el-space>
</el-form-item>
<el-form-item label="Offset">
<el-space>
<el-input-number v-model="config.offset[0]" placeholder="offsetLeft"
controls-position="right" />
<el-input-number v-model="config.offset[1]" placeholder="offsetTop"
controls-position="right" />
</el-space>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
const config = reactive({
content: 'vue-manage-system',
font: {
fontSize: 16,
color: 'rgba(0, 0, 0, 0.15)',
},
zIndex: -1,
rotate: -22,
gap: [100, 100] as [number, number],
offset: [] as unknown as [number, number],
})
</script>
================================================
FILE: src/views/home.vue
================================================
<template>
<div class="wrapper">
<v-header />
<v-sidebar />
<div class="content-box" :class="{ 'content-collapse': sidebar.collapse }">
<v-tabs></v-tabs>
<div class="content">
<router-view v-slot="{ Component }">
<transition name="move" mode="out-in">
<keep-alive :include="tabs.nameList">
<component :is="Component"></component>
</keep-alive>
</transition>
</router-view>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useSidebarStore } from '@/store/sidebar';
import { useTabsStore } from '@/store/tabs';
import vHeader from '@/components/header.vue';
import vSidebar from '@/components/sidebar.vue';
import vTabs from '@/components/tabs.vue';
const sidebar = useSidebarStore();
const tabs = useTabsStore();
</script>
<style>
.wrapper {
height: 100vh;
overflow: hidden;
}
.content-box {
position: absolute;
left: 250px;
right: 0;
top: 70px;
bottom: 0;
padding-bottom: 30px;
-webkit-transition: left 0.3s ease-in-out;
transition: left 0.3s ease-in-out;
background: #eef0fc;
overflow: hidden;
}
.content {
width: auto;
height: 100%;
padding: 20px;
overflow-y: scroll;
box-sizing: border-box;
}
.content::-webkit-scrollbar {
width: 0;
}
.content-collapse {
left: 65px;
}
</style>
================================================
FILE: src/views/pages/403.vue
================================================
<template>
<div class="error-page">
<div class="error-box">
<div class="error-code">403</div>
<div class="error-desc">啊哦~ 你没有权限访问该页面哦</div>
<div class="error-handle">
<router-link to="/">
<el-button type="primary" size="large">返回首页</el-button>
</router-link>
<el-button class="error-btn" size="large" @click="goBack">返回上一页</el-button>
</div>
</div>
</div>
</template>
<script setup lang="ts" name="403">
import { useRouter } from 'vue-router';
const router = useRouter();
const goBack = () => {
router.go(-2);
};
</script>
<style scoped>
.error-page {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100vh;
background: #eef0fc;
box-sizing: border-box;
}
.error-box {
width: 400px;
background-color: #fff;
padding: 80px 50px;
border-radius: 5px;
}
.error-code {
line-height: 1;
font-size: 100px;
font-weight: bold;
color: var(--el-color-primary);
margin-bottom: 20px;
text-align: center;
}
.error-desc {
font-size: 20px;
color: #777;
text-align: center;
}
.error-handle {
margin-top: 50px;
text-align: center;
}
.error-btn {
margin-left: 100px;
}
</style>
================================================
FILE: src/views/pages/404.vue
================================================
<template>
<div class="error-page">
<div class="error-box">
<div class="error-code">404</div>
<div class="error-desc">啊哦~ 你所访问的页面不存在</div>
<div class="error-handle">
<router-link to="/">
<el-button type="primary" size="large">返回首页</el-button>
</router-link>
<el-button class="error-btn" size="large" @click="goBack">返回上一页</el-button>
</div>
</div>
</div>
</template>
<script setup lang="ts" name="404">
import { useRouter } from 'vue-router';
const router = useRouter();
const goBack = () => {
router.go(-1);
};
</script>
<style scoped>
.error-page {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100vh;
background: #eef0fc;
box-sizing: border-box;
}
.error-box {
width: 400px;
background-color: #fff;
padding: 80px 50px;
border-radius: 5px;
}
.error-code {
line-height: 1;
font-size: 100px;
font-weight: bold;
color: var(--el-color-primary);
margin-bottom: 20px;
text-align: center;
}
.error-desc {
font-size: 20px;
color: #777;
text-align: center;
}
.error-handle {
margin-top: 50px;
text-align: center;
}
.error-btn {
margin-left: 100px;
}
</style>
================================================
FILE: src/views/pages/editor.vue
================================================
<template>
<div class="container">
<div class="plugins-tips">
wangEditor:轻量级 web 富文本编辑器,配置方便,使用简单。 访问地址:
<a href="https://www.wangeditor.com/doc/" target="_blank">wangEditor</a>
</div>
<div style="border: 1px solid #ccc; margin-bottom: 10px">
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig" />
<Editor
style="height: 500px; overflow-y: hidden"
v-model="valueHtml"
:defaultConfig="editorConfig"
@onCreated="handleCreated"
/>
</div>
<el-button type="primary" @click="syncHTML">提交</el-button>
</div>
</template>
<script setup lang="ts" name="editor">
import '@wangeditor/editor/dist/css/style.css'; // 引入 css
import { onBeforeUnmount, ref, reactive, shallowRef, onMounted } from 'vue';
import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef();
// 内容 HTML
const valueHtml = ref('<p>hello</p>');
// 模拟 ajax 异步获取内容
onMounted(() => {
setTimeout(() => {
valueHtml.value = '<p>模拟 Ajax 异步设置内容</p>';
}, 1500);
});
const toolbarConfig = {};
const editorConfig = { placeholder: '请输入内容...' };
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
const handleCreated = (editor: any) => {
editorRef.value = editor; // 记录 editor 实例,重要!
};
const syncHTML = () => {
console.log(valueHtml.value);
};
</script>
<style></style>
================================================
FILE: src/views/pages/icon.vue
================================================
<template>
<el-tabs type="border-card">
<el-tab-pane label="自定义图标">
<h2>使用方法</h2>
<p style="line-height: 50px">
直接通过设置类名为 el-icon-lx-iconName 来使用即可。例如:(共{{ iconList.length }}个图标)
</p>
<p class="example-p">
<i class="el-icon-lx-redpacket_fill" style="font-size: 30px; color: #ff5900"></i>
<span><i class="el-icon-lx-redpacket_fill"></i></span>
</p>
<p class="example-p">
<i class="el-icon-lx-weibo" style="font-size: 30px; color: #fd5656"></i>
<span><i class="el-icon-lx-weibo"></i></span>
</p>
<p class="example-p">
<i class="el-icon-lx-emojifill" style="font-size: 30px; color: #ffc300"></i>
<span><i class="el-icon-lx-emojifill"></i></span>
</p>
<br />
<h2>图标</h2>
<div class="search-box">
<el-input class="search" size="large" v-model="keyword" clearable placeholder="请输入图标名称"></el-input>
</div>
<ul>
<li class="icon-li" v-for="(item, index) in list" :key="index">
<div class="icon-li-content">
<i :class="`el-icon-lx-${item}`"></i>
<span>{{ item }}</span>
</div>
</li>
</ul>
</el-tab-pane>
<el-tab-pane label="Element图标">
<el-link type="primary" href="https://element-plus.org/zh-CN/component/icon.html#icon-collection"
target="_blank">前往官方文档查看</el-link>
</el-tab-pane>
</el-tabs>
</template>
<script setup lang="ts" name="icon">
import { computed, ref } from 'vue';
const iconList: Array<string> = [
'attentionforbid',
'attentionforbidfill',
'attention',
'attentionfill',
'tag',
'tagfill',
'people',
'peoplefill',
'notice',
'noticefill',
'mobile',
'mobilefill',
'voice',
'voicefill',
'unlock',
'lock',
'home',
'homefill',
'delete',
'deletefill',
'notification',
'notificationfill',
'notificationforbidfill',
'like',
'likefill',
'comment',
'commentfill',
'camera',
'camerafill',
'warn',
'warnfill',
'time',
'timefill',
'location',
'locationfill',
'favor',
'favorfill',
'skin',
'skinfill',
'news',
'newsfill',
'record',
'recordfill',
'emoji',
'emojifill',
'message',
'messagefill',
'goods',
'goodsfill',
'crown',
'crownfill',
'move',
'add',
'hot',
'hotfill',
'service',
'servicefill',
'present',
'presentfill',
'pic',
'picfill',
'rank',
'rankfill',
'male',
'female',
'down',
'top',
'recharge',
'rechargefill',
'forward',
'forwardfill',
'info',
'infofill',
'redpacket',
'redpacket_fill',
'roundadd',
'roundaddfill',
'friendadd',
'friendaddfill',
'cart',
'cartfill',
'more',
'moreandroid',
'back',
'right',
'shop',
'shopfill',
'question',
'questionfill',
'roundclose',
'roundclosefill',
'roundcheck',
'roundcheckfill',
'global',
'mail',
'punch',
'exit',
'upload',
'read',
'file',
'link',
'full',
'group',
'friend',
'profile',
'addressbook',
'calendar',
'text',
'copy',
'share',
'wifi',
'vipcard',
'weibo',
'remind',
'refresh',
'filter',
'settings',
'scan',
'qrcode',
'cascades',
'apps',
'sort',
'searchlist',
'search',
'edit',
'apple-line',
'baidu-fill',
'amazon-fill',
'netease-cloud-music-fill',
'qq-line',
'wechat-fill',
'alipay-fill',
'android-fill',
'android-line',
'whatsapp-line',
'whatsapp-fill',
'bilibili-fill',
'chrome-fill',
'dingding-fill',
'dingding-line',
'apple-fill',
'github-fill',
'qq-fill',
'wechat-pay-fill',
'windows-line',
'windows-fill',
'youtube-line',
'youtube-fill',
'wechat-pay-line',
'zhihu-line'
];
const keyword = ref('');
const list = computed(() => {
return iconList.filter(item => {
return item.indexOf(keyword.value) !== -1;
});
});
</script>
<style scoped>
.example-p {
height: 45px;
display: flex;
align-items: center;
}
.search-box {
text-align: center;
margin-top: 10px;
}
.search {
width: 300px;
}
ul,
li {
list-style: none;
}
.icon-li {
display: inline-block;
padding: 10px;
width: 120px;
height: 120px;
}
.icon-li-content {
display: flex;
height: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
}
.icon-li-content i {
font-size: 36px;
color: #606266;
}
.icon-li-content span {
margin-top: 10px;
color: #787878;
}
.iframe {
width: 100%;
height: 700px;
}
</style>
================================================
FILE: src/views/pages/login.vue
================================================
<template>
<div class="login-bg">
<div class="login-container">
<div class="login-header">
<img class="logo mr10" src="../../assets/img/logo.svg" alt="" />
<div class="login-title">后台管理系统</div>
</div>
<el-form :model="param" :rules="rules" ref="login" size="large">
<el-form-item prop="username">
<el-input v-model="param.username" placeholder="用户名">
<template #prepend>
<el-icon>
<User />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
placeholder="密码"
v-model="param.password"
@keyup.enter="submitForm(login)"
>
<template #prepend>
<el-icon>
<Lock />
</el-icon>
</template>
</el-input>
</el-form-item>
<div class="pwd-tips">
<el-checkbox class="pwd-checkbox" v-model="checked" label="记住密码" />
<el-link type="primary" @click="$router.push('/reset-pwd')">忘记密码</el-link>
</div>
<el-button class="login-btn" type="primary" size="large" @click="submitForm(login)">登录</el-button>
<p class="login-tips">Tips : 用户名和密码随便填。</p>
<p class="login-text">
没有账号?<el-link type="primary" @click="$router.push('/register')">立即注册</el-link>
</p>
</el-form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { useTabsStore } from '@/store/tabs';
import { usePermissStore } from '@/store/permiss';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import type { FormInstance, FormRules } from 'element-plus';
interface LoginInfo {
username: string;
password: string;
}
const lgStr = localStorage.getItem('login-param');
const defParam = lgStr ? JSON.parse(lgStr) : null;
const checked = ref(lgStr ? true : false);
const router = useRouter();
const param = reactive<LoginInfo>({
username: defParam ? defParam.username : '',
password: defParam ? defParam.password : '',
});
const rules: FormRules = {
username: [
{
required: true,
message: '请输入用户名',
trigger: 'blur',
},
],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
};
const permiss = usePermissStore();
const login = ref<FormInstance>();
const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate((valid: boolean) => {
if (valid) {
ElMessage.success('登录成功');
localStorage.setItem('vuems_name', param.username);
const keys = permiss.defaultList[param.username == 'admin' ? 'admin' : 'user'];
permiss.handleSet(keys);
router.push('/');
if (checked.value) {
localStorage.setItem('login-param', JSON.stringify(param));
} else {
localStorage.removeItem('login-param');
}
} else {
ElMessage.error('登录失败');
return false;
}
});
};
const tabs = useTabsStore();
tabs.clearTabs();
</script>
<style scoped>
.login-bg {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
}
.login-header {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40px;
}
.logo {
width: 35px;
}
.login-title {
font-size: 22px;
color: #333;
font-weight: bold;
}
.login-container {
width: 450px;
border-radius: 5px;
background: #fff;
padding: 40px 50px 50px;
box-sizing: border-box;
}
.pwd-tips {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
margin: -10px 0 10px;
color: #787878;
}
.pwd-checkbox {
height: auto;
}
.login-btn {
display: block;
width: 100%;
}
.login-tips {
font-size: 12px;
color: #999;
}
.login-text {
display: flex;
align-items: center;
margin-top: 20px;
font-size: 14px;
color: #787878;
}
</style>
================================================
FILE: src/views/pages/markdown.vue
================================================
<template>
<div class="container">
<div class="plugins-tips">
md-editor-v3:vue3版本的 markdown 编辑器,配置丰富,请详看文档。 访问地址:
<a href="https://imzbf.github.io/md-editor-v3/index" target="_blank">md-editor-v3</a>
</div>
<md-editor class="mgb20" v-model="text" @on-upload-img="onUploadImg" />
<el-button type="primary">提交</el-button>
</div>
</template>
<script setup lang="ts" name="md">
import { ref } from 'vue';
import MdEditor from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
const text = ref('Hello Editor!');
const onUploadImg = (files: any) => {
console.log(files);
};
</script>
================================================
FILE: src/views/pages/register.vue
================================================
<template>
<div class="login-bg">
<div class="login-container">
<div class="login-header">
<img class="logo mr10" src="../../assets/img/logo.svg" alt="" />
<div class="login-title">后台管理系统</div>
</div>
<el-form :model="param" :rules="rules" ref="register" size="large">
<el-form-item prop="username">
<el-input v-model="param.username" placeholder="用户名">
<template #prepend>
<el-icon>
<User />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="email">
<el-input v-model="param.email" placeholder="邮箱">
<template #prepend>
<el-icon>
<Message />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
placeholder="密码"
v-model="param.password"
@keyup.enter="submitForm(register)"
>
<template #prepend>
<el-icon>
<Lock />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-button class="login-btn" type="primary" size="large" @click="submitForm(register)">注册</el-button>
<p class="login-text">
已有账号,<el-link type="primary" @click="$router.push('/login')">立即登录</el-link>
</p>
</el-form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
import { Register } from '@/types/user';
const router = useRouter();
const param = reactive<Register>({
username: '',
password: '',
email: '',
});
const rules: FormRules = {
username: [
{
required: true,
message: '请输入用户名',
trigger: 'blur',
},
],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
email: [{ required: true, message: '请输入邮箱', trigger: 'blur' }],
};
const register = ref<FormInstance>();
const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate((valid: boolean) => {
if (valid) {
ElMessage.success('注册成功,请登录');
router.push('/login');
} else {
return false;
}
});
};
</script>
<style scoped>
.login-bg {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
}
.login-header {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40px;
}
.logo {
width: 35px;
}
.login-title {
font-size: 22px;
color: #333;
font-weight: bold;
}
.login-container {
width: 450px;
border-radius: 5px;
background: #fff;
padding: 40px 50px 50px;
box-sizing: border-box;
}
.login-btn {
display: block;
width: 100%;
}
.login-text {
display: flex;
align-items: center;
margin-top: 20px;
font-size: 14px;
color: #787878;
}
</style>
================================================
FILE: src/views/pages/reset-pwd.vue
================================================
<template>
<div class="login-bg">
<div class="login-container">
<div class="reset-title">重置密码</div>
<p class="reset-text">输入你的邮箱,我们将发送重置密码邮件</p>
<el-form :model="param" :rules="rules" ref="register" size="large">
<el-form-item prop="email">
<el-input v-model="param.email" placeholder="邮箱">
<template #prepend>
<el-icon>
<Message />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-button class="login-btn" type="primary" size="large" @click="submitForm(register)"
>发送邮件</el-button
>
<p class="login-text"><el-link type="primary" @click="$router.push('/login')">返回登录</el-link></p>
</el-form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
const param = ref({
email: '',
});
const rules: FormRules = {
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{
pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
message: '请输入正确的邮箱格式',
trigger: 'blur',
},
],
};
const register = ref<FormInstance>();
const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate((valid: boolean) => {
if (valid) {
ElMessage.success('邮件已发送,请注意查收');
} else {
return false;
}
});
};
</script>
<style scoped>
.login-bg {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
}
.reset-title {
text-align: center;
font-size: 22px;
color: #333;
font-weight: bold;
margin-bottom: 10px;
}
.reset-text {
text-align: center;
font-size: 14px;
color: #787878;
margin-bottom: 40px;
}
.login-container {
width: 450px;
border-radius: 5px;
background: #fff;
padding: 40px 50px 50px;
box-sizing: border-box;
}
.login-btn {
display: block;
width: 100%;
}
.login-text {
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
font-size: 14px;
color: #333;
}
</style>
================================================
FILE: src/views/pages/theme.vue
================================================
<template>
<div>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">系统主题</div>
</template>
<div class="theme-list mgb20">
<div class="theme-item" @click="setSystemTheme(item)" v-for="item in system"
:style="{ backgroundColor: item.color, color: '#fff' }">{{ item.name }}
</div>
</div>
<div class="flex-center">
<el-button @click="resetSystemTheme">重置主题</el-button>
</div>
</el-card>
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">Element-Plus主题</div>
</template>
<div class="theme-list mgb20">
<div class="theme-item" v-for="theme in themes">
<el-button :type="theme.name">{{ theme.name }}</el-button>
<div class="theme-color">{{ theme.color }}</div>
<el-color-picker v-model="color[theme.name]" @change="changeColor(theme.name)" />
</div>
</div>
<div class="flex-center">
<el-button @click="resetTheme">重置主题</el-button>
</div>
</el-card>
<el-row :gutter="50">
<el-col :span="12">
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">头部主题</div>
</template>
<div class="theme-list mgb20">
<div class="theme-item">
<el-button :color="color.headerBgColor">背景颜色</el-button>
<div class="theme-color">{{ color.headerBgColor }}</div>
<el-color-picker v-model="color.headerBgColor"
@change="themeStore.setHeaderBgColor(color.headerBgColor)" />
</div>
<div class="theme-item">
<el-button :color="color.headerTextColor">文字颜色</el-button>
<div class="theme-color">{{ color.headerTextColor }}</div>
<el-color-picker v-model="color.headerTextColor"
@change="themeStore.setHeaderTextColor(color.headerTextColor)" />
</div>
</div>
<div class="flex-center">
<el-button @click="resetHeader">重置主题</el-button>
</div>
</el-card>
</el-col>
<el-col :span="12">
<el-card class="mgb20" shadow="hover">
<template #header>
<div class="content-title">菜单主题</div>
</template>
<div class="them
gitextract_omct5e3d/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── README_EN.md ├── auto-imports.d.ts ├── components.d.ts ├── index.html ├── package.json ├── public/ │ ├── mock/ │ │ ├── role.json │ │ ├── table.json │ │ └── user.json │ └── template.xlsx ├── src/ │ ├── App.vue │ ├── api/ │ │ └── index.ts │ ├── assets/ │ │ └── css/ │ │ ├── icon.css │ │ └── main.css │ ├── components/ │ │ ├── countup.vue │ │ ├── header.vue │ │ ├── menu.ts │ │ ├── sidebar.vue │ │ ├── table-custom.vue │ │ ├── table-detail.vue │ │ ├── table-edit.vue │ │ ├── table-search.vue │ │ └── tabs.vue │ ├── main.ts │ ├── router/ │ │ └── index.ts │ ├── store/ │ │ ├── permiss.ts │ │ ├── sidebar.ts │ │ ├── tabs.ts │ │ └── theme.ts │ ├── types/ │ │ ├── form-option.ts │ │ ├── menu.ts │ │ ├── role.ts │ │ ├── table.ts │ │ └── user.ts │ ├── utils/ │ │ ├── china.ts │ │ ├── index.ts │ │ └── request.ts │ ├── views/ │ │ ├── chart/ │ │ │ ├── echarts.vue │ │ │ ├── options.ts │ │ │ └── schart.vue │ │ ├── dashboard.vue │ │ ├── element/ │ │ │ ├── calendar.vue │ │ │ ├── carousel.vue │ │ │ ├── form.vue │ │ │ ├── statistic.vue │ │ │ ├── steps.vue │ │ │ ├── tabs.vue │ │ │ ├── tour.vue │ │ │ ├── upload.vue │ │ │ └── watermark.vue │ │ ├── home.vue │ │ ├── pages/ │ │ │ ├── 403.vue │ │ │ ├── 404.vue │ │ │ ├── editor.vue │ │ │ ├── icon.vue │ │ │ ├── login.vue │ │ │ ├── markdown.vue │ │ │ ├── register.vue │ │ │ ├── reset-pwd.vue │ │ │ ├── theme.vue │ │ │ └── ucenter.vue │ │ ├── system/ │ │ │ ├── menu.vue │ │ │ ├── role-permission.vue │ │ │ ├── role.vue │ │ │ └── user.vue │ │ └── table/ │ │ ├── basetable.vue │ │ ├── export.vue │ │ ├── import.vue │ │ └── table-editor.vue │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts
SYMBOL INDEX (26 symbols across 11 files)
FILE: components.d.ts
type GlobalComponents (line 9) | interface GlobalComponents {
FILE: src/main.ts
method mounted (line 21) | mounted(el, binding) {
FILE: src/store/permiss.ts
type ObjectList (line 3) | interface ObjectList {
method handleSet (line 56) | handleSet(val: string[]) {
FILE: src/store/sidebar.ts
method handleCollapse (line 13) | handleCollapse() {
method setBgColor (line 16) | setBgColor(color: string) {
method setTextColor (line 20) | setTextColor(color: string) {
FILE: src/store/tabs.ts
type ListItem (line 3) | interface ListItem {
method delTabsItem (line 24) | delTabsItem(index: number) {
method setTabsItem (line 27) | setTabsItem(data: ListItem) {
method clearTabs (line 30) | clearTabs() {
method closeTabsOther (line 33) | closeTabsOther(data: ListItem[]) {
method closeCurrentTag (line 36) | closeCurrentTag(data: any) {
FILE: src/store/theme.ts
method initTheme (line 18) | initTheme() {
method resetTheme (line 30) | resetTheme() {
method setPropertyColor (line 35) | setPropertyColor(color: string, type: string = 'primary') {
method setThemeLight (line 41) | setThemeLight(type: string = 'primary') {
method setHeaderBgColor (line 47) | setHeaderBgColor(color: string) {
method setHeaderTextColor (line 52) | setHeaderTextColor(color: string) {
FILE: src/types/form-option.ts
type FormOption (line 1) | interface FormOption {
type FormOptionList (line 8) | interface FormOptionList {
FILE: src/types/menu.ts
type Menus (line 1) | interface Menus {
FILE: src/types/role.ts
type Role (line 2) | interface Role {
FILE: src/types/table.ts
type TableItem (line 1) | interface TableItem {
FILE: src/types/user.ts
type User (line 2) | interface User {
type Register (line 12) | interface Register {
Condensed preview — 76 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (258K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 683,
"preview": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [u"
},
{
"path": ".gitignore",
"chars": 254,
"preview": ".DS_Store\r\nnode_modules\r\n/dist\r\n\r\n\r\n# local env files\r\n.env.local\r\n.env.*.local\r\n\r\n# Log files\r\nnpm-debug.log*\r\nyarn-deb"
},
{
"path": "LICENSE",
"chars": 1078,
"preview": "MIT License\n\nCopyright (c) 2016-2023 vue-manage-system\n\nPermission is hereby granted, free of charge, to any person obta"
},
{
"path": "README.md",
"chars": 2046,
"preview": "# vue-manage-system\r\n\r\n <a href=\"https://github.com/lin-xin/vue-manage-system/releases\">\r\n <img src=\"https://img.shi"
},
{
"path": "README_EN.md",
"chars": 3379,
"preview": "# vue-manage-system\r\n\r\n<a href=\"https://github.com/vuejs/vue\">\r\n <img src=\"https://img.shields.io/badge/vue-2.6.10-br"
},
{
"path": "auto-imports.d.ts",
"chars": 69,
"preview": "// Generated by 'unplugin-auto-import'\nexport {}\ndeclare global {\n\n}\n"
},
{
"path": "components.d.ts",
"chars": 4682,
"preview": "// generated by unplugin-vue-components\n// We suggest you to commit this file into source control\n// Read more: https://"
},
{
"path": "index.html",
"chars": 679,
"preview": "<!DOCTYPE html>\r\n<html lang=\"\">\r\n\r\n<head>\r\n <meta charset=\"utf-8\">\r\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=ed"
},
{
"path": "package.json",
"chars": 1057,
"preview": "{\r\n\t\"name\": \"vue-manage-system\",\r\n\t\"version\": \"5.5.0\",\r\n\t\"private\": true,\r\n\t\"scripts\": {\r\n\t\t\"dev\": \"vite\",\r\n\t\t\"build\": \""
},
{
"path": "public/mock/role.json",
"chars": 957,
"preview": "{\r\n \"list\": [\r\n {\r\n \"id\": 1,\r\n \"name\": \"管理员\",\r\n \"key\": \"admin\",\r\n "
},
{
"path": "public/mock/table.json",
"chars": 1124,
"preview": "{\r\n \"list\": [\r\n {\r\n \"id\": 1,\r\n \"name\": \"张三\",\r\n \"money\": 123,\r\n \"ad"
},
{
"path": "public/mock/user.json",
"chars": 526,
"preview": "{\r\n \"list\": [\r\n {\r\n \"id\": 1,\r\n \"name\": \"张三\",\r\n \"password\": \"123\",\r\n "
},
{
"path": "src/App.vue",
"chars": 405,
"preview": "<template>\r\n\t<el-config-provider :locale=\"zhCn\">\r\n\t\t<router-view />\r\n\t</el-config-provider>\r\n</template>\r\n\r\n<script setu"
},
{
"path": "src/api/index.ts",
"chars": 434,
"preview": "import request from '../utils/request';\r\n\r\nexport const fetchData = () => {\r\n return request({\r\n url: './mock/"
},
{
"path": "src/assets/css/icon.css",
"chars": 91,
"preview": "[class*=\" el-icon-lx\"],\r\n[class^=el-icon-lx] {\r\n font-family: lx-iconfont !important;\r\n}"
},
{
"path": "src/assets/css/main.css",
"chars": 1316,
"preview": "* {\r\n\tmargin: 0;\r\n\tpadding: 0;\r\n\toutline: 0 !important;\r\n}\r\n\r\n\r\nbody {\r\n\tfont-family: 'PingFang SC', 'Helvetica Neue', H"
},
{
"path": "src/components/countup.vue",
"chars": 757,
"preview": "<template>\r\n <span ref=\"countRef\"></span>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { onMounted, ref, watch } "
},
{
"path": "src/components/header.vue",
"chars": 5686,
"preview": "<template>\r\n <div class=\"header\">\r\n <!-- 折叠按钮 -->\r\n <div class=\"header-left\">\r\n <img class=\""
},
{
"path": "src/components/menu.ts",
"chars": 5248,
"preview": "import { Menus } from '@/types/menu';\r\n\r\nexport const menuData: Menus[] = [\r\n {\r\n id: '0',\r\n title: '系统"
},
{
"path": "src/components/sidebar.vue",
"chars": 3087,
"preview": "<template>\r\n <div class=\"sidebar\">\r\n <el-menu\r\n class=\"sidebar-el-menu\"\r\n :default-activ"
},
{
"path": "src/components/table-custom.vue",
"chars": 6557,
"preview": "<template>\r\n <div>\r\n <div class=\"table-toolbar\" v-if=\"hasToolbar\">\r\n <div class=\"table-toolbar-left"
},
{
"path": "src/components/table-detail.vue",
"chars": 520,
"preview": "<template>\r\n\t<el-descriptions :title=\"title\" :column=\"column\" border>\r\n\t\t<el-descriptions-item v-for=\"item in list\" :spa"
},
{
"path": "src/components/table-edit.vue",
"chars": 3428,
"preview": "<template>\r\n\t<el-form ref=\"formRef\" :model=\"form\" :rules=\"rules\" :label-width=\"options.labelWidth\">\r\n\t\t<el-row>\r\n\t\t\t<el-"
},
{
"path": "src/components/table-search.vue",
"chars": 1834,
"preview": "<template>\r\n\t<div class=\"search-container\">\r\n\t\t<el-form ref=\"searchRef\" :model=\"query\" :inline=\"true\">\r\n\t\t\t<el-form-item"
},
{
"path": "src/components/tabs.vue",
"chars": 3786,
"preview": "<template>\r\n <div class=\"tabs-container\">\r\n <el-tabs v-model=\"activePath\" class=\"tabs\" type=\"card\" closable @t"
},
{
"path": "src/main.ts",
"chars": 791,
"preview": "import { createApp } from 'vue';\r\nimport { createPinia } from 'pinia';\r\nimport * as ElementPlusIconsVue from '@element-p"
},
{
"path": "src/router/index.ts",
"chars": 9637,
"preview": "import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';\r\nimport { usePermissStore } from '../st"
},
{
"path": "src/store/permiss.ts",
"chars": 1450,
"preview": "import { defineStore } from 'pinia';\r\n\r\ninterface ObjectList {\r\n [key: string]: string[];\r\n}\r\n\r\nexport const usePermi"
},
{
"path": "src/store/sidebar.ts",
"chars": 635,
"preview": "import { defineStore } from 'pinia';\r\n\r\nexport const useSidebarStore = defineStore('sidebar', {\r\n\tstate: () => {\r\n\t\tretu"
},
{
"path": "src/store/tabs.ts",
"chars": 1086,
"preview": "import { defineStore } from 'pinia';\r\n\r\ninterface ListItem {\r\n\tname: string;\r\n\tpath: string;\r\n\ttitle: string;\r\n}\r\n\r\nexpo"
},
{
"path": "src/store/theme.ts",
"chars": 2275,
"preview": "import { mix, setProperty } from '@/utils';\r\nimport { defineStore } from 'pinia';\r\n\r\nexport const useThemeStore = define"
},
{
"path": "src/types/form-option.ts",
"chars": 438,
"preview": "export interface FormOption {\r\n list: FormOptionList[];\r\n labelWidth?: number | string;\r\n span?: number;\r\n\r\n}\r\n"
},
{
"path": "src/types/menu.ts",
"chars": 171,
"preview": "export interface Menus {\r\n id: string;\r\n pid?: string;\r\n icon?: string;\r\n index: string;\r\n title: string;"
},
{
"path": "src/types/role.ts",
"chars": 127,
"preview": "\r\nexport interface Role {\r\n id: number;\r\n name: string;\r\n key: string;\r\n status: boolean;\r\n permiss: stri"
},
{
"path": "src/types/table.ts",
"chars": 168,
"preview": "export interface TableItem {\r\n id: number;\r\n name: string;\r\n thumb: string;\r\n money: number;\r\n state: str"
},
{
"path": "src/types/user.ts",
"chars": 265,
"preview": "\r\nexport interface User {\r\n id: number;\r\n name: string;\r\n password: string;\r\n email: string;\r\n phone: str"
},
{
"path": "src/utils/china.ts",
"chars": 41098,
"preview": "let chinaMap:any = {\"type\":\"FeatureCollection\",\"features\":[{\"id\":\"710000\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates"
},
{
"path": "src/utils/index.ts",
"chars": 574,
"preview": "export const setProperty = (prop: string, val: any, dom = document.documentElement) => {\r\n dom.style.setProperty(prop"
},
{
"path": "src/utils/request.ts",
"chars": 738,
"preview": "import axios, { AxiosInstance, AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios';\r\n\r\nconst service: A"
},
{
"path": "src/views/chart/echarts.vue",
"chars": 2710,
"preview": "<template>\r\n <div class=\"container\">\r\n <div class=\"plugins-tips\">\r\n vue-echarts:Apache ECharts™ 的 V"
},
{
"path": "src/views/chart/options.ts",
"chars": 8882,
"preview": "import { graphic } from 'echarts/core';\r\nexport const barOptions = {\r\n xAxis: {\r\n type: 'category',\r\n d"
},
{
"path": "src/views/chart/schart.vue",
"chars": 2801,
"preview": "<template>\r\n\t<div class=\"container\">\r\n\t\t<div class=\"plugins-tips\">\r\n\t\t\tvue-schart:vue.js封装sChart.js的图表组件。 访问地址:\r\n\t\t\t<a h"
},
{
"path": "src/views/dashboard.vue",
"chars": 9667,
"preview": "<template>\r\n <div>\r\n <el-row :gutter=\"20\" class=\"mgb20\">\r\n <el-col :span=\"6\">\r\n <el-"
},
{
"path": "src/views/element/calendar.vue",
"chars": 1908,
"preview": "<template>\r\n <div class=\"container\">\r\n <el-calendar v-model=\"value\">\r\n <template #date-cell=\"{ data"
},
{
"path": "src/views/element/carousel.vue",
"chars": 2097,
"preview": "<template>\r\n <div>\r\n <el-card class=\"mgb20\">\r\n <template #header>基础用法</template>\r\n <el-c"
},
{
"path": "src/views/element/form.vue",
"chars": 7060,
"preview": "<template>\r\n <div class=\"container\">\r\n <el-radio-group class=\"mgb20\" v-model=\"labelPosition\">\r\n <el"
},
{
"path": "src/views/element/statistic.vue",
"chars": 12052,
"preview": "<template>\r\n <div>\r\n\r\n\r\n <el-card class=\"mgb20\" shadow=\"hover\">\r\n <template #header>基础用法</template>"
},
{
"path": "src/views/element/steps.vue",
"chars": 1839,
"preview": "<template>\r\n <div class=\"container\">\r\n <div class=\"step-div\" v-if=\"step === 0\">\r\n <p>输入注册时的邮箱,我们会发送"
},
{
"path": "src/views/element/tabs.vue",
"chars": 3371,
"preview": "<template>\r\n\t<el-tabs v-model=\"message\" type=\"card\">\r\n\t\t<el-tab-pane :label=\"`未读消息(${state.unread.length})`\" name=\"first"
},
{
"path": "src/views/element/tour.vue",
"chars": 1030,
"preview": "<template>\r\n <div class=\"container\">\r\n <el-button type=\"primary\" @click=\"open = true\">开始引导</el-button>\r\n\r\n "
},
{
"path": "src/views/element/upload.vue",
"chars": 1302,
"preview": "<template>\r\n <div class=\"container\">\r\n <div class=\"content-title\">支持拖拽</div>\r\n <div class=\"plugins-tips"
},
{
"path": "src/views/element/watermark.vue",
"chars": 2616,
"preview": "<template>\r\n <div class=\"container\">\r\n <el-row :gutter=\"20\">\r\n <el-col :span=\"18\">\r\n "
},
{
"path": "src/views/home.vue",
"chars": 1567,
"preview": "<template>\r\n <div class=\"wrapper\">\r\n <v-header />\r\n <v-sidebar />\r\n <div class=\"content-box\" :cl"
},
{
"path": "src/views/pages/403.vue",
"chars": 1428,
"preview": "<template>\r\n <div class=\"error-page\">\r\n <div class=\"error-box\">\r\n <div class=\"error-code\">403</div>"
},
{
"path": "src/views/pages/404.vue",
"chars": 1427,
"preview": "<template>\r\n <div class=\"error-page\">\r\n <div class=\"error-box\">\r\n <div class=\"error-code\">404</div>"
},
{
"path": "src/views/pages/editor.vue",
"chars": 1657,
"preview": "<template>\r\n <div class=\"container\">\r\n <div class=\"plugins-tips\">\r\n wangEditor:轻量级 web 富文本编辑器,配置方便,"
},
{
"path": "src/views/pages/icon.vue",
"chars": 4487,
"preview": "<template>\r\n\r\n\t<el-tabs type=\"border-card\">\r\n\t\t<el-tab-pane label=\"自定义图标\">\r\n\t\t\t<h2>使用方法</h2>\r\n\t\t\t<p style=\"line-height: "
},
{
"path": "src/views/pages/login.vue",
"chars": 4865,
"preview": "<template>\r\n <div class=\"login-bg\">\r\n <div class=\"login-container\">\r\n <div class=\"login-header\">\r\n "
},
{
"path": "src/views/pages/markdown.vue",
"chars": 621,
"preview": "<template>\r\n\t<div class=\"container\">\r\n\t\t<div class=\"plugins-tips\">\r\n\t\t\tmd-editor-v3:vue3版本的 markdown 编辑器,配置丰富,请详看文档。 访问地"
},
{
"path": "src/views/pages/register.vue",
"chars": 3878,
"preview": "<template>\r\n <div class=\"login-bg\">\r\n <div class=\"login-container\">\r\n <div class=\"login-header\">\r\n "
},
{
"path": "src/views/pages/reset-pwd.vue",
"chars": 2625,
"preview": "<template>\r\n <div class=\"login-bg\">\r\n <div class=\"login-container\">\r\n <div class=\"reset-title\">重置密码"
},
{
"path": "src/views/pages/theme.vue",
"chars": 6939,
"preview": "<template>\r\n <div>\r\n <el-card class=\"mgb20\" shadow=\"hover\">\r\n <template #header>\r\n <"
},
{
"path": "src/views/pages/ucenter.vue",
"chars": 8136,
"preview": "<template>\r\n <div>\r\n <div class=\"user-container\">\r\n <el-card class=\"user-profile\" shadow=\"hover\" :b"
},
{
"path": "src/views/system/menu.vue",
"chars": 4279,
"preview": "<template>\r\n <div>\r\n <div class=\"container\">\r\n <TableCustom :columns=\"columns\" :tableData=\"menuData"
},
{
"path": "src/views/system/role-permission.vue",
"chars": 2039,
"preview": "<template>\r\n <div>\r\n <el-tree\r\n class=\"mgb10\"\r\n ref=\"tree\"\r\n :data=\"data\"\r\n "
},
{
"path": "src/views/system/role.vue",
"chars": 4928,
"preview": "<template>\r\n <div>\r\n <TableSearch :query=\"query\" :options=\"searchOpt\" :search=\"handleSearch\" />\r\n <div "
},
{
"path": "src/views/system/user.vue",
"chars": 4134,
"preview": "<template>\r\n <div>\r\n <TableSearch :query=\"query\" :options=\"searchOpt\" :search=\"handleSearch\" />\r\n <div "
},
{
"path": "src/views/table/basetable.vue",
"chars": 4388,
"preview": "<template>\r\n\t<div>\r\n\t\t<TableSearch :query=\"query\" :options=\"searchOpt\" :search=\"handleSearch\" />\r\n\t\t<div class=\"containe"
},
{
"path": "src/views/table/export.vue",
"chars": 2399,
"preview": "<template>\r\n <div>\r\n <div class=\"container\">\r\n <div class=\"handle-box\">\r\n <el-button"
},
{
"path": "src/views/table/import.vue",
"chars": 3170,
"preview": "<template>\r\n <div>\r\n <div class=\"container\">\r\n <div class=\"handle-box\">\r\n <el-upload"
},
{
"path": "src/views/table/table-editor.vue",
"chars": 2501,
"preview": "<template>\r\n\t<div class=\"container\">\r\n\t\t<TableCustom :columns=\"columns\" :tableData=\"tableData\" :hasToolbar=\"false\" :hasP"
},
{
"path": "src/vite-env.d.ts",
"chars": 251,
"preview": "/// <reference types=\"vite/client\" />\r\n\r\ndeclare module '*.vue' {\r\n import type { DefineComponent } from 'vue'\r\n const"
},
{
"path": "tsconfig.json",
"chars": 563,
"preview": "{\r\n \"compilerOptions\": {\r\n \"target\": \"ESNext\",\r\n \"useDefineForClassFields\": true,\r\n \"module\": \"ESNext\",\r\n \""
},
{
"path": "tsconfig.node.json",
"chars": 193,
"preview": "{\r\n \"compilerOptions\": {\r\n \"composite\": true,\r\n \"module\": \"ESNext\",\r\n \"moduleResolution\": \"Node\",\r\n \"allowS"
},
{
"path": "vite.config.ts",
"chars": 735,
"preview": "import { defineConfig } from 'vite';\r\nimport vue from '@vitejs/plugin-vue';\r\nimport VueSetupExtend from 'vite-plugin-vue"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the lin-xin/vue-manage-system GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 76 files (228.2 KB), approximately 88.9k tokens, and a symbol index with 26 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.