diff --git a/ruoyi-ui-next/src/components/business/Crontab/day.vue b/ruoyi-ui-next/src/components/business/Crontab/day.vue new file mode 100644 index 0000000..bee1b61 --- /dev/null +++ b/ruoyi-ui-next/src/components/business/Crontab/day.vue @@ -0,0 +1,161 @@ + + + diff --git a/ruoyi-ui-next/src/components/business/Crontab/hour.vue b/ruoyi-ui-next/src/components/business/Crontab/hour.vue new file mode 100644 index 0000000..245b334 --- /dev/null +++ b/ruoyi-ui-next/src/components/business/Crontab/hour.vue @@ -0,0 +1,118 @@ + + + diff --git a/ruoyi-ui-next/src/components/business/Crontab/index.vue b/ruoyi-ui-next/src/components/business/Crontab/index.vue new file mode 100644 index 0000000..c5ee695 --- /dev/null +++ b/ruoyi-ui-next/src/components/business/Crontab/index.vue @@ -0,0 +1,375 @@ + + + + + diff --git a/ruoyi-ui-next/src/components/business/Crontab/min.vue b/ruoyi-ui-next/src/components/business/Crontab/min.vue new file mode 100644 index 0000000..1f78b7c --- /dev/null +++ b/ruoyi-ui-next/src/components/business/Crontab/min.vue @@ -0,0 +1,118 @@ + + + diff --git a/ruoyi-ui-next/src/components/business/Crontab/month.vue b/ruoyi-ui-next/src/components/business/Crontab/month.vue new file mode 100644 index 0000000..0ee727e --- /dev/null +++ b/ruoyi-ui-next/src/components/business/Crontab/month.vue @@ -0,0 +1,118 @@ + + + diff --git a/ruoyi-ui-next/src/components/business/Crontab/result.vue b/ruoyi-ui-next/src/components/business/Crontab/result.vue new file mode 100644 index 0000000..7fbe097 --- /dev/null +++ b/ruoyi-ui-next/src/components/business/Crontab/result.vue @@ -0,0 +1,432 @@ + + + + + diff --git a/ruoyi-ui-next/src/components/business/Crontab/second.vue b/ruoyi-ui-next/src/components/business/Crontab/second.vue new file mode 100644 index 0000000..872b85d --- /dev/null +++ b/ruoyi-ui-next/src/components/business/Crontab/second.vue @@ -0,0 +1,118 @@ + + + diff --git a/ruoyi-ui-next/src/components/business/Crontab/week.vue b/ruoyi-ui-next/src/components/business/Crontab/week.vue new file mode 100644 index 0000000..9ce823b --- /dev/null +++ b/ruoyi-ui-next/src/components/business/Crontab/week.vue @@ -0,0 +1,183 @@ + + + diff --git a/ruoyi-ui-next/src/components/business/Crontab/year.vue b/ruoyi-ui-next/src/components/business/Crontab/year.vue new file mode 100644 index 0000000..c1b85e5 --- /dev/null +++ b/ruoyi-ui-next/src/components/business/Crontab/year.vue @@ -0,0 +1,125 @@ + + + diff --git a/ruoyi-ui-next/src/components/business/RightToolbar/index.vue b/ruoyi-ui-next/src/components/business/RightToolbar/index.vue new file mode 100644 index 0000000..b3058e9 --- /dev/null +++ b/ruoyi-ui-next/src/components/business/RightToolbar/index.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/ruoyi-ui-next/src/components/layout/AppMain.vue b/ruoyi-ui-next/src/components/layout/AppMain.vue new file mode 100644 index 0000000..a7c2058 --- /dev/null +++ b/ruoyi-ui-next/src/components/layout/AppMain.vue @@ -0,0 +1,54 @@ + + + + + + + diff --git a/ruoyi-ui-next/src/components/layout/Breadcrumb/index.vue b/ruoyi-ui-next/src/components/layout/Breadcrumb/index.vue new file mode 100644 index 0000000..95a75e1 --- /dev/null +++ b/ruoyi-ui-next/src/components/layout/Breadcrumb/index.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/ruoyi-ui-next/src/components/layout/Navbar/index.vue b/ruoyi-ui-next/src/components/layout/Navbar/index.vue new file mode 100644 index 0000000..76907f4 --- /dev/null +++ b/ruoyi-ui-next/src/components/layout/Navbar/index.vue @@ -0,0 +1,194 @@ + + + + + diff --git a/ruoyi-ui-next/src/components/layout/TagsView/ScrollPane.vue b/ruoyi-ui-next/src/components/layout/TagsView/ScrollPane.vue new file mode 100644 index 0000000..ae74d11 --- /dev/null +++ b/ruoyi-ui-next/src/components/layout/TagsView/ScrollPane.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/ruoyi-ui-next/src/components/layout/TagsView/index.vue b/ruoyi-ui-next/src/components/layout/TagsView/index.vue new file mode 100644 index 0000000..65c16bb --- /dev/null +++ b/ruoyi-ui-next/src/components/layout/TagsView/index.vue @@ -0,0 +1,389 @@ + + + + + + + diff --git a/ruoyi-ui-next/src/components/ui/DictTag/index.vue b/ruoyi-ui-next/src/components/ui/DictTag/index.vue new file mode 100644 index 0000000..16d0064 --- /dev/null +++ b/ruoyi-ui-next/src/components/ui/DictTag/index.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/ruoyi-ui-next/src/components/ui/Editor/index.vue b/ruoyi-ui-next/src/components/ui/Editor/index.vue new file mode 100644 index 0000000..036a22c --- /dev/null +++ b/ruoyi-ui-next/src/components/ui/Editor/index.vue @@ -0,0 +1,292 @@ + + + + + diff --git a/ruoyi-ui-next/src/components/ui/FileUpload/index.vue b/ruoyi-ui-next/src/components/ui/FileUpload/index.vue new file mode 100644 index 0000000..b12a7b5 --- /dev/null +++ b/ruoyi-ui-next/src/components/ui/FileUpload/index.vue @@ -0,0 +1,184 @@ + + + + + diff --git a/ruoyi-ui-next/src/components/ui/HeaderSearch/index.vue b/ruoyi-ui-next/src/components/ui/HeaderSearch/index.vue new file mode 100644 index 0000000..10d8040 --- /dev/null +++ b/ruoyi-ui-next/src/components/ui/HeaderSearch/index.vue @@ -0,0 +1,205 @@ + + + + + diff --git a/ruoyi-ui-next/src/components/ui/IconSelect/index.vue b/ruoyi-ui-next/src/components/ui/IconSelect/index.vue new file mode 100644 index 0000000..ef2d5b1 --- /dev/null +++ b/ruoyi-ui-next/src/components/ui/IconSelect/index.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/ruoyi-ui-next/src/components/ui/IconSelect/requireIcons.ts b/ruoyi-ui-next/src/components/ui/IconSelect/requireIcons.ts new file mode 100644 index 0000000..f060e87 --- /dev/null +++ b/ruoyi-ui-next/src/components/ui/IconSelect/requireIcons.ts @@ -0,0 +1,73 @@ +// Icon list for IconSelect component +// These icons should match the SVG files in src/assets/icons/svg/ +// Update this list when adding new icons + +const icons: string[] = [ + 'search', + 'user', + 'home', + 'setting', + 'menu', + 'dashboard', + 'system', + 'monitor', + 'tool', + 'log', + 'link', + 'example', + 'component', + 'form', + 'table', + 'chart', + 'lock', + 'unlock', + 'eye', + 'eye-open', + 'qq', + 'wechat', + 'email', + 'phone', + 'download', + 'upload', + 'print', + 'zip', + 'excel', + 'pdf', + 'word', + 'edit', + 'delete', + 'add', + 'save', + 'close', + 'refresh', + 'check', + 'warning', + 'error', + 'info', + 'success', + 'question', + 'star', + 'location', + 'calendar', + 'time', + 'document', + 'folder', + 'image', + 'video', + 'music', + 'play', + 'pause', + 'stop', + 'forward', + 'back', + 'top', + 'right', + 'left', + 'down', + 'arrow-up', + 'arrow-down', + 'arrow-left', + 'arrow-right' +] + +export default icons diff --git a/ruoyi-ui-next/src/components/ui/ImageUpload/index.vue b/ruoyi-ui-next/src/components/ui/ImageUpload/index.vue new file mode 100644 index 0000000..cabf68c --- /dev/null +++ b/ruoyi-ui-next/src/components/ui/ImageUpload/index.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/ruoyi-ui-next/src/components/ui/Pagination/index.vue b/ruoyi-ui-next/src/components/ui/Pagination/index.vue new file mode 100644 index 0000000..fed34f4 --- /dev/null +++ b/ruoyi-ui-next/src/components/ui/Pagination/index.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/ruoyi-ui-next/src/components/ui/Screenfull/index.vue b/ruoyi-ui-next/src/components/ui/Screenfull/index.vue new file mode 100644 index 0000000..2f93eef --- /dev/null +++ b/ruoyi-ui-next/src/components/ui/Screenfull/index.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/ruoyi-ui-next/src/components/ui/SizeSelect/index.vue b/ruoyi-ui-next/src/components/ui/SizeSelect/index.vue new file mode 100644 index 0000000..fd724f6 --- /dev/null +++ b/ruoyi-ui-next/src/components/ui/SizeSelect/index.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/ruoyi-ui-next/src/stores/modules/tagsView.ts b/ruoyi-ui-next/src/stores/modules/tagsView.ts new file mode 100644 index 0000000..4d5d1c4 --- /dev/null +++ b/ruoyi-ui-next/src/stores/modules/tagsView.ts @@ -0,0 +1,177 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import type { RouteLocationNormalizedLoaded } from 'vue-router' +import type { TagView } from '@/types' + +export const useTagsViewStore = defineStore('tagsView', () => { + const visitedViews = ref([]) + const cachedViews = ref([]) + + function addVisitedView(view: TagView) { + const index = visitedViews.value.findIndex(v => v.path === view.path) + if (index >= 0) { + visitedViews.value[index] = { ...visitedViews.value[index], ...view } + } else { + visitedViews.value.push({ ...view }) + } + } + + function addCachedView(view: TagView) { + const viewName = view.name + if (!viewName) return + if (cachedViews.value.includes(viewName)) return + if (view.meta?.noCache) return + cachedViews.value.push(viewName) + } + + function addView(view: TagView) { + addVisitedView(view) + addCachedView(view) + } + + function addVisitedViewOnly(view: TagView) { + addVisitedView(view) + } + + function delVisitedView(view: TagView): Promise { + return new Promise(resolve => { + const index = visitedViews.value.findIndex(v => v.path === view.path) + if (index > -1) { + visitedViews.value.splice(index, 1) + } + resolve([...visitedViews.value]) + }) + } + + function delCachedView(view: TagView): Promise { + return new Promise(resolve => { + const index = cachedViews.value.indexOf(view.name as string) + if (index > -1) { + cachedViews.value.splice(index, 1) + } + resolve([...cachedViews.value]) + }) + } + + function delView(view: TagView): Promise<{ visitedViews: TagView[]; cachedViews: string[] }> { + return new Promise(async resolve => { + await delVisitedView(view) + await delCachedView(view) + resolve({ + visitedViews: [...visitedViews.value], + cachedViews: [...cachedViews.value] + }) + }) + } + + function delVisitedViewsFrom(view: TagView): Promise { + return new Promise(resolve => { + const index = visitedViews.value.findIndex(v => v.path === view.path) + if (index > -1) { + visitedViews.value.splice(index, visitedViews.value.length - index) + } + resolve([...visitedViews.value]) + }) + } + + function delCachedViewsFrom(view: TagView): Promise { + return new Promise(resolve => { + const index = cachedViews.value.indexOf(view.name as string) + if (index > -1) { + cachedViews.value.splice(index, cachedViews.value.length - index) + } + resolve([...cachedViews.value]) + }) + } + + function delViewsFrom(view: TagView): Promise<{ visitedViews: TagView[]; cachedViews: string[] }> { + return new Promise(async resolve => { + await delVisitedViewsFrom(view) + await delCachedViewsFrom(view) + resolve({ + visitedViews: [...visitedViews.value], + cachedViews: [...cachedViews.value] + }) + }) + } + + function delVisitedViewsTo(view: TagView): Promise { + return new Promise(resolve => { + const index = visitedViews.value.findIndex(v => v.path === view.path) + if (index > -1) { + for (let i = index; i >= 0; i--) { + if (visitedViews.value[i].meta?.affix) continue + visitedViews.value.splice(i, 1) + } + } + resolve([...visitedViews.value]) + }) + } + + function delCachedViewsTo(view: TagView): Promise { + return new Promise(resolve => { + const index = cachedViews.value.indexOf(view.name as string) + if (index > -1) { + cachedViews.value.splice(0, index + 1) + } + resolve([...cachedViews.value]) + }) + } + + function delViewsTo(view: TagView): Promise<{ visitedViews: TagView[]; cachedViews: string[] }> { + return new Promise(async resolve => { + await delVisitedViewsTo(view) + await delCachedViewsTo(view) + resolve({ + visitedViews: [...visitedViews.value], + cachedViews: [...cachedViews.value] + }) + }) + } + + function delAllViews(): Promise<{ visitedViews: TagView[]; cachedViews: string[] }> { + return new Promise(resolve => { + // Keep affix tags + const affixTags = visitedViews.value.filter(tag => tag.meta?.affix) + visitedViews.value = affixTags + cachedViews.value = [] + resolve({ + visitedViews: [...visitedViews.value], + cachedViews: [...cachedViews.value] + }) + }) + } + + function updateVisitedView(view: TagView) { + const index = visitedViews.value.findIndex(v => v.path === view.path) + if (index >= 0) { + visitedViews.value[index] = { ...visitedViews.value[index], ...view } + } + } + + function resetTagsView() { + visitedViews.value = [] + cachedViews.value = [] + } + + return { + visitedViews, + cachedViews, + addVisitedView, + addCachedView, + addView, + addVisitedViewOnly, + delVisitedView, + delCachedView, + delView, + delVisitedViewsFrom, + delCachedViewsFrom, + delViewsFrom, + delVisitedViewsTo, + delCachedViewsTo, + delViewsTo, + delAllViews, + updateVisitedView, + resetTagsView + } +}) diff --git a/ruoyi-ui-next/src/types/index.ts b/ruoyi-ui-next/src/types/index.ts index 5fc6a5b..5e2415b 100644 --- a/ruoyi-ui-next/src/types/index.ts +++ b/ruoyi-ui-next/src/types/index.ts @@ -1,5 +1,157 @@ -export interface RouteMeta { +/** + * 路由配置接口 + */ +export interface RouteConfig { + path: string + name?: string + component?: any + redirect?: string + hidden?: boolean + children?: RouteConfig[] + meta?: { + title?: string + icon?: string + affix?: boolean + noCache?: boolean + activeMenu?: string + permissions?: string[] + roles?: string[] + breadcrumb?: boolean + link?: string + } +} + +/** + * 路由标签项 + */ +export interface TagView { + fullPath: string + path: string + name?: string title?: string - icon?: string + query?: Record + params?: Record + meta?: { + title?: string + affix?: boolean + noCache?: boolean + icon?: string + link?: string + } +} + +/** + * 用户信息接口 + */ +export interface UserInfo { + userId: number + userName: string + nickName?: string + avatar?: string + email?: string + phonenumber?: string + sex?: string + roles: string[] + permissions: string[] +} + +/** + * API 响应接口 + */ +export interface ApiResponse { + code: number + msg: string + data: T +} + +/** + * 分页响应接口 + */ +export interface PageResponse { + code: number + msg: string + total: number + rows: T[] +} + +/** + * 分页查询参数 + */ +export interface PageQuery { + pageNum: number + pageSize: number +} + +/** + * 字典数据项 + */ +export interface DictDataOption { + label: string + value: string | number + raw: { + listClass: string + cssClass: string + } +} + +/** + * 文件上传项 + */ +export interface UploadFileItem { + name: string + url: string + uid?: number | string +} + +/** + * Crontab 表达式各字段 + */ +export interface CrontabValueObj { + second: string + min: string + hour: string + day: string + month: string + week: string + year: string +} + +/** + * 右侧工具栏列配置 + */ +export interface ColumnOption { + key: number + label: string + visible?: boolean +} + +/** + * 分页组件 Props + */ +export interface PaginationProps { + total: number + page?: number + limit?: number + pageSizes?: number[] + pagerCount?: number + layout?: string + background?: boolean + autoScroll?: boolean hidden?: boolean } + +/** + * 分页组件事件 + */ +export interface PaginationEvent { + page: number + limit: number +} + +/** + * 搜索选项 + */ +export interface SearchOption { + path: string + title: string[] +} diff --git a/ruoyi-ui-next/src/utils/scroll-to.ts b/ruoyi-ui-next/src/utils/scroll-to.ts new file mode 100644 index 0000000..7c827b4 --- /dev/null +++ b/ruoyi-ui-next/src/utils/scroll-to.ts @@ -0,0 +1,67 @@ +/** + * Smooth scroll to a specified position + */ +function easeInOutQuad(t: number, b: number, c: number, d: number): number { + t /= d / 2 + if (t < 1) return (c / 2) * t * t + b + t-- + return (-c / 2) * (t * (t - 2) - 1) + b +} + +interface AnimationFrameRequester { + (callback: FrameRequestCallback): number +} + +const requestAnimFrame: AnimationFrameRequester = + typeof window !== 'undefined' + ? (window.requestAnimationFrame || + (window as any).webkitRequestAnimationFrame || + (window as any).mozRequestAnimationFrame || + function (callback) { + window.setTimeout(callback, 1000 / 60) + }) + : function (callback) { + setTimeout(callback, 1000 / 60) + } + +/** + * Move the scrollbar to a specific position + */ +function move(amountToScroll: number, start: number, increment: number, startTime: number = Date.now()): void { + if (typeof window === 'undefined') return + + const body = window.document.body + const element = body.scrollTop ? body : window.document.documentElement + + const now = Date.now() + const time = now - startTime + + if (time < 800) { + const position = easeInOutQuad(time, start, amountToScroll, 800) + element.scrollTop = position + requestAnimFrame(function () { + move(amountToScroll, start, increment, now) + }) + } else { + element.scrollTop = start + amountToScroll + } +} + +/** + * Scroll to a specified position + * @param element - The target element or position + * @param duration - Scroll duration in ms + */ +export function scrollTo(element: number | Element, duration?: number): void +export function scrollTo(y: number, duration?: number): void +export function scrollTo(element: number | Element, duration: number = 500): void { + if (typeof window === 'undefined') return + + if (typeof element === 'number') { + const start = window.document.documentElement.scrollTop || window.pageYOffset || window.document.body.scrollTop || 0 + const amountToScroll = element - start + move(amountToScroll, start, element) + } else { + element.scrollIntoView({ behavior: 'smooth' }) + } +}