1
0
mirror of https://github.com/zclzone/vue-naive-admin.git synced 2025-12-26 19:20:21 +08:00
This commit is contained in:
zclzone
2023-12-07 21:55:23 +08:00
commit cfeb813b62
401 changed files with 11125 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
<!--------------------------------
- @Author: Ronnie Zhang
- @LastEditor: Ronnie Zhang
- @LastEditTime: 2023/12/04 22:51:04
- @Email: zclzone@outlook.com
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
--------------------------------->
<template>
<div class="auto-bg" :class="{ 'card-border': bordered }">
<slot />
</div>
</template>
<script setup>
defineProps({
bordered: Boolean,
})
</script>

View File

@@ -0,0 +1,31 @@
<!--------------------------------
- @Author: Ronnie Zhang
- @LastEditor: Ronnie Zhang
- @LastEditTime: 2023/12/04 22:51:12
- @Email: zclzone@outlook.com
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
--------------------------------->
<template>
<main class="cus-scroll h-full flex-col flex-1 bg-#f5f6fb dark:bg-#121212">
<transition name="fade-slide" mode="out-in" appear>
<main :class="{ 'flex-1': full }" class="m-12"><slot /></main>
</transition>
<slot v-if="$slots.footer" name="footer" />
<TheFooter v-else-if="showFooter" class="mb-12 mt-auto" />
<n-back-top :bottom="20" />
</main>
</template>
<script setup>
defineProps({
full: {
type: Boolean,
default: false,
},
showFooter: {
type: Boolean,
default: false,
},
})
</script>

View File

@@ -0,0 +1,74 @@
<!--------------------------------
- @Author: Ronnie Zhang
- @LastEditor: Ronnie Zhang
- @LastEditTime: 2023/12/04 22:51:21
- @Email: zclzone@outlook.com
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
--------------------------------->
<template>
<main class="h-full flex-1 overflow-hidden bg-#f5f6fb dark:bg-#121212">
<div class="h-full flex-col">
<AppCard
v-if="showHeader"
class="sticky top-0 z-1 min-h-60 flex items-center justify-between px-24"
border-b="1px solid light_border dark:dark_border"
>
<slot v-if="$slots.header" name="header" />
<template v-else>
<div class="flex items-center">
<slot v-if="$slots['title-prefix']" name="title-prefix" />
<template v-else-if="back">
<div
class="mr-16 flex cursor-pointer items-center text-16 opacity-60 transition-all-300 hover:opacity-40"
@click="router.back()"
>
<i class="i-material-symbols:arrow-left-alt" />
<span class="ml-4">返回</span>
</div>
</template>
<div class="mr-12 h-16 w-4 rounded-l-2 bg-primary"></div>
<h2 class="font-normal">{{ title ?? route.meta?.title }}</h2>
<slot name="title-suffix" />
</div>
<slot name="action" />
</template>
</AppCard>
<transition name="fade-slide" mode="out-in" appear>
<AppCard class="cus-scroll m-12 h-0 flex-1 rounded-8 p-24" bordered>
<slot />
</AppCard>
</transition>
</div>
</main>
<footer class="bg-#f5f6fb dark:bg-#121212">
<slot v-if="$slots.footer" name="footer" />
<AppCard v-else-if="showFooter" class="py-12">
<TheFooter />
</AppCard>
</footer>
</template>
<script setup>
defineProps({
back: {
type: Boolean,
default: false,
},
showFooter: {
type: Boolean,
default: false,
},
showHeader: {
type: Boolean,
default: true,
},
title: {
type: String,
default: undefined,
},
})
const route = useRoute()
const router = useRouter()
</script>

View File

@@ -0,0 +1,24 @@
<!--------------------------------
- @Description: 底部
- @Author: Ronnie Zhang
- @LastEditor: Ronnie Zhang
- @LastEditTime: 2023/12/04 22:42:33
- @Email: zclzone@outlook.com
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
--------------------------------->
<template>
<footer class="f-c-c text-14 text-gray-500">
<p>
Copyright © 2023
<a
href="https://github.com/zclzone"
target="__blank"
class="transition"
hover="decoration-underline color-primary"
>
Ronnie Zhang(大脸怪)
</a>
</p>
</footer>
</template>

View File

@@ -0,0 +1,4 @@
export { default as AppCard } from './AppCard.vue'
export { default as TheFooter } from './TheFooter.vue'
export { default as AppPage } from './AppPage.vue'
export { default as CommonPage } from './CommonPage.vue'

2
src/components/index.js Normal file
View File

@@ -0,0 +1,2 @@
export * from './common'
export * from './me'

View File

@@ -0,0 +1,35 @@
<!--------------------------------
- @Author: Ronnie Zhang
- @LastEditor: Ronnie Zhang
- @LastEditTime: 2023/12/04 22:51:48
- @Email: zclzone@outlook.com
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
--------------------------------->
<template>
<div class="flex items-center">
<label v-if="label || label === 0" class="flex-shrink-0" :style="{ width: labelWidth + 'px' }">
{{ label }}
</label>
<div :style="{ width: contentWidth + 'px' }" class="flex-shrink-0">
<slot />
</div>
</div>
</template>
<script setup>
defineProps({
label: {
type: String,
default: '',
},
labelWidth: {
type: Number,
default: 80,
},
contentWidth: {
type: Number,
default: 220,
},
})
</script>

View File

@@ -0,0 +1,167 @@
<!--------------------------------
- @Author: Ronnie Zhang
- @LastEditor: Ronnie Zhang
- @LastEditTime: 2023/12/04 22:51:42
- @Email: zclzone@outlook.com
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
--------------------------------->
<template>
<AppCard
v-if="$slots.default"
bordered
bg="#fafafc dark:black"
class="mb-30 min-h-60 flex justify-between rounded-4 p-16"
>
<n-space wrap :size="[32, 16]">
<slot />
</n-space>
<div class="flex-shrink-0">
<n-button ghost type="primary" @click="handleReset">
<i class="i-fe:rotate-ccw mr-4" />
重置
</n-button>
<n-button class="ml-20" type="primary" @click="handleSearch">
<i class="i-fe:search mr-4" />
搜索
</n-button>
</div>
</AppCard>
<n-data-table
:remote="remote"
:loading="loading"
:scroll-x="scrollX"
:columns="columns"
:data="tableData"
:row-key="(row) => row[rowKey]"
:pagination="isPagination ? pagination : false"
@update:checked-row-keys="onChecked"
@update:page="onPageChange"
/>
</template>
<script setup>
import { NDataTable } from 'naive-ui'
import { utils, writeFile } from 'xlsx'
const props = defineProps({
/**
* @remote true: 后端分页 false 前端分页
*/
remote: {
type: Boolean,
default: true,
},
/**
* @remote 是否分页
*/
isPagination: {
type: Boolean,
default: true,
},
scrollX: {
type: Number,
default: 1200,
},
rowKey: {
type: String,
default: 'id',
},
columns: {
type: Array,
required: true,
},
/** queryBar中的参数 */
queryItems: {
type: Object,
default() {
return {}
},
},
/**
* ! 约定接口入参出参
* * 分页模式需约定分页接口入参
* @pageSize 分页参数一页展示多少条默认10
* @pageNo 分页参数页码默认1
* * 需约定接口出参
* @pageData 分页模式必须,非分页模式如果没有pageData则取上一层data
* @total 分页模式必须非分页模式如果没有total则取上一层data.length
*/
getData: {
type: Function,
required: true,
},
})
const emit = defineEmits(['update:queryItems', 'onChecked', 'onDataChange'])
const loading = ref(false)
const initQuery = { ...props.queryItems }
const tableData = ref([])
const pagination = reactive({ page: 1, pageSize: 10 })
async function handleQuery() {
try {
loading.value = true
let paginationParams = {}
// 如果非分页模式或者使用前端分页,则无需传分页参数
if (props.isPagination && props.remote) {
paginationParams = { pageNo: pagination.page, pageSize: pagination.pageSize }
}
const { data } = await props.getData({
...props.queryItems,
...paginationParams,
})
tableData.value = data?.pageData || data
pagination.itemCount = data.total ?? data.length
} catch (error) {
tableData.value = []
pagination.itemCount = 0
} finally {
emit('onDataChange', tableData.value)
loading.value = false
}
}
function handleSearch() {
pagination.page = 1
handleQuery()
}
async function handleReset() {
const queryItems = { ...props.queryItems }
for (const key in queryItems) {
queryItems[key] = ''
}
emit('update:queryItems', { ...queryItems, ...initQuery })
await nextTick()
pagination.page = 1
handleQuery()
}
function onPageChange(currentPage) {
pagination.page = currentPage
if (props.remote) {
handleQuery()
}
}
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('没有数据')
const columnsData = columns.filter((item) => !!item.title && !item.hideInExcel)
const thKeys = columnsData.map((item) => item.key)
const thData = columnsData.map((item) => item.title)
const trData = data.map((item) => thKeys.map((key) => item[key]))
const sheet = utils.aoa_to_sheet([thData, ...trData])
const workBook = utils.book_new()
utils.book_append_sheet(workBook, sheet, '数据报表')
writeFile(workBook, '数据报表.xlsx')
}
defineExpose({
handleSearch,
handleReset,
handleExport,
})
</script>

View File

@@ -0,0 +1,3 @@
export { default as MeModal } from './modal/index.vue'
export { default as MeCrud } from './crud/index.vue'
export { default as MeQueryItem } from './crud/QueryItem.vue'

View File

@@ -0,0 +1,171 @@
<!--------------------------------
- @Author: Ronnie Zhang
- @LastEditor: Ronnie Zhang
- @LastEditTime: 2023/12/04 22:51:58
- @Email: zclzone@outlook.com
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
--------------------------------->
<template>
<n-modal
v-model:show="show"
:style="{ width: modalOptions.width, ...modalOptions.style }"
:preset="undefined"
size="huge"
:bordered="false"
>
<n-card
:title="modalOptions.title"
:style="modalOptions.contentStyle"
:closable="modalOptions.closable"
@close="close()"
>
<slot></slot>
<!-- 底部按钮 -->
<template #footer>
<slot v-if="$slots.footer" name="footer" />
<footer v-else-if="modalOptions.showFooter" class="flex justify-end">
<n-button v-if="modalOptions.showCancel" @click="handleCancel()">
{{ modalOptions.cancelText }}
</n-button>
<n-button
v-if="modalOptions.showOk"
type="primary"
:loading="modalOptions.okLoading"
class="ml-20"
@click="handleOk()"
>
{{ modalOptions.okText }}
</n-button>
</footer>
</template>
</n-card>
</n-modal>
</template>
<script setup>
const props = defineProps({
width: {
type: String,
default: '800px',
},
title: {
type: String,
default: '',
},
closable: {
type: Boolean,
default: true,
},
cancelText: {
type: String,
default: '取消',
},
okText: {
type: String,
default: '确定',
},
showFooter: {
type: Boolean,
default: true,
},
showCancel: {
type: Boolean,
default: true,
},
showOk: {
type: Boolean,
default: true,
},
style: {
type: Object,
default: () => ({}),
},
contentStyle: {
type: Object,
default: () => ({}),
},
onOk: {
type: Function,
default: () => {},
},
onCancel: {
type: Function,
default: () => {},
},
})
// 声明一个show变量用于控制模态框的显示与隐藏
const show = ref(false)
// 声明一个modalOptions变量用于存储模态框的配置信息
const modalOptions = ref({})
// 打开模态框
function open(options = {}) {
// 将props和options合并赋值给modalOptions
modalOptions.value = { ...props, ...options }
// 将show的值设置为true
show.value = true
}
// 定义一个close函数用于关闭模态框
function close() {
show.value = false
}
// 定义一个handleOk函数用于处理模态框确定操作
async function handleOk(data) {
// 如果modalOptions中没有onOk函数则直接关闭模态框
if (typeof modalOptions.value.onOk !== 'function') {
return close()
}
try {
// 调用onOk函数传入data参数
const res = await modalOptions.value.onOk(data)
// 如果onOk函数的返回值不为false则关闭模态框
res !== false && close()
} catch (error) {
// 如果出现异常,则打印错误信息
console.error(error)
}
}
// 定义一个handleCancel函数用于处理模态框取消操作
async function handleCancel(data) {
// 如果modalOptions中没有onCancel函数则直接关闭模态框
if (typeof modalOptions.value.onCancel !== 'function') {
return close()
}
try {
// 调用onCancel函数传入data参数
const res = await modalOptions.value.onCancel(data)
// 如果onCancel函数的返回值不为false则关闭模态框
res !== false && close()
} catch (error) {
// 如果出现异常,则打印错误信息
console.error(error)
}
}
const okLoading = computed({
get() {
return !!modalOptions.value?.okLoading
},
set(v) {
if (modalOptions.value) {
modalOptions.value.okLoading = v
}
},
})
// 定义一个defineExpose函数用于暴露open、close、handleOk、handleCancel函数
defineExpose({
open,
close,
handleOk,
handleCancel,
okLoading,
options: modalOptions,
})
</script>