mirror of
https://github.com/zclzone/vue-naive-admin.git
synced 2026-01-22 23:50:22 +08:00
Compare commits
2 Commits
1934776063
...
2.x_vxe-ta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a977ec1f3a | ||
|
|
6d1b3a4159 |
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
@@ -19,5 +19,13 @@
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc"
|
||||
]
|
||||
],
|
||||
"common-intellisense.ui": [
|
||||
"naiveUi2"
|
||||
],
|
||||
"workbench.colorCustomizations": {
|
||||
"activityBar.background": "#233212",
|
||||
"titleBar.activeBackground": "#314619",
|
||||
"titleBar.activeForeground": "#F9FCF6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,6 @@
|
||||
<a href="./LICENSE"><img alt="MIT License" src="https://badgen.net/github/license/zclzone/vue-naive-admin"/></a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
<a href="https://hellogithub.com/repository/54f19ba1f9ae4238b3cbd111f3c428b0" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=54f19ba1f9ae4238b3cbd111f3c428b0&claim_uid=jXGayRdJZScqMNr" alt="Featured|HelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
## 简介
|
||||
|
||||
Vue Naive Admin 是一款极简风格的后台管理模板,包含前后端解决方案,前端使用 Vite + Vue3 + Pinia + Unocss,后端使用 Nestjs + TypeOrm + MySql,简单易用,赏心悦目,历经十几次重构和细节打磨,诚意满满!!
|
||||
|
||||
61
package.json
61
package.json
@@ -13,43 +13,46 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@arco-design/color": "^0.4.0",
|
||||
"@vueuse/core": "^13.1.0",
|
||||
"axios": "^1.8.4",
|
||||
"@vueuse/core": "^11.1.0",
|
||||
"axios": "^1.7.7",
|
||||
"dayjs": "^1.11.13",
|
||||
"echarts": "^5.6.0",
|
||||
"echarts": "^5.5.1",
|
||||
"exceljs": "^4.4.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"naive-ui": "^2.41.0",
|
||||
"pinia": "^3.0.2",
|
||||
"pinia-plugin-persistedstate": "^4.2.0",
|
||||
"vue": "^3.5.13",
|
||||
"naive-ui": "^2.40.1",
|
||||
"pinia": "^2.2.4",
|
||||
"pinia-plugin-persistedstate": "^4.1.1",
|
||||
"vue": "^3.5.12",
|
||||
"vue-echarts": "^7.0.3",
|
||||
"vue-router": "^4.5.0",
|
||||
"vue-router": "^4.4.5",
|
||||
"vxe-pc-ui": "4.2.5",
|
||||
"vxe-table": "4.8.13",
|
||||
"vxe-table-plugin-export-xlsx": "^4.0.4",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^4.12.0",
|
||||
"@iconify/json": "^2.2.331",
|
||||
"@unocss/eslint-config": "^66.0.0",
|
||||
"@unocss/eslint-plugin": "^66.0.0",
|
||||
"@unocss/preset-rem-to-px": "^66.0.0",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.2",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint-plugin-format": "^1.0.1",
|
||||
"@antfu/eslint-config": "^3.8.0",
|
||||
"@iconify/json": "^2.2.262",
|
||||
"@unocss/eslint-config": "^0.63.6",
|
||||
"@unocss/eslint-plugin": "^0.63.6",
|
||||
"@unocss/preset-rem-to-px": "^0.63.6",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint-plugin-format": "^0.1.2",
|
||||
"esno": "^4.8.0",
|
||||
"fs-extra": "^11.3.0",
|
||||
"glob": "^11.0.2",
|
||||
"lint-staged": "^15.5.1",
|
||||
"rollup-plugin-visualizer": "^5.14.0",
|
||||
"simple-git-hooks": "^2.13.0",
|
||||
"taze": "^19.0.4",
|
||||
"unocss": "^66.0.0",
|
||||
"unplugin-auto-import": "^19.1.2",
|
||||
"unplugin-vue-components": "^28.5.0",
|
||||
"vite": "^6.3.3",
|
||||
"fs-extra": "^11.2.0",
|
||||
"glob": "^11.0.0",
|
||||
"lint-staged": "^15.2.10",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"simple-git-hooks": "^2.11.1",
|
||||
"taze": "^0.17.2",
|
||||
"unocss": "^0.63.6",
|
||||
"unplugin-auto-import": "^0.18.3",
|
||||
"unplugin-vue-components": "^0.27.4",
|
||||
"vite": "^5.4.9",
|
||||
"vite-plugin-router-warn": "^1.0.0",
|
||||
"vite-plugin-vue-devtools": "^7.7.5",
|
||||
"vue3-intro-step": "^1.0.5"
|
||||
"vite-plugin-vue-devtools": "^7.5.2"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "pnpm lint-staged"
|
||||
|
||||
5705
pnpm-lock.yaml
generated
5705
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,7 @@
|
||||
<div>
|
||||
<n-tooltip trigger="hover" placement="left">
|
||||
<template #trigger>
|
||||
<div id="layout-setting" class="f-c-c rounded-4 bg-primary p-8" @click="modalRef.open()">
|
||||
<div class="f-c-c rounded-4 bg-primary p-8" @click="modalRef.open()">
|
||||
<i class="i-fe:settings cursor-pointer bg-white text-20" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
240
src/components/common/MeVxeGrid.vue
Normal file
240
src/components/common/MeVxeGrid.vue
Normal file
@@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<div
|
||||
:style="isComputedHeight ? `height: calc(100% - ${autoHeight}px);` : `height: ${autoHeight}px`"
|
||||
>
|
||||
<vxe-grid
|
||||
ref="gridRef"
|
||||
:columns="vxeColumns"
|
||||
:loading="loading"
|
||||
:data="data"
|
||||
height="auto"
|
||||
:stripe="true"
|
||||
:border="true"
|
||||
size="mini"
|
||||
:auto-resize="true"
|
||||
:pager-config="pagerConfig"
|
||||
:export-config="exportConfig"
|
||||
:toolbar-config="vxeToolbarConfig"
|
||||
:proxy-config="proxyConfig"
|
||||
v-on="gridEvent"
|
||||
>
|
||||
<template #loading>
|
||||
<n-spin :show="loading" />
|
||||
</template>
|
||||
<template #total>
|
||||
<span class="font-size-14 line-height-28px">共 {{ pagerConfig.total }} 条数据</span>
|
||||
</template>
|
||||
<template v-for="(c, k) in slots" :key="k" #[c.key]="{ row }">
|
||||
<template v-if="Array.isArray(c.render(row))">
|
||||
<component :is="r" v-for="(r, k1) in c.render(row)" :key="k1" :row="row" :col="c" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<component :is="c.render(row)" :row="row" :col="c" />
|
||||
</template>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
defineOptions({ name: 'MeVxeGrid' })
|
||||
const props = defineProps({
|
||||
/**
|
||||
* @remote 当isComputedHeight为true时为除table外的高度 false时为table高度
|
||||
*/
|
||||
autoHeight: {
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
/**
|
||||
* @isPagination 是否为自动计算高度
|
||||
*/
|
||||
isComputedHeight: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* @remote table的列
|
||||
*/
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
|
||||
pagination: {
|
||||
type: [Boolean, Object],
|
||||
default: true,
|
||||
},
|
||||
|
||||
exportConfig: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
toolbarConfig: {
|
||||
type: Object,
|
||||
default: () => ({ }),
|
||||
},
|
||||
/**
|
||||
* 是否显示复选框
|
||||
*/
|
||||
showCheck: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
rowKey: {
|
||||
type: String, // rowKey
|
||||
default: 'id',
|
||||
},
|
||||
|
||||
exportMethod: {
|
||||
type: Function,
|
||||
default: () => null,
|
||||
},
|
||||
remoteMethod: {
|
||||
type: Function,
|
||||
default: () => null,
|
||||
},
|
||||
})
|
||||
const emits = defineEmits(['update:checked-row-keys', 'pageChanged'])
|
||||
const isFirst = shallowRef(true)
|
||||
const gridRef = ref(null)
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(n) => {
|
||||
isFirst.value = false
|
||||
gridRef.value.reloadData(n)
|
||||
},
|
||||
)
|
||||
|
||||
const vxeToolbarConfig = computed(() => {
|
||||
return {
|
||||
size: 'mini',
|
||||
className: 'c-toolbar',
|
||||
perfect: true,
|
||||
refresh: true,
|
||||
export: true,
|
||||
zoom: true,
|
||||
custom: true,
|
||||
...props.toolbarConfig,
|
||||
}
|
||||
})
|
||||
const pagerConfig = computed(() => {
|
||||
if (props.pagination === false) {
|
||||
return {
|
||||
enabled: false,
|
||||
}
|
||||
}
|
||||
return {
|
||||
currentPage: props.pagination.pageNo,
|
||||
pageSize: props.pagination.pageSize,
|
||||
total: props.pagination.itemCount || 0,
|
||||
background: true,
|
||||
size: 'small',
|
||||
layouts: [
|
||||
'Home',
|
||||
'PrevPage',
|
||||
'JumpNumber',
|
||||
'NextPage',
|
||||
'End',
|
||||
'Sizes',
|
||||
'FullJump',
|
||||
// 'Total',
|
||||
],
|
||||
slots: {
|
||||
right: 'total',
|
||||
},
|
||||
}
|
||||
})
|
||||
/**
|
||||
* 将naive 转为vxe-table可用的列
|
||||
*/
|
||||
const vxeColumns = computed(() => {
|
||||
if (props.showCheck) {
|
||||
return [{
|
||||
type: 'checkbox',
|
||||
resizable: false,
|
||||
width: 45,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
}, ...props.columns.map((item) => {
|
||||
item.field = item.key
|
||||
if (item.render) {
|
||||
item.slots = { default: item.key }
|
||||
}
|
||||
return item
|
||||
})]
|
||||
}
|
||||
return props.columns.map((item) => {
|
||||
item.field = item.key
|
||||
if (item.render) {
|
||||
item.slots = { default: item.key }
|
||||
}
|
||||
return item
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* 将naive render函数转换为vxeTable的slots
|
||||
*/
|
||||
const slots = computed(() => {
|
||||
return props.columns.filter(item => item.render)
|
||||
})
|
||||
|
||||
const gridEvent = {
|
||||
checkboxAll() {
|
||||
const $grid = gridRef.value
|
||||
if ($grid) {
|
||||
emits(
|
||||
'update:checked-row-keys',
|
||||
$grid.getCheckboxRecords().map(item => item[props.rowKey]),
|
||||
)
|
||||
}
|
||||
},
|
||||
checkboxChange() {
|
||||
const $grid = gridRef.value
|
||||
if ($grid) {
|
||||
emits(
|
||||
'update:checked-row-keys',
|
||||
$grid.getCheckboxRecords().map(item => item[props.rowKey]),
|
||||
)
|
||||
}
|
||||
},
|
||||
pageChange(e) {
|
||||
emits('pageChanged', e)
|
||||
},
|
||||
}
|
||||
|
||||
const proxyConfig = {
|
||||
props: {
|
||||
result: 'data.pageData',
|
||||
total: 'data.total',
|
||||
},
|
||||
ajax: {
|
||||
query: async ({ page }) => {
|
||||
if (isFirst.value === true) {
|
||||
return Promise.reject('')
|
||||
}
|
||||
props.remoteMethod(page.currentPage, page.pageSize)
|
||||
},
|
||||
queryAll: async () => {
|
||||
const res = await props.exportMethod()
|
||||
return res
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,10 +1,8 @@
|
||||
<template>
|
||||
<div class="f-c-c">
|
||||
<n-tooltip trigger="hover" placement="bottom">
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<n-color-picker
|
||||
id="theme-setting"
|
||||
class="h-32 w-32"
|
||||
class="mr-16 h-32 w-32"
|
||||
:value="appStore.primaryColor"
|
||||
:swatches="primaryColors"
|
||||
:on-update:value="(v) => appStore.setPrimaryColor(v)"
|
||||
@@ -13,7 +11,6 @@
|
||||
</template>
|
||||
设置主题色
|
||||
</n-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<i
|
||||
id="toggleTheme"
|
||||
class="mr-16 cursor-pointer"
|
||||
:class="isDark ? 'i-fe:moon' : 'i-fe:sun'"
|
||||
@click="toggleDark"
|
||||
|
||||
@@ -2,5 +2,6 @@ export { default as AppCard } from './AppCard.vue'
|
||||
export { default as AppPage } from './AppPage.vue'
|
||||
export { default as CommonPage } from './CommonPage.vue'
|
||||
export { default as LayoutSetting } from './LayoutSetting.vue'
|
||||
export { default as MeVxeGrid } from './MeVxeGrid.vue'
|
||||
export { default as TheFooter } from './TheFooter.vue'
|
||||
export { default as ToggleTheme } from './ToggleTheme.vue'
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
--------------------------------->
|
||||
|
||||
<template>
|
||||
<div :class="computedHeight === true ? 'h-100%' : ''">
|
||||
<div id="search_main">
|
||||
<AppCard v-if="$slots.default" bordered bg="#fafafc dark:black" class="mb-30 min-h-60 rounded-4">
|
||||
<form class="flex justify-between p-16" @submit.prevent="handleSearch()">
|
||||
<n-scrollbar x-scrollable>
|
||||
@@ -37,8 +39,27 @@
|
||||
</div>
|
||||
</form>
|
||||
</AppCard>
|
||||
</div>
|
||||
<MeVxeGrid
|
||||
:remote="remote"
|
||||
:loading="loading"
|
||||
:scroll-x="scrollX"
|
||||
:columns="columns"
|
||||
:export-config="{ filename: exportName, modes: ['all', 'current'] }"
|
||||
:toolbar-config="toolbarConfig"
|
||||
:data="tableData"
|
||||
:auto-height="computedHeight === true ? autoHeight : computedHeight"
|
||||
:row-key="rowKey"
|
||||
:is-computed-height="computedHeight === true"
|
||||
:show-check="showCheck"
|
||||
:pagination="isPagination ? pagination : false"
|
||||
:remote-method="remoteMethod"
|
||||
:export-method="exportMethod"
|
||||
@update:checked-row-keys="onChecked"
|
||||
@update:page="onPageChange"
|
||||
/>
|
||||
|
||||
<NDataTable
|
||||
<!-- <NDataTable
|
||||
:remote="remote"
|
||||
:loading="loading"
|
||||
:scroll-x="scrollX"
|
||||
@@ -48,14 +69,39 @@
|
||||
:pagination="isPagination ? pagination : false"
|
||||
@update:checked-row-keys="onChecked"
|
||||
@update:page="onPageChange"
|
||||
/>
|
||||
/> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { NDataTable } from 'naive-ui'
|
||||
import { utils, writeFile } from 'xlsx'
|
||||
|
||||
const props = defineProps({
|
||||
/**
|
||||
* 是否自动计算高度,适用只有search和table
|
||||
*/
|
||||
computedHeight: {
|
||||
type: [Boolean, Number],
|
||||
default: () => true,
|
||||
},
|
||||
showCheck: {
|
||||
type: Boolean,
|
||||
default: () => false,
|
||||
},
|
||||
toolbarConfig: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
export: true,
|
||||
zoom: true,
|
||||
}),
|
||||
},
|
||||
/**
|
||||
* @remote 导出数据表格名称
|
||||
*/
|
||||
exportName: {
|
||||
type: String,
|
||||
default: '导出数据',
|
||||
},
|
||||
/**
|
||||
* @remote true: 后端分页 false: 前端分页
|
||||
*/
|
||||
@@ -105,8 +151,18 @@ const props = defineProps({
|
||||
/** 是否支持展开 */
|
||||
expand: Boolean,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:queryItems', 'onChecked', 'onDataChange'])
|
||||
|
||||
const autoHeight = ref(0)
|
||||
|
||||
window.onresize = () => {
|
||||
autoHeight.value = document.getElementById('search_main').clientHeight + 24
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
autoHeight.value = document.getElementById('search_main').clientHeight + 24
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const initQuery = { ...props.queryItems }
|
||||
const tableData = ref([])
|
||||
@@ -125,6 +181,22 @@ function toggleExpand() {
|
||||
isExpanded.value = !isExpanded.value
|
||||
}
|
||||
|
||||
async function exportMethod() {
|
||||
const { data } = await props.getData({
|
||||
pageNo: 1,
|
||||
pageSize: 2000,
|
||||
...props.queryItems,
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
function remoteMethod(page, pageSize) {
|
||||
pagination.page = page
|
||||
pagination.pageSize = pageSize
|
||||
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
async function handleQuery() {
|
||||
try {
|
||||
loading.value = true
|
||||
@@ -143,6 +215,7 @@ async function handleQuery() {
|
||||
// 如果当前页数据为空,且总条数不为0,则返回上一页数据
|
||||
onPageChange(pagination.page - 1)
|
||||
}
|
||||
return data
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
@@ -180,10 +253,8 @@ function onPageChange(currentPage) {
|
||||
}
|
||||
}
|
||||
function onChecked(rowKeys) {
|
||||
if (props.columns.some(item => item.type === 'selection')) {
|
||||
emit('onChecked', rowKeys)
|
||||
}
|
||||
}
|
||||
function handleExport(columns = props.columns, data = tableData.value) {
|
||||
if (!data?.length)
|
||||
return $message.warning('没有数据')
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
||||
**********************************/
|
||||
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { useForm, useModal } from '.'
|
||||
|
||||
const ACTIONS = {
|
||||
@@ -22,7 +21,7 @@ export function useCrud({ name, initForm = {}, doCreate, doDelete, doUpdate, ref
|
||||
|
||||
/** 新增 */
|
||||
function handleAdd(row = {}, title) {
|
||||
handleOpen({ action: 'add', title, row: Object.assign({}, cloneDeep(initForm), cloneDeep(row)) })
|
||||
handleOpen({ action: 'add', title, row: { ...initForm, ...row } })
|
||||
}
|
||||
|
||||
/** 修改 */
|
||||
|
||||
@@ -6,11 +6,9 @@
|
||||
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
||||
**********************************/
|
||||
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
export function useForm(initFormData = {}) {
|
||||
const formRef = ref(null)
|
||||
const formModel = ref(cloneDeep(initFormData))
|
||||
const formModel = ref({ ...initFormData })
|
||||
const rules = {
|
||||
required: {
|
||||
required: true,
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
**********************************/
|
||||
|
||||
import { router } from '@/router'
|
||||
import { withDirectives } from 'vue'
|
||||
|
||||
const permission = {
|
||||
mounted(el, binding) {
|
||||
@@ -22,17 +21,3 @@ const permission = {
|
||||
export function setupDirectives(app) {
|
||||
app.directive('permission', permission)
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于h函数使用自定义权限指令
|
||||
*
|
||||
* @param {*} vnode 虚拟节点
|
||||
* @param {*} code 权限码
|
||||
* @returns 返回一个包含权限指令的vnode
|
||||
*
|
||||
* 使用示例:withPermission(h('button', {class: 'text-red-500'}, '删除'), 'user:delete')
|
||||
*
|
||||
*/
|
||||
export function withPermission(vnode, code) {
|
||||
return withDirectives(vnode, [[permission, code]])
|
||||
}
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
<template>
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<i
|
||||
class="i-fe:beginner mr-16 cursor-pointer text-20"
|
||||
@click="show = true"
|
||||
/>
|
||||
</template>
|
||||
操作指引
|
||||
</n-tooltip>
|
||||
|
||||
<Vue3IntroStep
|
||||
ref="myIntroStep"
|
||||
v-model:show="show"
|
||||
:config="config"
|
||||
>
|
||||
<template #prev="{ tipItem, index }">
|
||||
<NButton class="mr-12" type="primary" color="#fff" text-color="#fff" ghost round size="small" @click="prev(tipItem, index)">
|
||||
上一步
|
||||
</NButton>
|
||||
</template>
|
||||
<template #next="{ tipItem }">
|
||||
<NButton class="mr-12" type="primary" color="#fff" text-color="#fff" ghost round size="small" @click="next(tipItem)">
|
||||
下一步
|
||||
</NButton>
|
||||
</template>
|
||||
|
||||
<template #skip>
|
||||
<NButton type="primary" color="#fff" text-color="#fff" ghost round size="small" @click="skip">
|
||||
跳过
|
||||
</NButton>
|
||||
</template>
|
||||
|
||||
<template #done>
|
||||
<NButton type="primary" color="#fff" text-color="#fff" ghost round size="small" @click="done">
|
||||
完成
|
||||
</NButton>
|
||||
</template>
|
||||
</Vue3IntroStep>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Vue3IntroStep from 'vue3-intro-step'
|
||||
|
||||
const myIntroStep = shallowRef(null)
|
||||
const show = shallowRef(false)
|
||||
const config = {
|
||||
backgroundOpacity: 0.8,
|
||||
titleStyle: {
|
||||
textAlign: 'left',
|
||||
fontSize: '18px',
|
||||
},
|
||||
contentStyle: {
|
||||
textAlign: 'left',
|
||||
fontSize: '14px',
|
||||
},
|
||||
tips: [
|
||||
{
|
||||
el: '#toggleTheme',
|
||||
tipPosition: 'bottom',
|
||||
title: '切换系统主题',
|
||||
content: '一键开启护眼模式',
|
||||
},
|
||||
{
|
||||
el: '#fullscreen',
|
||||
tipPosition: 'bottom',
|
||||
title: '全屏/退出全屏',
|
||||
content: '一键开启全屏',
|
||||
},
|
||||
{
|
||||
el: '#theme-setting',
|
||||
tipPosition: 'bottom',
|
||||
title: '设置主题色',
|
||||
content: '调整为你喜欢的主题色',
|
||||
},
|
||||
{
|
||||
el: '#user-dropdown',
|
||||
tipPosition: 'bottom',
|
||||
title: '个人中心',
|
||||
content: '查看个人资料和退出系统',
|
||||
},
|
||||
{
|
||||
el: '#menu-collapse',
|
||||
tipPosition: 'bottom',
|
||||
title: '展开/收起菜单',
|
||||
content: '一键展开/收起菜单',
|
||||
},
|
||||
{
|
||||
el: '#top-tab',
|
||||
tipPosition: 'bottom',
|
||||
title: '标签栏',
|
||||
content: '鼠标滚轮滑动可调整至最佳视野',
|
||||
},
|
||||
{
|
||||
el: '#layout-setting',
|
||||
tipPosition: 'left',
|
||||
title: '调整系统布局',
|
||||
content: '将系统布局调整为你喜欢的样子',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
function skip() {
|
||||
show.value = false
|
||||
}
|
||||
|
||||
function done() {
|
||||
show.value = false
|
||||
}
|
||||
|
||||
function next() {
|
||||
// tipItem当前的提示项信息
|
||||
// 调用vue3-intro-step的next方法 手动触发下一步
|
||||
myIntroStep.value.next()
|
||||
}
|
||||
function prev() {
|
||||
// 调用vue3-intro-step的prev方法 手动触发上一步
|
||||
myIntroStep.value.prev()
|
||||
}
|
||||
</script>
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<i
|
||||
id="fullscreen"
|
||||
class="mr-16 cursor-pointer"
|
||||
:class="isFullscreen ? 'i-fe:minimize' : 'i-fe:maximize'"
|
||||
@click="toggle"
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
<template>
|
||||
<div
|
||||
id="menu-collapse"
|
||||
class="f-c-c cursor-pointer rounded-4 p-6 text-22 transition-all-300 auto-bg-hover"
|
||||
@click="appStore.switchCollapsed"
|
||||
>
|
||||
|
||||
@@ -54,7 +54,6 @@ function handleMenuSelect(key, item) {
|
||||
})
|
||||
}
|
||||
else {
|
||||
if(!item.path) return
|
||||
router.push(item.path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<template>
|
||||
<n-dropdown :options="options" @select="handleSelect">
|
||||
<div id="user-dropdown" class="flex cursor-pointer items-center">
|
||||
<div class="flex cursor-pointer items-center">
|
||||
<n-avatar round :size="36" :src="userStore.avatar" />
|
||||
<div v-if="userStore.userInfo" class="ml-12 flex-col flex-shrink-0 items-center">
|
||||
<span class="text-14">{{ userStore.nickName ?? userStore.username }}</span>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export { default as BeginnerGuide } from './BeginnerGuide.vue'
|
||||
export { default as BreadCrumb } from './BreadCrumb.vue'
|
||||
export { default as Fullscreen } from './Fullscreen.vue'
|
||||
export { default as MenuCollapse } from './MenuCollapse.vue'
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
--------------------------------->
|
||||
|
||||
<template>
|
||||
<div id="top-tab">
|
||||
<div>
|
||||
<n-tabs
|
||||
:value="tabStore.activeTab"
|
||||
:closable="tabStore.tabs.length > 1"
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
<BreadCrumb />
|
||||
|
||||
<div class="ml-auto flex flex-shrink-0 items-center px-12 text-18">
|
||||
<BeginnerGuide />
|
||||
|
||||
<ToggleTheme />
|
||||
|
||||
<Fullscreen />
|
||||
@@ -37,7 +35,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ToggleTheme } from '@/components'
|
||||
import { BeginnerGuide, BreadCrumb, Fullscreen, MenuCollapse, UserAvatar } from '@/layouts/components'
|
||||
import { BreadCrumb, Fullscreen, MenuCollapse, UserAvatar } from '@/layouts/components'
|
||||
|
||||
function handleLinkClick(link) {
|
||||
window.open(link)
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
<span class="mx-6 opacity-20">|</span>
|
||||
|
||||
<div class="flex flex-shrink-0 items-center px-12 text-18">
|
||||
<BeginnerGuide />
|
||||
|
||||
<ToggleTheme />
|
||||
|
||||
<Fullscreen />
|
||||
@@ -39,7 +37,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ToggleTheme } from '@/components'
|
||||
import { AppTab, BeginnerGuide, Fullscreen, MenuCollapse, UserAvatar } from '@/layouts/components'
|
||||
import { AppTab, Fullscreen, MenuCollapse, UserAvatar } from '@/layouts/components'
|
||||
|
||||
function handleLinkClick(link) {
|
||||
window.open(link)
|
||||
|
||||
18
src/main.js
18
src/main.js
@@ -8,19 +8,35 @@
|
||||
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
|
||||
**********************************/
|
||||
|
||||
import ExcelJS from 'exceljs'
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import { setupDirectives } from './directives'
|
||||
|
||||
import { setupDirectives } from './directives'
|
||||
/**
|
||||
* 引入vxe-table 和 vxe-pc-ui excel导出
|
||||
*/
|
||||
import VxeUI from 'vxe-pc-ui'
|
||||
import VXETable from 'vxe-table'
|
||||
import VXETablePluginExportXLSX from 'vxe-table-plugin-export-xlsx'
|
||||
import { setupRouter } from './router'
|
||||
import { setupStore } from './store'
|
||||
|
||||
import { setupNaiveDiscreteApi } from './utils'
|
||||
import 'vxe-pc-ui/lib/style.css'
|
||||
import 'vxe-table/lib/style.css'
|
||||
import '@/styles/reset.css'
|
||||
import '@/styles/global.css'
|
||||
import 'uno.css'
|
||||
|
||||
async function bootstrap() {
|
||||
const app = createApp(App)
|
||||
|
||||
VXETable.use(VXETablePluginExportXLSX, {
|
||||
ExcelJS,
|
||||
})
|
||||
app.use(VxeUI).use(VXETable)
|
||||
|
||||
setupStore(app)
|
||||
setupDirectives(app)
|
||||
await setupRouter(app)
|
||||
|
||||
@@ -10,6 +10,7 @@ import { defaultLayout, defaultPrimaryColor, naiveThemeOverrides } from '@/setti
|
||||
import { generate, getRgbStr } from '@arco-design/color'
|
||||
import { useDark } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
import { VxeUI } from 'vxe-pc-ui'
|
||||
|
||||
export const useAppStore = defineStore('app', {
|
||||
state: () => ({
|
||||
@@ -40,6 +41,18 @@ export const useAppStore = defineStore('app', {
|
||||
list: true,
|
||||
dark: isDark,
|
||||
})
|
||||
VxeUI.setTheme(isDark ? 'dark' : 'light')
|
||||
if (isDark) {
|
||||
document.body.style.setProperty('--vxe-ui-font-color', '#fff')
|
||||
}
|
||||
else {
|
||||
document.body.style.setProperty('--vxe-ui-font-color', '#000')
|
||||
}
|
||||
|
||||
document.body.style.setProperty('--vxe-ui-font-primary-color', color)
|
||||
document.body.style.setProperty('--vxe-ui-font-primary-lighten-color', color)
|
||||
document.body.style.setProperty('--vxe-ui-font-primary-darken-color', color)
|
||||
|
||||
document.body.style.setProperty('--primary-color', getRgbStr(colors[5]))
|
||||
this.naiveThemeOverrides.common = Object.assign(this.naiveThemeOverrides.common || {}, {
|
||||
primaryColor: colors[5],
|
||||
@@ -50,7 +63,7 @@ export const useAppStore = defineStore('app', {
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
pick: ['collapsed', 'layout', 'primaryColor', 'naiveThemeOverrides'],
|
||||
paths: ['collapsed', 'layout', 'primaryColor', 'naiveThemeOverrides'],
|
||||
storage: sessionStorage,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -88,7 +88,7 @@ export const useTabStore = defineStore('tab', {
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
pick: ['tabs'],
|
||||
paths: ['tabs'],
|
||||
storage: sessionStorage,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -11,14 +11,14 @@ import { createStorage } from './storage'
|
||||
|
||||
const prefixKey = 'vue-naive-admin_'
|
||||
|
||||
export function createLocalStorage(option = {}) {
|
||||
export const createLocalStorage = function (option = {}) {
|
||||
return createStorage({
|
||||
prefixKey: option.prefixKey || '',
|
||||
storage: localStorage,
|
||||
})
|
||||
}
|
||||
|
||||
export function createSessionStorage(option = {}) {
|
||||
export const createSessionStorage = function (option = {}) {
|
||||
return createStorage({
|
||||
prefixKey: option.prefixKey || '',
|
||||
storage: sessionStorage,
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
<script setup>
|
||||
import { sleep } from '@/utils'
|
||||
|
||||
function handleDelete() {
|
||||
const handleDelete = function () {
|
||||
$dialog.confirm({
|
||||
content: '确认删除?',
|
||||
confirm() {
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
class="text-14 text-primary text-highlight hover:underline hover:opacity-80"
|
||||
href="https://isme.top"
|
||||
target="_blank"
|
||||
@click.prevent="message?.info('官网正在火速开发中...')"
|
||||
>
|
||||
isme.top
|
||||
</a>
|
||||
@@ -283,4 +284,6 @@ const skillOption = {
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const message = $message
|
||||
</script>
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
:columns="btnsColumns"
|
||||
:scroll-x="-1"
|
||||
:get-data="api.getButtons"
|
||||
:computed-height="300"
|
||||
:query-items="{ parentId: currentMenu.id }"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
:scroll-x="1200"
|
||||
:columns="columns"
|
||||
:get-data="api.getAllUsers"
|
||||
:show-check="true"
|
||||
@on-checked="onChecked"
|
||||
>
|
||||
<MeQueryItem label="用户名" :label-width="50">
|
||||
@@ -90,7 +91,7 @@ const genders = [
|
||||
]
|
||||
|
||||
const columns = [
|
||||
{ type: 'selection', fixed: 'left' },
|
||||
// { type: 'selection', fixed: 'left' },
|
||||
{
|
||||
title: '头像',
|
||||
key: 'avatar',
|
||||
@@ -101,11 +102,10 @@ const columns = [
|
||||
src: avatar,
|
||||
}),
|
||||
},
|
||||
{ title: '用户名', key: 'username', width: 150, ellipsis: { tooltip: true } },
|
||||
{ title: '用户名', key: 'username', ellipsis: { tooltip: true } },
|
||||
{
|
||||
title: '角色',
|
||||
key: 'roles',
|
||||
width: 200,
|
||||
ellipsis: { tooltip: true },
|
||||
render: ({ roles }) => {
|
||||
if (roles?.length) {
|
||||
@@ -156,7 +156,7 @@ const columns = [
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 100,
|
||||
width: 120,
|
||||
align: 'right',
|
||||
fixed: 'right',
|
||||
hideInExcel: true,
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
trigger: ['input', 'blur'],
|
||||
}"
|
||||
>
|
||||
<n-input v-model:value="modalForm.password" type="password" show-password-on="mousedown" />
|
||||
<n-input v-model:value="modalForm.password" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item v-if="['add', 'setRole'].includes(modalAction)" label="角色" path="roleIds">
|
||||
@@ -112,7 +112,6 @@
|
||||
<script setup>
|
||||
import { MeCrud, MeModal, MeQueryItem } from '@/components'
|
||||
import { useCrud } from '@/composables'
|
||||
import { withPermission } from '@/directives'
|
||||
import { formatDateTime } from '@/utils'
|
||||
import { NAvatar, NButton, NSwitch, NTag } from 'naive-ui'
|
||||
import api from './api'
|
||||
@@ -163,11 +162,10 @@ const columns = [
|
||||
src: avatar,
|
||||
}),
|
||||
},
|
||||
{ title: '用户名', key: 'username', width: 150, ellipsis: { tooltip: true } },
|
||||
{ title: '用户名', key: 'username', ellipsis: { tooltip: true } },
|
||||
{
|
||||
title: '角色',
|
||||
key: 'roles',
|
||||
width: 200,
|
||||
ellipsis: { tooltip: true },
|
||||
render: ({ roles }) => {
|
||||
if (roles?.length) {
|
||||
@@ -226,23 +224,11 @@ const columns = [
|
||||
hideInExcel: true,
|
||||
render(row) {
|
||||
return [
|
||||
withPermission(
|
||||
h(NButton, {
|
||||
size: 'small',
|
||||
type: 'primary',
|
||||
secondary: true,
|
||||
}, {
|
||||
default: () => '超管专属',
|
||||
icon: () => h('i', { class: 'i-carbon:user-role text-14' }),
|
||||
}),
|
||||
'SuperAdmin',
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'primary',
|
||||
class: 'ml-12px',
|
||||
secondary: true,
|
||||
onClick: () => handleOpenRolesSet(row),
|
||||
},
|
||||
|
||||
@@ -73,10 +73,10 @@
|
||||
require-mark-placement="left"
|
||||
>
|
||||
<n-form-item label="原密码" path="oldPassword" :rule="required">
|
||||
<n-input v-model:value="pwdForm.oldPassword" type="password" placeholder="请输入原密码" show-password-on="mousedown" />
|
||||
<n-input v-model:value="pwdForm.oldPassword" type="password" placeholder="请输入原密码" />
|
||||
</n-form-item>
|
||||
<n-form-item label="新密码" path="newPassword" :rule="required">
|
||||
<n-input v-model:value="pwdForm.newPassword" type="password" placeholder="请输入新密码" show-password-on="mousedown" />
|
||||
<n-input v-model:value="pwdForm.newPassword" type="password" placeholder="请输入新密码" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</MeModal>
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
|
||||
import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders'
|
||||
import presetRemToPx from '@unocss/preset-rem-to-px'
|
||||
import { defineConfig, presetAttributify, presetIcons, presetWind3 } from 'unocss'
|
||||
import { defineConfig, presetAttributify, presetIcons, presetUno } from 'unocss'
|
||||
import { getIcons } from './build/index.js'
|
||||
|
||||
const icons = getIcons()
|
||||
export default defineConfig({
|
||||
presets: [
|
||||
presetWind3(),
|
||||
presetUno(),
|
||||
presetAttributify(),
|
||||
presetIcons({
|
||||
warn: true,
|
||||
|
||||
Reference in New Issue
Block a user