├── .nvmrc ├── .npmrc ├── src ├── components │ ├── directives.js │ ├── Table │ │ └── index.js │ ├── Grid │ │ ├── index.js │ │ └── context.js │ ├── Filter │ │ ├── index.js │ │ ├── Filter.vue │ │ └── FilterItem.vue │ ├── Search │ │ ├── index.js │ │ └── SearchItem.vue │ ├── Cropper │ │ └── index.js │ ├── DemoBox │ │ ├── index.js │ │ ├── context.js │ │ └── DemoBoxGroup.vue │ ├── Loading │ │ ├── index.js │ │ ├── loading.js │ │ └── directive.js │ ├── Upload │ │ ├── index.js │ │ └── config.js │ ├── InfiniteScroll │ │ └── index.js │ ├── Popover │ │ └── index.js │ ├── Transfer │ │ └── config.js │ ├── TagSelect │ │ └── context.js │ ├── ResizeBox │ │ └── config.js │ ├── Preview │ │ ├── config.js │ │ └── index.js │ ├── Editor │ │ ├── index.less │ │ └── utils.js │ ├── index.js │ ├── Breadcrumb │ │ └── Breadcrumb.vue │ ├── Toolbar │ │ └── Toolbar.vue │ └── SendCode │ │ └── SendCode.vue ├── styles │ ├── antd │ │ ├── input.less │ │ ├── dropdown.less │ │ ├── card.less │ │ ├── radio.less │ │ ├── button.less │ │ ├── form.less │ │ ├── page_header.less │ │ ├── avatar.less │ │ ├── checkbox.less │ │ ├── index.less │ │ ├── tabs.less │ │ ├── modal.less │ │ └── tree.less │ ├── mixins │ │ ├── index.less │ │ ├── ellipsis.less │ │ └── scrollbar.less │ ├── index.less │ ├── scrollbar.less │ └── reset.less ├── assets │ ├── avatar.jpg │ ├── cropper.png │ ├── login_aside_bg.jpg │ └── logo.svg ├── hooks │ ├── useColors │ │ └── index.js │ ├── useProgress │ │ ├── index.js │ │ └── index.less │ ├── useMenu │ │ └── index.js │ ├── index.js │ ├── useConfigProvider │ │ └── index.js │ ├── useCheckUpdate │ │ └── index.js │ ├── useForm │ │ └── index.js │ └── usePagination │ │ └── index.js ├── config │ ├── modules │ │ ├── router.js │ │ ├── http.js │ │ ├── app.js │ │ └── storage.js │ └── index.js ├── core │ ├── exception.js │ └── index.js ├── directives │ ├── index.js │ └── action.js ├── main.js ├── enums │ └── system.js ├── apis │ ├── index.js │ └── modules │ │ ├── user.js │ │ ├── system.js │ │ └── common.js ├── layouts │ ├── RouteViewLayout.vue │ ├── index.js │ ├── hooks │ │ └── useMultiTab.js │ ├── CustomLayout.vue │ └── components │ │ ├── LayoutTransition.vue │ │ ├── IframeView.vue │ │ ├── ActionButton.vue │ │ └── BasicContent.vue ├── utils │ ├── index.js │ ├── storage.js │ ├── get.js │ └── format.js ├── views │ ├── components │ │ ├── button │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ └── Basic.vue │ │ ├── breadcrumb │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ └── Basic.vue │ │ ├── form-table │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ └── Basic.vue │ │ ├── editor │ │ │ ├── components │ │ │ │ ├── Basic.vue │ │ │ │ └── Inline.vue │ │ │ └── index.vue │ │ ├── resize-box │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ ├── Basic.vue │ │ │ │ └── Size.vue │ │ ├── tree │ │ │ └── index.vue │ │ ├── action-button │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ └── Basic.vue │ │ ├── filter │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ └── Custom.vue │ │ ├── cascader │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ ├── Advanced.vue │ │ │ │ └── Basic.vue │ │ ├── ellipsis │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ ├── Basic.vue │ │ │ │ └── Tooltip.vue │ │ ├── toolbar │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ ├── Bordered.vue │ │ │ │ └── Basic.vue │ │ ├── infinite-scroll │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ ├── Error.vue │ │ │ │ └── Basic.vue │ │ ├── send-code │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ ├── Basic.vue │ │ │ │ ├── Custom.vue │ │ │ │ └── Reset.vue │ │ ├── preview │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ ├── Basic.vue │ │ │ │ ├── Media.vue │ │ │ │ └── Multiple.vue │ │ ├── search │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ ├── Basic.vue │ │ │ │ └── Custom.vue │ │ ├── transfer │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ └── Basic.vue │ │ ├── table-column-setting │ │ │ └── index.vue │ │ ├── tag │ │ │ ├── components │ │ │ │ ├── Color.vue │ │ │ │ ├── Basic.vue │ │ │ │ ├── Secondary.vue │ │ │ │ ├── Bordered.vue │ │ │ │ ├── Shape.vue │ │ │ │ └── Closeable.vue │ │ │ └── index.vue │ │ ├── upload │ │ │ ├── components │ │ │ │ ├── Round.vue │ │ │ │ ├── Text.vue │ │ │ │ ├── Cropper.vue │ │ │ │ ├── Multiple.vue │ │ │ │ ├── Basic.vue │ │ │ │ ├── Input.vue │ │ │ │ └── Slot.vue │ │ │ └── index.vue │ │ ├── modal │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ ├── Basic.vue │ │ │ │ ├── Drag.vue │ │ │ │ └── Async.vue │ │ ├── cropper │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ ├── Basic.vue │ │ │ │ ├── Ratio.vue │ │ │ │ └── Dialog.vue │ │ ├── tag-select │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ ├── Multiple.vue │ │ │ │ ├── Unlimited.vue │ │ │ │ └── Basic.vue │ │ ├── scrollbar │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ ├── Basic.vue │ │ │ │ ├── Horizontal.vue │ │ │ │ └── MaxHeight.vue │ │ ├── qrcode │ │ │ ├── components │ │ │ │ ├── Icon.vue │ │ │ │ ├── Color.vue │ │ │ │ ├── Basic.vue │ │ │ │ ├── Popover.vue │ │ │ │ ├── Error.vue │ │ │ │ ├── Status.vue │ │ │ │ ├── Download.vue │ │ │ │ └── Size.vue │ │ │ └── index.vue │ │ ├── grid │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ ├── Offset.vue │ │ │ │ ├── Basic.vue │ │ │ │ ├── ResponsiveColumns.vue │ │ │ │ ├── Responsive.vue │ │ │ │ └── Gutter.vue │ │ ├── loading │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ ├── GlobalBasic.vue │ │ │ │ ├── DirectiveBasic.vue │ │ │ │ ├── DirectiveDescription.vue │ │ │ │ └── GlobalSize.vue │ │ └── chart │ │ │ ├── index.vue │ │ │ └── components │ │ │ ├── GaugeChart.vue │ │ │ ├── LineChart.vue │ │ │ ├── BarChart.vue │ │ │ ├── PieChart.vue │ │ │ ├── RadarChart.vue │ │ │ └── KlineChart.vue │ ├── user │ │ ├── center │ │ │ ├── components │ │ │ │ ├── AppList.vue │ │ │ │ └── ProjectList.vue │ │ │ └── index.vue │ │ └── setting │ │ │ └── components │ │ │ ├── Custom.vue │ │ │ ├── Safe.vue │ │ │ └── Message.vue │ ├── exception │ │ ├── 403.vue │ │ ├── 500.vue │ │ └── 404.vue │ ├── other │ │ └── badge │ │ │ └── index.vue │ ├── iframe │ │ └── index.vue │ ├── list │ │ └── search │ │ │ └── components │ │ │ └── PageHeader.vue │ └── result │ │ └── fail │ │ └── index.vue ├── router │ ├── routes │ │ ├── home.js │ │ ├── index.js │ │ ├── link.js │ │ ├── user.js │ │ ├── result.js │ │ ├── profile.js │ │ ├── iframe.js │ │ ├── exception.js │ │ ├── form.js │ │ └── other.js │ ├── config.js │ └── index.js ├── App.vue └── store │ └── index.js ├── .vscode └── extensions.json ├── commitlint.config.mjs ├── public ├── favicon.ico ├── images │ ├── wechat-qrcode.jpg │ └── logo.svg └── libs │ └── tinymce │ ├── langs │ └── README.md │ ├── license.md │ ├── skins │ ├── ui │ │ ├── oxide │ │ │ ├── skin.shadowdom.min.css │ │ │ └── skin.shadowdom.js │ │ ├── tinymce-5 │ │ │ ├── skin.shadowdom.min.css │ │ │ └── skin.shadowdom.js │ │ ├── oxide-dark │ │ │ ├── skin.shadowdom.min.css │ │ │ └── skin.shadowdom.js │ │ └── tinymce-5-dark │ │ │ ├── skin.shadowdom.min.css │ │ │ └── skin.shadowdom.js │ └── content │ │ ├── default │ │ ├── content.min.css │ │ └── content.js │ │ ├── tinymce-5 │ │ └── content.min.css │ │ └── writer │ │ └── content.min.css │ ├── notices.txt │ └── plugins │ ├── code │ └── plugin.min.js │ └── visualblocks │ └── plugin.min.js ├── docs ├── hooks │ ├── use-progress.md │ ├── use-check-update.md │ ├── use-pagination.md │ ├── use-menu.md │ ├── use-modal.md │ ├── use-multi-tab.md │ ├── use-colors.md │ ├── use-config-provider.md │ └── use-form.md ├── public │ └── images │ │ └── login.png ├── components │ ├── breadcrumb.md │ ├── index.md │ ├── action-button.md │ ├── filter.md │ ├── form-table.md │ ├── preview.md │ ├── button.md │ ├── toolbar.md │ ├── upload-input.md │ ├── editor.md │ ├── table-column-setting.md │ ├── loading.md │ ├── send-code.md │ ├── tree.md │ └── scrollbar.md ├── .vitepress │ ├── theme │ │ ├── Layout.vue │ │ └── index.js │ ├── nav.js │ └── config.js ├── guide │ ├── authority-management.md │ └── server.md ├── directives │ └── v-loading.md ├── utils │ ├── assets.md │ ├── deepMerge.md │ └── deep.md └── index.md ├── .husky ├── commit-msg └── pre-commit ├── config ├── useVuePlugin.js ├── useProgressPlugin.js ├── useCompressPlugin.js ├── useServer.js ├── useVisualizerPlugin.js └── useDemoPlugin.js ├── postcss.config.cjs ├── .env ├── .env.prod ├── .env.test ├── .env.pre ├── .gitignore ├── LICENSE └── eslint.config.mjs /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com/ -------------------------------------------------------------------------------- /src/components/directives.js: -------------------------------------------------------------------------------- 1 | export { vLoading } from './Loading' 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /src/styles/antd/input.less: -------------------------------------------------------------------------------- 1 | textarea.ant-input { 2 | resize: none; 3 | } 4 | -------------------------------------------------------------------------------- /commitlint.config.mjs: -------------------------------------------------------------------------------- 1 | export default { extends: ['@commitlint/config-conventional'] } 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengxianghan/xy-admin/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengxianghan/xy-admin/HEAD/src/assets/avatar.jpg -------------------------------------------------------------------------------- /src/assets/cropper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengxianghan/xy-admin/HEAD/src/assets/cropper.png -------------------------------------------------------------------------------- /src/styles/antd/dropdown.less: -------------------------------------------------------------------------------- 1 | .ant-dropdown-menu-submenu-title { 2 | border-radius: 4px; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/Table/index.js: -------------------------------------------------------------------------------- 1 | export { default as TableColumnSetting } from './TableColumnSetting.vue' 2 | -------------------------------------------------------------------------------- /src/styles/antd/card.less: -------------------------------------------------------------------------------- 1 | .ant-card { 2 | .ant-card-head { 3 | margin-bottom: 0; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/styles/antd/radio.less: -------------------------------------------------------------------------------- 1 | .ant-radio-wrapper .ant-radio { 2 | margin-block-start: 0.025rem; 3 | } 4 | -------------------------------------------------------------------------------- /docs/hooks/use-progress.md: -------------------------------------------------------------------------------- 1 | # useProgress 2 | 3 | ## 代码演示 4 | 5 | <<< @/../src/core/permission.js{1,6,20,64} 6 | -------------------------------------------------------------------------------- /docs/public/images/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengxianghan/xy-admin/HEAD/docs/public/images/login.png -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint -e 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install lint-staged 5 | -------------------------------------------------------------------------------- /src/assets/login_aside_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengxianghan/xy-admin/HEAD/src/assets/login_aside_bg.jpg -------------------------------------------------------------------------------- /public/images/wechat-qrcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengxianghan/xy-admin/HEAD/public/images/wechat-qrcode.jpg -------------------------------------------------------------------------------- /src/hooks/useColors/index.js: -------------------------------------------------------------------------------- 1 | import * as colors from '@ant-design/colors' 2 | 3 | export default () => ({ ...colors }) 4 | -------------------------------------------------------------------------------- /config/useVuePlugin.js: -------------------------------------------------------------------------------- 1 | import vue from '@vitejs/plugin-vue' 2 | 3 | export default () => { 4 | return vue() 5 | } 6 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/styles/mixins/index.less: -------------------------------------------------------------------------------- 1 | @import './color/colors.less'; 2 | @import './ellipsis.less'; 3 | @import './scrollbar.less'; 4 | -------------------------------------------------------------------------------- /src/components/Grid/index.js: -------------------------------------------------------------------------------- 1 | export { default as Grid } from './Grid.vue' 2 | export { default as GridItem } from './GridItem.vue' 3 | -------------------------------------------------------------------------------- /src/styles/antd/button.less: -------------------------------------------------------------------------------- 1 | .ant-btn { 2 | display: inline-flex; 3 | align-items: center; 4 | justify-content: center; 5 | } 6 | -------------------------------------------------------------------------------- /config/useProgressPlugin.js: -------------------------------------------------------------------------------- 1 | import progress from 'vite-plugin-progress' 2 | 3 | export default () => { 4 | return progress() 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Filter/index.js: -------------------------------------------------------------------------------- 1 | export { default as Filter } from './Filter.vue' 2 | export { default as FilterItem } from './FilterItem.vue' 3 | -------------------------------------------------------------------------------- /src/components/Search/index.js: -------------------------------------------------------------------------------- 1 | export { default as Search } from './Search.vue' 2 | export { default as SearchItem } from './SearchItem.vue' 3 | -------------------------------------------------------------------------------- /src/components/Cropper/index.js: -------------------------------------------------------------------------------- 1 | export { default as Cropper } from './Cropper.vue' 2 | export { default as CropperDialog } from './CropperDialog.vue' 3 | -------------------------------------------------------------------------------- /src/components/DemoBox/index.js: -------------------------------------------------------------------------------- 1 | export { default as DemoBox } from './DemoBox.vue' 2 | export { default as DemoBoxGroup } from './DemoBoxGroup.vue' 3 | -------------------------------------------------------------------------------- /src/components/Loading/index.js: -------------------------------------------------------------------------------- 1 | import vLoading from './directive' 2 | import { Loading } from './loading.js' 3 | 4 | export { Loading, vLoading } 5 | -------------------------------------------------------------------------------- /src/config/modules/router.js: -------------------------------------------------------------------------------- 1 | export default { 2 | base: import.meta.env.VITE_ROUTER_BASE, 3 | history: import.meta.env.VITE_ROUTER_HISTORY, 4 | } 5 | -------------------------------------------------------------------------------- /src/components/Upload/index.js: -------------------------------------------------------------------------------- 1 | export { default as UploadImage } from './UploadImage.vue' 2 | export { default as UploadInput } from './UploadInput.vue' 3 | -------------------------------------------------------------------------------- /src/styles/antd/form.less: -------------------------------------------------------------------------------- 1 | .ant-form-item { 2 | .ant-radio-group, 3 | .ant-checkbox-group { 4 | padding-block: 5px; 5 | row-gap: 4px; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /docs/components/breadcrumb.md: -------------------------------------------------------------------------------- 1 | # Breadcrumb 面包屑 2 | 3 | ## 代码演示 4 | 5 | ### 基础用法 6 | 7 | <<< @/../src/views/components/breadcrumb/components/Basic.vue#snippet 8 | -------------------------------------------------------------------------------- /src/config/modules/http.js: -------------------------------------------------------------------------------- 1 | export default { 2 | apiBasic: import.meta.env.VITE_API_BASIC, 3 | code: { 4 | ignore: [], 5 | success: 200, 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /src/core/exception.js: -------------------------------------------------------------------------------- 1 | export function setupException(app) { 2 | app.config.errorHandler = (err) => { 3 | console.error(err) 4 | } 5 | return app 6 | } 7 | -------------------------------------------------------------------------------- /src/components/InfiniteScroll/index.js: -------------------------------------------------------------------------------- 1 | export { default as InfiniteScroll } from './InfiniteScroll.vue' 2 | export { default as useInfiniteScroll } from './useInfiniteScroll' 3 | -------------------------------------------------------------------------------- /src/directives/index.js: -------------------------------------------------------------------------------- 1 | import { setupActionDirective } from './action' 2 | 3 | export function setupDirective(app) { 4 | setupActionDirective(app) 5 | return app 6 | } 7 | -------------------------------------------------------------------------------- /src/config/modules/app.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: import.meta.env.VITE_TITLE, 3 | logo: `${import.meta.env.BASE_URL}images/logo.svg`, 4 | mock: import.meta.env.VITE_MOKE, 5 | } 6 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from '@/App.vue' 3 | import { useCore } from '@/core' 4 | 5 | const app = createApp(App) 6 | useCore(app) 7 | app.mount('#app') 8 | -------------------------------------------------------------------------------- /docs/hooks/use-check-update.md: -------------------------------------------------------------------------------- 1 | # useCheckUpdate 2 | 3 | 版本更新后,提示用户刷新页面。 4 | 5 | ## 代码演示 6 | 7 | ::: tip 8 | 在入口文件中引入即可,无需做任何配置 9 | ::: 10 | 11 | <<< ../../src/core/index.js {7,12} 12 | -------------------------------------------------------------------------------- /public/libs/tinymce/langs/README.md: -------------------------------------------------------------------------------- 1 | This is where language files should be placed. 2 | 3 | Please DO NOT translate these directly, use this service instead: https://crowdin.com/project/tinymce 4 | -------------------------------------------------------------------------------- /src/enums/system.js: -------------------------------------------------------------------------------- 1 | import Enum from 'xy-enum' 2 | 3 | // 菜单类型 4 | export const menuTypeEnum = new Enum([ 5 | { key: 'menu', value: 1, desc: '菜单' }, 6 | { key: 'button', value: 2, desc: '按钮' }, 7 | ]) 8 | -------------------------------------------------------------------------------- /config/useCompressPlugin.js: -------------------------------------------------------------------------------- 1 | import compressPlugin from 'vite-plugin-compression' 2 | 3 | export default () => { 4 | return compressPlugin({ 5 | ext: '.gz', 6 | deleteOriginFile: false, 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /src/apis/index.js: -------------------------------------------------------------------------------- 1 | import * as common from './modules/common' 2 | import * as system from './modules/system' 3 | import * as user from './modules/user' 4 | 5 | export default { 6 | common, 7 | system, 8 | user, 9 | } 10 | -------------------------------------------------------------------------------- /src/layouts/RouteViewLayout.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/Popover/index.js: -------------------------------------------------------------------------------- 1 | import PopoverConstructor from './Popover.vue' 2 | import usePopover from './usePopover' 3 | 4 | const Popover = usePopover({ 5 | view: PopoverConstructor, 6 | }) 7 | 8 | export { Popover, usePopover } 9 | -------------------------------------------------------------------------------- /src/config/modules/storage.js: -------------------------------------------------------------------------------- 1 | export default { 2 | namespace: import.meta.env.VITE_STORAGE_NAMESPACE, 3 | domain: import.meta.env.VITE_STORAGE_DOMAIN, 4 | token: 'token', 5 | userInfo: 'user_info', 6 | config: 'config', 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export * from './basic' 2 | export * from './format' 3 | export * from './get' 4 | export * from './is' 5 | export { default as request } from './request' 6 | export { default as storage } from './storage' 7 | export * from './to' 8 | -------------------------------------------------------------------------------- /src/hooks/useProgress/index.js: -------------------------------------------------------------------------------- 1 | import NProgress from 'nprogress' 2 | import './index.less' 3 | 4 | export default (options) => { 5 | NProgress.configure({ 6 | ...options, 7 | showSpinner: false, 8 | }) 9 | return NProgress 10 | } 11 | -------------------------------------------------------------------------------- /src/views/components/button/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/Layout.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/views/components/breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/views/components/form-table/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/styles/antd/page_header.less: -------------------------------------------------------------------------------- 1 | .ant-page-header { 2 | &[main] { 3 | margin: -16px -16px 16px; 4 | } 5 | 6 | &[round] { 7 | border-radius: @border-radius-lg; 8 | } 9 | 10 | &[bordered] { 11 | border: @color-split solid 1px; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/styles/antd/avatar.less: -------------------------------------------------------------------------------- 1 | .ant-avatar { 2 | display: inline-flex; 3 | align-items: center; 4 | justify-content: center; 5 | 6 | .ant-avatar-string { 7 | display: inline-flex; 8 | align-items: center; 9 | justify-content: center; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # app 2 | VITE_TITLE=XYAdmin 3 | VITE_PUBLIC_PATH=/ 4 | VITE_OUT_DIR=dist 5 | 6 | # router 7 | VITE_ROUTER_BASE=/ 8 | VITE_ROUTER_HISTORY=history 9 | 10 | # api 11 | VITE_API_BASIC=/api 12 | 13 | # storage 14 | VITE_STORAGE_NAMESPACE=xyadmin_local_ 15 | VITE_STORAGE_DOMAIN= 16 | -------------------------------------------------------------------------------- /src/layouts/index.js: -------------------------------------------------------------------------------- 1 | import BasicLayout from './BasicLayout.vue' 2 | import CustomLayout from './CustomLayout.vue' 3 | import RouteViewLayout from './RouteViewLayout.vue' 4 | import UserLayout from './UserLayout.vue' 5 | 6 | export { BasicLayout, CustomLayout, RouteViewLayout, UserLayout } 7 | -------------------------------------------------------------------------------- /src/styles/index.less: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | img, 7 | svg { 8 | display: inline; 9 | } 10 | } 11 | 12 | @import './reset'; 13 | @import './utils'; 14 | @import './antd/index'; 15 | @import './scrollbar'; 16 | -------------------------------------------------------------------------------- /.env.prod: -------------------------------------------------------------------------------- 1 | # app 2 | VITE_TITLE=XYAdmin 3 | VITE_PUBLIC_PATH=/ 4 | VITE_OUT_DIR=dist 5 | 6 | # router 7 | VITE_ROUTER_HISTORY=hash 8 | 9 | # api 10 | VITE_API_BASIC=https://mock.apifox.cn/m1/3156808-0-default 11 | 12 | # storage 13 | VITE_STORAGE_NAMESPACE=xyadmin_ 14 | VITE_STORAGE_DOMAIN= 15 | -------------------------------------------------------------------------------- /docs/hooks/use-pagination.md: -------------------------------------------------------------------------------- 1 | # usePagination 2 | 3 | ## 代码演示 4 | 5 | ### 基础用法 6 | 7 | :::tip 8 | 主要查看 `listData` `paginationState` `loading` `showLoading` `hideLoading` `resetPagination` `searchFormData` `refreshPagination` 9 | ::: 10 | 11 | <<< @/../src/views/list/table/index.vue{164-173} 12 | -------------------------------------------------------------------------------- /src/views/user/center/components/AppList.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | # app 2 | VITE_TITLE=XYAdmin 3 | VITE_PUBLIC_PATH=/ 4 | VITE_OUT_DIR=dist 5 | 6 | # router 7 | VITE_ROUTER_HISTORY=hash 8 | 9 | # api 10 | VITE_API_BASIC=https://mock.apifox.cn/m1/3156808-0-default 11 | 12 | # storage 13 | VITE_STORAGE_NAMESPACE=xyadmin_test_ 14 | VITE_STORAGE_DOMAIN= 15 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.js: -------------------------------------------------------------------------------- 1 | import DefaultTheme, { VPBadge } from 'vitepress/theme' 2 | import Layout from './Layout.vue' 3 | import './index.css' 4 | 5 | export default { 6 | ...DefaultTheme, 7 | Layout, 8 | enhanceApp({ app }) { 9 | app.component('Badge', VPBadge) 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /src/views/components/editor/components/Basic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法 3 | 包含最常见用例所需的插件。 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/views/components/resize-box/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/views/components/tree/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/views/user/center/components/ProjectList.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/views/components/action-button/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/views/components/editor/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/views/components/filter/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/views/components/breadcrumb/components/Basic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法 3 | 虽然没什么用,但它还是诞生了。 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/views/components/cascader/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/views/components/ellipsis/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/views/components/toolbar/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/apis/modules/user.js: -------------------------------------------------------------------------------- 1 | import { request } from '@/utils' 2 | 3 | // 登录 4 | export const login = params => request.basic.post('/user/login', params) 5 | // 获取用户详情 6 | export const getUserDetail = () => request.basic.get('/user/detail') 7 | // 获取权限列表 8 | export const getAuthList = () => request.basic.get('/user/auth') 9 | -------------------------------------------------------------------------------- /.env.pre: -------------------------------------------------------------------------------- 1 | # app 2 | VITE_TITLE=XYAdmin 3 | VITE_PUBLIC_PATH=/ 4 | VITE_OUT_DIR=dist 5 | 6 | # router 7 | VITE_ROUTER_BASE=/ 8 | VITE_ROUTER_HISTORY=hash 9 | 10 | # api 11 | VITE_API_BASIC=https://mock.apifox.cn/m1/3156808-0-default 12 | 13 | # storage 14 | VITE_STORAGE_NAMESPACE=xyadmin_pre_ 15 | VITE_STORAGE_DOMAIN= 16 | -------------------------------------------------------------------------------- /config/useServer.js: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | host: '0.0.0.0', 3 | strictPort: false, 4 | proxy: { 5 | '/api': { 6 | target: 'https://m1.apifoxmock.com/m1/3156808-1518008-default', 7 | changeOrigin: true, 8 | rewrite: path => path.replace('/api', ''), 9 | }, 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /public/libs/tinymce/license.md: -------------------------------------------------------------------------------- 1 | # Software License Agreement 2 | 3 | **TinyMCE** – [](https://github.com/tinymce/tinymce) 4 | Copyright (c) 2024, Ephox Corporation DBA Tiny Technologies, Inc. 5 | 6 | Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). 7 | -------------------------------------------------------------------------------- /src/styles/antd/checkbox.less: -------------------------------------------------------------------------------- 1 | .ant-checkbox { 2 | margin-block-start: 0.025rem; 3 | } 4 | 5 | .ant-checkbox-wrapper.ant-checkbox-wrapper-in-form-item input[type='checkbox'] { 6 | width: auto; 7 | height: auto; 8 | } 9 | 10 | .ant-checkbox .ant-checkbox-input { 11 | left: 0; 12 | right: 0; 13 | top: 0; 14 | bottom: 0; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Transfer/config.js: -------------------------------------------------------------------------------- 1 | import XYEnum from 'xy-enum' 2 | 3 | export const DIRECTION_ENUM = new XYEnum([ 4 | { key: 'left', value: 'left', desc: '左侧' }, 5 | { key: 'right', value: 'right', desc: '右侧' }, 6 | ]) 7 | 8 | export const TRANSFER_KEY = Symbol('transfer') 9 | 10 | export const TRANSFER_LIST_KEY = Symbol('transfer_list') 11 | -------------------------------------------------------------------------------- /src/router/routes/home.js: -------------------------------------------------------------------------------- 1 | import { SmileOutlined } from '@ant-design/icons-vue' 2 | 3 | export default [ 4 | { 5 | path: 'welcome', 6 | name: 'welcome', 7 | component: 'welcome/index.vue', 8 | meta: { 9 | icon: SmileOutlined, 10 | title: '欢迎页', 11 | isMenu: true, 12 | keepAlive: true, 13 | }, 14 | }, 15 | ] 16 | -------------------------------------------------------------------------------- /src/router/routes/index.js: -------------------------------------------------------------------------------- 1 | const modules = import.meta.glob('./*.js', { eager: true }) 2 | const routes = [] 3 | 4 | Object.keys(modules).forEach((key) => { 5 | const name = key.slice(key.lastIndexOf('/') + 1, key.lastIndexOf('.')) 6 | if (name !== 'index') { 7 | routes.push(...modules[key].default) 8 | } 9 | }) 10 | 11 | export default routes 12 | -------------------------------------------------------------------------------- /src/views/components/infinite-scroll/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/views/components/send-code/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/views/components/preview/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/views/components/search/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/styles/antd/index.less: -------------------------------------------------------------------------------- 1 | @import './avatar.less'; 2 | @import './button.less'; 3 | @import './card.less'; 4 | @import './checkbox.less'; 5 | @import './dropdown.less'; 6 | @import './form.less'; 7 | @import './modal.less'; 8 | @import './page_header.less'; 9 | @import './radio.less'; 10 | @import './tabs.less'; 11 | @import './tree.less'; 12 | @import './input.less'; 13 | -------------------------------------------------------------------------------- /src/components/Upload/config.js: -------------------------------------------------------------------------------- 1 | import Enum from 'xy-enum' 2 | 3 | // 状态 4 | export const STATUS_ENUM = new Enum([ 5 | { key: 'wait', value: 1, desc: '等待上传' }, 6 | { key: 'uploading', value: 2, desc: '上传中' }, 7 | { key: 'done', value: 3, desc: '上传完成' }, 8 | { key: 'error', value: 4, desc: '上传错误' }, 9 | { key: 'removed', value: 5, desc: '已移除' }, 10 | ]) 11 | -------------------------------------------------------------------------------- /src/views/components/transfer/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/views/components/table-column-setting/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/hooks/useMenu/index.js: -------------------------------------------------------------------------------- 1 | import { useRouterStore } from '@/store' 2 | 3 | export default () => { 4 | const routerStore = useRouterStore() 5 | 6 | /** 7 | * 设置徽标 8 | * @param {string} name 路由名称 9 | * @param {number} count 数量 10 | */ 11 | function setBadge(name, count) { 12 | routerStore.setBadge({ name, count }) 13 | } 14 | 15 | return { 16 | setBadge, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/hooks/useProgress/index.less: -------------------------------------------------------------------------------- 1 | @import url('nprogress/nprogress.css'); 2 | 3 | #nprogress { 4 | .bar { 5 | background: @color-primary; 6 | } 7 | 8 | .peg { 9 | box-shadow: 10 | 0 0 10px @color-primary, 11 | 0 0 5px @color-primary; 12 | } 13 | 14 | .spinner-icon { 15 | border-top-color: @color-primary; 16 | border-left-color: @color-primary; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/styles/mixins/ellipsis.less: -------------------------------------------------------------------------------- 1 | .multi-ellipsis(@lines) { 2 | display: -webkit-box; 3 | overflow: hidden; 4 | text-overflow: ellipsis; 5 | -webkit-line-clamp: @lines; 6 | line-break: anywhere; 7 | 8 | /* autoprefixer: ignore next */ 9 | -webkit-box-orient: vertical; 10 | } 11 | 12 | .ellipsis() { 13 | overflow: hidden; 14 | white-space: nowrap; 15 | text-overflow: ellipsis; 16 | } 17 | -------------------------------------------------------------------------------- /src/views/components/tag/components/Color.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 颜色 3 | 使用一个颜色对象定制标签的颜色。 4 | 5 | 6 | 7 | 8 | 9 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/views/components/upload/components/Round.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 圆角 3 | 圆角 4 | 5 | 6 | 7 | 12 | 13 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/views/components/modal/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/views/components/editor/components/Inline.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 内联编辑器 3 | 展示TinyMCE的内联编辑功能。 4 | 5 | 6 | 7 | 14 | 15 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/views/components/upload/components/Text.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 自定义文案 3 | 自定义文案 4 | 5 | 6 | 7 | 12 | 13 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/views/components/cropper/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | */.vitepress/cache/**/* 15 | */.vitepress/.temp/** 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/views/components/tag-select/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /config/useVisualizerPlugin.js: -------------------------------------------------------------------------------- 1 | import process from 'node:process' 2 | import visualizer from 'rollup-plugin-visualizer' 3 | 4 | const lifecycle = process.env.npm_lifecycle_event 5 | 6 | export default () => { 7 | if (lifecycle === 'report') { 8 | return visualizer({ 9 | filename: './node_modules/.cache/visualizer/stats.html', 10 | open: true, 11 | gzipSize: true, 12 | brotliSize: true, 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/TagSelect/context.js: -------------------------------------------------------------------------------- 1 | import { computed, inject, provide } from 'vue' 2 | 3 | const tagSelectKey = Symbol('tagSelect') 4 | 5 | export function useTagSelectProvide(props) { 6 | provide(tagSelectKey, props) 7 | } 8 | 9 | export function useTagSelectInject() { 10 | return inject(tagSelectKey, { 11 | onSelect: () => {}, 12 | modelValue: computed(() => undefined), 13 | multiple: computed(() => false), 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /src/views/components/scrollbar/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/views/exception/403.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/views/exception/500.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/views/components/upload/components/Cropper.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 裁剪 3 | 上传完成后进行裁剪 4 | 5 | 6 | 7 | 12 | 13 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/views/components/upload/components/Multiple.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 批量上传 3 | `multiple` 启用批量上传,`draggable` 启用拖拽排序 4 | 5 | 6 | 7 | 12 | 13 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/layouts/hooks/useMultiTab.js: -------------------------------------------------------------------------------- 1 | import { onMounted } from 'vue' 2 | import { onBeforeRouteUpdate, useRouter } from 'vue-router' 3 | import { useMultiTab } from '@/hooks' 4 | 5 | export default () => { 6 | const { getSimpleRoute, open } = useMultiTab() 7 | const router = useRouter() 8 | 9 | onBeforeRouteUpdate((to) => { 10 | open(getSimpleRoute(to)) 11 | }) 12 | 13 | onMounted(() => { 14 | open(getSimpleRoute(router.currentRoute.value)) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia' 2 | 3 | export { default as useAppStore } from './modules/app' 4 | export { default as useMultiTabStore } from './modules/multiTab' 5 | export { default as useRouterStore } from './modules/router' 6 | export { default as useUserStore } from './modules/user' 7 | 8 | const store = createPinia() 9 | 10 | export function setupStore(app) { 11 | app.use(store) 12 | 13 | return app 14 | } 15 | 16 | export default store 17 | -------------------------------------------------------------------------------- /src/styles/antd/tabs.less: -------------------------------------------------------------------------------- 1 | .ant-tabs { 2 | &[no-margin-bottom] { 3 | .ant-tabs-nav { 4 | margin-bottom: 0; 5 | } 6 | } 7 | } 8 | 9 | .ant-tabs-dropdown { 10 | .ant-tabs-dropdown-menu { 11 | padding-inline: 4px; 12 | } 13 | 14 | .ant-tabs-dropdown-menu-item { 15 | border-radius: 4px; 16 | } 17 | 18 | .ant-dropdown-trigger { 19 | display: flex; 20 | 21 | .multi-tab__icon { 22 | margin: 0 0 0 auto; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/ResizeBox/config.js: -------------------------------------------------------------------------------- 1 | import Enum from 'xy-enum' 2 | 3 | export const DIRECTION_LEFT = 'left' 4 | export const DIRECTION_RIGHT = 'right' 5 | export const DIRECTION_TOP = 'top' 6 | export const DIRECTION_BOTTOM = 'bottom' 7 | 8 | // 方向 9 | export const directionEnum = new Enum([ 10 | { key: 'top', value: 'top', desc: '上' }, 11 | { key: 'bottom', value: 'bottom', desc: '下' }, 12 | { key: 'left', value: 'left', desc: '左' }, 13 | { key: 'right', value: 'right', desc: '右' }, 14 | ]) 15 | -------------------------------------------------------------------------------- /docs/guide/authority-management.md: -------------------------------------------------------------------------------- 1 | # 权限管理 2 | 3 | 权限控制是中后台系统中常见的需求之一,你可以利用我们提供的 路由权限 和 指令权限,实现一些基本的权限控制功能,脚手架中也包含了几个简单示例以提供参考。 4 | 5 | ## 路由和默认权限控制 6 | 7 | `XYAdmin` 提供了两套权限实现方案,其中默认方案为前端固定路由表和权限配置,由后端提供用户权限标识,来识别是否拥有该路由权限。另一套方案为后端提供权限和路由信息结构接口,动态生成权限和菜单。 8 | 9 | 默认实现方式是通过获取当前用户的权限去比对路由表,生成当前用户具有的权限可访问的路由表,通过 `router.addRoute` 动态挂载到 `router` 上。 10 | 11 | 具体实现逻辑如下所示: 12 | 13 | <<< @/../src/router/util.js 14 | 15 | ## 指令权限 16 | 17 | `XYAdmin` 封装了一个非常方便实现按钮级别权限的自定义指令。具体详见:[v-action](/directives/v-action) 18 | -------------------------------------------------------------------------------- /docs/hooks/use-menu.md: -------------------------------------------------------------------------------- 1 | # useMenu 2 | 3 | ## 代码演示 4 | 5 | ### 设置徽标 6 | 7 | ```vue 8 | 17 | 18 | 28 | 29 | 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/components/index.md: -------------------------------------------------------------------------------- 1 | # 组件 2 | 3 | 在VUE开发过程中,组件起了至关重要的一环。可以说组件就是VUE的灵魂。大到整个框架布局,小到下拉框的选项,都是组件。 4 | 5 | `XYAdmin` 在 `And Design Vue` 的基础上扩展了适用于更多业务场景的组件,我们还将继续探索更多组件实践。 6 | 7 | ## 全局组件 8 | 9 | - `XYAdmin` 的全局组件统一存放在 `/src/components/` 目录下 10 | - 为了区分全局组件和业务组件,全局组件均以 `x-` 为前缀 11 | - 全局组件统一在 `/src/core/index.js` 中引入,页面中不需要单独引入 12 | 13 | ## 局部组件 14 | 15 | 在特定的业务场景中使用,且使用频率较低。使用时需要在指定的页面中单独引入才可以使用。 16 | 17 | ## 自定义组件 18 | 19 | 除了 `Vue` 和 `And Design Vue` 提供的组件之外,`XYAdmin` 还在 `And Design Vue` 的基础上扩展了适用于更多业务场景的组件。 20 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | import { get } from 'lodash-es' 2 | 3 | const files = import.meta.glob('./modules/*.js', { eager: true }) 4 | 5 | const configs = {} 6 | 7 | Object.keys(files).forEach((key) => { 8 | const name = key.slice(key.lastIndexOf('/') + 1, key.lastIndexOf('.')) 9 | configs[name] = { ...(files[key]?.default || {}) } 10 | }) 11 | 12 | /** 13 | * 配置 14 | * @param {string} key 15 | * @param {*} [def] 默认值 16 | * @returns {*} 17 | */ 18 | export const config = (key, def) => get(configs, key, def) 19 | -------------------------------------------------------------------------------- /src/views/components/upload/components/Basic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法 3 | 基础用法 4 | 5 | 6 | 7 | 12 | 13 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/DemoBox/context.js: -------------------------------------------------------------------------------- 1 | import { computed, inject, provide } from 'vue' 2 | 3 | const DEMO_BOX_KEY = Symbol('demo_box') 4 | 5 | export function useDemoBoxProvide(props) { 6 | provide(DEMO_BOX_KEY, props) 7 | } 8 | 9 | export function useDemoBoxInject() { 10 | return inject(DEMO_BOX_KEY, { 11 | span: computed(() => undefined), 12 | md: computed(() => undefined), 13 | lg: computed(() => undefined), 14 | xl: computed(() => undefined), 15 | xxl: computed(() => undefined), 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/Preview/config.js: -------------------------------------------------------------------------------- 1 | import Enum from 'xy-enum' 2 | 3 | export const ACTION_ENUM = new Enum([ 4 | { label: 'zoomOut', value: 'zoomOut', desc: '缩小' }, 5 | { label: 'zoomIn', value: 'zoomIn', desc: '放大' }, 6 | { label: 'fullscreen', value: 'fullscreen', desc: '全屏' }, 7 | { label: 'rotateLeft', value: 'rotateLeft', desc: '向左旋转' }, 8 | { label: 'rotateRight', value: 'rotateRight', desc: '向右旋转' }, 9 | { label: 'prev', value: 'prev', desc: '上一个' }, 10 | { label: 'next', value: 'next', desc: '下一个' }, 11 | ]) 12 | -------------------------------------------------------------------------------- /src/views/components/ellipsis/components/Basic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法 3 | 只有内容超出后显示省略号 4 | 5 | 6 | 7 | 14 | 15 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/components/Editor/index.less: -------------------------------------------------------------------------------- 1 | .mixin-editor () { 2 | .tox:not(.tox-tinymce-inline) { 3 | border: @color-border solid 1px; 4 | border-radius: @border-radius; 5 | 6 | .tox-editor-header { 7 | box-shadow: none; 8 | border-bottom: @color-split solid 1px; 9 | } 10 | } 11 | 12 | .tox.tox-tinymce-inline { 13 | .tox-editor-header { 14 | border: none; 15 | border-radius: @border-radius; 16 | box-shadow: @box-shadow-secondary; 17 | } 18 | } 19 | } 20 | 21 | .mixin-editor() !important; 22 | -------------------------------------------------------------------------------- /src/views/components/qrcode/components/Icon.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 带 Icon 3 | 带 Icon 的二维码,支持定义背景色、间距、尺寸 4 | 5 | 6 | 7 | 13 | 14 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/views/components/tag/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/libs/tinymce/skins/ui/oxide/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} 2 | -------------------------------------------------------------------------------- /public/libs/tinymce/skins/ui/tinymce-5/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} 2 | -------------------------------------------------------------------------------- /src/views/components/preview/components/Basic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法 3 | 基础用法 4 | 5 | 6 | 7 | 16 | 17 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /public/libs/tinymce/skins/ui/oxide-dark/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} 2 | -------------------------------------------------------------------------------- /public/libs/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} 2 | -------------------------------------------------------------------------------- /src/components/Grid/context.js: -------------------------------------------------------------------------------- 1 | import { computed, inject, provide } from 'vue' 2 | 3 | const GRID_KEY = Symbol('grid') 4 | 5 | export function useGridProvide(props) { 6 | provide(GRID_KEY, props) 7 | } 8 | 9 | export function useGridInject() { 10 | return inject(GRID_KEY, { 11 | columnGap: computed(() => 0), 12 | columns: computed(() => 24), 13 | collapsed: computed(() => false), 14 | collapsedRows: computed(() => 1), 15 | children: computed(() => []), 16 | addChild: () => {}, 17 | removeChild: () => {}, 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /src/layouts/CustomLayout.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | 27 | -------------------------------------------------------------------------------- /src/styles/mixins/scrollbar.less: -------------------------------------------------------------------------------- 1 | .scrollbar(@handle-bg: rgba(0, 0, 0, 0.1), @handle-bg-hover: rgba(0,0,0,0.2), @handle-size: 4px, @border-radius: 10em) { 2 | &::-webkit-scrollbar { 3 | width: @handle-size; 4 | height: @handle-size; 5 | background: transparent; 6 | border-radius: @border-radius; 7 | } 8 | 9 | &::-webkit-scrollbar-thumb { 10 | background: @handle-bg; 11 | border-radius: @border-radius; 12 | 13 | &:hover { 14 | background: @handle-bg-hover; 15 | transition: all 0.2s; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /public/libs/tinymce/notices.txt: -------------------------------------------------------------------------------- 1 | Below is a list of third party libraries that this software uses: 2 | ---------------------------------------------------------------- 3 | 4 | dompurify 5 | owner: Mario Heiderich 6 | repo: https://github.com/cure53/DOMPurify 7 | version: 3.2.6 8 | license: MPL-2.0 OR Apache-2.0 9 | 10 | prismjs 11 | owner: Lea Verou 12 | repo: https://github.com/PrismJS/prism 13 | version: 1.25.0 14 | license: MIT 15 | 16 | 17 | prism-themes 18 | owner: Lea Verou 19 | repo: https://github.com/PrismJS/prism-themes 20 | version: 1.9.0 21 | license: MIT 22 | -------------------------------------------------------------------------------- /src/views/components/grid/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/directives/v-loading.md: -------------------------------------------------------------------------------- 1 | # v-loading 加载 2 | 3 | ## 代码演示 4 | 5 | ### 基础用法 6 | 7 | <<< @/../src/views/components/loading/components/DirectiveBasic.vue#snippet 8 | 9 | ### 自定义描述文案 10 | 11 | <<< @/../src/views/components/loading/components/DirectiveDescription.vue#snippet 12 | 13 | ## API 14 | 15 | ### Props 16 | 17 | | 名称 | 说明 | 类型 | 默认值 | 18 | | ------------------------- | ------------ | -------- | ------ | 19 | | loading-description | 描述文字 | `string` | - | 20 | | loading-description-style | 描述文字样式 | `string` | - | 21 | -------------------------------------------------------------------------------- /src/components/Filter/Filter.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 30 | -------------------------------------------------------------------------------- /src/views/components/upload/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/components/action-button.md: -------------------------------------------------------------------------------- 1 | # ActionButton 操作按钮 2 | 3 | ## 代码演示 4 | 5 | ### 基础用法 6 | 7 | <<< @/../src/views/components/action-button/components/Basic.vue#snippet 8 | 9 | ### 在 table 中使用 10 | 11 | <<< @/../src/views/components/action-button/components/Table.vue#snippet 12 | 13 | ## API 14 | 15 | ### Props 16 | 17 | | 参数 | 说明 | 类型 | 默认值 | 18 | | -------- | -------------- | --------- | ------- | 19 | | tag | 标签 | `string` | `span` | 20 | | divider | 是否显示分割线 | `boolean` | `true` | 21 | | disabled | 禁用 | `boolean` | `false` | 22 | -------------------------------------------------------------------------------- /docs/components/filter.md: -------------------------------------------------------------------------------- 1 | # Filter 筛选 2 | 3 | ## 代码演示 4 | 5 | ### 基础用法 6 | 7 | <<< @/../src/views/components/filter/components/Basic.vue#snippet 8 | 9 | ### 自定义 10 | 11 | <<< @/../src/views/components/filter/components/Custom.vue#snippet 12 | 13 | ## API 14 | 15 | ### Filter 16 | 17 | :::tip 提示 18 | `Filter` 组件基于 `AForm` 组件扩展,支持其所有参数。详见:[Form 组件](https://www.antdv.com/components/form-cn#api) 19 | ::: 20 | 21 | ### FilterItem 22 | 23 | :::tip 提示 24 | `FilterItem` 组件基于 `AFormItem` 组件扩展,支持其所有参数以及插槽。详见:[FormItem 组件](https://www.antdv.com/components/form-cn#form-item) 25 | ::: 26 | -------------------------------------------------------------------------------- /src/views/components/loading/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/hooks/index.js: -------------------------------------------------------------------------------- 1 | export { default as useCheckUpdate } from './useCheckUpdate' 2 | export { default as useColors } from './useColors' 3 | export { default as useConfigProvider } from './useConfigProvider' 4 | export { default as useDate } from './useDate' 5 | export { default as useForm } from './useForm' 6 | export { default as useMenu } from './useMenu' 7 | export { default as useModal } from './useModal' 8 | export { default as useMultiTab } from './useMultiTab' 9 | export { default as usePagination } from './usePagination' 10 | export { default as useProgress } from './useProgress' 11 | -------------------------------------------------------------------------------- /src/views/components/qrcode/components/Color.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 自定义颜色 3 | 通过设置 `color` 自定义二维码颜色 4 | 5 | 6 | 7 | 12 | 13 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/views/components/tag/components/Basic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法 3 | 它有不同的类型。 4 | 5 | 6 | 7 | 8 | 9 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/.vitepress/nav.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { text: '指南', link: '/guide/started', activeMatch: '/guide/' }, 3 | { text: '组件', link: '/components/', activeMatch: '/components/' }, 4 | { text: '钩子', link: '/hooks/use-check-update', activeMatch: '/hooks/' }, 5 | // { text: '工具类', link: '/utils/mapping', activeMatch: '/utils/' }, 6 | { text: '指令', link: '/directives/v-loading', activeMatch: '/directives/' }, 7 | { text: '插件', link: '/plugins/xy-ali-oss', activeMatch: '/plugins/' }, 8 | { text: '更新日志', link: 'https://github.com/mengxianghan/xy-admin/releases', target: '_blank' }, 9 | ] 10 | -------------------------------------------------------------------------------- /public/libs/tinymce/skins/ui/oxide/skin.shadowdom.js: -------------------------------------------------------------------------------- 1 | tinymce.Resource.add('ui/oxide/skin.shadowdom.css', `body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}`) -------------------------------------------------------------------------------- /public/libs/tinymce/skins/ui/tinymce-5/skin.shadowdom.js: -------------------------------------------------------------------------------- 1 | tinymce.Resource.add('ui/tinymce-5/skin.shadowdom.css', `body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}`) -------------------------------------------------------------------------------- /src/views/components/chart/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/views/components/qrcode/components/Basic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法 3 | 基础用法 4 | 5 | 6 | 7 | 12 | 13 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/libs/tinymce/skins/ui/oxide-dark/skin.shadowdom.js: -------------------------------------------------------------------------------- 1 | tinymce.Resource.add('ui/oxide-dark/skin.shadowdom.css', `body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}`) -------------------------------------------------------------------------------- /src/views/components/cropper/components/Basic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法 3 | 基础用法 4 | 5 | 6 | 7 | 18 | 19 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/libs/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.js: -------------------------------------------------------------------------------- 1 | tinymce.Resource.add('ui/tinymce-5-dark/skin.shadowdom.css', `body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}`) -------------------------------------------------------------------------------- /src/apis/modules/system.js: -------------------------------------------------------------------------------- 1 | import { request } from '@/utils' 2 | 3 | // 获取角色列表 4 | export const getUserRoleList = params => request.basic.get('/system/getUserRoleList', params) 5 | // 获取用户分页列表 6 | export const getUserPageList = params => request.basic.get('/system/getUserPageList', params) 7 | // 获取菜单列表 8 | export const getMenuList = params => request.basic.get('/system/getMenuList', params) 9 | // 获取新版菜单列表 10 | export const getNewMenuList = params => request.basic.get('/system/getNewMenuList', params) 11 | // 获取字典分类列表 12 | export const getDictTypeList = params => request.basic.get('/system/getDictTypeList', params) 13 | -------------------------------------------------------------------------------- /src/views/components/qrcode/components/Popover.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 高级用法 3 | 带气泡卡片的例子。 4 | 5 | 6 | 7 | 12 | 13 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /docs/utils/assets.md: -------------------------------------------------------------------------------- 1 | # assets 2 | 3 | ```js :no-line-numbers 4 | assets(url) 5 | ``` 6 | 7 | 获取正确的文件路径 8 | 9 | ## 参数 10 | 11 | | 名称 | 说明 | 类型 | 默认值 | 12 | | ---- | ---------------------------- | -------- | ------ | 13 | | url | 相对 `assets` 目录的文件地址 | `string` | `-` | 14 | 15 | ## 示例 16 | 17 | ### 基础用法 18 | 19 | ```vue 20 | 23 | 24 | 32 | 33 | 34 | ``` 35 | -------------------------------------------------------------------------------- /src/views/components/cascader/components/Advanced.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 高级用法 3 | 不同层级使用不同的数据接口。用于特殊场景的级联选择 4 | 5 | 6 | 7 | 17 | 18 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/components/DemoBox/DemoBoxGroup.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | 32 | -------------------------------------------------------------------------------- /src/utils/storage.js: -------------------------------------------------------------------------------- 1 | import Storage from 'xy-storage' 2 | import { config } from '@/config' 3 | 4 | const options = { 5 | namespace: config('storage.namespace'), 6 | attrs: { 7 | domain: config('storage.domain'), 8 | }, 9 | } 10 | 11 | export const local = new Storage({ 12 | ...options, 13 | name: 'local', 14 | }) 15 | 16 | export const session = new Storage({ 17 | ...options, 18 | name: 'session', 19 | }) 20 | 21 | export const cookie = new Storage({ 22 | ...options, 23 | name: 'cookie', 24 | }) 25 | 26 | export default { 27 | local, 28 | session, 29 | cookie, 30 | } 31 | -------------------------------------------------------------------------------- /src/views/components/cascader/components/Basic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法 3 | 主要解决 `ant-design-vue` 中 `Cascader` 使用远程加载数据时表单回填逻辑复杂的问题。 4 | 5 | 6 | 7 | 17 | 18 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/views/components/qrcode/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/components/form-table.md: -------------------------------------------------------------------------------- 1 | # FormTable 表单表格 2 | 3 | ## 代码演示 4 | 5 | ### 基础用法 6 | 7 | <<< @/../src/views/components/form-table/components/Basic.vue#snippet 8 | 9 | ## API 10 | 11 | ### Props 12 | 13 | | 名称 | 说明 | 类型 | 默认值 | 14 | | -------------------- | ---------------------- | -------- | ------ | 15 | | model-value(v-model) | 当前输入的值 | `array` | `[]` | 16 | | rowTpl | 行模板,用于插入行使用 | `object` | `{}` | 17 | 18 | ### Events 19 | 20 | | 名称 | 说明 | 返回值 | 21 | | ------ | -------- | -------- | 22 | | add | 添加数据 | - | 23 | | delete | 删除数据 | `record` | 24 | -------------------------------------------------------------------------------- /src/views/components/loading/components/GlobalBasic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法(全局) 3 | 最简单的使用方式,适应各种场景。 4 | 5 | 6 | 7 | 22 | 23 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/views/components/search/components/Basic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法 3 | 基础用法 4 | 5 | 6 | 7 | 8 | 9 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/guide/server.md: -------------------------------------------------------------------------------- 1 | # 和服务端进行交互 2 | 3 | `XYAdmin` 是一套基于 `Vue` 技术栈的单页面应用,我们提供的是前端代码和 `mock` 模拟数据的开发模式, 4 | 通过 `API` 的形式和任何技术栈的服务端应用一起工作。下面将简单介绍和服务端交互的基本写法。 5 | 6 | ## 使用 7 | 8 | 为了方便管理维护,统一的请求处理都放在 `/src/api` 目录中,并且一般按照 model 纬度进行拆分文件,如: 9 | 10 | ```{:no-line-numbers} 11 | apis/ 12 | modules/ 13 | user.js 14 | permission.js 15 | goods.js 16 | ... 17 | index.js 18 | ``` 19 | 20 | 其中,`/src/utils/request` 是基于 `axios` 的封装,便于统一处理 `POST`,`GET` 等请求参数,请求头,以及错误提示信息等。 21 | 它封装了全局 `request` 拦截器、`response` 拦截器、统一的错误处理、`baseURL` 设置等。 22 | 23 | <<< @/../src/utils/request.js 24 | 25 | :::tip 提示 26 | 通过配置 `baseURL` 可支持请求多服务接口。 27 | ::: 28 | -------------------------------------------------------------------------------- /src/core/index.js: -------------------------------------------------------------------------------- 1 | import antd from 'ant-design-vue' 2 | import components from '@/components' 3 | import { setupDirective } from '@/directives' 4 | import { useCheckUpdate } from '@/hooks' 5 | import { setupRouter } from '@/router' 6 | import { setupStore } from '@/store' 7 | import { setupException } from './exception' 8 | import './permission' 9 | import 'ant-design-vue/dist/reset.css' 10 | import '@/styles/index.less' 11 | 12 | useCheckUpdate() 13 | 14 | export function useCore(app) { 15 | app.use(antd) 16 | app.use(components) 17 | setupException(app) 18 | setupStore(app) 19 | setupRouter(app) 20 | setupDirective(app) 21 | } 22 | -------------------------------------------------------------------------------- /src/views/components/cropper/components/Ratio.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 比例 3 | 将裁剪比例设置为 `1/1` 4 | 5 | 6 | 7 | 18 | 19 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/views/components/toolbar/components/Bordered.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 带边框 3 | 圈(juàn)起来,更聚焦 4 | 5 | 6 | 7 | 8 | 9 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/router/routes/link.js: -------------------------------------------------------------------------------- 1 | import { LinkOutlined } from '@ant-design/icons-vue' 2 | 3 | export default [ 4 | { 5 | path: '', 6 | name: 'link', 7 | component: 'RouteViewLayout', 8 | meta: { 9 | icon: LinkOutlined, 10 | title: '外部链接', 11 | isMenu: true, 12 | keepAlive: false, 13 | }, 14 | children: [ 15 | { 16 | path: 'https://github.com/mengxianghan/xy-admin', 17 | name: 'github', 18 | meta: { 19 | type: 'link', 20 | title: 'Github', 21 | target: '_blank', 22 | isMenu: true, 23 | }, 24 | }, 25 | ], 26 | }, 27 | ] 28 | -------------------------------------------------------------------------------- /src/views/components/toolbar/components/Basic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法 3 | 用 `Row` 实现更方便,因为它也是基于 `Row` 扩展的 4 | 5 | 6 | 7 | 8 | 9 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/utils/get.js: -------------------------------------------------------------------------------- 1 | import { cloneDeep, keys, pick } from 'lodash-es' 2 | 3 | /** 4 | * 获取表单数据 5 | * 根据表单字段从行数据中获取对应的数据,用于回填表单 6 | * @param {object} record 7 | * @param {object} formData 8 | */ 9 | export const getFormData = (record = {}, formData = {}) => pick(cloneDeep(record), keys(formData) || []) || {} 10 | 11 | /** 12 | * 获取变量类型 13 | * @param {*} value 14 | * @returns 15 | */ 16 | export const getType = value => Object.prototype.toString.call(value).slice(8, -1).toLowerCase() 17 | 18 | /** 19 | * 获取文件后缀 20 | * @param {*} filename 21 | * @returns 22 | */ 23 | export const getSuffix = filename => filename.split('.').pop().toLowerCase() 24 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import * as components from './components' 2 | import * as directives from './directives' 3 | 4 | export * from './components' 5 | 6 | function install(app) { 7 | for (const key in components) { 8 | const component = components[key] 9 | 10 | if (component.install) { 11 | app.use(component) 12 | } 13 | else { 14 | app.component(component.name, component) 15 | } 16 | } 17 | 18 | for (const key in directives) { 19 | const directive = directives[key] 20 | app.directive(directive.name, directive) 21 | } 22 | 23 | return app 24 | } 25 | 26 | export default { install } 27 | -------------------------------------------------------------------------------- /src/views/exception/404.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /docs/hooks/use-modal.md: -------------------------------------------------------------------------------- 1 | # Modal 弹窗 2 | 3 | ## 代码演示 4 | 5 | ### 基础用法 6 | 7 | <<< @/../src/views/components/modal/components/Basic.vue#snippet 8 | 9 | ### 可拖拽对话框 10 | 11 | <<< @/../src/views/components/modal/components/Drag.vue#snippet 12 | 13 | ### 异步关闭 14 | 15 | <<< @/../src/views/components/modal/components/Async.vue#snippet 16 | 17 | ### 回调 18 | 19 | <<< @/../src/views/components/modal/components/Callback.vue#snippet 20 | 21 | ## API 22 | 23 | ### Props 24 | 25 | | 参数 | 说明 | 类型 | 默认值 | 26 | | ------- | -------------- | --------- | ------ | 27 | | tag | 标签 | `string` | `span` | 28 | | divider | 是否显示分割线 | `boolean` | `true` | 29 | -------------------------------------------------------------------------------- /docs/components/preview.md: -------------------------------------------------------------------------------- 1 | # Preview 预览 2 | 3 | ## 代码演示 4 | 5 | ### 基础用法 6 | 7 | <<< @/../src/views/components/preview/components/Basic.vue#snippet 8 | 9 | ### 多媒体 10 | 11 | <<< @/../src/views/components/preview/components/Multiple.vue#snippet 12 | 13 | ### 多张图片预览 14 | 15 | <<< @/../src/views/components/preview/components/Media.vue#snippet 16 | 17 | ## API 18 | 19 | ### Props 20 | 21 | | 名称 | 说明 | 类型 | 默认值 | 22 | | ------- | -------------------------------------- | -------- | ------ | 23 | | urls | url 数组,支持 `image/*` `.mp3` `.mp4` | `array` | `-` | 24 | | current | 预览起始位置索引 | `number` | `0` | 25 | -------------------------------------------------------------------------------- /src/layouts/components/LayoutTransition.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 18 | 33 | -------------------------------------------------------------------------------- /src/views/components/preview/components/Media.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 多媒体 3 | 支持图片、音频、视频三种类型文件 4 | 5 | 6 | 7 | 20 | 21 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/components/Filter/FilterItem.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/views/components/tag-select/components/Multiple.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 多选 3 | 多选 4 | 5 | 6 | 7 | 20 | 21 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/views/components/loading/components/DirectiveBasic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法(指令) 3 | 当在卡片中使用时,它会实现你的需求 4 | 5 | 6 | 7 | 16 | 17 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/styles/scrollbar.less: -------------------------------------------------------------------------------- 1 | @handle-bg: rgba(0, 0, 0, 0.35); 2 | @handle-bg-hover: rgba(0, 0, 0, 0.45); 3 | @handle-size: 6px; 4 | @border-radius: 10em; 5 | 6 | .scrollbar(@handle-bg, @handle-bg-hover, @handle-size, @border-radius); 7 | 8 | .os-scrollbar { 9 | --os-handle-bg: @handle-bg; 10 | --os-handle-min-size: @handle-size; 11 | --os-padding-axis: 0; 12 | --os-padding-perpendicular: 0; 13 | --os-size: @handle-size; 14 | --os-track-border-radius: @border-radius; 15 | --os-handle-bg-hover: @handle-bg-hover; 16 | } 17 | 18 | .rc-virtual-list-scrollbar { 19 | width: @handle-size !important; 20 | } 21 | 22 | .rc-virtual-list-scrollbar-thumb { 23 | background: @handle-bg !important; 24 | } 25 | -------------------------------------------------------------------------------- /docs/components/button.md: -------------------------------------------------------------------------------- 1 | # Button 按钮 2 | 3 | ## 代码演示 4 | 5 | ### 基础用法 6 | 7 | <<< @/../src/views/components/button/components/Basic.vue#snippet 8 | 9 | ## API 10 | 11 | ### Props 12 | 13 | | 参数 | 说明 | 类型 | 默认值 | 14 | | --------------------- | ------------------------------------------------------------ | -------- | ------------------------ | 15 | | color | 颜色,内置 `success`、`error`、`warning`,并与主题色保持一致 | `string` | - | 16 | | color-text | 文本颜色 | `string` | - | 17 | -------------------------------------------------------------------------------- /docs/utils/deepMerge.md: -------------------------------------------------------------------------------- 1 | # deepMerge 2 | 3 | ```js :no-line-numbers 4 | deepMerge(object, sources) 5 | ``` 6 | 7 | 合并两个对象 8 | 9 | ## 参数 10 | 11 | | 名称 | 说明 | 类型 | 默认值 | 12 | | ------- | -------- | -------- | ------ | 13 | | object | 目标对象 | `object` | - | 14 | | sources | 来源对象 | `object` | - | 15 | 16 | ## 代码演示 17 | 18 | ### 基础用法 19 | 20 | ::: code-group 21 | 22 | ```js [示例] 23 | const object = { 24 | a: [{ b: 2 }, { d: 4 }], 25 | } 26 | 27 | const other = { 28 | a: [{ c: 3 }, { e: 5 }], 29 | } 30 | 31 | console.log(deepMerge(object, other)) 32 | ``` 33 | 34 | ```json [输出] 35 | { 36 | "a": [ 37 | { "b": 2, "c": 3 }, 38 | { "d": 4, "e": 5 } 39 | ] 40 | } 41 | ``` 42 | 43 | ::: 44 | -------------------------------------------------------------------------------- /src/views/components/tag/components/Secondary.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 次要标签 3 | 次要标签 4 | 5 | 6 | 7 | 8 | 9 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /docs/components/toolbar.md: -------------------------------------------------------------------------------- 1 | # Toolbar 工具条 2 | 3 | ## 代码演示 4 | 5 | ### 基础用法 6 | 7 | <<< @/../src/views/components/toolbar/components/Basic.vue#snippet 8 | 9 | ### 带边框 10 | 11 | <<< @/../src/views/components/toolbar/components/Bordered.vue#snippet 12 | 13 | ## API 14 | 15 | ### Props 16 | 17 | | 名称 | 说明 | 类型 | 默认值 | 18 | | ---------------------- | ------------------------------------------------ | --------- | ------ | 19 | | bordered | 是否显示边框,显示边框时将会使用 `Card` 组件包裹 | `boolean` | `true` | 20 | 21 | ### Slots 22 | 23 | | 名称 | 说明 | 24 | | ------- | -------------------- | 25 | | default | 内容 | 26 | | extra | 扩展内容,显示在右侧 | 27 | -------------------------------------------------------------------------------- /src/views/components/upload/components/Input.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 上传框 3 | 单一文件上传,不需要预览的图片或者位置格式的文件 4 | 5 | 6 | 7 | 12 | 13 | 16 | 17 | 34 | 35 | -------------------------------------------------------------------------------- /docs/components/upload-input.md: -------------------------------------------------------------------------------- 1 | # UploadInput 上传文件 2 | 3 | ## 代码演示 4 | 5 | ### 基础用法 6 | 7 | <<< @/../src/views/components/upload/components/Input.vue#snippet 8 | 9 | ## API 10 | 11 | ### 参数 12 | 13 | | 名称 | 说明 | 类型 | 默认值 | 14 | | -------------------- | -------------- | --------- | ---------- | 15 | | model-value(v-model) | | `string` | `-` | 16 | | text | 按钮内容 | `string` | `选择文件` | 17 | | loading-text | 上传中按钮内容 | `string` | `上传中` | 18 | | allow-clear | 允许清空 | `boolean` | `true` | 19 | 20 | ### 事件 21 | 22 | | 名称 | 说明 | 返回值 | 23 | | ------ | ------------ | ------------ | 24 | | change | 内容发生改变 | `modelValue` | 25 | -------------------------------------------------------------------------------- /src/apis/modules/common.js: -------------------------------------------------------------------------------- 1 | import { request } from '@/utils' 2 | 3 | // 获取动态列表 4 | export const getDynamicList = () => request.basic.get('/dynamic') 5 | 6 | // 获取分页列表 7 | export const getPageList = params => request.basic.get('/data', params, { enableAbortController: false }) 8 | // 删除 9 | export const del = () => request.basic.delete(`/data`) 10 | // 创建 11 | export const create = params => request.basic.post('/data', params) 12 | // 更新 13 | export const update = (_, params) => request.basic.put(`/data`, params) 14 | 15 | // 获取地区 16 | export const getRegion = params => request.basic.get('/region', params) 17 | 18 | // 获取选项列表 19 | export const getOptions = params => request.basic.get('/common/options', params, { enableAbortController: false }) 20 | -------------------------------------------------------------------------------- /src/views/components/qrcode/components/Error.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 纠错比例 3 | 通过设置 errorLevel 调整不同的容错等级 4 | 5 | 6 | 7 | 14 | 15 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/hooks/useConfigProvider/index.js: -------------------------------------------------------------------------------- 1 | import { theme } from 'ant-design-vue' 2 | import zhCN from 'ant-design-vue/es/locale/zh_CN' 3 | 4 | export default () => { 5 | const configProviderAttrs = { 6 | locale: zhCN, 7 | theme: { 8 | algorithm: theme.defaultAlgorithm, 9 | token: { 10 | borderRadius: 4, 11 | borderRadiusSM: 2, 12 | }, 13 | components: { 14 | List: { 15 | paddingContentHorizontalLG: 0, 16 | }, 17 | Table: { 18 | paddingContentVerticalLG: 12, 19 | padding: 12, 20 | }, 21 | Card: { 22 | paddingLG: 16, 23 | }, 24 | }, 25 | }, 26 | } 27 | 28 | return { 29 | configProviderAttrs, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/views/components/grid/components/Offset.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 偏移 3 | 使用 `offset` 可以将列向右侧偏。例如:`offset={1}` 将元素向右侧偏移了 1 个列(column)的宽度。 4 | 5 | 6 | 7 | 8 | 9 | 22 | 23 | 34 | 35 | -------------------------------------------------------------------------------- /src/views/components/scrollbar/components/Basic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法 3 | 通过 `height` 属性设置滚动条高度,若不设置则根据父容器高度自适应。 4 | 5 | 6 | 7 | 8 | 9 | 20 | 21 | 33 | 34 | -------------------------------------------------------------------------------- /src/views/components/tag-select/components/Unlimited.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 不限 3 | 使用 `unlimited` 启用不限选项 4 | 5 | 6 | 7 | 21 | 22 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /public/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /src/views/components/preview/components/Multiple.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 多张图片预览 3 | 点击左右切换按钮可以预览多张图片。`current` 可以设置默认打开时展示第几张图片 4 | 5 | 6 | 7 | 22 | 23 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 11 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /src/views/components/tag/components/Bordered.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 无边框 3 | 无边框 4 | 5 | 6 | 7 | 8 | 9 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/views/components/action-button/components/Basic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法 3 | 告别手动添加分割线,一切都是因为懒。不信你看👇 4 | 5 | 6 | 7 | 18 | 19 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/views/components/qrcode/components/Status.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 不同的状态 3 | 可以通过 status 的值控制二维码的状态 4 | 5 | 6 | 7 | 17 | 18 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/views/components/form-table/components/Basic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法 3 | 基础用法 4 | 5 | 6 | 7 | 16 | 17 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/views/components/grid/components/Basic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法 3 | 基础用法 4 | 5 | 6 | 7 | 8 | 9 | 28 | 29 | 40 | 41 | -------------------------------------------------------------------------------- /src/views/components/loading/components/DirectiveDescription.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 自定义描述文案(指令) 3 | 告诉别人你的想法,使用 `loading-description-style` 可以定义说明文案的样式。 4 | 5 | 6 | 7 | 16 | 17 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/views/components/modal/components/Basic.vue: -------------------------------------------------------------------------------- 1 | 2 | # 基础用法 3 | 使用 `openModal` 打开弹窗,使用 `closeModal` 关闭弹窗 4 | 5 | 6 | 7 | 18 | 19 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/views/components/upload/components/Slot.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 插槽 3 | 插槽 4 | 5 | 6 | 7 | 12 | 13 | 23 | 24 | 41 | 42 | -------------------------------------------------------------------------------- /src/styles/reset.less: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | html, 7 | body { 8 | height: auto; 9 | min-height: 100%; 10 | word-break: break-word; 11 | hyphens: auto; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | overscroll-behavior: none; 15 | } 16 | 17 | #app { 18 | position: relative; 19 | } 20 | 21 | body { 22 | font: normal normal normal 14px/1.5 @font-family; 23 | color: @color-text; 24 | } 25 | 26 | img, 27 | video, 28 | audio { 29 | max-width: 100%; 30 | vertical-align: middle; 31 | } 32 | 33 | iframe { 34 | vertical-align: middle; 35 | } 36 | 37 | input:-internal-autofill-previewed, 38 | input:-internal-autofill-selected { 39 | transition: background-color 5000s ease-in-out 0s !important; 40 | } 41 | 42 | ul, 43 | ol, 44 | li { 45 | list-style: none; 46 | } 47 | -------------------------------------------------------------------------------- /src/views/components/chart/components/GaugeChart.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 仪表盘 3 | 仪表盘 4 | 5 | 6 | 7 | 35 | 36 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /docs/hooks/use-multi-tab.md: -------------------------------------------------------------------------------- 1 | # usePagination 2 | 3 | # API 4 | 5 | ## Methods 6 | 7 | | 方法名 | 说明 | 参数 | 默认值 | 8 | | ---------- | -------------- | ------------------------------------- | -------------- | 9 | | open | 打开新标签页 | `(route)=>void` | `currentRoute` | 10 | | close | 关闭标签页 | `(route)=>void` | `currentRoute` | 11 | | closeLeft | 关闭左侧标签页 | `(route)=>void` | `currentRoute` | 12 | | closeRight | 关闭右侧标签页 | `(route)=>void` | `currentRoute` | 13 | | closeOther | 关闭其他标签页 | `(route)=>void` | `currentRoute` | 14 | | reload | 重新加载 | `(route)=>void` | `currentRoute` | 15 | | setTitle | 设置标签页标题 | `(title: string, route: Route)=>void` | - | 16 | -------------------------------------------------------------------------------- /public/libs/tinymce/plugins/code/plugin.min.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("code",(e=>((e=>{e.addCommand("mceCodeEditor",(()=>{(e=>{const o=(e=>e.getContent({source_view:!0}))(e);e.windowManager.open({title:"Source Code",size:"large",body:{type:"panel",items:[{type:"textarea",name:"code",spellcheck:!1}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{code:o},onSubmit:o=>{((e,o)=>{e.focus(),e.undoManager.transact((()=>{e.setContent(o)})),e.selection.setCursorLocation(),e.nodeChanged()})(e,o.getData().code),o.close()}})})(e)}))})(e),(e=>{const o=()=>e.execCommand("mceCodeEditor");e.ui.registry.addButton("code",{icon:"sourcecode",tooltip:"Source code",onAction:o}),e.ui.registry.addMenuItem("code",{icon:"sourcecode",text:"Source code",onAction:o})})(e),{})))}(); -------------------------------------------------------------------------------- /src/components/Editor/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 基础的内容样式 3 | * @param {*} token 4 | * @param {*} styles 5 | * @returns 6 | */ 7 | export function getContentStyle(token, styles = '') { 8 | return ` 9 | .mce-content-body { 10 | font-size: 14px; 11 | font-weight: 400; 12 | margin: 0; 13 | padding: 8px 11px !important; 14 | line-height: ${token.lineHeight}; 15 | color: ${token.colorText}; 16 | } 17 | .mce-content-body p { 18 | margin: 0; 19 | } 20 | .mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before { 21 | left: 11px; 22 | font-size: inherit; 23 | color: ${token.colorTextPlaceholder}; 24 | line-height: ${token.lineHeight}; 25 | } 26 | ${styles} 27 | ` 28 | } 29 | -------------------------------------------------------------------------------- /src/router/routes/user.js: -------------------------------------------------------------------------------- 1 | import { UserOutlined } from '@ant-design/icons-vue' 2 | 3 | export default [ 4 | { 5 | path: 'user', 6 | name: 'user', 7 | component: 'RouteViewLayout', 8 | meta: { 9 | icon: UserOutlined, 10 | title: '个人页', 11 | isMenu: true, 12 | keepAlive: true, 13 | }, 14 | children: [ 15 | { 16 | path: 'center', 17 | name: 'userCenter', 18 | component: 'user/center/index.vue', 19 | meta: { 20 | title: '个人中心', 21 | isMenu: true, 22 | keepAlive: true, 23 | }, 24 | }, 25 | { 26 | path: 'setting', 27 | name: 'userSetting', 28 | component: 'user/setting/index.vue', 29 | meta: { 30 | title: '个人设置', 31 | isMenu: true, 32 | keepAlive: true, 33 | }, 34 | }, 35 | ], 36 | }, 37 | ] 38 | -------------------------------------------------------------------------------- /docs/hooks/use-colors.md: -------------------------------------------------------------------------------- 1 | # useColors 2 | 3 | 设定一个基础颜色,可以生成一个色板 4 | 5 | ## 代码演示 6 | 7 | ### 基础用法 8 | 9 | ```vue 10 | 18 | 19 | 22 | 23 | 24 | ``` 25 | 26 | :::tip 提示 27 | 基于 `@ant-design/colors` 进行扩展,更多用法详见:[@ant-design/colors](https://www.npmjs.com/package/@ant-design/colors) 28 | ::: 29 | 30 | ## API 31 | 32 | ### 返回值 33 | 34 | | 参数 | 说明 | 类型 | 35 | | -------- | ------------ | -------------------------------------- | 36 | | generate | 生成色板函数 | `(color: string) => Array` | 37 | -------------------------------------------------------------------------------- /src/components/Breadcrumb/Breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 34 | 35 | 42 | -------------------------------------------------------------------------------- /src/views/components/grid/components/ResponsiveColumns.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 响应式列数 3 | 响应式列数 4 | 5 | 6 | 7 | 10 | 11 | 29 | 30 | 41 | 42 | -------------------------------------------------------------------------------- /docs/.vitepress/config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress' 2 | import nav from './nav' 3 | import sidebar from './sidebar' 4 | 5 | // https://vitepress.dev/reference/site-config 6 | export default defineConfig({ 7 | title: 'XYAdmin', 8 | description: 'xy admin 使用手册', 9 | appearance: true, 10 | themeConfig: { 11 | logo: 'http://cdn.xuanyunet.com/images/logo.svg', 12 | outline: 'deep', 13 | outlineTitle: '本页目录', 14 | nav, 15 | sidebar, 16 | socialLinks: [{ icon: 'github', link: 'https://github.com/mengxianghan/xy-admin' }], 17 | footer: { 18 | message: '本文档内容版权为 XYAdmin 作者所有,保留所有权利。', 19 | }, 20 | lastUpdated: { 21 | text: '最后更新时间', 22 | }, 23 | }, 24 | markdown: { 25 | lineNumbers: true, 26 | }, 27 | vite: { 28 | server: { 29 | host: '0.0.0.0', 30 | strictPort: false, 31 | }, 32 | }, 33 | lastUpdated: true, 34 | }) 35 | -------------------------------------------------------------------------------- /src/views/components/send-code/components/Basic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法 3 | 告别复杂的倒计时逻辑,默认 60s。 4 | 5 | 6 | 7 | 16 | 17 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/router/config.js: -------------------------------------------------------------------------------- 1 | import * as layouts from '@/layouts' 2 | 3 | /** 4 | * 白名单 5 | * @type {string[]} 6 | */ 7 | export const whiteList = ['login', 'logout', '404'] 8 | 9 | /** 10 | * 未找到页面路由 11 | * @type {{redirect: string, path: string, hidden: boolean}} 12 | */ 13 | export const notFoundRoute = { 14 | path: '/:pathMatch(.*)*', 15 | redirect: '/exception/404', 16 | meta: { 17 | isLogin: false, 18 | isMenu: false, 19 | }, 20 | } 21 | 22 | /** 23 | * 基础路由 24 | * 关键字:index,login,exception,404,redirect 25 | * @type {*[]} 26 | */ 27 | export const constantRoutes = [ 28 | { 29 | path: '/base', 30 | component: layouts.UserLayout, 31 | children: [ 32 | { 33 | path: '/login', 34 | name: 'login', 35 | component: () => import('@/views/login/index.vue'), 36 | meta: { 37 | title: '登录', 38 | }, 39 | }, 40 | ], 41 | }, 42 | ] 43 | -------------------------------------------------------------------------------- /src/router/routes/result.js: -------------------------------------------------------------------------------- 1 | import { CheckCircleOutlined } from '@ant-design/icons-vue' 2 | 3 | export default [ 4 | { 5 | path: 'result', 6 | name: 'result', 7 | component: 'RouteViewLayout', 8 | meta: { 9 | icon: CheckCircleOutlined, 10 | title: '结果页', 11 | isMenu: true, 12 | keepAlive: true, 13 | }, 14 | children: [ 15 | { 16 | path: 'success', 17 | name: 'resultSuccess', 18 | component: 'result/success/index.vue', 19 | meta: { 20 | title: '成功页', 21 | isMenu: true, 22 | keepAlive: true, 23 | }, 24 | }, 25 | { 26 | path: 'fail', 27 | name: 'resultFail', 28 | component: 'result/fail/index.vue', 29 | meta: { 30 | title: '失败页', 31 | isMenu: true, 32 | keepAlive: true, 33 | }, 34 | }, 35 | ], 36 | }, 37 | ] 38 | -------------------------------------------------------------------------------- /src/views/components/scrollbar/components/Horizontal.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 横向滚动 3 | 当元素宽度大于滚动条宽度时,会显示横向滚动条。 4 | 5 | 6 | 7 | 8 | 9 | 22 | 23 | 41 | 42 | -------------------------------------------------------------------------------- /src/router/routes/profile.js: -------------------------------------------------------------------------------- 1 | import { ProfileOutlined } from '@ant-design/icons-vue' 2 | 3 | export default [ 4 | { 5 | path: 'profile', 6 | name: 'profile', 7 | component: 'RouteViewLayout', 8 | meta: { 9 | icon: ProfileOutlined, 10 | title: '详情页', 11 | isMenu: true, 12 | keepAlive: true, 13 | }, 14 | children: [ 15 | { 16 | path: 'basic', 17 | name: 'profileBasic', 18 | component: 'profile/basic/index.vue', 19 | meta: { 20 | title: '基础详情页', 21 | isMenu: true, 22 | keepAlive: true, 23 | }, 24 | }, 25 | { 26 | path: 'advanced', 27 | name: 'profileAdvanced', 28 | component: 'profile/advanced/index.vue', 29 | meta: { 30 | title: '高级详情页', 31 | isMenu: true, 32 | keepAlive: true, 33 | }, 34 | }, 35 | ], 36 | }, 37 | ] 38 | -------------------------------------------------------------------------------- /src/views/components/tag/components/Shape.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 形状 3 | 圆的 Tag 长得像个胶囊。 4 | 5 | 6 | 7 | 14 | 15 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/views/components/cropper/components/Dialog.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 弹窗中使用 3 | 弹窗中使用 4 | 5 | 6 | 7 | 27 | 28 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/views/components/grid/components/Responsive.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 响应式栅格项 3 | 响应式栅格项 4 | 5 | 6 | 7 | 10 | 11 | 32 | 33 | 44 | 45 | -------------------------------------------------------------------------------- /src/views/other/badge/index.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/views/components/modal/components/Drag.vue: -------------------------------------------------------------------------------- 1 | 2 | # 可拖拽对话框 3 | 设置 `draggable` 属性为 `true` 以做到拖拽,试着拖动一下 `header` 部分吧 4 | 5 | 6 | 7 | 21 | 22 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /docs/hooks/use-config-provider.md: -------------------------------------------------------------------------------- 1 | # useConfigProvider 2 | 3 | 一个项目中多处使用 `createApp` 创建组件可保持风格统一。 4 | 5 | ## 代码演示 6 | 7 | ### 基础用法 8 | 9 | ```vue 10 | 15 | 16 | 21 | 22 | 23 | ``` 24 | 25 | ## API 26 | 27 | ### 返回值 28 | 29 | | 参数 | 说明 | 类型 | 30 | | ------------------- | -------------------------------------------------------------------------------------------------- | --------------------- | 31 | | configProviderAttrs | 配置信息,详见:[ConfigProvider 全局配置](https://www.antdv.com/components/config-provider-cn#api) | `ConfigProviderAttrs` | 32 | -------------------------------------------------------------------------------- /src/views/components/chart/components/LineChart.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 折线图 3 | 折线图 4 | 5 | 6 | 7 | 39 | 40 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/views/components/loading/components/GlobalSize.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 各种大小(全局) 3 | 不同的尺寸供你选择 4 | 5 | 6 | 7 | 22 | 23 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router' 2 | 3 | import { config } from '@/config' 4 | import { constantRoutes } from './config' 5 | 6 | const router = createRouter({ 7 | history: 8 | config('router.history') === 'history' 9 | ? createWebHistory(config('router.base')) 10 | : createWebHashHistory(config('router.base')), 11 | routes: [...constantRoutes], 12 | }) 13 | 14 | /** 15 | * 打开新标签页 16 | * @param to 17 | */ 18 | router.open = function (to) { 19 | const { href } = router.resolve(to) 20 | if (!href) 21 | return 22 | window.open(href) 23 | } 24 | 25 | /** 26 | * 重新启动 27 | * @param to 28 | */ 29 | router.reLaunch = function (to) { 30 | const { href } = router.resolve(to) 31 | if (!href) 32 | return 33 | window.location.href = href 34 | } 35 | 36 | export function setupRouter(app) { 37 | app.use(router) 38 | return app 39 | } 40 | 41 | export default router 42 | -------------------------------------------------------------------------------- /src/views/components/ellipsis/components/Tooltip.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 文字提示 3 | 只有内容超出后才会显示文字提示,支持 `Tooltip` 组件的所有属性 4 | 5 | 6 | 7 | 15 | 16 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/views/components/qrcode/components/Download.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 下载二维码 3 | 下载二维码的简单实现。 4 | 5 | 6 | 7 | 24 | 25 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/views/components/send-code/components/Custom.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 自定义 3 | 定义按钮样式,并将时长设置为 10s。 4 | 5 | 6 | 7 | 16 | 17 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/views/components/resize-box/components/Basic.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 基础用法 3 | 通过设置 directions,可以指定四条边中的哪几条边可以进行伸缩 4 | 5 | 6 | 7 | 8 | 9 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/views/components/chart/components/BarChart.vue: -------------------------------------------------------------------------------- 1 | 2 | ## 柱状图 3 | 柱状图 4 | 5 | 6 | 7 | 41 | 42 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/views/components/modal/components/Async.vue: -------------------------------------------------------------------------------- 1 | 2 | # 异步关闭 3 | 点击确定后异步关闭对话框,例如提交表单。 4 | 5 | 6 | 7 | 25 | 26 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /docs/components/editor.md: -------------------------------------------------------------------------------- 1 | # Editor 富文本编辑器 2 | 3 | ## 代码演示 4 | 5 | ### 基础用法 6 | 7 | <<< @/../src/views/components/editor/components/Basic.vue#snippet 8 | 9 | ### 内联编辑器 10 | 11 | <<< @/../src/views/components/editor/components/Inline.vue#snippet 12 | 13 | ### 更多用法 14 | 15 | :::tip 提示 16 | 基于 `TinyMCE` 实现,更多使用方式请参考官方文档。[https://www.tiny.cloud/docs/tinymce/6/basic-setup/](https://www.tiny.cloud/docs/tinymce/6/basic-setup/) 17 | ::: 18 | 19 | ## API 20 | 21 | ### Props 22 | 23 | | 名称 | 说明 | 类型 | 默认值 | 24 | | -------------------- | -------- | --------- | ------- | 25 | | model-value(v-model) | 内容 | `string` | - | 26 | | option | 配置 | `object` | `{}` | 27 | | height | 高 | `number` | `300` | 28 | | placeholder | 占位文本 | `string` | - | 29 | | disabled | 禁用 | `boolean` | `false` | 30 | 31 | ### Events 32 | 33 | | 事件名 | 说明 | 返回值 | 34 | | ----------- | ---------- | -------- | 35 | | initialized | 初始化完成 | `editor` | 36 | -------------------------------------------------------------------------------- /docs/components/table-column-setting.md: -------------------------------------------------------------------------------- 1 | # TableColumnSetting 列设置 2 | 3 | ## 代码演示 4 | 5 | ### 基础用法 6 | 7 | <<< @/../src/views/components/table-column-setting/components/Basic.vue#snippet 8 | 9 | ### 自定义 10 | 11 | <<< @/../src/views/components/table-column-setting/components/Custom.vue#snippet 12 | 13 | ### 动态列 14 | 15 | <<< @/../src/views/components/table-column-setting/components/DynamicColumns.vue#snippet 16 | 17 | ## API 18 | 19 | ### Props 20 | 21 | | 参数 | 说明 | 类型 | 默认值 | 22 | | ------------------ | ---- | ------- | ------ | 23 | | columns(v-model) | 列 | `array` | `[]` | 24 | 25 | ### Events 26 | 27 | | 事件名 | 说明 | 回调参数 | 28 | | ------ | ---------- | ---------------- | 29 | | change | 列发生改变 | `columns: array` | 30 | 31 | ### Methods 32 | 33 | 通过 ref 可以获取到 TableColumnSetting 实例并调用实例方法。 34 | 35 | | 方法名 | 说明 | 参数 | 返回值 | 36 | | ------ | ---------------------------- | -------------------------- | ------ | 37 | | init | 初始化,动态改变表格列时调用 | `column: array` 新的表格列 | - | 38 | -------------------------------------------------------------------------------- /src/directives/action.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name Action 3 | * @description 权限 4 | * @example v-action="'action'" || v-action="['action1', 'action2']" 5 | */ 6 | import router from '@/router' 7 | 8 | function onCheckAction(el, binding) { 9 | if (!checkAction(binding.value)) { 10 | el.remove() || (el.style.display = 'none') 11 | } 12 | } 13 | 14 | const action = { 15 | mounted: onCheckAction, 16 | updated: onCheckAction, 17 | } 18 | 19 | /** 20 | * 校验权限 21 | * @param {string | Array} actions 22 | */ 23 | export function checkAction(actions = '') { 24 | const route = router.currentRoute.value 25 | const currentActions = route?.meta?.actions ?? [] 26 | actions = typeof actions === 'string' ? actions.split() : actions 27 | 28 | if (currentActions.includes('*')) { 29 | return true 30 | } 31 | 32 | return currentActions.some(action => actions.includes(action)) 33 | } 34 | 35 | export function setupActionDirective(app) { 36 | app.directive('action', action) 37 | app.config.globalProperties.$checkAction = checkAction 38 | return app 39 | } 40 | -------------------------------------------------------------------------------- /src/layouts/components/IframeView.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 |