diff --git a/.env.development b/.env.development index 3db56fe..03e0f3d 100644 --- a/.env.development +++ b/.env.development @@ -13,14 +13,18 @@ VITE_BASE_URL = / # 是否删除console VITE_DROP_CONSOLE = true +# 跨域代理,可以配置多个,请注意不要换行 +#VITE_PROXY = [["/appApi","http://localhost:8001"],["/upload","http://localhost:8001/upload"]] +# VITE_PROXY=[["/api","https://naive-ui-admin"]] + # API 接口地址 -VITE_APP_API_URL = / +VITE_GLOB_API_URL = # 图片上传地址 -VITE_GLOB_UPLOAD_URL= / +VITE_GLOB_UPLOAD_URL= # 图片前缀地址 -VITE_GLOB_IMG_URL= / +VITE_GLOB_IMG_URL= # 接口前缀 VITE_GLOB_API_URL_PREFIX = /api diff --git a/.env.production b/.env.production index 295f78a..0ee697f 100644 --- a/.env.production +++ b/.env.production @@ -11,13 +11,21 @@ VITE_BASE_URL = / VITE_DROP_CONSOLE = true # API -VITE_APP_API_URL = / +VITE_GLOB_API_URL = # 图片上传地址 -VITE_GLOB_UPLOAD_URL= / +VITE_GLOB_UPLOAD_URL= # 图片前缀地址 -VITE_GLOB_IMG_URL= / +VITE_GLOB_IMG_URL= # 接口前缀 VITE_GLOB_API_URL_PREFIX = /api + +# 是否启用gzip压缩或brotli压缩 +# 可选: gzip | brotli | none +# 如果你需要多种形式,你可以用','来分隔 +VITE_BUILD_COMPRESS = 'none' + +# 使用压缩时是否删除原始文件,默认为false +VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false diff --git a/CHANGELOG.md b/CHANGELOG.md index 53ac8ef..978878f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +# 1.5 (2021-07-30) +### 🐛 Bug Fixes +- 修复表格列配置,拖拽时最后的操作列重复增加 +- 多标签页交互优化 + +- ### ✨ Features +- `项目文档`已上线 +- `Application`组件加载机制优化,解决路由守卫,Axios中可使用,Dialog,Message 等之类组件 +- `BasicTable` 组件新增`高度自适应`,`单元格编辑`,`整行编辑` 特性 +- `nprogress` 移除,用 `Loading Bar`代替 +- 打包支持`gzip`,`brotli` 压缩 +- 新增代理`VITE_PROXY`配置 +- 路由菜单,支持多级菜单 +- 依赖升级 +- 本次更新,有破坏性更新,涉及文件重命名,增删调整 + + # 1.4 (2021-07-21) ### 🐛 Bug Fixes - vite降至2.3.6 diff --git a/README.md b/README.md index 6cca18f..2483810 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## 简介 -Naive Ui Admin 是一个免费开源的中后台模版,使用了最新的`vue3`,`vite2`,`TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,也可用于学习参考。 +[Naive Ui Admin](https://github.com/jekip/naive-ui-admin) 是一个基于 [Vue3.0](https://github.com/vuejs/vue-next)、[Vite](https://github.com/vitejs/vite)、 [Naive UI](https://www.naiveui.com/)、[TypeScript](https://www.typescriptlang.org/) 的中后台解决方案,它使用了最新的前端技术栈,并提炼了典型的业务模型,页面,包括二次封装组件、动态菜单、权限校验、粒子化权限控制等功能,它可以帮助你快速搭建企业级中后台项目,该项目使用最新的前端技术栈,相信不管是从新技术使用还是其他方面,都能帮助到你。 ## 特性 - **最新技术栈**:使用 Vue3/vite2 等前端前沿技术开发 @@ -10,35 +10,15 @@ Naive Ui Admin 是一个免费开源的中后台模版,使用了最新的`vue3 - **权限** 内置完善的动态路由权限生成方案 - **组件** 二次封装了多个常用的组件 -### 页面功能 -#### 系统看板 -- [x] 主控台 -- [ ] 监控页 -- [x] 工作台 -- [x] 表单页面 -- [x] 列表页面 -- [x] 异常页面 -- [x] 结果页面 -- [x] 设置页面 -- [x] 系统设置 -- [x] 菜单权限 -- [x] 角色权限 - -### 页面组件 -#### ProTable -- [x] 基础表格 -- [x] 上传图片 -- [x] 滑块验证码 -- 持续开发中... ## 在线预览 - [naive-ui-admin](https://jekip.github.io) -账号:admin,密码:123456 +账号:admin,密码:123456(随意) ## 文档 -[文档地址](https://github.com/jekip/naive-ui-admin) - 待完善 +[文档地址](https://jekip.github.io/docs/) ## 准备 @@ -85,7 +65,7 @@ yarn build [CHANGELOG](./CHANGELOG.md) ## 感谢 -[@Vben](https://github.com/anncwb/vue-vben-admin) 借鉴 vue-vben-admin 实现的骨架,同时也使用作者开发的 vite 插件,非常感谢作者。 +[@Vben](https://github.com/anncwb/vue-vben-admin) 借鉴 vue-vben-admin 实现的骨架,同时也使用作者开发的 vite 插件,再次感谢作者。 ## 如何贡献 diff --git a/build/utils.ts b/build/utils.ts index a731048..a3059f6 100644 --- a/build/utils.ts +++ b/build/utils.ts @@ -31,8 +31,7 @@ export function wrapperEnv(envConf: Recordable): ViteEnv { if (envName === 'VITE_PROXY') { try { realName = JSON.parse(realName); - } catch (error) { - } + } catch (error) {} } ret[envName] = realName; process.env[envName] = realName; @@ -51,12 +50,11 @@ export function getEnvConfig(match = 'VITE_GLOB_', confFiles = ['.env', '.env.pr try { const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item))); envConfig = { ...envConfig, ...env }; - } catch (error) { - } + } catch (error) {} }); Object.keys(envConfig).forEach((key) => { - const reg = new RegExp(`^(${ match })`); + const reg = new RegExp(`^(${match})`); if (!reg.test(key)) { Reflect.deleteProperty(envConfig, key); } diff --git a/build/vite/plugin/compress.ts b/build/vite/plugin/compress.ts new file mode 100644 index 0000000..9563679 --- /dev/null +++ b/build/vite/plugin/compress.ts @@ -0,0 +1,35 @@ +/** + * Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated + * https://github.com/anncwb/vite-plugin-compression + */ +import type { Plugin } from 'vite'; + +import compressPlugin from 'vite-plugin-compression'; + +export function configCompressPlugin( + compress: 'gzip' | 'brotli' | 'none', + deleteOriginFile = false +): Plugin | Plugin[] { + const compressList = compress.split(','); + + const plugins: Plugin[] = []; + + if (compressList.includes('gzip')) { + plugins.push( + compressPlugin({ + ext: '.gz', + deleteOriginFile, + }) + ); + } + if (compressList.includes('brotli')) { + plugins.push( + compressPlugin({ + ext: '.br', + algorithm: 'brotliCompress', + deleteOriginFile, + }) + ); + } + return plugins; +} diff --git a/build/vite/plugin/index.ts b/build/vite/plugin/index.ts index 0fbb6f5..e6104bd 100644 --- a/build/vite/plugin/index.ts +++ b/build/vite/plugin/index.ts @@ -5,9 +5,10 @@ import vueJsx from '@vitejs/plugin-vue-jsx'; import { configHtmlPlugin } from './html'; import { configMockPlugin } from './mock'; +import { configCompressPlugin } from './compress'; export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock) { - const { VITE_USE_MOCK } = viteEnv; + const { VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv; const vitePlugins: (Plugin | Plugin[])[] = [ // have to @@ -22,5 +23,12 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock) // vite-plugin-mock VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild, prodMock)); + if (isBuild) { + // rollup-plugin-gzip + vitePlugins.push( + configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE) + ); + } + return vitePlugins; } diff --git a/build/vite/proxy.ts b/build/vite/proxy.ts new file mode 100644 index 0000000..dc23646 --- /dev/null +++ b/build/vite/proxy.ts @@ -0,0 +1,34 @@ +/** + * Used to parse the .env.development proxy configuration + */ +import type { ProxyOptions } from 'vite'; + +type ProxyItem = [string, string]; + +type ProxyList = ProxyItem[]; + +type ProxyTargetList = Record string }>; + +const httpsRE = /^https:\/\//; + +/** + * Generate proxy + * @param list + */ +export function createProxy(list: ProxyList = []) { + const ret: ProxyTargetList = {}; + for (const [prefix, target] of list) { + const isHttps = httpsRE.test(target); + + // https://github.com/http-party/node-http-proxy#options + ret[prefix] = { + target: target, + changeOrigin: true, + ws: true, + rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''), + // https is require secure=false + ...(isHttps ? { secure: false } : {}), + }; + } + return ret; +} diff --git a/index.html b/index.html index 96e8993..0bbcfde 100644 --- a/index.html +++ b/index.html @@ -13,6 +13,7 @@ +
diff --git a/mock/table/list.ts b/mock/table/list.ts index d807f5e..f701a88 100644 --- a/mock/table/list.ts +++ b/mock/table/list.ts @@ -14,7 +14,7 @@ const tableList = (pageSize) => { date: `@date('yyyy-MM-dd')`, time: `@time('HH:mm')`, 'no|100000-10000000': 100000, - 'status|1': ['normal', 'enable', 'disable'], + 'status|1': [true, false], }); }); return result; diff --git a/package.json b/package.json index 44d9765..81f1352 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "naive-ui-admin", - "version": "1.4", + "version": "1.5", "author": { "name": "Ahjung", "email": "735878602@qq.com", @@ -8,8 +8,12 @@ }, "private": true, "scripts": { + "bootstrap": "yarn install", + "serve": "npm run dev", "dev": "vite", "build": "vite build && esno ./build/script/postBuild.ts", + "build:no-cache": "yarn clean:cache && npm run build", + "report": "cross-env REPORT=true npm run build", "preview": "vite preview", "build typecheck": "vuedx-typecheck . && vite build", "deploy": "gh-pages -d dist", @@ -34,8 +38,7 @@ "makeit-captcha": "^1.2.5", "mitt": "^2.1.0", "mockjs": "^1.1.0", - "naive-ui": "^2.15.5", - "nprogress": "^1.0.0-1", + "naive-ui": "^2.15.11", "pinia": "^2.0.0-beta.3", "qs": "^6.10.1", "vfonts": "^0.1.0", @@ -76,14 +79,16 @@ "postcss": "^8.3.5", "prettier": "^2.3.1", "pretty-quick": "^3.1.0", + "rimraf": "^3.0.2", "stylelint": "^13.13.1", "stylelint-config-prettier": "^8.0.2", "stylelint-config-standard": "^22.0.0", "stylelint-order": "^4.1.0", "stylelint-scss": "^3.19.0", - "tailwindcss": "^2.2.4", - "typescript": "^4.3.2", + "tailwindcss": "^2.2.7", + "typescript": "^4.3.5", "vite": "2.3.6", + "vite-plugin-compression": "^0.3.1", "vite-plugin-html": "^2.0.7", "vite-plugin-mock": "^2.9.3", "vite-plugin-style-import": "^1.0.1", diff --git a/src/App.vue b/src/App.vue index 130a17d..19c97cd 100644 --- a/src/App.vue +++ b/src/App.vue @@ -11,7 +11,7 @@ - + @@ -34,9 +34,7 @@ const designStore = useDesignSettingStore(); const isLock = computed(() => useLockscreen.isLock); const lockTime = computed(() => useLockscreen.lockTime); - /** - * @type import('naive-ui').GlobalThemeOverrides - */ + const getThemeOverrides = computed(() => { return { common: { @@ -89,9 +87,7 @@ diff --git a/src/components/Table/src/components/editable/helper.ts b/src/components/Table/src/components/editable/helper.ts new file mode 100644 index 0000000..48e7682 --- /dev/null +++ b/src/components/Table/src/components/editable/helper.ts @@ -0,0 +1,15 @@ +import { ComponentType } from '../../types/componentType'; + +/** + * @description: 生成placeholder + */ +export function createPlaceholderMessage(component: ComponentType) { + if (component === 'NInput') return '请输入'; + if ( + ['NPicker', 'NSelect', 'NCheckbox', 'NRadio', 'NSwitch', 'NDatePicker', 'NTimePicker'].includes( + component + ) + ) + return '请选择'; + return ''; +} diff --git a/src/components/Table/src/components/editable/index.ts b/src/components/Table/src/components/editable/index.ts new file mode 100644 index 0000000..064763d --- /dev/null +++ b/src/components/Table/src/components/editable/index.ts @@ -0,0 +1,49 @@ +import type { BasicColumn } from '@/components/Table/src/types/table'; +import { h, Ref } from 'vue'; + +import EditableCell from './EditableCell.vue'; + +export function renderEditCell(column: BasicColumn) { + return (record, index) => { + const _key = column.key; + const value = record[_key]; + record.onEdit = async (edit: boolean, submit = false) => { + if (!submit) { + record.editable = edit; + } + + if (!edit && submit) { + const res = await record.onSubmitEdit?.(); + if (res) { + record.editable = false; + return true; + } + return false; + } + // cancel + if (!edit && !submit) { + record.onCancelEdit?.(); + } + return true; + }; + return h(EditableCell, { + value, + record, + column, + index, + }); + }; +} + +export type EditRecordRow = Partial< + { + onEdit: (editable: boolean, submit?: boolean) => Promise; + editable: boolean; + onCancel: Fn; + onSubmit: Fn; + submitCbs: Fn[]; + cancelCbs: Fn[]; + validCbs: Fn[]; + editValueRefs: Recordable; + } & T +>; diff --git a/src/components/Table/src/components/settings/ColumnSetting.vue b/src/components/Table/src/components/settings/ColumnSetting.vue index 5b46b30..930fa8b 100644 --- a/src/components/Table/src/components/settings/ColumnSetting.vue +++ b/src/components/Table/src/components/settings/ColumnSetting.vue @@ -158,7 +158,7 @@ table.getColumns().forEach((item) => { newRet.push({ ...item }); }); - return newRet; + return newRet.filter((item) => item.key != 'action' && item.title != '操作'); } //重置 diff --git a/src/components/Table/src/hooks/useColumns.ts b/src/components/Table/src/hooks/useColumns.ts index 29c7ba5..50150db 100644 --- a/src/components/Table/src/hooks/useColumns.ts +++ b/src/components/Table/src/hooks/useColumns.ts @@ -1,9 +1,12 @@ -import { ref, Ref, ComputedRef, unref, computed, watch, toRaw } from 'vue'; +import { ref, Ref, ComputedRef, unref, computed, watch, toRaw, h } from 'vue'; import type { BasicColumn, BasicTableProps } from '../types/table'; import { isEqual, cloneDeep } from 'lodash-es'; import { isArray, isString, isBoolean, isFunction } from '@/utils/is'; import { usePermission } from '@/hooks/web/usePermission'; import { ActionItem } from '@/components/Table'; +import { renderEditCell } from '../components/editable'; +import { NTooltip, NIcon } from 'naive-ui'; +import { FormOutlined } from '@vicons/antd'; export function useColumns(propsRef: ComputedRef) { const columnsRef = ref(unref(propsRef).columns) as unknown as Ref; @@ -33,13 +36,48 @@ export function useColumns(propsRef: ComputedRef) { return isIfShow; } + const renderTooltip = (trigger, content) => { + return h(NTooltip, null, { + trigger: () => trigger, + default: () => content, + }); + }; + const getPageColumns = computed(() => { const pageColumns = unref(getColumnsRef); const columns = cloneDeep(pageColumns); - return columns.filter((column) => { - // @ts-ignore - return hasPermission(column.auth) && isIfShow(column); - }); + return columns + .filter((column) => { + // @ts-ignore + return hasPermission(column.auth) && isIfShow(column); + }) + .map((column) => { + const { edit, editRow } = column; + if (edit) { + column.render = renderEditCell(column); + if (edit) { + const title: any = column.title; + column.title = () => { + return renderTooltip( + h('span', {}, [ + h('span', { style: { 'margin-right': '5px' } }, title), + h( + NIcon, + { + size: 14, + }, + { + default: () => h(FormOutlined), + } + ), + ]), + '该列可编辑' + ); + }; + } + } + return column; + }); }); watch( diff --git a/src/components/Table/src/props.ts b/src/components/Table/src/props.ts index 36c770b..1942c7a 100644 --- a/src/components/Table/src/props.ts +++ b/src/components/Table/src/props.ts @@ -1,4 +1,5 @@ import type { PropType } from 'vue'; +import { propTypes } from '@/utils/propTypes'; import { BasicColumn } from './types/table'; export const basicProps = { @@ -16,7 +17,7 @@ export const basicProps = { }, tableData: { type: [Object], - default: () => {}, + default: () => [], }, columns: { type: [Array] as PropType, @@ -36,6 +37,7 @@ export const basicProps = { type: [Object, Boolean], default: () => {}, }, + //废弃 showPagination: { type: [String, Boolean], default: 'auto', @@ -44,4 +46,6 @@ export const basicProps = { type: Object as PropType, default: null, }, + canResize: propTypes.bool.def(true), + resizeHeightOffset: propTypes.number.def(0), }; diff --git a/src/components/Table/src/types/componentType.ts b/src/components/Table/src/types/componentType.ts new file mode 100644 index 0000000..e926748 --- /dev/null +++ b/src/components/Table/src/types/componentType.ts @@ -0,0 +1,8 @@ +export type ComponentType = + | 'NInput' + | 'NInputNumber' + | 'NSelect' + | 'NCheckbox' + | 'NSwitch' + | 'NDatePicker' + | 'NTimePicker'; diff --git a/src/components/Table/src/types/table.ts b/src/components/Table/src/types/table.ts index c375b36..9e8830d 100644 --- a/src/components/Table/src/types/table.ts +++ b/src/components/Table/src/types/table.ts @@ -1,6 +1,20 @@ import type { TableBaseColumn } from 'naive-ui/lib/data-table/src/interface'; - -export type BasicColumn = TableBaseColumn; +import { ComponentType } from './componentType'; +export interface BasicColumn extends TableBaseColumn { + //编辑表格 + edit?: boolean; + editRow?: boolean; + editable?: boolean; + editComponent?: ComponentType; + editComponentProps?: Recordable; + editRule?: boolean | ((text: string, record: Recordable) => Promise); + editValueMap?: (value: any) => string; + onEditRow?: () => void; + // 权限编码控制是否显示 + auth?: RoleEnum | RoleEnum[] | string | string[]; + // 业务控制是否显示 + ifShow?: boolean | ((column: BasicColumn) => boolean); +} export interface TableActionType { reload: (opt) => Promise; @@ -16,4 +30,6 @@ export interface BasicTableProps { pagination: object; showPagination: boolean; actionColumn: any[]; + canResize: boolean; + resizeHeightOffset: number; } diff --git a/src/directives/clickOutside.ts b/src/directives/clickOutside.ts new file mode 100644 index 0000000..2758a9d --- /dev/null +++ b/src/directives/clickOutside.ts @@ -0,0 +1,86 @@ +import { on } from '@/utils/domUtils'; +import { isServer } from '@/utils/is'; +import type { ComponentPublicInstance, DirectiveBinding, ObjectDirective } from 'vue'; + +type DocumentHandler = (mouseup: T, mousedown: T) => void; + +type FlushList = Map< + HTMLElement, + { + documentHandler: DocumentHandler; + bindingFn: (...args: unknown[]) => unknown; + } +>; + +const nodeList: FlushList = new Map(); + +let startClick: MouseEvent; + +if (!isServer) { + on(document, 'mousedown', (e: MouseEvent) => (startClick = e)); + on(document, 'mouseup', (e: MouseEvent) => { + for (const { documentHandler } of nodeList.values()) { + documentHandler(e, startClick); + } + }); +} + +function createDocumentHandler(el: HTMLElement, binding: DirectiveBinding): DocumentHandler { + let excludes: HTMLElement[] = []; + if (Array.isArray(binding.arg)) { + excludes = binding.arg; + } else { + // due to current implementation on binding type is wrong the type casting is necessary here + excludes.push(binding.arg as unknown as HTMLElement); + } + return function (mouseup, mousedown) { + const popperRef = ( + binding.instance as ComponentPublicInstance<{ + popperRef: Nullable; + }> + ).popperRef; + const mouseUpTarget = mouseup.target as Node; + const mouseDownTarget = mousedown.target as Node; + const isBound = !binding || !binding.instance; + const isTargetExists = !mouseUpTarget || !mouseDownTarget; + const isContainedByEl = el.contains(mouseUpTarget) || el.contains(mouseDownTarget); + const isSelf = el === mouseUpTarget; + + const isTargetExcluded = + (excludes.length && excludes.some((item) => item?.contains(mouseUpTarget))) || + (excludes.length && excludes.includes(mouseDownTarget as HTMLElement)); + const isContainedByPopper = + popperRef && (popperRef.contains(mouseUpTarget) || popperRef.contains(mouseDownTarget)); + if ( + isBound || + isTargetExists || + isContainedByEl || + isSelf || + isTargetExcluded || + isContainedByPopper + ) { + return; + } + binding.value(); + }; +} + +const ClickOutside: ObjectDirective = { + beforeMount(el, binding) { + nodeList.set(el, { + documentHandler: createDocumentHandler(el, binding), + bindingFn: binding.value, + }); + }, + updated(el, binding) { + nodeList.set(el, { + documentHandler: createDocumentHandler(el, binding), + bindingFn: binding.value, + }); + }, + unmounted(el) { + nodeList.delete(el); + }, +}; + +export default ClickOutside; diff --git a/src/hooks/event/useWindowSizeFn.ts b/src/hooks/event/useWindowSizeFn.ts new file mode 100644 index 0000000..7b18ca0 --- /dev/null +++ b/src/hooks/event/useWindowSizeFn.ts @@ -0,0 +1,36 @@ +import { tryOnMounted, tryOnUnmounted } from '@vueuse/core'; +import { useDebounceFn } from '@vueuse/core'; + +interface WindowSizeOptions { + once?: boolean; + immediate?: boolean; + listenerOptions?: AddEventListenerOptions | boolean; +} + +export function useWindowSizeFn(fn: Fn, wait = 150, options?: WindowSizeOptions) { + let handler = () => { + fn(); + }; + const handleSize = useDebounceFn(handler, wait); + handler = handleSize; + + const start = () => { + if (options && options.immediate) { + handler(); + } + window.addEventListener('resize', handler); + }; + + const stop = () => { + window.removeEventListener('resize', handler); + }; + + tryOnMounted(() => { + start(); + }); + + tryOnUnmounted(() => { + stop(); + }); + return [start, stop]; +} diff --git a/src/layout/components/Footer/index.vue b/src/layout/components/Footer/index.vue index d21761e..6202799 100644 --- a/src/layout/components/Footer/index.vue +++ b/src/layout/components/Footer/index.vue @@ -23,7 +23,7 @@ diff --git a/src/views/comp/table/basicColumns.ts b/src/views/comp/table/basicColumns.ts new file mode 100644 index 0000000..b536eda --- /dev/null +++ b/src/views/comp/table/basicColumns.ts @@ -0,0 +1,66 @@ +import { h } from 'vue'; +import { NAvatar, NTag } from 'naive-ui'; + +export const columns = [ + { + title: 'id', + key: 'id', + }, + { + title: '编码', + key: 'no', + }, + { + title: '名称', + key: 'name', + width: 200, + }, + { + title: '头像', + key: 'avatar', + render(row) { + return h(NAvatar, { + size: 48, + src: row.avatar, + }); + }, + }, + { + title: '地址', + key: 'address', + width: 200, + }, + { + title: '开始日期', + key: 'beginTime', + width: 200, + }, + { + title: '结束日期', + key: 'endTime', + width: 200, + }, + { + title: '状态', + key: 'status', + render(row) { + return h( + NTag, + { + type: row.status ? 'success' : 'error', + }, + { + default: () => (row.status ? '启用' : '禁用'), + } + ); + }, + }, + { + title: '创建时间', + key: 'date', + }, + { + title: '停留时间', + key: 'time', + }, +]; diff --git a/src/views/comp/table/columns.ts b/src/views/comp/table/columns.ts deleted file mode 100644 index 943007f..0000000 --- a/src/views/comp/table/columns.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { h } from 'vue'; -import { NAvatar, NButton } from 'naive-ui'; - -export const columns = [ - { - title: 'id', - key: 'id', - }, - { - title: '名称', - key: 'name', - }, - { - title: '头像', - key: 'avatar', - render(row) { - return h(NAvatar, { - size: 48, - src: row.avatar, - }); - }, - }, - { - title: '地址', - key: 'address', - }, - { - title: '开始日期', - key: 'beginTime', - }, - { - title: '结束日期', - key: 'endTime', - }, - { - title: '创建时间', - key: 'date', - }, - { - title: '操作', - key: 'actions', - width: 150, - //简单写一下例子,不建议这么写,过段时间,这里封二次封装 - render() { - return [ - h( - NButton, - { - size: 'small', - type: 'error', - style: 'margin-right:10px', - onClick: () => {}, - }, - { default: () => '删除' } - ), - h( - NButton, - { - size: 'small', - onClick: () => {}, - }, - { default: () => '编辑' } - ), - ]; - }, - }, -]; diff --git a/src/views/comp/table/editCell.vue b/src/views/comp/table/editCell.vue new file mode 100644 index 0000000..07dc2ee --- /dev/null +++ b/src/views/comp/table/editCell.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/src/views/comp/table/editRow.vue b/src/views/comp/table/editRow.vue new file mode 100644 index 0000000..90e44e5 --- /dev/null +++ b/src/views/comp/table/editRow.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/src/views/comp/table/list.vue b/src/views/comp/table/list.vue deleted file mode 100644 index 024a623..0000000 --- a/src/views/comp/table/list.vue +++ /dev/null @@ -1,61 +0,0 @@ - - - - - diff --git a/src/views/comp/table/rowColumns.ts b/src/views/comp/table/rowColumns.ts new file mode 100644 index 0000000..4ea4baf --- /dev/null +++ b/src/views/comp/table/rowColumns.ts @@ -0,0 +1,89 @@ +import { h } from 'vue'; +import { NAvatar } from 'naive-ui'; + +export const columns = [ + { + title: 'id', + key: 'id', + }, + { + title: '编码', + key: 'no', + }, + { + title: '名称', + key: 'name', + editComponent: 'NInput', + editRow: true, + // 默认必填校验 + editRule: true, + edit: true, + width: 200, + }, + { + title: '头像', + key: 'avatar', + render(row) { + return h(NAvatar, { + size: 48, + src: row.avatar, + }); + }, + }, + { + title: '地址', + key: 'address', + editRow: true, + editComponent: 'NSelect', + editComponentProps: { + options: [ + { + label: '广东省', + value: 1, + }, + { + label: '浙江省', + value: 2, + }, + ], + }, + edit: true, + width: 200, + }, + { + title: '开始日期', + key: 'beginTime', + editRow: true, + edit: true, + width: 250, + editComponent: 'NDatePicker', + editComponentProps: { + type: 'datetime', + format: 'yyyy-MM-dd HH:mm:ss', + }, + }, + { + title: '结束日期', + key: 'endTime', + width: 200, + }, + { + title: '状态', + key: 'status', + editRow: true, + edit: true, + width: 100, + editComponent: 'NSwitch', + editValueMap: (value) => { + return value ? '启用' : '禁用'; + }, + }, + { + title: '创建时间', + key: 'date', + }, + { + title: '停留时间', + key: 'time', + }, +]; diff --git a/src/views/login/index.vue b/src/views/login/index.vue index 3219e51..98f7dab 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -219,7 +219,7 @@ @media (min-width: 768px) { .view-account { - background-image: url('@/assets/images/login.svg'); + background-image: url('../../assets/images/login.svg'); background-repeat: no-repeat; background-position: 50%; background-size: 100%; diff --git a/src/views/system/menu/menu.vue b/src/views/system/menu/menu.vue index 6a50f5f..1c8d357 100644 --- a/src/views/system/menu/menu.vue +++ b/src/views/system/menu/menu.vue @@ -5,7 +5,6 @@ 页面数据为 Mock 示例数据,非真实数据。
- @@ -35,7 +34,6 @@ - @@ -77,7 +76,7 @@ 编辑菜单{{ treeItemTitle ? `:${treeItemTitle}` : '' }} - 从菜单列表选择一项后,进行编辑 + 从菜单列表选择一项后,进行编辑 -
-