4 Commits

23 changed files with 3058 additions and 51 deletions

View File

@ -2,4 +2,4 @@
ENV = 'production'
# 线上环境接口地址
VITE_API_URL = 'https://gitee.com/gitee.io'
VITE_API_URL = 'https://lyt-top.gitee.io/vue-next-admin-preview/'

View File

@ -2,6 +2,24 @@
🎉🎉🔥 `vue-next-admin` 基于 vue3.x 、Typescript、vite、Element plus 等适配手机、平板、pc 的后台开源免费模板库vue2.x 请切换 vue-prev-admin 分支)
## 1.0.13
`2021.07.25`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 数据可视化演示界面(/visualizingDemo1、/visualizingDemo2
- 🎉 新增 登录页扫码登录
## 1.0.12
`2021.07.16`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 数据可视化演示空界面(待完善)
- 🎯 优化 tagsView 动态路由xxx/:id/:name时的右键菜单刷新、关闭其它时参数丢失问题2021.07.15 优化)
- 🐞 修复 路由带参数时,复制路径到登录页,跳转后参数消失的问题
- 🐞 修复 设置多个外链,点击后,页面内容停留在上一个内容(内容未改变)、国际化处理、打开新窗口 sessionStorage 共享等
## 1.0.11
`2021.07.14`

View File

@ -1,6 +1,6 @@
{
"name": "vue-next-admin",
"version": "1.0.11",
"version": "1.0.13",
"scripts": {
"dev": "vite",
"build": "vite build",
@ -11,8 +11,9 @@
"countup.js": "^2.0.7",
"cropperjs": "^1.5.12",
"echarts": "^5.1.2",
"echarts-gl": "^2.0.6",
"echarts-wordcloud": "^2.0.0",
"element-plus": "^1.0.2-beta.55",
"element-plus": "^1.0.2-beta.57",
"mitt": "^3.0.0",
"nprogress": "^0.2.0",
"print-js": "^1.6.0",
@ -23,7 +24,7 @@
"vue": "^3.0.11",
"vue-clipboard3": "^1.0.1",
"vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.4",
"vue-i18n": "^9.1.7",
"vue-router": "^4.0.8",
"vue-web-screen-shot": "^1.2.0",
"vuex": "^4.0.1",
@ -32,22 +33,22 @@
"devDependencies": {
"@types/axios": "^0.14.0",
"@types/clipboard": "^2.0.1",
"@types/node": "^16.3.1",
"@types/node": "^16.4.1",
"@types/nprogress": "^0.2.0",
"@types/sortablejs": "^1.10.7",
"@typescript-eslint/eslint-plugin": "^4.28.3",
"@typescript-eslint/parser": "^4.28.3",
"@typescript-eslint/eslint-plugin": "^4.28.4",
"@typescript-eslint/parser": "^4.28.4",
"@vitejs/plugin-vue": "^1.2.5",
"@vue/compiler-sfc": "^3.1.4",
"@vue/compiler-sfc": "^3.1.5",
"dotenv": "^10.0.0",
"eslint": "^7.30.0",
"eslint-plugin-vue": "^7.13.0",
"eslint": "^7.31.0",
"eslint-plugin-vue": "^7.14.0",
"prettier": "^2.3.2",
"sass": "^1.35.2",
"sass": "^1.36.0",
"sass-loader": "^12.1.0",
"typescript": "^4.3.5",
"vite": "^2.4.2",
"vue-eslint-parser": "^7.8.0"
"vite": "^2.4.3",
"vue-eslint-parser": "^7.9.0"
},
"browserslist": [
"> 1%",

View File

@ -58,11 +58,19 @@ export default {
paramsCommonDetails: 'General routing details',
paramsDynamicDetails: 'Dynamic routing details',
chartIndex: 'chartIndex',
visualizingIndex: 'visualizingIndex',
visualizingLinkDemo1: 'visualizingLinkDemo1',
visualizingLinkDemo2: 'visualizingLinkDemo2',
personal: 'personal',
tools: 'tools',
layoutLinkView: 'LinkView',
layoutIfameView: 'IfameView',
},
staticRoutes: {
signIn: 'signIn',
notFound: 'notFound',
noPower: 'noPower',
},
user: {
title0: 'Component size',
title1: 'Language switching',

View File

@ -58,11 +58,19 @@ export default {
paramsCommonDetails: '普通路由详情',
paramsDynamicDetails: '动态路由详情',
chartIndex: '大数据图表',
visualizingIndex: '数据可视化',
visualizingLinkDemo1: '数据可视化演示1',
visualizingLinkDemo2: '数据可视化演示2',
personal: '个人中心',
tools: '工具类集合',
layoutLinkView: '外链',
layoutIfameView: '内嵌 iframe',
},
staticRoutes: {
signIn: '登录',
notFound: '找不到此页面',
noPower: '没有权限',
},
user: {
title0: '组件大小',
title1: '语言切换',

View File

@ -58,11 +58,19 @@ export default {
paramsCommonDetails: '普通路由詳情',
paramsDynamicDetails: '動態路由詳情',
chartIndex: '大資料圖表',
visualizingIndex: '數據視覺化',
visualizingLinkDemo1: '數據視覺化演示1',
visualizingLinkDemo2: '數據視覺化演示2',
personal: '個人中心',
tools: '工具類集合',
layoutLinkView: '外鏈',
layoutIfameView: '内嵌 iframe',
},
staticRoutes: {
signIn: '登入',
notFound: '找不到此頁面',
noPower: '沒有許可權',
},
user: {
title0: '組件大小',
title1: '語言切換',

View File

@ -187,10 +187,14 @@ export default {
screenfulls.request(element);
});
};
// 当前项右键菜单点击,拿当前点击的路由路径对比 浏览器缓存中的 tagsView 路由数组,取当前点击项的详细路由信息
const getCurrentRouteItem = (path: string) => {
return Session.get('tagsViewList').find((v: any) => v.path === path);
};
// 当前项右键菜单点击
const onCurrentContextmenuClick = (item) => {
const { id, path } = item;
const { meta, name, params, query } = route;
const { meta, name, params, query } = getCurrentRouteItem(path);
switch (id) {
case 0:
refreshCurrentTagsView(path);
@ -330,6 +334,8 @@ export default {
});
// 页面加载前
onBeforeMount(() => {
// 初始化,防止手机端直接访问时还可以拖拽
onSortableResize();
// 拖动问题https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI
window.addEventListener('resize', onSortableResize);
// 监听非本页面调用 0 刷新当前1 关闭当前2 关闭其它3 关闭全部 4 当前页全屏

View File

@ -16,7 +16,7 @@
{{ $t(val.meta.title) }}
</template>
<template #title v-else>
<a :href="val.meta.isLink" target="_blank">
<a :href="val.meta.isLink" target="_blank" rel="opener">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
{{ $t(val.meta.title) }}
</a>

View File

@ -13,7 +13,7 @@
<span>{{ $t(val.meta.title) }}</span>
</template>
<template v-else>
<a :href="val.meta.isLink" target="_blank">
<a :href="val.meta.isLink" target="_blank" rel="opener">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
{{ $t(val.meta.title) }}
</a>

View File

@ -21,7 +21,7 @@
<span>{{ $t(val.meta.title) }}</span>
</template>
<template #title v-else>
<a :href="val.meta.isLink" target="_blank">{{ $t(val.meta.title) }}</a></template
<a :href="val.meta.isLink" target="_blank" rel="opener">{{ $t(val.meta.title) }}</a></template
>
</el-menu-item>
</template>

View File

@ -1,13 +1,13 @@
<template>
<div class="layout-view-bg-white flex layout-view-link" :style="{ height: `calc(100vh - ${setLinkHeight}` }">
<a href="https://element-plus.gitee.io/#/zh-CN/component/installation" target="_blank" class="flex-margin"
>{{ currentRouteMeta.title }}{{ currentRouteMeta.isLink }}</a
<a :href="currentRouteMeta.isLink" target="_blank" rel="opener" class="flex-margin"
>{{ $t(currentRouteMeta.title) }}{{ currentRouteMeta.isLink }}</a
>
</div>
</template>
<script lang="ts">
import { defineComponent, toRefs, reactive, onMounted, computed } from 'vue';
import { defineComponent, toRefs, reactive, computed, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index';
export default defineComponent({
@ -18,20 +18,22 @@ export default defineComponent({
const state = reactive({
currentRouteMeta: {},
});
// 初始化获取当前路由 meta
const initGetMeta = () => {
state.currentRouteMeta = route.meta;
};
// 设置 link 的高度
const setLinkHeight = computed(() => {
let { isTagsview } = store.state.themeConfig.themeConfig;
if (isTagsview) return `114px`;
else return `80px`;
});
// 页面加载时
onMounted(() => {
initGetMeta();
});
// 监听路由的变化,设置内容
watch(
() => route.path,
() => {
state.currentRouteMeta = route.meta;
},
{
immediate: true,
}
);
return {
setLinkHeight,
...toRefs(state),

View File

@ -198,7 +198,7 @@ router.beforeEach(async (to, from, next) => {
NProgress.done();
} else {
if (!token) {
next(`/login?redirect=${to.path}`);
next(`/login?redirect=${to.path}&params=${JSON.stringify(to.query ? to.query : to.params)}`);
Session.clear();
resetRoute();
NProgress.done();

View File

@ -829,7 +829,7 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
isKeepAlive: true,
isAffix: false,
isIframe: false,
auth: ['admin', 'test'],
auth: ['admin'],
icon: 'iconfont icon-zhongduancanshu',
},
children: [
@ -895,6 +895,54 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
},
],
},
{
path: '/visualizing',
name: 'visualizingIndex',
component: () => import('/@/layout/routerView/parent.vue'),
redirect: '/visualizing/visualizingLinkDemo1',
meta: {
title: 'message.router.visualizingIndex',
isLink: '',
isHide: false,
isKeepAlive: true,
isAffix: false,
isIframe: false,
auth: ['admin'],
icon: 'el-icon-data-line',
},
children: [
{
path: '/visualizing/visualizingLinkDemo1',
name: 'visualizingLinkDemo1',
component: () => import('/@/layout/routerView/link.vue'),
meta: {
title: 'message.router.visualizingLinkDemo1',
isLink: `${import.meta.env.VITE_API_URL}#/visualizingDemo1`,
isHide: false,
isKeepAlive: false,
isAffix: false,
isIframe: false,
auth: ['admin'],
icon: 'iconfont icon-caozuo-wailian',
},
},
{
path: '/visualizing/visualizingLinkDemo2',
name: 'visualizingLinkDemo2',
component: () => import('/@/layout/routerView/link.vue'),
meta: {
title: 'message.router.visualizingLinkDemo2',
isLink: `${import.meta.env.VITE_API_URL}#/visualizingDemo2`,
isHide: false,
isKeepAlive: false,
isAffix: false,
isIframe: false,
auth: ['admin'],
icon: 'iconfont icon-caozuo-wailian',
},
},
],
},
{
path: '/chart',
name: 'chartIndex',
@ -985,7 +1033,7 @@ export const staticRoutes: Array<RouteRecordRaw> = [
name: 'login',
component: () => import('/@/views/login/index.vue'),
meta: {
title: '登',
title: '登',
},
},
{
@ -993,7 +1041,7 @@ export const staticRoutes: Array<RouteRecordRaw> = [
name: 'notFound',
component: () => import('/@/views/error/404.vue'),
meta: {
title: '找不到此页面',
title: 'message.staticRoutes.notFound',
},
},
{
@ -1001,7 +1049,27 @@ export const staticRoutes: Array<RouteRecordRaw> = [
name: 'noPower',
component: () => import('/@/views/error/401.vue'),
meta: {
title: '没有权限',
title: 'message.staticRoutes.noPower',
},
},
/**
* 提示:写在这里的为全屏界面,不建议写在这里
* 请写在 `dynamicRoutes` 路由数组中
*/
{
path: '/visualizingDemo1',
name: 'visualizingDemo1',
component: () => import('/@/views/visualizing/demo1.vue'),
meta: {
title: 'message.router.visualizingLinkDemo1',
},
},
{
path: '/visualizingDemo2',
name: 'visualizingDemo2',
component: () => import('/@/views/visualizing/demo2.vue'),
meta: {
title: 'message.router.visualizingLinkDemo2',
},
},
];

View File

@ -1,6 +1,6 @@
// 字体图标 url
const cssCdnUrlList: Array<string> = [
'//at.alicdn.com/t/font_2298093_h8g6cpatww5.css',
'//at.alicdn.com/t/font_2298093_y6u00apwst.css',
'//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css',
];
// 第三方 js url

View File

@ -147,7 +147,14 @@ export default defineComponent({
// 登录成功,跳到转首页
// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
// 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
route.query?.redirect ? router.push(route.query.redirect) : router.push('/');
if (route.query?.redirect) {
router.push({
path: route.query?.redirect,
query: Object.keys(route.query?.params).length > 0 ? JSON.parse(route.query?.params) : '',
});
} else {
router.push('/');
}
// 登录成功提示
setTimeout(() => {
// 关闭 loading

View File

@ -0,0 +1,46 @@
<template>
<div class="login-scan-container">
<div class="login-scan-qrcode" ref="qrcodeRef"></div>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent, onMounted, getCurrentInstance } from 'vue';
import QRCode from 'qrcodejs2-fixes';
export default defineComponent({
name: 'login11',
setup() {
const { proxy } = getCurrentInstance() as any;
const state = reactive({});
// 初始化生成二维码
const initQrcode = () => {
proxy.$refs.qrcodeRef.innerHTML = '';
new QRCode(proxy.$refs.qrcodeRef, {
text: `https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi`,
width: 260,
height: 260,
colorDark: '#000000',
colorLight: '#ffffff',
});
};
// 页面加载时
onMounted(() => {
initQrcode();
});
return {
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.login-scan-container {
.login-scan-qrcode {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -40%);
}
}
</style>

View File

@ -6,21 +6,27 @@
<div class="login-content" :class="{ 'login-content-mobile': tabsActiveName === 'mobile' }">
<div class="login-content-main">
<h4 class="login-content-title">{{ getThemeConfig.globalTitle }}后台模板</h4>
<el-tabs v-model="tabsActiveName" @tab-click="onTabsClick">
<el-tab-pane :label="$t('message.label.one1')" name="account" :disabled="tabsActiveName === 'account'">
<transition name="el-zoom-in-center">
<Account v-show="isTabPaneShow" />
</transition>
</el-tab-pane>
<el-tab-pane :label="$t('message.label.two2')" name="mobile" :disabled="tabsActiveName === 'mobile'">
<transition name="el-zoom-in-center">
<Mobile v-show="!isTabPaneShow" />
</transition>
</el-tab-pane>
</el-tabs>
<div class="mt10">
<el-button type="text" size="small">{{ $t('message.link.one3') }}</el-button>
<el-button type="text" size="small">{{ $t('message.link.two4') }}</el-button>
<div v-if="!isScan">
<el-tabs v-model="tabsActiveName" @tab-click="onTabsClick">
<el-tab-pane :label="$t('message.label.one1')" name="account" :disabled="tabsActiveName === 'account'">
<transition name="el-zoom-in-center">
<Account v-show="isTabPaneShow" />
</transition>
</el-tab-pane>
<el-tab-pane :label="$t('message.label.two2')" name="mobile" :disabled="tabsActiveName === 'mobile'">
<transition name="el-zoom-in-center">
<Mobile v-show="!isTabPaneShow" />
</transition>
</el-tab-pane>
</el-tabs>
<div class="mt10">
<el-button type="text" size="small">{{ $t('message.link.one3') }}</el-button>
<el-button type="text" size="small">{{ $t('message.link.two4') }}</el-button>
</div>
</div>
<Scan v-else />
<div class="login-content-main-sacn" @click="isScan = !isScan">
<i class="iconfont" :class="isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>
</div>
</div>
</div>
@ -35,15 +41,17 @@
import { toRefs, reactive, computed } from 'vue';
import Account from '/@/views/login/component/account.vue';
import Mobile from '/@/views/login/component/mobile.vue';
import Scan from '/@/views/login/component/scan.vue';
import { useStore } from '/@/store/index';
export default {
name: 'login',
components: { Account, Mobile },
components: { Account, Mobile, Scan },
setup() {
const store = useStore();
const state = reactive({
tabsActiveName: 'account',
isTabPaneShow: true,
isScan: false,
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
@ -108,6 +116,40 @@ export default {
white-space: nowrap;
}
}
.login-content-main-sacn {
position: absolute;
top: 0;
right: 0;
width: 51px;
height: 50px;
overflow: hidden;
cursor: pointer;
opacity: 0.7;
transition: all ease 0.3s;
&::before {
content: '';
position: absolute;
width: 0;
height: 0;
border-bottom: 50px solid #ffffff;
border-right: 50px solid transparent;
z-index: 2;
}
&:hover {
opacity: 1;
transition: all ease 0.3s;
}
i {
content: '';
width: 50px;
height: 50px;
position: absolute;
top: -2px;
right: 0px;
font-size: 50px;
z-index: 1;
}
}
}
.login-content-mobile {
height: 418px;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 KiB

View File

@ -0,0 +1,51 @@
// 地图模拟数据
export const echartsMapList: Array<object> = [
{ name: '深圳市人民政府', value: '100' },
{ name: '莲花山公园', value: '100' },
{ name: '世界之窗', value: '100' },
{ name: '华侨城欢乐谷', value: '100' },
{ name: '宝安区西乡', value: '100' },
];
// 地图经纬度数据
export const echartsMapData: object = {
: [114.064524, 22.549225],
: [114.0658, 22.560072],
: [113.979419, 22.540579],
: [113.986066, 22.548056],
西: [113.869053, 22.581714],
};
// 地图图片显示
export const echartsMapImgs: Array<object> = [
{
url: 'https://img1.baidu.com/it/u=4244861097,3561366422&fm=11&fmt=auto&gp=0.jpg',
name: '深圳市人民政府',
add: '深圳市福田区福中三路市民中心C区',
dec: '深圳市人民政府是根据《中华人民共和国地方各级人民代表大会和地方各级人民政府组织法》设立的,是深圳市人民代表大会的执行机关,是深圳市的国家行政机关。',
},
{
url: 'https://img1.baidu.com/it/u=3793608028,4006842751&fm=26&fmt=auto&gp=0.jpg',
name: '莲花山公园',
add: '广东省深圳市福田区莲花街道莲花北社区红荔路6030号',
dec: '莲花山公园筹建于1992年10月10日 1997年6月23日正式对外局部开放。',
},
{
url: 'https://img0.baidu.com/it/u=1406340112,1927292660&fm=26&fmt=auto&gp=0.jpg',
name: '世界之窗',
add: '深圳市南山区深南大道9037号',
dec: '这里,世界首座实景拍摄悬空式球幕影院“飞跃美利坚””,为游客提供集休闲放松于一体的都市时尚生活空间。',
},
{
url: 'https://img0.baidu.com/it/u=3042342330,902556630&fm=26&fmt=auto&gp=0.jpg',
name: '华侨城欢乐谷',
add: '广东省深圳市南山区沙河街道星河街社区侨城西街1号',
dec: '深圳欢乐谷注重满足人们参与、体验的新型诱游需求,营造出自然、清新、活泼、惊奇、热烈、刺激的休闲旅游氛围。',
},
{
url: 'https://img2.baidu.com/it/u=1075072079,1229283519&fm=11&fmt=auto&gp=0.jpg',
name: '宝安区西乡',
add: '西乡街道下辖25个社区',
dec: '西乡街道,隶属于广东省深圳市宝安区,位于宝安区西南部,东接石岩街道,南接新安街道,西至珠江口岸边,北接航城街道。',
},
];

View File

@ -0,0 +1,131 @@
// 顶部下来菜单
export const dropdownList: Array<object> = [
{
label: '广东省农业农村厅',
},
{
label: '广西省农业农村厅',
},
{
label: '四川省农业农村厅',
},
{
label: '湖北省农业农村厅',
},
{
label: '福建省农业农村厅',
},
{
label: '山东省农业农村厅',
},
{
label: '江西省农业农村厅',
},
];
// sky 天气
export const skyList: Array<object> = [
{
v1: '时间',
v2: '天气',
v3: '温度',
v4: '湿度',
v5: '降水概率',
v6: '风向',
v7: '风力',
type: 'title',
},
{
v1: '今天',
v2: 'el-icon-cloudy-and-sunny',
v3: '20°/26°',
v4: '80%',
v5: '50%',
v6: '东南风',
v7: '13m/s',
},
{
v1: '明天',
v2: 'el-icon-lightning',
v3: '20°/26°',
v4: '80%',
v5: '50%',
v6: '东南风',
v7: '13m/s',
},
{
v1: '后天',
v2: 'el-icon-sunny',
v3: '20°/26°',
v4: '80%',
v5: '50%',
v6: '东南风',
v7: '13m/s',
},
];
// 当前设置状态
export const dBtnList: Array<object> = [
{
v1: '地块A-灌溉',
v2: '阳光玫瑰种植',
v3: '126天',
v4: '设备在线',
},
{
v1: '地块B-收割',
v2: '阳光玫瑰种植',
v3: '360天',
v4: '设备预警',
},
];
// 当前设备监测
export const chartData4List: Array<object> = [
{
label: '温度',
},
{
label: '光照',
},
{
label: '湿度',
},
{
label: '风力',
},
{
label: '张力',
},
{
label: '气压',
},
];
// 3DEarth 地图周围按钮组
export const earth3DBtnList: Array<object> = [
{
topLevelClass: 'fixed-top',
icon: 'el-icon-s-marketing',
label: '环境监测',
type: 0,
},
{
topLevelClass: 'fixed-right',
icon: 'el-icon-s-cooperation',
label: '精准管理',
type: 1,
},
{
topLevelClass: 'fixed-bottom',
icon: 'el-icon-s-order',
label: '数据报表',
type: 2,
},
{
topLevelClass: 'fixed-left',
icon: 'el-icon-s-claim',
label: '产品追溯',
type: 3,
},
];