completepage: 🍻 多语言添加页面完成

This commit is contained in:
bunny 2024-09-30 14:18:02 +08:00
parent 7efa04759c
commit d373c138fb
59 changed files with 1157 additions and 2032 deletions

View File

@ -1,82 +0,0 @@
// 模拟后端动态生成路由
import { defineFakeRoute } from 'vite-plugin-fake-server/client';
/**
* roles "admin""common"
* admin
* common
*/
const permissionRouter = {
path: '/permission',
meta: {
title: 'menus.purePermission',
icon: 'ep:lollipop',
rank: 10,
},
children: [
{
path: '/permission/page/index',
name: 'PermissionPage',
meta: {
title: 'menus.purePermissionPage',
roles: ['admin', 'common'],
},
},
{
path: '/permission/button/router',
component: 'permission/button/index',
name: 'PermissionButtonRouter',
meta: {
title: 'menus.purePermissionButtonRouter',
auths: ['permission:btn:add', 'permission:btn:edit', 'permission:btn:delete'],
},
},
{
path: '/permission/button/login',
component: 'permission/button/perms',
name: 'PermissionButtonLogin',
meta: {
title: 'menus.purePermissionButtonLogin',
},
},
],
};
const user = {
path: '/user',
meta: {
icon: 'ep:lollipop',
title: '用户',
rank: 1,
},
children: [
{
path: '/user/index',
meta: {
icon: null,
title: '用户index',
rank: 2,
roles: ['admin', 'common'],
auths: ['*', '*::*', '*::*::*', '*', '*::*', '::*::*'],
},
id: '2',
parentId: '1',
name: '用户index',
children: [],
},
],
};
// 获取系统路由
export default defineFakeRoute([
{
url: '/mock/get-async-routes',
method: 'get',
response: () => {
return {
success: true,
data: [permissionRouter, user],
};
},
},
]);

View File

@ -1,16 +0,0 @@
import { defineFakeRoute } from 'vite-plugin-fake-server/client';
import en from './i18n/en';
import zh from './i18n/zh';
export default defineFakeRoute([
{
url: '/mock/getI18n',
method: 'get',
response: () => {
return {
code: 200,
data: { zh, en, local: 'zh' },
};
},
},
]);

View File

@ -1,20 +0,0 @@
import { buttons } from "./en/buttons";
import { search } from "./en/search";
import { panel } from "./en/panel";
import { menus } from "./en/menus";
import { status } from "./en/status";
import { login } from "./en/login";
import { style } from "./en/style";
import { system } from "./en/system";
export default {
name: "en",
buttons,
search,
panel,
menus,
status,
login,
style,
system
};

View File

@ -1,25 +0,0 @@
export const buttons = {
openSystemSet: "Open System Configs",
pureOpenSystemSet: "pureOpenSystemSet",
pureAccountSettings: "Account",
pureLoginOut: "LoginOut",
pureLogin: "Login",
pureReload: "Reload",
pureCloseCurrentTab: "Close CurrentTab",
pureCloseLeftTabs: "Close LeftTabs",
pureCloseRightTabs: "Close RightTabs",
pureCloseOtherTabs: "Close OtherTabs",
pureCloseAllTabs: "Close AllTabs",
pureContentFullScreen: "Content FullScreen",
pureContentExitFullScreen: "Content ExitFullScreen",
pureClickCollapse: "Collapse",
pureClickExpand: "Expand",
confirm: "Confirm",
pureSwitch: "Switch",
close: "Close",
pureBackTop: "BackTop",
pureOpenText: "Open",
pureCloseText: "Close",
rest: "Rest"
};

View File

@ -1,40 +0,0 @@
export const login = {
loginSuccess: "Login Success",
loginFail: "Login Fail",
usernameRegex: "please input username",
username: "input username",
password: "input password",
login: "Login",
email: "email",
repeatPassword: "Sure Password",
emailCode: "input email code",
verifyCode: "verify code",
emailRegex: "please input email",
passwordRegex: "please input password",
passwordSureRegex: "Please entr confirm password",
passwordDifferentRegex: "The two passwords do not match!",
emailCodeRegex: "please input email code",
getEmailCode: "get email code",
rememberMe: "days no need to login",
rememberInfo:
"After checking and logging in, will automatically log in to the system without entering your username and password within the specified number of days.",
forgetPassword: "Forget Password?",
getCodeInfo: "Seconds",
getVerifyCode: "Get VerifyCode",
definite: "definite",
back: "back",
passWordUpdateReg: "Password has been updated",
pureTip: 'After scanning the code, click "Confirm" to complete the login',
pureRegisterSuccess: "Regist Success",
pureTickPrivacy: "Please tick Privacy Policy",
pureReadAccept: "I have read it carefully and accept",
purePrivacyPolicy: "Privacy Policy",
pureVerifyCodeReg: "Please enter verify code",
pureVerifyCodeCorrectReg: "Please enter correct verify code",
pureVerifyCodeSixReg: "Please enter a 6-digit verify code",
purePhoneReg: "Please enter the phone",
purePhoneCorrectReg: "Please enter the correct phone number format",
purePassWordRuleReg:
"The password format should be any combination of 8-18 digits"
};

View File

@ -1,120 +0,0 @@
export const menus = {
home: "Home",
purePermissionButtonRouter: "PermissionButtonRouter",
purePermissionButtonLogin: "purePermissionButtonLogin",
pureLogin: "Login",
pureEmpty: "Empty Page",
pureTable: "Table",
pureSysManagement: "System Manage",
pureUser: "User Manage",
pureRole: "Role Manage",
pureSystemMenu: "Menu Manage",
pureDept: "Dept Manage",
pureSysMonitor: "System Monitor",
pureOnlineUser: "Online User",
pureLoginLog: "Login Log",
pureOperationLog: "Operation Log",
pureSystemLog: "System Log",
pureEditor: "Editor",
pureAbnormal: "Abnormal Page",
pureFourZeroFour: "404",
pureFourZeroOne: "403",
pureFive: "500",
pureComponents: "Components",
pureDialog: "Dialog",
pureMessage: "Message Tips",
pureVideo: "Video",
pureSegmented: "Segmented",
pureWaterfall: "Waterfall",
pureMap: "Map",
pureDraggable: "Draggable",
pureSplitPane: "Split Pane",
pureText: "Text Ellipsis",
pureElButton: "Button",
pureButton: "Button Animation",
pureCheckButton: "Check Button",
pureCropping: "Picture Cropping",
pureAnimatecss: "AnimateCss Selector",
pureCountTo: "Digital Animation",
pureSelector: "Scope Selector",
pureFlowChart: "Flow Chart",
pureSeamless: "Seamless Scroll",
pureContextmenu: "Context Menu",
pureTypeit: "Typeit",
pureJsonEditor: "JSON Editor",
pureColorPicker: "Color Picker",
pureDatePicker: "Date Picker",
pureDateTimePicker: "DateTimePicker",
pureTimePicker: "TimePicker",
pureTag: "Tag",
pureStatistic: "Statistic",
pureCollapse: "Collapse",
pureGanttastic: "Gantt Chart",
pureProgress: "Progress",
pureUpload: "File Upload",
pureCheckCard: "CheckCard",
pureMenus: "MultiLevel Menu",
pureMenu1: "Menu1",
pureMenu2: "Menu2",
purePermission: "Permission Manage",
purePermissionPage: "Page Permission",
purePermissionButton: "Button Permission",
pureTabs: "Tabs Operate",
pureGuide: "Guide",
pureAble: "Able",
pureMenuTree: "Menu Tree",
pureVideoFrame: "Video Frame Capture",
pureWavesurfer: "Audio Visualization",
pureRipple: "Ripple",
pureMqtt: "Mqtt Client",
pureOptimize: "Debounce、Throttle、Copy、Longpress Directives",
pureVerify: "Captcha",
pureWatermark: "Water Mark",
purePrint: "Print",
pureDownload: "Download",
pureExternalPage: "External Page",
pureExternalDoc: "Docs External",
pureEmbeddedDoc: "Docs Embedded",
pureExternalLink: "Vue-Pure-Admin",
pureUtilsLink: "Pure-Admin-Utils",
pureColorHuntDoc: "ColorHunt",
pureUiGradients: "UiGradients",
pureEpDoc: "Element-Plus",
pureTailwindcssDoc: "Tailwindcss",
pureVueDoc: "Vue3",
pureViteDoc: "Vite",
purePiniaDoc: "Pinia",
pureRouterDoc: "Vue-Router",
pureAbout: "About",
pureResult: "Result Page",
pureSuccess: "Success Page",
pureFail: "Fail Page",
pureIconSelect: "Icon Select",
pureTimeline: "Time Line",
pureLineTree: "LineTree",
pureList: "List Page",
pureCardList: "Card List Page",
pureDebounce: "Debounce & Throttle",
pureFormDesign: "Form Design",
pureBarcode: "Barcode",
pureQrcode: "Qrcode",
pureCascader: "Area Cascader",
pureSwiper: "Swiper Plugin",
pureVirtualList: "Virtual List",
purePdf: "PDF Preview",
pureExcel: "Export Excel",
pureInfiniteScroll: "Table Infinite Scroll",
pureSensitive: "Sensitive Filter",
purePinyin: "PinYin",
pureDanmaku: "Danmaku",
pureSchemaForm: "Form",
pureTableBase: "Base Usage",
pureTableHigh: "High Usage",
pureTableEdit: "Edit Usage",
pureVxeTable: "Virtual Usage",
pureBoard: "Paint Board",
pureMindMap: "Mind Map",
pureMenuOverflow: "Menu Overflow Show Tooltip Text",
pureChildMenuOverflow: "Child Menu Overflow Show Tooltip Text",
systemctlTest: "Systemctl lTest"
};

View File

@ -1,38 +0,0 @@
export const panel = {
pureSystemSet: "System Configs",
pureCloseSystemSet: "Close System Configs",
pureClearCacheAndToLogin: "Clear cache and return to login page",
pureClearCache: "Clear Cache",
pureOverallStyle: "Overall Style",
pureOverallStyleLight: "Light",
pureOverallStyleLightTip:
"Set sail freshly and light up the comfortable work interface",
pureOverallStyleDark: "Dark",
pureOverallStyleDarkTip:
"Moonlight Overture, indulge in the tranquility and elegance of the night",
pureOverallStyleSystem: "Auto",
pureOverallStyleSystemTip:
"Synchronize time, the interface naturally responds to morning and dusk",
pureThemeColor: "Theme Color",
pureLayoutModel: "Layout Model",
pureVerticalTip: "The menu on the left is familiar and friendly",
pureHorizontalTip: "Top menu, concise overview",
pureMixTip: "Mixed menu, flexible",
pureStretch: "Stretch Page",
pureStretchFixed: "Fixed",
pureStretchFixedTip:
"Compact pages make it easy to find the information you need",
pureStretchCustom: "Custom",
pureStretchCustomTip: "Minimum 1280, maximum 1600",
pureTagsStyle: "Tags Style",
pureTagsStyleSmart: "Smart",
pureTagsStyleSmartTip: "Smart tags add fun and brilliance",
pureTagsStyleCard: "Card",
pureTagsStyleCardTip: "Card tags for efficient browsing",
pureInterfaceDisplay: "Interface Display",
pureGreyModel: "Grey Model",
pureWeakModel: "Weak Model",
pureHiddenTags: "Hidden Tags",
pureHiddenFooter: "Hidden Footer",
pureMultiTagsCache: "MultiTags Cache"
};

View File

@ -1,8 +0,0 @@
export const search = {
pureTotal: "Total",
pureHistory: "History",
pureCollect: "Collect",
pureDragSort: "Drag Sort",
pureEmpty: "Empty",
purePlaceholder: "Search Menu"
};

View File

@ -1,11 +0,0 @@
export const status = {
pureLoad: "Loading...",
pureMessage: "Message",
pureNotify: "Notify",
pureTodo: "Todo",
pureNoMessage: "No Message",
pureNoNotify: "No Notify",
pureNoTodo: "No Todo",
enable: "enable",
disable: "disable"
};

View File

@ -1,5 +0,0 @@
export const style = {
larger: "Larger",
default: "Default",
small: "Small"
};

View File

@ -1,8 +0,0 @@
export const system = {
carousel: "carousel setting",
config: "system config",
favicon: "system favicon",
feedback: "system feedback",
emailUsers: "email users",
log: "system log"
};

View File

@ -1,20 +0,0 @@
import { buttons } from "./zh/buttons";
import { search } from "./zh/search";
import { panel } from "./zh/panel";
import { menus } from "./zh/menus";
import { status } from "./zh/status";
import { login } from "./zh/login";
import { style } from "./zh/style";
import { system } from "./zh/system";
export default {
name: "zh",
buttons,
search,
panel,
menus,
status,
login,
style,
system
};

View File

@ -1,26 +0,0 @@
export const buttons = {
openSystemSet: "打开系统配置",
pureOpenSystemSet: "权限设定",
pureAccountSettings: "账户设置",
pureLoginOut: "退出系统",
pureLogin: "登录",
pureReload: "重新加载",
pureCloseCurrentTab: "关闭当前标签页",
pureCloseLeftTabs: "关闭左侧标签页",
pureCloseRightTabs: "关闭右侧标签页",
pureCloseOtherTabs: "关闭其他标签页",
pureCloseAllTabs: "关闭全部标签页",
pureContentFullScreen: "内容区全屏",
pureContentExitFullScreen: "内容区退出全屏",
pureClickCollapse: "点击折叠",
pureClickExpand: "点击展开",
confirm: "确认",
pureSwitch: "切换",
close: "关闭",
pureBackTop: "回到顶部",
pureOpenText: "开",
pureCloseText: "关",
rest: "重置",
search: "搜索"
};

View File

@ -1,38 +0,0 @@
export const login = {
loginSuccess: "登录成功",
loginFail: "登录失败",
usernameRegex: "请输入账号",
username: "输入用户名",
password: "输入密码",
login: "登录",
email: "输入邮箱",
repeatPassword: "确认密码",
emailCode: "邮箱验证码",
verifyCode: "图形验证码",
emailRegex: "输入邮箱",
passwordRegex: "请输入密码",
passwordSureRegex: "请输入确认密码",
repeatPasswordRegex: "请输入确认密码",
passwordDifferentRegex: "两次密码不一致!",
emailCodeRegex: "请输入邮箱验证码",
verifyCodeRegex: "输入验证码",
getEmailCode: "获取邮箱验证码",
rememberMe: "天内免登录",
rememberInfo: "勾选并登录后,规定天数内无需输入用户名和密码会自动登入系统",
forgetPassword: "忘记密码?",
getVerifyCode: "获取验证码",
definite: "确定",
back: "返回",
getCodeInfo: "秒后重新获取",
passWordUpdateReg: "修改密码成功",
pureRegisterSuccess: "注册成功",
pureTickPrivacy: "请勾选隐私政策",
pureReadAccept: "我已仔细阅读并接受",
purePrivacyPolicy: "《隐私政策》",
pureVerifyCodeCorrectReg: "请输入正确的验证码",
pureVerifyCodeSixReg: "请输入6位数字验证码",
purePhoneReg: "请输入手机号码",
purePhoneCorrectReg: "请输入正确的手机号码格式",
purePassWordRuleReg: "密码格式应为8-18位数字、字母、符号的任意两种组合"
};

View File

@ -1,120 +0,0 @@
export const menus = {
home: "首页",
purePermissionButtonRouter: "权限1",
purePermissionButtonLogin: "权限2",
pureLogin: "登录",
pureEmpty: "无Layout页",
pureTable: "表格",
pureSysManagement: "系统管理",
pureUser: "用户管理",
pureRole: "角色管理",
pureSystemMenu: "菜单管理",
pureDept: "部门管理",
pureSysMonitor: "系统监控",
pureOnlineUser: "在线用户",
pureLoginLog: "登录日志",
pureOperationLog: "操作日志",
pureSystemLog: "系统日志",
pureEditor: "编辑器",
pureAbnormal: "异常页面",
pureFourZeroFour: "404",
pureFourZeroOne: "403",
pureFive: "500",
pureComponents: "组件",
pureDialog: "函数式弹框",
pureMessage: "消息提示",
pureVideo: "视频",
pureSegmented: "分段控制器",
pureWaterfall: "瀑布流无限滚动",
pureMap: "地图",
pureDraggable: "拖拽",
pureSplitPane: "切割面板",
pureText: "文本省略",
pureElButton: "按钮",
pureCheckButton: "可选按钮",
pureButton: "按钮动效",
pureCropping: "图片裁剪",
pureAnimatecss: "animate.css选择器",
pureCountTo: "数字动画",
pureSelector: "范围选择器",
pureFlowChart: "流程图",
pureSeamless: "无缝滚动",
pureContextmenu: "右键菜单",
pureTypeit: "打字机",
pureJsonEditor: "JSON编辑器",
pureColorPicker: "颜色选择器",
pureDatePicker: "日期选择器",
pureDateTimePicker: "日期时间选择器",
pureTimePicker: "时间选择器",
pureTag: "标签",
pureStatistic: "统计组件",
pureCollapse: "折叠面板",
pureGanttastic: "甘特图",
pureProgress: "进度条",
pureUpload: "文件上传",
pureCheckCard: "多选卡片",
pureMenus: "多级菜单",
pureMenu1: "菜单1",
pureMenu2: "菜单2",
purePermission: "权限管理",
purePermissionPage: "页面权限",
purePermissionButton: "按钮权限",
pureTabs: "标签页操作",
pureGuide: "引导页",
pureAble: "功能",
pureMenuTree: "菜单树结构",
pureVideoFrame: "视频帧截取-wasm版",
pureWavesurfer: "音频可视化",
pureRipple: "波纹(Ripple)",
pureMqtt: "MQTT客户端(mqtt)",
pureOptimize: "防抖、截流、复制、长按指令",
pureVerify: "图形验证码",
pureWatermark: "水印",
purePrint: "打印",
pureDownload: "下载",
pureExternalPage: "外部页面",
pureExternalDoc: "文档外链",
pureEmbeddedDoc: "文档内嵌",
pureExternalLink: "vue-pure-admin",
pureUtilsLink: "pure-admin-utils",
pureColorHuntDoc: "调色板",
pureUiGradients: "渐变色",
pureEpDoc: "element-plus",
pureTailwindcssDoc: "tailwindcss",
pureVueDoc: "vue3",
pureViteDoc: "vite",
purePiniaDoc: "pinia",
pureRouterDoc: "vue-router",
pureAbout: "关于",
pureResult: "结果页面",
pureSuccess: "成功页面",
pureFail: "失败页面",
pureIconSelect: "图标选择器",
pureTimeline: "时间线",
pureLineTree: "树形连接线",
pureList: "列表页面",
pureCardList: "卡片列表页",
pureDebounce: "防抖节流",
pureFormDesign: "表单设计器",
pureBarcode: "条形码",
pureQrcode: "二维码",
pureCascader: "区域级联选择器",
pureSwiper: "Swiper插件",
pureVirtualList: "虚拟列表",
purePdf: "PDF预览",
pureExcel: "导出Excel",
pureInfiniteScroll: "表格无限滚动",
pureSensitive: "敏感词过滤",
purePinyin: "汉语拼音",
pureDanmaku: "弹幕",
pureSchemaForm: "表单",
pureTableBase: "基础用法",
pureTableHigh: "高级用法",
pureTableEdit: "可编辑用法",
pureVxeTable: "虚拟滚动",
pureBoard: "艺术画板",
pureMindMap: "思维导图",
pureMenuOverflow: "目录超出显示 Tooltip 文字提示",
pureChildMenuOverflow: "菜单超出显示 Tooltip 文字提示",
systemctlTest: "系统测试"
};

View File

@ -1,34 +0,0 @@
export const panel = {
pureSystemSet: "系统配置",
pureCloseSystemSet: "关闭配置",
pureClearCacheAndToLogin: "清空缓存并返回登录页",
pureClearCache: "清空缓存",
pureOverallStyle: "整体风格",
pureOverallStyleLight: "浅色",
pureOverallStyleLightTip: "清新启航,点亮舒适的工作界面",
pureOverallStyleDark: "深色",
pureOverallStyleDarkTip: "月光序曲,沉醉于夜的静谧雅致",
pureOverallStyleSystem: "自动",
pureOverallStyleSystemTip: "同步时光,界面随晨昏自然呼应",
pureThemeColor: "主题色",
pureLayoutModel: "导航模式",
pureVerticalTip: "左侧菜单,亲切熟悉",
pureHorizontalTip: "顶部菜单,简洁概览",
pureMixTip: "混合菜单,灵活多变",
pureStretch: "页宽",
pureStretchFixed: "固定",
pureStretchFixedTip: "紧凑页面,轻松找到所需信息",
pureStretchCustom: "自定义",
pureStretchCustomTip: "最小1280、最大1600",
pureTagsStyle: "页签风格",
pureTagsStyleSmart: "灵动",
pureTagsStyleSmartTip: "灵动标签,添趣生辉",
pureTagsStyleCard: "卡片",
pureTagsStyleCardTip: "卡片标签,高效浏览",
pureInterfaceDisplay: "界面显示",
pureGreyModel: "灰色模式",
pureWeakModel: "色弱模式",
pureHiddenTags: "隐藏标签页",
pureHiddenFooter: "隐藏页脚",
pureMultiTagsCache: "页签持久化"
};

View File

@ -1,10 +0,0 @@
export const search = {
search: {
pureTotal: "共",
pureHistory: "搜索历史",
pureCollect: "收藏",
pureDragSort: "(可拖拽排序)",
pureEmpty: "暂无搜索结果",
purePlaceholder: "搜索菜单(支持拼音搜索)"
}
};

View File

@ -1,11 +0,0 @@
export const status = {
pureLoad: "加载中...",
pureMessage: "消息",
pureNotify: "通知",
pureTodo: "待办",
pureNoMessage: "暂无消息",
pureNoNotify: "暂无通知",
pureNoTodo: "暂无待办",
enable: "启用",
disable: "不启用"
};

View File

@ -1,5 +0,0 @@
export const style = {
larger: "宽松",
default: "默认",
small: "紧凑"
};

View File

@ -1,8 +0,0 @@
export const system = {
config: "系统设置",
carousel: "轮播图设置",
favicon: "图标设置",
feedback: "用户反馈",
emailUsers: "邮件用户",
log: "系统日志"
};

View File

@ -1,42 +0,0 @@
// 根据角色动态生成路由
import { defineFakeRoute } from "vite-plugin-fake-server/client";
export default defineFakeRoute([
{
url: "/mock/login",
method: "post",
response: ({ body }) => {
if (body.username === "admin") {
return {
success: true,
data: {
avatar: "https://avatars.githubusercontent.com/u/44761321",
username: "admin",
nickname: "小铭",
// 一个用户可能有多个角色
roles: ["admin"],
// 按钮级别权限
permissions: ["*:*:*"],
accessToken: "eyJhbGciOiJIUzUxMiJ9.admin",
refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh",
expires: "2030/10/30 00:00:00"
}
};
} else {
return {
success: true,
data: {
avatar: "https://avatars.githubusercontent.com/u/52823142",
username: "common",
nickname: "小林",
roles: ["common"],
permissions: ["permission:btn:add", "permission:btn:edit"],
accessToken: "eyJhbGciOiJIUzUxMiJ9.common",
refreshToken: "eyJhbGciOiJIUzUxMiJ9.commonRefresh",
expires: "2030/10/30 00:00:00"
}
};
}
}
}
]);

View File

@ -82,7 +82,7 @@ class PureHttp {
return whiteList.some(url => config.url.endsWith(url)) return whiteList.some(url => config.url.endsWith(url))
? config ? config
: new Promise(resolve => { : new Promise(resolve => {
const data = getToken(); const data: any = getToken();
if (data) { if (data) {
const now = new Date().getTime(); const now = new Date().getTime();
const expired = parseInt(data.expires) - now <= 0; const expired = parseInt(data.expires) - now <= 0;

View File

@ -154,8 +154,6 @@ class PureHttp {
// 关闭进度条动画 // 关闭进度条动画
NProgress.done(); NProgress.done();
message(error.message, { type: 'error' });
return error; return error;
}, },
); );

View File

@ -12,7 +12,7 @@ export const fetchGetI18n = () => {
* --- * ---
*/ */
export const fetchGetI18nList = (data: any) => { export const fetchGetI18nList = (data: any) => {
return http.request<BaseResult<ResultTable>>('get', `i18n/getI18nList/${data.currentPage}/${data.pageSize}`, { data }); return http.request<BaseResult<ResultTable>>('get', `i18n/getI18nList/${data.currentPage}/${data.pageSize}`, { params: data });
}; };
/** /**

View File

@ -1,114 +1,114 @@
export const animates = [ export const animates = [
/* Attention seekers */ /* Attention seekers */
"bounce", 'bounce',
"flash", 'flash',
"pulse", 'pulse',
"rubberBand", 'rubberBand',
"shakeX", 'shakeX',
"headShake", 'headShake',
"swing", 'swing',
"tada", 'tada',
"wobble", 'wobble',
"jello", 'jello',
"heartBeat", 'heartBeat',
/* Back entrances */ /* Back entrances */
"backInDown", 'backInDown',
"backInLeft", 'backInLeft',
"backInRight", 'backInRight',
"backInUp", 'backInUp',
/* Back exits */ /* Back exits */
"backOutDown", 'backOutDown',
"backOutLeft", 'backOutLeft',
"backOutRight", 'backOutRight',
"backOutUp", 'backOutUp',
/* Bouncing entrances */ /* Bouncing entrances */
"bounceIn", 'bounceIn',
"bounceInDown", 'bounceInDown',
"bounceInLeft", 'bounceInLeft',
"bounceInRight", 'bounceInRight',
"bounceInUp", 'bounceInUp',
/* Bouncing exits */ /* Bouncing exits */
"bounceOut", 'bounceOut',
"bounceOutDown", 'bounceOutDown',
"bounceOutLeft", 'bounceOutLeft',
"bounceOutRight", 'bounceOutRight',
"bounceOutUp", 'bounceOutUp',
/* Fading entrances */ /* Fading entrances */
"fadeIn", 'fadeIn',
"fadeInDown", 'fadeInDown',
"fadeInDownBig", 'fadeInDownBig',
"fadeInLeft", 'fadeInLeft',
"fadeInLeftBig", 'fadeInLeftBig',
"fadeInRight", 'fadeInRight',
"fadeInRightBig", 'fadeInRightBig',
"fadeInUp", 'fadeInUp',
"fadeInUpBig", 'fadeInUpBig',
"fadeInTopLeft", 'fadeInTopLeft',
"fadeInTopRight", 'fadeInTopRight',
"fadeInBottomLeft", 'fadeInBottomLeft',
"fadeInBottomRight", 'fadeInBottomRight',
/* Fading exits */ /* Fading exits */
"fadeOut", 'fadeOut',
"fadeOutDown", 'fadeOutDown',
"fadeOutDownBig", 'fadeOutDownBig',
"fadeOutLeft", 'fadeOutLeft',
"fadeOutLeftBig", 'fadeOutLeftBig',
"fadeOutRight", 'fadeOutRight',
"fadeOutRightBig", 'fadeOutRightBig',
"fadeOutUp", 'fadeOutUp',
"fadeOutUpBig", 'fadeOutUpBig',
"fadeOutTopLeft", 'fadeOutTopLeft',
"fadeOutTopRight", 'fadeOutTopRight',
"fadeOutBottomRight", 'fadeOutBottomRight',
"fadeOutBottomLeft", 'fadeOutBottomLeft',
/* Flippers */ /* Flippers */
"flip", 'flip',
"flipInX", 'flipInX',
"flipInY", 'flipInY',
"flipOutX", 'flipOutX',
"flipOutY", 'flipOutY',
/* Lightspeed */ /* Lightspeed */
"lightSpeedInRight", 'lightSpeedInRight',
"lightSpeedInLeft", 'lightSpeedInLeft',
"lightSpeedOutRight", 'lightSpeedOutRight',
"lightSpeedOutLeft", 'lightSpeedOutLeft',
/* Rotating entrances */ /* Rotating entrances */
"rotateIn", 'rotateIn',
"rotateInDownLeft", 'rotateInDownLeft',
"rotateInDownRight", 'rotateInDownRight',
"rotateInUpLeft", 'rotateInUpLeft',
"rotateInUpRight", 'rotateInUpRight',
/* Rotating exits */ /* Rotating exits */
"rotateOut", 'rotateOut',
"rotateOutDownLeft", 'rotateOutDownLeft',
"rotateOutDownRight", 'rotateOutDownRight',
"rotateOutUpLeft", 'rotateOutUpLeft',
"rotateOutUpRight", 'rotateOutUpRight',
/* Specials */ /* Specials */
"hinge", 'hinge',
"jackInTheBox", 'jackInTheBox',
"rollIn", 'rollIn',
"rollOut", 'rollOut',
/* Zooming entrances */ /* Zooming entrances */
"zoomIn", 'zoomIn',
"zoomInDown", 'zoomInDown',
"zoomInLeft", 'zoomInLeft',
"zoomInRight", 'zoomInRight',
"zoomInUp", 'zoomInUp',
/* Zooming exits */ /* Zooming exits */
"zoomOut", 'zoomOut',
"zoomOutDown", 'zoomOutDown',
"zoomOutLeft", 'zoomOutLeft',
"zoomOutRight", 'zoomOutRight',
"zoomOutUp", 'zoomOutUp',
/* Sliding entrances */ /* Sliding entrances */
"slideInDown", 'slideInDown',
"slideInLeft", 'slideInLeft',
"slideInRight", 'slideInRight',
"slideInUp", 'slideInUp',
/* Sliding exits */ /* Sliding exits */
"slideOutDown", 'slideOutDown',
"slideOutLeft", 'slideOutLeft',
"slideOutRight", 'slideOutRight',
"slideOutUp" 'slideOutUp',
]; ];

View File

@ -1,17 +1,18 @@
<script setup lang="ts"> <script lang="ts" setup>
import { ref, computed } from "vue"; import { computed, ref } from 'vue';
import { animates } from "./animate"; import { animates } from './animate';
import { cloneDeep } from "@pureadmin/utils"; import { cloneDeep } from '@pureadmin/utils';
import { $t } from '@/plugins/i18n';
defineOptions({ defineOptions({
name: "ReAnimateSelector" name: 'ReAnimateSelector',
}); });
defineProps({ defineProps({
placeholder: { placeholder: {
type: String, type: String,
default: "请选择动画" default: $t('please_select_animation'),
} },
}); });
const inputValue = defineModel({ type: String }); const inputValue = defineModel({ type: String });
@ -21,116 +22,78 @@ const animatesList = ref(animates);
const copyAnimatesList = cloneDeep(animatesList); const copyAnimatesList = cloneDeep(animatesList);
const animateClass = computed(() => { const animateClass = computed(() => {
return [ return ['mt-1', 'flex', 'border', 'w-[130px]', 'h-[100px]', 'items-center', 'cursor-pointer', 'transition-all', 'justify-center', 'border-[#e5e7eb]', 'hover:text-primary', 'hover:duration-[700ms]'];
"mt-1",
"flex",
"border",
"w-[130px]",
"h-[100px]",
"items-center",
"cursor-pointer",
"transition-all",
"justify-center",
"border-[#e5e7eb]",
"hover:text-primary",
"hover:duration-[700ms]"
];
}); });
const animateStyle = computed( const animateStyle = computed(
() => (i: string) => () => (i: string) =>
inputValue.value === i inputValue.value === i
? { ? {
borderColor: "var(--el-color-primary)", borderColor: 'var(--el-color-primary)',
color: "var(--el-color-primary)" color: 'var(--el-color-primary)',
} }
: "" : '',
); );
function onChangeIcon(animate: string) { function onChangeIcon(animate: string) {
inputValue.value = animate; inputValue.value = animate;
} }
function onClear() { function onClear() {
inputValue.value = ""; inputValue.value = '';
} }
function filterMethod(value: any) { function filterMethod(value: any) {
searchVal.value = value; searchVal.value = value;
animatesList.value = copyAnimatesList.value.filter((i: string | any[]) => animatesList.value = copyAnimatesList.value.filter((i: string | any[]) => i.includes(value));
i.includes(value)
);
} }
const animateMap = ref({}); const animateMap = ref({});
function onMouseEnter(index: string | number) { function onMouseEnter(index: string | number) {
animateMap.value[index] = animateMap.value[index]?.loading animateMap.value[index] = animateMap.value[index]?.loading
? Object.assign({}, animateMap.value[index], { ? Object.assign({}, animateMap.value[index], {
loading: false loading: false,
}) })
: Object.assign({}, animateMap.value[index], { : Object.assign({}, animateMap.value[index], {
loading: true loading: true,
}); });
} }
function onMouseleave() { function onMouseleave() {
animateMap.value = {}; animateMap.value = {};
} }
</script> </script>
<template> <template>
<el-select <el-select :filter-method="filterMethod" :model-value="inputValue" :placeholder="placeholder" clearable filterable popper-class="pure-animate-popper" @clear="onClear">
clearable <template #empty>
filterable <div class="w-[280px]">
:placeholder="placeholder" <el-scrollbar :view-style="{ overflow: 'hidden' }" class="border-t border-[#e5e7eb]" height="212px" noresize>
popper-class="pure-animate-popper" <ul class="flex flex-wrap justify-around mb-1">
:model-value="inputValue" <li
:filter-method="filterMethod" v-for="(animate, index) in animatesList"
@clear="onClear" :key="index"
> :class="animateClass"
<template #empty> :style="animateStyle(animate)"
<div class="w-[280px]"> @click="onChangeIcon(animate)"
<el-scrollbar @mouseenter.prevent="onMouseEnter(index)"
noresize @mouseleave.prevent="onMouseleave"
height="212px" >
:view-style="{ overflow: 'hidden' }" <h4 :class="[`animate__animated animate__${animateMap[index]?.loading ? animate + ' animate__infinite' : ''} `]">
class="border-t border-[#e5e7eb]" {{ animate }}
> </h4>
<ul class="flex flex-wrap justify-around mb-1"> </li>
<li </ul>
v-for="(animate, index) in animatesList" <el-empty v-show="animatesList.length === 0" :description="`${searchVal} ${$t('animation_not_exist')}`" :image-size="60" />
:key="index" </el-scrollbar>
:class="animateClass" </div>
:style="animateStyle(animate)" </template>
@mouseenter.prevent="onMouseEnter(index)" </el-select>
@mouseleave.prevent="onMouseleave"
@click="onChangeIcon(animate)"
>
<h4
:class="[
`animate__animated animate__${
animateMap[index]?.loading
? animate + ' animate__infinite'
: ''
} `
]"
>
{{ animate }}
</h4>
</li>
</ul>
<el-empty
v-show="animatesList.length === 0"
:description="`${searchVal} 动画不存在`"
:image-size="60"
/>
</el-scrollbar>
</div>
</template>
</el-select>
</template> </template>
<style> <style>
.pure-animate-popper { .pure-animate-popper {
min-width: 0 !important; min-width: 0 !important;
} }
</style> </style>

View File

@ -1,20 +1,18 @@
import { defineComponent, Fragment } from "vue"; import { defineComponent, Fragment } from 'vue';
import { hasAuth } from "@/router/utils"; import { hasAuth } from '@/router/utils';
export default defineComponent({ export default defineComponent({
name: "Auth", name: 'Auth',
props: { props: {
value: { value: {
type: undefined, type: undefined,
default: [] default: [],
} },
}, },
setup(props, { slots }) { setup(props, { slots }) {
return () => { return () => {
if (!slots) return null; if (!slots) return null;
return hasAuth(props.value) ? ( return hasAuth(props.value) ? <Fragment>{slots.default?.()}</Fragment> : null;
<Fragment>{slots.default?.()}</Fragment> };
) : null; },
};
}
}); });

View File

@ -1,39 +1,32 @@
import { ref } from "vue"; import { ref } from 'vue';
import reDialog from "./index.vue"; import reDialog from './index.vue';
import { useTimeoutFn } from "@vueuse/core"; import { useTimeoutFn } from '@vueuse/core';
import { withInstall } from "@pureadmin/utils"; import { withInstall } from '@pureadmin/utils';
import type { import type { ArgsType, ButtonProps, DialogOptions, DialogProps, EventType } from './type';
EventType,
ArgsType,
DialogProps,
ButtonProps,
DialogOptions
} from "./type";
const dialogStore = ref<Array<DialogOptions>>([]); const dialogStore = ref<Array<DialogOptions>>([]);
/** 打开弹框 */ /** 打开弹框 */
const addDialog = (options: DialogOptions) => { const addDialog = (options: DialogOptions) => {
const open = () => const open = () => dialogStore.value.push(Object.assign(options, { visible: true }));
dialogStore.value.push(Object.assign(options, { visible: true })); if (options?.openDelay) {
if (options?.openDelay) { useTimeoutFn(() => {
useTimeoutFn(() => { open();
open(); }, options.openDelay);
}, options.openDelay); } else {
} else { open();
open(); }
}
}; };
/** 关闭弹框 */ /** 关闭弹框 */
const closeDialog = (options: DialogOptions, index: number, args?: any) => { const closeDialog = (options: DialogOptions, index: number, args?: any) => {
dialogStore.value[index].visible = false; dialogStore.value[index].visible = false;
options.closeCallBack && options.closeCallBack({ options, index, args }); options.closeCallBack && options.closeCallBack({ options, index, args });
const closeDelay = options?.closeDelay ?? 200; const closeDelay = options?.closeDelay ?? 200;
useTimeoutFn(() => { useTimeoutFn(() => {
dialogStore.value.splice(index, 1); dialogStore.value.splice(index, 1);
}, closeDelay); }, closeDelay);
}; };
/** /**
@ -42,13 +35,13 @@ const closeDialog = (options: DialogOptions, index: number, args?: any) => {
* @param key `title` * @param key `title`
* @param index `0``index` * @param index `0``index`
*/ */
const updateDialog = (value: any, key = "title", index = 0) => { const updateDialog = (value: any, key = 'title', index = 0) => {
dialogStore.value[index][key] = value; dialogStore.value[index][key] = value;
}; };
/** 关闭所有弹框 */ /** 关闭所有弹框 */
const closeAllDialog = () => { const closeAllDialog = () => {
dialogStore.value = []; dialogStore.value = [];
}; };
/** 使`addDialog` /** 使`addDialog`
@ -59,11 +52,4 @@ const closeAllDialog = () => {
const ReDialog = withInstall(reDialog); const ReDialog = withInstall(reDialog);
export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions }; export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions };
export { export { ReDialog, dialogStore, addDialog, closeDialog, updateDialog, closeAllDialog };
ReDialog,
dialogStore,
addDialog,
closeDialog,
updateDialog,
closeAllDialog
};

View File

@ -1,206 +1,154 @@
<script setup lang="ts"> <script lang="ts" setup>
import { import { type ButtonProps, closeDialog, type DialogOptions, dialogStore, type EventType } from './index';
type EventType, import { computed, ref } from 'vue';
type ButtonProps, import { isFunction } from '@pureadmin/utils';
type DialogOptions, import Fullscreen from '@iconify-icons/ri/fullscreen-fill';
closeDialog, import ExitFullscreen from '@iconify-icons/ri/fullscreen-exit-fill';
dialogStore import { $t } from '@/plugins/i18n';
} from "./index";
import { ref, computed } from "vue";
import { isFunction } from "@pureadmin/utils";
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
defineOptions({ defineOptions({
name: "ReDialog" name: 'ReDialog',
}); });
const sureBtnMap = ref({}); const sureBtnMap = ref({});
const fullscreen = ref(false); const fullscreen = ref(false);
const footerButtons = computed(() => { const footerButtons = computed(() => {
return (options: DialogOptions) => { return (options: DialogOptions) => {
return options?.footerButtons?.length > 0 return options?.footerButtons?.length > 0
? options.footerButtons ? options.footerButtons
: ([ : ([
{ {
label: "取消", label: $t('cancel'),
text: true, text: true,
bg: true, bg: true,
btnClick: ({ dialog: { options, index } }) => { btnClick: ({ dialog: { options, index } }) => {
const done = () => const done = () => closeDialog(options, index, { command: 'cancel' });
closeDialog(options, index, { command: "cancel" }); if (options?.beforeCancel && isFunction(options?.beforeCancel)) {
if (options?.beforeCancel && isFunction(options?.beforeCancel)) { options.beforeCancel(done, { options, index });
options.beforeCancel(done, { options, index }); } else {
} else { done();
done(); }
} },
} },
}, {
{ label: $t('confirm'),
label: "确定", type: 'primary',
type: "primary", text: true,
text: true, bg: true,
bg: true, popconfirm: options?.popconfirm,
popconfirm: options?.popconfirm, btnClick: ({ dialog: { options, index } }) => {
btnClick: ({ dialog: { options, index } }) => { if (options?.sureBtnLoading) {
if (options?.sureBtnLoading) { sureBtnMap.value[index] = Object.assign({}, sureBtnMap.value[index], {
sureBtnMap.value[index] = Object.assign( loading: true,
{}, });
sureBtnMap.value[index], }
{ const closeLoading = () => {
loading: true if (options?.sureBtnLoading) {
} sureBtnMap.value[index].loading = false;
); }
} };
const closeLoading = () => { const done = () => {
if (options?.sureBtnLoading) { closeLoading();
sureBtnMap.value[index].loading = false; closeDialog(options, index, { command: 'sure' });
} };
}; if (options?.beforeSure && isFunction(options?.beforeSure)) {
const done = () => { options.beforeSure(done, { options, index, closeLoading });
closeLoading(); } else {
closeDialog(options, index, { command: "sure" }); done();
}; }
if (options?.beforeSure && isFunction(options?.beforeSure)) { },
options.beforeSure(done, { options, index, closeLoading }); },
} else { ] as Array<ButtonProps>);
done(); };
}
}
}
] as Array<ButtonProps>);
};
}); });
const fullscreenClass = computed(() => { const fullscreenClass = computed(() => {
return [ return ['el-icon', 'el-dialog__close', '-translate-x-2', 'cursor-pointer', 'hover:!text-[red]'];
"el-icon",
"el-dialog__close",
"-translate-x-2",
"cursor-pointer",
"hover:!text-[red]"
];
}); });
function eventsCallBack( function eventsCallBack(event: EventType, options: DialogOptions, index: number, isClickFullScreen = false) {
event: EventType, if (!isClickFullScreen) fullscreen.value = options?.fullscreen ?? false;
options: DialogOptions, if (options?.[event] && isFunction(options?.[event])) {
index: number, return options?.[event]({ options, index });
isClickFullScreen = false }
) {
if (!isClickFullScreen) fullscreen.value = options?.fullscreen ?? false;
if (options?.[event] && isFunction(options?.[event])) {
return options?.[event]({ options, index });
}
} }
function handleClose( function handleClose(options: DialogOptions, index: number, args = { command: 'close' }) {
options: DialogOptions, closeDialog(options, index, args);
index: number, eventsCallBack('close', options, index);
args = { command: "close" }
) {
closeDialog(options, index, args);
eventsCallBack("close", options, index);
} }
</script> </script>
<template> <template>
<el-dialog <el-dialog
v-for="(options, index) in dialogStore" v-for="(options, index) in dialogStore"
:key="index" :key="index"
v-bind="options" v-model="options.visible"
v-model="options.visible" :fullscreen="fullscreen ? true : options?.fullscreen ? true : false"
class="pure-dialog" class="pure-dialog"
:fullscreen="fullscreen ? true : options?.fullscreen ? true : false" v-bind="options"
@closed="handleClose(options, index)" @closeAutoFocus="eventsCallBack('closeAutoFocus', options, index)"
@opened="eventsCallBack('open', options, index)" @closed="handleClose(options, index)"
@openAutoFocus="eventsCallBack('openAutoFocus', options, index)" @openAutoFocus="eventsCallBack('openAutoFocus', options, index)"
@closeAutoFocus="eventsCallBack('closeAutoFocus', options, index)" @opened="eventsCallBack('open', options, index)"
> >
<!-- header --> <!-- header -->
<template <template v-if="options?.fullscreenIcon || options?.headerRenderer" #header="{ close, titleId, titleClass }">
v-if="options?.fullscreenIcon || options?.headerRenderer" <div v-if="options?.fullscreenIcon" class="flex items-center justify-between">
#header="{ close, titleId, titleClass }" <span :id="titleId" :class="titleClass">{{ options?.title }}</span>
> <i
<div v-if="!options?.fullscreen"
v-if="options?.fullscreenIcon" :class="fullscreenClass"
class="flex items-center justify-between" @click="
> () => {
<span :id="titleId" :class="titleClass">{{ options?.title }}</span> fullscreen = !fullscreen;
<i eventsCallBack('fullscreenCallBack', { ...options, fullscreen }, index, true);
v-if="!options?.fullscreen" }
:class="fullscreenClass" "
@click=" >
() => { <IconifyIconOffline :icon="options?.fullscreen ? ExitFullscreen : fullscreen ? ExitFullscreen : Fullscreen" class="pure-dialog-svg" />
fullscreen = !fullscreen; </i>
eventsCallBack( </div>
'fullscreenCallBack', <component :is="options?.headerRenderer({ close, titleId, titleClass })" v-else />
{ ...options, fullscreen }, </template>
index, <component :is="options.contentRenderer({ options, index })" v-bind="options?.props" @close="args => handleClose(options, index, args)" />
true <!-- footer -->
); <template v-if="!options?.hideFooter" #footer>
} <template v-if="options?.footerRenderer">
" <component :is="options?.footerRenderer({ options, index })" />
> </template>
<IconifyIconOffline <span v-else>
class="pure-dialog-svg" <template v-for="(btn, key) in footerButtons(options)" :key="key">
:icon=" <el-popconfirm
options?.fullscreen v-if="btn.popconfirm"
? ExitFullscreen v-bind="btn.popconfirm"
: fullscreen @confirm="
? ExitFullscreen btn.btnClick({
: Fullscreen dialog: { options, index },
" button: { btn, index: key },
/> })
</i> "
</div> >
<component <template #reference>
:is="options?.headerRenderer({ close, titleId, titleClass })" <el-button v-bind="btn">{{ btn?.label }}</el-button>
v-else </template>
/> </el-popconfirm>
</template> <el-button
<component v-else
v-bind="options?.props" :loading="key === 1 && sureBtnMap[index]?.loading"
:is="options.contentRenderer({ options, index })" v-bind="btn"
@close="args => handleClose(options, index, args)" @click="
/> btn.btnClick({
<!-- footer --> dialog: { options, index },
<template v-if="!options?.hideFooter" #footer> button: { btn, index: key },
<template v-if="options?.footerRenderer"> })
<component :is="options?.footerRenderer({ options, index })" /> "
</template> >
<span v-else> {{ btn?.label }}
<template v-for="(btn, key) in footerButtons(options)" :key="key"> </el-button>
<el-popconfirm </template>
v-if="btn.popconfirm" </span>
v-bind="btn.popconfirm" </template>
@confirm=" </el-dialog>
btn.btnClick({
dialog: { options, index },
button: { btn, index: key }
})
"
>
<template #reference>
<el-button v-bind="btn">{{ btn?.label }}</el-button>
</template>
</el-popconfirm>
<el-button
v-else
v-bind="btn"
:loading="key === 1 && sureBtnMap[index]?.loading"
@click="
btn.btnClick({
dialog: { options, index },
button: { btn, index: key }
})
"
>
{{ btn?.label }}
</el-button>
</template>
</span>
</template>
</el-dialog>
</template> </template>

View File

@ -1,275 +1,206 @@
import type { CSSProperties, VNode, Component } from "vue"; import type { Component, CSSProperties, VNode } from 'vue';
type DoneFn = (cancel?: boolean) => void; type DoneFn = (cancel?: boolean) => void;
type EventType = type EventType = 'open' | 'close' | 'openAutoFocus' | 'closeAutoFocus' | 'fullscreenCallBack';
| "open"
| "close"
| "openAutoFocus"
| "closeAutoFocus"
| "fullscreenCallBack";
type ArgsType = { type ArgsType = {
/** `cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或空白页或按下了esc键 */ /** `cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或空白页或按下了esc键 */
command: "cancel" | "sure" | "close"; command: 'cancel' | 'sure' | 'close';
}; };
type ButtonType = type ButtonType = 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'text';
| "primary"
| "success"
| "warning"
| "danger"
| "info"
| "text";
/** https://element-plus.org/zh-CN/component/dialog.html#attributes */ /** https://element-plus.org/zh-CN/component/dialog.html#attributes */
type DialogProps = { type DialogProps = {
/** `Dialog` 的显示与隐藏 */ /** `Dialog` 的显示与隐藏 */
visible?: boolean; visible?: boolean;
/** `Dialog` 的标题 */ /** `Dialog` 的标题 */
title?: string; title?: string;
/** `Dialog` 的宽度,默认 `50%` */ /** `Dialog` 的宽度,默认 `50%` */
width?: string | number; width?: string | number;
/** 是否为全屏 `Dialog`(会一直处于全屏状态,除非弹框关闭),默认 `false``fullscreen` 和 `fullscreenIcon` 都传时只有 `fullscreen` 会生效 */ /** 是否为全屏 `Dialog`(会一直处于全屏状态,除非弹框关闭),默认 `false``fullscreen` 和 `fullscreenIcon` 都传时只有 `fullscreen` 会生效 */
fullscreen?: boolean; fullscreen?: boolean;
/** 是否显示全屏操作图标,默认 `false``fullscreen` 和 `fullscreenIcon` 都传时只有 `fullscreen` 会生效 */ /** 是否显示全屏操作图标,默认 `false``fullscreen` 和 `fullscreenIcon` 都传时只有 `fullscreen` 会生效 */
fullscreenIcon?: boolean; fullscreenIcon?: boolean;
/** `Dialog CSS` 中的 `margin-top` 值,默认 `15vh` */ /** `Dialog CSS` 中的 `margin-top` 值,默认 `15vh` */
top?: string; top?: string;
/** 是否需要遮罩层,默认 `true` */ /** 是否需要遮罩层,默认 `true` */
modal?: boolean; modal?: boolean;
/** `Dialog` 自身是否插入至 `body` 元素上。嵌套的 `Dialog` 必须指定该属性并赋值为 `true`,默认 `false` */ /** `Dialog` 自身是否插入至 `body` 元素上。嵌套的 `Dialog` 必须指定该属性并赋值为 `true`,默认 `false` */
appendToBody?: boolean; appendToBody?: boolean;
/** 是否在 `Dialog` 出现时将 `body` 滚动锁定,默认 `true` */ /** 是否在 `Dialog` 出现时将 `body` 滚动锁定,默认 `true` */
lockScroll?: boolean; lockScroll?: boolean;
/** `Dialog` 的自定义类名 */ /** `Dialog` 的自定义类名 */
class?: string; class?: string;
/** `Dialog` 的自定义样式 */ /** `Dialog` 的自定义样式 */
style?: CSSProperties; style?: CSSProperties;
/** `Dialog` 打开的延时时间,单位毫秒,默认 `0` */ /** `Dialog` 打开的延时时间,单位毫秒,默认 `0` */
openDelay?: number; openDelay?: number;
/** `Dialog` 关闭的延时时间,单位毫秒,默认 `0` */ /** `Dialog` 关闭的延时时间,单位毫秒,默认 `0` */
closeDelay?: number; closeDelay?: number;
/** 是否可以通过点击 `modal` 关闭 `Dialog`,默认 `true` */ /** 是否可以通过点击 `modal` 关闭 `Dialog`,默认 `true` */
closeOnClickModal?: boolean; closeOnClickModal?: boolean;
/** 是否可以通过按下 `ESC` 关闭 `Dialog`,默认 `true` */ /** 是否可以通过按下 `ESC` 关闭 `Dialog`,默认 `true` */
closeOnPressEscape?: boolean; closeOnPressEscape?: boolean;
/** 是否显示关闭按钮,默认 `true` */ /** 是否显示关闭按钮,默认 `true` */
showClose?: boolean; showClose?: boolean;
/** 关闭前的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */ /** 关闭前的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
beforeClose?: (done: DoneFn) => void; beforeClose?: (done: DoneFn) => void;
/** 为 `Dialog` 启用可拖拽功能,默认 `false` */ /** 为 `Dialog` 启用可拖拽功能,默认 `false` */
draggable?: boolean; draggable?: boolean;
/** 是否让 `Dialog` 的 `header` 和 `footer` 部分居中排列,默认 `false` */ /** 是否让 `Dialog` 的 `header` 和 `footer` 部分居中排列,默认 `false` */
center?: boolean; center?: boolean;
/** 是否水平垂直对齐对话框,默认 `false` */ /** 是否水平垂直对齐对话框,默认 `false` */
alignCenter?: boolean; alignCenter?: boolean;
/** 当关闭 `Dialog` 时,销毁其中的元素,默认 `false` */ /** 当关闭 `Dialog` 时,销毁其中的元素,默认 `false` */
destroyOnClose?: boolean; destroyOnClose?: boolean;
}; };
//element-plus.org/zh-CN/component/popconfirm.html#attributes //element-plus.org/zh-CN/component/popconfirm.html#attributes
type Popconfirm = { type Popconfirm = {
/** 标题 */ /** 标题 */
title?: string; title?: string;
/** 确定按钮文字 */ /** 确定按钮文字 */
confirmButtonText?: string; confirmButtonText?: string;
/** 取消按钮文字 */ /** 取消按钮文字 */
cancelButtonText?: string; cancelButtonText?: string;
/** 确定按钮类型,默认 `primary` */ /** 确定按钮类型,默认 `primary` */
confirmButtonType?: ButtonType; confirmButtonType?: ButtonType;
/** 取消按钮类型,默认 `text` */ /** 取消按钮类型,默认 `text` */
cancelButtonType?: ButtonType; cancelButtonType?: ButtonType;
/** 自定义图标,默认 `QuestionFilled` */ /** 自定义图标,默认 `QuestionFilled` */
icon?: string | Component; icon?: string | Component;
/** `Icon` 颜色,默认 `#f90` */ /** `Icon` 颜色,默认 `#f90` */
iconColor?: string; iconColor?: string;
/** 是否隐藏 `Icon`,默认 `false` */ /** 是否隐藏 `Icon`,默认 `false` */
hideIcon?: boolean; hideIcon?: boolean;
/** 关闭时的延迟,默认 `200` */ /** 关闭时的延迟,默认 `200` */
hideAfter?: number; hideAfter?: number;
/** 是否将 `popover` 的下拉列表插入至 `body` 元素,默认 `true` */ /** 是否将 `popover` 的下拉列表插入至 `body` 元素,默认 `true` */
teleported?: boolean; teleported?: boolean;
/** 当 `popover` 组件长时间不触发且 `persistent` 属性设置为 `false` 时, `popover` 将会被删除,默认 `false` */ /** 当 `popover` 组件长时间不触发且 `persistent` 属性设置为 `false` 时, `popover` 将会被删除,默认 `false` */
persistent?: boolean; persistent?: boolean;
/** 弹层宽度,最小宽度 `150px`,默认 `150` */ /** 弹层宽度,最小宽度 `150px`,默认 `150` */
width?: string | number; width?: string | number;
}; };
type BtnClickDialog = { type BtnClickDialog = {
options?: DialogOptions; options?: DialogOptions;
index?: number; index?: number;
}; };
type BtnClickButton = { type BtnClickButton = {
btn?: ButtonProps; btn?: ButtonProps;
index?: number; index?: number;
}; };
/** https://element-plus.org/zh-CN/component/button.html#button-attributes */ /** https://element-plus.org/zh-CN/component/button.html#button-attributes */
type ButtonProps = { type ButtonProps = {
/** 按钮文字 */ /** 按钮文字 */
label: string; label: string;
/** 按钮尺寸 */ /** 按钮尺寸 */
size?: "large" | "default" | "small"; size?: 'large' | 'default' | 'small';
/** 按钮类型 */ /** 按钮类型 */
type?: "primary" | "success" | "warning" | "danger" | "info"; type?: 'primary' | 'success' | 'warning' | 'danger' | 'info';
/** 是否为朴素按钮,默认 `false` */ /** 是否为朴素按钮,默认 `false` */
plain?: boolean; plain?: boolean;
/** 是否为文字按钮,默认 `false` */ /** 是否为文字按钮,默认 `false` */
text?: boolean; text?: boolean;
/** 是否显示文字按钮背景颜色,默认 `false` */ /** 是否显示文字按钮背景颜色,默认 `false` */
bg?: boolean; bg?: boolean;
/** 是否为链接按钮,默认 `false` */ /** 是否为链接按钮,默认 `false` */
link?: boolean; link?: boolean;
/** 是否为圆角按钮,默认 `false` */ /** 是否为圆角按钮,默认 `false` */
round?: boolean; round?: boolean;
/** 是否为圆形按钮,默认 `false` */ /** 是否为圆形按钮,默认 `false` */
circle?: boolean; circle?: boolean;
/** 确定按钮的 `Popconfirm` 气泡确认框相关配置 */ /** 确定按钮的 `Popconfirm` 气泡确认框相关配置 */
popconfirm?: Popconfirm; popconfirm?: Popconfirm;
/** 是否为加载中状态,默认 `false` */ /** 是否为加载中状态,默认 `false` */
loading?: boolean; loading?: boolean;
/** 自定义加载中状态图标组件 */ /** 自定义加载中状态图标组件 */
loadingIcon?: string | Component; loadingIcon?: string | Component;
/** 按钮是否为禁用状态,默认 `false` */ /** 按钮是否为禁用状态,默认 `false` */
disabled?: boolean; disabled?: boolean;
/** 图标组件 */ /** 图标组件 */
icon?: string | Component; icon?: string | Component;
/** 是否开启原生 `autofocus` 属性,默认 `false` */ /** 是否开启原生 `autofocus` 属性,默认 `false` */
autofocus?: boolean; autofocus?: boolean;
/** 原生 `type` 属性,默认 `button` */ /** 原生 `type` 属性,默认 `button` */
nativeType?: "button" | "submit" | "reset"; nativeType?: 'button' | 'submit' | 'reset';
/** 自动在两个中文字符之间插入空格 */ /** 自动在两个中文字符之间插入空格 */
autoInsertSpace?: boolean; autoInsertSpace?: boolean;
/** 自定义按钮颜色, 并自动计算 `hover` 和 `active` 触发后的颜色 */ /** 自定义按钮颜色, 并自动计算 `hover` 和 `active` 触发后的颜色 */
color?: string; color?: string;
/** `dark` 模式, 意味着自动设置 `color` 为 `dark` 模式的颜色,默认 `false` */ /** `dark` 模式, 意味着自动设置 `color` 为 `dark` 模式的颜色,默认 `false` */
dark?: boolean; dark?: boolean;
/** 自定义元素标签 */ /** 自定义元素标签 */
tag?: string | Component; tag?: string | Component;
/** 点击按钮后触发的回调 */ /** 点击按钮后触发的回调 */
btnClick?: ({ btnClick?: ({
dialog, dialog,
button button,
}: { }: {
/** 当前 `Dialog` 信息 */ /** 当前 `Dialog` 信息 */
dialog: BtnClickDialog; dialog: BtnClickDialog;
/** 当前 `button` 信息 */ /** 当前 `button` 信息 */
button: BtnClickButton; button: BtnClickButton;
}) => void; }) => void;
}; };
interface DialogOptions extends DialogProps { interface DialogOptions extends DialogProps {
/** 内容区组件的 `props`,可通过 `defineProps` 接收 */ /** 内容区组件的 `props`,可通过 `defineProps` 接收 */
props?: any; props?: any;
/** 是否隐藏 `Dialog` 按钮操作区的内容 */ /** 是否隐藏 `Dialog` 按钮操作区的内容 */
hideFooter?: boolean; hideFooter?: boolean;
/** 确定按钮的 `Popconfirm` 气泡确认框相关配置 */ /** 确定按钮的 `Popconfirm` 气泡确认框相关配置 */
popconfirm?: Popconfirm; popconfirm?: Popconfirm;
/** 点击确定按钮后是否开启 `loading` 加载动画 */ /** 点击确定按钮后是否开启 `loading` 加载动画 */
sureBtnLoading?: boolean; sureBtnLoading?: boolean;
/** /**
* @description * @description
* @see {@link https://element-plus.org/zh-CN/component/dialog.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%A4%B4%E9%83%A8} * @see {@link https://element-plus.org/zh-CN/component/dialog.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%A4%B4%E9%83%A8}
*/ */
headerRenderer?: ({ headerRenderer?: ({ close, titleId, titleClass }: { close: Function; titleId: string; titleClass: string }) => VNode | Component;
close, /** 自定义内容渲染器 */
titleId, contentRenderer?: ({ options, index }: { options: DialogOptions; index: number }) => VNode | Component;
titleClass /** 自定义按钮操作区的内容渲染器,会覆盖`footerButtons`以及默认的 `取消` 和 `确定` 按钮 */
}: { footerRenderer?: ({ options, index }: { options: DialogOptions; index: number }) => VNode | Component;
close: Function; /** 自定义底部按钮操作 */
titleId: string; footerButtons?: Array<ButtonProps>;
titleClass: string; /** `Dialog` 打开后的回调 */
}) => VNode | Component; open?: ({ options, index }: { options: DialogOptions; index: number }) => void;
/** 自定义内容渲染器 */ /** `Dialog` 关闭后的回调只有点击右上角关闭按钮或空白页或按下了esc键关闭页面时才会触发 */
contentRenderer?: ({ close?: ({ options, index }: { options: DialogOptions; index: number }) => void;
options, /** `Dialog` 关闭后的回调。 `args` 返回的 `command` 值解析:`cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或空白页或按下了esc键 */
index closeCallBack?: ({ options, index, args }: { options: DialogOptions; index: number; args: any }) => void;
}: { /** 点击全屏按钮时的回调 */
options: DialogOptions; fullscreenCallBack?: ({ options, index }: { options: DialogOptions; index: number }) => void;
index: number; /** 输入焦点聚焦在 `Dialog` 内容时的回调 */
}) => VNode | Component; openAutoFocus?: ({ options, index }: { options: DialogOptions; index: number }) => void;
/** 自定义按钮操作区的内容渲染器,会覆盖`footerButtons`以及默认的 `取消` 和 `确定` 按钮 */ /** 输入焦点从 `Dialog` 内容失焦时的回调 */
footerRenderer?: ({ closeAutoFocus?: ({ options, index }: { options: DialogOptions; index: number }) => void;
options, /** 点击底部取消按钮的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
index beforeCancel?: (
}: { done: Function,
options: DialogOptions; {
index: number; options,
}) => VNode | Component; index,
/** 自定义底部按钮操作 */ }: {
footerButtons?: Array<ButtonProps>; options: DialogOptions;
/** `Dialog` 打开后的回调 */ index: number;
open?: ({ },
options, ) => void;
index /** 点击底部确定按钮的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
}: { beforeSure?: (
options: DialogOptions; done: Function,
index: number; {
}) => void; options,
/** `Dialog` 关闭后的回调只有点击右上角关闭按钮或空白页或按下了esc键关闭页面时才会触发 */ index,
close?: ({ closeLoading,
options, }: {
index options: DialogOptions;
}: { index: number;
options: DialogOptions; /** 关闭确定按钮的 `loading` 加载动画 */
index: number; closeLoading: Function;
}) => void; },
/** `Dialog` 关闭后的回调。 `args` 返回的 `command` 值解析:`cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或空白页或按下了esc键 */ ) => void;
closeCallBack?: ({
options,
index,
args
}: {
options: DialogOptions;
index: number;
args: any;
}) => void;
/** 点击全屏按钮时的回调 */
fullscreenCallBack?: ({
options,
index
}: {
options: DialogOptions;
index: number;
}) => void;
/** 输入焦点聚焦在 `Dialog` 内容时的回调 */
openAutoFocus?: ({
options,
index
}: {
options: DialogOptions;
index: number;
}) => void;
/** 输入焦点从 `Dialog` 内容失焦时的回调 */
closeAutoFocus?: ({
options,
index
}: {
options: DialogOptions;
index: number;
}) => void;
/** 点击底部取消按钮的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
beforeCancel?: (
done: Function,
{
options,
index
}: {
options: DialogOptions;
index: number;
}
) => void;
/** 点击底部确定按钮的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
beforeSure?: (
done: Function,
{
options,
index,
closeLoading
}: {
options: DialogOptions;
index: number;
/** 关闭确定按钮的 `loading` 加载动画 */
closeLoading: Function;
}
) => void;
} }
export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions }; export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions };

View File

@ -1,6 +1,6 @@
import iconifyIconOffline from "./src/iconifyIconOffline"; import iconifyIconOffline from './src/iconifyIconOffline';
import iconifyIconOnline from "./src/iconifyIconOnline"; import iconifyIconOnline from './src/iconifyIconOnline';
import fontIcon from "./src/iconfont"; import fontIcon from './src/iconfont';
/** 本地图标组件 */ /** 本地图标组件 */
const IconifyIconOffline = iconifyIconOffline; const IconifyIconOffline = iconifyIconOffline;

View File

@ -1,48 +1,45 @@
import { h, defineComponent } from "vue"; import { defineComponent, h } from 'vue';
// 封装iconfont组件默认`font-class`引用模式,支持`unicode`引用、`font-class`引用、`symbol`引用 https://www.iconfont.cn/help/detail?spm=a313x.7781069.1998910419.20&helptype=code // 封装iconfont组件默认`font-class`引用模式,支持`unicode`引用、`font-class`引用、`symbol`引用 https://www.iconfont.cn/help/detail?spm=a313x.7781069.1998910419.20&helptype=code
export default defineComponent({ export default defineComponent({
name: "FontIcon", name: 'FontIcon',
props: { props: {
icon: { icon: {
type: String, type: String,
default: "" default: '',
} },
}, },
render() { render() {
const attrs = this.$attrs; const attrs = this.$attrs;
if (Object.keys(attrs).includes("uni") || attrs?.iconType === "uni") { if (Object.keys(attrs).includes('uni') || attrs?.iconType === 'uni') {
return h( return h(
"i", 'i',
{ {
class: "iconfont", class: 'iconfont',
...attrs ...attrs,
}, },
this.icon this.icon,
); );
} else if ( } else if (Object.keys(attrs).includes('svg') || attrs?.iconType === 'svg') {
Object.keys(attrs).includes("svg") || return h(
attrs?.iconType === "svg" 'svg',
) { {
return h( class: 'icon-svg',
"svg", 'aria-hidden': true,
{ },
class: "icon-svg", {
"aria-hidden": true default: () => [
}, h('use', {
{ 'xlink:href': `#${this.icon}`,
default: () => [ }),
h("use", { ],
"xlink:href": `#${this.icon}` },
}) );
] } else {
} return h('i', {
); class: `iconfont ${this.icon}`,
} else { ...attrs,
return h("i", { });
class: `iconfont ${this.icon}`, }
...attrs },
});
}
}
}); });

View File

@ -1,30 +1,28 @@
import { h, defineComponent } from "vue"; import { defineComponent, h } from 'vue';
import { Icon as IconifyIcon, addIcon } from "@iconify/vue/dist/offline"; import { addIcon, Icon as IconifyIcon } from '@iconify/vue/dist/offline';
// Iconify Icon在Vue里本地使用用于内网环境 // Iconify Icon在Vue里本地使用用于内网环境
export default defineComponent({ export default defineComponent({
name: "IconifyIconOffline", name: 'IconifyIconOffline',
components: { IconifyIcon }, components: { IconifyIcon },
props: { props: {
icon: { icon: {
default: null default: null,
} },
}, },
render() { render() {
if (typeof this.icon === "object") addIcon(this.icon, this.icon); if (typeof this.icon === 'object') addIcon(this.icon, this.icon);
const attrs = this.$attrs; const attrs = this.$attrs;
return h( return h(
IconifyIcon, IconifyIcon,
{ {
icon: this.icon, icon: this.icon,
style: attrs?.style style: attrs?.style ? Object.assign(attrs.style, { outline: 'none' }) : { outline: 'none' },
? Object.assign(attrs.style, { outline: "none" }) ...attrs,
: { outline: "none" }, },
...attrs {
}, default: () => [],
{ },
default: () => [] );
} },
);
}
}); });

View File

@ -1,30 +1,28 @@
import { h, defineComponent } from "vue"; import { defineComponent, h } from 'vue';
import { Icon as IconifyIcon } from "@iconify/vue"; import { Icon as IconifyIcon } from '@iconify/vue';
// Iconify Icon在Vue里在线使用用于外网环境 // Iconify Icon在Vue里在线使用用于外网环境
export default defineComponent({ export default defineComponent({
name: "IconifyIconOnline", name: 'IconifyIconOnline',
components: { IconifyIcon }, components: { IconifyIcon },
props: { props: {
icon: { icon: {
type: String, type: String,
default: "" default: '',
} },
}, },
render() { render() {
const attrs = this.$attrs; const attrs = this.$attrs;
return h( return h(
IconifyIcon, IconifyIcon,
{ {
icon: `${this.icon}`, icon: `${this.icon}`,
style: attrs?.style style: attrs?.style ? Object.assign(attrs.style, { outline: 'none' }) : { outline: 'none' },
? Object.assign(attrs.style, { outline: "none" }) ...attrs,
: { outline: "none" }, },
...attrs {
}, default: () => [],
{ },
default: () => [] );
} },
);
}
}); });

View File

@ -1,14 +1,16 @@
// 这里存放本地图标,在 src/layout/index.vue 文件中加载,避免在首启动加载 // 这里存放本地图标,在 src/layout/index.vue 文件中加载,避免在首启动加载
import { addIcon } from "@iconify/vue/dist/offline"; import { addIcon } from '@iconify/vue/dist/offline';
// 本地菜单图标,后端在路由的 icon 中返回对应的图标字符串并且前端在此处使用 addIcon 添加即可渲染菜单图标 // 本地菜单图标,后端在路由的 icon 中返回对应的图标字符串并且前端在此处使用 addIcon 添加即可渲染菜单图标
// @iconify-icons/ep // @iconify-icons/ep
import Lollipop from "@iconify-icons/ep/lollipop"; import Lollipop from '@iconify-icons/ep/lollipop';
import HomeFilled from "@iconify-icons/ep/home-filled"; import HomeFilled from '@iconify-icons/ep/home-filled';
addIcon("ep:lollipop", Lollipop);
addIcon("ep:home-filled", HomeFilled);
// @iconify-icons/ri // @iconify-icons/ri
import Search from "@iconify-icons/ri/search-line"; import Search from '@iconify-icons/ri/search-line';
import InformationLine from "@iconify-icons/ri/information-line"; import InformationLine from '@iconify-icons/ri/information-line';
addIcon("ri:search-line", Search);
addIcon("ri:information-line", InformationLine); addIcon('ep:lollipop', Lollipop);
addIcon('ep:home-filled', HomeFilled);
addIcon('ri:search-line', Search);
addIcon('ri:information-line', InformationLine);

View File

@ -1,20 +1,20 @@
export interface iconType { export interface iconType {
// iconify (https://docs.iconify.design/icon-components/vue/#properties) // iconify (https://docs.iconify.design/icon-components/vue/#properties)
inline?: boolean; inline?: boolean;
width?: string | number; width?: string | number;
height?: string | number; height?: string | number;
horizontalFlip?: boolean; horizontalFlip?: boolean;
verticalFlip?: boolean; verticalFlip?: boolean;
flip?: string; flip?: string;
rotate?: number | string; rotate?: number | string;
color?: string; color?: string;
horizontalAlign?: boolean; horizontalAlign?: boolean;
verticalAlign?: boolean; verticalAlign?: boolean;
align?: string; align?: string;
onLoad?: Function; onLoad?: Function;
includes?: Function; includes?: Function;
// svg 需要什么SVG属性自行添加 // svg 需要什么SVG属性自行添加
fill?: string; fill?: string;
// all icon // all icon
style?: object; style?: object;
} }

View File

@ -1,2 +0,0 @@
normal 普通数字动画组件
rebound 回弹式数字动画组件

View File

@ -1,216 +1,175 @@
import { useRenderIcon } from "@/components/CommonIcon/src/hooks"; import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
import { import { isFunction, isNumber, useDark, useResizeObserver } from '@pureadmin/utils';
isFunction, import { defineComponent, getCurrentInstance, h, nextTick, type PropType, ref, toRef, watch } from 'vue';
isNumber, import './index.css';
useDark, import type { OptionsType } from './type';
useResizeObserver
} from "@pureadmin/utils";
import {
type PropType,
defineComponent,
getCurrentInstance,
h,
nextTick,
ref,
toRef,
watch
} from "vue";
import "./index.css";
import type { OptionsType } from "./type";
const props = { const props = {
options: { options: {
type: Array<OptionsType>, type: Array<OptionsType>,
default: () => [] default: () => [],
}, },
/** 默认选中,按照第一个索引为 `0` 的模式,可选(`modelValue`只有传`number`类型时才为响应式) */ /** 默认选中,按照第一个索引为 `0` 的模式,可选(`modelValue`只有传`number`类型时才为响应式) */
modelValue: { modelValue: {
type: undefined, type: undefined,
require: false, require: false,
default: "0" default: '0',
}, },
/** 将宽度调整为父元素宽度 */ /** 将宽度调整为父元素宽度 */
block: { block: {
type: Boolean, type: Boolean,
default: false default: false,
}, },
/** 控件尺寸 */ /** 控件尺寸 */
size: { size: {
type: String as PropType<"small" | "default" | "large"> type: String as PropType<'small' | 'default' | 'large'>,
}, },
/** 是否全局禁用,默认 `false` */ /** 是否全局禁用,默认 `false` */
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false default: false,
}, },
/** 当内容发生变化时,设置 `resize` 可使其自适应容器位置 */ /** 当内容发生变化时,设置 `resize` 可使其自适应容器位置 */
resize: { resize: {
type: Boolean, type: Boolean,
default: false default: false,
} },
}; };
export default defineComponent({ export default defineComponent({
name: "ReSegmented", name: 'ReSegmented',
props, props,
emits: ["change", "update:modelValue"], emits: ['change', 'update:modelValue'],
setup(props, { emit }) { setup(props, { emit }) {
const width = ref(0); const width = ref(0);
const translateX = ref(0); const translateX = ref(0);
const { isDark } = useDark(); const { isDark } = useDark();
const initStatus = ref(false); const initStatus = ref(false);
const curMouseActive = ref(-1); const curMouseActive = ref(-1);
const segmentedItembg = ref(""); const segmentedItembg = ref('');
const instance = getCurrentInstance()!; const instance = getCurrentInstance()!;
const curIndex = isNumber(props.modelValue) const curIndex = isNumber(props.modelValue) ? toRef(props, 'modelValue') : ref(0);
? toRef(props, "modelValue")
: ref(0);
function handleChange({ option, index }, event: Event) { function handleChange({ option, index }, event: Event) {
if (props.disabled || option.disabled) return; if (props.disabled || option.disabled) return;
event.preventDefault(); event.preventDefault();
isNumber(props.modelValue) isNumber(props.modelValue) ? emit('update:modelValue', index) : (curIndex.value = index);
? emit("update:modelValue", index) segmentedItembg.value = '';
: (curIndex.value = index); emit('change', { index, option });
segmentedItembg.value = ""; }
emit("change", { index, option });
}
function handleMouseenter({ option, index }, event: Event) { function handleMouseenter({ option, index }, event: Event) {
if (props.disabled) return; if (props.disabled) return;
event.preventDefault(); event.preventDefault();
curMouseActive.value = index; curMouseActive.value = index;
if (option.disabled || curIndex.value === index) { if (option.disabled || curIndex.value === index) {
segmentedItembg.value = ""; segmentedItembg.value = '';
} else { } else {
segmentedItembg.value = isDark.value segmentedItembg.value = isDark.value ? '#1f1f1f' : 'rgba(0, 0, 0, 0.06)';
? "#1f1f1f" }
: "rgba(0, 0, 0, 0.06)"; }
}
}
function handleMouseleave(_, event: Event) { function handleMouseleave(_, event: Event) {
if (props.disabled) return; if (props.disabled) return;
event.preventDefault(); event.preventDefault();
curMouseActive.value = -1; curMouseActive.value = -1;
} }
function handleInit(index = curIndex.value) { function handleInit(index = curIndex.value) {
nextTick(() => { nextTick(() => {
const curLabelRef = instance?.proxy?.$refs[`labelRef${index}`] as ElRef; const curLabelRef = instance?.proxy?.$refs[`labelRef${index}`] as ElRef;
if (!curLabelRef) return; if (!curLabelRef) return;
width.value = curLabelRef.clientWidth; width.value = curLabelRef.clientWidth;
translateX.value = curLabelRef.offsetLeft; translateX.value = curLabelRef.offsetLeft;
initStatus.value = true; initStatus.value = true;
}); });
} }
function handleResizeInit() { function handleResizeInit() {
useResizeObserver(".pure-segmented", () => { useResizeObserver('.pure-segmented', () => {
nextTick(() => { nextTick(() => {
handleInit(curIndex.value); handleInit(curIndex.value);
}); });
}); });
} }
(props.block || props.resize) && handleResizeInit(); (props.block || props.resize) && handleResizeInit();
watch( watch(
() => curIndex.value, () => curIndex.value,
index => { index => {
nextTick(() => { nextTick(() => {
handleInit(index); handleInit(index);
}); });
}, },
{ {
immediate: true immediate: true,
} },
); );
watch(() => props.size, handleResizeInit, { watch(() => props.size, handleResizeInit, {
immediate: true immediate: true,
}); });
const rendLabel = () => { const rendLabel = () => {
return props.options.map((option, index) => { return props.options.map((option, index) => {
return ( return (
<label <label
ref={`labelRef${index}`} ref={`labelRef${index}`}
class={[ class={['pure-segmented-item', (props.disabled || option?.disabled) && 'pure-segmented-item-disabled']}
"pure-segmented-item", style={{
(props.disabled || option?.disabled) && background: curMouseActive.value === index ? segmentedItembg.value : '',
"pure-segmented-item-disabled" color: props.disabled ? null : !option.disabled && (curIndex.value === index || curMouseActive.value === index) ? (isDark.value ? 'rgba(255, 255, 255, 0.85)' : 'rgba(0,0,0,.88)') : '',
]} }}
style={{ onMouseenter={event => handleMouseenter({ option, index }, event)}
background: onMouseleave={event => handleMouseleave({ option, index }, event)}
curMouseActive.value === index ? segmentedItembg.value : "", onClick={event => handleChange({ option, index }, event)}
color: props.disabled >
? null <input type='radio' name='segmented' />
: !option.disabled && <div
(curIndex.value === index || curMouseActive.value === index) class='pure-segmented-item-label'
? isDark.value v-tippy={{
? "rgba(255, 255, 255, 0.85)" content: option?.tip,
: "rgba(0,0,0,.88)" zIndex: 41000,
: "" }}
}} >
onMouseenter={event => handleMouseenter({ option, index }, event)} {option.icon && !isFunction(option.label) ? (
onMouseleave={event => handleMouseleave({ option, index }, event)} <span class='pure-segmented-item-icon' style={{ marginRight: option.label ? '6px' : 0 }}>
onClick={event => handleChange({ option, index }, event)} {h(
> useRenderIcon(option.icon, {
<input type="radio" name="segmented" /> ...option?.iconAttrs,
<div }),
class="pure-segmented-item-label" )}
v-tippy={{ </span>
content: option?.tip, ) : null}
zIndex: 41000 {option.label ? isFunction(option.label) ? h(option.label) : <span>{option.label}</span> : null}
}} </div>
> </label>
{option.icon && !isFunction(option.label) ? ( );
<span });
class="pure-segmented-item-icon" };
style={{ marginRight: option.label ? "6px" : 0 }}
>
{h(
useRenderIcon(option.icon, {
...option?.iconAttrs
})
)}
</span>
) : null}
{option.label ? (
isFunction(option.label) ? (
h(option.label)
) : (
<span>{option.label}</span>
)
) : null}
</div>
</label>
);
});
};
return () => ( return () => (
<div <div
class={{ class={{
"pure-segmented": true, 'pure-segmented': true,
"pure-segmented-block": props.block, 'pure-segmented-block': props.block,
"pure-segmented--large": props.size === "large", 'pure-segmented--large': props.size === 'large',
"pure-segmented--small": props.size === "small" 'pure-segmented--small': props.size === 'small',
}} }}
> >
<div class="pure-segmented-group"> <div class='pure-segmented-group'>
<div <div
class="pure-segmented-item-selected" class='pure-segmented-item-selected'
style={{ style={{
width: `${width.value}px`, width: `${width.value}px`,
transform: `translateX(${translateX.value}px)`, transform: `translateX(${translateX.value}px)`,
display: initStatus.value ? "block" : "none" display: initStatus.value ? 'block' : 'none',
}} }}
></div> ></div>
{rendLabel()} {rendLabel()}
</div> </div>
</div> </div>
); );
} },
}); });

View File

@ -169,7 +169,7 @@ export default defineComponent({
}; };
const isFixedColumn = (label: string) => { const isFixedColumn = (label: string) => {
return !!dynamicColumns.value.filter(item => $t(item.label) === $t(label))[0].fixed; return !!dynamicColumns.value.filter(item => item.label === label)[0].fixed;
}; };
const rendTippyProps = (content: string) => ({ const rendTippyProps = (content: string) => ({
@ -213,7 +213,7 @@ export default defineComponent({
<div class={[topClass.value]}> <div class={[topClass.value]}>
<el-checkbox class='!-mr-1' label='列展示' v-model={checkAll.value} indeterminate={isIndeterminate.value} onChange={value => handleCheckAllChange(value)} /> <el-checkbox class='!-mr-1' label='列展示' v-model={checkAll.value} indeterminate={isIndeterminate.value} onChange={value => handleCheckAllChange(value)} />
<el-button type='primary' link onClick={() => onReset()}> <el-button type='primary' link onClick={() => onReset()}>
{$t('buttons.reset')}
</el-button> </el-button>
</div> </div>
@ -226,8 +226,8 @@ export default defineComponent({
<div class='flex items-center'> <div class='flex items-center'>
<DragIcon class={['drag-btn w-[16px] mr-2', isFixedColumn(item) ? '!cursor-no-drop' : '!cursor-grab']} onMouseenter={(event: { preventDefault: () => void }) => rowDrop(event)} /> <DragIcon class={['drag-btn w-[16px] mr-2', isFixedColumn(item) ? '!cursor-no-drop' : '!cursor-grab']} onMouseenter={(event: { preventDefault: () => void }) => rowDrop(event)} />
<el-checkbox key={index} label={item} value={item} onChange={value => handleCheckColumnListChange(value, item)}> <el-checkbox key={index} label={item} value={item} onChange={value => handleCheckColumnListChange(value, item)}>
<span title={$t(item)} class='inline-block w-[120px] truncate hover:text-text_color_primary'> <span title={item} class='inline-block w-[120px] truncate hover:text-text_color_primary'>
{$t(item)} {item}
</span> </span>
</el-checkbox> </el-checkbox>
</div> </div>

View File

@ -1,70 +1,71 @@
<script setup lang="ts"> <script lang="ts" setup>
import { useRouter } from "vue-router"; import { useRouter } from 'vue-router';
import noAccess from "@/assets/status/403.svg?component"; import noAccess from '@/assets/status/403.svg?component';
import { $t } from '@/plugins/i18n';
defineOptions({ defineOptions({
name: "403" name: '403',
}); });
const router = useRouter(); const router = useRouter();
</script> </script>
<template> <template>
<div class="flex justify-center items-center h-[640px]"> <div class="flex justify-center items-center h-[640px]">
<noAccess /> <noAccess />
<div class="ml-12"> <div class="ml-12">
<p <p
v-motion v-motion
class="font-medium text-4xl mb-4 dark:text-white" :enter="{
:initial="{ opacity: 1,
opacity: 0, y: 0,
y: 100 transition: {
}" delay: 80,
:enter="{ },
opacity: 1, }"
y: 0, :initial="{
transition: { opacity: 0,
delay: 80 y: 100,
} }"
}" class="font-medium text-4xl mb-4 dark:text-white"
> >
403 403
</p> </p>
<p <p
v-motion v-motion
class="mb-4 text-gray-500" :enter="{
:initial="{ opacity: 1,
opacity: 0, y: 0,
y: 100 transition: {
}" delay: 120,
:enter="{ },
opacity: 1, }"
y: 0, :initial="{
transition: { opacity: 0,
delay: 120 y: 100,
} }"
}" class="mb-4 text-gray-500"
> >
抱歉你无权访问该页面 {{ $t('sorry_no_access') }}
</p> </p>
<el-button <el-button
v-motion v-motion
type="primary" :enter="{
:initial="{ opacity: 1,
opacity: 0, y: 0,
y: 100 transition: {
}" delay: 160,
:enter="{ },
opacity: 1, }"
y: 0, :initial="{
transition: { opacity: 0,
delay: 160 y: 100,
} }"
}" type="primary"
@click="router.push('/')" @click="router.push('/')"
> >
返回首页 {{ $t('return_to_homepage') }}
</el-button> </el-button>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,70 +1,71 @@
<script setup lang="ts"> <script lang="ts" setup>
import { useRouter } from "vue-router"; import { useRouter } from 'vue-router';
import noExist from "@/assets/status/404.svg?component"; import noExist from '@/assets/status/404.svg?component';
import { $t } from '@/plugins/i18n';
defineOptions({ defineOptions({
name: "404" name: '404',
}); });
const router = useRouter(); const router = useRouter();
</script> </script>
<template> <template>
<div class="flex justify-center items-center h-[640px]"> <div class="flex justify-center items-center h-[640px]">
<noExist /> <noExist />
<div class="ml-12"> <div class="ml-12">
<p <p
v-motion v-motion
class="font-medium text-4xl mb-4 dark:text-white" :enter="{
:initial="{ opacity: 1,
opacity: 0, y: 0,
y: 100 transition: {
}" delay: 80,
:enter="{ },
opacity: 1, }"
y: 0, :initial="{
transition: { opacity: 0,
delay: 80 y: 100,
} }"
}" class="font-medium text-4xl mb-4 dark:text-white"
> >
404 404
</p> </p>
<p <p
v-motion v-motion
class="mb-4 text-gray-500" :enter="{
:initial="{ opacity: 1,
opacity: 0, y: 0,
y: 100 transition: {
}" delay: 120,
:enter="{ },
opacity: 1, }"
y: 0, :initial="{
transition: { opacity: 0,
delay: 120 y: 100,
} }"
}" class="mb-4 text-gray-500"
> >
抱歉你访问的页面不存在 {{ $t('sorry_page_not_found') }}
</p> </p>
<el-button <el-button
v-motion v-motion
type="primary" :enter="{
:initial="{ opacity: 1,
opacity: 0, y: 0,
y: 100 transition: {
}" delay: 160,
:enter="{ },
opacity: 1, }"
y: 0, :initial="{
transition: { opacity: 0,
delay: 160 y: 100,
} }"
}" type="primary"
@click="router.push('/')" @click="router.push('/')"
> >
返回首页 {{ $t('return_to_homepage') }}
</el-button> </el-button>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,70 +1,71 @@
<script setup lang="ts"> <script lang="ts" setup>
import { useRouter } from "vue-router"; import { useRouter } from 'vue-router';
import noServer from "@/assets/status/500.svg?component"; import noServer from '@/assets/status/500.svg?component';
import { $t } from '@/plugins/i18n';
defineOptions({ defineOptions({
name: "500" name: '500',
}); });
const router = useRouter(); const router = useRouter();
</script> </script>
<template> <template>
<div class="flex justify-center items-center h-[640px]"> <div class="flex justify-center items-center h-[640px]">
<noServer /> <noServer />
<div class="ml-12"> <div class="ml-12">
<p <p
v-motion v-motion
class="font-medium text-4xl mb-4 dark:text-white" :enter="{
:initial="{ opacity: 1,
opacity: 0, y: 0,
y: 100 transition: {
}" delay: 80,
:enter="{ },
opacity: 1, }"
y: 0, :initial="{
transition: { opacity: 0,
delay: 80 y: 100,
} }"
}" class="font-medium text-4xl mb-4 dark:text-white"
> >
500 500
</p> </p>
<p <p
v-motion v-motion
class="mb-4 text-gray-500" :enter="{
:initial="{ opacity: 1,
opacity: 0, y: 0,
y: 100 transition: {
}" delay: 120,
:enter="{ },
opacity: 1, }"
y: 0, :initial="{
transition: { opacity: 0,
delay: 120 y: 100,
} }"
}" class="mb-4 text-gray-500"
> >
抱歉服务器出错了 {{ $t('sorry_server_error') }}
</p> </p>
<el-button <el-button
v-motion v-motion
type="primary" :enter="{
:initial="{ opacity: 1,
opacity: 0, y: 0,
y: 100 transition: {
}" delay: 160,
:enter="{ },
opacity: 1, }"
y: 0, :initial="{
transition: { opacity: 0,
delay: 160 y: 100,
} }"
}" type="primary"
@click="router.push('/')" @click="router.push('/')"
> >
返回首页 {{ $t('return_to_homepage') }}
</el-button> </el-button>
</div> </div>
</div> </div>
</template> </template>

View File

@ -209,8 +209,8 @@ const markOptions = computed<Array<OptionsType>>(() => {
value: 'card', value: 'card',
}, },
{ {
label: 'panel.pureTagsStyleChrome', label: t('panel.pureTagsStyleChrome'),
tip: 'panel.pureTagsStyleChromeTip', tip: t('panel.pureTagsStyleChrome'),
value: 'chrome', value: 'chrome',
}, },
]; ];

View File

@ -545,7 +545,7 @@ onBeforeUnmount(() => {
<TagChrome /> <TagChrome />
</div> </div>
<span class="tag-title"> <span class="tag-title">
{{ transformI18n(item.meta.title) }} {{ $t(item.meta.title) }}
</span> </span>
<span v-if="isFixedTag(item) ? false : index !== 0" class="chrome-close-btn" @click.stop="deleteMenu(item)"> <span v-if="isFixedTag(item) ? false : index !== 0" class="chrome-close-btn" @click.stop="deleteMenu(item)">
<IconifyIconOffline :icon="Close" /> <IconifyIconOffline :icon="Close" />

View File

@ -1,68 +1,64 @@
import { useI18n } from "@/plugins/i18n"; import { useI18n } from '@/plugins/i18n';
import { setupStore } from "@/store"; import { setupStore } from '@/store';
import { MotionPlugin } from "@vueuse/motion"; import { MotionPlugin } from '@vueuse/motion';
import App from "./App.vue"; import App from './App.vue';
import { getPlatformConfig } from "./config"; import { getPlatformConfig } from './config';
import router from "./router"; import router from './router';
// import { useEcharts } from "@/plugins/echarts"; // import { useEcharts } from "@/plugins/echarts";
import { useElementPlus } from "@/plugins/elementPlus"; import { useElementPlus } from '@/plugins/elementPlus';
import { injectResponsiveStorage } from "@/utils/responsive"; import { injectResponsiveStorage } from '@/utils/responsive';
import { createApp, type Directive } from "vue"; import { createApp, type Directive } from 'vue';
import Table from "@pureadmin/table"; import Table from '@pureadmin/table';
// import PureDescriptions from "@pureadmin/descriptions"; // import PureDescriptions from "@pureadmin/descriptions";
// 引入重置样式 // 引入重置样式
import "./style/reset.scss"; import './style/reset.scss';
// 导入公共样式 // 导入公共样式
import "./style/index.scss"; import './style/index.scss';
// 一定要在main.ts中导入tailwind.css防止vite每次hmr都会请求src/style/index.scss整体css文件导致热更新慢的问题 // 一定要在main.ts中导入tailwind.css防止vite每次hmr都会请求src/style/index.scss整体css文件导致热更新慢的问题
import "element-plus/dist/index.css"; import 'element-plus/dist/index.css';
import "./style/tailwind.css"; import './style/tailwind.css';
// 导入字体图标 // 导入字体图标
import "./assets/iconfont/iconfont.css"; import './assets/iconfont/iconfont.css';
import "./assets/iconfont/iconfont.js"; import './assets/iconfont/iconfont.js';
// 自定义指令 // 自定义指令
import * as directives from "@/directives"; import * as directives from '@/directives';
// 全局注册@iconify/vue图标库 // 全局注册@iconify/vue图标库
import { import { FontIcon, IconifyIconOffline, IconifyIconOnline } from './components/CommonIcon';
FontIcon,
IconifyIconOffline,
IconifyIconOnline
} from "./components/CommonIcon";
// 全局注册按钮级别权限组件 // 全局注册按钮级别权限组件
import { Auth } from "@/components/Auth"; import { Auth } from '@/components/Auth';
import { Perms } from "@/components/Perms"; import { Perms } from '@/components/Perms';
// 全局注册vue-tippy // 全局注册vue-tippy
import "tippy.js/dist/tippy.css"; import 'tippy.js/dist/tippy.css';
import "tippy.js/themes/light.css"; import 'tippy.js/themes/light.css';
import VueTippy from "vue-tippy"; import VueTippy from 'vue-tippy';
import { useEcharts } from "@/plugins/echarts"; import { useEcharts } from '@/plugins/echarts';
const app = createApp(App); const app = createApp(App);
Object.keys(directives).forEach(key => { Object.keys(directives).forEach(key => {
app.directive(key, (directives as { [key: string]: Directive })[key]); app.directive(key, (directives as { [key: string]: Directive })[key]);
}); });
app.component("IconifyIconOffline", IconifyIconOffline); app.component('IconifyIconOffline', IconifyIconOffline);
app.component("IconifyIconOnline", IconifyIconOnline); app.component('IconifyIconOnline', IconifyIconOnline);
app.component("FontIcon", FontIcon); app.component('FontIcon', FontIcon);
app.component("Auth", Auth); app.component('Auth', Auth);
app.component("Perms", Perms); app.component('Perms', Perms);
app.use(VueTippy); app.use(VueTippy);
getPlatformConfig(app).then(async config => { getPlatformConfig(app).then(async config => {
setupStore(app); setupStore(app);
app.use(router); app.use(router);
await router.isReady(); await router.isReady();
injectResponsiveStorage(app, config); injectResponsiveStorage(app, config);
app app
.use(MotionPlugin) .use(MotionPlugin)
.use(useI18n) .use(useI18n)
.use(useElementPlus) .use(useElementPlus)
.use(Table) .use(Table)
// .use(PureDescriptions) // .use(PureDescriptions)
.use(useEcharts); .use(useEcharts);
app.mount("#app"); app.mount('#app');
}); });

View File

@ -1,24 +1,24 @@
// 多组件库的国际化和本地项目国际化兼容 // 多组件库的国际化和本地项目国际化兼容
import { createI18n } from "vue-i18n"; import { createI18n } from 'vue-i18n';
import type { App } from "vue"; import type { App } from 'vue';
// ? 从本地存储中获取数据 // ? 从本地存储中获取数据
const languageData = localStorage.getItem("i18nStore"); const languageData = localStorage.getItem('i18nStore');
// 配置多语言 // 配置多语言
export const i18n = createI18n({ export const i18n = createI18n({
// 如果要支持 compositionAPI此项必须设置为 false // 如果要支持 compositionAPI此项必须设置为 false
legacy: false, legacy: false,
// locale: 'zh', // locale: 'zh',
fallbackLocale: "en", fallbackLocale: 'en',
// ? 全局注册$t方法 // ? 全局注册$t方法
globalInjection: true, globalInjection: true,
// 本地内容存在时,首次加载如果本地存储没有多语言需要再刷新 // 本地内容存在时,首次加载如果本地存储没有多语言需要再刷新
messages: languageData ? JSON.parse(languageData).i18n : {} messages: languageData ? JSON.parse(languageData).i18n : {},
}); });
export const $t: any = (i18n.global as any).t as any; export const $t: any = (i18n.global as any).t as any;
export function useI18n(app: App) { export function useI18n(app: App) {
app.use(i18n); app.use(i18n);
} }

View File

@ -1,7 +1,7 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { fetchAddI18n, fetchDeleteI18n, fetchGetI18n, fetchGetI18nList, fetchUpdateI18n } from '@/api/v1/i18n'; import { fetchAddI18n, fetchDeleteI18n, fetchGetI18n, fetchGetI18nList, fetchUpdateI18n } from '@/api/v1/i18n';
import { pageSizes } from '@/enums/baseConstant'; import { pageSizes } from '@/enums/baseConstant';
import { message, storeMessage } from '@/utils/message'; import { storeMessage } from '@/utils/message';
export const userI18nStore = defineStore('i18nStore', { export const userI18nStore = defineStore('i18nStore', {
persist: true, persist: true,
@ -12,7 +12,7 @@ export const userI18nStore = defineStore('i18nStore', {
// 多语言列表 // 多语言列表
datalist: [], datalist: [],
// 查询表单 // 查询表单
form: { keyName: undefined, translation: undefined }, form: { keyName: '', translation: '', typeName: '' },
isAddShown: false, isAddShown: false,
// ? 分页查询结果 // ? 分页查询结果
pagination: { pagination: {
@ -50,6 +50,9 @@ export const userI18nStore = defineStore('i18nStore', {
*/ */
async getI18nMangeList() { async getI18nMangeList() {
const data = { ...this.pagination, ...this.form }; const data = { ...this.pagination, ...this.form };
delete data.pageSizes;
delete data.total;
delete data.background;
const result = await fetchGetI18nList(data); const result = await fetchGetI18nList(data);
// 如果成功赋值内容 // 如果成功赋值内容
@ -61,7 +64,6 @@ export const userI18nStore = defineStore('i18nStore', {
return true; return true;
} }
message(result.message, { type: 'error' });
return false; return false;
}, },

View File

@ -95,6 +95,7 @@ export const closeAllMessage = (): void => ElMessage.closeAll();
*/ */
export const storeMessage = (result: BaseResult<any>) => { export const storeMessage = (result: BaseResult<any>) => {
if (result.code !== 200) { if (result.code !== 200) {
message(result.message, { type: 'warning' });
return false; return false;
} }
message(result.message, { type: 'success' }); message(result.message, { type: 'success' });

View File

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { onMounted, ref } from 'vue';
import { FormInstance } from 'element-plus'; import { FormInstance } from 'element-plus';
import { rules } from '@/views/i18n/i18n-setting/utils/columns'; import { rules } from '@/views/i18n/i18n-setting/utils/columns';
import { FormProps } from '@/views/i18n/i18n-setting/utils/types'; import { FormProps } from '@/views/i18n/i18n-setting/utils/types';
@ -9,7 +9,7 @@ const props = withDefaults(defineProps<FormProps>(), {
formInline: () => ({ formInline: () => ({
keyName: '', keyName: '',
translation: '', translation: '',
typeId: '', typeName: '',
}), }),
}); });
@ -17,14 +17,17 @@ const ruleFormRef = ref<FormInstance>();
const form = ref(props.formInline); const form = ref(props.formInline);
const i18nTypeStore = userI18nTypeStore(); const i18nTypeStore = userI18nTypeStore();
onMounted(() => {
i18nTypeStore.getI18nTypeList();
});
defineExpose({ ruleFormRef }); defineExpose({ ruleFormRef });
</script> </script>
<template> <template>
<el-form ref="ruleFormRef" :model="form" :rules="rules" isDefault-icon label-position="left" label-width="135px"> <el-form ref="ruleFormRef" :model="form" :rules="rules" isDefault-icon label-position="left" label-width="135px">
<el-form-item label="选择添加语言分类" prop="typeId"> <el-form-item label="选择添加语言分类" prop="typeName">
<el-select v-model="form.typeId" filterable placeholder="选择添加语言分类"> <el-select v-model="form.typeName" filterable placeholder="选择添加语言分类">
<el-option v-for="item in i18nTypeStore.datalist" :key="item.id" :label="item.typeName" :value="item.id" /> <el-option v-for="item in i18nTypeStore.datalist" :key="item.typeName" :label="item.typeName" :value="item.typeName" />
</el-select> </el-select>
</el-form-item> </el-form-item>

View File

@ -10,17 +10,17 @@ import PureTableBar from '@/components/TableBar/src/bar';
import PureTable from '@pureadmin/table'; import PureTable from '@pureadmin/table';
import { columns } from '@/views/i18n/i18n-setting/utils/columns'; import { columns } from '@/views/i18n/i18n-setting/utils/columns';
import Refresh from '@iconify-icons/ep/refresh'; import Refresh from '@iconify-icons/ep/refresh';
import { resetForm } from '@/views/menu/utils/hook'; import { $t } from '@/plugins/i18n';
const tableRef = ref(); const tableRef = ref();
const formRef = ref(); const pageFormRef = ref();
const i18nStore = userI18nStore(); const i18nStore = userI18nStore();
/** /**
* 重置表单 * 重置表单
* @param formEl * @param formEl
*/ */
const resetForm = async formEl => { const resetForm = async (formEl: any) => {
if (!formEl) return; if (!formEl) return;
formEl.resetFields(); formEl.resetFields();
await onSearch(); await onSearch();
@ -58,22 +58,25 @@ onMounted(() => {
<template> <template>
<div class="main"> <div class="main">
<el-form ref="formRef" :inline="true" :model="i18nStore.form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto"> <el-form ref="pageFormRef" :inline="true" :model="i18nStore.form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item label="多语言key" prop="keyName"> <el-form-item :label="$t('i18n.keyName')" prop="keyName">
<el-input v-model="i18nStore.form.keyName" class="!w-[180px]" clearable placeholder="输入多语言key" /> <el-input v-model="i18nStore.form.keyName" :placeholder="`${$t('input')}${$t('i18n.keyName')}`" class="!w-[180px]" clearable />
</el-form-item> </el-form-item>
<el-form-item label="多语言翻译" prop="translation"> <el-form-item :label="$t('i18n.translation')" prop="translation">
<el-input v-model="i18nStore.form.translation" class="!w-[180px]" clearable placeholder="输入多语言翻译" /> <el-input v-model="i18nStore.form.translation" :placeholder="`${$t('input')}${$t('i18n.translation')}`" class="!w-[180px]" clearable />
</el-form-item>
<el-form-item :label="$t('i18n.typeName')" prop="typeName">
<el-input v-model="i18nStore.form.typeName" :placeholder="`${$t('input')}${$t('i18n.typeName')}`" class="!w-[180px]" clearable />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="i18nStore.loading" type="primary" @click="onSearch"> 搜索 </el-button> <el-button :icon="useRenderIcon('ri:search-line')" :loading="i18nStore.loading" type="primary" @click="onSearch"> {{ $t('search') }} </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> 重置</el-button> <el-button :icon="useRenderIcon(Refresh)" @click="resetForm(pageFormRef)"> {{ $t('buttons.reset') }}</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<PureTableBar :columns="columns" :tableRef="tableRef?.getTableRef()" title="多语言管理" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch"> <PureTableBar :columns="columns" :tableRef="tableRef?.getTableRef()" :title="$t('multilingual_management')" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch">
<template #buttons> <template #buttons>
<el-button :icon="useRenderIcon(AddFill)" type="primary" @click="onAdd"> 添加多语言</el-button> <el-button :icon="useRenderIcon(AddFill)" type="primary" @click="onAdd"> {{ $t('add_multilingual') }} </el-button>
</template> </template>
<template v-slot="{ size, dynamicColumns }"> <template v-slot="{ size, dynamicColumns }">
<pure-table <pure-table
@ -87,6 +90,8 @@ onMounted(() => {
:size="size" :size="size"
adaptive adaptive
align-whole="center" align-whole="center"
border
highlight-current-row
row-key="id" row-key="id"
showOverflowTooltip showOverflowTooltip
table-layout="auto" table-layout="auto"
@ -95,11 +100,13 @@ onMounted(() => {
@page-current-change="onCurrentPageChange" @page-current-change="onCurrentPageChange"
> >
<template #operation="{ row }"> <template #operation="{ row }">
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onUpdate(row)"> 修改 </el-button> <el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onUpdate(row)"> {{ $t('modify') }} </el-button>
<el-button v-show="row.menuType !== 3" :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary"> 新增 </el-button> <el-button v-show="row.menuType !== 3" :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary"> {{ $t('add_new') }} </el-button>
<el-popconfirm :title="`是否确认删除 ${row.translation}数据`" @confirm="onDelete(row)"> <el-popconfirm :title="`${$t('confirm_delete')} ${row.translation}`" @confirm="onDelete(row)">
<template #reference> <template #reference>
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary"> 删除 </el-button> <el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary">
{{ $t('delete') }}
</el-button>
</template> </template>
</el-popconfirm> </el-popconfirm>
</template> </template>

View File

@ -1,22 +1,23 @@
// 多语言表格列字段 // 多语言表格列字段
import { reactive } from 'vue'; import { reactive } from 'vue';
import type { FormRules } from 'element-plus'; import type { FormRules } from 'element-plus';
import { $t } from '@/plugins/i18n';
export const columns: TableColumnList = [ export const columns: TableColumnList = [
{ label: 'id', prop: 'id' }, { label: $t('id'), prop: 'id' },
{ label: 'i18n_keyName', prop: 'keyName' }, { label: $t('i18n.keyName'), prop: 'keyName' },
{ label: 'i18n_translation', prop: 'translation' }, { label: $t('i18n.translation'), prop: 'translation' },
{ label: 'i18n_typeId', prop: 'typeId' }, { label: $t('i18n.typeName'), prop: 'typeName' },
{ label: 'updateTime', prop: 'updateTime' }, { label: $t('table.updateTime'), prop: 'updateTime' },
{ label: 'createTime', prop: 'createTime' }, { label: $t('table.createTime'), prop: 'createTime' },
{ label: 'createUser', prop: 'createUser' }, { label: $t('table.createUser'), prop: 'createUser' },
{ label: 'updateUser', prop: 'updateUser' }, { label: $t('table.updateUser'), prop: 'updateUser' },
{ label: 'operation', fixed: 'right', width: 210, slot: 'operation' }, { label: $t('table.operation'), fixed: 'right', width: 210, slot: 'operation' },
]; ];
// 添加多语言表单规则 // 添加多语言表单规则
export const rules = reactive<FormRules>({ export const rules = reactive<FormRules>({
keyName: [{ required: true, message: '输入多语言key', trigger: 'blur' }], keyName: [{ required: true, message: `${$t('input')}${$t('i18n.keyName')}`, trigger: 'blur' }],
translation: [{ required: true, message: '输入多语言翻译', trigger: 'blur' }], translation: [{ required: true, message: `${$t('input')}${$t('i18n.translation')}`, trigger: 'blur' }],
typeId: [{ required: true, message: '选择添加语言分类', trigger: 'blur' }], typeName: [{ required: true, message: `${$t('input')}${$t('i18n.typeName')}`, trigger: 'blur' }],
}); });

View File

@ -1,23 +1,15 @@
import { h, reactive, ref } from 'vue'; import { h, ref } from 'vue';
import { userI18nStore } from '@/store/i18n/i18n'; import { userI18nStore } from '@/store/i18n/i18n';
import { messageBox } from '@/utils/message'; import { messageBox } from '@/utils/message';
import { addDialog } from '@/components/BaseDialog/index'; import { addDialog, closeDialog } from '@/components/BaseDialog/index';
import { deviceDetection } from '@pureadmin/utils'; import { deviceDetection } from '@pureadmin/utils';
import I18nDialog from '@/views/i18n/i18n-setting/i18n-dialog.vue'; import I18nDialog from '@/views/i18n/i18n-setting/i18n-dialog.vue';
import type { FormProps } from '@/views/i18n/i18n-setting/utils/types'; import type { FormProps } from '@/views/i18n/i18n-setting/utils/types';
import { $t } from '@/plugins/i18n';
export const formRef = ref(); export const formRef = ref();
const i18nStore = userI18nStore(); const i18nStore = userI18nStore();
const ids = ref<string[]>([]); export const ids = ref<string[]>([]);
// 更新表单数据
export const updateForm = reactive({
id: '',
languageId: '',
keyName: '',
translate: '',
parentId: '',
});
/** /**
* * * *
@ -34,25 +26,56 @@ export const onSearch = async () => {
*/ */
export const onAdd = () => { export const onAdd = () => {
addDialog({ addDialog({
title: `添加多语言`, title: `${$t('add_multilingual')}`,
width: '30%', width: '30%',
props: { formInline: { keyName: '', translation: '', typeId: '' } }, props: { formInline: { keyName: '', translation: '', typeName: '' } },
draggable: true, draggable: true,
fullscreen: deviceDetection(), fullscreen: deviceDetection(),
fullscreenIcon: true, fullscreenIcon: true,
closeOnClickModal: false, closeOnClickModal: false,
contentRenderer: () => h(I18nDialog, { ref: formRef }), contentRenderer: () => h(I18nDialog, { ref: formRef }),
beforeSure: (done, { options }) => { footerButtons: [
const form = options.props.formInline as FormProps; {
formRef.value.ruleFormRef.validate(async (valid: any) => { label: '取消',
if (!valid) return; text: true,
bg: true,
btnClick: ({ dialog: { options, index } }) => {
closeDialog(options, index);
},
},
{
label: $t('buttons.pureConfirm'),
type: 'primary',
text: true,
bg: true,
btnClick: ({ dialog: { options, index } }) => {
const form = options.props.formInline as FormProps;
formRef.value.ruleFormRef.validate(async (valid: any) => {
if (!valid) return;
const result = await i18nStore.addI18n(form); const result = await i18nStore.addI18n(form);
if (!result) return; if (!result) return;
done(); closeDialog(options, index);
await onSearch(); await onSearch();
}); });
}, },
},
{
label: '继续添加',
type: 'success',
text: true,
bg: true,
btnClick: ({ dialog: { options } }) => {
const form = options.props.formInline as FormProps;
formRef.value.ruleFormRef.validate(async (valid: any) => {
if (!valid) return;
const result = await i18nStore.addI18n(form);
if (!result) return;
await onSearch();
});
},
},
],
}); });
}; };

View File

@ -2,7 +2,7 @@
export interface FormItemProps { export interface FormItemProps {
keyName: string; keyName: string;
translation: string; translation: string;
typeId: string; typeName: string;
} }
// 添加或修改表单Props // 添加或修改表单Props

View File

@ -10,7 +10,7 @@ import { onAdd, onDelete, onSearch, onUpdate } from '@/views/i18n/i18n-type-sett
import Delete from '@iconify-icons/ep/delete'; import Delete from '@iconify-icons/ep/delete';
import EditPen from '@iconify-icons/ep/edit-pen'; import EditPen from '@iconify-icons/ep/edit-pen';
import TableIsDefaultTag from '@/components/TableBar/src/TableIsDefaultTag.vue'; import TableIsDefaultTag from '@/components/TableBar/src/TableIsDefaultTag.vue';
import { resetForm } from '@/views/menu/utils/hook'; import { resetForm } from '@/views/system/menu/utils/hook';
import Refresh from '@iconify-icons/ep/refresh'; import Refresh from '@iconify-icons/ep/refresh';
const tableRef = ref(); const tableRef = ref();

View File

@ -1,8 +1,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import ReCol from '@/components/MyCol'; import ReCol from '@/components/MyCol';
import { formRules } from './utils/rule'; import { formRules } from '@/views/system/menu/utils/rule';
import { FormProps } from './utils/types'; import { FormProps } from '@/views/system/menu/utils/types';
import { IconSelect } from '@/components/ReIcon'; import { IconSelect } from '@/components/ReIcon';
import Segmented from '@/components/ReSegmented'; import Segmented from '@/components/ReSegmented';
import ReAnimateSelector from '@/components/AnimateSelector'; import ReAnimateSelector from '@/components/AnimateSelector';

View File

@ -7,10 +7,10 @@ import Delete from '@iconify-icons/ep/delete';
import EditPen from '@iconify-icons/ep/edit-pen'; import EditPen from '@iconify-icons/ep/edit-pen';
import Refresh from '@iconify-icons/ep/refresh'; import Refresh from '@iconify-icons/ep/refresh';
import AddFill from '@iconify-icons/ri/add-circle-line'; import AddFill from '@iconify-icons/ri/add-circle-line';
import { dataList, handleDelete, loading, onSearch, openDialog, resetForm } from '@/views/menu/utils/hook'; import { dataList, handleDelete, loading, onSearch, openDialog, resetForm } from '@/views/system/menu/utils/hook';
import form from '@/views/role/form.vue'; import form from '@/views/role/form.vue';
import PureTable from '@pureadmin/table'; import PureTable from '@pureadmin/table';
import { columns } from '@/views/menu/utils/rule'; import { columns } from '@/views/system/menu/utils/rule';
defineOptions({ defineOptions({
name: 'SystemMenu', name: 'SystemMenu',

View File

@ -3,9 +3,9 @@ import { handleTree } from '@/utils/tree';
import { message } from '@/utils/message'; import { message } from '@/utils/message';
import { getMenuList } from '@/api/v1/system'; import { getMenuList } from '@/api/v1/system';
import { $t } from '@/plugins/i18n'; import { $t } from '@/plugins/i18n';
import { addDialog } from '@/components/BaseDialog'; import { addDialog } from '@/components/BaseDialog/index';
import { h, reactive, ref } from 'vue'; import { h, reactive, ref } from 'vue';
import type { FormItemProps } from '../utils/types'; import type { FormItemProps } from './types';
import { cloneDeep, deviceDetection, isAllEmpty } from '@pureadmin/utils'; import { cloneDeep, deviceDetection, isAllEmpty } from '@pureadmin/utils';
import { userRouterStore } from '@/store/modules/router'; import { userRouterStore } from '@/store/modules/router';

View File

@ -3,7 +3,7 @@ import type { FormRules } from 'element-plus';
import { useRenderIcon } from '@/components/ReIcon/src/hooks'; import { useRenderIcon } from '@/components/ReIcon/src/hooks';
import { $t } from '@/plugins/i18n'; import { $t } from '@/plugins/i18n';
import { isAllEmpty } from '@pureadmin/utils'; import { isAllEmpty } from '@pureadmin/utils';
import { getMenuType } from '@/views/menu/utils/hook'; import { getMenuType } from '@/views/system/menu/utils/hook';
export const columns: TableColumnList = [ export const columns: TableColumnList = [
{ {