feat:新增pure-admin前端
55
Yi.Pure.Vue3/src/App.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<el-config-provider :locale="currentLocale">
|
||||
<router-view />
|
||||
<ReDialog />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { checkVersion } from "version-rocket";
|
||||
import { ElConfigProvider } from "element-plus";
|
||||
import { ReDialog } from "@/components/ReDialog";
|
||||
import en from "element-plus/es/locale/lang/en";
|
||||
import zhCn from "element-plus/es/locale/lang/zh-cn";
|
||||
import plusEn from "plus-pro-components/es/locale/lang/en";
|
||||
import plusZhCn from "plus-pro-components/es/locale/lang/zh-cn";
|
||||
|
||||
export default defineComponent({
|
||||
name: "app",
|
||||
components: {
|
||||
[ElConfigProvider.name]: ElConfigProvider,
|
||||
ReDialog
|
||||
},
|
||||
computed: {
|
||||
currentLocale() {
|
||||
return this.$storage.locale?.locale === "zh"
|
||||
? { ...zhCn, ...plusZhCn }
|
||||
: { ...en, ...plusEn };
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
const { version, name: title } = __APP_INFO__.pkg;
|
||||
const { VITE_PUBLIC_PATH, MODE } = import.meta.env;
|
||||
// https://github.com/guMcrey/version-rocket/blob/main/README.zh-CN.md#api
|
||||
if (MODE === "production") {
|
||||
// 版本实时更新检测,只作用于线上环境
|
||||
checkVersion(
|
||||
// config
|
||||
{
|
||||
// 5分钟检测一次版本
|
||||
pollingTime: 300000,
|
||||
localPackageVersion: version,
|
||||
originVersionFileUrl: `${location.origin}${VITE_PUBLIC_PATH}version.json`
|
||||
},
|
||||
// options
|
||||
{
|
||||
title,
|
||||
description: "检测到新版本",
|
||||
buttonText: "立即更新"
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
14
Yi.Pure.Vue3/src/api/list.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { http } from "@/utils/http";
|
||||
|
||||
type Result = {
|
||||
success: boolean;
|
||||
data?: {
|
||||
/** 列表数据 */
|
||||
list: Array<any>;
|
||||
};
|
||||
};
|
||||
|
||||
/** 卡片列表 */
|
||||
export const getCardList = (data?: object) => {
|
||||
return http.request<Result>("post", "/get-card-list", { data });
|
||||
};
|
||||
25
Yi.Pure.Vue3/src/api/mock.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { http } from "@/utils/http";
|
||||
|
||||
type Result = {
|
||||
success: boolean;
|
||||
data: Array<any>;
|
||||
};
|
||||
|
||||
/** 地图数据 */
|
||||
export const mapJson = (params?: object) => {
|
||||
return http.request<Result>("get", "/get-map-info", { params });
|
||||
};
|
||||
|
||||
/** 文件上传 */
|
||||
export const formUpload = data => {
|
||||
return http.request<Result>(
|
||||
"post",
|
||||
"https://run.mocky.io/v3/3aa761d7-b0b3-4a03-96b3-6168d4f7467b",
|
||||
{ data },
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data"
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
10
Yi.Pure.Vue3/src/api/routes.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { http } from "@/utils/http";
|
||||
|
||||
type Result = {
|
||||
success: boolean;
|
||||
data: Array<any>;
|
||||
};
|
||||
|
||||
export const getAsyncRoutes = () => {
|
||||
return http.request<Result>("get", "/get-async-routes");
|
||||
};
|
||||
85
Yi.Pure.Vue3/src/api/system.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { http } from "@/utils/http";
|
||||
|
||||
type Result = {
|
||||
success: boolean;
|
||||
data?: Array<any>;
|
||||
};
|
||||
|
||||
type ResultTable = {
|
||||
success: boolean;
|
||||
data?: {
|
||||
/** 列表数据 */
|
||||
list: Array<any>;
|
||||
/** 总条目数 */
|
||||
total?: number;
|
||||
/** 每页显示条目个数 */
|
||||
pageSize?: number;
|
||||
/** 当前页数 */
|
||||
currentPage?: number;
|
||||
};
|
||||
};
|
||||
|
||||
/** 获取系统管理-用户管理列表 */
|
||||
export const getUserList = (data?: object) => {
|
||||
return http.request<ResultTable>("post", "/user", { data });
|
||||
};
|
||||
|
||||
/** 系统管理-用户管理-获取所有角色列表 */
|
||||
export const getAllRoleList = () => {
|
||||
return http.request<Result>("get", "/list-all-role");
|
||||
};
|
||||
|
||||
/** 系统管理-用户管理-根据userId,获取对应角色id列表(userId:用户id) */
|
||||
export const getRoleIds = (data?: object) => {
|
||||
return http.request<Result>("post", "/list-role-ids", { data });
|
||||
};
|
||||
|
||||
/** 获取系统管理-角色管理列表 */
|
||||
export const getRoleList = (data?: object) => {
|
||||
return http.request<ResultTable>("post", "/role", { data });
|
||||
};
|
||||
|
||||
/** 获取系统管理-菜单管理列表 */
|
||||
export const getMenuList = (data?: object) => {
|
||||
return http.request<Result>("post", "/menu", { data });
|
||||
};
|
||||
|
||||
/** 获取系统管理-部门管理列表 */
|
||||
export const getDeptList = (data?: object) => {
|
||||
return http.request<Result>("post", "/dept", { data });
|
||||
};
|
||||
|
||||
/** 获取系统监控-在线用户列表 */
|
||||
export const getOnlineLogsList = (data?: object) => {
|
||||
return http.request<ResultTable>("post", "/online-logs", { data });
|
||||
};
|
||||
|
||||
/** 获取系统监控-登录日志列表 */
|
||||
export const getLoginLogsList = (data?: object) => {
|
||||
return http.request<ResultTable>("post", "/login-logs", { data });
|
||||
};
|
||||
|
||||
/** 获取系统监控-操作日志列表 */
|
||||
export const getOperationLogsList = (data?: object) => {
|
||||
return http.request<ResultTable>("post", "/operation-logs", { data });
|
||||
};
|
||||
|
||||
/** 获取系统监控-系统日志列表 */
|
||||
export const getSystemLogsList = (data?: object) => {
|
||||
return http.request<ResultTable>("post", "/system-logs", { data });
|
||||
};
|
||||
|
||||
/** 获取系统监控-系统日志-根据 id 查日志详情 */
|
||||
export const getSystemLogsDetail = (data?: object) => {
|
||||
return http.request<Result>("post", "/system-logs-detail", { data });
|
||||
};
|
||||
|
||||
/** 获取角色管理-权限-菜单权限 */
|
||||
export const getRoleMenu = (data?: object) => {
|
||||
return http.request<Result>("post", "/role-menu", { data });
|
||||
};
|
||||
|
||||
/** 获取角色管理-权限-菜单权限-根据角色 id 查对应菜单 */
|
||||
export const getRoleMenuIds = (data?: object) => {
|
||||
return http.request<Result>("post", "/role-menu-ids", { data });
|
||||
};
|
||||
89
Yi.Pure.Vue3/src/api/user.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { http } from "@/utils/http";
|
||||
|
||||
export type UserResult = {
|
||||
success: boolean;
|
||||
data: {
|
||||
/** 头像 */
|
||||
avatar: string;
|
||||
/** 用户名 */
|
||||
username: string;
|
||||
/** 昵称 */
|
||||
nickname: string;
|
||||
/** 当前登录用户的角色 */
|
||||
roles: Array<string>;
|
||||
/** 按钮级别权限 */
|
||||
permissions: Array<string>;
|
||||
/** `token` */
|
||||
accessToken: string;
|
||||
/** 用于调用刷新`accessToken`的接口时所需的`token` */
|
||||
refreshToken: string;
|
||||
/** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */
|
||||
expires: Date;
|
||||
};
|
||||
};
|
||||
|
||||
export type RefreshTokenResult = {
|
||||
success: boolean;
|
||||
data: {
|
||||
/** `token` */
|
||||
accessToken: string;
|
||||
/** 用于调用刷新`accessToken`的接口时所需的`token` */
|
||||
refreshToken: string;
|
||||
/** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */
|
||||
expires: Date;
|
||||
};
|
||||
};
|
||||
|
||||
export type UserInfo = {
|
||||
/** 头像 */
|
||||
avatar: string;
|
||||
/** 用户名 */
|
||||
username: string;
|
||||
/** 昵称 */
|
||||
nickname: string;
|
||||
/** 邮箱 */
|
||||
email: string;
|
||||
/** 联系电话 */
|
||||
phone: string;
|
||||
/** 简介 */
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type UserInfoResult = {
|
||||
success: boolean;
|
||||
data: UserInfo;
|
||||
};
|
||||
|
||||
type ResultTable = {
|
||||
success: boolean;
|
||||
data?: {
|
||||
/** 列表数据 */
|
||||
list: Array<any>;
|
||||
/** 总条目数 */
|
||||
total?: number;
|
||||
/** 每页显示条目个数 */
|
||||
pageSize?: number;
|
||||
/** 当前页数 */
|
||||
currentPage?: number;
|
||||
};
|
||||
};
|
||||
|
||||
/** 登录 */
|
||||
export const getLogin = (data?: object) => {
|
||||
return http.request<UserResult>("post", "/login", { data });
|
||||
};
|
||||
|
||||
/** 刷新`token` */
|
||||
export const refreshTokenApi = (data?: object) => {
|
||||
return http.request<RefreshTokenResult>("post", "/refresh-token", { data });
|
||||
};
|
||||
|
||||
/** 账户设置-个人信息 */
|
||||
export const getMine = (data?: object) => {
|
||||
return http.request<UserInfoResult>("get", "/mine", { data });
|
||||
};
|
||||
|
||||
/** 账户设置-个人安全日志 */
|
||||
export const getMineLogs = (data?: object) => {
|
||||
return http.request<ResultTable>("get", "/mine-logs", { data });
|
||||
};
|
||||
BIN
Yi.Pure.Vue3/src/assets/car.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
27
Yi.Pure.Vue3/src/assets/iconfont/iconfont.css
Normal file
@@ -0,0 +1,27 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2208059 */
|
||||
src:
|
||||
url("iconfont.woff2?t=1671895108120") format("woff2"),
|
||||
url("iconfont.woff?t=1671895108120") format("woff"),
|
||||
url("iconfont.ttf?t=1671895108120") format("truetype");
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.pure-iconfont-tabs:before {
|
||||
content: "\e63e";
|
||||
}
|
||||
|
||||
.pure-iconfont-logo:before {
|
||||
content: "\e620";
|
||||
}
|
||||
|
||||
.pure-iconfont-new:before {
|
||||
content: "\e615";
|
||||
}
|
||||
69
Yi.Pure.Vue3/src/assets/iconfont/iconfont.js
Normal file
30
Yi.Pure.Vue3/src/assets/iconfont/iconfont.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"id": "2208059",
|
||||
"name": "pure-admin",
|
||||
"font_family": "iconfont",
|
||||
"css_prefix_text": "pure-iconfont-",
|
||||
"description": "pure-admin-iconfont",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "20594647",
|
||||
"name": "Tabs",
|
||||
"font_class": "tabs",
|
||||
"unicode": "e63e",
|
||||
"unicode_decimal": 58942
|
||||
},
|
||||
{
|
||||
"icon_id": "22129506",
|
||||
"name": "PureLogo",
|
||||
"font_class": "logo",
|
||||
"unicode": "e620",
|
||||
"unicode_decimal": 58912
|
||||
},
|
||||
{
|
||||
"icon_id": "7795615",
|
||||
"name": "New",
|
||||
"font_class": "new",
|
||||
"unicode": "e615",
|
||||
"unicode_decimal": 58901
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Yi.Pure.Vue3/src/assets/iconfont/iconfont.ttf
Normal file
BIN
Yi.Pure.Vue3/src/assets/iconfont/iconfont.woff
Normal file
BIN
Yi.Pure.Vue3/src/assets/iconfont/iconfont.woff2
Normal file
1
Yi.Pure.Vue3/src/assets/login/avatar.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path fill="#386BF3" d="M410.558.109c0 210.974-300.876 361.752-300.876 633.548 0 174.943 134.704 316.787 300.876 316.787s300.877-141.817 300.877-316.787C711.408 361.752 410.558 210.974 410.558.109"/><path fill="#C3D2FB" d="M613.469 73.665c0 211.055-300.877 361.914-300.877 633.547C312.592 882.156 447.296 1024 613.47 1024s300.876-141.817 300.876-316.788C914.29 435.58 613.469 284.72 613.469 73.665"/><path fill="#303F5B" d="M312.592 707.212c0-183.713 137.636-312.171 226.723-441.39 81.702 106.112 172.12 218.74 172.12 367.726A309.755 309.755 0 0 1 420.36 950.064a323.1 323.1 0 0 1-107.769-242.852z"/></svg>
|
||||
|
After Width: | Height: | Size: 706 B |
BIN
Yi.Pure.Vue3/src/assets/login/bg.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
1
Yi.Pure.Vue3/src/assets/login/illustration.svg
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
1
Yi.Pure.Vue3/src/assets/status/403.svg
Normal file
|
After Width: | Height: | Size: 10 KiB |
1
Yi.Pure.Vue3/src/assets/status/404.svg
Normal file
|
After Width: | Height: | Size: 11 KiB |
1
Yi.Pure.Vue3/src/assets/status/500.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
1
Yi.Pure.Vue3/src/assets/svg/back.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="32" height="32" viewBox="0 0 48 48"><path fill="#2F88FF" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width="4" d="M44 40.836q-7.34-8.96-13.036-10.168t-10.846-.365V41L4 23.545 20.118 7v10.167q9.523.075 16.192 6.833 6.668 6.758 7.69 16.836Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 300 B |
1
Yi.Pure.Vue3/src/assets/svg/back_top.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M2.88 18.054a35.9 35.9 0 0 1 8.531-16.32.8.8 0 0 1 1.178 0q.25.27.413.455a35.9 35.9 0 0 1 8.118 15.865c-2.141.451-4.34.747-6.584.874l-2.089 4.178a.5.5 0 0 1-.894 0l-2.089-4.178a44 44 0 0 1-6.584-.874m6.698-1.123 1.157.066L12 19.527l1.265-2.53 1.157-.066a42 42 0 0 0 4.227-.454A33.9 33.9 0 0 0 12 4.09a33.9 33.9 0 0 0-6.649 12.387q2.093.334 4.227.454M12 15a3 3 0 1 1 0-6 3 3 0 0 1 0 6m0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/></svg>
|
||||
|
After Width: | Height: | Size: 533 B |
1
Yi.Pure.Vue3/src/assets/svg/calendar.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="1em" height="1em" fill="none" class="t-icon t-icon-calendar" viewBox="0 0 16 16"><path fill="currentColor" d="M10 3H6V1.5H5V3H3a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1h-2V1.5h-1zM5 5h1V4h4v1h1V4h2v2H3V4h2zM3 7h10v6H3z"/></svg>
|
||||
|
After Width: | Height: | Size: 261 B |
1
Yi.Pure.Vue3/src/assets/svg/dark.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.38 2.019a7.5 7.5 0 1 0 10.6 10.6C21.662 17.854 17.316 22 12.001 22 6.477 22 2 17.523 2 12c0-5.315 4.146-9.661 9.38-9.981"/></svg>
|
||||
|
After Width: | Height: | Size: 262 B |
1
Yi.Pure.Vue3/src/assets/svg/day.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12M11 1h2v3h-2zm0 19h2v3h-2zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414zm2.121-14.85 1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414zM23 11v2h-3v-2zM4 11v2H1v-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 435 B |
1
Yi.Pure.Vue3/src/assets/svg/enter_outlined.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="iconify iconify--ant-design" viewBox="0 0 1024 1024"><path fill="currentColor" d="M864 170h-60c-4.4 0-8 3.6-8 8v518H310v-73c0-6.7-7.8-10.5-13-6.3l-141.9 112a8 8 0 0 0 0 12.6l141.9 112c5.3 4.2 13 .4 13-6.3v-75h498c35.3 0 64-28.7 64-64V178c0-4.4-3.6-8-8-8"/></svg>
|
||||
|
After Width: | Height: | Size: 351 B |
1
Yi.Pure.Vue3/src/assets/svg/exit_screen.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" aria-hidden="true" class="re-screen" color="#00000073" viewBox="0 0 16 16"><path fill="currentColor" d="M3.5 4H1V3h2V1h1v2.5zM13 3V1h-1v2.5l.5.5H15V3zm-1 9.5V15h1v-2h2v-1h-2.5zM1 12v1h2v2h1v-2.5l-.5-.5zm11-1.5-.5.5h-7l-.5-.5v-5l.5-.5h7l.5.5zM10 7H6v2h4z"/></svg>
|
||||
|
After Width: | Height: | Size: 327 B |
1
Yi.Pure.Vue3/src/assets/svg/full_screen.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" aria-hidden="true" class="re-screen" color="#00000073" viewBox="0 0 16 16"><path fill="currentColor" d="M3 12h10V4H3zm2-6h6v4H5zM2 6H1V2.5l.5-.5H5v1H2zm13-3.5V6h-1V3h-3V2h3.5zM14 10h1v3.5l-.5.5H11v-1h3zM2 13h3v1H1.5l-.5-.5V10h1z"/></svg>
|
||||
|
After Width: | Height: | Size: 302 B |
1
Yi.Pure.Vue3/src/assets/svg/globalization.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" aria-hidden="true" class="globalization" viewBox="0 0 512 512"><path fill="currentColor" d="m478.33 433.6-90-218a22 22 0 0 0-40.67 0l-90 218a22 22 0 1 0 40.67 16.79L316.66 406h102.67l18.33 44.39A22 22 0 0 0 458 464a22 22 0 0 0 20.32-30.4zM334.83 362 368 281.65 401.17 362zm-66.99-19.08a22 22 0 0 0-4.89-30.7c-.2-.15-15-11.13-36.49-34.73 39.65-53.68 62.11-114.75 71.27-143.49H330a22 22 0 0 0 0-44H214V70a22 22 0 0 0-44 0v20H54a22 22 0 0 0 0 44h197.25c-9.52 26.95-27.05 69.5-53.79 108.36-31.41-41.68-43.08-68.65-43.17-68.87a22 22 0 0 0-40.58 17c.58 1.38 14.55 34.23 52.86 83.93.92 1.19 1.83 2.35 2.74 3.51-39.24 44.35-77.74 71.86-93.85 80.74a22 22 0 1 0 21.07 38.63c2.16-1.18 48.6-26.89 101.63-85.59 22.52 24.08 38 35.44 38.93 36.1a22 22 0 0 0 30.75-4.9z"/></svg>
|
||||
|
After Width: | Height: | Size: 826 B |
1
Yi.Pure.Vue3/src/assets/svg/hot.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 1024 1024"><path fill="#FF5D50" d="M428.698 107.315c-6.503 72.192-36.352 207.258-160.256 337.408 3.686-48.025-7.117-83.763-19.047-107.673-6.605-13.159-26.06-10.599-28.877 3.84-5.734 29.44-20.582 75.059-57.6 137.779-71.628 121.395-62.566 459.878 340.736 459.878S934.093 585.728 876.8 442.522c-37.376-93.44-93.952-152.525-128.82-182.324-11.417-9.779-29.132-1.945-29.593 13.056-.921 30.464-7.321 73.37-33.075 102.144-.666-52.787-38.144-208.384-202.445-296.857-23.296-12.544-51.763 2.457-54.17 28.774z"/><path fill="#FFDF99" d="M702.26 678.4c-4.2-45.056-60.673-166.554-212.634-246.426-10.599-5.58-23.092 3.124-21.504 15.002 6.246 46.848 12.953 140.493-24.064 184.73 4.044-40.397-18.125-73.83-36.66-94.31-8.396-9.217-23.552-4.66-25.497 7.68-3.533 22.322-12.851 56.268-36.557 97.945-42.086 74.035-86.989 188.672 124.57 294.656 10.956.563 22.17.87 33.74.87a618 618 0 0 0 32.717-.87C694.631 878.182 709.837 759.706 702.26 678.4"/></svg>
|
||||
|
After Width: | Height: | Size: 1004 B |
1
Yi.Pure.Vue3/src/assets/svg/keyboard_esc.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" aria-hidden="true" class="iconify iconify--mdi" viewBox="0 0 24 24"><path fill="currentColor" d="M1 7h6v2H3v2h4v2H3v2h4v2H1zm10 0h4v2h-4v2h2a2 2 0 0 1 2 2v2c0 1.11-.89 2-2 2H9v-2h4v-2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2m8 0h2a2 2 0 0 1 2 2v1h-2V9h-2v6h2v-1h2v1c0 1.11-.89 2-2 2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2"/></svg>
|
||||
|
After Width: | Height: | Size: 379 B |
1
Yi.Pure.Vue3/src/assets/svg/laptop.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="1em" height="1em" fill="none" class="t-icon t-icon-laptop" viewBox="0 0 16 16"><path fill="currentColor" d="M2.5 12a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h11a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1zm0-1h11V4h-11zM15 13H1v1h14z"/></svg>
|
||||
|
After Width: | Height: | Size: 228 B |
1
Yi.Pure.Vue3/src/assets/svg/service.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="1em" height="1em" fill="none" class="t-icon t-icon-service" viewBox="0 0 16 16"><path fill="currentColor" d="M2.52 6.37a5.5 5.5 0 0 1 10.98.13v4c0 .05 0 .1-.02.15A4.5 4.5 0 0 1 9 14.7H8v-1h1a3.5 3.5 0 0 0 3.4-2.7h-1.9a.5.5 0 0 1-.5-.5v-4c0-.28.22-.5.5-.5h1.93a4.5 4.5 0 0 0-8.86 0H5.5c.28 0 .5.22.5.5v4a.5.5 0 0 1-.5.5H3a.5.5 0 0 1-.5-.5v-4c0-.04 0-.09.02-.13M12.5 7H11v3h1.5zm-9 0v3H5V7z"/></svg>
|
||||
|
After Width: | Height: | Size: 409 B |
1
Yi.Pure.Vue3/src/assets/svg/shop.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="1em" height="1em" fill="none" class="t-icon t-icon-shop" viewBox="0 0 16 16"><path fill="currentColor" d="M8 1a2.5 2.5 0 0 0-2.5 2.5V5h-2a.5.5 0 0 0-.5.5v9c0 .28.22.5.5.5h9a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5h-2V3.5A2.5 2.5 0 0 0 8 1m1.5 5v2h1V6H12v8H4V6h1.5v2h1V6zm0-1h-3V3.5a1.5 1.5 0 1 1 3 0z"/></svg>
|
||||
|
After Width: | Height: | Size: 317 B |
1
Yi.Pure.Vue3/src/assets/svg/system.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="icon" viewBox="0 0 1024 1024"><path d="M554 849.574c0 23.365-18.635 42.307-42 42.307s-42-18.941-42-42.307V662.719c0-23.365 18.635-42.307 42-42.307v-7.051c23.365 0 42 25.993 42 49.358z"/><path d="M893 888.5c0 17.397-14.103 31.5-31.5 31.5h-700c-17.397 0-31.5-14.103-31.5-31.5s14.103-31.5 31.5-31.5h700c17.397 0 31.5 14.103 31.5 31.5m33-714.074C926 135.484 894.686 105 855.744 105H168.256C129.314 105 98 135.484 98 174.426V533h828zM98 630.988C98 669.931 129.314 702 168.256 702h687.488C894.686 702 926 669.931 926 630.988V596H98z"/></svg>
|
||||
|
After Width: | Height: | Size: 605 B |
1
Yi.Pure.Vue3/src/assets/svg/user_avatar.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="1em" height="1em" fill="none" class="t-icon t-icon-user-avatar" viewBox="0 0 16 16"><path fill="currentColor" d="M8 10.5c1.24 0 2.42.31 3.5.88v1.12h1v-1.14a.94.94 0 0 0-.49-.84 8.48 8.48 0 0 0-8.02 0 .94.94 0 0 0-.49.84v1.14h1v-1.12A7.5 7.5 0 0 1 8 10.5M10.5 6a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0m-1 0a1.5 1.5 0 1 0-3 0 1.5 1.5 0 0 0 3 0"/><path fill="currentColor" d="M2.5 1.5a1 1 0 0 0-1 1v11a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1v-11a1 1 0 0 0-1-1zm11 1v11h-11v-11z"/></svg>
|
||||
|
After Width: | Height: | Size: 482 B |
1
Yi.Pure.Vue3/src/assets/table-bar/collapse.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M13.79 10.21a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42l-2.5-2.5a1 1 0 0 0-.33-.21 1 1 0 0 0-.76 0 1 1 0 0 0-.33.21l-2.5 2.5a1 1 0 0 0 1.42 1.42l.79-.8v5.18l-.79-.8a1 1 0 0 0-1.42 1.42l2.5 2.5a1 1 0 0 0 .33.21.94.94 0 0 0 .76 0 1 1 0 0 0 .33-.21l2.5-2.5a1 1 0 0 0-1.42-1.42l-.79.8V9.41ZM7 4h10a1 1 0 0 0 0-2H7a1 1 0 0 0 0 2m10 16H7a1 1 0 0 0 0 2h10a1 1 0 0 0 0-2"/></svg>
|
||||
|
After Width: | Height: | Size: 439 B |
1
Yi.Pure.Vue3/src/assets/table-bar/drag.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="32" height="32" fill="currentColor" aria-hidden="true" data-icon="holder" viewBox="64 64 896 896"><path d="M300 276.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97m0 284a56 56 0 1 0 56-97 56 56 0 0 0-56 97M640 228a56 56 0 1 0 112 0 56 56 0 0 0-112 0m0 284a56 56 0 1 0 112 0 56 56 0 0 0-112 0M300 844.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97M640 796a56 56 0 1 0 112 0 56 56 0 0 0-112 0"/></svg>
|
||||
|
After Width: | Height: | Size: 392 B |
1
Yi.Pure.Vue3/src/assets/table-bar/expand.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M22 4V2H2v2h9v14.17l-5.5-5.5-1.42 1.41L12 22l7.92-7.92-1.42-1.41-5.5 5.5V4z"/></svg>
|
||||
|
After Width: | Height: | Size: 161 B |
1
Yi.Pure.Vue3/src/assets/table-bar/refresh.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 11A8.1 8.1 0 0 0 4.5 9M4 5v4h4m-4 4a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"/></svg>
|
||||
|
After Width: | Height: | Size: 235 B |
1
Yi.Pure.Vue3/src/assets/table-bar/settings.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M3.34 17a10 10 0 0 1-.978-2.326 3 3 0 0 0 .002-5.347A10 10 0 0 1 4.865 4.99a3 3 0 0 0 4.631-2.674 10 10 0 0 1 5.007.002 3 3 0 0 0 4.632 2.672A10 10 0 0 1 20.66 7c.433.749.757 1.53.978 2.326a3 3 0 0 0-.002 5.347 10 10 0 0 1-2.501 4.337 3 3 0 0 0-4.631 2.674 10 10 0 0 1-5.007-.002 3 3 0 0 0-4.632-2.672A10 10 0 0 1 3.34 17m5.66.196a5 5 0 0 1 2.25 2.77q.75.071 1.499.001A5 5 0 0 1 15 17.197a5 5 0 0 1 3.525-.565q.435-.614.748-1.298A5 5 0 0 1 18 12c0-1.26.47-2.437 1.273-3.334a8 8 0 0 0-.75-1.298A5 5 0 0 1 15 6.804a5 5 0 0 1-2.25-2.77q-.75-.071-1.499-.001A5 5 0 0 1 9 6.803a5 5 0 0 1-3.525.565 8 8 0 0 0-.748 1.298A5 5 0 0 1 6 12a5 5 0 0 1-1.273 3.334 8 8 0 0 0 .75 1.298A5 5 0 0 1 9 17.196M12 15a3 3 0 1 1 0-6 3 3 0 0 1 0 6m0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/></svg>
|
||||
|
After Width: | Height: | Size: 840 B |
BIN
Yi.Pure.Vue3/src/assets/user.jpg
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
7
Yi.Pure.Vue3/src/components/ReAnimateSelector/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { withInstall } from "@pureadmin/utils";
|
||||
import reAnimateSelector from "./src/index.vue";
|
||||
|
||||
/** [animate.css](https://animate.style/) 选择器组件 */
|
||||
export const ReAnimateSelector = withInstall(reAnimateSelector);
|
||||
|
||||
export default ReAnimateSelector;
|
||||
114
Yi.Pure.Vue3/src/components/ReAnimateSelector/src/animate.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
export const animates = [
|
||||
/* Attention seekers */
|
||||
"bounce",
|
||||
"flash",
|
||||
"pulse",
|
||||
"rubberBand",
|
||||
"shakeX",
|
||||
"headShake",
|
||||
"swing",
|
||||
"tada",
|
||||
"wobble",
|
||||
"jello",
|
||||
"heartBeat",
|
||||
/* Back entrances */
|
||||
"backInDown",
|
||||
"backInLeft",
|
||||
"backInRight",
|
||||
"backInUp",
|
||||
/* Back exits */
|
||||
"backOutDown",
|
||||
"backOutLeft",
|
||||
"backOutRight",
|
||||
"backOutUp",
|
||||
/* Bouncing entrances */
|
||||
"bounceIn",
|
||||
"bounceInDown",
|
||||
"bounceInLeft",
|
||||
"bounceInRight",
|
||||
"bounceInUp",
|
||||
/* Bouncing exits */
|
||||
"bounceOut",
|
||||
"bounceOutDown",
|
||||
"bounceOutLeft",
|
||||
"bounceOutRight",
|
||||
"bounceOutUp",
|
||||
/* Fading entrances */
|
||||
"fadeIn",
|
||||
"fadeInDown",
|
||||
"fadeInDownBig",
|
||||
"fadeInLeft",
|
||||
"fadeInLeftBig",
|
||||
"fadeInRight",
|
||||
"fadeInRightBig",
|
||||
"fadeInUp",
|
||||
"fadeInUpBig",
|
||||
"fadeInTopLeft",
|
||||
"fadeInTopRight",
|
||||
"fadeInBottomLeft",
|
||||
"fadeInBottomRight",
|
||||
/* Fading exits */
|
||||
"fadeOut",
|
||||
"fadeOutDown",
|
||||
"fadeOutDownBig",
|
||||
"fadeOutLeft",
|
||||
"fadeOutLeftBig",
|
||||
"fadeOutRight",
|
||||
"fadeOutRightBig",
|
||||
"fadeOutUp",
|
||||
"fadeOutUpBig",
|
||||
"fadeOutTopLeft",
|
||||
"fadeOutTopRight",
|
||||
"fadeOutBottomRight",
|
||||
"fadeOutBottomLeft",
|
||||
/* Flippers */
|
||||
"flip",
|
||||
"flipInX",
|
||||
"flipInY",
|
||||
"flipOutX",
|
||||
"flipOutY",
|
||||
/* Lightspeed */
|
||||
"lightSpeedInRight",
|
||||
"lightSpeedInLeft",
|
||||
"lightSpeedOutRight",
|
||||
"lightSpeedOutLeft",
|
||||
/* Rotating entrances */
|
||||
"rotateIn",
|
||||
"rotateInDownLeft",
|
||||
"rotateInDownRight",
|
||||
"rotateInUpLeft",
|
||||
"rotateInUpRight",
|
||||
/* Rotating exits */
|
||||
"rotateOut",
|
||||
"rotateOutDownLeft",
|
||||
"rotateOutDownRight",
|
||||
"rotateOutUpLeft",
|
||||
"rotateOutUpRight",
|
||||
/* Specials */
|
||||
"hinge",
|
||||
"jackInTheBox",
|
||||
"rollIn",
|
||||
"rollOut",
|
||||
/* Zooming entrances */
|
||||
"zoomIn",
|
||||
"zoomInDown",
|
||||
"zoomInLeft",
|
||||
"zoomInRight",
|
||||
"zoomInUp",
|
||||
/* Zooming exits */
|
||||
"zoomOut",
|
||||
"zoomOutDown",
|
||||
"zoomOutLeft",
|
||||
"zoomOutRight",
|
||||
"zoomOutUp",
|
||||
/* Sliding entrances */
|
||||
"slideInDown",
|
||||
"slideInLeft",
|
||||
"slideInRight",
|
||||
"slideInUp",
|
||||
/* Sliding exits */
|
||||
"slideOutDown",
|
||||
"slideOutLeft",
|
||||
"slideOutRight",
|
||||
"slideOutUp"
|
||||
];
|
||||
136
Yi.Pure.Vue3/src/components/ReAnimateSelector/src/index.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue";
|
||||
import { animates } from "./animate";
|
||||
import { cloneDeep } from "@pureadmin/utils";
|
||||
|
||||
defineOptions({
|
||||
name: "ReAnimateSelector"
|
||||
});
|
||||
|
||||
defineProps({
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请选择动画"
|
||||
}
|
||||
});
|
||||
|
||||
const inputValue = defineModel({ type: String });
|
||||
|
||||
const searchVal = ref();
|
||||
const animatesList = ref(animates);
|
||||
const copyAnimatesList = cloneDeep(animatesList);
|
||||
|
||||
const animateClass = computed(() => {
|
||||
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]"
|
||||
];
|
||||
});
|
||||
|
||||
const animateStyle = computed(
|
||||
() => (i: string) =>
|
||||
inputValue.value === i
|
||||
? {
|
||||
borderColor: "var(--el-color-primary)",
|
||||
color: "var(--el-color-primary)"
|
||||
}
|
||||
: ""
|
||||
);
|
||||
|
||||
function onChangeIcon(animate: string) {
|
||||
inputValue.value = animate;
|
||||
}
|
||||
|
||||
function onClear() {
|
||||
inputValue.value = "";
|
||||
}
|
||||
|
||||
function filterMethod(value: any) {
|
||||
searchVal.value = value;
|
||||
animatesList.value = copyAnimatesList.value.filter((i: string | any[]) =>
|
||||
i.includes(value)
|
||||
);
|
||||
}
|
||||
|
||||
const animateMap = ref({});
|
||||
function onMouseEnter(index: string | number) {
|
||||
animateMap.value[index] = animateMap.value[index]?.loading
|
||||
? Object.assign({}, animateMap.value[index], {
|
||||
loading: false
|
||||
})
|
||||
: Object.assign({}, animateMap.value[index], {
|
||||
loading: true
|
||||
});
|
||||
}
|
||||
function onMouseleave() {
|
||||
animateMap.value = {};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-select
|
||||
clearable
|
||||
filterable
|
||||
:placeholder="placeholder"
|
||||
popper-class="pure-animate-popper"
|
||||
:model-value="inputValue"
|
||||
:filter-method="filterMethod"
|
||||
@clear="onClear"
|
||||
>
|
||||
<template #empty>
|
||||
<div class="w-[280px]">
|
||||
<el-scrollbar
|
||||
noresize
|
||||
height="212px"
|
||||
:view-style="{ overflow: 'hidden' }"
|
||||
class="border-t border-[#e5e7eb]"
|
||||
>
|
||||
<ul class="flex flex-wrap justify-around mb-1">
|
||||
<li
|
||||
v-for="(animate, index) in animatesList"
|
||||
:key="index"
|
||||
:class="animateClass"
|
||||
:style="animateStyle(animate)"
|
||||
@mouseenter.prevent="onMouseEnter(index)"
|
||||
@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>
|
||||
|
||||
<style>
|
||||
.pure-animate-popper {
|
||||
min-width: 0 !important;
|
||||
}
|
||||
</style>
|
||||
5
Yi.Pure.Vue3/src/components/ReAuth/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import auth from "./src/auth";
|
||||
|
||||
const Auth = auth;
|
||||
|
||||
export { Auth };
|
||||
20
Yi.Pure.Vue3/src/components/ReAuth/src/auth.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { defineComponent, Fragment } from "vue";
|
||||
import { hasAuth } from "@/router/utils";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Auth",
|
||||
props: {
|
||||
value: {
|
||||
type: undefined,
|
||||
default: []
|
||||
}
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
return () => {
|
||||
if (!slots) return null;
|
||||
return hasAuth(props.value) ? (
|
||||
<Fragment>{slots.default?.()}</Fragment>
|
||||
) : null;
|
||||
};
|
||||
}
|
||||
});
|
||||
7
Yi.Pure.Vue3/src/components/ReBarcode/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { withInstall } from "@pureadmin/utils";
|
||||
import reBarcode from "./src/index.vue";
|
||||
|
||||
/** 条形码组件 */
|
||||
export const ReBarcode = withInstall(reBarcode);
|
||||
|
||||
export default ReBarcode;
|
||||
42
Yi.Pure.Vue3/src/components/ReBarcode/src/index.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import JsBarcode from "jsbarcode";
|
||||
import { ref, onMounted } from "vue";
|
||||
|
||||
defineOptions({
|
||||
name: "ReBarcode"
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
tag: {
|
||||
type: String,
|
||||
default: "canvas"
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
// 完整配置 https://github.com/lindell/JsBarcode/wiki/Options
|
||||
options: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// type 相当于 options.format,如果 type 和 options.format 同时存在,type 值优先;
|
||||
type: {
|
||||
type: String,
|
||||
default: "CODE128"
|
||||
}
|
||||
});
|
||||
|
||||
const wrapEl = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
const opt = { ...props.options, format: props.type };
|
||||
JsBarcode(wrapEl.value, props.text, opt);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="tag" ref="wrapEl" />
|
||||
</template>
|
||||
29
Yi.Pure.Vue3/src/components/ReCol/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { ElCol } from "element-plus";
|
||||
import { h, defineComponent } from "vue";
|
||||
|
||||
// 封装element-plus的el-col组件
|
||||
export default defineComponent({
|
||||
name: "ReCol",
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
default: 24
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const attrs = this.$attrs;
|
||||
const val = this.value;
|
||||
return h(
|
||||
ElCol,
|
||||
{
|
||||
xs: val,
|
||||
sm: val,
|
||||
md: val,
|
||||
lg: val,
|
||||
xl: val,
|
||||
...attrs
|
||||
},
|
||||
{ default: () => this.$slots.default() }
|
||||
);
|
||||
}
|
||||
});
|
||||
2
Yi.Pure.Vue3/src/components/ReCountTo/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
normal 普通数字动画组件
|
||||
rebound 回弹式数字动画组件
|
||||
11
Yi.Pure.Vue3/src/components/ReCountTo/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import reNormalCountTo from "./src/normal";
|
||||
import reboundCountTo from "./src/rebound";
|
||||
import { withInstall } from "@pureadmin/utils";
|
||||
|
||||
/** 普通数字动画组件 */
|
||||
const ReNormalCountTo = withInstall(reNormalCountTo);
|
||||
|
||||
/** 回弹式数字动画组件 */
|
||||
const ReboundCountTo = withInstall(reboundCountTo);
|
||||
|
||||
export { ReNormalCountTo, ReboundCountTo };
|
||||
179
Yi.Pure.Vue3/src/components/ReCountTo/src/normal/index.tsx
Normal file
@@ -0,0 +1,179 @@
|
||||
import {
|
||||
watch,
|
||||
unref,
|
||||
computed,
|
||||
reactive,
|
||||
onMounted,
|
||||
defineComponent
|
||||
} from "vue";
|
||||
import { countToProps } from "./props";
|
||||
import { isNumber } from "@pureadmin/utils";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ReNormalCountTo",
|
||||
props: countToProps,
|
||||
emits: ["mounted", "callback"],
|
||||
setup(props, { emit }) {
|
||||
const state = reactive<{
|
||||
localStartVal: number;
|
||||
printVal: number | null;
|
||||
displayValue: string;
|
||||
paused: boolean;
|
||||
localDuration: number | null;
|
||||
startTime: number | null;
|
||||
timestamp: number | null;
|
||||
rAF: any;
|
||||
remaining: number | null;
|
||||
color: string;
|
||||
fontSize: string;
|
||||
}>({
|
||||
localStartVal: props.startVal,
|
||||
displayValue: formatNumber(props.startVal),
|
||||
printVal: null,
|
||||
paused: false,
|
||||
localDuration: props.duration,
|
||||
startTime: null,
|
||||
timestamp: null,
|
||||
remaining: null,
|
||||
rAF: null,
|
||||
color: null,
|
||||
fontSize: "16px"
|
||||
});
|
||||
|
||||
const getCountDown = computed(() => {
|
||||
return props.startVal > props.endVal;
|
||||
});
|
||||
|
||||
watch([() => props.startVal, () => props.endVal], () => {
|
||||
if (props.autoplay) {
|
||||
start();
|
||||
}
|
||||
});
|
||||
|
||||
function start() {
|
||||
const { startVal, duration, color, fontSize } = props;
|
||||
state.localStartVal = startVal;
|
||||
state.startTime = null;
|
||||
state.localDuration = duration;
|
||||
state.paused = false;
|
||||
state.color = color;
|
||||
state.fontSize = fontSize;
|
||||
state.rAF = requestAnimationFrame(count);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function pauseResume() {
|
||||
if (state.paused) {
|
||||
resume();
|
||||
state.paused = false;
|
||||
} else {
|
||||
pause();
|
||||
state.paused = true;
|
||||
}
|
||||
}
|
||||
|
||||
function pause() {
|
||||
cancelAnimationFrame(state.rAF);
|
||||
}
|
||||
|
||||
function resume() {
|
||||
state.startTime = null;
|
||||
state.localDuration = +(state.remaining as number);
|
||||
state.localStartVal = +(state.printVal as number);
|
||||
requestAnimationFrame(count);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function reset() {
|
||||
state.startTime = null;
|
||||
cancelAnimationFrame(state.rAF);
|
||||
state.displayValue = formatNumber(props.startVal);
|
||||
}
|
||||
|
||||
function count(timestamp: number) {
|
||||
const { useEasing, easingFn, endVal } = props;
|
||||
if (!state.startTime) state.startTime = timestamp;
|
||||
state.timestamp = timestamp;
|
||||
const progress = timestamp - state.startTime;
|
||||
state.remaining = (state.localDuration as number) - progress;
|
||||
if (useEasing) {
|
||||
if (unref(getCountDown)) {
|
||||
state.printVal =
|
||||
state.localStartVal -
|
||||
easingFn(
|
||||
progress,
|
||||
0,
|
||||
state.localStartVal - endVal,
|
||||
state.localDuration as number
|
||||
);
|
||||
} else {
|
||||
state.printVal = easingFn(
|
||||
progress,
|
||||
state.localStartVal,
|
||||
endVal - state.localStartVal,
|
||||
state.localDuration as number
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (unref(getCountDown)) {
|
||||
state.printVal =
|
||||
state.localStartVal -
|
||||
(state.localStartVal - endVal) *
|
||||
(progress / (state.localDuration as number));
|
||||
} else {
|
||||
state.printVal =
|
||||
state.localStartVal +
|
||||
(endVal - state.localStartVal) *
|
||||
(progress / (state.localDuration as number));
|
||||
}
|
||||
}
|
||||
if (unref(getCountDown)) {
|
||||
state.printVal = state.printVal < endVal ? endVal : state.printVal;
|
||||
} else {
|
||||
state.printVal = state.printVal > endVal ? endVal : state.printVal;
|
||||
}
|
||||
state.displayValue = formatNumber(state.printVal);
|
||||
if (progress < (state.localDuration as number)) {
|
||||
state.rAF = requestAnimationFrame(count);
|
||||
} else {
|
||||
emit("callback");
|
||||
}
|
||||
}
|
||||
|
||||
function formatNumber(num: number | string) {
|
||||
const { decimals, decimal, separator, suffix, prefix } = props;
|
||||
num = Number(num).toFixed(decimals);
|
||||
num += "";
|
||||
const x = num.split(".");
|
||||
let x1 = x[0];
|
||||
const x2 = x.length > 1 ? decimal + x[1] : "";
|
||||
const rgx = /(\d+)(\d{3})/;
|
||||
if (separator && !isNumber(separator)) {
|
||||
while (rgx.test(x1)) {
|
||||
x1 = x1.replace(rgx, "$1" + separator + "$2");
|
||||
}
|
||||
}
|
||||
return prefix + x1 + x2 + suffix;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.autoplay) {
|
||||
start();
|
||||
}
|
||||
emit("mounted");
|
||||
});
|
||||
|
||||
return () => (
|
||||
<>
|
||||
<span
|
||||
style={{
|
||||
color: props.color,
|
||||
fontSize: props.fontSize
|
||||
}}
|
||||
>
|
||||
{state.displayValue}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
32
Yi.Pure.Vue3/src/components/ReCountTo/src/normal/props.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { PropType } from "vue";
|
||||
import propTypes from "@/utils/propTypes";
|
||||
|
||||
export const countToProps = {
|
||||
startVal: propTypes.number.def(0),
|
||||
endVal: propTypes.number.def(2020),
|
||||
duration: propTypes.number.def(1300),
|
||||
autoplay: propTypes.bool.def(true),
|
||||
decimals: {
|
||||
type: Number as PropType<number>,
|
||||
required: false,
|
||||
default: 0,
|
||||
validator(value: number) {
|
||||
return value >= 0;
|
||||
}
|
||||
},
|
||||
color: propTypes.string.def(),
|
||||
fontSize: propTypes.string.def(),
|
||||
decimal: propTypes.string.def("."),
|
||||
separator: propTypes.string.def(","),
|
||||
prefix: propTypes.string.def(""),
|
||||
suffix: propTypes.string.def(""),
|
||||
useEasing: propTypes.bool.def(true),
|
||||
easingFn: {
|
||||
type: Function as PropType<
|
||||
(t: number, b: number, c: number, d: number) => number
|
||||
>,
|
||||
default(t: number, b: number, c: number, d: number) {
|
||||
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
|
||||
}
|
||||
}
|
||||
};
|
||||
72
Yi.Pure.Vue3/src/components/ReCountTo/src/rebound/index.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import "./rebound.css";
|
||||
import {
|
||||
ref,
|
||||
unref,
|
||||
onBeforeMount,
|
||||
defineComponent,
|
||||
onBeforeUnmount
|
||||
} from "vue";
|
||||
import { reboundProps } from "./props";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ReboundCountTo",
|
||||
props: reboundProps,
|
||||
setup(props) {
|
||||
const ulRef = ref();
|
||||
const timer = ref(null);
|
||||
|
||||
onBeforeMount(() => {
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
const testUA = regexp => regexp.test(ua);
|
||||
const isSafari = testUA(/safari/g) && !testUA(/chrome/g);
|
||||
|
||||
// Safari浏览器的兼容代码
|
||||
isSafari &&
|
||||
(timer.value = setTimeout(() => {
|
||||
ulRef.value.setAttribute(
|
||||
"style",
|
||||
`
|
||||
animation: none;
|
||||
transform: translateY(calc(var(--i) * -9.09%))
|
||||
`
|
||||
);
|
||||
}, props.delay * 1000));
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearTimeout(unref(timer));
|
||||
});
|
||||
|
||||
return () => (
|
||||
<>
|
||||
<div
|
||||
class="scroll-num"
|
||||
style={{ "--i": props.i, "--delay": props.delay }}
|
||||
>
|
||||
<ul ref="ulRef" style={{ fontSize: "32px" }}>
|
||||
<li>0</li>
|
||||
<li>1</li>
|
||||
<li>2</li>
|
||||
<li>3</li>
|
||||
<li>4</li>
|
||||
<li>5</li>
|
||||
<li>6</li>
|
||||
<li>7</li>
|
||||
<li>8</li>
|
||||
<li>9</li>
|
||||
<li>0</li>
|
||||
</ul>
|
||||
|
||||
<svg width="0" height="0">
|
||||
<filter id="blur">
|
||||
<feGaussianBlur
|
||||
in="SourceGraphic"
|
||||
stdDeviation={`0 ${props.blur}`}
|
||||
/>
|
||||
</filter>
|
||||
</svg>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
15
Yi.Pure.Vue3/src/components/ReCountTo/src/rebound/props.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { PropType } from "vue";
|
||||
import propTypes from "@/utils/propTypes";
|
||||
|
||||
export const reboundProps = {
|
||||
delay: propTypes.number.def(1),
|
||||
blur: propTypes.number.def(2),
|
||||
i: {
|
||||
type: Number as PropType<number>,
|
||||
required: false,
|
||||
default: 0,
|
||||
validator(value: number) {
|
||||
return value < 10 && value >= 0 && Number.isInteger(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
.scroll-num {
|
||||
width: var(--width, 20px);
|
||||
height: var(--height, calc(var(--width, 20px) * 1.8));
|
||||
color: var(--color, #333);
|
||||
font-size: var(--height, calc(var(--width, 20px) * 1.1));
|
||||
line-height: var(--height, calc(var(--width, 20px) * 1.8));
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
animation: enhance-bounce-in-down 1s calc(var(--delay) * 1s) forwards;
|
||||
}
|
||||
|
||||
ul {
|
||||
animation:
|
||||
move 0.3s linear infinite,
|
||||
bounce-in-down 1s calc(var(--delay) * 1s) forwards;
|
||||
}
|
||||
|
||||
@keyframes move {
|
||||
from {
|
||||
transform: translateY(-90%);
|
||||
filter: url(#blur);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(1%);
|
||||
filter: url(#blur);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounce-in-down {
|
||||
from {
|
||||
transform: translateY(calc(var(--i) * -9.09% - 7%));
|
||||
filter: none;
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: translateY(calc(var(--i) * -9.09% + 3%));
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(calc(var(--i) * -9.09% - 1%));
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: translateY(calc(var(--i) * -9.09% + 0.6%));
|
||||
}
|
||||
|
||||
85% {
|
||||
transform: translateY(calc(var(--i) * -9.09% - 0.3%));
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(calc(var(--i) * -9.09%));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes enhance-bounce-in-down {
|
||||
25% {
|
||||
transform: translateY(8%);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(-4%);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: translateY(2%);
|
||||
}
|
||||
|
||||
85% {
|
||||
transform: translateY(-1%);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
7
Yi.Pure.Vue3/src/components/ReCropper/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import reCropper from "./src";
|
||||
import { withInstall } from "@pureadmin/utils";
|
||||
|
||||
/** 图片裁剪组件 */
|
||||
export const ReCropper = withInstall(reCropper);
|
||||
|
||||
export default ReCropper;
|
||||
8
Yi.Pure.Vue3/src/components/ReCropper/src/circled.css
Normal file
@@ -0,0 +1,8 @@
|
||||
@import "cropperjs/dist/cropper.css";
|
||||
|
||||
.re-circled {
|
||||
.cropper-view-box,
|
||||
.cropper-face {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
457
Yi.Pure.Vue3/src/components/ReCropper/src/index.tsx
Normal file
@@ -0,0 +1,457 @@
|
||||
import "./circled.css";
|
||||
import Cropper from "cropperjs";
|
||||
import { ElUpload } from "element-plus";
|
||||
import type { CSSProperties } from "vue";
|
||||
import { useEventListener } from "@vueuse/core";
|
||||
import { longpress } from "@/directives/longpress";
|
||||
import { useTippy, directive as tippy } from "vue-tippy";
|
||||
import {
|
||||
type PropType,
|
||||
ref,
|
||||
unref,
|
||||
computed,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
defineComponent
|
||||
} from "vue";
|
||||
import {
|
||||
delay,
|
||||
debounce,
|
||||
isArray,
|
||||
downloadByBase64,
|
||||
useResizeObserver
|
||||
} from "@pureadmin/utils";
|
||||
import {
|
||||
Reload,
|
||||
Upload,
|
||||
ArrowH,
|
||||
ArrowV,
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
ArrowLeft,
|
||||
ChangeIcon,
|
||||
ArrowRight,
|
||||
RotateLeft,
|
||||
SearchPlus,
|
||||
RotateRight,
|
||||
SearchMinus,
|
||||
DownloadIcon
|
||||
} from "./svg";
|
||||
|
||||
type Options = Cropper.Options;
|
||||
|
||||
const defaultOptions: Options = {
|
||||
aspectRatio: 1,
|
||||
zoomable: true,
|
||||
zoomOnTouch: true,
|
||||
zoomOnWheel: true,
|
||||
cropBoxMovable: true,
|
||||
cropBoxResizable: true,
|
||||
toggleDragModeOnDblclick: true,
|
||||
autoCrop: true,
|
||||
background: true,
|
||||
highlight: true,
|
||||
center: true,
|
||||
responsive: true,
|
||||
restore: true,
|
||||
checkCrossOrigin: true,
|
||||
checkOrientation: true,
|
||||
scalable: true,
|
||||
modal: true,
|
||||
guides: true,
|
||||
movable: true,
|
||||
rotatable: true
|
||||
};
|
||||
|
||||
const props = {
|
||||
src: { type: String, required: true },
|
||||
alt: { type: String },
|
||||
circled: { type: Boolean, default: false },
|
||||
/** 是否可以通过点击裁剪区域关闭右键弹出的功能菜单,默认 `true` */
|
||||
isClose: { type: Boolean, default: true },
|
||||
realTimePreview: { type: Boolean, default: true },
|
||||
height: { type: [String, Number], default: "360px" },
|
||||
crossorigin: {
|
||||
type: String as PropType<"" | "anonymous" | "use-credentials" | undefined>,
|
||||
default: undefined
|
||||
},
|
||||
imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) },
|
||||
options: { type: Object as PropType<Options>, default: () => ({}) }
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: "ReCropper",
|
||||
props,
|
||||
setup(props, { attrs, emit }) {
|
||||
const tippyElRef = ref<ElRef<HTMLImageElement>>();
|
||||
const imgElRef = ref<ElRef<HTMLImageElement>>();
|
||||
const cropper = ref<Nullable<Cropper>>();
|
||||
const inCircled = ref(props.circled);
|
||||
const isInClose = ref(props.isClose);
|
||||
const inSrc = ref(props.src);
|
||||
const isReady = ref(false);
|
||||
const imgBase64 = ref();
|
||||
|
||||
let scaleX = 1;
|
||||
let scaleY = 1;
|
||||
|
||||
const debounceRealTimeCroppered = debounce(realTimeCroppered, 80);
|
||||
|
||||
const getImageStyle = computed((): CSSProperties => {
|
||||
return {
|
||||
height: props.height,
|
||||
maxWidth: "100%",
|
||||
...props.imageStyle
|
||||
};
|
||||
});
|
||||
|
||||
const getClass = computed(() => {
|
||||
return [
|
||||
attrs.class,
|
||||
{
|
||||
["re-circled"]: inCircled.value
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
const iconClass = computed(() => {
|
||||
return [
|
||||
"p-[6px]",
|
||||
"h-[30px]",
|
||||
"w-[30px]",
|
||||
"outline-none",
|
||||
"rounded-[4px]",
|
||||
"cursor-pointer",
|
||||
"hover:bg-[rgba(0,0,0,0.06)]"
|
||||
];
|
||||
});
|
||||
|
||||
const getWrapperStyle = computed((): CSSProperties => {
|
||||
return { height: `${props.height}`.replace(/px/, "") + "px" };
|
||||
});
|
||||
|
||||
onMounted(init);
|
||||
|
||||
onUnmounted(() => {
|
||||
cropper.value?.destroy();
|
||||
isReady.value = false;
|
||||
cropper.value = null;
|
||||
imgBase64.value = "";
|
||||
scaleX = 1;
|
||||
scaleY = 1;
|
||||
});
|
||||
|
||||
useResizeObserver(tippyElRef, () => handCropper("reset"));
|
||||
|
||||
async function init() {
|
||||
const imgEl = unref(imgElRef);
|
||||
if (!imgEl) return;
|
||||
cropper.value = new Cropper(imgEl, {
|
||||
...defaultOptions,
|
||||
ready: () => {
|
||||
isReady.value = true;
|
||||
realTimeCroppered();
|
||||
delay(400).then(() => emit("readied", cropper.value));
|
||||
},
|
||||
crop() {
|
||||
debounceRealTimeCroppered();
|
||||
},
|
||||
zoom() {
|
||||
debounceRealTimeCroppered();
|
||||
},
|
||||
cropmove() {
|
||||
debounceRealTimeCroppered();
|
||||
},
|
||||
...props.options
|
||||
});
|
||||
}
|
||||
|
||||
function realTimeCroppered() {
|
||||
props.realTimePreview && croppered();
|
||||
}
|
||||
|
||||
function croppered() {
|
||||
if (!cropper.value) return;
|
||||
const canvas = inCircled.value
|
||||
? getRoundedCanvas()
|
||||
: cropper.value.getCroppedCanvas();
|
||||
// https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toBlob
|
||||
canvas.toBlob(blob => {
|
||||
if (!blob) return;
|
||||
const fileReader: FileReader = new FileReader();
|
||||
fileReader.readAsDataURL(blob);
|
||||
fileReader.onloadend = e => {
|
||||
if (!e.target?.result || !blob) return;
|
||||
imgBase64.value = e.target.result;
|
||||
emit("cropper", {
|
||||
base64: e.target.result,
|
||||
blob,
|
||||
info: { size: blob.size, ...cropper.value.getData() }
|
||||
});
|
||||
};
|
||||
fileReader.onerror = () => {
|
||||
emit("error");
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getRoundedCanvas() {
|
||||
const sourceCanvas = cropper.value!.getCroppedCanvas();
|
||||
const canvas = document.createElement("canvas");
|
||||
const context = canvas.getContext("2d")!;
|
||||
const width = sourceCanvas.width;
|
||||
const height = sourceCanvas.height;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
context.imageSmoothingEnabled = true;
|
||||
context.drawImage(sourceCanvas, 0, 0, width, height);
|
||||
context.globalCompositeOperation = "destination-in";
|
||||
context.beginPath();
|
||||
context.arc(
|
||||
width / 2,
|
||||
height / 2,
|
||||
Math.min(width, height) / 2,
|
||||
0,
|
||||
2 * Math.PI,
|
||||
true
|
||||
);
|
||||
context.fill();
|
||||
return canvas;
|
||||
}
|
||||
|
||||
function handCropper(event: string, arg?: number | Array<number>) {
|
||||
if (event === "scaleX") {
|
||||
scaleX = arg = scaleX === -1 ? 1 : -1;
|
||||
}
|
||||
|
||||
if (event === "scaleY") {
|
||||
scaleY = arg = scaleY === -1 ? 1 : -1;
|
||||
}
|
||||
arg && isArray(arg)
|
||||
? cropper.value?.[event]?.(...arg)
|
||||
: cropper.value?.[event]?.(arg);
|
||||
}
|
||||
|
||||
function beforeUpload(file) {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
inSrc.value = "";
|
||||
reader.onload = e => {
|
||||
inSrc.value = e.target?.result as string;
|
||||
};
|
||||
reader.onloadend = () => {
|
||||
init();
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
const menuContent = defineComponent({
|
||||
directives: {
|
||||
tippy,
|
||||
longpress
|
||||
},
|
||||
setup() {
|
||||
return () => (
|
||||
<div class="flex flex-wrap w-[60px] justify-between">
|
||||
<ElUpload
|
||||
accept="image/*"
|
||||
show-file-list={false}
|
||||
before-upload={beforeUpload}
|
||||
>
|
||||
<Upload
|
||||
class={iconClass.value}
|
||||
v-tippy={{
|
||||
content: "上传",
|
||||
placement: "left-start"
|
||||
}}
|
||||
/>
|
||||
</ElUpload>
|
||||
<DownloadIcon
|
||||
class={iconClass.value}
|
||||
v-tippy={{
|
||||
content: "下载",
|
||||
placement: "right-start"
|
||||
}}
|
||||
onClick={() => downloadByBase64(imgBase64.value, "cropping.png")}
|
||||
/>
|
||||
<ChangeIcon
|
||||
class={iconClass.value}
|
||||
v-tippy={{
|
||||
content: "圆形、矩形裁剪",
|
||||
placement: "left-start"
|
||||
}}
|
||||
onClick={() => {
|
||||
inCircled.value = !inCircled.value;
|
||||
realTimeCroppered();
|
||||
}}
|
||||
/>
|
||||
<Reload
|
||||
class={iconClass.value}
|
||||
v-tippy={{
|
||||
content: "重置",
|
||||
placement: "right-start"
|
||||
}}
|
||||
onClick={() => handCropper("reset")}
|
||||
/>
|
||||
<ArrowUp
|
||||
class={iconClass.value}
|
||||
v-tippy={{
|
||||
content: "上移(可长按)",
|
||||
placement: "left-start"
|
||||
}}
|
||||
v-longpress={[() => handCropper("move", [0, -10]), "0:100"]}
|
||||
/>
|
||||
<ArrowDown
|
||||
class={iconClass.value}
|
||||
v-tippy={{
|
||||
content: "下移(可长按)",
|
||||
placement: "right-start"
|
||||
}}
|
||||
v-longpress={[() => handCropper("move", [0, 10]), "0:100"]}
|
||||
/>
|
||||
<ArrowLeft
|
||||
class={iconClass.value}
|
||||
v-tippy={{
|
||||
content: "左移(可长按)",
|
||||
placement: "left-start"
|
||||
}}
|
||||
v-longpress={[() => handCropper("move", [-10, 0]), "0:100"]}
|
||||
/>
|
||||
<ArrowRight
|
||||
class={iconClass.value}
|
||||
v-tippy={{
|
||||
content: "右移(可长按)",
|
||||
placement: "right-start"
|
||||
}}
|
||||
v-longpress={[() => handCropper("move", [10, 0]), "0:100"]}
|
||||
/>
|
||||
<ArrowH
|
||||
class={iconClass.value}
|
||||
v-tippy={{
|
||||
content: "水平翻转",
|
||||
placement: "left-start"
|
||||
}}
|
||||
onClick={() => handCropper("scaleX", -1)}
|
||||
/>
|
||||
<ArrowV
|
||||
class={iconClass.value}
|
||||
v-tippy={{
|
||||
content: "垂直翻转",
|
||||
placement: "right-start"
|
||||
}}
|
||||
onClick={() => handCropper("scaleY", -1)}
|
||||
/>
|
||||
<RotateLeft
|
||||
class={iconClass.value}
|
||||
v-tippy={{
|
||||
content: "逆时针旋转",
|
||||
placement: "left-start"
|
||||
}}
|
||||
onClick={() => handCropper("rotate", -45)}
|
||||
/>
|
||||
<RotateRight
|
||||
class={iconClass.value}
|
||||
v-tippy={{
|
||||
content: "顺时针旋转",
|
||||
placement: "right-start"
|
||||
}}
|
||||
onClick={() => handCropper("rotate", 45)}
|
||||
/>
|
||||
<SearchPlus
|
||||
class={iconClass.value}
|
||||
v-tippy={{
|
||||
content: "放大(可长按)",
|
||||
placement: "left-start"
|
||||
}}
|
||||
v-longpress={[() => handCropper("zoom", 0.1), "0:100"]}
|
||||
/>
|
||||
<SearchMinus
|
||||
class={iconClass.value}
|
||||
v-tippy={{
|
||||
content: "缩小(可长按)",
|
||||
placement: "right-start"
|
||||
}}
|
||||
v-longpress={[() => handCropper("zoom", -0.1), "0:100"]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function onContextmenu(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const { show, setProps, destroy, state } = useTippy(tippyElRef, {
|
||||
content: menuContent,
|
||||
arrow: false,
|
||||
theme: "light",
|
||||
trigger: "manual",
|
||||
interactive: true,
|
||||
appendTo: "parent",
|
||||
// hideOnClick: false,
|
||||
placement: "bottom-end"
|
||||
});
|
||||
|
||||
setProps({
|
||||
getReferenceClientRect: () => ({
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: event.clientY,
|
||||
bottom: event.clientY,
|
||||
left: event.clientX,
|
||||
right: event.clientX
|
||||
})
|
||||
});
|
||||
|
||||
show();
|
||||
|
||||
if (isInClose.value) {
|
||||
if (!state.value.isShown && !state.value.isVisible) return;
|
||||
useEventListener(tippyElRef, "click", destroy);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
inSrc,
|
||||
props,
|
||||
imgElRef,
|
||||
tippyElRef,
|
||||
getClass,
|
||||
getWrapperStyle,
|
||||
getImageStyle,
|
||||
isReady,
|
||||
croppered,
|
||||
onContextmenu
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
const {
|
||||
inSrc,
|
||||
isReady,
|
||||
getClass,
|
||||
getImageStyle,
|
||||
onContextmenu,
|
||||
getWrapperStyle
|
||||
} = this;
|
||||
const { alt, crossorigin } = this.props;
|
||||
|
||||
return inSrc ? (
|
||||
<div
|
||||
ref="tippyElRef"
|
||||
class={getClass}
|
||||
style={getWrapperStyle}
|
||||
onContextmenu={event => onContextmenu(event)}
|
||||
>
|
||||
<img
|
||||
v-show={isReady}
|
||||
ref="imgElRef"
|
||||
style={getImageStyle}
|
||||
src={inSrc}
|
||||
alt={alt}
|
||||
crossorigin={crossorigin}
|
||||
/>
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M862 465.3h-81c-4.6 0-9 2-12.1 5.5L550 723.1V160c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v563.1L255.1 470.8c-3-3.5-7.4-5.5-12.1-5.5h-81c-6.8 0-10.5 8.1-6 13.2L487.9 861a31.96 31.96 0 0 0 48.3 0L868 478.5c4.5-5.2.8-13.2-6-13.2"/></svg>
|
||||
|
After Width: | Height: | Size: 346 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path d="m296.992 216.992-272 272L3.008 512l21.984 23.008 272 272 46.016-46.016L126.016 544h772L680.992 760.992l46.016 46.016 272-272L1020.992 512l-21.984-23.008-272-272-46.048 46.048L898.016 480h-772l216.96-216.992z"/></svg>
|
||||
|
After Width: | Height: | Size: 325 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M872 474H286.9l350.2-304c5.6-4.9 2.2-14-5.2-14h-88.5c-3.9 0-7.6 1.4-10.5 3.9L155 487.8a31.96 31.96 0 0 0 0 48.3L535.1 866c1.5 1.3 3.3 2 5.2 2h91.5c7.4 0 10.8-9.2 5.2-14L286.9 550H872c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8"/></svg>
|
||||
|
After Width: | Height: | Size: 343 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M869 487.8 491.2 159.9c-2.9-2.5-6.6-3.9-10.5-3.9h-88.5c-7.4 0-10.8 9.2-5.2 14l350.2 304H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h585.1L386.9 854c-5.6 4.9-2.2 14 5.2 14h91.5c1.9 0 3.8-.7 5.2-2L869 536.2a32.07 32.07 0 0 0 0-48.4"/></svg>
|
||||
|
After Width: | Height: | Size: 350 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M868 545.5 536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2"/></svg>
|
||||
|
After Width: | Height: | Size: 338 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path d="m512 67.008-23.008 21.984-256 256 46.048 46.048L480 190.016v644L279.008 632.96l-46.048 46.08 256 256 23.008 21.984 23.008-21.984 256-256-46.016-46.016L544 834.016v-644l200.992 200.96 46.016-45.984-256-256z"/></svg>
|
||||
|
After Width: | Height: | Size: 323 B |
1
Yi.Pure.Vue3/src/components/ReCropper/src/svg/change.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path d="M956.8 988.8H585.6c-16 0-25.6-9.6-25.6-28.8V576c0-16 9.6-28.8 25.6-28.8h371.2c16 0 25.6 9.6 25.6 28.8v384c0 16-9.6 28.8-25.6 28.8M608 937.6h326.4V598.4H608zm-121.6 44.8C262.4 982.4 144 848 144 595.2c0-19.2 9.6-28.8 25.6-28.8s25.6 12.8 25.6 28.8c0 220.8 96 326.4 288 326.4 16 0 25.6 12.8 25.6 28.8s-6.4 32-22.4 32"/><path d="M262.4 694.4c-6.4 0-9.6-3.2-16-6.4L160 601.6c-9.6-9.6-9.6-22.4 0-28.8s22.4-9.6 28.8 0l86.4 86.4c9.6 9.6 9.6 22.4 0 28.8-3.2 3.2-6.4 6.4-12.8 6.4"/><path d="M86.4 694.4c-6.4 0-9.6-3.2-16-6.4-9.6-9.6-9.6-22.4 0-28.8l86.4-86.4c9.6-9.6 22.4-9.6 28.8 0 9.6 9.6 9.6 22.4 0 28.8L99.2 688c-3.2 3.2-6.4 6.4-12.8 6.4m790.4-249.6c-16 0-28.8-12.8-28.8-32 0-224-99.2-336-300.8-336-16 0-28.8-12.8-28.8-32s9.6-32 28.8-32c233.6 0 355.2 137.6 355.2 396.8 0 22.4-9.6 35.2-25.6 35.2"/><path d="M876.8 448c-6.4 0-9.6-3.2-16-6.4l-86.4-86.4c-9.6-9.6-9.6-22.4 0-28.8s22.4-9.6 28.8 0l86.4 86.4c9.6 9.6 9.6 22.4 0 28.8 0 3.2-6.4 6.4-12.8 6.4"/><path d="M876.8 448c-6.4 0-9.6-3.2-16-6.4-9.6-9.6-9.6-22.4 0-28.8l86.4-86.4c9.6-9.6 22.4-9.6 28.8 0s9.6 22.4 0 28.8l-86.4 86.4c-3.2 3.2-6.4 6.4-12.8 6.4M288 524.8C156.8 524.8 48 416 48 278.4S156.8 35.2 288 35.2 528 144 528 281.6 419.2 524.8 288 524.8m-3.2-432c-99.2 0-179.2 83.2-179.2 185.6S185.6 464 284.8 464 464 380.8 464 278.4 384 92.8 284.8 92.8"/></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M505.7 661a8 8 0 0 0 12.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8"/></svg>
|
||||
|
After Width: | Height: | Size: 417 B |
31
Yi.Pure.Vue3/src/components/ReCropper/src/svg/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import Reload from "./reload.svg?component";
|
||||
import Upload from "./upload.svg?component";
|
||||
import ArrowH from "./arrow-h.svg?component";
|
||||
import ArrowV from "./arrow-v.svg?component";
|
||||
import ArrowUp from "./arrow-up.svg?component";
|
||||
import ChangeIcon from "./change.svg?component";
|
||||
import ArrowDown from "./arrow-down.svg?component";
|
||||
import ArrowLeft from "./arrow-left.svg?component";
|
||||
import DownloadIcon from "./download.svg?component";
|
||||
import ArrowRight from "./arrow-right.svg?component";
|
||||
import RotateLeft from "./rotate-left.svg?component";
|
||||
import SearchPlus from "./search-plus.svg?component";
|
||||
import RotateRight from "./rotate-right.svg?component";
|
||||
import SearchMinus from "./search-minus.svg?component";
|
||||
|
||||
export {
|
||||
Reload,
|
||||
Upload,
|
||||
ArrowH,
|
||||
ArrowV,
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
ArrowLeft,
|
||||
ChangeIcon,
|
||||
ArrowRight,
|
||||
RotateLeft,
|
||||
SearchPlus,
|
||||
RotateRight,
|
||||
SearchMinus,
|
||||
DownloadIcon
|
||||
};
|
||||
1
Yi.Pure.Vue3/src/components/ReCropper/src/svg/reload.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M168 504.2c1-43.7 10-86.1 26.9-126 17.3-41 42.1-77.7 73.7-109.4S337 212.3 378 195c42.4-17.9 87.4-27 133.9-27s91.5 9.1 133.8 27A341.5 341.5 0 0 1 755 268.8c9.9 9.9 19.2 20.4 27.8 31.4l-60.2 47a8 8 0 0 0 3 14.1l175.7 43c5 1.2 9.9-2.6 9.9-7.7l.8-180.9c0-6.7-7.7-10.5-12.9-6.3l-56.4 44.1C765.8 155.1 646.2 92 511.8 92 282.7 92 96.3 275.6 92 503.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8m756 7.8h-60c-4.4 0-7.9 3.5-8 7.8-1 43.7-10 86.1-26.9 126-17.3 41-42.1 77.8-73.7 109.4A342.45 342.45 0 0 1 512.1 856a342.24 342.24 0 0 1-243.2-100.8c-9.9-9.9-19.2-20.4-27.8-31.4l60.2-47a8 8 0 0 0-3-14.1l-175.7-43c-5-1.2-9.9 2.6-9.9 7.7l-.7 181c0 6.7 7.7 10.5 12.9 6.3l56.4-44.1C258.2 868.9 377.8 932 512.2 932c229.2 0 415.5-183.7 419.8-411.8a8 8 0 0 0-8-8.2"/></svg>
|
||||
|
After Width: | Height: | Size: 863 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M672 418H144c-17.7 0-32 14.3-32 32v414c0 17.7 14.3 32 32 32h528c17.7 0 32-14.3 32-32V450c0-17.7-14.3-32-32-32m-44 402H188V494h440z"/><path fill="currentColor" d="M819.3 328.5c-78.8-100.7-196-153.6-314.6-154.2l-.2-64c0-6.5-7.6-10.1-12.6-6.1l-128 101c-4 3.1-3.9 9.1 0 12.3L492 318.6c5.1 4 12.7.4 12.6-6.1v-63.9c12.9.1 25.9.9 38.8 2.5 42.1 5.2 82.1 18.2 119 38.7 38.1 21.2 71.2 49.7 98.4 84.3 27.1 34.7 46.7 73.7 58.1 115.8 11 40.7 14 82.7 8.9 124.8-.7 5.4-1.4 10.8-2.4 16.1h74.9c14.8-103.6-11.3-213-81-302.3"/></svg>
|
||||
|
After Width: | Height: | Size: 630 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M480.5 251.2c13-1.6 25.9-2.4 38.8-2.5v63.9c0 6.5 7.5 10.1 12.6 6.1L660 217.6c4-3.2 4-9.2 0-12.3l-128-101c-5.1-4-12.6-.4-12.6 6.1l-.2 64c-118.6.5-235.8 53.4-314.6 154.2-69.6 89.2-95.7 198.6-81.1 302.4h74.9c-.9-5.3-1.7-10.7-2.4-16.1-5.1-42.1-2.1-84.1 8.9-124.8 11.4-42.2 31-81.1 58.1-115.8 27.2-34.7 60.3-63.2 98.4-84.3 37-20.6 76.9-33.6 119.1-38.8"/><path fill="currentColor" d="M880 418H352c-17.7 0-32 14.3-32 32v414c0 17.7 14.3 32 32 32h528c17.7 0 32-14.3 32-32V450c0-17.7-14.3-32-32-32m-44 402H396V494h440z"/></svg>
|
||||
|
After Width: | Height: | Size: 633 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M637 443H325c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h312c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8m284 424L775 721c122.1-148.9 113.6-369.5-26-509-148-148.1-388.4-148.1-537 0-148.1 148.6-148.1 389 0 537 139.5 139.6 360.1 148.1 509 26l146 146c3.2 2.8 8.3 2.8 11 0l43-43c2.8-2.7 2.8-7.8 0-11M696 696c-118.8 118.7-311.2 118.7-430 0-118.7-118.8-118.7-311.2 0-430 118.8-118.7 311.2-118.7 430 0 118.7 118.8 118.7 311.2 0 430"/></svg>
|
||||
|
After Width: | Height: | Size: 532 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M637 443H519V309c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v134H325c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h118v134c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V519h118c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8m284 424L775 721c122.1-148.9 113.6-369.5-26-509-148-148.1-388.4-148.1-537 0-148.1 148.6-148.1 389 0 537 139.5 139.6 360.1 148.1 509 26l146 146c3.2 2.8 8.3 2.8 11 0l43-43c2.8-2.7 2.8-7.8 0-11M696 696c-118.8 118.7-311.2 118.7-430 0-118.7-118.8-118.7-311.2 0-430 118.8-118.7 311.2-118.7 430 0 118.7 118.8 118.7 311.2 0 430"/></svg>
|
||||
|
After Width: | Height: | Size: 628 B |
1
Yi.Pure.Vue3/src/components/ReCropper/src/svg/upload.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M400 317.7h73.9V656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V317.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 163a8 8 0 0 0-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13M878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8"/></svg>
|
||||
|
After Width: | Height: | Size: 421 B |
7
Yi.Pure.Vue3/src/components/ReCropperPreview/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import reCropperPreview from "./src/index.vue";
|
||||
import { withInstall } from "@pureadmin/utils";
|
||||
|
||||
/** 图片裁剪预览组件 */
|
||||
export const ReCropperPreview = withInstall(reCropperPreview);
|
||||
|
||||
export default ReCropperPreview;
|
||||
76
Yi.Pure.Vue3/src/components/ReCropperPreview/src/index.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<script setup lang="tsx">
|
||||
import { ref } from "vue";
|
||||
import ReCropper from "@/components/ReCropper";
|
||||
import { formatBytes } from "@pureadmin/utils";
|
||||
|
||||
defineOptions({
|
||||
name: "ReCropperPreview"
|
||||
});
|
||||
|
||||
defineProps({
|
||||
imgSrc: String
|
||||
});
|
||||
|
||||
const emit = defineEmits(["cropper"]);
|
||||
|
||||
const infos = ref();
|
||||
const popoverRef = ref();
|
||||
const refCropper = ref();
|
||||
const showPopover = ref(false);
|
||||
const cropperImg = ref<string>("");
|
||||
|
||||
function onCropper({ base64, blob, info }) {
|
||||
infos.value = info;
|
||||
cropperImg.value = base64;
|
||||
emit("cropper", { base64, blob, info });
|
||||
}
|
||||
|
||||
function hidePopover() {
|
||||
popoverRef.value.hide();
|
||||
}
|
||||
|
||||
defineExpose({ hidePopover });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-loading="!showPopover" element-loading-background="transparent">
|
||||
<el-popover
|
||||
ref="popoverRef"
|
||||
:visible="showPopover"
|
||||
placement="right"
|
||||
width="18vw"
|
||||
>
|
||||
<template #reference>
|
||||
<div class="w-[18vw]">
|
||||
<ReCropper
|
||||
ref="refCropper"
|
||||
:src="imgSrc"
|
||||
circled
|
||||
@cropper="onCropper"
|
||||
@readied="showPopover = true"
|
||||
/>
|
||||
<p v-show="showPopover" class="mt-1 text-center">
|
||||
温馨提示:右键上方裁剪区可开启功能菜单
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex flex-wrap justify-center items-center text-center">
|
||||
<el-image
|
||||
v-if="cropperImg"
|
||||
:src="cropperImg"
|
||||
:preview-src-list="Array.of(cropperImg)"
|
||||
fit="cover"
|
||||
/>
|
||||
<div v-if="infos" class="mt-1">
|
||||
<p>
|
||||
图像大小:{{ parseInt(infos.width) }} ×
|
||||
{{ parseInt(infos.height) }}像素
|
||||
</p>
|
||||
<p>
|
||||
文件大小:{{ formatBytes(infos.size) }}({{ infos.size }} 字节)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
69
Yi.Pure.Vue3/src/components/ReDialog/index.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { ref } from "vue";
|
||||
import reDialog from "./index.vue";
|
||||
import { useTimeoutFn } from "@vueuse/core";
|
||||
import { withInstall } from "@pureadmin/utils";
|
||||
import type {
|
||||
EventType,
|
||||
ArgsType,
|
||||
DialogProps,
|
||||
ButtonProps,
|
||||
DialogOptions
|
||||
} from "./type";
|
||||
|
||||
const dialogStore = ref<Array<DialogOptions>>([]);
|
||||
|
||||
/** 打开弹框 */
|
||||
const addDialog = (options: DialogOptions) => {
|
||||
const open = () =>
|
||||
dialogStore.value.push(Object.assign(options, { visible: true }));
|
||||
if (options?.openDelay) {
|
||||
useTimeoutFn(() => {
|
||||
open();
|
||||
}, options.openDelay);
|
||||
} else {
|
||||
open();
|
||||
}
|
||||
};
|
||||
|
||||
/** 关闭弹框 */
|
||||
const closeDialog = (options: DialogOptions, index: number, args?: any) => {
|
||||
dialogStore.value[index].visible = false;
|
||||
options.closeCallBack && options.closeCallBack({ options, index, args });
|
||||
|
||||
const closeDelay = options?.closeDelay ?? 200;
|
||||
useTimeoutFn(() => {
|
||||
dialogStore.value.splice(index, 1);
|
||||
}, closeDelay);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 更改弹框自身属性值
|
||||
* @param value 属性值
|
||||
* @param key 属性,默认`title`
|
||||
* @param index 弹框索引(默认`0`,代表只有一个弹框,对于嵌套弹框要改哪个弹框的属性值就把该弹框索引赋给`index`)
|
||||
*/
|
||||
const updateDialog = (value: any, key = "title", index = 0) => {
|
||||
dialogStore.value[index][key] = value;
|
||||
};
|
||||
|
||||
/** 关闭所有弹框 */
|
||||
const closeAllDialog = () => {
|
||||
dialogStore.value = [];
|
||||
};
|
||||
|
||||
/** 千万别忘了在下面这三处引入并注册下,放心注册,不使用`addDialog`调用就不会被挂载
|
||||
* https://github.com/pure-admin/vue-pure-admin/blob/main/src/App.vue#L4
|
||||
* https://github.com/pure-admin/vue-pure-admin/blob/main/src/App.vue#L12
|
||||
* https://github.com/pure-admin/vue-pure-admin/blob/main/src/App.vue#L22
|
||||
*/
|
||||
const ReDialog = withInstall(reDialog);
|
||||
|
||||
export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions };
|
||||
export {
|
||||
ReDialog,
|
||||
dialogStore,
|
||||
addDialog,
|
||||
closeDialog,
|
||||
updateDialog,
|
||||
closeAllDialog
|
||||
};
|
||||
206
Yi.Pure.Vue3/src/components/ReDialog/index.vue
Normal file
@@ -0,0 +1,206 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
type EventType,
|
||||
type ButtonProps,
|
||||
type DialogOptions,
|
||||
closeDialog,
|
||||
dialogStore
|
||||
} 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({
|
||||
name: "ReDialog"
|
||||
});
|
||||
|
||||
const sureBtnMap = ref({});
|
||||
const fullscreen = ref(false);
|
||||
|
||||
const footerButtons = computed(() => {
|
||||
return (options: DialogOptions) => {
|
||||
return options?.footerButtons?.length > 0
|
||||
? options.footerButtons
|
||||
: ([
|
||||
{
|
||||
label: "取消",
|
||||
text: true,
|
||||
bg: true,
|
||||
btnClick: ({ dialog: { options, index } }) => {
|
||||
const done = () =>
|
||||
closeDialog(options, index, { command: "cancel" });
|
||||
if (options?.beforeCancel && isFunction(options?.beforeCancel)) {
|
||||
options.beforeCancel(done, { options, index });
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "确定",
|
||||
type: "primary",
|
||||
text: true,
|
||||
bg: true,
|
||||
popconfirm: options?.popconfirm,
|
||||
btnClick: ({ dialog: { options, index } }) => {
|
||||
if (options?.sureBtnLoading) {
|
||||
sureBtnMap.value[index] = Object.assign(
|
||||
{},
|
||||
sureBtnMap.value[index],
|
||||
{
|
||||
loading: true
|
||||
}
|
||||
);
|
||||
}
|
||||
const closeLoading = () => {
|
||||
if (options?.sureBtnLoading) {
|
||||
sureBtnMap.value[index].loading = false;
|
||||
}
|
||||
};
|
||||
const done = () => {
|
||||
closeLoading();
|
||||
closeDialog(options, index, { command: "sure" });
|
||||
};
|
||||
if (options?.beforeSure && isFunction(options?.beforeSure)) {
|
||||
options.beforeSure(done, { options, index, closeLoading });
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
}
|
||||
}
|
||||
] as Array<ButtonProps>);
|
||||
};
|
||||
});
|
||||
|
||||
const fullscreenClass = computed(() => {
|
||||
return [
|
||||
"el-icon",
|
||||
"el-dialog__close",
|
||||
"-translate-x-2",
|
||||
"cursor-pointer",
|
||||
"hover:!text-[red]"
|
||||
];
|
||||
});
|
||||
|
||||
function eventsCallBack(
|
||||
event: EventType,
|
||||
options: DialogOptions,
|
||||
index: number,
|
||||
isClickFullScreen = false
|
||||
) {
|
||||
if (!isClickFullScreen) fullscreen.value = options?.fullscreen ?? false;
|
||||
if (options?.[event] && isFunction(options?.[event])) {
|
||||
return options?.[event]({ options, index });
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose(
|
||||
options: DialogOptions,
|
||||
index: number,
|
||||
args = { command: "close" }
|
||||
) {
|
||||
closeDialog(options, index, args);
|
||||
eventsCallBack("close", options, index);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
v-for="(options, index) in dialogStore"
|
||||
:key="index"
|
||||
v-bind="options"
|
||||
v-model="options.visible"
|
||||
class="pure-dialog"
|
||||
:fullscreen="fullscreen ? true : options?.fullscreen ? true : false"
|
||||
@closed="handleClose(options, index)"
|
||||
@opened="eventsCallBack('open', options, index)"
|
||||
@openAutoFocus="eventsCallBack('openAutoFocus', options, index)"
|
||||
@closeAutoFocus="eventsCallBack('closeAutoFocus', options, index)"
|
||||
>
|
||||
<!-- header -->
|
||||
<template
|
||||
v-if="options?.fullscreenIcon || options?.headerRenderer"
|
||||
#header="{ close, titleId, titleClass }"
|
||||
>
|
||||
<div
|
||||
v-if="options?.fullscreenIcon"
|
||||
class="flex items-center justify-between"
|
||||
>
|
||||
<span :id="titleId" :class="titleClass">{{ options?.title }}</span>
|
||||
<i
|
||||
v-if="!options?.fullscreen"
|
||||
:class="fullscreenClass"
|
||||
@click="
|
||||
() => {
|
||||
fullscreen = !fullscreen;
|
||||
eventsCallBack(
|
||||
'fullscreenCallBack',
|
||||
{ ...options, fullscreen },
|
||||
index,
|
||||
true
|
||||
);
|
||||
}
|
||||
"
|
||||
>
|
||||
<IconifyIconOffline
|
||||
class="pure-dialog-svg"
|
||||
:icon="
|
||||
options?.fullscreen
|
||||
? ExitFullscreen
|
||||
: fullscreen
|
||||
? ExitFullscreen
|
||||
: Fullscreen
|
||||
"
|
||||
/>
|
||||
</i>
|
||||
</div>
|
||||
<component
|
||||
:is="options?.headerRenderer({ close, titleId, titleClass })"
|
||||
v-else
|
||||
/>
|
||||
</template>
|
||||
<component
|
||||
v-bind="options?.props"
|
||||
:is="options.contentRenderer({ options, index })"
|
||||
@close="args => handleClose(options, index, args)"
|
||||
/>
|
||||
<!-- footer -->
|
||||
<template v-if="!options?.hideFooter" #footer>
|
||||
<template v-if="options?.footerRenderer">
|
||||
<component :is="options?.footerRenderer({ options, index })" />
|
||||
</template>
|
||||
<span v-else>
|
||||
<template v-for="(btn, key) in footerButtons(options)" :key="key">
|
||||
<el-popconfirm
|
||||
v-if="btn.popconfirm"
|
||||
v-bind="btn.popconfirm"
|
||||
@confirm="
|
||||
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>
|
||||
275
Yi.Pure.Vue3/src/components/ReDialog/type.ts
Normal file
@@ -0,0 +1,275 @@
|
||||
import type { CSSProperties, VNode, Component } from "vue";
|
||||
|
||||
type DoneFn = (cancel?: boolean) => void;
|
||||
type EventType =
|
||||
| "open"
|
||||
| "close"
|
||||
| "openAutoFocus"
|
||||
| "closeAutoFocus"
|
||||
| "fullscreenCallBack";
|
||||
type ArgsType = {
|
||||
/** `cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或空白页或按下了esc键 */
|
||||
command: "cancel" | "sure" | "close";
|
||||
};
|
||||
type ButtonType =
|
||||
| "primary"
|
||||
| "success"
|
||||
| "warning"
|
||||
| "danger"
|
||||
| "info"
|
||||
| "text";
|
||||
|
||||
/** https://element-plus.org/zh-CN/component/dialog.html#attributes */
|
||||
type DialogProps = {
|
||||
/** `Dialog` 的显示与隐藏 */
|
||||
visible?: boolean;
|
||||
/** `Dialog` 的标题 */
|
||||
title?: string;
|
||||
/** `Dialog` 的宽度,默认 `50%` */
|
||||
width?: string | number;
|
||||
/** 是否为全屏 `Dialog`(会一直处于全屏状态,除非弹框关闭),默认 `false`,`fullscreen` 和 `fullscreenIcon` 都传时只有 `fullscreen` 会生效 */
|
||||
fullscreen?: boolean;
|
||||
/** 是否显示全屏操作图标,默认 `false`,`fullscreen` 和 `fullscreenIcon` 都传时只有 `fullscreen` 会生效 */
|
||||
fullscreenIcon?: boolean;
|
||||
/** `Dialog CSS` 中的 `margin-top` 值,默认 `15vh` */
|
||||
top?: string;
|
||||
/** 是否需要遮罩层,默认 `true` */
|
||||
modal?: boolean;
|
||||
/** `Dialog` 自身是否插入至 `body` 元素上。嵌套的 `Dialog` 必须指定该属性并赋值为 `true`,默认 `false` */
|
||||
appendToBody?: boolean;
|
||||
/** 是否在 `Dialog` 出现时将 `body` 滚动锁定,默认 `true` */
|
||||
lockScroll?: boolean;
|
||||
/** `Dialog` 的自定义类名 */
|
||||
class?: string;
|
||||
/** `Dialog` 的自定义样式 */
|
||||
style?: CSSProperties;
|
||||
/** `Dialog` 打开的延时时间,单位毫秒,默认 `0` */
|
||||
openDelay?: number;
|
||||
/** `Dialog` 关闭的延时时间,单位毫秒,默认 `0` */
|
||||
closeDelay?: number;
|
||||
/** 是否可以通过点击 `modal` 关闭 `Dialog`,默认 `true` */
|
||||
closeOnClickModal?: boolean;
|
||||
/** 是否可以通过按下 `ESC` 关闭 `Dialog`,默认 `true` */
|
||||
closeOnPressEscape?: boolean;
|
||||
/** 是否显示关闭按钮,默认 `true` */
|
||||
showClose?: boolean;
|
||||
/** 关闭前的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
|
||||
beforeClose?: (done: DoneFn) => void;
|
||||
/** 为 `Dialog` 启用可拖拽功能,默认 `false` */
|
||||
draggable?: boolean;
|
||||
/** 是否让 `Dialog` 的 `header` 和 `footer` 部分居中排列,默认 `false` */
|
||||
center?: boolean;
|
||||
/** 是否水平垂直对齐对话框,默认 `false` */
|
||||
alignCenter?: boolean;
|
||||
/** 当关闭 `Dialog` 时,销毁其中的元素,默认 `false` */
|
||||
destroyOnClose?: boolean;
|
||||
};
|
||||
|
||||
//element-plus.org/zh-CN/component/popconfirm.html#attributes
|
||||
type Popconfirm = {
|
||||
/** 标题 */
|
||||
title?: string;
|
||||
/** 确定按钮文字 */
|
||||
confirmButtonText?: string;
|
||||
/** 取消按钮文字 */
|
||||
cancelButtonText?: string;
|
||||
/** 确定按钮类型,默认 `primary` */
|
||||
confirmButtonType?: ButtonType;
|
||||
/** 取消按钮类型,默认 `text` */
|
||||
cancelButtonType?: ButtonType;
|
||||
/** 自定义图标,默认 `QuestionFilled` */
|
||||
icon?: string | Component;
|
||||
/** `Icon` 颜色,默认 `#f90` */
|
||||
iconColor?: string;
|
||||
/** 是否隐藏 `Icon`,默认 `false` */
|
||||
hideIcon?: boolean;
|
||||
/** 关闭时的延迟,默认 `200` */
|
||||
hideAfter?: number;
|
||||
/** 是否将 `popover` 的下拉列表插入至 `body` 元素,默认 `true` */
|
||||
teleported?: boolean;
|
||||
/** 当 `popover` 组件长时间不触发且 `persistent` 属性设置为 `false` 时, `popover` 将会被删除,默认 `false` */
|
||||
persistent?: boolean;
|
||||
/** 弹层宽度,最小宽度 `150px`,默认 `150` */
|
||||
width?: string | number;
|
||||
};
|
||||
|
||||
type BtnClickDialog = {
|
||||
options?: DialogOptions;
|
||||
index?: number;
|
||||
};
|
||||
type BtnClickButton = {
|
||||
btn?: ButtonProps;
|
||||
index?: number;
|
||||
};
|
||||
/** https://element-plus.org/zh-CN/component/button.html#button-attributes */
|
||||
type ButtonProps = {
|
||||
/** 按钮文字 */
|
||||
label: string;
|
||||
/** 按钮尺寸 */
|
||||
size?: "large" | "default" | "small";
|
||||
/** 按钮类型 */
|
||||
type?: "primary" | "success" | "warning" | "danger" | "info";
|
||||
/** 是否为朴素按钮,默认 `false` */
|
||||
plain?: boolean;
|
||||
/** 是否为文字按钮,默认 `false` */
|
||||
text?: boolean;
|
||||
/** 是否显示文字按钮背景颜色,默认 `false` */
|
||||
bg?: boolean;
|
||||
/** 是否为链接按钮,默认 `false` */
|
||||
link?: boolean;
|
||||
/** 是否为圆角按钮,默认 `false` */
|
||||
round?: boolean;
|
||||
/** 是否为圆形按钮,默认 `false` */
|
||||
circle?: boolean;
|
||||
/** 确定按钮的 `Popconfirm` 气泡确认框相关配置 */
|
||||
popconfirm?: Popconfirm;
|
||||
/** 是否为加载中状态,默认 `false` */
|
||||
loading?: boolean;
|
||||
/** 自定义加载中状态图标组件 */
|
||||
loadingIcon?: string | Component;
|
||||
/** 按钮是否为禁用状态,默认 `false` */
|
||||
disabled?: boolean;
|
||||
/** 图标组件 */
|
||||
icon?: string | Component;
|
||||
/** 是否开启原生 `autofocus` 属性,默认 `false` */
|
||||
autofocus?: boolean;
|
||||
/** 原生 `type` 属性,默认 `button` */
|
||||
nativeType?: "button" | "submit" | "reset";
|
||||
/** 自动在两个中文字符之间插入空格 */
|
||||
autoInsertSpace?: boolean;
|
||||
/** 自定义按钮颜色, 并自动计算 `hover` 和 `active` 触发后的颜色 */
|
||||
color?: string;
|
||||
/** `dark` 模式, 意味着自动设置 `color` 为 `dark` 模式的颜色,默认 `false` */
|
||||
dark?: boolean;
|
||||
/** 自定义元素标签 */
|
||||
tag?: string | Component;
|
||||
/** 点击按钮后触发的回调 */
|
||||
btnClick?: ({
|
||||
dialog,
|
||||
button
|
||||
}: {
|
||||
/** 当前 `Dialog` 信息 */
|
||||
dialog: BtnClickDialog;
|
||||
/** 当前 `button` 信息 */
|
||||
button: BtnClickButton;
|
||||
}) => void;
|
||||
};
|
||||
|
||||
interface DialogOptions extends DialogProps {
|
||||
/** 内容区组件的 `props`,可通过 `defineProps` 接收 */
|
||||
props?: any;
|
||||
/** 是否隐藏 `Dialog` 按钮操作区的内容 */
|
||||
hideFooter?: boolean;
|
||||
/** 确定按钮的 `Popconfirm` 气泡确认框相关配置 */
|
||||
popconfirm?: Popconfirm;
|
||||
/** 点击确定按钮后是否开启 `loading` 加载动画 */
|
||||
sureBtnLoading?: boolean;
|
||||
/**
|
||||
* @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}
|
||||
*/
|
||||
headerRenderer?: ({
|
||||
close,
|
||||
titleId,
|
||||
titleClass
|
||||
}: {
|
||||
close: Function;
|
||||
titleId: string;
|
||||
titleClass: string;
|
||||
}) => VNode | Component;
|
||||
/** 自定义内容渲染器 */
|
||||
contentRenderer?: ({
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DialogOptions;
|
||||
index: number;
|
||||
}) => VNode | Component;
|
||||
/** 自定义按钮操作区的内容渲染器,会覆盖`footerButtons`以及默认的 `取消` 和 `确定` 按钮 */
|
||||
footerRenderer?: ({
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DialogOptions;
|
||||
index: number;
|
||||
}) => VNode | Component;
|
||||
/** 自定义底部按钮操作 */
|
||||
footerButtons?: Array<ButtonProps>;
|
||||
/** `Dialog` 打开后的回调 */
|
||||
open?: ({
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DialogOptions;
|
||||
index: number;
|
||||
}) => void;
|
||||
/** `Dialog` 关闭后的回调(只有点击右上角关闭按钮或空白页或按下了esc键关闭页面时才会触发) */
|
||||
close?: ({
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DialogOptions;
|
||||
index: number;
|
||||
}) => void;
|
||||
/** `Dialog` 关闭后的回调。 `args` 返回的 `command` 值解析:`cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或空白页或按下了esc键 */
|
||||
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 };
|
||||
39
Yi.Pure.Vue3/src/components/ReFlicker/index.css
Normal file
@@ -0,0 +1,39 @@
|
||||
.point {
|
||||
width: var(--point-width);
|
||||
height: var(--point-height);
|
||||
background: var(--point-background);
|
||||
position: relative;
|
||||
border-radius: var(--point-border-radius);
|
||||
}
|
||||
|
||||
.point-flicker:after {
|
||||
background: var(--point-background);
|
||||
}
|
||||
|
||||
.point-flicker:before,
|
||||
.point-flicker:after {
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
border-radius: var(--point-border-radius);
|
||||
animation: flicker 1.2s ease-out infinite;
|
||||
}
|
||||
|
||||
@keyframes flicker {
|
||||
0% {
|
||||
transform: scale(0.5);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
30% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(var(--point-scale));
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
44
Yi.Pure.Vue3/src/components/ReFlicker/index.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import "./index.css";
|
||||
import { type Component, h, defineComponent } from "vue";
|
||||
|
||||
export interface attrsType {
|
||||
width?: string;
|
||||
height?: string;
|
||||
borderRadius?: number | string;
|
||||
background?: string;
|
||||
scale?: number | string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 圆点、方形闪烁动画组件
|
||||
* @param width 可选 string 宽
|
||||
* @param height 可选 string 高
|
||||
* @param borderRadius 可选 number | string 传0为方形、传50%或者不传为圆形
|
||||
* @param background 可选 string 闪烁颜色
|
||||
* @param scale 可选 number | string 闪烁范围,默认2,值越大闪烁范围越大
|
||||
* @returns Component
|
||||
*/
|
||||
export function useRenderFlicker(attrs?: attrsType): Component {
|
||||
return defineComponent({
|
||||
name: "ReFlicker",
|
||||
render() {
|
||||
return h(
|
||||
"div",
|
||||
{
|
||||
class: "point point-flicker",
|
||||
style: {
|
||||
"--point-width": attrs?.width ?? "12px",
|
||||
"--point-height": attrs?.height ?? "12px",
|
||||
"--point-background":
|
||||
attrs?.background ?? "var(--el-color-primary)",
|
||||
"--point-border-radius": attrs?.borderRadius ?? "50%",
|
||||
"--point-scale": attrs?.scale ?? "2"
|
||||
}
|
||||
},
|
||||
{
|
||||
default: () => []
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
7
Yi.Pure.Vue3/src/components/ReFlop/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import reFlop from "./src/index.vue";
|
||||
import { withInstall } from "@pureadmin/utils";
|
||||
|
||||
/** 时间翻牌组件 */
|
||||
export const ReFlop = withInstall(reFlop);
|
||||
|
||||
export default ReFlop;
|
||||
184
Yi.Pure.Vue3/src/components/ReFlop/src/filpper.css
Normal file
@@ -0,0 +1,184 @@
|
||||
.m-flipper {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 60px;
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
border: solid 1px #000;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
font-size: 66px;
|
||||
color: #fff;
|
||||
box-shadow: 0 0 6px rgb(0 0 0 / 50%);
|
||||
text-align: center;
|
||||
font-family: "Helvetica Neue";
|
||||
}
|
||||
|
||||
.m-flipper .digital::before,
|
||||
.m-flipper .digital::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #000;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.m-flipper .digital::before {
|
||||
top: 0;
|
||||
bottom: 50%;
|
||||
border-radius: 10px 10px 0 0;
|
||||
border-bottom: solid 1px #666;
|
||||
}
|
||||
|
||||
.m-flipper .digital::after {
|
||||
top: 50%;
|
||||
bottom: 0;
|
||||
border-radius: 0 0 10px 10px;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
/* 向下翻 */
|
||||
.m-flipper.down .front::before {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.m-flipper.down .back::after {
|
||||
z-index: 2;
|
||||
transform-origin: 50% 0%;
|
||||
transform: perspective(160px) rotateX(180deg);
|
||||
}
|
||||
|
||||
.m-flipper.down .front::after,
|
||||
.m-flipper.down .back::before {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.m-flipper.down.go .front::before {
|
||||
transform-origin: 50% 100%;
|
||||
animation: frontFlipDown 0.6s ease-in-out both;
|
||||
box-shadow: 0 -2px 6px rgb(255 255 255 / 30%);
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.m-flipper.down.go .back::after {
|
||||
animation: backFlipDown 0.6s ease-in-out both;
|
||||
}
|
||||
|
||||
/* 向上翻 */
|
||||
.m-flipper.up .front::after {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.m-flipper.up .back::before {
|
||||
z-index: 2;
|
||||
transform-origin: 50% 100%;
|
||||
transform: perspective(160px) rotateX(-180deg);
|
||||
}
|
||||
|
||||
.m-flipper.up .front::before,
|
||||
.m-flipper.up .back::after {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.m-flipper.up.go .front::after {
|
||||
transform-origin: 50% 0;
|
||||
animation: frontFlipUp 0.6s ease-in-out both;
|
||||
box-shadow: 0 2px 6px rgb(255 255 255 / 30%);
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.m-flipper.up.go .back::before {
|
||||
animation: backFlipUp 0.6s ease-in-out both;
|
||||
}
|
||||
|
||||
@keyframes frontFlipDown {
|
||||
0% {
|
||||
transform: perspective(160px) rotateX(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: perspective(160px) rotateX(-180deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes backFlipDown {
|
||||
0% {
|
||||
transform: perspective(160px) rotateX(180deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: perspective(160px) rotateX(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes frontFlipUp {
|
||||
0% {
|
||||
transform: perspective(160px) rotateX(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: perspective(160px) rotateX(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes backFlipUp {
|
||||
0% {
|
||||
transform: perspective(160px) rotateX(-180deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: perspective(160px) rotateX(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
.m-flipper .number0::before,
|
||||
.m-flipper .number0::after {
|
||||
content: "0";
|
||||
}
|
||||
|
||||
.m-flipper .number1::before,
|
||||
.m-flipper .number1::after {
|
||||
content: "1";
|
||||
}
|
||||
|
||||
.m-flipper .number2::before,
|
||||
.m-flipper .number2::after {
|
||||
content: "2";
|
||||
}
|
||||
|
||||
.m-flipper .number3::before,
|
||||
.m-flipper .number3::after {
|
||||
content: "3";
|
||||
}
|
||||
|
||||
.m-flipper .number4::before,
|
||||
.m-flipper .number4::after {
|
||||
content: "4";
|
||||
}
|
||||
|
||||
.m-flipper .number5::before,
|
||||
.m-flipper .number5::after {
|
||||
content: "5";
|
||||
}
|
||||
|
||||
.m-flipper .number6::before,
|
||||
.m-flipper .number6::after {
|
||||
content: "6";
|
||||
}
|
||||
|
||||
.m-flipper .number7::before,
|
||||
.m-flipper .number7::after {
|
||||
content: "7";
|
||||
}
|
||||
|
||||
.m-flipper .number8::before,
|
||||
.m-flipper .number8::after {
|
||||
content: "8";
|
||||
}
|
||||
|
||||
.m-flipper .number9::before,
|
||||
.m-flipper .number9::after {
|
||||
content: "9";
|
||||
}
|
||||
92
Yi.Pure.Vue3/src/components/ReFlop/src/filpper.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import "./filpper.css";
|
||||
import propTypes from "@/utils/propTypes";
|
||||
import { defineComponent, ref } from "vue";
|
||||
|
||||
const props = {
|
||||
// front paper text
|
||||
// 前牌文字
|
||||
frontText: propTypes.number.def(0),
|
||||
// back paper text
|
||||
// 后牌文字
|
||||
backText: propTypes.number.def(1),
|
||||
// flipping duration, please be consistent with the CSS animation-duration value.
|
||||
// 翻牌动画时间,与CSS中设置的animation-duration保持一致
|
||||
duration: propTypes.number.def(600)
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: "ReFlop",
|
||||
props,
|
||||
setup(props) {
|
||||
const { frontText, backText, duration } = props;
|
||||
const isFlipping = ref(false);
|
||||
const flipType = ref("down");
|
||||
const frontTextFromData = ref(frontText);
|
||||
const backTextFromData = ref(backText);
|
||||
|
||||
const textClass = (number: number) => {
|
||||
return "number" + number;
|
||||
};
|
||||
|
||||
const flip = (type: string, front: number, back: number) => {
|
||||
// 如果处于翻转中,则不执行
|
||||
if (isFlipping.value) return false;
|
||||
frontTextFromData.value = front;
|
||||
backTextFromData.value = back;
|
||||
// 根据传递过来的type设置翻转方向
|
||||
flipType.value = type;
|
||||
// 设置翻转状态为true
|
||||
isFlipping.value = true;
|
||||
|
||||
setTimeout(() => {
|
||||
// 设置翻转状态为false
|
||||
isFlipping.value = false;
|
||||
frontTextFromData.value = back;
|
||||
}, duration);
|
||||
};
|
||||
|
||||
// 下翻牌
|
||||
const flipDown = (front: any, back: any): void => {
|
||||
flip("down", front, back);
|
||||
};
|
||||
|
||||
// 上翻牌
|
||||
const flipUp = (front: any, back: any): void => {
|
||||
flip("up", front, back);
|
||||
};
|
||||
|
||||
// 设置前牌文字
|
||||
function setFront(text: number): void {
|
||||
frontTextFromData.value = text;
|
||||
}
|
||||
|
||||
// 设置后牌文字
|
||||
const setBack = (text: number): void => {
|
||||
backTextFromData.value = text;
|
||||
};
|
||||
|
||||
return {
|
||||
flipType,
|
||||
isFlipping,
|
||||
frontTextFromData,
|
||||
backTextFromData,
|
||||
textClass,
|
||||
flipDown,
|
||||
flipUp,
|
||||
setFront,
|
||||
setBack
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
const main = `m-flipper ${this.flipType} ${this.isFlipping ? "go" : ""}`;
|
||||
const front = `digital front ${this.textClass(this.frontTextFromData)}`;
|
||||
const back = `digital back ${this.textClass(this.backTextFromData)}`;
|
||||
return (
|
||||
<div class={main}>
|
||||
<div class={front} />
|
||||
<div class={back} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
135
Yi.Pure.Vue3/src/components/ReFlop/src/index.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<script setup lang="ts">
|
||||
import flippers from "./filpper";
|
||||
import { ref, unref, nextTick, onUnmounted } from "vue";
|
||||
|
||||
defineOptions({
|
||||
name: "ReFlop"
|
||||
});
|
||||
|
||||
const timer = ref(null);
|
||||
const flipObjs = ref([]);
|
||||
|
||||
const flipperHour1 = ref();
|
||||
const flipperHour2 = ref();
|
||||
const flipperMinute1 = ref();
|
||||
const flipperMinute2 = ref();
|
||||
const flipperSecond1 = ref();
|
||||
const flipperSecond2 = ref();
|
||||
|
||||
// 初始化数字
|
||||
const init = () => {
|
||||
const now = new Date();
|
||||
const nowTimeStr = formatDate(new Date(now.getTime()), "hhiiss");
|
||||
for (let i = 0; i < flipObjs.value.length; i++) {
|
||||
flipObjs?.value[i]?.setFront(nowTimeStr[i]);
|
||||
}
|
||||
};
|
||||
|
||||
// 开始计时
|
||||
const run = () => {
|
||||
timer.value = setInterval(() => {
|
||||
// 获取当前时间
|
||||
const now = new Date();
|
||||
const nowTimeStr = formatDate(new Date(now.getTime() - 1000), "hhiiss");
|
||||
const nextTimeStr = formatDate(now, "hhiiss");
|
||||
for (let i = 0; i < flipObjs.value.length; i++) {
|
||||
if (nowTimeStr[i] === nextTimeStr[i]) {
|
||||
continue;
|
||||
}
|
||||
flipObjs?.value[i]?.flipDown(nowTimeStr[i], nextTimeStr[i]);
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
// 正则格式化日期
|
||||
const formatDate = (date: Date, dateFormat: string) => {
|
||||
/* 单独格式化年份,根据y的字符数量输出年份
|
||||
* 例如:yyyy => 2019
|
||||
yy => 19
|
||||
y => 9
|
||||
*/
|
||||
if (/(y+)/.test(dateFormat)) {
|
||||
dateFormat = dateFormat.replace(
|
||||
RegExp.$1,
|
||||
(date.getFullYear() + "").substr(4 - RegExp.$1.length)
|
||||
);
|
||||
}
|
||||
// 格式化月、日、时、分、秒
|
||||
const o = {
|
||||
"m+": date.getMonth() + 1,
|
||||
"d+": date.getDate(),
|
||||
"h+": date.getHours(),
|
||||
"i+": date.getMinutes(),
|
||||
"s+": date.getSeconds()
|
||||
};
|
||||
for (const k in o) {
|
||||
if (new RegExp(`(${k})`).test(dateFormat)) {
|
||||
// 取出对应的值
|
||||
const str = o[k] + "";
|
||||
/* 根据设置的格式,输出对应的字符
|
||||
* 例如: 早上8时,hh => 08,h => 8
|
||||
* 但是,当数字>=10时,无论格式为一位还是多位,不做截取,这是与年份格式化不一致的地方
|
||||
* 例如: 下午15时,hh => 15, h => 15
|
||||
*/
|
||||
dateFormat = dateFormat.replace(
|
||||
RegExp.$1,
|
||||
RegExp.$1.length === 1 ? str : padLeftZero(str)
|
||||
);
|
||||
}
|
||||
}
|
||||
return dateFormat;
|
||||
};
|
||||
|
||||
// 日期时间补零
|
||||
const padLeftZero = (str: string | any[]) => {
|
||||
return ("00" + str).substr(str.length);
|
||||
};
|
||||
|
||||
nextTick(() => {
|
||||
flipObjs.value = [
|
||||
unref(flipperHour1),
|
||||
unref(flipperHour2),
|
||||
unref(flipperMinute1),
|
||||
unref(flipperMinute2),
|
||||
unref(flipperSecond1),
|
||||
unref(flipperSecond2)
|
||||
];
|
||||
|
||||
init();
|
||||
run();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer.value) {
|
||||
clearInterval(timer.value);
|
||||
timer.value = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flip-clock">
|
||||
<flippers ref="flipperHour1" />
|
||||
<flippers ref="flipperHour2" />
|
||||
<em>:</em>
|
||||
<flippers ref="flipperMinute1" />
|
||||
<flippers ref="flipperMinute2" />
|
||||
<em>:</em>
|
||||
<flippers ref="flipperSecond1" />
|
||||
<flippers ref="flipperSecond2" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.flip-clock .m-flipper {
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
.flip-clock em {
|
||||
display: inline-block;
|
||||
font-size: 66px;
|
||||
font-style: normal;
|
||||
line-height: 102px;
|
||||
vertical-align: top;
|
||||
}
|
||||
</style>
|
||||
17
Yi.Pure.Vue3/src/components/ReFlowChart/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import control from "./src/Control.vue";
|
||||
import nodePanel from "./src/NodePanel.vue";
|
||||
import dataDialog from "./src/DataDialog.vue";
|
||||
import { withInstall } from "@pureadmin/utils";
|
||||
|
||||
/** LogicFlow流程图-控制面板 */
|
||||
const Control = withInstall(control);
|
||||
|
||||
/** LogicFlow流程图-拖拽面板 */
|
||||
const NodePanel = withInstall(nodePanel);
|
||||
|
||||
/** LogicFlow流程图-查看数据 */
|
||||
const DataDialog = withInstall(dataDialog);
|
||||
|
||||
export { Control, NodePanel, DataDialog };
|
||||
|
||||
// LogicFlow流程图文档:http://logic-flow.org/
|
||||
147
Yi.Pure.Vue3/src/components/ReFlowChart/src/Control.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, unref, onMounted } from "vue";
|
||||
import { LogicFlow } from "@logicflow/core";
|
||||
|
||||
interface Props {
|
||||
lf: LogicFlow;
|
||||
catTurboData?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
lf: null
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "catData"): void;
|
||||
}>();
|
||||
|
||||
const controlButton3 = ref();
|
||||
const controlButton4 = ref();
|
||||
|
||||
const focusIndex = ref<Number>(-1);
|
||||
const titleLists = ref([
|
||||
{
|
||||
icon: "icon-zoom-out-hs",
|
||||
text: "缩小",
|
||||
size: "18",
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
icon: "icon-enlarge-hs",
|
||||
text: "放大",
|
||||
size: "18",
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
icon: "icon-full-screen-hs",
|
||||
text: "适应",
|
||||
size: "15",
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
icon: "icon-previous-hs",
|
||||
text: "上一步",
|
||||
size: "15",
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
icon: "icon-next-step-hs",
|
||||
text: "下一步",
|
||||
size: "17",
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
icon: "icon-download-hs",
|
||||
text: "下载图片",
|
||||
size: "17",
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
icon: "icon-watch-hs",
|
||||
text: "查看数据",
|
||||
size: "17",
|
||||
disabled: false
|
||||
}
|
||||
]);
|
||||
|
||||
const onControl = (item, key) => {
|
||||
["zoom", "zoom", "resetZoom", "undo", "redo", "getSnapshot"].forEach(
|
||||
(v, i) => {
|
||||
const domControl = props.lf;
|
||||
if (key === 1) {
|
||||
domControl.zoom(true);
|
||||
}
|
||||
if (key === 6) {
|
||||
emit("catData");
|
||||
}
|
||||
if (key === i) {
|
||||
domControl[v]();
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const onEnter = key => {
|
||||
focusIndex.value = key;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
props.lf.on("history:change", ({ data: { undoAble, redoAble } }) => {
|
||||
unref(titleLists)[3].disabled = unref(controlButton3).disabled = !undoAble;
|
||||
unref(titleLists)[4].disabled = unref(controlButton4).disabled = !redoAble;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="control-container">
|
||||
<!-- 功能按钮 -->
|
||||
<ul>
|
||||
<li
|
||||
v-for="(item, key) in titleLists"
|
||||
:key="key"
|
||||
:title="item.text"
|
||||
class="dark:text-bg_color"
|
||||
@mouseenter.prevent="onEnter(key)"
|
||||
@mouseleave.prevent="focusIndex = -1"
|
||||
>
|
||||
<button
|
||||
:ref="'controlButton' + key"
|
||||
v-tippy="{
|
||||
content: item.text
|
||||
}"
|
||||
:disabled="item.disabled"
|
||||
:style="{
|
||||
cursor: item.disabled === false ? 'pointer' : 'not-allowed',
|
||||
color: item.disabled === false ? '' : '#00000040',
|
||||
background: 'transparent'
|
||||
}"
|
||||
@click="onControl(item, key)"
|
||||
>
|
||||
<span
|
||||
:class="'iconfont ' + item.icon"
|
||||
:style="{ fontSize: `${item.size}px` }"
|
||||
/>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@import url("./assets/iconfont/iconfont.css");
|
||||
|
||||
.control-container {
|
||||
background: hsl(0deg 0% 100% / 80%);
|
||||
box-shadow: 0 1px 4px rgb(0 0 0 / 20%);
|
||||
}
|
||||
|
||||
.control-container ul li {
|
||||
margin: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.control-container ul li span:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
</style>
|
||||
17
Yi.Pure.Vue3/src/components/ReFlowChart/src/DataDialog.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import VueJsonPretty from "vue-json-pretty";
|
||||
import "vue-json-pretty/lib/styles.css";
|
||||
|
||||
defineProps({
|
||||
graphData: Object
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<vue-json-pretty
|
||||
:path="'res'"
|
||||
:deep="3"
|
||||
:showLength="true"
|
||||
:data="graphData"
|
||||
/>
|
||||
</template>
|
||||
154
Yi.Pure.Vue3/src/components/ReFlowChart/src/NodePanel.vue
Normal file
166
Yi.Pure.Vue3/src/components/ReFlowChart/src/adpterForTurbo.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
const TurboType = {
|
||||
SEQUENCE_FLOW: 1,
|
||||
START_EVENT: 2,
|
||||
END_EVENT: 3,
|
||||
USER_TASK: 4,
|
||||
SERVICE_TASK: 5,
|
||||
EXCLUSIVE_GATEWAY: 6
|
||||
};
|
||||
|
||||
function getTurboType(type) {
|
||||
switch (type) {
|
||||
case "bpmn:sequenceFlow":
|
||||
return TurboType.SEQUENCE_FLOW;
|
||||
case "bpmn:startEvent":
|
||||
return TurboType.START_EVENT;
|
||||
case "bpmn:endEvent":
|
||||
return TurboType.END_EVENT;
|
||||
case "bpmn:userTask":
|
||||
return TurboType.USER_TASK;
|
||||
case "bpmn:serviceTask":
|
||||
return TurboType.SERVICE_TASK;
|
||||
case "bpmn:exclusiveGateway":
|
||||
return TurboType.EXCLUSIVE_GATEWAY;
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
function convertNodeToTurboElement(node) {
|
||||
const { id, type, x, y, text = "", properties } = node;
|
||||
return {
|
||||
incoming: [],
|
||||
outgoing: [],
|
||||
dockers: [],
|
||||
type: getTurboType(node.type),
|
||||
properties: {
|
||||
...properties,
|
||||
name: (text && text.value) || "",
|
||||
x: x,
|
||||
y: y,
|
||||
text,
|
||||
logicFlowType: type
|
||||
},
|
||||
key: id
|
||||
};
|
||||
}
|
||||
|
||||
function convertEdgeToTurboElement(edge) {
|
||||
const {
|
||||
id,
|
||||
type,
|
||||
sourceNodeId,
|
||||
targetNodeId,
|
||||
startPoint,
|
||||
endPoint,
|
||||
pointsList,
|
||||
text = "",
|
||||
properties
|
||||
} = edge;
|
||||
return {
|
||||
incoming: [sourceNodeId],
|
||||
outgoing: [targetNodeId],
|
||||
type: getTurboType(type),
|
||||
dockers: [],
|
||||
properties: {
|
||||
...properties,
|
||||
name: (text && text.value) || "",
|
||||
text,
|
||||
startPoint,
|
||||
endPoint,
|
||||
pointsList,
|
||||
logicFlowType: type
|
||||
},
|
||||
key: id
|
||||
};
|
||||
}
|
||||
|
||||
export function toTurboData(data) {
|
||||
const nodeMap = new Map();
|
||||
const turboData = {
|
||||
flowElementList: []
|
||||
};
|
||||
data.nodes.forEach(node => {
|
||||
const flowElement = convertNodeToTurboElement(node);
|
||||
turboData.flowElementList.push(flowElement);
|
||||
nodeMap.set(node.id, flowElement);
|
||||
});
|
||||
data.edges.forEach(edge => {
|
||||
const flowElement = convertEdgeToTurboElement(edge);
|
||||
const sourceElement = nodeMap.get(edge.sourceNodeId);
|
||||
sourceElement.outgoing.push(flowElement.key);
|
||||
const targetElement = nodeMap.get(edge.targetNodeId);
|
||||
targetElement.incoming.push(flowElement.key);
|
||||
turboData.flowElementList.push(flowElement);
|
||||
});
|
||||
return turboData;
|
||||
}
|
||||
|
||||
function convertFlowElementToEdge(element) {
|
||||
const { incoming, outgoing, properties, key } = element;
|
||||
const { text, startPoint, endPoint, pointsList, logicFlowType } = properties;
|
||||
const edge = {
|
||||
id: key,
|
||||
type: logicFlowType,
|
||||
sourceNodeId: incoming[0],
|
||||
targetNodeId: outgoing[0],
|
||||
text,
|
||||
startPoint,
|
||||
endPoint,
|
||||
pointsList,
|
||||
properties: {}
|
||||
};
|
||||
const excludeProperties = [
|
||||
"startPoint",
|
||||
"endPoint",
|
||||
"pointsList",
|
||||
"text",
|
||||
"logicFlowType"
|
||||
];
|
||||
Object.keys(element.properties).forEach(property => {
|
||||
if (excludeProperties.indexOf(property) === -1) {
|
||||
edge.properties[property] = element.properties[property];
|
||||
}
|
||||
});
|
||||
return edge;
|
||||
}
|
||||
|
||||
function convertFlowElementToNode(element) {
|
||||
const { properties, key } = element;
|
||||
const { x, y, text, logicFlowType } = properties;
|
||||
const node = {
|
||||
id: key,
|
||||
type: logicFlowType,
|
||||
x,
|
||||
y,
|
||||
text,
|
||||
properties: {}
|
||||
};
|
||||
const excludeProperties = ["x", "y", "text", "logicFlowType"];
|
||||
Object.keys(element.properties).forEach(property => {
|
||||
if (excludeProperties.indexOf(property) === -1) {
|
||||
node.properties[property] = element.properties[property];
|
||||
}
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
export function toLogicflowData(data) {
|
||||
const lfData = {
|
||||
nodes: [],
|
||||
edges: []
|
||||
};
|
||||
const list = data.flowElementList;
|
||||
list &&
|
||||
list.length > 0 &&
|
||||
list.forEach(element => {
|
||||
if (element.type === TurboType.SEQUENCE_FLOW) {
|
||||
const edge = convertFlowElementToEdge(element);
|
||||
lfData.edges.push(edge);
|
||||
} else {
|
||||
const node = convertFlowElementToNode(element);
|
||||
lfData.nodes.push(node);
|
||||
}
|
||||
});
|
||||
return lfData;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
@font-face {
|
||||
font-family: "iconfont";
|
||||
src: url("iconfont.eot?t=1618544337340"); /* IE9 */
|
||||
src:
|
||||
url("iconfont.eot?t=1618544337340#iefix") format("embedded-opentype"),
|
||||
/* IE6-IE8 */
|
||||
url("data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAZ0AAsAAAAADKgAAAYmAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDZAqLQIldATYCJAMgCxIABCAFhG0HgQkb6ApRlA9Sk+xngd1wXQyjTXRCW7pkEvLB0N9/pZhyo7nvIIK1Nisnipg3omjUREiURDXNNEL/jDRCI5H/riTu/9q0D5OakT05VaM3E4kMJI2QhanZillesmYnVT0pD5+399suTrCEkjDhqLtAxyURhIU6Ser/1tp8aDPgI2g7ex2ah+Q7i0rI+Gy9rSNYOtEEdPFQVkrlj/1c3oZFk6Sv/bYQqWUunsgkk8QRkrgkCJEKpUcO8zx0cFLQr+x6CEiNi0BN2YWV4MwJhmDEqhdU4BwR8oIOEXPCjGMzcoKDuLmnLwLw6vy9vMCFM6ggIW50umRpIbVW14U29L/QmIZgqDs5cD0JDKwCHFIylReQ51yFpO+XKBwDcjHltbq9801mxdeFzX8inbguoAq1yCWzpH95JuRUJIC0EDPH5nNGtIkkA4GgvROBocpEEKLCCBwVj0BRF/CJHFYhEo9WCbF1TCdgEEgF0A0Ee8NxioIeN97QzQqFMd2tdfIJC3KeK0T3eJYu0J07g6BVbCB0IiDVDNsQ1mFcbNxDCTk6IWEb2ShHfHxUlvAjkfj0mHDhC56GAL4CWMUgQXgEywDxuH0TBAD7gDZuRqtx7KWpnyTbushlJUpytdfnUvoS/pXG880npIYe3wueUdIJoa9HlRgdsYiF5QJv8C2zjIbzXERGQmwH0QylmjJfC4evBB8UUKQZMsAMG2aWMU6nc6s9m7X4Thn0gTfomgnm5d0qwX4v0rQH3GZn4Ajp8F2VeUcTTARpA+FfyLcpc+T05bOemT2fny8EH8Vn4LPFh3htyOtB3jDSJj34IpEQ3HNboUdasWNDQifcA8BfPPkTe6YaWp0nF/IrhQHGW2D5HTO7O2zfTH3+gxip/NioTs9VwUXL7T3AbzTxHa3qSu1e4EZTfZl/QiC2c7UI5jZ/ET938pSH8Z8IPBwU0NopeLgB7h6Kvp0GVCOw72KAjKFA71sPKX7/9g+Js/AmNfj8/o28sqNVdSTVI93p08F3v/75zqw8W79vb0RVaCTrw6aNntrQwCtbzzDKosTRFMjp/WFqtpZUEGxsi6P8L09byvlyrrvUJ6/ZFJR/X32mbUmndlduWjbdnwnY2ZBHo8OIKIVDUJah62hi4aKdSoqZsWypN7d0w6nsAzb12tWrqZOl12+W/W7YyLFxDy/7U369cgFF85PUVevYahz8y/HS9ZGrbv7saR0sn5MfEzhinC2Dizcv5xHycyChG33pcskigbRkvXnDaurRjRuIeDdu4rnSgPQ/L196FHQg6FGs7266c82aTtDT1jU0CqzWoG2Ndf91wRo1g/0wo9b4VPtV+2iwl/fjvxq4f83CBZeYgx6njp8mb7jzou9FfPdwBBpffvyUx6XARoc/1umGwtrl034lryLH/YCEwly/XrrckYHsd+/YWY/u3EGI085rV6RD5+Bw7dqnoAvBjzifw3S3zdaNZL/dRnfz7XZup232DX4VtD6Cn+AzkqFgBq6unr/gwtCDuydN51fk76ocHS/nN25Y/WqMe1fzBRgEQHPEjqE0gIbkR1CKM/zYUukn9ItRVMHwLfuO1kaP2mlUivpAUpbb8f5wZS1eib+cs3/qlD9r8DU2NEccqhPVFos3SRGSKtb4hyJEcX6VZhArj8Y+edgVpHICKD9tt8ddsvuYpNLZfQGoyBiY2CzKm1chkFmHUGwbUityTs70kCCSE2DZZADRaSeo0heYTpdQ3vwIAv0+QagzEKTOQnnOzHzoXTMkrCJYy6q7Wb1GNPO6hLi6keVYaDeqpDDFGarGkqy3sLFRMXFPDjZjqYsD5A6BI4RneUk0sdlwM2w0iqxFEtuwhkTpCLHER0fzWQ+I0ogmcLVPgqkQmBZLrdvC1tMQmfGTE66J3y+HCdoZqUgFBd/Y1TCJTL92VqwoMRVQOUxzpYJTiZd1EHAIyXmskS4RmbCySY4ZpVPEsmRv1QbTIKLoGtgt4kVTI74qM2p4tulMzwFS4qPiUDFxCSSUSGJJKJd2ozFS1kgYmyN1snOnimh0brybVuw0G0WV9iF3xeYjFAg4LcEi4Q692C7TUI8omiJRZAN3M+4ikTLBlosAAAA=")
|
||||
format("woff2"),
|
||||
url("iconfont.woff?t=1618544337340") format("woff"),
|
||||
url("iconfont.ttf?t=1618544337340") format("truetype"),
|
||||
/* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
|
||||
url("iconfont.svg?t=1618544337340#iconfont") format("svg"); /* iOS 4.1- */
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-full-screen-hs::before {
|
||||
content: "\e656";
|
||||
}
|
||||
|
||||
.icon-watch-hs::before {
|
||||
content: "\e766";
|
||||
}
|
||||
|
||||
.icon-download-hs::before {
|
||||
content: "\e6af";
|
||||
}
|
||||
|
||||
.icon-enlarge-hs::before {
|
||||
content: "\e765";
|
||||
}
|
||||
|
||||
.icon-previous-hs::before {
|
||||
content: "\e84c";
|
||||
}
|
||||
|
||||
.icon-zoom-out-hs::before {
|
||||
content: "\e744";
|
||||
}
|
||||
|
||||
.icon-next-step-hs::before {
|
||||
content: "\e84b";
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"id": "2491438",
|
||||
"name": "liu'c'tu",
|
||||
"font_family": "iconfont",
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "755619",
|
||||
"name": "自适应图标",
|
||||
"font_class": "full-screen-hs",
|
||||
"unicode": "e656",
|
||||
"unicode_decimal": 58966
|
||||
},
|
||||
{
|
||||
"icon_id": "14445801",
|
||||
"name": "查看",
|
||||
"font_class": "watch-hs",
|
||||
"unicode": "e766",
|
||||
"unicode_decimal": 59238
|
||||
},
|
||||
{
|
||||
"icon_id": "9712640",
|
||||
"name": "下载",
|
||||
"font_class": "download-hs",
|
||||
"unicode": "e6af",
|
||||
"unicode_decimal": 59055
|
||||
},
|
||||
{
|
||||
"icon_id": "1029099",
|
||||
"name": "放大",
|
||||
"font_class": "enlarge-hs",
|
||||
"unicode": "e765",
|
||||
"unicode_decimal": 59237
|
||||
},
|
||||
{
|
||||
"icon_id": "20017362",
|
||||
"name": "上一步",
|
||||
"font_class": "previous-hs",
|
||||
"unicode": "e84c",
|
||||
"unicode_decimal": 59468
|
||||
},
|
||||
{
|
||||
"icon_id": "1010015",
|
||||
"name": "缩小",
|
||||
"font_class": "zoom-out-hs",
|
||||
"unicode": "e744",
|
||||
"unicode_decimal": 59204
|
||||
},
|
||||
{
|
||||
"icon_id": "20017363",
|
||||
"name": "下一步",
|
||||
"font_class": "next-step-hs",
|
||||
"unicode": "e84b",
|
||||
"unicode_decimal": 59467
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
55
Yi.Pure.Vue3/src/components/ReFlowChart/src/config.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
export const nodeList = [
|
||||
{
|
||||
text: "开始",
|
||||
type: "start",
|
||||
class: "node-start"
|
||||
},
|
||||
{
|
||||
text: "矩形",
|
||||
type: "rect",
|
||||
class: "node-rect"
|
||||
},
|
||||
{
|
||||
type: "user",
|
||||
text: "用户",
|
||||
class: "node-user"
|
||||
},
|
||||
{
|
||||
type: "push",
|
||||
text: "推送",
|
||||
class: "node-push"
|
||||
},
|
||||
{
|
||||
type: "download",
|
||||
text: "位置",
|
||||
class: "node-download"
|
||||
},
|
||||
{
|
||||
type: "end",
|
||||
text: "结束",
|
||||
class: "node-end"
|
||||
}
|
||||
];
|
||||
|
||||
export const BpmnNode = [
|
||||
{
|
||||
type: "bpmn:startEvent",
|
||||
text: "开始",
|
||||
class: "bpmn-start"
|
||||
},
|
||||
{
|
||||
type: "bpmn:endEvent",
|
||||
text: "结束",
|
||||
class: "bpmn-end"
|
||||
},
|
||||
{
|
||||
type: "bpmn:exclusiveGateway",
|
||||
text: "网关",
|
||||
class: "bpmn-exclusiveGateway"
|
||||
},
|
||||
{
|
||||
type: "bpmn:userTask",
|
||||
text: "用户",
|
||||
class: "bpmn-user"
|
||||
}
|
||||
];
|
||||