This commit is contained in:
chenxiaoxi
2021-04-21 14:46:48 +08:00
parent 2eb52eec59
commit 312057f04d
197 changed files with 18308 additions and 123 deletions
+2
View File
@@ -1,3 +1,5 @@
# 支持浏览器配置
> 1%
last 2 versions
not dead
+12 -3
View File
@@ -1,5 +1,14 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
# 编辑器配置
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
trim_trailing_whitespace = true
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
+1
View File
@@ -1,4 +1,5 @@
/**
* @author chuzhixin 1204505056@qq.com (不想保留author可删除)
* @description .eslintrc.js
*/
+12
View File
@@ -0,0 +1,12 @@
# .gitattributes
*.html text eol=lf
*.css text eol=lf
*.js text eol=lf
*.scss text eol=lf
*.vue text eol=lf
*.hbs text eol=lf
*.sh text eol=lf
*.md text eol=lf
*.json text eol=lf
*.yml text eol=lf
*.js linguist-language=vue
+13 -10
View File
@@ -1,23 +1,26 @@
# .gitignore
.DS_Store
node_modules
/dist
# local env files
dist
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn.lock
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
public/video
*.zip
*.7z
/src/layouts/components/zx-layouts
/zx-templates
/package-lock.json
/src/styles/themes/green.scss
/src/styles/themes/dark.scss
/src/styles/themes/glory.scss
+6
View File
@@ -0,0 +1,6 @@
/**
* @description stylelint
*/
module.exports = {
extends: ['stylelint-config-recess-order', 'stylelint-config-prettier'],
}
+1 -6
View File
@@ -1,4 +1,4 @@
# futian
# FOTON
## Project setup
```
@@ -15,10 +15,5 @@ npm run serve
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
+1
View File
@@ -1,4 +1,5 @@
/**
* @author chuzhixin 1204505056@qq.com (不想保留author可删除)
* @description babel.config
*/
module.exports = {
+41
View File
@@ -0,0 +1,41 @@
const data = [
{
title: 'Admin Pro 3.0付费版本已发布,增加多项贴心功能,点我提前体验',
url:
'https://chu1204505056.gitee.io/admin-pro?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci=',
},
{
title: 'Admin Plus 3.0内测版本已发布,增加多项贴心功能,点我提前体验',
url:
'https://chu1204505056.gitee.io/admin-plus?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci=',
},
{
title: 'vue-admin-beautifulantdv vue3.0版本已发布,点我提前体验',
url:
'http://vue-admin-beautiful.com/vue-admin-beautiful-antdv?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci=',
},
{
title: 'vue-admin-beautifulelement-plus vue3.0版本已发布,点我提前体验',
url:
'https://chu1204505056.gitee.io/admin-plus?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci=',
},
{
title:
'程序无国界,但程序员有国界,中国国家尊严不容挑衅,如果您在特殊时期继续购买HM、耐克、阿迪达斯等品牌那么您将无权继续使用Vab',
url:
'https://chu1204505056.gitee.io/admin-pro?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci=',
},
]
module.exports = [
{
url: '/ad/getList',
type: 'get',
response() {
return {
code: 200,
msg: 'success',
data,
}
},
},
]
+250
View File
@@ -0,0 +1,250 @@
const data = [
{
content: '在github上获得了第一个star,感恩一位名叫Bequiet2014的github用户',
timestamp: '2020-03-23',
},
{
content: '增加更换主题功能',
timestamp: '2020-04-10',
},
{
content: '大幅精简代码',
timestamp: '2020-04-14',
},
{
content: '修复群友反馈的bug',
timestamp: '2020-04-16',
},
{
content: '剔除maptalks',
timestamp: '2020-04-17',
},
{
content:
'换行符统一修改为lf 支持苹果 linux windows协同开发 强制开启最严格eslint规则 不要哭 严格是有好处的',
timestamp: '2020-04-17',
},
{
content: '彻底完成手机端适配,记录这一天熬夜到了晚上三点',
timestamp: '2020-04-18',
},
{
content:
'删除babel-polyfill 提高打包速度 减少压缩体积(放弃ie是这个项目做出的最伟大的决定)',
timestamp: '2020-04-18',
},
{
content: '源码精简至800k',
timestamp: '2020-04-19',
},
{
content: '添加视频播放器组件',
timestamp: '2020-04-20',
},
{
content: '修复路由懒加载 完善主题配色',
timestamp: '2020-04-22',
},
{
content: '修复全局axios拦截 加快动画展示效果 修改登录页样式',
timestamp: '2020-04-24',
},
{
content: '简化权限与登录逻辑 更新mockServer',
timestamp: '2020-04-25',
},
{
content: '优化登录退出逻辑 代码更清晰 退出不再重载网页 改为重载路由形式',
timestamp: '2020-04-26',
},
{
content: '无端的指责只会让我更加努力 修复sidebar 简化permission',
timestamp: '2020-04-28',
},
{
content: '又是一个深夜 实现了表格增删改查的一键生成',
timestamp: '2020-04-30',
},
{
content: '大幅优化tagsview标签动画',
timestamp: '2020-05-02',
},
{
content: '三种图标组件实现mock模拟分页',
timestamp: '2020-05-03',
},
{
content: '添加了markdown编辑器组件',
timestamp: '2020-05-04',
},
{
content: '添加stylelint-plus自动规整排序样式',
timestamp: '2020-05-06',
},
{
content: '添加商城模板',
timestamp: '2020-05-12',
},
{
content: 'github标星超过1000 感恩',
timestamp: '2020-05-13',
},
{
content: '添加验证码组件',
timestamp: '2020-05-14',
},
{
content: '修复横向菜单bug',
timestamp: '2020-05-16',
},
{
content: '又被人骂了 挺好的 让我下定决心重写了tabsBar',
timestamp: '2020-05-20',
},
{
content: '仿ant-design 添加雪花屏',
timestamp: '2020-05-26',
},
{
content: '添加人员管理模块',
timestamp: '2020-06-02',
},
{
content: 'github标星超过2000 感恩',
timestamp: '2020-06-03',
},
{
content: '添加炫酷地图组件',
timestamp: '2020-06-11',
},
{
content: '抽离更多公共配置,框架使用更顺手',
timestamp: '2020-06-19',
},
{
content: '彻底完成了tabsBar多标签页的重构',
timestamp: '2020-06-22',
},
{
content: '感恩github标星过3.0K 祝大家端午节快乐',
timestamp: '2020-06-25',
},
{
content: '彻底重构了SideBar与TopBar 大幅精简dom渲染逻辑 全球首发',
timestamp: '2020-06-25',
},
{
content: '添加菜单管理',
timestamp: '2020-07-7',
},
{
content: '首次采用sass-loader 9.0写法,感谢github用户 shaonialife',
timestamp: '2020-07-7',
},
{
content: '添加vue-amap组件',
timestamp: '2020-07-11',
},
{
content: '修改zx-layouts引入方式',
timestamp: '2020-07-15',
},
{
content:
'记录这一天vue-admin-beautiful在插件市场、百度已花费超过1万元广告费用,希望一切都值得',
timestamp: '2020-07-18',
},
{
content: '主题配置添加绿荫草场、荣耀典藏、暗黑之子模式',
timestamp: '2020-07-18',
},
{
content: '全局axios请求全面支持Status Code拦截处理',
timestamp: '2020-07-29',
},
{
content: '重构全局loadding加载代码',
timestamp: '2020-07-31',
},
{
content: '升级stylelint自动排序功能',
timestamp: '2020-08-25',
},
{
content: '修复视频播放器组件重载路由失效的bug',
timestamp: '2020-09-03',
},
{
content: '修复极个别情况image-loader打包报错',
timestamp: '2020-09-18',
},
{
content: '全网首个基于vue3.0开发的admin框架已发布,具体请访问github',
timestamp: '2020-09-22',
},
{
content: '完善路由后端渲染方案,弃用之前写法',
timestamp: '2020-09-30',
},
{
content: '大版本迭代,请关注github tag',
timestamp: '2020-09-30',
},
/* {
content:
"面对自称加拿大华人的捕蛇者说作者laike9m的发帖诋毁,和人身攻击,本想去知乎找他理论,可奈何他的知乎已经由于经常发布错误言论被官方封号了https://www.zhihu.com/people/laike9m,既然自己都不是中国人了,就不要妄加揣测国产的开源项目了,一切都交给时间吧,后来我释怀了,laike9m、Cruii这群人不止是抹黑我的框架,甚至还去抹黑中国民营企业的佼佼者华为,充满了无奈啊,也许只有发布这样的言论,他们的目的才能达到把 https://github.com/evil-huawei/evil-huawei",
timestamp: "2020-10-01",
}, */
{
content: '凌晨两点,我累了,移除无用组件,精简package',
timestamp: '2020-10-02',
},
{
content: '富文本编辑器支持精细化配置',
timestamp: '2020-10-20',
},
{
content: '公共布局支持自动导出',
timestamp: '2020-10-23',
},
{
content: '遮罩添加高斯模糊',
timestamp: '2020-10-25',
},
{
content: 'vue3.0 + element-plus版本正式上线',
timestamp: '2020-12-5',
},
{
content: '完成首页重构,跟进echarts 5.0',
timestamp: '2020-12-7',
},
{
content: '更新sass版本至最新',
timestamp: '2021-1-7',
},
{
content: '更新vue-echarts版本至最新,options变更为option',
timestamp: '2021-2-23',
},
{
content:
'开源版品牌名升级为vue-admin-beautiful-pro,付费版vue2.x品牌升级为Admin Pro,付费版vue3.x品牌名升级为Admin Plus',
timestamp: '最近更新',
},
]
module.exports = [
{
url: '/changeLog/getList',
type: 'post',
response() {
return {
code: 200,
msg: 'success',
totalCount: 999,
data: data,
}
},
},
]
+324
View File
@@ -0,0 +1,324 @@
const data = [
'alphabetical_sorting',
'alphabetical_sorting',
'alarm_clock',
'area_chart',
'approval',
'answers',
'approve',
'assistant',
'automotive',
'automatic',
'bad_decision',
'bar_chart',
'bearish',
'biomass',
'biohazard',
'binoculars',
'bookmark',
'briefcase',
'biotech',
'broken_link',
'business',
'bullish',
'business_contact',
'businesswoman',
'cable_release',
'calculator',
'businessman',
'calendar',
'butting_in',
'call_transfer',
'callback',
'camcorder',
'camera',
'camcorder_pro',
'cancel',
'camera_addon',
'camera_identificatio',
'capacitor',
'candle_sticks',
'checkmark',
'circuit',
'charge_battery',
'clear_filters',
'clapperboard',
'clock',
'close_up_mode',
'collaboration',
'cell_phone',
'collapse',
'collect',
'cloth',
'combo_chart',
'comments',
'conference_call',
'compact_camera',
'contacts',
'copyleft',
'copyright',
'crystal_oscillator',
'cursor',
'currency_exchange',
'customer_support',
'dam',
'data_backup',
'data_configuration',
'data_encryption',
'data_protection',
'data_recovery',
'database',
'data_sheet',
'debt',
'decision',
'delete_column',
'delete_database',
'department',
'delete_row',
'deployment',
'dislike',
'disapprove',
'disclaimer',
'display',
'document',
'do_not_insert',
'do_not_mix',
'do_not_inhale',
'donate',
'down',
'doughnut_chart',
'down_left',
'down_right',
'download',
'edit_image',
'electrical_sensor',
'electrical_threshold',
'electricity',
'electro_devices',
'electronics',
'empty_battery',
'empty_filter',
'empty_trash',
'end_call',
'engineering',
'entering_heaven_aliv',
'expand',
'export',
'expired',
'factory',
'factory_breakdown',
'external',
'faq',
'feed_in',
'file',
'feedback',
'film',
'filled_filter',
'filing_cabinet',
'film_reel',
'flash_auto',
'fine_print',
'flash_off',
'flash_on',
'flow_chart',
'folder',
'frame',
'full_battery',
'full_trash',
'gallery',
'generic_sorting_asc',
'generic_sorting_desc',
'genealogy',
'globe',
'good_decision',
'headset',
'grid',
'graduation_cap',
'heat_map',
'high_priority',
'high_battery',
'image_file',
'home',
'idea',
'import',
'in_transit',
'integrated_webcam',
'inspection',
'invite',
'internal',
'ipad',
'info',
'iphone',
'kindle',
'key',
'landscape',
'left',
'left_down',
'left_up',
'leave',
'like_placeholder',
'light_at_the_end_of_',
'library',
'line_chart',
'link',
'like',
'lock',
'list',
'lock_landscape',
'low_battery',
'lock_portrait',
'low_priority',
'make_decision',
'medium_priority',
'manager',
'menu',
'middle_battery',
'minus',
'missed_call',
'mind_map',
'mms',
'multiple_cameras',
'money_transfer',
'music',
'multiple_devices',
'multiple_smartphones',
'multiple_inputs',
'negative_dynamic',
'neutral_decision',
'night_landscape',
'news',
'neutral_trading',
'night_portrait',
'no_idea',
'next',
'no_video',
'nook',
'ok',
'org_unit',
'opened_folder',
'old_time_camera',
'online_support',
'organization',
'package',
'paid',
'parallel_tasks',
'overtime',
'panorama',
'phone',
'phone_android',
'photo_reel',
'pie_chart',
'picture',
'planner',
'plus',
'podium_with_audience',
'podium_without_speak',
'podium_with_speaker',
'previous',
'portrait_mode',
'positive_dynamic',
'privacy',
'process',
'puzzle',
'questions',
'print',
'radar_plot',
'rating',
'ratings',
'reading',
'redo',
'reading_ebook',
'refresh',
'registered_trademark',
'right',
'reuse',
'remove_image',
'right_down',
'right_up',
'rotate_to_portrait',
'rules',
'rotate_camera',
'rotate_to_landscape',
'ruler',
'scatter_plot',
'search',
'safe',
'self_service_kiosk',
'selfie',
'serial_tasks',
'sales_performance',
'settings',
'services',
'share',
'shipped',
'sim_card',
'shop',
'service_mark',
'sim_card_chip',
'signature',
'smartphone_tablet',
'sound_recording_copy',
'sms',
'speaker',
'slr_back_side',
'start',
'stack_of_photos',
'statistics',
'sports_mode',
'support',
'synchronize',
'switch_camera',
'survey',
'tablet_android',
'template',
'trademark',
'todo_list',
'touchscreen_smartpho',
'timeline',
'tree_structure',
'undo',
'up_left',
'two_smartphones',
'unlock',
'up',
'up_right',
'upload',
'video_call',
'video_file',
'view_details',
'video_projector',
'vip',
'voice_presentation',
'webcam',
'voicemail',
'workflow',
'about',
'accept_database',
'add_image',
'add_column',
'add_database',
'add_row',
]
module.exports = [
{
url: '/colorfulIcon/getList',
type: 'post',
response(config) {
const { title, pageNo = 1, pageSize = 72 } = config.body
let mockList = data.filter((item) => {
if (title && item.indexOf(title) < 0) return false
return true
})
const pageList = mockList.filter(
(item, index) =>
index < pageSize * pageNo && index >= pageSize * (pageNo - 1)
)
return {
code: 200,
msg: 'success',
totalCount: mockList.length,
data: pageList,
}
},
},
]
+43
View File
@@ -0,0 +1,43 @@
const { mock } = require('mockjs')
const List = []
const count = 999
let num = 0
for (let i = 0; i < count; i++) {
List.push(
mock({
uuid: '@uuid',
image: `https://picsum.photos/300/600?random=${num++}`,
title: '@ctitle',
description: '@csentence',
link: 'https://www.baidu.com',
price: '@integer(100, 500)',
'status|1': [1, 0],
'isRecommend|1': [1, 0],
})
)
}
module.exports = [
{
url: '/goodsList/getList',
type: 'post',
response(config) {
const { title = '', pageNo = 1, pageSize = 20 } = config.body
let mockList = List.filter((item) => {
if (title && item.title.indexOf(title) < 0) return false
return true
})
const pageList = mockList.filter(
(item, index) =>
index < pageSize * pageNo && index >= pageSize * (pageNo - 1)
)
return {
code: 200,
msg: 'success',
totalCount: count,
data: pageList,
}
},
},
]
+985
View File
@@ -0,0 +1,985 @@
const data = [
'air-freshener',
'align-center',
'align-justify',
'align-left',
'align-right',
'allergies',
'ambulance',
'american-sign-language-interpreting',
'anchor',
'angle-double-down',
'angle-double-left',
'angle-double-right',
'angle-double-up',
'angle-down',
'angle-left',
'angle-right',
'angle-up',
'angry',
'ankh',
'apple-alt',
'archive',
'archway',
'arrow-alt-circle-down',
'arrow-alt-circle-left',
'arrow-alt-circle-right',
'arrow-alt-circle-up',
'arrow-circle-down',
'arrow-circle-left',
'arrow-circle-right',
'arrow-circle-up',
'arrow-down',
'arrow-left',
'arrow-right',
'arrow-up',
'arrows-alt',
'arrows-alt-h',
'arrows-alt-v',
'assistive-listening-systems',
'asterisk',
'at',
'atlas',
'atom',
'audio-description',
'award',
'baby',
'baby-carriage',
'backspace',
'backward',
'bacon',
'bahai',
'balance-scale',
'balance-scale-left',
'balance-scale-right',
'ban',
'band-aid',
'barcode',
'bars',
'baseball-ball',
'basketball-ball',
'bath',
'battery-empty',
'battery-full',
'battery-half',
'battery-quarter',
'battery-three-quarters',
'bed',
'beer',
'bell',
'bell-slash',
'bezier-curve',
'bible',
'bicycle',
'biking',
'binoculars',
'biohazard',
'birthday-cake',
'blender',
'blender-phone',
'blind',
'blog',
'bold',
'bolt',
'bomb',
'bone',
'bong',
'book',
'book-dead',
'book-medical',
'book-open',
'book-reader',
'bookmark',
'border-all',
'border-none',
'border-style',
'bowling-ball',
'box',
'box-open',
'boxes',
'braille',
'brain',
'bread-slice',
'briefcase',
'briefcase-medical',
'broadcast-tower',
'broom',
'brush',
'bug',
'building',
'bullhorn',
'bullseye',
'burn',
'bus',
'bus-alt',
'business-time',
'calculator',
'calendar',
'calendar-alt',
'calendar-check',
'calendar-day',
'calendar-minus',
'calendar-plus',
'calendar-times',
'calendar-week',
'camera',
'camera-retro',
'campground',
'candy-cane',
'cannabis',
'capsules',
'car',
'car-alt',
'car-battery',
'car-crash',
'car-side',
'caravan',
'caret-down',
'caret-left',
'caret-right',
'caret-square-down',
'caret-square-left',
'caret-square-right',
'caret-square-up',
'caret-up',
'carrot',
'cart-arrow-down',
'cart-plus',
'cash-register',
'cat',
'certificate',
'chair',
'chalkboard',
'chalkboard-teacher',
'charging-station',
'chart-area',
'chart-bar',
'chart-line',
'chart-pie',
'check',
'check-circle',
'check-double',
'check-square',
'cheese',
'chess',
'chess-bishop',
'chess-board',
'chess-king',
'chess-knight',
'chess-pawn',
'chess-queen',
'chess-rook',
'chevron-circle-down',
'chevron-circle-left',
'chevron-circle-right',
'chevron-circle-up',
'chevron-down',
'chevron-left',
'chevron-right',
'chevron-up',
'child',
'church',
'circle',
'circle-notch',
'city',
'clinic-medical',
'clipboard',
'clipboard-check',
'clipboard-list',
'clock',
'clone',
'closed-captioning',
'cloud',
'cloud-download-alt',
'cloud-meatball',
'cloud-moon',
'cloud-moon-rain',
'cloud-rain',
'cloud-showers-heavy',
'cloud-sun',
'cloud-sun-rain',
'cloud-upload-alt',
'cocktail',
'code',
'code-branch',
'coffee',
'cog',
'cogs',
'coins',
'columns',
'comment',
'comment-alt',
'comment-dollar',
'comment-dots',
'comment-medical',
'comment-slash',
'comments',
'comments-dollar',
'compact-disc',
'compass',
'compress',
'compress-alt',
'compress-arrows-alt',
'concierge-bell',
'cookie',
'cookie-bite',
'copy',
'copyright',
'couch',
'credit-card',
'crop',
'crop-alt',
'cross',
'crosshairs',
'crow',
'crown',
'crutch',
'cube',
'cubes',
'cut',
'database',
'deaf',
'democrat',
'desktop',
'dharmachakra',
'diagnoses',
'dice',
'dice-d20',
'dice-d6',
'dice-five',
'dice-four',
'dice-one',
'dice-six',
'dice-three',
'dice-two',
'digital-tachograph',
'directions',
'divide',
'dizzy',
'dna',
'dog',
'dollar-sign',
'dolly',
'dolly-flatbed',
'donate',
'door-closed',
'door-open',
'dot-circle',
'dove',
'download',
'drafting-compass',
'dragon',
'draw-polygon',
'drum',
'drum-steelpan',
'drumstick-bite',
'dumbbell',
'dumpster',
'dumpster-fire',
'dungeon',
'edit',
'egg',
'eject',
'ellipsis-h',
'ellipsis-v',
'envelope',
'envelope-open',
'envelope-open-text',
'envelope-square',
'equals',
'eraser',
'ethernet',
'euro-sign',
'exchange-alt',
'exclamation',
'exclamation-circle',
'exclamation-triangle',
'expand',
'expand-alt',
'expand-arrows-alt',
'external-link-alt',
'external-link-square-alt',
'eye',
'eye-dropper',
'eye-slash',
'fan',
'fast-backward',
'fast-forward',
'fax',
'feather',
'feather-alt',
'female',
'fighter-jet',
'file',
'file-alt',
'file-archive',
'file-audio',
'file-code',
'file-contract',
'file-csv',
'file-download',
'file-excel',
'file-export',
'file-image',
'file-import',
'file-invoice',
'file-invoice-dollar',
'file-medical',
'file-medical-alt',
'file-pdf',
'file-powerpoint',
'file-prescription',
'file-signature',
'file-upload',
'file-video',
'file-word',
'fill',
'fill-drip',
'film',
'filter',
'fingerprint',
'fire',
'fire-alt',
'fire-extinguisher',
'first-aid',
'fish',
'fist-raised',
'flag',
'flag-checkered',
'flag-usa',
'flask',
'flushed',
'folder',
'folder-minus',
'folder-open',
'folder-plus',
'font',
'football-ball',
'forward',
'frog',
'frown',
'frown-open',
'funnel-dollar',
'futbol',
'gamepad',
'gas-pump',
'gavel',
'gem',
'genderless',
'ghost',
'gift',
'gifts',
'glass-cheers',
'glass-martini',
'glass-martini-alt',
'glass-whiskey',
'glasses',
'globe',
'globe-africa',
'globe-americas',
'globe-asia',
'globe-europe',
'golf-ball',
'gopuram',
'graduation-cap',
'greater-than',
'greater-than-equal',
'grimace',
'grin',
'grin-alt',
'grin-beam',
'grin-beam-sweat',
'grin-hearts',
'grin-squint',
'grin-squint-tears',
'grin-stars',
'grin-tears',
'grin-tongue',
'grin-tongue-squint',
'grin-tongue-wink',
'grin-wink',
'grip-horizontal',
'grip-lines',
'grip-lines-vertical',
'grip-vertical',
'guitar',
'h-square',
'hamburger',
'hammer',
'hamsa',
'hand-holding',
'hand-holding-heart',
'hand-holding-usd',
'hand-lizard',
'hand-middle-finger',
'hand-paper',
'hand-peace',
'hand-point-down',
'hand-point-left',
'hand-point-right',
'hand-point-up',
'hand-pointer',
'hand-rock',
'hand-scissors',
'hand-spock',
'hands',
'hands-helping',
'handshake',
'hanukiah',
'hard-hat',
'hashtag',
'hat-cowboy',
'hat-cowboy-side',
'hat-wizard',
'hdd',
'heading',
'headphones',
'headphones-alt',
'headset',
'heart',
'heart-broken',
'heartbeat',
'helicopter',
'highlighter',
'hiking',
'hippo',
'history',
'hockey-puck',
'holly-berry',
'home',
'horse',
'horse-head',
'hospital',
'hospital-alt',
'hospital-symbol',
'hot-tub',
'hotdog',
'hotel',
'hourglass',
'hourglass-end',
'hourglass-half',
'hourglass-start',
'house-damage',
'hryvnia',
'i-cursor',
'ice-cream',
'icicles',
'icons',
'id-badge',
'id-card',
'id-card-alt',
'igloo',
'image',
'images',
'inbox',
'indent',
'industry',
'infinity',
'info',
'info-circle',
'italic',
'jedi',
'joint',
'journal-whills',
'kaaba',
'key',
'keyboard',
'khanda',
'kiss',
'kiss-beam',
'kiss-wink-heart',
'kiwi-bird',
'landmark',
'language',
'laptop',
'laptop-code',
'laptop-medical',
'laugh',
'laugh-beam',
'laugh-squint',
'laugh-wink',
'layer-group',
'leaf',
'lemon',
'less-than',
'less-than-equal',
'level-down-alt',
'level-up-alt',
'life-ring',
'lightbulb',
'link',
'lira-sign',
'list',
'list-alt',
'list-ol',
'list-ul',
'location-arrow',
'lock',
'lock-open',
'long-arrow-alt-down',
'long-arrow-alt-left',
'long-arrow-alt-right',
'long-arrow-alt-up',
'low-vision',
'luggage-cart',
'magic',
'magnet',
'mail-bulk',
'male',
'map',
'map-marked',
'map-marked-alt',
'map-marker',
'map-marker-alt',
'map-pin',
'map-signs',
'marker',
'mars',
'mars-double',
'mars-stroke',
'mars-stroke-h',
'mars-stroke-v',
'mask',
'medal',
'medkit',
'meh',
'meh-blank',
'meh-rolling-eyes',
'memory',
'menorah',
'mercury',
'meteor',
'microchip',
'microphone',
'microphone-alt',
'microphone-alt-slash',
'microphone-slash',
'microscope',
'minus',
'minus-circle',
'minus-square',
'mitten',
'mobile',
'mobile-alt',
'money-bill',
'money-bill-alt',
'money-bill-wave',
'money-bill-wave-alt',
'money-check',
'money-check-alt',
'monument',
'moon',
'mortar-pestle',
'mosque',
'motorcycle',
'mountain',
'mouse',
'mouse-pointer',
'mug-hot',
'music',
'network-wired',
'neuter',
'newspaper',
'not-equal',
'notes-medical',
'object-group',
'object-ungroup',
'oil-can',
'om',
'otter',
'outdent',
'pager',
'paint-brush',
'paint-roller',
'palette',
'pallet',
'paper-plane',
'paperclip',
'parachute-box',
'paragraph',
'parking',
'passport',
'pastafarianism',
'paste',
'pause',
'pause-circle',
'paw',
'peace',
'pen',
'pen-alt',
'pen-fancy',
'pen-nib',
'pen-square',
'pencil-alt',
'pencil-ruler',
'people-carry',
'pepper-hot',
'percent',
'percentage',
'person-booth',
'phone',
'phone-alt',
'phone-slash',
'phone-square',
'phone-square-alt',
'phone-volume',
'photo-video',
'piggy-bank',
'pills',
'pizza-slice',
'place-of-worship',
'plane',
'plane-arrival',
'plane-departure',
'play',
'play-circle',
'plug',
'plus',
'plus-circle',
'plus-square',
'podcast',
'poll',
'poll-h',
'poo',
'poo-storm',
'poop',
'portrait',
'pound-sign',
'power-off',
'pray',
'praying-hands',
'prescription',
'prescription-bottle',
'prescription-bottle-alt',
'print',
'procedures',
'project-diagram',
'puzzle-piece',
'qrcode',
'question',
'question-circle',
'quidditch',
'quote-left',
'quote-right',
'quran',
'radiation',
'radiation-alt',
'rainbow',
'random',
'receipt',
'record-vinyl',
'recycle',
'redo',
'redo-alt',
'registered',
'remove-format',
'reply',
'reply-all',
'republican',
'restroom',
'retweet',
'ribbon',
'ring',
'road',
'robot',
'rocket',
'route',
'rss',
'rss-square',
'ruble-sign',
'ruler',
'ruler-combined',
'ruler-horizontal',
'ruler-vertical',
'running',
'rupee-sign',
'sad-cry',
'sad-tear',
'satellite',
'satellite-dish',
'save',
'school',
'screwdriver',
'scroll',
'sd-card',
'search',
'search-dollar',
'search-location',
'search-minus',
'search-plus',
'seedling',
'server',
'shapes',
'share',
'share-alt',
'share-alt-square',
'share-square',
'shekel-sign',
'shield-alt',
'ship',
'shipping-fast',
'shoe-prints',
'shopping-bag',
'shopping-basket',
'shopping-cart',
'shower',
'shuttle-van',
'sign',
'sign-in-alt',
'sign-language',
'sign-out-alt',
'signal',
'signature',
'sim-card',
'sitemap',
'skating',
'skiing',
'skiing-nordic',
'skull',
'skull-crossbones',
'slash',
'sleigh',
'sliders-h',
'smile',
'smile-beam',
'smile-wink',
'smog',
'smoking',
'smoking-ban',
'sms',
'snowboarding',
'snowflake',
'snowman',
'snowplow',
'socks',
'solar-panel',
'sort',
'sort-alpha-down',
'sort-alpha-down-alt',
'sort-alpha-up',
'sort-alpha-up-alt',
'sort-amount-down',
'sort-amount-down-alt',
'sort-amount-up',
'sort-amount-up-alt',
'sort-down',
'sort-numeric-down',
'sort-numeric-down-alt',
'sort-numeric-up',
'sort-numeric-up-alt',
'sort-up',
'spa',
'space-shuttle',
'spell-check',
'spider',
'spinner',
'splotch',
'spray-can',
'square',
'square-full',
'square-root-alt',
'stamp',
'star',
'star-and-crescent',
'star-half',
'star-half-alt',
'star-of-david',
'star-of-life',
'step-backward',
'step-forward',
'stethoscope',
'sticky-note',
'stop',
'stop-circle',
'stopwatch',
'store',
'store-alt',
'stream',
'street-view',
'strikethrough',
'stroopwafel',
'subscript',
'subway',
'suitcase',
'suitcase-rolling',
'sun',
'superscript',
'surprise',
'swatchbook',
'swimmer',
'swimming-pool',
'synagogue',
'sync',
'sync-alt',
'syringe',
'table',
'table-tennis',
'tablet',
'tablet-alt',
'tablets',
'tachometer-alt',
'tag',
'tags',
'tape',
'tasks',
'taxi',
'teeth',
'teeth-open',
'temperature-high',
'temperature-low',
'tenge',
'terminal',
'text-height',
'text-width',
'th',
'th-large',
'th-list',
'theater-masks',
'thermometer',
'thermometer-empty',
'thermometer-full',
'thermometer-half',
'thermometer-quarter',
'thermometer-three-quarters',
'thumbs-down',
'thumbs-up',
'thumbtack',
'ticket-alt',
'times',
'times-circle',
'tint',
'tint-slash',
'tired',
'toggle-off',
'toggle-on',
'toilet',
'toilet-paper',
'toolbox',
'tools',
'tooth',
'torah',
'torii-gate',
'tractor',
'trademark',
'traffic-light',
'trailer',
'train',
'tram',
'transgender',
'transgender-alt',
'trash',
'trash-alt',
'trash-restore',
'trash-restore-alt',
'tree',
'trophy',
'truck',
'truck-loading',
'truck-monster',
'truck-moving',
'truck-pickup',
'tshirt',
'tty',
'tv',
'umbrella',
'umbrella-beach',
'underline',
'undo',
'undo-alt',
'universal-access',
'university',
'unlink',
'unlock',
'unlock-alt',
'upload',
'user',
'user-alt',
'user-alt-slash',
'user-astronaut',
'user-check',
'user-circle',
'user-clock',
'user-cog',
'user-edit',
'user-friends',
'user-graduate',
'user-injured',
'user-lock',
'user-md',
'user-minus',
'user-ninja',
'user-nurse',
'user-plus',
'user-secret',
'user-shield',
'user-slash',
'user-tag',
'user-tie',
'user-times',
'users',
'users-cog',
'utensil-spoon',
'utensils',
'vector-square',
'venus',
'venus-double',
'venus-mars',
'vial',
'vials',
'video',
'video-slash',
'vihara',
'voicemail',
'volleyball-ball',
'volume-down',
'volume-mute',
'volume-off',
'volume-up',
'vote-yea',
'vr-cardboard',
'walking',
'wallet',
'warehouse',
'water',
'wave-square',
'weight',
'weight-hanging',
'wheelchair',
'wifi',
'wind',
'window-close',
'window-maximize',
'window-minimize',
'window-restore',
'wine-bottle',
'wine-glass',
'wine-glass-alt',
'won-sign',
'wrench',
'x-ray',
'yen-sign',
'yin-yang',
]
module.exports = [
{
url: '/icon/getList',
type: 'post',
response(config) {
const { title, pageNo = 1, pageSize = 72 } = config.body
let mockList = data.filter((item) => {
if (title && item.indexOf(title) < 0) return false
return true
})
const pageList = mockList.filter(
(item, index) =>
index < pageSize * pageNo && index >= pageSize * (pageNo - 1)
)
return {
code: 200,
msg: 'success',
totalCount: mockList.length,
data: pageList,
}
},
},
]
+51
View File
@@ -0,0 +1,51 @@
module.exports = [
{
url: '/menuManagement/getTree',
type: 'post',
response() {
return {
code: 200,
msg: 'success',
totalCount: 999,
data: [
{
id: 'root',
label: '全部角色',
children: [
{
id: '@id',
permission: 'admin',
label: 'admin角色',
},
{
id: '@id',
permission: 'editor',
label: 'editor角色',
},
],
},
],
}
},
},
{
url: '/menuManagement/doEdit',
type: 'post',
response() {
return {
code: 200,
msg: '模拟保存成功',
}
},
},
{
url: '/menuManagement/doDelete',
type: 'post',
response() {
return {
code: 200,
msg: '模拟删除成功',
}
},
},
]
+33
View File
@@ -0,0 +1,33 @@
const data = [
{
title:
'温馨提示:集成版虽功能丰富,但冗余依赖过多,建议开发时使用基础版进行开发 Vab Admin Plusvue3.0 + element-plus)已发布,欢迎体验:<a target="_blank" href="https://chu1204505056.gitee.io/admin-plus/?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci=#/index">Vab Admin Plus</a>',
closable: false,
type: 'success',
},
{
title:
'作者寄语:感谢Star,感恩相遇,愿世间美好与我们环环相扣,加油!屏幕前的我们,打破桎梏,坚守初心。其实人生改变命运的机会并没有太多,我们并不是不优秀,我们也并不是一无是处,我们也希望驻足山巅被众人仰望,也许我们缺少的只是一个机会,缺少的只是生命中的导师,我希望这个框架帮助到更多的人,希望有一天,我们面试的时候不再胆怯,希望有一天别人看到的不仅仅是你的努力,还有你的功成名就,出人头地。',
closable: false,
type: 'warning',
},
{
title:
'随笔:我一直在寻找开源的真谛,我一直再想什么是开源,我一开始觉得免费就是开源,好像又不是。我理解的开源是:你也开源,我也开源,大家一起贡献,相互帮助。我最担心的事情是:我一个小人物,去伺候一众的伸手党,我想,这不是开源该有的氛围。我还太年轻,不懂什么是格局,我只知道,无私的帮助他人,能给我带来快乐,却不能给我带来收入,当然,有时候,快乐对我来说就已经足够了。可惜我是一个人,没有精力帮助到每一个人,可惜这个世界需要赚钱,才能过上平凡的生活,可惜了我的梦想,这个物欲横流的时代,理想主义的我们,即使内心坚决如铁,也似乎寸步难行。',
closable: false,
type: 'success',
},
]
module.exports = [
{
url: '/notice/getList',
type: 'post',
response() {
return {
code: 200,
msg: 'success',
data,
}
},
},
]
+42
View File
@@ -0,0 +1,42 @@
const { mock } = require('mockjs')
module.exports = [
{
url: '/personalCenter/getList',
type: 'post',
response(config) {
return {
code: 200,
msg: 'success',
totalCount: 999,
data: mock({
'data|10': [
{
id: '@id',
},
],
}).data,
}
},
},
{
url: '/personalCenter/doEdit',
type: 'post',
response() {
return {
code: 200,
msg: '模拟保存成功',
}
},
},
{
url: '/personalCenter/doDelete',
type: 'post',
response() {
return {
code: 200,
msg: '模拟删除成功',
}
},
},
]
File diff suppressed because it is too large Load Diff
+53
View File
@@ -0,0 +1,53 @@
const totalCount = 2
const List = [
{
id: '@id',
permission: 'admin',
},
{
id: '@id',
permission: 'editor',
},
]
module.exports = [
{
url: '/roleManagement/getList',
type: 'post',
response(config) {
const { title = '', pageNo = 1, pageSize = 20 } = config.body
let mockList = List.filter((item) => {
return !(title && item.title.indexOf(title) < 0)
})
const pageList = mockList.filter(
(item, index) =>
index < pageSize * pageNo && index >= pageSize * (pageNo - 1)
)
return {
code: 200,
msg: 'success',
totalCount,
data: pageList,
}
},
},
{
url: '/roleManagement/doEdit',
type: 'post',
response() {
return {
code: 200,
msg: '模拟保存成功',
}
},
},
{
url: '/roleManagement/doDelete',
type: 'post',
response() {
return {
code: 200,
msg: '模拟删除成功',
}
},
},
]
+298
View File
@@ -0,0 +1,298 @@
const data = [
{
path: '/',
component: 'Layout',
redirect: 'index',
children: [
{
path: 'index',
name: 'Index',
component: '@/views/index/index',
meta: {
title: '首页',
icon: 'home',
affix: true,
},
},
],
},
{
path: '/personnelManagement',
component: 'Layout',
redirect: 'noRedirect',
name: 'PersonnelManagement',
meta: { title: '人员', icon: 'users-cog', permissions: ['admin'] },
children: [
{
path: 'userManagement',
name: 'UserManagement',
component: '@/views/personnelManagement/userManagement/index',
meta: { title: '用户管理' },
},
{
path: 'roleManagement',
name: 'RoleManagement',
component: '@/views/personnelManagement/roleManagement/index',
meta: { title: '角色管理' },
},
{
path: 'menuManagement',
name: 'MenuManagement',
component: '@/views/personnelManagement/menuManagement/index',
meta: { title: '菜单管理', badge: 'New' },
},
],
},
{
path: '/vab',
component: 'Layout',
redirect: 'noRedirect',
name: 'Vab',
alwaysShow: true,
meta: { title: '组件', icon: 'cloud' },
children: [
{
path: 'permissions',
name: 'Permission',
component: '@/views/vab/permissions/index',
meta: {
title: '权限控制',
permissions: ['admin', 'editor'],
badge: 'New',
},
},
{
path: 'icon',
component: 'EmptyLayout',
redirect: 'noRedirect',
name: 'Icon',
meta: {
title: '图标',
permissions: ['admin'],
},
children: [
{
path: 'awesomeIcon',
name: 'AwesomeIcon',
component: '@/views/vab/icon/index',
meta: { title: '常规图标' },
},
{
path: 'remixIcon',
name: 'RemixIcon',
component: '@/views/vab/icon/remixIcon',
meta: { title: '小清新图标' },
},
{
path: 'colorfulIcon',
name: 'ColorfulIcon',
component: '@/views/vab/icon/colorfulIcon',
meta: { title: '多彩图标' },
},
],
},
{
path: 'table',
component: '@/views/vab/table/index',
name: 'Table',
meta: {
title: '表格',
permissions: ['admin'],
},
},
{
path: 'map',
name: 'Map',
component: '@/views/vab/map/index',
meta: { title: '地图', permissions: ['admin'], badge: 'Pro' },
},
{
path: 'webSocket',
name: 'WebSocket',
component: '@/views/vab/webSocket/index',
meta: { title: 'webSocket', permissions: ['admin'] },
},
{
path: 'form',
name: 'Form',
component: '@/views/vab/form/index',
meta: { title: '表单', permissions: ['admin'] },
},
{
path: 'element',
name: 'Element',
component: '@/views/vab/element/index',
meta: { title: '常用组件', permissions: ['admin'] },
},
{
path: 'tree',
name: 'Tree',
component: '@/views/vab/tree/index',
meta: { title: '树', permissions: ['admin'] },
},
{
path: 'verify',
name: 'Verify',
component: '@/views/vab/verify/index',
meta: { title: '验证码', permissions: ['admin'] },
},
{
path: 'menu1',
component: '@/views/vab/nested/menu1/index',
name: 'Menu1',
alwaysShow: true,
meta: {
title: '嵌套路由 1',
permissions: ['admin'],
},
children: [
{
path: 'menu1-1',
name: 'Menu1-1',
alwaysShow: true,
meta: { title: '嵌套路由 1-1' },
component: '@/views/vab/nested/menu1/menu1-1/index',
children: [
{
path: 'menu1-1-1',
name: 'Menu1-1-1',
meta: { title: '嵌套路由 1-1-1' },
component: '@/views/vab/nested/menu1/menu1-1/menu1-1-1/index',
},
],
},
],
},
{
path: 'magnifier',
name: 'Magnifier',
component: '@/views/vab/magnifier/index',
meta: { title: '放大镜', permissions: ['admin'] },
},
{
path: 'loading',
name: 'Loading',
component: '@/views/vab/loading/index',
meta: { title: 'loading', permissions: ['admin'] },
},
{
path: 'player',
name: 'Player',
component: '@/views/vab/player/index',
meta: { title: '视频播放器', permissions: ['admin'] },
},
{
path: 'markdownEditor',
name: 'MarkdownEditor',
component: '@/views/vab/markdownEditor/index',
meta: { title: 'markdown编辑器', permissions: ['admin'] },
},
{
path: 'editor',
name: 'Editor',
component: '@/views/vab/editor/index',
meta: { title: '富文本编辑器', permissions: ['admin'], badge: 'New' },
},
{
path: 'backToTop',
name: 'BackToTop',
component: '@/views/vab/backToTop/index',
meta: { title: '返回顶部', permissions: ['admin'] },
},
{
path: 'lodash',
name: 'Lodash',
component: '@/views/vab/lodash/index',
meta: { title: 'lodash', permissions: ['admin'] },
},
{
path: 'smallComponents',
name: 'SmallComponents',
component: '@/views/vab/smallComponents/index',
meta: { title: '小组件', permissions: ['admin'] },
},
{
path: 'upload',
name: 'Upload',
component: '@/views/vab/upload/index',
meta: { title: '上传', permissions: ['admin'] },
},
{
path: 'log',
name: 'Log',
component: '@/views/vab/errorLog/index',
meta: { title: '错误日志模拟', permissions: ['admin'] },
},
{
path: 'more',
name: 'More',
component: '@/views/vab/more/index',
meta: { title: '关于', permissions: ['admin'] },
},
],
},
{
path: '/mall',
component: 'Layout',
redirect: 'noRedirect',
name: 'Mall',
meta: {
title: '商城',
icon: 'shopping-cart',
permissions: ['admin'],
},
children: [
{
path: 'pay',
name: 'Pay',
component: '@/views/mall/pay/index',
meta: {
title: '支付',
noKeepAlive: true,
},
children: null,
},
{
path: 'goodsList',
name: 'GoodsList',
component: '@/views/mall/goodsList/index',
meta: {
title: '商品列表',
},
},
],
},
{
path: '/error',
component: 'EmptyLayout',
redirect: 'noRedirect',
name: 'Error',
meta: { title: '错误页', icon: 'bug' },
children: [
{
path: '401',
name: 'Error401',
component: '@/views/401',
meta: { title: '401' },
},
{
path: '404',
name: 'Error404',
component: '@/views/404',
meta: { title: '404' },
},
],
},
]
module.exports = [
{
url: '/menu/navigate',
type: 'post',
response() {
return { code: 200, msg: 'success', data: data }
},
},
]
+88
View File
@@ -0,0 +1,88 @@
const { mock } = require('mockjs')
const { handleRandomImage } = require('../utils')
const List = []
const count = 999
for (let i = 0; i < count; i++) {
List.push(
mock({
uuid: '@uuid',
id: '@id',
title: '@csentence(1, 2)',
'status|1': ['published', 'draft', 'deleted'],
author: '@cname',
datetime: '@datetime',
pageViews: '@integer(300, 5000)',
img: handleRandomImage(200, 200),
smallImg: handleRandomImage(40, 40),
switch: '@boolean',
percent: '@integer(80,99)',
})
)
}
module.exports = [
{
url: '/table/getList',
type: 'post',
response(config) {
if (!config.body) {
return {
code: 200,
msg: 'success',
totalCount: count,
data: mock({
'data|50': [
{
id: '@id',
title: '@csentence(1, 2)',
'status|1': ['published', 'draft', 'deleted'],
author: '@cname',
datetime: '@datetime',
pageViews: '@integer(300, 5000)',
img: handleRandomImage(200, 200),
smallImg: handleRandomImage(40, 40),
switch: '@boolean',
percent: '@integer(80,99)',
},
],
}).data,
}
}
const { title = '', pageNo = 1, pageSize = 20 } = config.body
let mockList = List.filter((item) => {
return !(title && item.title.indexOf(title) < 0)
})
const pageList = mockList.filter(
(item, index) =>
index < pageSize * pageNo && index >= pageSize * (pageNo - 1)
)
return {
code: 200,
msg: 'success',
totalCount: count,
data: pageList,
}
},
},
{
url: '/table/doEdit',
type: 'post',
response() {
return {
code: 200,
msg: '模拟保存成功',
}
},
},
{
url: '/table/doDelete',
type: 'post',
response() {
return {
code: 200,
msg: '模拟删除成功',
}
},
},
]
+54
View File
@@ -0,0 +1,54 @@
const data = [
{
id: '1',
parentId: '0',
name: 'root',
title: 'root',
text: 'root',
value: '1',
rank: 1,
children: [
{
id: '32816b88ff72423f960e7d492a386131',
parentId: '1',
name: '一级',
title: '一级',
text: '一级',
value: '32816b88ff72423f960e7d492a386131',
rank: 2,
children: [
{
id: '9e11afc35d55475fb0bd3164b9684cbe',
parentId: '32816b88ff72423f960e7d492a386131',
name: '二级',
title: '二级',
text: '二级',
value: '9e11afc35d55475fb0bd3164b9684cbe',
rank: 3,
children: [
{
id: '4cc1b04635e4444292526c5391699077',
parentId: '9e11afc35d55475fb0bd3164b9684cbe',
name: '三级',
title: '三级',
text: '三级',
value: '4cc1b04635e4444292526c5391699077',
rank: 4,
children: [],
},
],
},
],
},
],
},
]
module.exports = [
{
url: '/tree/list',
type: 'post',
response() {
return { code: 200, msg: 'success', data }
},
},
]
+95
View File
@@ -0,0 +1,95 @@
const accessTokens = {
admin: 'admin-accessToken',
editor: 'editor-accessToken',
test: 'test-accessToken',
}
module.exports = [
{
url: '/publicKey',
type: 'post',
response() {
return {
code: 200,
msg: 'success',
data: {
mockServer: true,
publicKey:
'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBT2vr+dhZElF73FJ6xiP181txKWUSNLPQQlid6DUJhGAOZblluafIdLmnUyKE8mMHhT3R+Ib3ssZcJku6Hn72yHYj/qPkCGFv0eFo7G+GJfDIUeDyalBN0QsuiE/XzPHJBuJDfRArOiWvH0BXOv5kpeXSXM8yTt5Na1jAYSiQ/wIDAQAB',
},
}
},
},
{
url: '/login',
type: 'post',
response(config) {
const { username } = config.body
const accessToken = accessTokens[username]
if (!accessToken) {
return {
code: 500,
msg: '帐户或密码不正确。',
}
}
return {
code: 200,
msg: 'success',
data: { accessToken },
}
},
},
{
url: '/register',
type: 'post',
response() {
return {
code: 200,
msg: '模拟注册成功',
}
},
},
{
url: '/userInfo',
type: 'post',
response(config) {
const { accessToken } = config.body
let permissions = ['admin']
let username = 'admin'
if ('admin-accessToken' === accessToken) {
permissions = ['admin']
username = 'admin'
}
if ('editor-accessToken' === accessToken) {
permissions = ['editor']
username = 'editor'
}
if ('test-accessToken' === accessToken) {
permissions = ['admin', 'editor']
username = 'test'
}
return {
code: 200,
msg: 'success',
data: {
permissions,
username,
'avatar|1': [
'https://i.gtimg.cn/club/item/face/img/2/15922_100.gif',
'https://i.gtimg.cn/club/item/face/img/8/15918_100.gif',
],
},
}
},
},
{
url: '/logout',
type: 'post',
response() {
return {
code: 200,
msg: 'success',
}
},
},
]
+70
View File
@@ -0,0 +1,70 @@
const totalCount = 3
const List = [
{
id: '@id',
username: 'admin',
password: 'admin',
email: '@email',
permissions: ['admin'],
datatime: '@datetime',
},
{
id: '@id',
username: 'editor',
password: 'editor',
email: '@email',
permissions: ['editor'],
datatime: '@datetime',
},
{
id: '@id',
username: 'test',
password: 'test',
email: '@email',
permissions: ['admin', 'editor'],
datatime: '@datetime',
},
]
module.exports = [
{
url: '/userManagement/getList',
type: 'post',
response(config) {
const { title = '', pageNo = 1, pageSize = 20 } = config.body
let mockList = List.filter((item) => {
if (title && item.title.indexOf(title) < 0) return false
return true
})
const pageList = mockList.filter(
(item, index) =>
index < pageSize * pageNo && index >= pageSize * (pageNo - 1)
)
return {
code: 200,
msg: 'success',
totalCount,
data: pageList,
}
},
},
{
url: '/userManagement/doEdit',
type: 'post',
response() {
return {
code: 200,
msg: '模拟保存成功',
}
},
},
{
url: '/userManagement/doDelete',
type: 'post',
response() {
return {
code: 200,
msg: '模拟删除成功',
}
},
},
]
+98
View File
@@ -0,0 +1,98 @@
const chokidar = require('chokidar')
const bodyParser = require('body-parser')
const chalk = require('chalk')
const path = require('path')
const { mock } = require('mockjs')
const { baseURL } = require('../src/config')
const mockDir = path.join(process.cwd(), 'mock')
const { handleMockArray } = require('./utils')
/**
*
* @param app
* @returns {{mockStartIndex: number, mockRoutesLength: number}}
*/
const registerRoutes = (app) => {
let mockLastIndex
const mocks = []
const mockArray = handleMockArray()
mockArray.forEach((item) => {
const obj = require(item)
mocks.push(...obj)
})
const mocksForServer = mocks.map((route) => {
return responseFake(route.url, route.type, route.response)
})
for (const mock of mocksForServer) {
app[mock.type](mock.url, mock.response)
mockLastIndex = app._router.stack.length
}
const mockRoutesLength = Object.keys(mocksForServer).length
return {
mockRoutesLength: mockRoutesLength,
mockStartIndex: mockLastIndex - mockRoutesLength,
}
}
/**
*
* @param url
* @param type
* @param respond
* @returns {{response(*=, *=): void, type: (*|string), url: RegExp}}
*/
const responseFake = (url, type, respond) => {
return {
url: new RegExp(`${baseURL}${url}`),
type: type || 'get',
response(req, res) {
res.status(200)
if (JSON.stringify(req.body) !== '{}') {
console.log(chalk.green(`> 请求地址:${req.path}`))
console.log(chalk.green(`> 请求参数:${JSON.stringify(req.body)}\n`))
} else {
console.log(chalk.green(`> 请求地址:${req.path}\n`))
}
res.json(mock(respond instanceof Function ? respond(req, res) : respond))
},
}
}
/**
*
* @param app
*/
module.exports = (app) => {
app.use(bodyParser.json())
app.use(
bodyParser.urlencoded({
extended: true,
})
)
const mockRoutes = registerRoutes(app)
let mockRoutesLength = mockRoutes.mockRoutesLength
let mockStartIndex = mockRoutes.mockStartIndex
chokidar
.watch(mockDir, {
ignored: /mock-server/,
ignoreInitial: true,
})
.on('all', (event) => {
if (event === 'change' || event === 'add') {
try {
app._router.stack.splice(mockStartIndex, mockRoutesLength)
Object.keys(require.cache).forEach((item) => {
if (item.includes(mockDir)) {
delete require.cache[require.resolve(item)]
}
})
const mockRoutes = registerRoutes(app)
mockRoutesLength = mockRoutes.mockRoutesLength
mockStartIndex = mockRoutes.mockStartIndex
} catch (error) {
console.log(chalk.red(error))
}
}
})
}
+43
View File
@@ -0,0 +1,43 @@
const { Random } = require('mockjs')
const { join } = require('path')
const fs = require('fs')
/**
* @author chuzhixin 1204505056@qq.com (不想保留author可删除)
* @description 随机生成图片url。
* @param width
* @param height
* @returns {string}
*/
function handleRandomImage(width = 50, height = 50) {
return `https://picsum.photos/${width}/${height}?random=${Random.guid()}`
}
/**
* @author chuzhixin 1204505056@qq.com (不想保留author可删除)
* @description 处理所有 controller 模块,npm run serve时在node环境中自动输出controller文件夹下Mock接口,请勿修改。
* @returns {[]}
*/
function handleMockArray() {
const mockArray = []
const getFiles = (jsonPath) => {
const jsonFiles = []
const findJsonFile = (path) => {
const files = fs.readdirSync(path)
files.forEach((item) => {
const fPath = join(path, item)
const stat = fs.statSync(fPath)
if (stat.isDirectory() === true) findJsonFile(item)
if (stat.isFile() === true) jsonFiles.push(item)
})
}
findJsonFile(jsonPath)
jsonFiles.forEach((item) => mockArray.push(`./controller/${item}`))
}
getFiles('mock/controller')
return mockArray
}
module.exports = {
handleRandomImage,
handleMockArray,
}
+70 -17
View File
@@ -1,37 +1,90 @@
{
"name": "FOTON",
"name": "vue-admin-beautiful",
"version": "1.0.0",
"author": "91isoft",
"private": true,
"author": "vue-admin-beautiful",
"participants": [],
"homepage": "https://chu1204505056.gitee.io/vue-admin-beautiful",
"publishConfig": {
"registry": "https://npm.pkg.github.com/"
},
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
"lint": "vue-cli-service lint",
"lint:style": "stylelint-config-prettier-check"
},
"repository": {
"type": "git",
"url": "git+https://github.com/chuzhixin/vue-admin-beautiful.git"
},
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.{js,jsx,vue}": [
"vue-cli-service lint",
"git add"
]
},
"dependencies": {
"axios": "^0.21.1",
"core-js": "^3.6.5",
"clipboard": "^2.0.8",
"core-js": "^3.10.1",
"dayjs": "^1.10.4",
"jquery": "^3.6.0",
"echarts": "^5.1.0",
"echarts-wordcloud": "^2.0.0",
"element-ui": "^2.15.1",
"jsencrypt": "^3.1.0",
"lodash": "^4.17.21",
"moment": "^2.29.1",
"maptalks": "^0.49.3",
"mapv": "^2.0.62",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
"qs": "^6.10.1",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
"screenfull": "^5.1.0",
"sortablejs": "^1.13.0",
"vab-icon": "^0.0.1",
"vue": "^2.6.12",
"vue-echarts": "5.0.0-beta.0",
"vue-router": "^3.5.1",
"vuex": "^3.6.2",
"zx-count": "^0.3.7",
"zx-layouts": "^0.6.24",
"zx-magnifie": "^0.4.0",
"zx-markdown-editor": "^0.0.2",
"zx-player": "^1.0.2",
"zx-quill": "^0.0.3",
"zx-templates": "^0.0.26",
"zx-verify": "^0.0.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/cli-plugin-babel": "^4.5.12",
"@vue/cli-plugin-eslint": "^4.5.12",
"@vue/cli-service": "^4.5.12",
"@vue/eslint-config-prettier": "^6.0.0",
"babel-eslint": "^10.1.0",
"body-parser": "^1.19.0",
"chalk": "^4.1.0",
"node-sass": "^4.12.0",
"sass-loader": "^8.0.2",
"chokidar": "^3.5.1",
"eslint": "^7.24.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-vue": "^7.9.0",
"filemanager-webpack-plugin": "^4.0.0",
"image-webpack-loader": "^7.0.1",
"lint-staged": "^10.5.4",
"plop": "^2.7.4",
"prettier": "^2.2.1",
"sass": "^1.32.10",
"sass-loader": "^10.1.1",
"stylelint": "^13.12.0",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-recess-order": "^2.4.0",
"vue-template-compiler": "^2.6.11"
"svg-sprite-loader": "^6.0.5",
"vue-template-compiler": "^2.6.12",
"webpackbar": "^4.0.0"
},
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
}
}
+21
View File
@@ -0,0 +1,21 @@
/**
* @author chuzhixin 1204505056@qq.com (不想保留author可删除)
* @description 代码规范
*/
module.exports = {
printWidth: 80,
tabWidth: 2,
useTabs: false,
semi: false,
singleQuote: true,
quoteProps: 'as-needed',
jsxSingleQuote: false,
trailingComma: 'es5',
bracketSpacing: true,
jsxBracketSameLine: false,
arrowParens: 'always',
htmlWhitespaceSensitivity: 'ignore',
vueIndentScriptAndStyle: true,
endOfLine: 'lf',
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+50 -24
View File
@@ -1,30 +1,56 @@
<!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">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="stylesheet" href="<%= BASE_URL %>static/css/loading.css"/>
<title>全球标准法规系统-福田</title>
</head>
<body>
<noscript>
<strong>您的浏览器不支持该系统使用,请更换或升级浏览器</strong>
</noscript>
<div id="foton">
<div class="first-loading-wrp">
<html lang="zh-cmn-Hans">
<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" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title><%= VUE_APP_TITLE %></title>
<meta
name="keywords"
content="vab,vab官网,后台管理框架,vue后台管理框架,vue-admin-beautiful-pro源码分享,vue-admin-beautiful-pro源码,vue-admin-beautiful官网,vue-admin-beautiful文档,vue-element-admin,vue-element-admin官网,vue-element-admin文档,vue-admin,vue-admin官网,vue-admin文档"
/>
<meta
name="description"
content="<%= VUE_APP_TITLE %>官网与文档基于vue-admin-beautiful构建,简称vab(是一款超棒的vue+element中后台前端快速开发框架),QQ群972435319,作者:<%= VUE_APP_AUTHOR %>"
/>
<meta name="author" content="<%= VUE_APP_AUTHOR %>" />
<link rel="stylesheet" href="<%= BASE_URL %>static/css/loading.css" />
</head>
<body>
<noscript>
非常抱歉鉴于安全考量,您无法查看<%= VUE_APP_TITLE %>
源代码,该系统基于vue-admin-beautiful开发
</noscript>
<div id="vue-admin-beautiful">
<div class="first-loading-wrp">
<div class="loading-wrp">
<span class="dot dot-spin">
<i></i>
<i></i>
<i></i>
<i></i>
</span>
<span class="dot dot-spin">
<i></i>
<i></i>
<i></i>
<i></i>
</span>
</div>
<h1><%= VUE_APP_TITLE %></h1>
</div>
</div>
</div>
<!-- built files will be auto injected -->
</body>
<script>
;/^http(s*):\/\//.test(location.href) ||
alert(
'基于vue-admin-beautiful-pro开源版开发的项目需要部署到服务器下访问'
)
</script>
<script>
if (window.location.hostname !== 'localhost') {
var _hmt = _hmt || []
;(function () {
var hm = document.createElement('script')
hm.src = 'https://hm.baidu.com/hm.js?7174bade1219f9cc272e7978f9523fc8'
var s = document.getElementsByTagName('script')[0]
s.parentNode.insertBefore(hm, s)
})()
}
</script>
</body>
</html>
+8 -5
View File
@@ -1,9 +1,12 @@
<template>
<div id="foton">
<router-view/>
<div id="vue-admin-beautiful">
<router-view />
</div>
</template>
<style lang="scss">
</style>
<script>
export default {
name: 'App',
mounted() {},
}
</script>
+9
View File
@@ -0,0 +1,9 @@
import request from '@/utils/request'
export function getList(data) {
return request({
url: '/ad/getList',
method: 'get',
data,
})
}
+9
View File
@@ -0,0 +1,9 @@
import request from '@/utils/request'
export function getList(data) {
return request({
url: '/changeLog/getList',
method: 'post',
data,
})
}
+9
View File
@@ -0,0 +1,9 @@
import request from '@/utils/request'
export function getIconList(data) {
return request({
url: '/colorfulIcon/getList',
method: 'post',
data,
})
}
+20
View File
@@ -0,0 +1,20 @@
import request from 'axios'
export function getRepos(params) {
return request({
url: 'https://api.github.com/repos/chuzhixin/vue-admin-beautiful',
method: 'get',
params,
timeout: 10000,
})
}
export function getStargazers(params) {
return request({
url:
'https://api.github.com/repos/chuzhixin/vue-admin-beautiful/stargazers',
method: 'get',
params,
timeout: 10000,
})
}
+9
View File
@@ -0,0 +1,9 @@
import request from '@/utils/request'
export function getList(data) {
return request({
url: '/goodsList/getList',
method: 'post',
data,
})
}
+9
View File
@@ -0,0 +1,9 @@
import request from '@/utils/request'
export function getIconList(data) {
return request({
url: '/icon/getList',
method: 'post',
data,
})
}
+8
View File
@@ -0,0 +1,8 @@
import request from 'axios'
export function getList() {
return request({
url: 'https://cdn.jsdelivr.net/gh/prettier/prettier@master/docs/options.md',
method: 'get',
})
}
+25
View File
@@ -0,0 +1,25 @@
import request from '@/utils/request'
export function getTree(data) {
return request({
url: '/menuManagement/getTree',
method: 'post',
data,
})
}
export function doEdit(data) {
return request({
url: '/menuManagement/doEdit',
method: 'post',
data,
})
}
export function doDelete(data) {
return request({
url: '/menuManagement/doDelete',
method: 'post',
data,
})
}
+8
View File
@@ -0,0 +1,8 @@
import request from '@/utils/request'
export function getNoticeList() {
return request({
url: '/notice/getList',
method: 'post',
})
}
+25
View File
@@ -0,0 +1,25 @@
import request from '@/utils/request'
export function getList(data) {
return request({
url: '/personalCenter/getList',
method: 'post',
data,
})
}
export function doEdit(data) {
return request({
url: '/personalCenter/doEdit',
method: 'post',
data,
})
}
export function doDelete(data) {
return request({
url: '/personalCenter/doDelete',
method: 'post',
data,
})
}
+8
View File
@@ -0,0 +1,8 @@
import request from '@/utils/request'
export function getPublicKey() {
return request({
url: '/publicKey',
method: 'post',
})
}
+9
View File
@@ -0,0 +1,9 @@
import request from '@/utils/request'
export function getIconList(data) {
return request({
url: '/remixIcon/getList',
method: 'post',
data,
})
}
+25
View File
@@ -0,0 +1,25 @@
import request from '@/utils/request'
export function getList(data) {
return request({
url: '/roleManagement/getList',
method: 'post',
data,
})
}
export function doEdit(data) {
return request({
url: '/roleManagement/doEdit',
method: 'post',
data,
})
}
export function doDelete(data) {
return request({
url: '/roleManagement/doDelete',
method: 'post',
data,
})
}
+9
View File
@@ -0,0 +1,9 @@
import request from '@/utils/request'
export function getRouterList(data) {
return request({
url: '/menu/navigate',
method: 'post',
data,
})
}
+25
View File
@@ -0,0 +1,25 @@
import request from '@/utils/request'
export function getList(data) {
return request({
url: '/table/getList',
method: 'post',
data,
})
}
export function doEdit(data) {
return request({
url: '/table/doEdit',
method: 'post',
data,
})
}
export function doDelete(data) {
return request({
url: '/table/doDelete',
method: 'post',
data,
})
}
+9
View File
@@ -0,0 +1,9 @@
import request from '@/utils/request'
export function getTreeList(data) {
return request({
url: '/tree/list',
method: 'post',
data,
})
}
+38
View File
@@ -0,0 +1,38 @@
import request from '@/utils/request'
import { encryptedData } from '@/utils/encrypt'
import { loginRSA, tokenName } from '@/config'
export async function login(data) {
if (loginRSA) {
data = await encryptedData(data)
}
return request({
url: '/login',
method: 'post',
data,
})
}
export function getUserInfo(accessToken) {
return request({
url: '/userInfo',
method: 'post',
data: {
[tokenName]: accessToken,
},
})
}
export function logout() {
return request({
url: '/logout',
method: 'post',
})
}
export function register() {
return request({
url: '/register',
method: 'post',
})
}
+25
View File
@@ -0,0 +1,25 @@
import request from '@/utils/request'
export function getList(data) {
return request({
url: '/userManagement/getList',
method: 'post',
data,
})
}
export function doEdit(data) {
return request({
url: '/userManagement/doEdit',
method: 'post',
data,
})
}
export function doDelete(data) {
return request({
url: '/userManagement/doDelete',
method: 'post',
data,
})
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

+13
View File
@@ -0,0 +1,13 @@
const req = require.context('./svg', false, /\.svg$/),
requireAll = (requireContext) => {
/*let a = requireContext.keys().map(requireContext);
let arr = [];
for (let i = 0; i < a.length; i++) {
console.log();
let icon = a[i].default.id;
arr.push(icon);
}
console.log(JSON.stringify(arr));*/
return requireContext.keys().map(requireContext)
}
requireAll(req)
@@ -0,0 +1 @@
<svg class="icon" width="128" height="128" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M358.4 853.333H245.333l-23.466 64H147.2l121.6-324.266h61.867l119.466 324.266h-68.266l-23.467-64zm-98.133-57.6h81.066l-40.533-121.6-40.533 121.6zm4.266-418.133h162.134v53.333H179.2V390.4L341.333 160H179.2v-53.333h243.2v36.266L264.533 377.6z" fill="#2196F3"/><path d="M810.667 704V106.667h-85.334V704h-128L768 917.333 938.667 704z" fill="#546E7A"/></svg>

After

Width:  |  Height:  |  Size: 463 B

+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
width="550px" height="400px"
xml:space="preserve">
<g id="PathID_1" transform="matrix(10.7099, 0, 0, 10.7099, 76.4, 396.15)" opacity="1">
<path style="fill: #41b882; fill-opacity: 1;" d="M3.75 -36.65L18.4 -36.65Q22.75 -36.65 24.85 -36.25Q27 -35.9 28.7 -34.75Q30.4 -33.6 31.5 -31.7Q32.65 -29.8 32.65 -27.4Q32.65 -24.85 31.25 -22.7Q29.85 -20.55 27.5 -19.5Q30.85 -18.5 32.65 -16.15Q34.45 -13.8 34.45 -10.6Q34.45 -8.1 33.25 -5.75Q32.1 -3.4 30.1 -1.95Q28.1 -0.55 25.15 -0.25Q23.3 -0.05 16.2 0L3.75 0L3.75 -36.65M11.15 -30.55L11.15 -22.1L16 -22.1Q20.3 -22.1 21.35 -22.2Q23.25 -22.4 24.35 -23.5Q25.45 -24.6 25.45 -26.35Q25.45 -28.05 24.5 -29.1Q23.55 -30.2 21.7 -30.4Q20.6 -30.55 15.4 -30.55L11.15 -30.55M11.15 -16L11.15 -6.2L18 -6.2Q22 -6.2 23.05 -6.4Q24.7 -6.7 25.75 -7.85Q26.8 -9.05 26.8 -11Q26.8 -12.65 26 -13.8Q25.2 -14.95 23.65 -15.45Q22.15 -16 17.1 -16L11.15 -16" />
</g>
<g id="PathID_2" transform="matrix(10.7099, 0, 0, 10.7099, 76.4, 396.15)" opacity="1">
</g>
<g id="PathID_3" transform="matrix(5.31826, 0, 0, 2.59618, 172.9, 161.55)" opacity="1">
<path style="fill: #35495e; fill-opacity: 1;" d="M3.75 -36.65L17.25 -36.65Q21.8 -36.65 24.2 -35.95Q27.45 -35 29.75 -32.55Q32.05 -30.15 33.25 -26.6Q34.45 -23.1 34.45 -17.95Q34.45 -13.45 33.3 -10.15Q31.95 -6.15 29.4 -3.7Q27.45 -1.8 24.2 -0.75Q21.75 0 17.65 0L3.75 0L3.75 -36.65M11.15 -30.45L11.15 -6.2L16.65 -6.2Q19.75 -6.2 21.1 -6.55Q22.9 -6.95 24.1 -8Q25.3 -9.1 26.05 -11.55Q26.8 -14.05 26.8 -18.3Q26.8 -22.55 26.05 -24.8Q25.3 -27.1 23.95 -28.35Q22.6 -29.65 20.5 -30.1Q18.95 -30.45 14.45 -30.45L11.15 -30.45" />
</g>
<g id="PathID_4" transform="matrix(5.31826, 0, 0, 2.59618, 172.9, 161.55)" opacity="1">
</g>
<g id="PathID_5" transform="matrix(5.78477, 0, 0, 3.1825, 171.7, 333.8)" opacity="1">
<path style="fill: #35495e; fill-opacity: 1;" d="M3.75 -36.65L17.25 -36.65Q21.8 -36.65 24.2 -35.95Q27.45 -35 29.75 -32.55Q32.05 -30.15 33.25 -26.6Q34.45 -23.1 34.45 -17.95Q34.45 -13.45 33.3 -10.15Q31.95 -6.15 29.4 -3.7Q27.45 -1.8 24.2 -0.75Q21.75 0 17.65 0L3.75 0L3.75 -36.65M11.15 -30.45L11.15 -6.2L16.65 -6.2Q19.75 -6.2 21.1 -6.55Q22.9 -6.95 24.1 -8Q25.3 -9.1 26.05 -11.55Q26.8 -14.05 26.8 -18.3Q26.8 -22.55 26.05 -24.8Q25.3 -27.1 23.95 -28.35Q22.6 -29.65 20.5 -30.1Q18.95 -30.45 14.45 -30.45L11.15 -30.45" />
</g>
<g id="PathID_6" transform="matrix(5.78477, 0, 0, 3.1825, 171.7, 333.8)" opacity="1">
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

+201
View File
@@ -0,0 +1,201 @@
<template>
<div class="select-tree-template">
<el-select
v-model="selectValue"
:clearable="clearable"
:collapse-tags="selectType == 'multiple'"
:multiple="selectType == 'multiple'"
class="vab-tree-select"
value-key="id"
@clear="clearHandle"
@remove-tag="removeTag"
>
<el-option :value="selectKey">
<el-tree
id="treeOption"
ref="treeOption"
:current-node-key="currentNodeKey"
:data="treeOptions"
:default-checked-keys="defaultSelectedKeys"
:default-expanded-keys="defaultSelectedKeys"
:highlight-current="true"
:props="defaultProps"
:show-checkbox="selectType == 'multiple'"
node-key="id"
@check="checkNode"
@node-click="nodeClick"
></el-tree>
</el-option>
</el-select>
</div>
</template>
<script>
export default {
name: 'SelectTreeTemplate',
props: {
/* 树形结构数据 */
treeOptions: {
type: Array,
default: () => {
return []
},
},
/* 单选/多选 */
selectType: {
type: String,
default: () => {
return 'single'
},
},
/* 初始选中值key */
selectedKey: {
type: String,
default: () => {
return ''
},
},
/* 初始选中值name */
selectedValue: {
type: String,
default: () => {
return ''
},
},
/* 可做选择的层级 */
selectLevel: {
type: [String, Number],
default: () => {
return ''
},
},
/* 可清空选项 */
clearable: {
type: Boolean,
default: () => {
return true
},
},
},
data() {
return {
defaultProps: {
children: 'children',
label: 'name',
},
defaultSelectedKeys: [], //初始选中值数组
currentNodeKey: this.selectedKey,
selectValue:
this.selectType == 'multiple'
? this.selectedValue.split(',')
: this.selectedValue, //下拉框选中值label
selectKey:
this.selectType == 'multiple'
? this.selectedKey.split(',')
: this.selectedKey, //下拉框选中值value
}
},
mounted() {
const that = this
this.initTree()
},
methods: {
// 初始化树的值
initTree() {
const that = this
if (that.selectedKey) {
that.defaultSelectedKeys = that.selectedKey.split(',') // 设置默认展开
if (that.selectType == 'single') {
that.$refs.treeOption.setCurrentKey(that.selectedKey) // 设置默认选中
} else {
that.$refs.treeOption.setCheckedKeys(that.defaultSelectedKeys)
}
}
},
// 清除选中
clearHandle() {
const that = this
this.selectValue = ''
this.selectKey = ''
this.defaultSelectedKeys = []
this.currentNodeKey = ''
this.clearSelected()
if (that.selectType == 'single') {
that.$refs.treeOption.setCurrentKey('') // 设置默认选中
} else {
that.$refs.treeOption.setCheckedKeys([])
}
},
/* 清空选中样式 */
clearSelected() {
const allNode = document.querySelectorAll('#treeOption .el-tree-node')
allNode.forEach((element) => element.classList.remove('is-current'))
},
// select多选时移除某项操作
removeTag(val) {
this.$refs.treeOption.setCheckedKeys([])
},
// 点击叶子节点
nodeClick(data, node, el) {
if (data.rank >= this.selectLevel) {
this.selectValue = data.name
this.selectKey = data.id
}
},
// 节点选中操作
checkNode(data, node, el) {
const checkedNodes = this.$refs.treeOption.getCheckedNodes()
const keyArr = []
const valueArr = []
checkedNodes.forEach((item) => {
if (item.rank >= this.selectLevel) {
keyArr.push(item.id)
valueArr.push(item.name)
}
})
this.selectValue = valueArr
this.selectKey = keyArr
},
},
}
</script>
<style lang="scss" scoped>
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
height: auto;
max-height: 274px;
padding: 0;
overflow-y: auto;
}
.el-select-dropdown__item.selected {
font-weight: normal;
}
ul li > .el-tree .el-tree-node__content {
height: auto;
padding: 0 20px;
}
.el-tree-node__label {
font-weight: normal;
}
.el-tree > .is-current .el-tree-node__label {
font-weight: 700;
color: #409eff;
}
.el-tree > .is-current .el-tree-node__children .el-tree-node__label {
font-weight: normal;
color: #606266;
}
</style>
<style lang="scss">
/* .vab-tree-select{
.el-tag__close.el-icon-close{
width:0;
overflow:hidden;
}
} */
</style>
+191
View File
@@ -0,0 +1,191 @@
<template>
<div class="content">
<div class="g-container" :style="styleObj">
<div class="g-number">
<vab-count
:start-val="startVal"
:end-val="endVal"
:duration="duration"
:separator="separator"
:prefix="prefix"
:suffix="suffix"
:decimals="decimals"
/>
</div>
<div class="g-contrast">
<div class="g-circle"></div>
<ul class="g-bubbles">
<li v-for="(item, index) in 15" :key="index"></li>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'VabCharge',
props: {
styleObj: {
type: Object,
default: () => {
return {}
},
},
startVal: {
type: Number,
default: 0,
},
endVal: {
type: Number,
default: 100,
},
},
data() {
return {
decimals: 2,
prefix: '',
suffix: '%',
separator: ',',
duration: 3000,
}
},
created() {},
mounted() {},
methods: {},
}
</script>
<style lang="scss" scoped>
.content {
position: relative;
display: flex;
align-items: center; /* 垂直居中 */
justify-content: center; /* 水平居中 */
width: 100%;
background: #000;
.g-number {
position: absolute;
top: 27%;
z-index: 99;
width: 300px;
font-size: 32px;
color: #fff;
text-align: center;
}
.g-container {
position: relative;
width: 300px;
height: 400px;
margin: auto;
}
.g-contrast {
width: 300px;
height: 400px;
overflow: hidden;
background-color: #000;
filter: contrast(15) hue-rotate(0);
animation: hueRotate 10s infinite linear;
}
.g-circle {
position: relative;
box-sizing: border-box;
width: 300px;
height: 300px;
filter: blur(8px);
&::after {
position: absolute;
top: 40%;
left: 50%;
width: 200px;
height: 200px;
content: '';
background-color: #00ff6f;
border-radius: 42% 38% 62% 49% / 45%;
transform: translate(-50%, -50%) rotate(0);
animation: rotate 10s infinite linear;
}
&::before {
position: absolute;
top: 40%;
left: 50%;
z-index: 99;
width: 176px;
height: 176px;
content: '';
background-color: #000;
border-radius: 50%;
transform: translate(-50%, -50%);
}
}
.g-bubbles {
position: absolute;
bottom: 0;
left: 50%;
width: 100px;
height: 40px;
background-color: #00ff6f;
filter: blur(5px);
border-radius: 100px 100px 0 0;
transform: translate(-50%, 0);
}
li {
position: absolute;
background: #00ff6f;
border-radius: 50%;
}
@for $i from 0 through 15 {
li:nth-child(#{$i}) {
$width: 15 + random(15) + px;
top: 50%;
left: 15 + random(70) + px;
width: $width;
height: $width;
transform: translate(-50%, -50%);
animation: moveToTop
#{random(6) +
3}s
ease-in-out -#{random(5000) /
1000}s
infinite;
}
}
@keyframes rotate {
50% {
border-radius: 45% / 42% 38% 58% 49%;
}
100% {
transform: translate(-50%, -50%) rotate(720deg);
}
}
@keyframes moveToTop {
90% {
opacity: 1;
}
100% {
opacity: 0.1;
transform: translate(-50%, -180px);
}
}
@keyframes hueRotate {
100% {
filter: contrast(15) hue-rotate(360deg);
}
}
}
</style>
+313
View File
@@ -0,0 +1,313 @@
<template>
<div class="card" :style="styleObj">
<div class="card-borders">
<div class="border-top"></div>
<div class="border-right"></div>
<div class="border-bottom"></div>
<div class="border-left"></div>
</div>
<div class="card-content">
<el-image :src="avatar" class="avatar"></el-image>
<div class="username">{{ username }}</div>
<div class="social-icons">
<a
v-for="(item, index) in iconArray"
:key="index"
class="social-icon"
:href="item.url"
target="_blank"
>
<vab-icon :icon="['fas', item.icon]" />
</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'VabProfile',
props: {
styleObj: {
type: Object,
default: () => {
return {}
},
},
username: {
type: String,
default: '',
},
avatar: {
type: String,
default: '',
},
iconArray: {
type: Array,
default: () => {
return [
{ icon: 'bell', url: '' },
{ icon: 'bookmark', url: '' },
{ icon: 'cloud-sun', url: '' },
]
},
},
},
data() {
return {}
},
created() {},
mounted() {},
methods: {},
}
</script>
<style lang="scss" scoped>
.card {
--card-bg-color: hsl(240, 31%, 25%);
--card-bg-color-transparent: hsla(240, 31%, 25%, 0.7);
position: relative;
width: 100%;
height: 100%;
.card-borders {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
.border-top {
position: absolute;
top: 0;
width: 100%;
height: 2px;
background: var(--card-bg-color);
transform: translateX(-100%);
animation: slide-in-horizontal 0.8s cubic-bezier(0.645, 0.045, 0.355, 1)
forwards;
}
.border-right {
position: absolute;
right: 0;
width: 2px;
height: 100%;
background: var(--card-bg-color);
transform: translateY(100%);
animation: slide-in-vertical 0.8s cubic-bezier(0.645, 0.045, 0.355, 1)
forwards;
}
.border-bottom {
position: absolute;
bottom: 0;
width: 100%;
height: 2px;
background: var(--card-bg-color);
transform: translateX(100%);
animation: slide-in-horizontal-reverse 0.8s
cubic-bezier(0.645, 0.045, 0.355, 1) forwards;
}
.border-left {
position: absolute;
top: 0;
width: 2px;
height: 100%;
background: var(--card-bg-color);
transform: translateY(-100%);
animation: slide-in-vertical-reverse 0.8s
cubic-bezier(0.645, 0.045, 0.355, 1) forwards;
}
}
.card-content {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
padding: 40px 0 40px 0;
background: var(--card-bg-color-transparent);
opacity: 0;
transform: scale(0.6);
animation: bump-in 0.5s 0.8s forwards;
.avatar {
width: 80px;
height: 80px;
border: 1px solid $base-color-white;
border-radius: 50%;
opacity: 0;
transform: scale(0.6);
animation: bump-in 0.5s 1s forwards;
}
.username {
position: relative;
margin-top: 20px;
margin-bottom: 20px;
font-size: 26px;
color: transparent;
letter-spacing: 2px;
animation: fill-text-white 1.2s 2s forwards;
&::before {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
color: black;
content: '';
background: #35b9f1;
transform: scaleX(0);
transform-origin: left;
animation: slide-in-out 1.2s 1.2s cubic-bezier(0.75, 0, 0, 1) forwards;
}
}
.social-icons {
display: flex;
.social-icon {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 2.5em;
height: 2.5em;
margin: 0 15px;
color: white;
text-decoration: none;
border-radius: 50%;
@for $i from 1 through 3 {
&:nth-child(#{$i}) {
&::before {
animation-delay: 2s + 0.1s * $i;
}
&::after {
animation-delay: 2.1s + 0.1s * $i;
}
svg {
animation-delay: 2.2s + 0.1s * $i;
}
}
}
&::before,
&::after {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
content: '';
border-radius: inherit;
transform: scale(0);
}
&::before {
background: #f7f1e3;
animation: scale-in 0.5s cubic-bezier(0.75, 0, 0, 1) forwards;
}
&::after {
background: #2c3e50;
animation: scale-in 0.5s cubic-bezier(0.75, 0, 0, 1) forwards;
}
svg {
z-index: 99;
transform: scale(0);
animation: scale-in 0.5s cubic-bezier(0.75, 0, 0, 1) forwards;
}
}
}
}
}
@keyframes bump-in {
50% {
transform: scale(1.05);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes slide-in-horizontal {
50% {
transform: translateX(0);
}
to {
transform: translateX(100%);
}
}
@keyframes slide-in-horizontal-reverse {
50% {
transform: translateX(0);
}
to {
transform: translateX(-100%);
}
}
@keyframes slide-in-vertical {
50% {
transform: translateY(0);
}
to {
transform: translateY(-100%);
}
}
@keyframes slide-in-vertical-reverse {
50% {
transform: translateY(0);
}
to {
transform: translateY(100%);
}
}
@keyframes slide-in-out {
50% {
transform: scaleX(1);
transform-origin: left;
}
50.1% {
transform-origin: right;
}
100% {
transform: scaleX(0);
transform-origin: right;
}
}
@keyframes fill-text-white {
to {
color: white;
}
}
@keyframes scale-in {
to {
transform: scale(1);
}
}
</style>
+82
View File
@@ -0,0 +1,82 @@
<template>
<div class="content" :style="styleObj">
<div v-for="(item, index) in 200" :key="index" class="snow"></div>
</div>
</template>
<script>
export default {
name: 'VabSnow',
props: {
styleObj: {
type: Object,
default: () => {
return {}
},
},
},
data() {
return {}
},
created() {},
mounted() {},
methods: {},
}
</script>
<style lang="scss" scoped>
.content {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
background: radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%);
filter: drop-shadow(0 0 10px white);
}
@function random_range($min, $max) {
$rand: random();
$random_range: $min + floor($rand * (($max - $min) + 1));
@return $random_range;
}
.snow {
$total: 200;
position: absolute;
width: 10px;
height: 10px;
background: white;
border-radius: 50%;
@for $i from 1 through $total {
$random-x: random(1000000) * 0.0001vw;
$random-offset: random_range(-100000, 100000) * 0.0001vw;
$random-x-end: $random-x + $random-offset;
$random-x-end-yoyo: $random-x + ($random-offset / 2);
$random-yoyo-time: random_range(30000, 80000) / 100000;
$random-yoyo-y: $random-yoyo-time * 100vh;
$random-scale: random(10000) * 0.0001;
$fall-duration: random_range(10, 30) * 1s;
$fall-delay: random(30) * -1s;
&:nth-child(#{$i}) {
opacity: random(10000) * 0.0001;
transform: translate($random-x, -10px) scale($random-scale);
animation: fall-#{$i} $fall-duration $fall-delay linear infinite;
}
@keyframes fall-#{$i} {
#{percentage($random-yoyo-time)} {
transform: translate($random-x-end, $random-yoyo-y)
scale($random-scale);
}
to {
transform: translate($random-x-end-yoyo, 100vh) scale($random-scale);
}
}
}
}
</style>
+255
View File
@@ -0,0 +1,255 @@
<template>
<el-dialog
:before-close="handleClose"
:close-on-click-modal="false"
:title="title"
:visible.sync="dialogFormVisible"
width="909px"
>
<div class="upload">
<el-alert
:closable="false"
:title="`支持jpg、jpeg、png格式,单次可最多选择${limit}张图片,每张不可大于${size}M,如果大于${size}M会自动为您过滤`"
type="info"
></el-alert>
<br />
<el-upload
ref="upload"
:action="action"
:auto-upload="false"
:close-on-click-modal="false"
:data="data"
:file-list="fileList"
:headers="headers"
:limit="limit"
:multiple="true"
:name="name"
:on-change="handleChange"
:on-error="handleError"
:on-exceed="handleExceed"
:on-preview="handlePreview"
:on-progress="handleProgress"
:on-remove="handleRemove"
:on-success="handleSuccess"
accept="image/png, image/jpeg"
class="upload-content"
list-type="picture-card"
>
<i slot="trigger" class="el-icon-plus"></i>
<el-dialog
:visible.sync="dialogVisible"
append-to-body
title="查看大图"
>
<div>
<img :src="dialogImageUrl" alt="" width="100%" />
</div>
</el-dialog>
</el-upload>
</div>
<div
slot="footer"
class="dialog-footer"
style="position: relative; padding-right: 15px; text-align: right"
>
<div
v-if="show"
style="position: absolute; top: 10px; left: 15px; color: #999"
>
正在上传中... 当前上传成功数:{{ imgSuccessNum }} 当前上传失败数:{{
imgErrorNum
}}
</div>
<el-button type="primary" @click="handleClose">关闭</el-button>
<el-button
:loading="loading"
size="small"
style="margin-left: 10px"
type="success"
@click="submitUpload"
>
开始上传
</el-button>
</div>
</el-dialog>
</template>
<script>
import { baseURL, tokenName } from '@/config'
export default {
name: 'VabUpload',
props: {
url: {
type: String,
default: '/upload',
required: true,
},
name: {
type: String,
default: 'file',
required: true,
},
limit: {
type: Number,
default: 50,
required: true,
},
size: {
type: Number,
default: 1,
required: true,
},
},
data() {
return {
show: false,
loading: false,
dialogVisible: false,
dialogImageUrl: '',
action: 'https://vab-unicloud-3a9da9.service.tcloudbase.com/upload',
headers: {},
fileList: [],
picture: 'picture',
imgNum: 0,
imgSuccessNum: 0,
imgErrorNum: 0,
typeList: null,
title: '上传',
dialogFormVisible: false,
data: {},
}
},
computed: {
percentage() {
if (this.allImgNum == 0) return 0
return this.$baseLodash.round(this.imgNum / this.allImgNum, 2) * 100
},
},
methods: {
submitUpload() {
this.$refs.upload.submit()
},
handleProgress(event, file, fileList) {
this.loading = true
this.show = true
},
handleChange(file, fileList) {
if (file.size > 1048576 * this.size) {
fileList.map((item, index) => {
if (item === file) {
fileList.splice(index, 1)
}
})
this.fileList = fileList
} else {
this.allImgNum = fileList.length
}
},
handleSuccess(response, file, fileList) {
this.imgNum = this.imgNum + 1
this.imgSuccessNum = this.imgSuccessNum + 1
if (fileList.length === this.imgNum) {
setTimeout(() => {
this.$baseMessage(
`上传完成! 共上传${fileList.length}张图片`,
'success'
)
}, 1000)
}
setTimeout(() => {
this.loading = false
this.show = false
}, 1000)
},
handleError(err, file, fileList) {
this.imgNum = this.imgNum + 1
this.imgErrorNum = this.imgErrorNum + 1
this.$baseMessage(
`文件[${file.raw.name}]上传失败,文件大小为${this.$baseLodash.round(
file.raw.size / 1024,
0
)}KB`,
'error'
)
setTimeout(() => {
this.loading = false
this.show = false
}, 1000)
},
handleRemove(file, fileList) {
this.imgNum = this.imgNum - 1
this.allNum = this.allNum - 1
},
handlePreview(file) {
this.dialogImageUrl = file.url
this.dialogVisible = true
},
handleExceed(files, fileList) {
this.$baseMessage(
`当前限制选择 ${this.limit} 个文件,本次选择了
${files.length}
个文件`,
'error'
)
},
handleShow(data) {
this.title = '上传'
this.data = data
this.dialogFormVisible = true
},
handleClose() {
this.fileList = []
this.picture = 'picture'
this.allImgNum = 0
this.imgNum = 0
this.imgSuccessNum = 0
this.imgErrorNum = 0
/* if ("development" === process.env.NODE_ENV) {
this.api = process.env.VUE_APP_BASE_API;
} else {
this.api = `${window.location.protocol}//${window.location.host}`;
}
this.action = this.api + this.url; */
this.dialogFormVisible = false
},
},
}
</script>
<style lang="scss" scoped>
.upload {
height: 500px;
.upload-content {
.el-upload__tip {
display: block;
height: 30px;
line-height: 30px;
}
::v-deep {
.el-upload--picture-card {
width: 128px;
height: 128px;
margin: 3px 8px 8px 8px;
border: 2px dashed #c0ccda;
}
.el-upload-list--picture {
margin-bottom: 20px;
}
.el-upload-list--picture-card {
.el-upload-list__item {
width: 128px;
height: 128px;
margin: 3px 8px 8px 8px;
}
}
}
}
}
</style>
+6 -1
View File
@@ -1,3 +1,8 @@
/**
* @description 3个子配置,通用配置|主题配置|网络配置导出
*/
const setting = require('./setting.config')
const theme = require('./theme.config')
module.exports = Object.assign({}, setting, theme)
const network = require('./net.config')
const routes = require('./routes')
module.exports = Object.assign({}, setting, theme, network, routes)
+18
View File
@@ -0,0 +1,18 @@
/**
* @description 导出默认网路配置
**/
const network = {
//配后端数据的接收方式application/json;charset=UTF-8或者application/x-www-form-urlencoded;charset=UTF-8
contentType: 'application/json;charset=UTF-8',
//消息框消失时间
messageDuration: 3000,
//最长请求时间
requestTimeout: 5000,
//操作正常code,支持String、Array、int多种类型
successCode: [200, 0],
//登录失效code
invalidCode: 402,
//无权限code
noPermissionCode: 401,
}
module.exports = network
+88
View File
@@ -0,0 +1,88 @@
/**
* @author chuzhixin 1204505056@qq.com (不想保留author可删除)
* @description 路由守卫,目前两种模式:all模式与intelligence模式
*/
import router from '@/router'
import store from '@/store'
import VabProgress from 'nprogress'
import 'nprogress/nprogress.css'
import getPageTitle from '@/utils/pageTitle'
import {
authentication,
loginInterception,
progressBar,
recordRoute,
routesWhiteList,
} from '@/config'
VabProgress.configure({
easing: 'ease',
speed: 500,
trickleSpeed: 200,
showSpinner: false,
})
router.beforeResolve(async (to, from, next) => {
if (progressBar) VabProgress.start()
let hasToken = store.getters['user/accessToken']
if (!loginInterception) hasToken = true
store.dispatch('user/getUserInfo')
store.dispatch('routes/setAllRoutes')
next()
// if (hasToken) {
// if (to.path === '/login') {
// next({ path: '/' })
// if (progressBar) VabProgress.done()
// } else {
// const hasPermissions =
// store.getters['user/permissions'] &&
// store.getters['user/permissions'].length > 0
// if (hasPermissions) {
// next()
// } else {
// try {
// let permissions
// if (!loginInterception) {
// //settings.js loginInterception为false时,创建虚拟权限
// await store.dispatch('user/setPermissions', ['admin'])
// permissions = ['admin']
// } else {
// // permissions = await store.dispatch('user/getUserInfo')
// permissions = ['admin']
// }
//
// let accessRoutes = []
// if (authentication === 'intelligence') {
// accessRoutes = await store.dispatch('routes/setRoutes', permissions)
// } else if (authentication === 'all') {
// accessRoutes = await store.dispatch('routes/setAllRoutes')
// }
// router.addRoutes(accessRoutes)
// next({ ...to, replace: true })
// } catch {
// await store.dispatch('user/resetAccessToken')
// if (progressBar) VabProgress.done()
// }
// }
// }
// } else {
// if (routesWhiteList.indexOf(to.path) !== -1) {
// next()
// } else {
// if (recordRoute) {
// next(`/login?redirect=${to.path}`)
// } else {
// next('/login')
// }
//
// if (progressBar) VabProgress.done()
// }
// }
document.title = getPageTitle(to.meta.title)
})
router.afterEach(() => {
if (progressBar) VabProgress.done()
})
+293
View File
@@ -0,0 +1,293 @@
const data = [
{
path: '/',
component: 'Layout',
redirect: 'index',
children: [
{
path: 'index',
name: 'Index',
component: '@/views/index/index',
meta: {
title: '首页',
icon: 'home',
affix: true,
},
},
],
},
{
path: '/personnelManagement',
component: 'Layout',
redirect: 'noRedirect',
name: 'PersonnelManagement',
meta: { title: '人员', icon: 'users-cog', permissions: ['admin'] },
children: [
{
path: 'userManagement',
name: 'UserManagement',
component: '@/views/personnelManagement/userManagement/index',
meta: { title: '用户管理' },
},
{
path: 'roleManagement',
name: 'RoleManagement',
component: '@/views/personnelManagement/roleManagement/index',
meta: { title: '角色管理' },
},
{
path: 'menuManagement',
name: 'MenuManagement',
component: '@/views/personnelManagement/menuManagement/index',
meta: { title: '菜单管理', badge: 'New' },
},
],
},
{
path: '/vab',
component: 'Layout',
redirect: 'noRedirect',
name: 'Vab',
alwaysShow: true,
meta: { title: '组件', icon: 'cloud' },
children: [
{
path: 'permissions',
name: 'Permission',
component: '@/views/vab/permissions/index',
meta: {
title: '权限控制',
permissions: ['admin', 'editor'],
badge: 'New',
},
},
{
path: 'icon',
component: 'EmptyLayout',
redirect: 'noRedirect',
name: 'Icon',
meta: {
title: '图标',
permissions: ['admin'],
},
children: [
{
path: 'awesomeIcon',
name: 'AwesomeIcon',
component: '@/views/vab/icon/index',
meta: { title: '常规图标' },
},
{
path: 'remixIcon',
name: 'RemixIcon',
component: '@/views/vab/icon/remixIcon',
meta: { title: '小清新图标' },
},
{
path: 'colorfulIcon',
name: 'ColorfulIcon',
component: '@/views/vab/icon/colorfulIcon',
meta: { title: '多彩图标' },
},
],
},
{
path: 'table',
component: '@/views/vab/table/index',
name: 'Table',
meta: {
title: '表格',
permissions: ['admin'],
},
},
{
path: 'map',
name: 'Map',
component: '@/views/vab/map/index',
meta: { title: '地图', permissions: ['admin'], badge: 'Pro' },
},
{
path: 'webSocket',
name: 'WebSocket',
component: '@/views/vab/webSocket/index',
meta: { title: 'webSocket', permissions: ['admin'] },
},
{
path: 'form',
name: 'Form',
component: '@/views/vab/form/index',
meta: { title: '表单', permissions: ['admin'] },
},
{
path: 'element',
name: 'Element',
component: '@/views/vab/element/index',
meta: { title: '常用组件', permissions: ['admin'] },
},
{
path: 'tree',
name: 'Tree',
component: '@/views/vab/tree/index',
meta: { title: '树', permissions: ['admin'] },
},
{
path: 'verify',
name: 'Verify',
component: '@/views/vab/verify/index',
meta: { title: '验证码', permissions: ['admin'] },
},
{
path: 'menu1',
component: '@/views/vab/nested/menu1/index',
name: 'Menu1',
alwaysShow: true,
meta: {
title: '嵌套路由 1',
permissions: ['admin'],
},
children: [
{
path: 'menu1-1',
name: 'Menu1-1',
alwaysShow: true,
meta: { title: '嵌套路由 1-1' },
component: '@/views/vab/nested/menu1/menu1-1/index',
children: [
{
path: 'menu1-1-1',
name: 'Menu1-1-1',
meta: { title: '嵌套路由 1-1-1' },
component: '@/views/vab/nested/menu1/menu1-1/menu1-1-1/index',
},
],
},
],
},
{
path: 'magnifier',
name: 'Magnifier',
component: '@/views/vab/magnifier/index',
meta: { title: '放大镜', permissions: ['admin'] },
},
{
path: 'loading',
name: 'Loading',
component: '@/views/vab/loading/index',
meta: { title: 'loading', permissions: ['admin'] },
},
{
path: 'player',
name: 'Player',
component: '@/views/vab/player/index',
meta: { title: '视频播放器', permissions: ['admin'] },
},
{
path: 'markdownEditor',
name: 'MarkdownEditor',
component: '@/views/vab/markdownEditor/index',
meta: { title: 'markdown编辑器', permissions: ['admin'] },
},
{
path: 'editor',
name: 'Editor',
component: '@/views/vab/editor/index',
meta: { title: '富文本编辑器', permissions: ['admin'], badge: 'New' },
},
{
path: 'backToTop',
name: 'BackToTop',
component: '@/views/vab/backToTop/index',
meta: { title: '返回顶部', permissions: ['admin'] },
},
{
path: 'lodash',
name: 'Lodash',
component: '@/views/vab/lodash/index',
meta: { title: 'lodash', permissions: ['admin'] },
},
{
path: 'smallComponents',
name: 'SmallComponents',
component: '@/views/vab/smallComponents/index',
meta: { title: '小组件', permissions: ['admin'] },
},
{
path: 'upload',
name: 'Upload',
component: '@/views/vab/upload/index',
meta: { title: '上传', permissions: ['admin'] },
},
{
path: 'log',
name: 'Log',
component: '@/views/vab/errorLog/index',
meta: { title: '错误日志模拟', permissions: ['admin'] },
},
{
path: 'more',
name: 'More',
component: '@/views/vab/more/index',
meta: { title: '关于', permissions: ['admin'] },
},
],
},
{
path: '/mall',
component: 'Layout',
redirect: 'noRedirect',
name: 'Mall',
meta: {
title: '商城',
icon: 'shopping-cart',
permissions: ['admin'],
},
children: [
{
path: 'pay',
name: 'Pay',
component: '@/views/mall/pay/index',
meta: {
title: '支付',
noKeepAlive: true,
},
children: null,
},
{
path: 'goodsList',
name: 'GoodsList',
component: '@/views/mall/goodsList/index',
meta: {
title: '商品列表',
},
},
],
},
{
path: '/error',
component: 'EmptyLayout',
redirect: 'noRedirect',
name: 'Error',
meta: { title: '错误页', icon: 'bug' },
children: [
{
path: '401',
name: 'Error401',
component: '@/views/401',
meta: { title: '401' },
},
{
path: '404',
name: 'Error404',
component: '@/views/404',
meta: { title: '404' },
},
],
},
]
const routes = {
routes: data,
}
module.exports = routes
+56 -10
View File
@@ -8,19 +8,65 @@ const setting = {
outputDir: 'dist',
// 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录。
assetsDir: 'static',
// 开发环境每次保存时是否输出为eslint编译警告
lintOnSave: true,
// 进行编译的依赖
transpileDependencies: [],
baseURL:
process.env.NODE_ENV === 'development'
? '/'
: '/',
// 标题
title: 'FOTON',
// 开发环境端口号
// 默认的接口地址 如果是开发环境和生产环境走vab-mock-server,当然你也可以选择自己配置成需要的接口地址
baseURL: process.env.NODE_ENV === 'development' ? '/api/' : '/api/',
//标题 (包括初次加载雪花屏的标题 页面的标题 浏览器的标题)
title: '福田汽车',
//简写
abbreviation: 'vab',
//开发环境端口号
devPort: '8888',
// 版本号
//版本号
version: process.env.VUE_APP_VERSION,
// 需要自动注入并加载的模块
providePlugin: { maptalks: 'maptalks', 'window.maptalks': 'maptalks' }
//这一项非常重要!请务必保留MIT协议下package.json及copyright作者信息 即可免费商用,不遵守此项约定你将无法使用该框架,如需自定义版权信息请联系QQ1204505056
copyright: 'vab',
//是否显示页面底部自定义版权信息
footerCopyright: true,
//是否显示顶部进度条
progressBar: true,
//缓存路由的最大数量
keepAliveMaxNum: 99,
// 路由模式,可选值为 history 或 hash
routerMode: 'hash',
//不经过token校验的路由
routesWhiteList: ['/login', '/register', '/404', '/401'],
//加载时显示文字
loadingText: '正在加载中...',
//token名称
tokenName: 'accessToken',
//token在localStorage、sessionStorage存储的key的名称
tokenTableName: 'vue-admin-beautiful-2021',
//token存储位置localStorage sessionStorage
storage: 'localStorage',
//token失效回退到登录页时是否记录本次的路由
recordRoute: true,
//是否显示logo,不显示时设置false,显示时请填写remixIcon图标名称,暂时只支持设置remixIcon
logo: 'vuejs-fill',
//是否显示在页面高亮错误
errorLog: ['development', 'production'],
//是否开启登录拦截
loginInterception: true,
//是否开启登录RSA加密
loginRSA: true,
//intelligence和all两种方式,前者后端权限只控制permissions不控制view文件的import(前后端配合,减轻后端工作量),all方式完全交给后端前端只负责加载
authentication: 'intelligence',
//vertical布局时是否只保持一个子菜单的展开
uniqueOpened: true,
//vertical布局时默认展开的菜单path,使用逗号隔开建议只展开一个
defaultOopeneds: ['/vab'],
//需要加loading层的请求,防止重复提交
debounce: ['doEdit'],
//需要自动注入并加载的模块
providePlugin: { maptalks: 'maptalks', 'window.maptalks': 'maptalks' },
//npm run build时是否自动生成7z压缩包
build7z: false,
//代码生成机生成在view下的文件夹名称
templateFolder: 'project',
//是否显示终端donation打印
donation: true,
}
module.exports = setting
+6
View File
@@ -0,0 +1,6 @@
/**
* @description 3个子配置,通用配置|主题配置|网络配置
*/
//默认配置
const { setting, theme, network } = require('./')
module.exports = Object.assign({}, setting, theme, network)
+3
View File
@@ -0,0 +1,3 @@
<template>
<router-view />
</template>
@@ -0,0 +1,13 @@
import permissions from './permissions'
const install = function (Vue) {
Vue.directive('permissions', permissions)
}
if (window.Vue) {
window['permissions'] = permissions
Vue.use(install)
}
permissions.install = install
export default permissions
@@ -0,0 +1,13 @@
import store from '@/store'
export default {
inserted(element, binding) {
const { value } = binding
const permissions = store.getters['user/permissions']
if (value && value instanceof Array && value.length > 0) {
const hasPermission = permissions.some((role) => value.includes(role))
if (!hasPermission)
element.parentNode && element.parentNode.removeChild(element)
}
},
}
+70
View File
@@ -0,0 +1,70 @@
<!-- 滚动通知组件 -->
<template>
<div class="vab-ad">
<el-carousel
v-if="adList"
height="30px"
direction="vertical"
:autoplay="true"
:interval="3000"
indicator-position="none"
>
<el-carousel-item v-for="(item, index) in adList" :key="index">
<el-tag type="warning">通知</el-tag>
<a target="_blank" :href="item.url">{{ item.title }}</a>
</el-carousel-item>
</el-carousel>
</div>
</template>
<script>
import { getList } from '@/api/ad'
export default {
name: 'VabAd',
data() {
return {
nodeEnv: process.env.NODE_ENV,
adList: [],
}
},
created() {
this.fetchData()
},
methods: {
async fetchData() {
// const { data } = await getList()
const data = [
{
title: '这是我们项目的模板',
url:
'https://chu1204505056.gitee.io/admin-pro?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci=',
},
{
title: '这个是模板的滚动文字组件',
url:
'https://chu1204505056.gitee.io/admin-plus?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci=',
},
{
title: '这个可以用作通知',
url:
'https://chu1204505056.gitee.io/admin-plus?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci=',
},
]
this.adList = data
},
},
}
</script>
<style lang="scss" scoped>
.vab-ad {
height: 30px;
padding-right: $base-padding;
padding-left: $base-padding;
margin-bottom: -20px;
line-height: 30px;
cursor: pointer;
a {
color: #999;
}
}
</style>
@@ -0,0 +1,93 @@
<template>
<div v-if="routerView" class="app-main-container">
<vab-github-corner />
<transition mode="out-in" name="fade-transform">
<keep-alive :include="cachedRoutes" :max="keepAliveMaxNum">
<router-view :key="key" class="app-main-height" />
</keep-alive>
</transition>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import { copyright, footerCopyright, keepAliveMaxNum, title } from '@/config'
export default {
name: 'VabAppMain',
data() {
return {
show: false,
fullYear: new Date().getFullYear(),
copyright,
title,
keepAliveMaxNum,
routerView: true,
footerCopyright,
}
},
computed: {
...mapGetters({
visitedRoutes: 'tabsBar/visitedRoutes',
device: 'settings/device',
}),
cachedRoutes() {
const cachedRoutesArr = []
this.visitedRoutes.forEach((item) => {
if (!item.meta.noKeepAlive) {
cachedRoutesArr.push(item.name)
}
})
return cachedRoutesArr
},
key() {
return this.$route.path
},
},
watch: {
$route: {
handler(route) {
if ('mobile' === this.device) this.foldSideBar()
},
immediate: true,
},
},
created() {
//重载所有路由
this.$baseEventBus.$on('reload-router-view', () => {
this.routerView = false
this.$nextTick(() => {
this.routerView = true
})
})
},
mounted() {},
methods: {
...mapActions({
foldSideBar: 'settings/foldSideBar',
}),
},
}
</script>
<style lang="scss" scoped>
.app-main-container {
position: relative;
width: 100%;
overflow: hidden;
.vab-keel {
margin: $base-padding;
}
.app-main-height {
min-height: $base-app-main-height;
}
.footer-copyright {
min-height: 55px;
line-height: 55px;
color: rgba(0, 0, 0, 0.45);
text-align: center;
border-top: 1px dashed $base-border-color;
}
}
</style>
+105
View File
@@ -0,0 +1,105 @@
<template>
<el-dropdown @command="handleCommand">
<span class="avatar-dropdown">
<!--<el-avatar class="user-avatar" :src="avatar"></el-avatar>-->
<img class="user-avatar" :src="avatar" alt="" />
<div class="user-name">
{{ username }}
<i class="el-icon-arrow-down el-icon--right"></i>
</div>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="github">github地址</el-dropdown-item>
<el-dropdown-item command="gitee" divided>码云地址</el-dropdown-item>
<el-dropdown-item command="pro" divided>pro付费版地址</el-dropdown-item>
<el-dropdown-item command="plus" divided>plus付费版地址</el-dropdown-item>
<el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
import { mapGetters } from 'vuex'
import { recordRoute } from '@/config'
export default {
name: 'VabAvatar',
computed: {
...mapGetters({
avatar: 'user/avatar',
username: 'user/username',
}),
},
methods: {
handleCommand(command) {
switch (command) {
case 'logout':
this.logout()
break
case 'personalCenter':
this.personalCenter()
break
case 'github':
window.open('https://github.com/chuzhixin/vue-admin-beautiful')
break
case 'gitee':
window.open('https://gitee.com/chu1204505056/vue-admin-beautiful')
break
case 'pro':
window.open(
'https://chu1204505056.gitee.io/admin-pro/?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci='
)
break
case 'plus':
window.open(
'https://chu1204505056.gitee.io/admin-plus/?hmsr=homeAd&hmpl=&hmcu=&hmkw=&hmci='
)
}
},
personalCenter() {
this.$router.push('/personalCenter/personalCenter')
},
logout() {
this.$baseConfirm(
'您确定要退出' + this.$baseTitle + '吗?',
null,
async () => {
await this.$store.dispatch('user/logout')
if (recordRoute) {
const fullPath = this.$route.fullPath
this.$router.push(`/login?redirect=${fullPath}`)
} else {
this.$router.push('/login')
}
}
)
},
},
}
</script>
<style lang="scss" scoped>
.avatar-dropdown {
display: flex;
align-content: center;
align-items: center;
justify-content: center;
justify-items: center;
height: 50px;
padding: 0;
.user-avatar {
width: 40px;
height: 40px;
cursor: pointer;
border-radius: 50%;
}
.user-name {
position: relative;
margin-left: 5px;
margin-left: 5px;
cursor: pointer;
}
}
</style>
@@ -0,0 +1,63 @@
<template>
<el-breadcrumb class="breadcrumb-container" separator=">">
<el-breadcrumb-item v-for="item in list" :key="item.path">
{{ item.meta.title }}
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script>
export default {
name: 'VabBreadcrumb',
data() {
return {
list: this.getBreadcrumb(),
}
},
watch: {
$route() {
this.list = this.getBreadcrumb()
},
},
methods: {
getBreadcrumb() {
return this.$route.matched.filter(
(item) => item.name && item.meta.title
)
},
},
}
</script>
<style lang="scss" scoped>
.breadcrumb-container {
height: $base-nav-bar-height;
font-size: $base-font-size-default;
line-height: $base-nav-bar-height;
::v-deep {
.el-breadcrumb__item {
.el-breadcrumb__inner {
a {
display: flex;
float: left;
font-weight: normal;
color: #515a6e;
i {
margin-right: 3px;
}
}
}
&:last-child {
.el-breadcrumb__inner {
a {
color: #999;
}
}
}
}
}
}
</style>
@@ -0,0 +1,65 @@
<template>
<img
v-if="isExternal"
:src="styleExternalIcon"
class="svg-external-icon svg-icon"
v-on="$listeners"
/>
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
</template>
<script>
import { isExternal } from '@/utils/validate'
export default {
name: 'VabColorfulIcon',
props: {
iconClass: {
type: String,
required: true,
},
className: {
type: String,
default: '',
},
},
computed: {
isExternal() {
return isExternal(this.iconClass)
},
iconName() {
return `#colorful-icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
},
styleExternalIcon() {
return this.iconClass
},
},
}
</script>
<style lang="scss" scoped>
.svg-icon {
width: 1em;
height: 1em;
overflow: hidden;
vertical-align: -0.15em;
fill: currentColor;
&:hover {
opacity: 0.8;
}
}
.svg-external-icon {
display: inline-block;
}
</style>
@@ -0,0 +1,128 @@
<template>
<div v-if="errorLogs.length > 0">
<el-badge
:value="errorLogs.length"
@click.native="dialogTableVisible = true"
>
<el-button type="danger">
<vab-icon :icon="['fas', 'bug']" />
</el-button>
</el-badge>
<el-dialog
:visible.sync="dialogTableVisible"
append-to-body
width="70%"
title="vue-admin-beautiful异常捕获(温馨提示:错误必须解决)"
>
<el-table :data="errorLogs">
<el-table-column label="报错路由">
<template slot-scope="{ row }">
<a :href="row.url" target="_blank">
<el-tag type="success">{{ row.url }}</el-tag>
</a>
</template>
</el-table-column>
<el-table-column label="错误信息">
<template slot-scope="{ row }">
<el-tag type="danger">{{ decodeUnicode(row.err.message) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="错误详情" width="120">
<template slot-scope="scope">
<el-popover placement="top-start" trigger="hover">
<div style="color: red">
{{ scope.row.err.stack }}
</div>
<el-button slot="reference">查看</el-button>
</el-popover>
</template>
</el-table-column>
<el-table-column width="380" label="操作">
<template slot-scope="{ row }">
<a
v-for="(item, index) in searchList"
:key="index"
:href="item.url + decodeUnicode(row.err.message)"
target="_blank"
>
<el-button style="margin-left: 5px" type="primary">
<vab-icon :icon="['fas', 'search']" />
{{ item.title }}
</el-button>
</a>
</template>
</el-table-column>
</el-table>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogTableVisible = false"> </el-button>
<el-button type="danger" icon="el-icon-delete" @click="clearAll">
暂不显示
</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { abbreviation, title } from '@/config'
import { mapGetters } from 'vuex'
export default {
name: 'VabErrorLog',
data() {
return {
dialogTableVisible: false,
title: title,
abbreviation: abbreviation,
searchList: [
{
title: '百度搜索',
url: 'https://www.baidu.com/baidu?wd=',
},
{
title: '谷歌搜索',
url: 'https://www.google.com/search?q=',
},
{
title: 'Magi搜索',
url: 'https://magi.com/search?q=',
},
],
}
},
computed: {
...mapGetters({
errorLogs: 'errorLog/errorLogs',
}),
},
methods: {
clearAll() {
this.dialogTableVisible = false
this.$store.dispatch('errorLog/clearErrorLog')
},
decodeUnicode(str) {
str = str.replace(/\\/g, '%')
str = unescape(str)
str = str.replace(/%/g, '\\')
str = str.replace(/\\/g, '')
return str
},
},
}
</script>
<style lang="scss" scoped>
::v-deep {
.el-badge {
.el-button {
display: flex;
align-items: center;
justify-items: center;
height: 28px;
}
}
}
</style>
@@ -0,0 +1,53 @@
<template>
<span :title="isFullscreen ? '退出全屏' : '进入全屏'">
<vab-icon
:icon="[
'fas',
isFullscreen ? 'compress-arrows-alt' : 'expand-arrows-alt',
]"
@click="click"
></vab-icon>
</span>
</template>
<script>
import screenfull from 'screenfull'
export default {
name: 'VabFullScreenBar',
data() {
return {
isFullscreen: false,
}
},
mounted() {
this.init()
},
beforeDestroy() {
this.destroy()
},
methods: {
click() {
if (!screenfull.isEnabled) {
this.$baseMessage('开启全屏失败', 'error')
return false
}
screenfull.toggle()
this.$emit('refresh')
},
change() {
this.isFullscreen = screenfull.isFullscreen
},
init() {
if (screenfull.isEnabled) {
screenfull.on('change', this.change)
}
},
destroy() {
if (screenfull.isEnabled) {
screenfull.off('change', this.change)
}
},
},
}
</script>
@@ -0,0 +1,75 @@
<template>
<a
href="https://github.com/chuzhixin/vue-admin-beautiful"
target="_blank"
class="github-corner"
aria-label="View source on Github"
>
<svg
width="80"
height="80"
viewBox="0 0 250 250"
class="github-color"
aria-hidden="true"
>
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" />
<path
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
fill="currentColor"
style="transform-origin: 130px 106px"
class="octo-arm"
/>
<path
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
fill="currentColor"
class="octo-body"
/>
</svg>
</a>
</template>
<script>
export default {
name: 'VabGithubCorner',
}
</script>
<style lang="scss" scoped>
.github-corner {
position: absolute;
top: 0;
right: 0;
z-index: $base-z-index - 3;
.octo-arm {
animation: octocat-wave 560ms ease-in-out infinite;
}
&:hover {
.octo-arm {
animation: octocat-wave 560ms ease-in-out infinite;
}
}
.github-color {
color: #fff;
fill: $base-color-blue;
}
}
@keyframes octocat-wave {
0%,
100% {
transform: rotate(0);
}
20%,
60% {
transform: rotate(-25deg);
}
40%,
80% {
transform: rotate(100deg);
}
}
</style>
+92
View File
@@ -0,0 +1,92 @@
<template>
<div :class="'logo-container-' + layout">
<router-link to="/">
<!-- 这里是logo变更的位置 -->
<vab-remix-icon v-if="logo" class="logo" :icon-class="logo" />
<span
class="title"
:class="{ 'hidden-xs-only': layout === 'horizontal' }"
:title="title"
>
{{ title }}
</span>
</router-link>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'VabLogo',
data() {
return {
title: this.$baseTitle,
}
},
computed: {
...mapGetters({
logo: 'settings/logo',
layout: 'settings/layout',
}),
},
}
</script>
<style lang="scss" scoped>
@mixin container {
position: relative;
height: $base-top-bar-height;
overflow: hidden;
line-height: $base-top-bar-height;
background: $base-menu-background;
}
@mixin logo {
display: inline-block;
width: 28px;
height: 28px;
margin-right: 3px;
color: $base-title-color;
vertical-align: middle;
}
@mixin title {
display: inline-block;
overflow: hidden;
font-size: 18px;
line-height: 55px;
color: $base-title-color;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
}
.logo-container-horizontal {
@include container;
.logo {
@include logo;
}
.title {
@include title;
}
}
.logo-container-vertical {
@include container;
height: $base-logo-height;
line-height: $base-logo-height;
text-align: center;
.logo {
@include logo;
}
.title {
@include title;
max-width: calc(#{$base-left-menu-width} - 60px);
}
}
</style>
+136
View File
@@ -0,0 +1,136 @@
<template>
<div class="nav-bar-container">
<el-row :gutter="15">
<el-col :xs="4" :sm="12" :md="12" :lg="12" :xl="12">
<div class="left-panel">
<i
:class="collapse ? 'el-icon-s-unfold' : 'el-icon-s-fold'"
:title="collapse ? '展开' : '收起'"
class="fold-unfold"
@click="handleCollapse"
></i>
<vab-breadcrumb class="hidden-xs-only" />
</div>
</el-col>
<el-col :xs="20" :sm="12" :md="12" :lg="12" :xl="12">
<div class="right-panel">
<vab-error-log />
<vab-full-screen-bar @refresh="refreshRoute" />
<vab-theme-bar class="hidden-xs-only" />
<vab-icon
title="重载所有路由"
:pulse="pulse"
:icon="['fas', 'redo']"
@click="refreshRoute"
/>
<vab-avatar />
<!-- <vab-icon
title="退出系统"
:icon="['fas', 'sign-out-alt']"
@click="logout"
/>-->
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
export default {
name: 'VabNavBar',
data() {
return {
pulse: false,
}
},
computed: {
...mapGetters({
collapse: 'settings/collapse',
visitedRoutes: 'tabsBar/visitedRoutes',
device: 'settings/device',
routes: 'routes/routes',
}),
},
methods: {
...mapActions({
changeCollapse: 'settings/changeCollapse',
}),
handleCollapse() {
this.changeCollapse()
},
async refreshRoute() {
this.$baseEventBus.$emit('reload-router-view')
this.pulse = true
setTimeout(() => {
this.pulse = false
}, 1000)
},
},
}
</script>
<style lang="scss" scoped>
.nav-bar-container {
position: relative;
height: $base-nav-bar-height;
padding-right: $base-padding;
padding-left: $base-padding;
overflow: hidden;
user-select: none;
background: $base-color-white;
box-shadow: $base-box-shadow;
.left-panel {
display: flex;
align-items: center;
justify-items: center;
height: $base-nav-bar-height;
.fold-unfold {
color: $base-color-gray;
cursor: pointer;
}
::v-deep {
.breadcrumb-container {
margin-left: 10px;
}
}
}
.right-panel {
display: flex;
align-content: center;
align-items: center;
justify-content: flex-end;
height: $base-nav-bar-height;
::v-deep {
svg {
width: 1em;
height: 1em;
margin-right: 15px;
font-size: $base-font-size-small;
color: $base-color-gray;
cursor: pointer;
fill: $base-color-gray;
}
button {
svg {
margin-right: 0;
color: $base-color-white;
cursor: pointer;
fill: $base-color-white;
}
}
.el-badge {
margin-right: 15px;
}
}
}
}
</style>
@@ -0,0 +1,20 @@
<template>
<el-col :span="24">
<div class="bottom-panel">
<slot></slot>
</div>
</el-col>
</template>
<script>
export default {
name: 'VabQueryFormBottomPanel',
props: {},
data() {
return {}
},
created() {},
mounted() {},
methods: {},
}
</script>
@@ -0,0 +1,25 @@
<template>
<el-col :xs="24" :sm="24" :md="24" :lg="span" :xl="span">
<div class="left-panel">
<slot></slot>
</div>
</el-col>
</template>
<script>
export default {
name: 'VabQueryFormLeftPanel',
props: {
span: {
type: Number,
default: 14,
},
},
data() {
return {}
},
created() {},
mounted() {},
methods: {},
}
</script>
@@ -0,0 +1,25 @@
<template>
<el-col :xs="24" :sm="24" :md="24" :lg="span" :xl="span">
<div class="right-panel">
<slot></slot>
</div>
</el-col>
</template>
<script>
export default {
name: 'VabQueryFormRightPanel',
props: {
span: {
type: Number,
default: 10,
},
},
data() {
return {}
},
created() {},
mounted() {},
methods: {},
}
</script>
@@ -0,0 +1,20 @@
<template>
<el-col :span="24">
<div class="top-panel">
<slot></slot>
</div>
</el-col>
</template>
<script>
export default {
name: 'VabQueryFormTopPanel',
props: {},
data() {
return {}
},
created() {},
mounted() {},
methods: {},
}
</script>
@@ -0,0 +1,63 @@
<template>
<el-row :gutter="0" class="vab-query-form">
<slot></slot>
</el-row>
</template>
<script>
export default {
name: 'VabQueryForm',
props: {},
data() {
return {}
},
created() {},
mounted() {},
methods: {},
}
</script>
<style lang="scss" scoped>
@mixin panel {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-start;
}
.vab-query-form {
margin-bottom: 10px;
::v-deep {
.top-panel {
@include panel;
}
.bottom-panel {
@include panel;
padding-top: 14px;
border-top: 1px solid #dcdfe6;
}
.left-panel {
@include panel;
> .el-button,
.el-form-item {
margin: 5px;
}
}
.right-panel {
@include panel;
justify-content: flex-end;
.el-form-item {
margin: 5px;
}
}
}
}
</style>
@@ -0,0 +1,69 @@
<template>
<div
v-if="isExternal"
:style="styleExternalIcon"
class="svg-external-icon svg-icon"
v-on="$listeners"
/>
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
</template>
<script>
import { isExternal } from '@/utils/validate'
export default {
name: 'VabRemixIcon',
props: {
iconClass: {
type: String,
required: true,
},
className: {
type: String,
default: '',
},
},
computed: {
isExternal() {
return isExternal(this.iconClass)
},
iconName() {
return `#remix-icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
},
styleExternalIcon() {
return {
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`,
}
},
},
}
</script>
<style lang="scss" scoped>
.svg-icon {
width: 1.125em;
height: 1.125em;
overflow: hidden;
fill: currentColor;
&:hover {
opacity: 0.8;
}
}
.svg-external-icon {
display: inline-block;
background-color: currentColor;
mask-size: cover !important;
}
</style>
@@ -0,0 +1,84 @@
<template>
<el-menu-item :index="handlePath(routeChildren.path)" @click="handleLink">
<vab-icon
v-if="routeChildren.meta.icon"
:icon="['fas', routeChildren.meta.icon]"
class="vab-fas-icon"
/>
<span>{{ routeChildren.meta.title }}</span>
<el-tag
v-if="routeChildren.meta && routeChildren.meta.badge"
type="danger"
effect="dark"
>
{{ routeChildren.meta.badge }}
</el-tag>
</el-menu-item>
</template>
<script>
import { isExternal } from '@/utils/validate'
import path from 'path'
export default {
name: 'VabMenuItem',
props: {
routeChildren: {
type: Object,
default() {
return null
},
},
item: {
type: Object,
default() {
return null
},
},
fullPath: {
type: String,
default: '',
},
},
methods: {
handlePath(routePath) {
if (isExternal(routePath)) {
return routePath
}
if (isExternal(this.fullPath)) {
return this.fullPath
}
return path.resolve(this.fullPath, routePath)
},
handleLink() {
const routePath = this.routeChildren.path
const target = this.routeChildren.meta.target
if (target === '_blank') {
if (isExternal(routePath)) {
window.open(routePath)
} else if (isExternal(this.fullPath)) {
window.open(this.fullPath)
} else if (
this.$route.path !== path.resolve(this.fullPath, routePath)
) {
let routeData = this.$router.resolve(
path.resolve(this.fullPath, routePath)
)
window.open(routeData.href)
}
} else {
if (isExternal(routePath)) {
window.location.href = routePath
} else if (isExternal(this.fullPath)) {
window.location.href = this.fullPath
} else if (
this.$route.path !== path.resolve(this.fullPath, routePath)
) {
this.$router.push(path.resolve(this.fullPath, routePath))
}
}
},
},
}
</script>
@@ -0,0 +1,108 @@
<template>
<component
:is="menuComponent"
v-if="!item.hidden"
:item="item"
:full-path="fullPath"
:route-children="routeChildren"
>
<template v-if="item.children && item.children.length">
<vab-side-bar-item
v-for="route in item.children"
:key="route.path"
:full-path="handlePath(route.path)"
:item="route"
/>
</template>
</component>
</template>
<script>
import { isExternal } from '@/utils/validate'
import path from 'path'
export default {
name: 'VabSideBarItem',
props: {
item: {
type: Object,
required: true,
},
fullPath: {
type: String,
default: '',
},
},
data() {
this.onlyOneChild = null
return {}
},
computed: {
menuComponent() {
if (
this.handleChildren(this.item.children, this.item) &&
(!this.routeChildren.children ||
this.routeChildren.notShowChildren) &&
!this.item.alwaysShow
) {
return 'VabMenuItem'
} else {
return 'VabSubmenu'
}
},
},
methods: {
handleChildren(children = [], parent) {
if (children === null) children = []
const showChildren = children.filter((item) => {
if (item.hidden) {
return false
} else {
this.routeChildren = item
return true
}
})
if (showChildren.length === 1) {
return true
}
if (showChildren.length === 0) {
this.routeChildren = {
...parent,
path: '',
notShowChildren: true,
}
return true
}
return false
},
handlePath(routePath) {
if (isExternal(routePath)) {
return routePath
}
if (isExternal(this.fullPath)) {
return this.fullPath
}
return path.resolve(this.fullPath, routePath)
},
},
}
</script>
<style lang="scss" scoped>
.vab-nav-icon {
margin-right: 4px;
}
::v-deep {
.el-tag {
float: right;
height: 16px;
padding-right: 4px;
padding-left: 4px;
margin-top: calc((#{$base-menu-item-height} - 16px) / 2);
line-height: 16px;
border: 0;
}
}
</style>
@@ -0,0 +1,60 @@
<template>
<el-submenu
ref="subMenu"
:index="handlePath(item.path)"
:popper-append-to-body="false"
>
<template slot="title">
<vab-icon
v-if="item.meta && item.meta.icon"
:icon="['fas', item.meta.icon]"
class="vab-fas-icon"
/>
<vab-remix-icon
v-if="item.meta && item.meta.remixIcon"
:icon-class="item.meta.remixIcon"
class="vab-remix-icon"
/>
<span>{{ item.meta.title }}</span>
</template>
<slot />
</el-submenu>
</template>
<script>
import { isExternal } from '@/utils/validate'
import path from 'path'
export default {
name: 'VabSubmenu',
props: {
routeChildren: {
type: Object,
default() {
return null
},
},
item: {
type: Object,
default() {
return null
},
},
fullPath: {
type: String,
default: '',
},
},
methods: {
handlePath(routePath) {
if (isExternal(routePath)) {
return routePath
}
if (isExternal(this.fullPath)) {
return this.fullPath
}
return path.resolve(this.fullPath, routePath)
},
},
}
</script>
+141
View File
@@ -0,0 +1,141 @@
<template>
<el-scrollbar class="side-bar-container" :class="{ 'is-collapse': collapse }">
<vab-logo />
<el-menu
:background-color="variables['menu-background']"
:text-color="variables['menu-color']"
:active-text-color="variables['menu-color-active']"
:default-active="activeMenu"
:collapse="collapse"
:collapse-transition="false"
:default-openeds="defaultOpens"
:unique-opened="uniqueOpened"
mode="vertical"
>
<template v-for="route in routes">
<vab-side-bar-item
:key="route.path"
:full-path="route.path"
:item="route"
/>
</template>
</el-menu>
</el-scrollbar>
</template>
<script>
import variables from '@/styles/variables.scss'
import { mapGetters } from 'vuex'
import { defaultOopeneds, uniqueOpened } from '@/config'
export default {
name: 'VabSideBar',
data() {
return {
uniqueOpened,
}
},
computed: {
...mapGetters({
collapse: 'settings/collapse',
routes: 'routes/routes',
}),
defaultOpens() {
if (this.collapse) {
}
return defaultOopeneds
},
activeMenu() {
const route = this.$route
const { meta, path } = route
if (meta.activeMenu) {
return meta.activeMenu
}
return path
},
variables() {
return variables
},
},
}
</script>
<style lang="scss" scoped>
@mixin active {
&:hover {
color: $base-color-white;
background-color: $base-menu-background-active !important;
}
&.is-active {
color: $base-color-white;
background-color: $base-menu-background-active !important;
}
}
.side-bar-container {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: $base-z-index;
width: $base-left-menu-width;
height: 100vh;
overflow: hidden;
background: $base-menu-background;
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
transition: width $base-transition-time;
&.is-collapse {
width: $base-left-menu-width-min;
border-right: 0;
::v-deep {
.el-menu {
transition: width $base-transition-time;
}
.el-menu--collapse {
border-right: 0;
.el-submenu__icon-arrow {
right: 10px;
margin-top: -3px;
}
}
}
}
::v-deep {
.el-scrollbar__wrap {
overflow-x: hidden;
}
.el-menu {
border: 0;
.vab-fas-icon {
padding-right: 3px;
font-size: $base-font-size-default;
}
.vab-remix-icon {
padding-right: 3px;
font-size: $base-font-size-default + 2;
}
}
.el-menu-item,
.el-submenu__title {
height: $base-menu-item-height;
overflow: hidden;
line-height: $base-menu-item-height;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
}
.el-menu-item {
@include active;
}
}
}
</style>
+297
View File
@@ -0,0 +1,297 @@
<template>
<div id="tabs-bar-container" class="tabs-bar-container">
<el-tabs
v-model="tabActive"
type="card"
class="tabs-content"
@tab-click="handleTabClick"
@tab-remove="handleTabRemove"
>
<el-tab-pane
v-for="item in visitedRoutes"
:key="item.path"
:label="item.meta.title"
:name="item.path"
:closable="!isAffix(item)"
></el-tab-pane>
</el-tabs>
<el-dropdown @command="handleCommand">
<span style="cursor: pointer">
更多操作
<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown" class="tabs-more">
<!-- <el-dropdown-item command="refreshRoute">
<vab-icon :icon="['fas', 'circle-notch']" />
刷新
</el-dropdown-item> -->
<el-dropdown-item command="closeOtherstabs">
<vab-icon :icon="['fas', 'times-circle']" />
关闭其他
</el-dropdown-item>
<el-dropdown-item command="closeLefttabs">
<vab-icon :icon="['fas', 'arrow-alt-circle-left']"></vab-icon>
关闭左侧
</el-dropdown-item>
<el-dropdown-item command="closeRighttabs">
<vab-icon :icon="['fas', 'arrow-alt-circle-right']"></vab-icon>
关闭右侧
</el-dropdown-item>
<el-dropdown-item command="closeAlltabs">
<vab-icon :icon="['fas', 'ban']"></vab-icon>
关闭全部
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</template>
<script>
import path from 'path'
import { mapGetters } from 'vuex'
export default {
name: 'VabTabsBar',
data() {
return {
affixtabs: [],
tabActive: '',
}
},
computed: {
...mapGetters({
visitedRoutes: 'tabsBar/visitedRoutes',
routes: 'routes/routes',
}),
},
watch: {
$route: {
handler(route) {
this.inittabs()
this.addtabs()
let tabActive = ''
this.visitedRoutes.forEach((item, index) => {
if (item.path === this.$route.path) {
tabActive = item.path
}
})
this.tabActive = tabActive
},
immediate: true,
},
},
mounted() {
//console.log(this.visitedRoutes);
},
methods: {
async handleTabRemove(tabActive) {
let view
this.visitedRoutes.forEach((item, index) => {
if (tabActive == item.path) {
view = item
}
})
const { visitedRoutes } = await this.$store.dispatch(
'tabsBar/delRoute',
view
)
if (this.isActive(view)) {
this.toLastTag(visitedRoutes, view)
}
},
handleTabClick(tab) {
const route = this.visitedRoutes.filter((item, index) => {
if (tab.index == index) return item
})[0]
if (this.$route.path !== route.path) {
this.$router.push({
path: route.path,
query: route.query,
fullPath: route.fullPath,
})
} else {
return false
}
},
isActive(route) {
return route.path === this.$route.path
},
isAffix(tag) {
return tag.meta && tag.meta.affix
},
filterAffixtabs(routes, basePath = '/') {
let tabs = []
routes.forEach((route) => {
if (route.meta && route.meta.affix) {
const tagPath = path.resolve(basePath, route.path)
tabs.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta },
})
}
if (route.children) {
const temptabs = this.filterAffixtabs(route.children, route.path)
if (temptabs.length >= 1) {
tabs = [...tabs, ...temptabs]
}
}
})
return tabs
},
inittabs() {
const affixtabs = (this.affixtabs = this.filterAffixtabs(this.routes))
for (const tag of affixtabs) {
if (tag.name) {
this.$store.dispatch('tabsBar/addVisitedRoute', tag)
}
}
},
addtabs() {
const { name } = this.$route
if (name) {
this.$store.dispatch('tabsBar/addVisitedRoute', this.$route)
}
return false
},
handleCommand(command) {
switch (command) {
case 'refreshRoute':
this.refreshRoute()
break
case 'closeOtherstabs':
this.closeOtherstabs()
break
case 'closeLefttabs':
this.closeLefttabs()
break
case 'closeRighttabs':
this.closeRighttabs()
break
case 'closeAlltabs':
this.closeAlltabs()
break
}
},
async refreshRoute() {
this.$baseEventBus.$emit('reloadrouter-view')
},
async closeSelectedTag(view) {
const { visitedRoutes } = await this.$store.dispatch(
'tabsBar/delRoute',
view
)
if (this.isActive(view)) {
this.toLastTag(visitedRoutes, view)
}
},
async closeOtherstabs() {
const view = await this.toThisTag()
await this.$store.dispatch('tabsBar/delOthersRoutes', view)
},
async closeLefttabs() {
const view = await this.toThisTag()
await this.$store.dispatch('tabsBar/delLeftRoutes', view)
},
async closeRighttabs() {
const view = await this.toThisTag()
await this.$store.dispatch('tabsBar/delRightRoutes', view)
},
async closeAlltabs() {
const view = await this.toThisTag()
const { visitedRoutes } = await this.$store.dispatch(
'tabsBar/delAllRoutes'
)
if (this.affixtabs.some((tag) => tag.path === view.path)) {
return
}
this.toLastTag(visitedRoutes, view)
},
toLastTag(visitedRoutes, view) {
const latestView = visitedRoutes.slice(-1)[0]
if (latestView) {
this.$router.push(latestView)
} else {
this.$router.push('/')
}
},
async toThisTag() {
const view = this.visitedRoutes.filter((item, index) => {
if (item.path === this.$route.fullPath) {
return item
}
})[0]
if (this.$route.path !== view.path) this.$router.push(view)
return view
},
},
}
</script>
<style lang="scss" scoped>
.tabs-bar-container {
position: relative;
box-sizing: border-box;
display: flex;
align-content: center;
align-items: center;
justify-content: space-between;
height: $base-tabs-bar-height;
padding-right: $base-padding;
padding-left: $base-padding;
user-select: none;
background: $base-color-white;
border-top: 1px solid #f6f6f6;
::v-deep {
.fold-unfold {
margin-right: $base-padding;
}
}
.tabs-content {
width: calc(100% - 90px);
height: $base-tag-item-height;
::v-deep {
.el-tabs__nav-next,
.el-tabs__nav-prev {
height: $base-tag-item-height;
line-height: $base-tag-item-height;
}
.el-tabs__header {
border-bottom: 0;
.el-tabs__nav {
border: 0;
}
.el-tabs__item {
box-sizing: border-box;
height: $base-tag-item-height;
margin-right: 5px;
line-height: $base-tag-item-height;
border: 1px solid $base-border-color;
border-radius: $base-border-radius;
transition: padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1) !important;
&.is-active {
border: 1px solid $base-color-blue;
}
}
}
}
}
.more {
display: flex;
align-content: center;
align-items: center;
cursor: pointer;
}
}
</style>
@@ -0,0 +1,259 @@
<template>
<span v-if="themeBar">
<vab-icon
title="主题配置"
:icon="['fas', 'palette']"
@click="handleOpenThemeBar"
/>
<div class="theme-bar-setting">
<div @click="handleOpenThemeBar">
<vab-icon :icon="['fas', 'palette']" />
<p>主题配置</p>
</div>
<div @click="handleGetCode">
<vab-icon :icon="['fas', 'laptop-code']"></vab-icon>
<p>拷贝源码</p>
</div>
</div>
<el-drawer
title="主题配置"
:visible.sync="drawerVisible"
direction="rtl"
append-to-body
size="470px"
>
<el-scrollbar style="height: 94vh; overflow: hidden">
<div class="el-drawer__body">
<el-form ref="form" :model="theme" label-position="top">
<el-form-item label="主题">
<el-radio-group v-model="theme.name">
<el-radio-button label="default">默认</el-radio-button>
<el-radio-button label="green">绿荫草场</el-radio-button>
<el-radio-button label="glory">荣耀典藏</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="布局">
<el-radio-group v-model="theme.layout">
<el-radio-button label="vertical">纵向布局</el-radio-button>
<el-radio-button label="horizontal">横向布局</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="头部">
<el-radio-group v-model="theme.header">
<el-radio-button label="fixed">固定头部</el-radio-button>
<el-radio-button label="noFixed">不固定头部</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="多标签">
<el-radio-group v-model="theme.tabsBar">
<el-radio-button label="true">开启</el-radio-button>
<el-radio-button label="false">不开启</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSaveTheme">
保存
</el-button>
</el-form-item>
</el-form>
</div>
</el-scrollbar>
</el-drawer>
</span>
</template>
<script>
import variables from '@/styles/variables.scss'
import { mapActions, mapGetters } from 'vuex'
import { layout as defaultLayout } from '@/config'
export default {
name: 'VabThemeBar',
data() {
return {
drawerVisible: false,
theme: {
name: 'default',
layout: '',
header: 'fixed',
tabsBar: '',
},
}
},
computed: {
...mapGetters({
layout: 'settings/layout',
header: 'settings/header',
tabsBar: 'settings/tabsBar',
themeBar: 'settings/themeBar',
}),
},
created() {
this.$baseEventBus.$on('theme', () => {
this.handleOpenThemeBar()
})
const theme = localStorage.getItem('vue-admin-beautiful-theme')
if (null !== theme) {
this.theme = JSON.parse(theme)
this.handleSetTheme()
} else {
this.theme.layout = this.layout
this.theme.header = this.header
this.theme.tabsBar = this.tabsBar
}
},
methods: {
...mapActions({
changeLayout: 'settings/changeLayout',
changeHeader: 'settings/changeHeader',
changeTabsBar: 'settings/changeTabsBar',
}),
handleIsMobile() {
return document.body.getBoundingClientRect().width - 1 < 992
},
handleOpenThemeBar() {
this.drawerVisible = true
},
handleSetTheme() {
let { name, layout, header, tabsBar } = this.theme
localStorage.setItem(
'vue-admin-beautiful-theme',
`{
"name":"${name}",
"layout":"${layout}",
"header":"${header}",
"tabsBar":"${tabsBar}"
}`
)
if (!this.handleIsMobile()) this.changeLayout(layout)
this.changeHeader(header)
this.changeTabsBar(tabsBar)
document.getElementsByTagName(
'body'
)[0].className = `vue-admin-beautiful-theme-${name}`
this.drawerVisible = false
},
handleSaveTheme() {
this.handleSetTheme()
},
handleSetDfaultTheme() {
let { name } = this.theme
document
.getElementsByTagName('body')[0]
.classList.remove(`vue-admin-beautiful-theme-${name}`)
localStorage.removeItem('vue-admin-beautiful-theme')
this.$refs['form'].resetFields()
Object.assign(this.$data, this.$options.data())
this.changeHeader(defaultLayout)
this.theme.name = 'default'
this.theme.layout = this.layout
this.theme.header = this.header
this.theme.tabsBar = this.tabsBar
this.drawerVisible = false
location.reload()
},
handleGetCode() {
const url =
'https://github.com/chuzhixin/vue-admin-beautiful/tree/master/src/views'
let path = this.$route.path + '/index.vue'
if (path === '/vab/menu1/menu1-1/menu1-1-1/index.vue') {
path = '/vab/nested/menu1/menu1-1/menu1-1-1/index.vue'
}
if (path === '/vab/icon/awesomeIcon/index.vue') {
path = '/vab/icon/index.vue'
}
if (path === '/vab/icon/remixIcon/index.vue') {
path = '/vab/icon/remixIcon.vue'
}
if (path === '/vab/icon/colorfulIcon/index.vue') {
path = '/vab/icon/colorfulIcon.vue'
}
if (path === '/vab/table/comprehensiveTable/index.vue') {
path = '/vab/table/index.vue'
}
if (path === '/vab/table/inlineEditTable/index.vue') {
path = '/vab/table/inlineEditTable.vue'
}
window.open(url + path)
},
},
}
</script>
<style lang="scss" scoped>
@mixin right-bar {
position: fixed;
right: 0;
z-index: $base-z-index;
width: 60px;
min-height: 60px;
text-align: center;
cursor: pointer;
background: $base-color-blue;
border-radius: $base-border-radius;
> div {
padding-top: 10px;
border-bottom: 0 !important;
&:hover {
opacity: 0.9;
}
& + div {
border-top: 1px solid $base-color-white;
}
p {
padding: 0;
margin: 0;
font-size: $base-font-size-small;
line-height: 30px;
color: $base-color-white;
}
}
}
.theme-bar-setting {
@include right-bar;
top: calc((100vh - 110px) / 2);
::v-deep {
svg:not(:root).svg-inline--fa {
display: block;
margin-right: auto;
margin-left: auto;
color: $base-color-white;
}
.svg-icon {
display: block;
margin-right: auto;
margin-left: auto;
font-size: 20px;
color: $base-color-white;
fill: $base-color-white;
}
}
}
.el-drawer__body {
padding: 20px;
}
</style>
<style lang="scss">
.el-drawer__wrapper {
outline: none !important;
* {
outline: none !important;
}
}
.vab-color-picker {
.el-color-dropdown__link-btn {
display: none;
}
}
</style>
+224
View File
@@ -0,0 +1,224 @@
<template>
<div class="top-bar-container">
<div class="vab-main">
<el-row>
<el-col :xl="7" :lg="7" :md="7" :sm="7" :xs="7">
<vab-logo />
</el-col>
<el-col :xl="12" :lg="12" :md="12" :sm="12" :xs="12">
<el-menu
:background-color="variables['menu-background']"
:text-color="variables['menu-color']"
:active-text-color="variables['menu-color-active']"
:default-active="activeMenu"
mode="horizontal"
menu-trigger="hover"
>
<template v-for="route in routes">
<vab-side-bar-item
v-if="!route.hidden"
:key="route.path"
:full-path="route.path"
:item="route"
/>
</template>
</el-menu>
</el-col>
<el-col :xl="5" :lg="5" :md="5" :sm="5" :xs="5">
<div class="right-panel">
<vab-error-log />
<vab-full-screen-bar @refresh="refreshRoute" />
<vab-theme-bar class="hidden-md-and-down" />
<vab-icon
title="重载路由"
:pulse="pulse"
:icon="['fas', 'redo']"
@click="refreshRoute"
/>
<vab-avatar />
</div>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
import variables from '@/styles/variables.scss'
import { mapGetters } from 'vuex'
export default {
name: 'VabTopBar',
data() {
return {
pulse: false,
menuTrigger: 'hover',
}
},
computed: {
...mapGetters({
routes: 'routes/routes',
visitedRoutes: 'tabsBar/visitedRoutes',
}),
activeMenu() {
const route = this.$route
const { meta, path } = route
if (meta.activeMenu) {
return meta.activeMenu
}
return path
},
variables() {
return variables
},
},
methods: {
async refreshRoute() {
this.$baseEventBus.$emit('reload-router-view')
this.pulse = true
setTimeout(() => {
this.pulse = false
}, 1000)
},
},
}
</script>
<style lang="scss" scoped>
.top-bar-container {
display: flex;
align-items: center;
justify-items: flex-end;
height: $base-top-bar-height;
background: $base-menu-background;
.vab-main {
background: $base-menu-background;
::v-deep {
.el-menu {
&.el-menu--horizontal {
display: flex;
align-items: center;
justify-content: flex-end;
height: $base-top-bar-height;
border-bottom: 0 solid transparent !important;
.el-menu-item,
.el-submenu__title {
padding: 0 15px;
}
@media only screen and (max-width: 767px) {
.el-menu-item,
.el-submenu__title {
padding: 0 8px;
}
li:nth-child(4),
li:nth-child(5) {
display: none !important;
}
}
> .el-menu-item {
height: $base-top-bar-height;
line-height: $base-top-bar-height;
}
> .el-submenu {
.el-submenu__title {
height: $base-top-bar-height;
line-height: $base-top-bar-height;
}
}
}
svg {
width: 1rem;
margin-right: 3px;
}
&--horizontal {
.el-menu {
.el-menu-item,
.el-submenu__title {
height: $base-menu-item-height;
line-height: $base-menu-item-height;
}
}
.el-submenu,
.el-menu-item {
&.is-active {
background-color: $base-color-blue !important;
border-bottom: 0 solid transparent !important;
.el-submenu__title {
border-bottom: 0 solid transparent !important;
}
}
}
> .el-menu-item {
.el-tag {
margin-top: calc(#{$base-top-bar-height} / 2 - 7.5px);
margin-left: 5px;
}
@media only screen and (max-width: 1199px) {
.el-tag {
display: none;
}
}
&.is-active {
background-color: transparent !important;
border-bottom: 3px solid $base-color-blue !important;
}
}
}
}
}
}
.right-panel {
display: flex;
align-items: center;
justify-content: flex-end;
height: $base-top-bar-height;
::v-deep {
.user-name {
color: rgba($base-color-white, 0.9);
}
.user-name + i {
color: rgba($base-color-white, 0.9);
}
svg {
width: 1em;
height: 1em;
margin-right: 15px;
font-size: $base-font-size-big;
color: rgba($base-color-white, 0.9);
cursor: pointer;
fill: rgba($base-color-white, 0.9);
}
button {
svg {
margin-right: 0;
color: rgba($base-color-white, 0.9);
cursor: pointer;
fill: rgba($base-color-white, 0.9);
}
}
.el-badge {
margin-right: 15px;
}
}
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More