【同步】前端项目源码

【修复】工作流兼容问题
This commit is contained in:
chudong
2025-05-10 11:53:11 +08:00
parent c514471adc
commit f1a75afaba
584 changed files with 55714 additions and 110 deletions

View File

@@ -0,0 +1,71 @@
import type { ThemeTemplate, PresetConfig } from '../../types'
import './style.css'
// 预设参数配置,用于预设视图组件的相关参数
const presets: PresetConfig = {
Modal: {
preset: 'card',
},
}
// 宝塔亮色主题
const baotaLight: ThemeTemplate = {
name: 'baotaLight',
type: 'light',
title: '宝塔亮色主题',
themeOverrides: {
Layout: {
color: '#f9fafb',
},
common: {
primaryColor: '#20a53a',
fontSize: '12px',
fontWeight: '400',
},
Dialog: {
titleTextColor: '#333',
titleFontSize: '14px',
titleFontWeight: '400',
titlePadding: '12px 16px',
iconSize: '0px',
padding: '0px',
fontSize: '12px',
closeMargin: '10px 12px',
border: '1px solid #d9d9d9',
},
Card: {
titleFontSizeMedium: '14px',
titleFontWeightMedium: '400',
titlePaddingMedium: '12px 16px',
border: '1px solid #d9d9d9',
padding: '0px',
actionColor: '#20a53a',
},
Button: {
fontSizeSmall: '12px',
fontSizeMedium: '12px',
paddingMedium: '8px 16px',
heightMedium: '32px',
},
DataTable: {
thPaddingMedium: '8px',
fontSizeMedium: '12px',
thFontWeight: '400',
},
Table: {
thPaddingMedium: '8px',
fontSizeMedium: '12px',
thFontWeight: '400',
},
Tabs: {
navBgColor: '#fff',
navActiveBgColor: '#eaf8ed',
barColor: '#20a53a',
tabFontWeightActive: '400',
},
},
presetsOverrides: presets,
}
export { baotaLight }

View File

@@ -0,0 +1,51 @@
/* 变量配置 */
:root[class='baotaLight'] {
--n-dialog-title-padding: 0 10px 0 18px; /* 对话框标题内边距 */
--n-dialog-title-height: 40px; /* 对话框标题高度 */
--n-dialog-content-padding: 20px 12px; /* 对话框内容内边距 */
--n-dialog-action-bg: #f0f0f0; /* 对话框标题背景色 */
--n-dialog-action-padding: 8px 16px; /* 对话框操作按钮内边距 */
--bt-card-bg-color: #fff; /* tab卡片背景色 */
--bt-card-bg-color-active: #eaf8ed; /* tab卡片背景色-激活 */
}
:root[class='baotaDark'] {
--n-dialog-title-color: #eee; /* 对话框标题文字颜色 */
--n-dialog-action-bg: #333; /* 对话框标题背景色 */
--n-dialog-border: 1px solid #333; /* 对话框标题下边框 */
--bt-card-bg-color: #18181c; /* tab卡片背景色 */
--bt-card-bg-color-active: #1e2720; /* tab卡片背景色-激活 */
}
/* ------------------------------对话框--------------------------------- */
/* 对话框/模态框标题 */
.n-dialog .n-dialog__title,
.n-modal .n-card-header {
padding: var(--n-dialog-title-padding) !important;
background-color: var(--n-dialog-action-bg);
color: var(--n-dialog-title-color);
border-bottom: var(--n-dialog-border);
height: var(--n-dialog-title-height);
border-top-right-radius: var(--n-border-radius);
border-top-left-radius: var(--n-border-radius);
}
/* 对话框/模态框内容 */
.n-dialog .n-dialog__content,
.n-modal .n-card__content {
margin: 0;
padding: var(--n-dialog-content-padding);
}
/* 对话框操作按钮 */
.n-dialog .n-dialog__action,
.n-modal .n-card__footer {
border-top: var(--n-dialog-border);
padding: var(--n-dialog-action-padding);
background-color: var(--n-dialog-action-bg);
border-bottom-left-radius: var(--n-border-radius);
border-bottom-right-radius: var(--n-border-radius);
}
/* 对话框-END */

View File

@@ -0,0 +1,20 @@
import type { ThemeTemplate, PresetConfig } from '../../types'
import './style.css'
// 预设变量,用于继承预设主题
const presets: PresetConfig = {
Modal: {
preset: 'card',
},
}
// 暗色主题
const goldDark: ThemeTemplate = {
name: 'darkGold',
type: 'dark',
title: '暗金主题',
themeOverrides: {},
presetsOverrides: presets, // 预设变量
}
export { goldDark }

View File

@@ -0,0 +1,4 @@
/* Dark Theme */
:root[class='darkGold'] {
/* Empty light theme styles */
}

View File

@@ -0,0 +1,229 @@
import { computed, ref, effectScope, onScopeDispose, watch } from 'vue'
import { useDark, useLocalStorage } from '@vueuse/core'
import { darkTheme, lightTheme, useThemeVars } from 'naive-ui'
import themes from './model'
import type { ThemeName, ThemeItemProps, ThemeTemplate } from './types'
// 驼峰命名转中划线命名的缓存
const camelToKebabCache = new Map<string, string>()
/**
* @description 驼峰命名转中划线命名
* @param {string} str 输入的驼峰字符串
* @returns {string} 转换后的中划线字符串
*/
const camelToKebabCase = (str: string): string => {
if (camelToKebabCache.has(str)) {
return camelToKebabCache.get(str)!
}
// 修改正则表达式,支持在字母与数字之间添加中划线
const result = str
.replace(/([a-z])([A-Z0-9])/g, '$1-$2')
.replace(/([0-9])([a-zA-Z])/g, '$1-$2')
.toLowerCase()
camelToKebabCache.set(str, result)
return result
}
/**
* @description 主题组合函数
* @param {ThemeName} name 初始主题名称
* @returns 主题状态和方法
*/
export const useTheme = (name?: ThemeName) => {
// 主题状态
const themeActive = useLocalStorage<ThemeName>('theme-active', name || 'defaultLight') // 主题名称
// 主题激活状态 Ref
const themeActiveOverrides = ref<ThemeTemplate | null>(null)
// 是否暗黑
const isDark = useDark()
// 主题
const theme = computed(() => {
return isDark.value ? darkTheme : lightTheme
})
// 主题继承修改
const themeOverrides = computed(() => {
// 如果没有激活的主题,则返回空对象
if (!themeActiveOverrides.value) return {}
return themeActiveOverrides.value.themeOverrides || {}
})
// 预设配置
const presetsOverrides = computed(() => {
// 如果没有激活的主题,则返回空对象
console.log('presetsOverrides', themeActiveOverrides.value)
if (!themeActiveOverrides.value) return {}
return themeActiveOverrides.value.presetsOverrides || {}
})
/**
* @description 切换暗黑模式
* @param {boolean} hasAnimation 是否有动画
*/
const cutDarkMode = (hasAnimation: boolean = false, e?: MouseEvent) => {
// 检查当前主题是否存在暗黑模式
isDark.value = !isDark.value
if (hasAnimation) {
// 如果有动画,则执行切换暗黑模式动画
cutDarkModeAnimation(e ? { clientX: e.clientX, clientY: e.clientY } : undefined)
} else {
themeActive.value = isDark.value ? 'defaultDark' : 'defaultLight'
}
// 更新主题名称
}
/**
* @description 切换暗色模式动画
*/
const cutDarkModeAnimation = (event?: { clientY: number; clientX: number }) => {
const root = document.documentElement
// 先移除现有动画类
root.classList.remove('animate-to-light', 'animate-to-dark')
// 添加相应的动画类
root.classList.add(isDark.value ? 'animate-to-light' : 'animate-to-dark')
// 切换主题
themeActive.value = isDark.value ? 'defaultDark' : 'defaultLight'
setTimeout(() => {
// 先移除现有动画类
root.classList.remove('animate-to-light', 'animate-to-dark')
}, 500)
}
/**
* @description 动态加载CSS内容
* @param {string} cssContent CSS内容
* @param {string} id 样式标签ID
*/
const loadDynamicCss = (cssContent: string, id: string) => {
// 检查是否已存在相同ID的样式标签
let styleElement = document.getElementById(id) as HTMLStyleElement
if (!styleElement) {
// 如果不存在创建新的style标签
styleElement = document.createElement('style')
styleElement.id = id
document.head.appendChild(styleElement)
}
// 更新样式内容
styleElement.textContent = cssContent
}
/**
* @description 加载主题样式
* @param {string} themeName 主题名称
*/
const loadThemeStyles = async (themeName: string) => {
// 根据主题名称加载对应的样式文件
try {
// 从主题配置中获取样式路径
const themeItem = themes[themeName]
if (!themeItem) return
// 加载主题样式
const themeConfig = await themeItem.import()
const themeStyles = await themeItem.styleContent() // 获取主题样式内容
// 加载新样式
if (themeStyles || themeStyles) {
loadDynamicCss(themeStyles as string, 'theme-style')
}
// 更新激活的主题
themeActiveOverrides.value = themeConfig
} catch (error) {
console.error(`加载主题失败 ${themeName}:`, error)
}
}
/**
* @description 获取主题列表
* @returns {ThemeItemProps[]} 主题列表
*/
const getThemeList = () => {
const themeList: ThemeItemProps[] = []
for (const key in themes) {
themeList.push(themes[key])
}
return themeList
}
const scope = effectScope()
scope.run(() => {
watch(
themeActive,
(newVal) => {
// 移除之前的主题类名
if (themeActive.value) document.documentElement.classList.remove(themeActive.value)
// 添加新的主题类名
document.documentElement.classList.add(newVal)
// 更新主题名称
themeActive.value = newVal
// 加载主题样式
loadThemeStyles(newVal)
},
{ immediate: true },
)
onScopeDispose(() => {
scope.stop()
})
})
return {
// 状态
theme,
themeOverrides,
presetsOverrides,
isDark,
themeActive,
// 方法
getThemeList, // 获取主题列表
cutDarkModeAnimation, // 切换暗黑模式动画
cutDarkMode, // 切换暗黑模式
loadThemeStyles, // 加载主题样式
loadDynamicCss, // 动态加载CSS内容
}
}
/**
* @description 主题样式提取
* @param {string[]} options 主题变量
* @param options
*/
/**
* @description 主题样式提取
* @param {string[]} options 主题变量
* @returns {string} 生成的样式字符串
*/
export const useThemeCssVar = (options: string[]) => {
const vars = useThemeVars()
const stylesRef = ref('')
const scope = effectScope()
scope.run(() => {
watch(
vars,
(newVal) => {
// 使用数组收集样式,最后统一拼接
const styles: string[] = []
for (const key of options) {
if (key in newVal) {
const kebabKey = camelToKebabCase(key)
styles.push(`--n-${kebabKey}: ${newVal[key as keyof typeof vars.value]};`)
}
}
// 拼接样式字符串
stylesRef.value = styles.join('\n')
},
{ immediate: true },
)
onScopeDispose(() => {
scope.stop()
})
})
return stylesRef
}

View File

@@ -0,0 +1,55 @@
import type { ThemeTemplate, PresetConfig } from '../../types'
import './style.css'
// 预设变量,用于继承预设主题
const presets: PresetConfig = {
Modal: {
preset: 'card',
},
}
// 默认亮色主题
const defaultLight: ThemeTemplate = {
name: 'defaultLight', // 主题标识
type: 'light', // 主题类型,可选值为 light、dark用于继承预设主题
title: '默认亮色主题', // 主题名称
themeOverrides: {
common: {
borderRadius: '0.6rem', // 圆角
},
}, // 主题变量
presetsOverrides: presets, // 预设变量
}
// 默认暗色主题
const defaultDark: ThemeTemplate = {
name: 'defaultDark',
type: 'dark',
title: '默认暗色主题',
themeOverrides: {
common: {
// baseColor: '#F1F1F1', // 基础色
primaryColor: '#4caf50', // 主色
primaryColorHover: '#20a53a', // 主色悬停
primaryColorPressed: '#157f3a', // 主色按下
primaryColorSuppl: '#4caf50', // 主色补充
borderRadius: '0.6rem', // 圆角
},
Popover: {
// color: '#ffffff', // 弹出层背景色
},
Button: {
textColorPrimary: '#ffffff', // 主按钮文本色
textColorHoverPrimary: '#ffffff', // 主按钮文本色悬停
textColorPressedPrimary: '#ffffff', // 主按钮文本色按下
textColorFocusPrimary: '#ffffff', // 主按钮文本色聚焦
},
Radio: {
buttonTextColorActive: '#ffffff', // 单选框文本色
},
},
presetsOverrides: presets, // 预设变量
}
export { defaultLight, defaultDark }

View File

@@ -0,0 +1,53 @@
/* Light Theme */
:root[class='defaultLight'] {
/* Empty light theme styles */
--background-color: #121212;
--text-color: #f1f1f1;
--bt-popover-color: #ffffff;
}
/* Dark Theme */
:root[class='defaultDark'] {
/* Empty dark theme styles */
--bg-color: #121212;
--bt-popover-color: #48484e;
}
/* 创建动画 */
@keyframes fadeToLight {
from {
opacity: 0.8;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes fadeToDark {
from {
opacity: 0.8;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
/* 应用动画 */
:root {
--background-color: #ffffff;
--text-color: #333333;
}
:root.animate-to-light {
animation: fadeToLight 0.5s ease forwards;
overflow: hidden;
}
:root.animate-to-dark {
animation: fadeToDark 0.5s ease forwards;
overflow: hidden;
}

View File

@@ -0,0 +1,42 @@
// 导出主题表,需要自己定义
import type { ThemeJsonProps } from '../types'
const cssModules = import.meta.glob('../model/*/*.css', {
eager: false,
import: 'default',
// as: 'url', // 使用 url 加载器,将 CSS 文件作为独立的资源加载
})
const themes: ThemeJsonProps = {
defaultLight: {
name: 'defaultLight', // 主题标识
type: 'light', // 主题类型,可选值为 light、dark用于继承预设主题
title: '默认亮色主题', // 主题名称
import: async () => (await import('./default/index')).defaultLight, // 主题导入函数,用于动态导入主题文件
styleContent: async () => (await cssModules['./default/style.css']()) as string, // 主题样式导入函数,用于动态导入主题样式文件
},
defaultDark: {
name: 'defaultDark',
type: 'dark',
title: '默认暗色主题',
import: async () => (await import('./default/index')).defaultDark,
styleContent: async () => (await cssModules['./default/style.css']()) as string, // 主题样式导入函数,用于动态导入主题样式文件
},
// baotaLight: {
// name: 'baotaLight',
// type: 'light',
// title: '宝塔主题',
// import: async () => (await import('./baota/index')).baotaLight,
// styleContent: async () => (await cssModules['./baota/style.css']()) as string, // 主题样式导入函数,用于动态导入主题样式文件
// },
// darkGold: {
// name: 'darkGold',
// type: 'dark',
// title: '暗金主题',
// import: async () => (await import('./dark-gold/index')).goldDark,
// styleContent: async () => (await cssModules['./dark-gold/style.css']()) as string, // 主题样式导入函数,用于动态导入主题样式文件
// },
}
export default themes

View File

@@ -0,0 +1,52 @@
import type { ThemeTemplate, PresetConfig } from '../../types'
import './style.css'
// 预设变量,用于继承预设主题
const presets: PresetConfig = {
Modal: {
preset: 'card',
},
}
// 默认亮色主题
const blueLight: ThemeTemplate = {
name: 'blueLight', // 主题标识
type: 'light', // 主题类型,可选值为 light、dark用于继承预设主题
title: '蓝色主题', // 主题名称
themeOverrides: {
common: {},
}, // 主题变量
presetsOverrides: presets, // 预设变量
}
// 默认暗色主题
const blueDark: ThemeTemplate = {
name: 'blueDark',
type: 'dark',
title: '蓝色主题',
themeOverrides: {
common: {
// baseColor: '#F1F1F1', // 基础色
primaryColor: '#4caf50', // 主色
primaryColorHover: '#20a53a', // 主色悬停
primaryColorPressed: '#157f3a', // 主色按下
primaryColorSuppl: '#4caf50', // 主色补充
},
Popover: {
// color: '#ffffff', // 弹出层背景色
},
Button: {
textColorPrimary: '#ffffff', // 主按钮文本色
textColorHoverPrimary: '#ffffff', // 主按钮文本色悬停
textColorPressedPrimary: '#ffffff', // 主按钮文本色按下
textColorFocusPrimary: '#ffffff', // 主按钮文本色聚焦
},
Radio: {
buttonTextColorActive: '#ffffff', // 单选框文本色
},
},
presetsOverrides: presets, // 预设变量
}
export { blueLight, blueDark }

View File

@@ -0,0 +1,69 @@
/* Light Theme */
:root[class='defaultLight'] {
/* Empty light theme styles */
--background-color: #121212;
--text-color: #f1f1f1;
--bt-popover-color: #ffffff;
}
/* Dark Theme */
:root[class='defaultDark'] {
/* Empty dark theme styles */
--bg-color: #121212;
--bt-popover-color: #48484e;
}
/* 创建动画 */
@keyframes fadeToLight {
from {
opacity: 0.8;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes fadeToDark {
from {
opacity: 0.8;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
/* 应用动画 */
:root {
--background-color: #ffffff;
--text-color: #333333;
}
:root.animate-to-light {
animation: fadeToLight 0.5s ease forwards;
overflow: hidden;
}
:root.animate-to-dark {
animation: fadeToDark 0.5s ease forwards;
overflow: hidden;
}
.text-info {
color: #666;
}
.text-success {
color: #4caf50;
}
.text-warning {
color: #ff9800;
}
.text-error {
color: #f44336;
}

View File

@@ -0,0 +1,36 @@
import type { GlobalThemeOverrides, ModalProps, FormProps, FormItemProps, TableProps } from 'naive-ui'
export interface ThemeTemplate {
name: string
type: 'light' | 'dark'
title: string
themeOverrides: GlobalThemeOverrides
presetsOverrides: PresetConfig
}
export interface ThemeTemplateType {
[key: string]: ThemeTemplate
}
// 主题名称
export type ThemeName = string
// 预设配置
export interface PresetConfig {
Modal?: ModalProps
Form?: FormProps
FormItem?: FormItemProps
Table?: TableProps
}
export interface ThemeItemProps {
name: string // 主题标识
type: 'light' | 'dark' // 主题类型,可选值为 light、dark用于继承预设主题
title: string // 主题名称
import: () => Promise<ThemeTemplate> // 主题导入函数,用于动态导入主题文件
styleContent: () => Promise<string> // 主题样式内容,用于动态生成主题样式
}
export interface ThemeJsonProps {
[key: string]: ThemeItemProps // 主题表key为主题标识value为主题配置
}