From ecebb98ab6a7d283a34dee16ff6b2fbeec1d2955 Mon Sep 17 00:00:00 2001 From: xiaoxian521 <1923740402@qq.com> Date: Mon, 15 May 2023 14:41:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=88=86=E6=AE=B5?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E5=99=A8=E7=BB=84=E4=BB=B6=E5=B9=B6=E9=80=82?= =?UTF-8?q?=E9=85=8D=E6=9A=97=E9=BB=91=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yaml | 1 + locales/zh-CN.yaml | 1 + src/components/ReSegmented/index.ts | 8 + src/components/ReSegmented/src/index.css | 78 +++++++++ src/components/ReSegmented/src/index.tsx | 150 +++++++++++++++++ src/components/ReSegmented/src/type.ts | 15 ++ src/router/modules/components.ts | 9 + src/style/dark.scss | 14 ++ src/views/components/segmented/index.vue | 201 +++++++++++++++++++++++ 9 files changed, 477 insertions(+) create mode 100644 src/components/ReSegmented/index.ts create mode 100644 src/components/ReSegmented/src/index.css create mode 100644 src/components/ReSegmented/src/index.tsx create mode 100644 src/components/ReSegmented/src/type.ts create mode 100644 src/views/components/segmented/index.vue diff --git a/locales/en.yaml b/locales/en.yaml index 3035c784a..96a4ccbea 100644 --- a/locales/en.yaml +++ b/locales/en.yaml @@ -39,6 +39,7 @@ menus: hsdialog: Dialog Components hsmessage: Message Tips Components hsvideo: Video Components + hssegmented: Segmented Components hswaterfall: Waterfall Components hsmap: Map Components hsdraggable: Draggable Components diff --git a/locales/zh-CN.yaml b/locales/zh-CN.yaml index 9f52c29c1..4f34e4ce8 100644 --- a/locales/zh-CN.yaml +++ b/locales/zh-CN.yaml @@ -39,6 +39,7 @@ menus: hsdialog: 函数式弹框组件 hsmessage: 消息提示组件 hsvideo: 视频组件 + hssegmented: 分段控制器组件 hswaterfall: 瀑布流组件 hsmap: 地图组件 hsdraggable: 拖拽组件 diff --git a/src/components/ReSegmented/index.ts b/src/components/ReSegmented/index.ts new file mode 100644 index 000000000..de4253c49 --- /dev/null +++ b/src/components/ReSegmented/index.ts @@ -0,0 +1,8 @@ +import reSegmented from "./src/index"; +import { withInstall } from "@pureadmin/utils"; + +/** 分段控制器组件 */ +export const ReSegmented = withInstall(reSegmented); + +export default ReSegmented; +export type { OptionsType } from "./src/type"; diff --git a/src/components/ReSegmented/src/index.css b/src/components/ReSegmented/src/index.css new file mode 100644 index 000000000..a2f6be099 --- /dev/null +++ b/src/components/ReSegmented/src/index.css @@ -0,0 +1,78 @@ +.pure-segmented { + box-sizing: border-box; + display: inline-block; + padding: 2px; + font-size: 14px; + color: rgba(0, 0, 0, 0.65); + background-color: rgb(0 0 0 / 4%); + border-radius: 2px; + transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); +} + +.pure-segmented-group { + position: relative; + display: flex; + align-items: stretch; + justify-items: flex-start; + width: 100%; +} + +.pure-segmented-item-selected { + position: absolute; + top: 0; + left: 0; + box-sizing: border-box; + display: none; + width: 0; + height: 100%; + padding: 4px 0; + background-color: #fff; + border-radius: 4px; + box-shadow: 0 2px 8px -2px rgb(0 0 0 / 5%), 0 1px 4px -1px rgb(0 0 0 / 7%), + 0 0 1px rgb(0 0 0 / 7%); + transition: transform 0.5s cubic-bezier(0.645, 0.045, 0.355, 1), + width 0.5s cubic-bezier(0.645, 0.045, 0.355, 1); + will-change: transform, width; +} + +.pure-segmented-item { + position: relative; + text-align: center; + cursor: pointer; + border-radius: 4px; + transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); +} + +.pure-segmented-item > div { + min-height: 28px; + line-height: 28px; + padding: 0 11px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.pure-segmented-item > input { + position: absolute; + inset-block-start: 0; + inset-inline-start: 0; + width: 0; + height: 0; + opacity: 0; + pointer-events: none; +} + +.pure-segmented-item-label { + display: flex; + align-items: center; +} + +.pure-segmented-item-icon svg { + width: 16px; + height: 16px; +} + +.pure-segmented-item-disabled { + color: rgba(0, 0, 0, 0.25); + cursor: not-allowed; +} diff --git a/src/components/ReSegmented/src/index.tsx b/src/components/ReSegmented/src/index.tsx new file mode 100644 index 000000000..f7f5a6796 --- /dev/null +++ b/src/components/ReSegmented/src/index.tsx @@ -0,0 +1,150 @@ +import "./index.css"; +import { + h, + ref, + watch, + nextTick, + defineComponent, + getCurrentInstance +} from "vue"; +import type { OptionsType } from "./type"; +import { isFunction, useDark } from "@pureadmin/utils"; +import { useRenderIcon } from "@/components/ReIcon/src/hooks"; + +const props = { + options: { + type: Array, + default: () => [] + }, + /** 默认选中,按照第一个索引为 `0` 的模式 */ + defaultValue: { + type: Number, + default: 0 + } +}; + +export default defineComponent({ + name: "ReSegmented", + props, + emits: ["change"], + setup(props, { emit }) { + const width = ref(0); + const translateX = ref(0); + const { isDark } = useDark(); + const initStatus = ref(false); + const curMouseActive = ref(-1); + const segmentedItembg = ref(""); + const instance = getCurrentInstance()!; + const curIndex = ref(props.defaultValue); + + function handleChange({ option, index }, event: Event) { + if (option.disabled) return; + event.preventDefault(); + curIndex.value = index; + segmentedItembg.value = ""; + emit("change", { index, option }); + } + + function handleMouseenter({ option, index }, event: Event) { + event.preventDefault(); + curMouseActive.value = index; + if (option.disabled || curIndex.value === index) { + segmentedItembg.value = ""; + } else { + segmentedItembg.value = isDark.value + ? "#1f1f1f" + : "rgba(0, 0, 0, 0.06)"; + } + } + + function handleMouseleave(_, event: Event) { + event.preventDefault(); + curMouseActive.value = -1; + } + + function handleInit(index = curIndex.value) { + nextTick(() => { + const curLabelRef = instance?.proxy?.$refs[`labelRef${index}`] as ElRef; + width.value = curLabelRef.clientWidth; + translateX.value = curLabelRef.offsetLeft; + initStatus.value = true; + }); + } + + watch( + () => curIndex.value, + index => { + nextTick(() => { + handleInit(index); + }); + }, + { + deep: true, + immediate: true + } + ); + + const rendLabel = () => { + return props.options.map((option, index) => { + return ( + + ); + }); + }; + + return () => ( +
+
+
+ {rendLabel()} +
+
+ ); + } +}); diff --git a/src/components/ReSegmented/src/type.ts b/src/components/ReSegmented/src/type.ts new file mode 100644 index 000000000..e96d8166c --- /dev/null +++ b/src/components/ReSegmented/src/type.ts @@ -0,0 +1,15 @@ +import type { VNode, Component } from "vue"; + +export interface OptionsType { + /** 文字 */ + label?: string | (() => VNode | Component); + /** + * @description 图标,采用平台内置的 `useRenderIcon` 函数渲染 + * @see {@link 用法参考 https://yiming_chang.gitee.io/pure-admin-doc/pages/icon/#%E9%80%9A%E7%94%A8%E5%9B%BE%E6%A0%87-userendericon-hooks } + */ + icon?: string | Component; + /** 值 */ + value?: string | number; + /** 是否禁用 */ + disabled?: boolean; +} diff --git a/src/router/modules/components.ts b/src/router/modules/components.ts index 5fb555b92..b637524fc 100644 --- a/src/router/modules/components.ts +++ b/src/router/modules/components.ts @@ -31,6 +31,15 @@ export default { title: $t("menus.hsmessage") } }, + { + path: "/components/segmented", + name: "Segmented", + component: () => import("@/views/components/segmented/index.vue"), + meta: { + title: $t("menus.hssegmented"), + extraIcon: "IF-pure-iconfont-new svg" + } + }, { path: "/components/waterfall", name: "Waterfall", diff --git a/src/style/dark.scss b/src/style/dark.scss index 5e4a24849..e93f889c2 100644 --- a/src/style/dark.scss +++ b/src/style/dark.scss @@ -138,4 +138,18 @@ html.dark { } } } + + /* ReSegmented 组件 */ + .pure-segmented { + color: rgb(255 255 255 / 65%); + background-color: #000; + + .pure-segmented-item-selected { + background-color: #1f1f1f; + } + + .pure-segmented-item-disabled { + color: rgb(255 255 255 / 25%); + } + } } diff --git a/src/views/components/segmented/index.vue b/src/views/components/segmented/index.vue new file mode 100644 index 000000000..63cff62e3 --- /dev/null +++ b/src/views/components/segmented/index.vue @@ -0,0 +1,201 @@ + + +