├── .npmignore ├── packages ├── style │ ├── base.less │ ├── css-variables.less │ ├── var.less │ ├── border.less │ └── animation.less ├── utils │ ├── index.ts │ ├── format.ts │ ├── types.ts │ ├── with-install.ts │ ├── touch.ts │ ├── tools.ts │ └── vant-emulator.js ├── dialog │ ├── index.ts │ ├── types.ts │ ├── dialogfn.tsx │ ├── index.less │ └── doalog.tsx ├── toast │ ├── index.ts │ ├── types.ts │ ├── toastfn.tsx │ ├── index.less │ └── toast.tsx ├── popup │ ├── index.ts │ ├── index.less │ └── popup.tsx ├── cell │ ├── index.ts │ ├── cell.less │ └── cell.tsx ├── rate │ ├── index.ts │ ├── rate.less │ ├── rate.tsx │ └── star.tsx ├── icons │ ├── index.ts │ ├── icon.tsx │ ├── icon.less │ ├── heart.tsx │ └── star.tsx ├── calendar │ ├── index.ts │ ├── index.less │ ├── date.ts │ └── calendar.tsx ├── button │ ├── index.ts │ ├── button.tsx │ └── button.less ├── picker │ ├── index.ts │ ├── picker.less │ ├── picker.tsx │ └── columns.tsx ├── slider │ ├── index.ts │ ├── slider.less │ ├── slider.tsx │ └── slider copy.tsx └── index.ts ├── README.md └── package.json /.npmignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/style/base.less: -------------------------------------------------------------------------------- 1 | @import './border.less'; 2 | 3 | @import './var.less'; 4 | @import './css-variables.less'; 5 | @import './animation.less'; -------------------------------------------------------------------------------- /packages/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './format'; 2 | export * from './with-install'; 3 | export * from './types'; 4 | export * from './tools'; 5 | -------------------------------------------------------------------------------- /packages/utils/format.ts: -------------------------------------------------------------------------------- 1 | const camelizeRE = /-(\w)/g; 2 | 3 | // 将横线转成大写 例如 lan-button -> lanButton 4 | export const camelize = (str: string): string => 5 | str.replace(camelizeRE, (_, c) => c.toUpperCase()); 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lanli-ui 2 | 3 | 4 | ## install 5 | ```js 6 | npm i --save lanli-ui 7 | ``` 8 | 9 | ## use 10 | [document](https://slailcp.github.io/lanli-ui/index.html) 11 | 12 | ## 源码 13 | [源码](https://github.com/slailcp/lanli-ui) -------------------------------------------------------------------------------- /packages/utils/types.ts: -------------------------------------------------------------------------------- 1 | import { ComponentPublicInstance } from 'vue' 2 | 3 | export type Interceptor = ( 4 | ...args: any[] 5 | ) => Promise | boolean | undefined | void; 6 | 7 | export type ComponentInstance = ComponentPublicInstance<{}, any>; -------------------------------------------------------------------------------- /packages/dialog/index.ts: -------------------------------------------------------------------------------- 1 | import { Dialog} from './dialogfn'; 2 | import "./index.less" 3 | export default Dialog; 4 | export { Dialog }; 5 | 6 | declare module 'vue' { 7 | export interface GlobalComponents { 8 | LanDialog: typeof Dialog.Component; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/toast/index.ts: -------------------------------------------------------------------------------- 1 | import { Toast } from './toastfn'; 2 | import "./index.less" 3 | export default Toast; 4 | export { Toast }; 5 | export * from "./types" 6 | 7 | declare module 'vue' { 8 | export interface GlobalComponents { 9 | LanToast: typeof Toast.Component; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/popup/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '../utils'; 2 | import popupCom from './popup'; 3 | import "./index.less" 4 | 5 | export const Popup = withInstall(popupCom); 6 | export default Popup; 7 | 8 | declare module 'vue' { 9 | export interface GlobalComponents { 10 | LanPopup: typeof Popup; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/cell/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cell'; 2 | 3 | import { withInstall } from '../utils'; 4 | import CellCom from './cell'; 5 | import "./cell.less" 6 | 7 | export const Cell = withInstall(CellCom); 8 | export default Cell; 9 | 10 | 11 | declare module 'vue' { 12 | export interface GlobalComponents { 13 | LanCell: typeof Cell; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/rate/index.ts: -------------------------------------------------------------------------------- 1 | export * from './rate'; 2 | 3 | import { withInstall } from '../utils'; 4 | import RateCom from './rate'; 5 | import "./rate.less" 6 | 7 | export const Rate = withInstall(RateCom); 8 | export default Rate; 9 | 10 | 11 | declare module 'vue' { 12 | export interface GlobalComponents { 13 | LanRate: typeof Rate; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/icons/index.ts: -------------------------------------------------------------------------------- 1 | export * from './icon'; 2 | 3 | import { withInstall } from '../utils'; 4 | import IconCom from './icon'; 5 | import "./icon.less" 6 | 7 | export const Icon = withInstall(IconCom); 8 | export default Icon; 9 | 10 | 11 | declare module 'vue' { 12 | export interface GlobalComponents { 13 | LanIcon: typeof Icon; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/calendar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './date'; 2 | import { withInstall } from '../utils'; 3 | import _Calendar from './calendar'; 4 | import "./index.less" 5 | 6 | export const Calendar = withInstall(_Calendar); 7 | export default Calendar; 8 | 9 | declare module 'vue' { 10 | export interface GlobalComponents { 11 | LanCalendar: typeof Calendar; 12 | } 13 | } -------------------------------------------------------------------------------- /packages/button/index.ts: -------------------------------------------------------------------------------- 1 | export * from './button'; 2 | 3 | import { withInstall } from '../utils'; 4 | import ButtonCom from './button'; 5 | import "./button.less" 6 | 7 | export const Button = withInstall(ButtonCom); 8 | export default Button; 9 | 10 | 11 | declare module 'vue' { 12 | export interface GlobalComponents { 13 | LanButton: typeof Button; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/picker/index.ts: -------------------------------------------------------------------------------- 1 | export * from './picker'; 2 | 3 | import { withInstall } from '../utils'; 4 | import PickerCom from './picker'; 5 | import "./picker.less" 6 | 7 | export const Picker = withInstall(PickerCom); 8 | export default Picker; 9 | 10 | 11 | declare module 'vue' { 12 | export interface GlobalComponents { 13 | LanPicker: typeof Picker; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/slider/index.ts: -------------------------------------------------------------------------------- 1 | export * from './slider'; 2 | 3 | import { withInstall } from '../utils'; 4 | import SliderCom from './slider'; 5 | import "./slider.less" 6 | 7 | export const Slider = withInstall(SliderCom); 8 | export default Slider; 9 | 10 | 11 | declare module 'vue' { 12 | export interface GlobalComponents { 13 | LanSlider: typeof Slider; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/index.ts: -------------------------------------------------------------------------------- 1 | import "./style/base.less" 2 | import "./utils/vant-emulator.js" 3 | 4 | 5 | export * from "./button" 6 | export * from "./icons" 7 | export * from "./popup" 8 | export * from "./toast" 9 | export * from "./dialog" 10 | 11 | export * from "./cell" 12 | export * from "./rate" 13 | export * from "./slider" 14 | export * from "./picker" 15 | export * from "./calendar" 16 | -------------------------------------------------------------------------------- /packages/style/css-variables.less: -------------------------------------------------------------------------------- 1 | @import './var.less'; 2 | 3 | :root { 4 | // Color Palette 5 | --lan-black: @black; 6 | --lan-animation-duration-base: @animation-duration-base; 7 | --lan-animation-duration-fast: @animation-duration-fast; 8 | --lan-animation-timing-function-enter: @animation-timing-function-enter; 9 | --lan-animation-timing-function-leave: @animation-timing-function-leave; 10 | } 11 | -------------------------------------------------------------------------------- /packages/style/var.less: -------------------------------------------------------------------------------- 1 | // Color Palette 2 | @black: #000; 3 | @orange:#ff6e00; 4 | 5 | 6 | @primary:#42e2f7; 7 | @success:#3d91eb; 8 | @warning:#f1ca1c; 9 | @danger:rgb(245, 122, 122); 10 | @default:#fff; 11 | @gray:#999999; 12 | 13 | // Animation 14 | @animation-duration-base: 0.3s; 15 | @animation-duration-fast: 0.2s; 16 | @animation-timing-function-enter: ease-out; 17 | @animation-timing-function-leave: ease-in; 18 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lanli-ui", 3 | "version": "0.0.3", 4 | "description": "lanli-ui", 5 | "main": "./package/index.ts", 6 | "keywords":["lanli-ui"], 7 | "author":"shenlanlan", 8 | "private": false, 9 | "license":"MIT", 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/slailcp/lanli-ui.git" 13 | }, 14 | "scripts": { 15 | }, 16 | "dependencies": { 17 | 18 | }, 19 | "devDependencies": { 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/dialog/types.ts: -------------------------------------------------------------------------------- 1 | import { Interceptor } from "../utils" 2 | 3 | export type DialogOptions = { 4 | title?: string; 5 | width?: string | number; 6 | message?: string; 7 | callback?: Interceptor 8 | className?: unknown; 9 | allowHtml?: boolean; 10 | lockScroll?: boolean; 11 | beforeClose?: Interceptor; 12 | cancelButtonText?: string; 13 | confirmButtonText?: string; 14 | showCancelButton?: boolean; 15 | showConfirmButton?: boolean; 16 | }; 17 | 18 | export type DialogAction = 'confirm' | 'cancel'; -------------------------------------------------------------------------------- /packages/toast/types.ts: -------------------------------------------------------------------------------- 1 | import { Interceptor, ComponentInstance } from "../utils" 2 | 3 | export type ToastOptions = { 4 | type?: 'loading' | 'success' | 'fail' | 'text'; 5 | width?: string | number; 6 | message?: string; 7 | duration?: number; 8 | shade?: boolean; 9 | shadeClassName?: string; 10 | className?: string; 11 | allowHtml?: boolean; 12 | position?: 'top' | 'bottom' | 'center'; // 'top' 'bottom' 'center' 13 | onOpened?: Interceptor; 14 | onClose?: Interceptor; 15 | }; 16 | 17 | export type ToastAction = 'open' | 'close' 18 | 19 | export type mountComponentVoid = { 20 | instance: ComponentInstance; 21 | unmount: Interceptor | null | undefined 22 | } | null | undefined -------------------------------------------------------------------------------- /packages/utils/with-install.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue'; 2 | import { camelize } from './format'; 3 | 4 | type EventShim = { 5 | new (...args: any[]): { 6 | $props: { 7 | onClick?: (...args: any[]) => void; 8 | }; 9 | }; 10 | }; 11 | 12 | export type WithInstall = T & { 13 | install(app: App): void; 14 | } & EventShim; 15 | 16 | // 封装组件的install,全局调用 17 | export function withInstall(options: T) { 18 | (options as Record).install = (app: App) => { 19 | const { name } = options as unknown as { name: string }; 20 | app.component(name, options); 21 | app.component(camelize(`-${name}`), options); 22 | }; 23 | 24 | return options as WithInstall; 25 | } 26 | -------------------------------------------------------------------------------- /packages/rate/rate.less: -------------------------------------------------------------------------------- 1 | @import '../style/base.less'; 2 | 3 | 4 | :root { 5 | --lan-cell-padding: 10px 0 10px 0; 6 | 7 | } 8 | 9 | .lan-rate-box{position: relative; 10 | .lan-rate{display: inline-block;cursor: pointer; vertical-align: middle;box-sizing: border-box;} 11 | &.lan-rate-disabled{ 12 | .lan-rate{cursor: no-drop; } 13 | opacity: .8; 14 | } 15 | &.lan-rate-readonly{ 16 | .lan-rate{cursor: no-drop;} 17 | } 18 | &.lan-rate-move{ 19 | &::after{position: absolute;top:0;bottom:0;right:0;left:0;bottom: 0;z-index: 10;content: "";cursor: pointer;} 20 | &.lan-rate-readonly,&.lan-rate-disabled{ 21 | &::after{cursor: no-drop;} 22 | } 23 | } 24 | 25 | .lan-rate-span{display: inline-block;} 26 | } -------------------------------------------------------------------------------- /packages/style/border.less: -------------------------------------------------------------------------------- 1 | .borderbot1px(@c: #C7C7C7) { 2 | content: " "; 3 | position: absolute; 4 | left: 0; 5 | bottom: 0; 6 | width: 200%; 7 | height: 1px; 8 | border-bottom: 1px solid @c; 9 | color: @c; 10 | // height: 200%; 11 | transform-origin: left top; 12 | transform: scale(0.5);pointer-events: none; 13 | } 14 | 15 | .bordertop1px(@c: #C7C7C7) { 16 | content: " "; 17 | position: absolute; 18 | left: 0; 19 | top: 0; 20 | width: 200%; 21 | height: 1px; 22 | border-top: 1px solid @c; 23 | color: @c; 24 | // height: 200%; 25 | transform-origin: left top; 26 | transform: scale(0.5);pointer-events: none; 27 | } 28 | 29 | .border1px(@c: #C7C7C7,@radius:0) { 30 | content: " "; 31 | position: absolute; 32 | left: 0; 33 | top: 0; 34 | width: 200%; 35 | border: 1px solid @c; 36 | border-radius:@radius; 37 | color: @c; 38 | height: 200%; 39 | transform-origin: left top; 40 | transform: scale(0.5);pointer-events: none; 41 | } 42 | -------------------------------------------------------------------------------- /packages/calendar/index.less: -------------------------------------------------------------------------------- 1 | :root { 2 | --lan-calendar-choose-bgcolor: rgb(245, 223, 181); 3 | --lan-calendar-choose-color: #333; 4 | 5 | --lan-calendar-pass-bgcolor: #fafafa; 6 | --lan-calendar-pass-color: #999; 7 | 8 | --lan-calendar-active-bgcolor: #409eff; 9 | --lan-calendar-active-color: #fff; 10 | 11 | --lan-calendar-fontsize: 14px; 12 | } 13 | 14 | .lan-calendar-month { 15 | text-align: center; 16 | padding: 15px 0; 17 | } 18 | 19 | .lan-calendar-con { 20 | display: flex; 21 | flex-wrap: wrap; 22 | } 23 | 24 | .lan-calendar-con-date { 25 | flex: 1; 26 | min-width: 14.28%; 27 | max-width: 14.28%; 28 | height: 60px; 29 | line-height: 60px; 30 | text-align: center; 31 | cursor: pointer; 32 | font-size: var(--lan-calendar-fontsize); 33 | &.lan-calendar-pass, 34 | &.lan-calendar-future { 35 | background-color: var(--lan-calendar-pass-bgcolor); 36 | color: var(--lan-calendar-pass-color); 37 | } 38 | } 39 | 40 | .lan-calendar-date-active { 41 | background-color: var(--lan-calendar-active-bgcolor); 42 | color: var(--lan-calendar-active-color); 43 | } 44 | .lan-calendar-date-choose { 45 | background-color: var(--lan-calendar-choose-bgcolor); 46 | color: var(--lan-calendar-choose-color); 47 | } -------------------------------------------------------------------------------- /packages/popup/index.less: -------------------------------------------------------------------------------- 1 | 2 | @import '../style/base.less'; 3 | 4 | :root { 5 | --lan-popup-background-color: #fff; 6 | } 7 | 8 | 9 | .lan-popup-left { 10 | position: fixed; 11 | top: 0; 12 | left: 0; 13 | bottom: 0; 14 | width: 50%; 15 | background-color: var(--lan-popup-background-color); 16 | z-index: 2; 17 | overflow-y: auto; 18 | } 19 | 20 | .lan-popup-right { 21 | position: fixed; 22 | top: 0; 23 | right: 0; 24 | bottom: 0; 25 | width: 50%; 26 | background-color: var(--lan-popup-background-color); 27 | z-index: 200; 28 | overflow-y: auto; 29 | } 30 | 31 | .lan-popup-bottom { 32 | position: fixed; 33 | bottom: 0; 34 | right: 0; 35 | left: 0; 36 | height: 50%; 37 | background-color: var(--lan-popup-background-color); 38 | z-index: 2; 39 | overflow-y: auto; 40 | } 41 | 42 | .lan-popup-top { 43 | position: fixed; 44 | top: 0; 45 | right: 0; 46 | left: 0; 47 | height: 50%; 48 | background-color: var(--lan-popup-background-color); 49 | z-index: 2; 50 | overflow-y: auto; 51 | } 52 | 53 | .lan-popup-center { 54 | position: fixed; 55 | top: 50%; 56 | left: 50%; 57 | transform: translate(-50%, -50%); 58 | background-color: var(--lan-popup-background-color); 59 | z-index: 200; 60 | } 61 | 62 | .lan-mask-popup { 63 | position: fixed; 64 | top: 0; 65 | left: 0; 66 | bottom: 0; 67 | right: 0; 68 | background-color: rgba(0, 0, 0, 0.7); 69 | z-index: 199; 70 | } -------------------------------------------------------------------------------- /packages/slider/slider.less: -------------------------------------------------------------------------------- 1 | @import '../style/base.less'; 2 | 3 | @corfff: #fff; 4 | @cor333: #333; 5 | @font14: 14px; 6 | 7 | 8 | :root { 9 | --lan-slider-item-width:30px; 10 | --lan-slider-item-bg:rgba(255,255,255,.9); 11 | --lan-slider-height:10px; 12 | } 13 | 14 | 15 | .lan-slider-container{ 16 | position: relative; 17 | height:var(--lan-slider-item-width); 18 | &::after{ 19 | content: ""; 20 | position: absolute; 21 | left:0; 22 | right:0; 23 | top:50%; 24 | margin-top:calc(var(--lan-slider-height) / -2); 25 | height:var(--lan-slider-height); 26 | background-color: #ccc; 27 | z-index: 1; 28 | border-radius: 5px; 29 | } 30 | .lan-slider-item-left,.lan-slider-item-right{ 31 | position: absolute; 32 | width:var(--lan-slider-item-width); 33 | height:var(--lan-slider-item-width); 34 | border-radius: 50%; 35 | background-color: var(--lan-slider-item-bg);box-shadow: 0 0 3px #999; 36 | z-index: 3; 37 | top:50%;user-select: none; 38 | } 39 | .lan-slider-item-left{left:0;transform: translate3d(-50%,-50%,0);} 40 | .lan-slider-item-right{right:0;transform: translate3d(50%,-50%,0);} 41 | .lan-slider-fill{ 42 | position: absolute;left:0;width:100%; 43 | height:var(--lan-slider-item-width); 44 | &::after{ 45 | content: ""; 46 | position: absolute; 47 | left:0; 48 | right:0; 49 | top:50%;border-radius: 5px; 50 | margin-top:calc(var(--lan-slider-height) / -2); 51 | height:var(--lan-slider-height); 52 | background-color: #1989fa; 53 | z-index: 2; 54 | } 55 | } 56 | 57 | } 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /packages/picker/picker.less: -------------------------------------------------------------------------------- 1 | @import '../style/base.less'; 2 | 3 | :root { 4 | --lan-pciker-bgcolor: rgb(245, 223, 181); 5 | --lan-picker-mask-color: linear-gradient(180deg, rgba(255, 255, 255, .9), rgba(255, 255, 255, .4)), linear-gradient(0deg, rgba(255, 255, 255, .9), rgba(255, 255, 255, .4)); 6 | --lan-pciker-title-confirm-color: @primary; 7 | --lan-pciker-title-cancel-color: @gray; 8 | --lan-pciker-column-height: 175px; 9 | --lan-pciker-column-item-height: 35px; 10 | 11 | 12 | } 13 | 14 | .lan-pciker-container{} 15 | 16 | .lan-pciker-columns { 17 | display: flex; 18 | width: 100%; 19 | 20 | } 21 | .lan-pciker-columns>div{ flex: 1;position: relative;} 22 | .lan-pciker-column { 23 | height:var(--lan-pciker-column-height); 24 | overflow:hidden; 25 | 26 | .lan-pciker-column-item { 27 | line-height: var(--lan-pciker-column-item-height); 28 | text-align: center; 29 | cursor: grab; 30 | user-select:none; 31 | } 32 | } 33 | 34 | 35 | .van-picker-mask { 36 | position: absolute; 37 | top: 0; 38 | left: 0; 39 | z-index: 1; 40 | width: 100%; 41 | height: 100%; 42 | background-image:var(--lan-picker-mask-color); 43 | background-repeat: no-repeat; 44 | background-position: top,bottom; 45 | transform: translateZ(0); 46 | pointer-events: none; 47 | 48 | background-size: 100% 77px; 49 | } 50 | 51 | .lan-picker-title{display: flex;line-height: 35px;margin-bottom:20px; 52 | .lan-picker-title-cancel{width:100px;color:var(--lan-pciker-title-cancel-color);cursor: pointer;} 53 | .lan-picker-title-con{flex:1;text-align: center;} 54 | .lan-picker-title-enter{width:100px;text-align: right;color:var(--lan-pciker-title-confirm-color);cursor: pointer;} 55 | } 56 | -------------------------------------------------------------------------------- /packages/calendar/date.ts: -------------------------------------------------------------------------------- 1 | import moment, { Moment } from "moment" 2 | 3 | export type DateItem = { 4 | date: Moment | null; 5 | pass?: string; 6 | future?: string; 7 | } 8 | export type DateMon = { 9 | month: Moment; 10 | date: Array 11 | } 12 | 13 | export type DateObj = { 14 | [propName in string | number]: DateMon; 15 | } 16 | 17 | // const startWeek = 1; // 从周一开始 18 | 19 | export function getAllDate(start = moment(), end = moment().add(1, 'months'), startWeek = 1) { // 获取两个日期间的所有日期数据 20 | const startDate = start.clone(); 21 | const endDate = end.clone(); 22 | 23 | const dataObject: DateObj = {}; 24 | const diff = Math.abs(startDate.startOf('month').diff(endDate.endOf('month'), 'days')); // 两个日期相差多少天(start的月初,到end的月末) 25 | 26 | for (let i = 0; i < diff; i++) { 27 | const date = startDate.clone().add(i, 'days') 28 | const isfirst = date.format('DD') == '01' // 月初第一天 29 | const firstX = date.clone().startOf('month').format('x') 30 | 31 | if (!dataObject[firstX]) { dataObject[firstX] = { month: date, date: [] } } 32 | 33 | if (isfirst) { 34 | const weekday = date.weekday() === 0 ? 7 : date.weekday() 35 | dataObject[firstX].date.push(...fillEmpty(weekday - startWeek))// 初始第一个月 36 | } 37 | 38 | dataObject[firstX].date.push({ 39 | date: date, 40 | pass: start.diff(date) > 0 ? 'lan-calendar-pass' : '', 41 | future: end.diff(date) < 0 ? 'lan-calendar-future' : '' 42 | }) 43 | } 44 | 45 | return dataObject 46 | } 47 | 48 | function fillEmpty(num: number) { 49 | const ret: DateItem[] = [] 50 | for (let i = 0; i < num; i++) { 51 | ret.push({ 52 | date: null, 53 | pass: 'pass' 54 | }) 55 | } 56 | return ret 57 | } -------------------------------------------------------------------------------- /packages/icons/icon.tsx: -------------------------------------------------------------------------------- 1 | import * as icon from './com' 2 | import * as star from './star' 3 | import * as heart from './heart' 4 | import { setIcon } from "../utils" 5 | 6 | const icons = { ...icon, ...star, ...heart } 7 | 8 | 9 | import { defineComponent, Fragment } from "vue" 10 | 11 | export const IconProps = { 12 | name: { default: "user", type: String }, 13 | size: { default: "", type: String }, 14 | color: { default: "", type: String }, 15 | dot: { default: false, type: Boolean }, 16 | badge: { default: "", type: String }, 17 | iconIndex: { default: "0", type: [String, Number] }, 18 | } 19 | export default defineComponent({ 20 | name: 'lan-icon', 21 | props: Object.assign({}, IconProps), 22 | emits: ['click'], 23 | setup(props, { emit, slots }) { 24 | const IsUrl = /\//g.test(props.name); 25 | const style: any = {} 26 | if(props.size){style.width = props.size} 27 | if(props.color){style.color = props.color} 28 | 29 | 30 | function iconRender() { 31 | const iconCom = setIcon(icons, props.name) 32 | return ( 33 | 34 | // {name} 35 | ) 36 | } 37 | function imageRender() { 38 | return ( 39 | 40 | ) 41 | } 42 | 43 | function onClick(event: MouseEvent) { 44 | emit('click', event) 45 | } 46 | return () => ( 47 |
48 | {IsUrl ? imageRender() : iconRender()} 49 | {props.dot ? : null} 50 | {props.badge ? {props.badge} : null} 51 |
52 | ) 53 | } 54 | }) -------------------------------------------------------------------------------- /packages/icons/icon.less: -------------------------------------------------------------------------------- 1 | @import '../style/base.less'; 2 | 3 | 4 | :root { 5 | --lan-icon-badge-bgcolor:red; 6 | --lan-icon-badge-top: 0; 7 | --lan-icon-badge-right: 0; 8 | --lan-icon-badge-bottom: auto; 9 | --lan-icon-badge-left: auto; 10 | --lan-icon-badge-fontsize: 12px; 11 | --lan-icon-dot-width: 5px; 12 | } 13 | 14 | 15 | .lan-icon-wraper{ 16 | display: inline-block; 17 | position: relative;vertical-align: middle; 18 | &::after{position: absolute;top:0;bottom:0;right:0;left:0;bottom: 0;z-index: 9;content: "";} 19 | svg{display: block;} 20 | img{width:20px;} 21 | .lan-icon-dot{ 22 | position: absolute; 23 | display: block; 24 | background-color: var(--lan-icon-badge-bgcolor); 25 | box-sizing:border-box; 26 | right: var(--lan-icon-badge-right); 27 | top: var(--lan-icon-badge-top); 28 | left: var(--lan-icon-badge-left); 29 | bottom: var(--lan-icon-badge-bottom); 30 | border-radius: 50%; 31 | width:var(--lan-icon-dot-width); 32 | height:var(--lan-icon-dot-width); 33 | transform: translateX(50%) translateY(-50%);; 34 | } 35 | .lan-icon-badge{ 36 | position: absolute; 37 | display: block; 38 | background-color: var(--lan-icon-badge-bgcolor); 39 | box-sizing:border-box; 40 | min-width: 20px; 41 | text-align: center; 42 | color: #fff; 43 | right: var(--lan-icon-badge-right); 44 | top: var(--lan-icon-badge-top); 45 | left: var(--lan-icon-badge-left); 46 | bottom: var(--lan-icon-badge-bottom); 47 | border-radius: 999px; 48 | font-style: normal; 49 | line-height: 1.2;padding:1px 2px; 50 | font-size: var(--lan-icon-badge-fontsize);transform: translateX(85%) translateY(-50%);; 51 | } 52 | } 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /packages/cell/cell.less: -------------------------------------------------------------------------------- 1 | @import '../style/base.less'; 2 | 3 | @corfff: #fff; 4 | @cor333: #333; 5 | @font14: 14px; 6 | 7 | :root { 8 | --lan-cell-padding: 10px 0 10px 0; 9 | --lan-cell-title-width: 80px; 10 | --lan-cell-titicon-width: 30px; 11 | --lan-cell-valicon-width: 20px; 12 | --lan-cell-border-color: #d8d8d8; 13 | --lan-cell-icon-size: 17px; 14 | --lan-cell-icon-color: #bbb; 15 | } 16 | 17 | 18 | 19 | 20 | .lan-cell-style { 21 | display: flex; 22 | flex-wrap: wrap; 23 | padding: var(--lan-cell-padding); 24 | font-size: 14px; 25 | position: relative; 26 | 27 | &::before { 28 | .borderbot1px(var(--lan-cell-border-color)) 29 | } 30 | 31 | &:last-child { 32 | &::before { 33 | display: none; 34 | } 35 | } 36 | 37 | // &:active{background-color: #e9fdfd;} 38 | 39 | 40 | .lan-cell-tit-icon { 41 | width: var(--lan-cell-titicon-width); 42 | display: flex; 43 | align-items: center; 44 | justify-content: flex-start; 45 | } 46 | 47 | .lan-cell-title { 48 | width: var(--lan-cell-title-width); 49 | display: flex; 50 | 51 | &.lan-cell-tit-ver-top { 52 | align-items: start; 53 | } 54 | 55 | &.lan-cell-tit-ver-middle { 56 | align-items: center; 57 | } 58 | 59 | &.lan-cell-tit-ver-bottom { 60 | align-items: end; 61 | } 62 | } 63 | 64 | 65 | 66 | .lan-cell-value { 67 | flex: 1; 68 | .lan-cell-h4{padding-bottom:5px;} 69 | .lan-cell-label {line-height: 24px;} 70 | 71 | } 72 | 73 | .lan-cell-right-icon { 74 | width: var(--lan-cell-valicon-width); 75 | display: flex; 76 | align-items: center; 77 | justify-content: flex-end; 78 | } 79 | 80 | .lan-cell-icon { 81 | vertical-align: middle; 82 | width: var(--lan-cell-icon-size); 83 | color: var(--lan-cell-icon-color) 84 | } 85 | 86 | 87 | 88 | 89 | 90 | &.lan-cell-default { 91 | .lan-cell-value { 92 | text-align: right; 93 | } 94 | } 95 | 96 | 97 | 98 | } -------------------------------------------------------------------------------- /packages/toast/toastfn.tsx: -------------------------------------------------------------------------------- 1 | import { withInstall } from '../utils'; 2 | import { createApp, Component, App } from 'vue' 3 | import LanToast from './toast' 4 | import { ToastOptions, ToastAction, mountComponentVoid } from "./types" 5 | import { ComponentInstance } from "../utils" 6 | 7 | const inBrowser = typeof window !== 'undefined' 8 | let instance: ComponentInstance; 9 | 10 | function mountComponent(RootComponent: Component, obj: ToastOptions) { 11 | const app = createApp(RootComponent, obj); 12 | const root = document.createElement('div'); 13 | 14 | const toastRandom = `lan${Math.floor(Math.random() * 100000000000000000)}` 15 | root.setAttribute('lan-toast-random', toastRandom) 16 | 17 | document.body.appendChild(root); 18 | return { 19 | instance: app.mount(root), 20 | toastRandom: toastRandom 21 | }; 22 | } 23 | 24 | function Toast(options: ToastOptions) { 25 | let toastRandom: any 26 | if (!inBrowser) { 27 | return; 28 | } 29 | const props: ToastOptions = Object.assign({}, Toast.defaultOptions, options, { 30 | onClose: (action: ToastAction) => { 31 | const root = document.querySelector('[lan-toast-random=' + toastRandom + ']') 32 | if (root) { 33 | document.body.removeChild(root); 34 | } 35 | options.onClose?.() 36 | } 37 | }); 38 | 39 | ({ instance, toastRandom } = mountComponent(, props)); 40 | return toastRandom; 41 | } 42 | 43 | Toast.defaultOptions = { 44 | type: 'text', 45 | width: "", 46 | message: "", 47 | duration: 2000, 48 | shade: false, 49 | shadeClassName: "", 50 | className: "", 51 | allowHtml: false, 52 | position: 'center', 53 | onOpened: null, 54 | onClose: null 55 | }; 56 | 57 | Toast.currentOptions = Object.assign({}, Toast.defaultOptions); 58 | 59 | Toast.close = (toastRandom: string) => { 60 | const root = document.querySelector('[lan-toast-random=' + toastRandom + ']') 61 | if (root) { 62 | document.body.removeChild(root); 63 | } 64 | }; 65 | 66 | Toast.Component = withInstall(LanToast); 67 | 68 | Toast.install = (app: App) => { 69 | app.use(Toast.Component); 70 | app.config.globalProperties.$toast = Toast; 71 | }; 72 | 73 | 74 | export { Toast } -------------------------------------------------------------------------------- /packages/utils/touch.ts: -------------------------------------------------------------------------------- 1 | type Direction = '' | 'vertical' | 'horizontal'; 2 | 3 | function getDirection(x: number, y: number) { 4 | if (x > y) { 5 | return 'horizontal'; 6 | } 7 | if (y > x) { 8 | return 'vertical'; 9 | } 10 | return ''; 11 | } 12 | 13 | export class Touch { 14 | initX = 0; 15 | initY = 0; 16 | parentOffsetX = 0; 17 | parentOffsetY = 0; 18 | deltaX = 0; 19 | deltaY = 0; 20 | offsetX = 0; 21 | offsetY = 0; 22 | direction = ''; 23 | isLockDirection = false; 24 | 25 | 26 | isVertical() { return this.direction === 'vertical'; } 27 | isHorizontal() { return this.direction === 'horizontal'; } 28 | 29 | reset() { 30 | this.deltaX = 0; 31 | this.deltaY = 0; 32 | this.parentOffsetX = 0; 33 | this.parentOffsetY = 0; 34 | this.offsetX = 0; 35 | this.offsetY = 0; 36 | this.direction = ''; 37 | this.isLockDirection = false; 38 | } 39 | 40 | start(event: TouchEvent) { 41 | this.reset() 42 | this.initX = event.touches[0].clientX; 43 | this.initY = event.touches[0].clientY; 44 | if (event.target) { 45 | const bound = (event.target as HTMLElement).getBoundingClientRect(); 46 | this.parentOffsetX = Math.floor(bound.x); 47 | this.parentOffsetY = Math.floor(bound.y); 48 | } 49 | } 50 | 51 | move(event: TouchEvent) { 52 | const touch = event.touches[0]; 53 | this.deltaX = (touch.clientX < 0 ? 0 : touch.clientX) - this.initX; 54 | this.deltaY = touch.clientY - this.initY; 55 | this.offsetX = Math.abs(this.deltaX); 56 | this.offsetY = Math.abs(this.deltaY); 57 | 58 | 59 | const DIRECTION_DISTANCE = 10; // 超过这个距离就锁定方向。 60 | 61 | if (!this.isLockDirection && (this.offsetX > DIRECTION_DISTANCE || this.offsetY > DIRECTION_DISTANCE)) { 62 | this.isLockDirection = true; 63 | this.direction = getDirection(this.offsetX, this.offsetY); 64 | } 65 | 66 | if (this.offsetX < DIRECTION_DISTANCE && this.offsetX < DIRECTION_DISTANCE) { 67 | this.direction = getDirection(this.offsetX, this.offsetY); 68 | } 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /packages/dialog/dialogfn.tsx: -------------------------------------------------------------------------------- 1 | import { withInstall, ComponentInstance } from '../utils'; 2 | import { createApp, Component, App } from 'vue' 3 | import LanDialog from './doalog' 4 | import {DialogOptions, DialogAction} from "./types" 5 | 6 | const inBrowser = typeof window !== 'undefined' 7 | let instance: ComponentInstance; 8 | 9 | function mountComponent(RootComponent: Component, obj: DialogOptions) { 10 | const app = createApp(RootComponent, obj); 11 | const root = document.createElement('div'); 12 | 13 | document.body.appendChild(root); 14 | const bodyoverflow = document.body.style.overflow; 15 | 16 | if (obj.lockScroll) { 17 | document.body.style.overflow = "hidden" 18 | } 19 | 20 | return { 21 | instance: app.mount(root), 22 | unmount() { 23 | app.unmount(); 24 | document.body.removeChild(root); 25 | if (obj.lockScroll) { 26 | document.body.style.overflow = bodyoverflow 27 | } 28 | }, 29 | }; 30 | } 31 | 32 | function Dialog(options: DialogOptions) { 33 | let unmount: any 34 | if (!inBrowser) { 35 | return Promise.resolve(); 36 | } 37 | return new Promise((resolve, reject) => { 38 | const props = Object.assign({}, Dialog.defaultOptions, options, { 39 | callback: (action: DialogAction) => { 40 | (action === 'confirm' ? resolve : reject)(action); 41 | setTimeout(() => { unmount() }, 300) 42 | }, 43 | 44 | }); 45 | 46 | ({ instance, unmount } = mountComponent(, props)); 47 | }); 48 | } 49 | 50 | Dialog.defaultOptions = { 51 | title: '提示', 52 | width: '', 53 | message: '', 54 | callback: null, 55 | className: '', 56 | allowHtml: false, 57 | lockScroll: false, 58 | beforeClose: null, 59 | cancelButtonText: '取消', 60 | confirmButtonText: '确认', 61 | showConfirmButton: true, 62 | showCancelButton: false, 63 | }; 64 | 65 | Dialog.currentOptions = Object.assign({}, Dialog.defaultOptions); 66 | 67 | Dialog.alert = Dialog; 68 | 69 | Dialog.confirm = (options: DialogOptions) => 70 | Dialog(Object.assign({ showCancelButton: true }, options)); 71 | 72 | Dialog.close = () => { 73 | if (instance) { 74 | instance.toggle(false); 75 | } 76 | }; 77 | 78 | 79 | Dialog.Component = withInstall(LanDialog); 80 | 81 | Dialog.install = (app: App) => { 82 | app.use(Dialog.Component); 83 | app.config.globalProperties.$dialog = Dialog; 84 | }; 85 | 86 | 87 | export { Dialog } -------------------------------------------------------------------------------- /packages/toast/index.less: -------------------------------------------------------------------------------- 1 | :root { 2 | --lan-toast-background-color: rgba(0, 0, 0, 0.7); 3 | --lan-toast-button-active-color: orange; 4 | --lan-toast-spinner-size: 50px; 5 | --lan-loading-color-primary: #fff; 6 | } 7 | 8 | 9 | @keyframes loading-rotate { 10 | to { 11 | transform: rotate(360deg) 12 | } 13 | } 14 | 15 | @keyframes loading-dash { 16 | 0% { 17 | stroke-dasharray: 1, 200; 18 | stroke-dashoffset: 0 19 | } 20 | 21 | 50% { 22 | stroke-dasharray: 90, 150; 23 | stroke-dashoffset: -40px 24 | } 25 | 26 | to { 27 | stroke-dasharray: 90, 150; 28 | stroke-dashoffset: -120px 29 | } 30 | } 31 | 32 | 33 | .lan-toast-center { 34 | border-radius: 12px; 35 | overflow: hidden; 36 | position: fixed; 37 | top: 50%; 38 | left: 50%; 39 | transform: translate(-50%, -50%); 40 | background-color: var(--lan-toast-background-color); 41 | z-index: 200; 42 | 43 | &.top { 44 | top: 30% 45 | } 46 | 47 | &.bottom { 48 | bottom: 30%; 49 | top: auto 50 | } 51 | 52 | } 53 | 54 | .lan-mask-toast { 55 | position: fixed; 56 | top: 0; 57 | left: 0; 58 | bottom: 0; 59 | right: 0; 60 | background-color: rgba(0, 0, 0, 0); 61 | z-index: 199; 62 | } 63 | 64 | .lan-toast-content { 65 | padding: 10px 15px; 66 | overflow-y: auto; 67 | font-size: 14px; 68 | color: #fff; 69 | text-align: center; 70 | } 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | /*loading*/ 79 | .lan-toast-icon { 80 | min-height: 50px; 81 | 82 | .toastcircular { 83 | display: inline; 84 | height: var(--lan-toast-spinner-size); 85 | width: var(--lan-toast-spinner-size); 86 | animation: loading-rotate 2s linear infinite; 87 | } 88 | 89 | .toastcircular .path { 90 | animation: loading-dash 1.5s ease-in-out infinite; 91 | stroke-dasharray: 90, 150; 92 | stroke-dashoffset: 0; 93 | stroke-width: 2; 94 | stroke: var(--lan-loading-color-primary); 95 | stroke-linecap: round; 96 | } 97 | } 98 | 99 | 100 | 101 | /*lan-success-fail*/ 102 | .lan-toast-icon { 103 | text-align: center;padding-top:5px; 104 | color: var(--lan-loading-color-primary); 105 | .successfail-svg svg { 106 | height: 70px; 107 | width: 70px;color:#fff; 108 | } 109 | } 110 | 111 | .lan-toast-loading-wraper,.lan-toast-success-wraper,.lan-toast-fail-wraper{ 112 | width:30%;max-width: 200px; 113 | .lan-toast-content{padding-top:0;} 114 | } -------------------------------------------------------------------------------- /packages/dialog/index.less: -------------------------------------------------------------------------------- 1 | :root { 2 | --lan-dialog-background-color: #fff; 3 | --lan-dialog-button-active-color: orange; 4 | --lan-loading-spinner-size: 20px; 5 | --lan-color-primary: #409eff; 6 | } 7 | 8 | 9 | @keyframes loading-rotate { 10 | to { 11 | transform: rotate(360deg) 12 | } 13 | } 14 | 15 | @keyframes loading-dash { 16 | 0% { 17 | stroke-dasharray: 1, 200; 18 | stroke-dashoffset: 0 19 | } 20 | 21 | 50% { 22 | stroke-dasharray: 90, 150; 23 | stroke-dashoffset: -40px 24 | } 25 | 26 | to { 27 | stroke-dasharray: 90, 150; 28 | stroke-dashoffset: -120px 29 | } 30 | } 31 | 32 | .lan-dialog .circular { 33 | display: inline; 34 | height: var(--lan-loading-spinner-size); 35 | width: var(--lan-loading-spinner-size); 36 | animation: loading-rotate 2s linear infinite; 37 | } 38 | 39 | .lan-dialog .circular .path { 40 | animation: loading-dash 1.5s ease-in-out infinite; 41 | stroke-dasharray: 90, 150; 42 | stroke-dashoffset: 0; 43 | stroke-width: 2; 44 | stroke: var(--lan-color-primary); 45 | stroke-linecap: round; 46 | } 47 | 48 | .lan-dialog-center { 49 | position: fixed; 50 | top: 50%; 51 | left: 50%; 52 | width: 340px; 53 | transform: translate(-50%, -50%); 54 | background-color: var(--lan-dialog-background-color); 55 | z-index: 200; 56 | } 57 | 58 | .lan-mask-dialog { 59 | position: fixed; 60 | top: 0; 61 | left: 0; 62 | bottom: 0; 63 | right: 0; 64 | background-color: rgba(0, 0, 0, 0.7); 65 | z-index: 199; 66 | } 67 | 68 | .lan-dialog-title { 69 | height: 23px; 70 | padding: 10px 15px 5px 15px; 71 | font-size: 14px; 72 | } 73 | 74 | .lan-dialog-content { 75 | max-height: 100px; 76 | min-height: 50px; 77 | padding: 10px 15px; 78 | overflow-y: auto; 79 | font-size: 14px; 80 | } 81 | 82 | .lan-dialog-buttons { 83 | padding: 0 15px; 84 | display: flex; 85 | text-align: center; 86 | font-size: 14px; 87 | line-height: 48px; 88 | 89 | .lan-dialog-btn { 90 | flex: 1; 91 | cursor: pointer; 92 | &.disable{cursor: no-drop;} 93 | &:last-child { 94 | color: var(--lan-dialog-button-active-color) 95 | } 96 | } 97 | } 98 | 99 | .lan-dialog { 100 | border-radius: 12px; 101 | overflow: hidden; 102 | } 103 | 104 | .lan-dialogbound { 105 | &-enter-from { 106 | transform: translate3d(-50%, -50%, 0) scale(0.7); 107 | opacity: 0; 108 | } 109 | 110 | &-enter-active, 111 | &-leave-active { 112 | transition: all .3s; 113 | } 114 | 115 | &-leave-active { 116 | transform: translate3d(-50%, -50%, 0) scale(0.9); 117 | opacity: 0; 118 | } 119 | } -------------------------------------------------------------------------------- /packages/button/button.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, 3 | PropType 4 | } from "vue"; 5 | import {setIcon} from "../utils" 6 | 7 | import * as icons from '../icons' 8 | 9 | export type ButtonType = "primary" | "success" | "warning" | "danger" | "default" 10 | export type ButtonSize = "large" | "small" | "mini" | "normal" 11 | 12 | const IconsTem: any = icons 13 | 14 | export const ButtonProps = { 15 | type: { default: "default", type: String as PropType }, 16 | icon: { default: "", type: String }, 17 | size: { default: "normal", type: String as PropType }, 18 | text: { default: "", type: String }, 19 | bgColor: { default: "", type: String }, 20 | borderColor: { default: "", type: String }, 21 | color: { default: "", type: String }, 22 | disabled: { default: false, type: Boolean }, 23 | radius: { default: "", type: String }, 24 | width: { default: "", type: String }, 25 | style: { default() { return {} }, type: Object }, 26 | } 27 | 28 | export default defineComponent({ 29 | name: "lan-button", 30 | props: ButtonProps, 31 | emits: ['click'], 32 | setup(props, { emit, slots }) { 33 | const { type, size, disabled, radius, bgColor, borderColor, color, width, icon } = props 34 | 35 | const classname = `lan-button-${type} lan-button-size-${size} ${disabled ? "disabled" : ''}` 36 | const style: any = { 37 | ...props.style 38 | } 39 | if (radius) { style.borderRadius = radius } 40 | if (bgColor) { 41 | style["background"] = bgColor; 42 | 43 | } 44 | if (color) { 45 | style["color"] = color; 46 | } 47 | if (width) { 48 | style["width"] = width; 49 | } 50 | 51 | const afterStyle: any = {} 52 | if (type === 'default') { 53 | 54 | if (radius) { 55 | const unit = (radius.match(/[^0-9]/g) || []).join("") 56 | const num = (radius.match(/\d/g) || []).join("") 57 | afterStyle.borderRadius = `${Number(num) * 2}${unit}` 58 | } 59 | if (borderColor) { 60 | afterStyle.borderColor = borderColor 61 | } 62 | } 63 | 64 | let iconTag: any = null 65 | if (icon) { 66 | iconTag = setIcon(icons,icon) 67 | } 68 | 69 | const onClick = (event: MouseEvent) => { 70 | emit('click', event); 71 | } 72 | 73 | 74 | return () => { 75 | return ( 76 |
77 | {iconTag ? : null} 78 | {slots.default ? slots.default() : props.text} 79 | {type === 'default' ? : null} 80 |
81 | ) 82 | } 83 | } 84 | }) 85 | -------------------------------------------------------------------------------- /packages/utils/tools.ts: -------------------------------------------------------------------------------- 1 | import { ref, Ref } from 'vue'; 2 | 3 | 4 | export function setIcon(IconsTem: any, IconN: string) { 5 | const iconName = IconN.replace(/-(\w)/g, (_, c) => c.toUpperCase()); 6 | const IconName = iconName.charAt(0).toUpperCase() + iconName.substr(1, iconName.length - 1) 7 | return IconsTem[iconName] || IconsTem[IconName] || "" 8 | } 9 | 10 | export function isInteger(num: number) { 11 | return num % 1 === 0 12 | } 13 | 14 | 15 | export function calNum(num: number) { 16 | const BL = 1000 17 | let decimal = num * BL % BL; 18 | 19 | let isInteger = num * BL - decimal; 20 | 21 | decimal = decimal / BL; 22 | isInteger = isInteger / BL; 23 | return { decimal, isInteger } 24 | } 25 | 26 | 27 | export function rateCom(decimal: number) { 28 | decimal = decimal * 100; 29 | const n = Math.floor(decimal / 10) * 10; 30 | 31 | return n 32 | } 33 | 34 | export function deepclone(target: any) { 35 | target = target === undefined ? {} : target 36 | if (typeof target !== 'object' || typeof target == null) { 37 | return target 38 | } 39 | let ret: any | any[]; 40 | if (target instanceof Array) { 41 | ret = [] 42 | } else { 43 | ret = {} 44 | } 45 | 46 | for (const key in target) { 47 | if (target.hasOwnProperty(key)) { 48 | ret[key] = deepclone(target[key]) 49 | } 50 | } 51 | return ret; 52 | } 53 | 54 | export function getZIndex() { 55 | const els: NodeListOf = document.querySelectorAll('[class^=lan-mask]') 56 | if (!els || !els.length) { 57 | return -1; 58 | } 59 | 60 | const zArr: number[] = []; 61 | els.forEach(item => { 62 | if (window.getComputedStyle(item).position != 'static') { 63 | zArr.push(parseInt(window.getComputedStyle(item).zIndex) || -1) 64 | } 65 | }) 66 | const ret = Number(Math.max.apply(null, zArr) || 0) 67 | return ret > 500 ? ret : 501 68 | } 69 | 70 | 71 | export function scrollToDistance(toXDistance:number, toYDistance:number) { 72 | let disX = toXDistance - Number(document.documentElement && document.documentElement.scrollLeft); 73 | let disY = toYDistance - Number(document.documentElement && document.documentElement.scrollTop); 74 | 75 | animateScrollToTop() 76 | 77 | function animateScrollToTop() { 78 | 79 | disX = Math.abs(disX) > 1 ? disX - disX / 8 : 1; 80 | disY = Math.abs(disY) > 1 ? disY - disY / 8 : 1; 81 | 82 | const retX = toXDistance - disX 83 | const retY = toYDistance - disY 84 | 85 | if (Math.abs(disX) > 1 || Math.abs(disY) > 1) { 86 | window.requestAnimationFrame(animateScrollToTop); 87 | window.scrollTo(retX, retY); 88 | } 89 | } 90 | } 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /packages/popup/popup.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, 3 | Transition, 4 | watch, ref, PropType 5 | } from 'vue'; 6 | import { getZIndex } from '../utils'; 7 | 8 | export type PopupPosition = "center" | "top" | "bottom" | "left" | "right" 9 | 10 | export default defineComponent({ 11 | name: "lan-popup", 12 | emits: [ 13 | 'opened', 14 | 'closed', 15 | 'open', 16 | 'close', 17 | 'update:show', 18 | ], 19 | props: { 20 | position: { Type: String as PropType, default: 'bottom' }, 21 | transition: { Type: String, default: '' }, 22 | transitionAppear: { type: Boolean, default: true }, 23 | show: { type: Boolean, default: false }, 24 | maskIsClick: { type: Boolean, default: true }, 25 | style: { type: Object, default: () => { return {} } }, 26 | }, 27 | 28 | setup(props, { emit, attrs, slots }) { 29 | const popupRef = ref(); 30 | watch( 31 | () => props.show, 32 | (show) => { 33 | if (show) { 34 | emit('open'); 35 | } 36 | if (!show) { 37 | emit('close'); 38 | } 39 | } 40 | ); 41 | 42 | const onOpened = () => emit('opened'); 43 | const onClosed = () => emit('closed'); 44 | 45 | const onCloseMask = () => { 46 | if(!props.maskIsClick) return; 47 | emit('update:show', false) 48 | }; 49 | 50 | const renderPopup = () => { 51 | const name = 52 | (position === 'center' || position === '') ? 'lan-popup-center' : `lan-popup-${position}`; 53 | 54 | return ( 55 |
56 | {slots.default?.()} 57 |
58 | ) 59 | } 60 | 61 | const renderPopupMask = () => { 62 | return ( 63 | 64 |
65 |
66 | ) 67 | } 68 | 69 | const { position, transition, transitionAppear } = props; 70 | 71 | const name = 72 | (position === 'center' || position === '') ? 'lan-fade' : `lan-slide-${position === 'top' ? 'down' : position === 'bottom' ? 'up' : position}`; 73 | 74 | 75 | const renderTransition = () => { 76 | return ( 77 |
78 | 85 |
86 | ) 87 | } 88 | 89 | return () => { 90 | return ( 91 | <> 92 | {renderTransition()} 93 | {renderPopupMask()} 94 | 95 | ); 96 | }; 97 | }, 98 | }); 99 | -------------------------------------------------------------------------------- /packages/picker/picker.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, 3 | PropType, watch 4 | } from "vue"; 5 | 6 | import Columns, { ColType } from "./columns" 7 | 8 | export type PickerColumnsViod = { 9 | value: string; 10 | defaultIndex: number | string; 11 | } 12 | 13 | export type PickerType = "selector" | "multiSelector" | "linkage"; 14 | export type PickerMode = "selector" | "multiSelector"; 15 | 16 | export const PcikerProps = { 17 | className: { type: String, default: "" }, 18 | title: { type: String, default: "请选择" }, 19 | type: { type: String as PropType, default: "base" }, 20 | 21 | mode: { type: String as PropType, default: 'selector' }, 22 | value: { default: 2 }, 23 | range: { type: Array as PropType, default: () => { return [] } }, 24 | rangeKey: { type: String, default: "name" }, 25 | } 26 | 27 | export default defineComponent({ 28 | name: "lan-picker", 29 | props: PcikerProps, 30 | emits: ['confirm', 'cancel', 'change'], 31 | setup(props, { emit, slots }) { 32 | let retBaseData: any = typeof props.value !== 'object' ? props.value : props.value; 33 | const retMultiData: any = typeof props.value !== 'object' ? props.value : props.value; 34 | 35 | const onEnter = () => { 36 | if (props.mode === 'selector') { 37 | emit('confirm', retBaseData) 38 | } 39 | if (props.mode === 'multiSelector') { 40 | emit('confirm', retMultiData) 41 | } 42 | } 43 | const onCancel = () => { 44 | if (props.mode === 'selector') { 45 | emit('cancel', retBaseData) 46 | } 47 | if (props.mode === 'multiSelector') { 48 | emit('cancel', retMultiData) 49 | } 50 | } 51 | 52 | 53 | const onChange = (e: number | string, index: number) => { 54 | if (props.mode === 'selector') { 55 | retBaseData = e; 56 | emit('change', e) 57 | } else if (props.mode === 'multiSelector') { 58 | retMultiData[index] = e; 59 | emit('change', retMultiData) 60 | } 61 | } 62 | 63 | return () => { 64 | return ( 65 |
66 |
67 | 取消 68 | {props.title} 69 | 确定 70 |
71 |
72 | 73 | { 74 | props.mode === 'selector' ? 75 | { onChange(e, 0) }} /> : 80 | 81 | null 82 | } 83 | {props.mode === 'multiSelector' ? 84 | (props.range as ColType[]).map((item: any, index: number) => ( 85 | { onChange(e, index) }} 91 | /> 92 | )) : null} 93 |
94 |
95 | ); 96 | } 97 | } 98 | }) 99 | -------------------------------------------------------------------------------- /packages/toast/toast.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, onMounted, PropType, reactive, Transition 3 | } from 'vue'; 4 | import { ToastAction } from "./types" 5 | import Icon from '../icons'; 6 | import { getZIndex } from '../utils'; 7 | 8 | const ToastPropsOptions = { 9 | type: String, // 'loading' 'success' 'fail' 'html' 10 | width: [String, Number], 11 | allowHtml: Boolean, 12 | message: String, 13 | duration: Number, 14 | shade: Boolean, 15 | shadeClassName: String, 16 | className: String, 17 | position: String, // 'top' 'bottom' 18 | onOpened: Function as PropType<(action?: ToastAction) => void | Boolean>, 19 | onClose: Function as PropType<(action?: ToastAction) => void | Boolean>, 20 | } 21 | 22 | export default defineComponent({ 23 | name: "lan-toast", 24 | props: ToastPropsOptions, 25 | 26 | setup(props, { emit, attrs, slots }) { 27 | const state = reactive({ 28 | show: true, 29 | }) 30 | 31 | onMounted(() => { 32 | props.onOpened?.('open') 33 | if (props.type === 'text' || props.type === 'success' || props.type === 'fail') { 34 | setTimeout(() => { 35 | state.show = false; 36 | props.onClose?.('close') 37 | }, props.duration) 38 | } 39 | }) 40 | 41 | const loadingRender = () => { 42 | const ren = props.allowHtml ?
:
{props.message}
; 43 | return ( 44 | <> 45 |
46 | 47 | 48 |
49 | {ren} 50 | 51 | ) 52 | } 53 | 54 | const successRender = () => { 55 | return 56 | } 57 | 58 | const failRender = () => { 59 | return 60 | } 61 | 62 | const renderTransition = () => { 63 | let Ren = props.allowHtml ?
:
{props.message}
64 | if (props.type === 'loading') { 65 | Ren = loadingRender() 66 | } 67 | 68 | const successRen = props.type === 'success' ?
{successRender()}
: "" 69 | const failRen = props.type === 'fail' ?
{failRender()}
: "" 70 | return ( 71 | 75 |
78 | {successRen} 79 | {failRen} 80 | {Ren} 81 |
82 |
83 | ) 84 | } 85 | 86 | const renderToastMask = () => { 87 | return ( 88 | 89 |
90 |
91 | ) 92 | } 93 | 94 | return () => { 95 | return ( 96 | <> 97 | {renderTransition()} 98 | {props.shade ? renderToastMask() : ''} 99 | 100 | ); 101 | }; 102 | }, 103 | }); 104 | -------------------------------------------------------------------------------- /packages/button/button.less: -------------------------------------------------------------------------------- 1 | @import '../style/base.less'; 2 | 3 | @corfff: #fff; 4 | @cor333: #333; 5 | @font14: 14px; 6 | 7 | :root { 8 | --lan-button-primary-bg: @primary; 9 | --lan-button-success-bg: @success; 10 | --lan-button-warning-bg: @warning; 11 | --lan-button-danger-bg: @danger; 12 | --lan-button-default-bg: @default; 13 | 14 | --lan-button-primary-color: @corfff; 15 | --lan-button-success-color: @corfff; 16 | --lan-button-warning-color: @corfff; 17 | --lan-button-danger-color: @corfff; 18 | --lan-button-default-color: @cor333; 19 | 20 | --lan-button-default-border-color: #d8d8d8; 21 | 22 | --lan-button-border-radius: 2px; 23 | --lan-button-padding-tb: 2px; 24 | --lan-button-padding-lr: 15px; 25 | 26 | --lan-button-size-large: 32px; 27 | --lan-button-size-normal: 30px; 28 | --lan-button-size-small: 28px; 29 | --lan-button-size-mini: 24px; 30 | 31 | --lan-button-margin-r: 10px; 32 | 33 | --lan-button-icon-r: 5px; 34 | --lan-button-icon-size: 17px; 35 | } 36 | 37 | .lan-button-style { 38 | margin-bottom: 10px;box-shadow: 0 0 5px #ccc; 39 | padding: var(--lan-button-padding-tb) var(--lan-button-padding-lr); 40 | color: var(--lan-button-default-color); 41 | border-radius: var(--lan-button-border-radius); 42 | text-align: center; 43 | font-size: @font14; 44 | display: inline-block; 45 | // border: 0.5px solid var(--lan-button-default-border-color); 46 | background-color: var(--lan-button-default-bg); 47 | line-height: var(--lan-button-size-normal); 48 | cursor: pointer; 49 | min-width: 40px; 50 | margin-right: var(--lan-button-margin-r); 51 | position: relative; 52 | vertical-align: middle; 53 | 54 | &:last-child { 55 | margin-right: 0; 56 | } 57 | 58 | .lan-button-after { 59 | position: absolute; 60 | left: 0; 61 | top: 0; 62 | width: 200%; 63 | height: 1px; 64 | border: 1px solid var(--lan-button-default-border-color); 65 | border-radius: var(--lan-button-border-radius); 66 | color: var(--lan-button-default-border-color); 67 | height: 200%; 68 | transform-origin: left top; 69 | transform: scale(0.5); 70 | } 71 | 72 | &.lan-button-size-large { 73 | line-height: var(--lan-button-size-large); 74 | } 75 | 76 | &.lan-button-size-small { 77 | line-height: var(--lan-button-size-small); 78 | } 79 | 80 | &.lan-button-size-mini { 81 | line-height: var(--lan-button-size-mini); 82 | } 83 | 84 | &.lan-button-primary { 85 | background-color: var(--lan-button-primary-bg); 86 | color: var(--lan-button-primary-color); 87 | border-color: var(--lan-button-primary-bg); 88 | } 89 | 90 | &.lan-button-success { 91 | background-color: var(--lan-button-success-bg); 92 | color: var(--lan-button-success-color); 93 | border-color: var(--lan-button-success-bg); 94 | } 95 | 96 | &.lan-button-warning { 97 | background-color: var(--lan-button-warning-bg); 98 | color: var(--lan-button-warning-color); 99 | border-color: var(--lan-button-warning-bg); 100 | } 101 | 102 | &.lan-button-danger { 103 | background-color: var(--lan-button-danger-bg); 104 | color: var(--lan-button-danger-color); 105 | border-color: var(--lan-button-danger-bg); 106 | } 107 | 108 | &:hover { 109 | opacity: .7; 110 | } 111 | 112 | &:active { 113 | opacity: .9; 114 | } 115 | 116 | &.disabled { 117 | opacity: .8; 118 | cursor: no-drop; 119 | } 120 | 121 | .lan-button-icon { 122 | vertical-align: middle; 123 | width: var(--lan-button-icon-size); 124 | margin-right: var(--lan-button-icon-r) 125 | } 126 | 127 | .lan-button-text{vertical-align: middle;} 128 | 129 | 130 | } -------------------------------------------------------------------------------- /packages/cell/cell.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, 3 | Fragment, 4 | PropType 5 | } from "vue"; 6 | import {setIcon} from "../utils" 7 | import * as icons from '../icons' 8 | 9 | export type CelltitVerAlignType = "top" | "middle" | "bottom" 10 | export type CelltypeType = "1" | "default" 11 | export type CellvalueAlignType = "left" | "center" | "right" 12 | 13 | 14 | export const CellProps = { 15 | type: { default: "default", type: String as PropType }, 16 | title: { default: "", type: String }, 17 | titleIcon: { default: "", type: String }, 18 | titVerAlign: { default: "middle", type: String as PropType }, 19 | titleWidth: { default: "", type: String }, 20 | 21 | value: { default: "", type: String }, 22 | valueIcon: { default: "", type: String }, 23 | 24 | label: { default: "", type: String }, 25 | 26 | alowHtml: { default: false, type: Boolean }, 27 | border: { default: false, type: Boolean }, 28 | isLink: { default: false, type: Boolean }, 29 | required: { default: false, type: Boolean }, 30 | style: { default() { return {} }, type: Object }, 31 | } 32 | 33 | 34 | export default defineComponent({ 35 | name: "lan-cell", 36 | props: CellProps, 37 | emits: ['click'], 38 | setup(props, { emit, slots }) { 39 | const { type, titleIcon, valueIcon, titleWidth } = props 40 | 41 | const titleStyle: any = {} 42 | if (props.titleWidth) { titleStyle["width"] = titleWidth } 43 | 44 | function onClick() { 45 | emit('click') 46 | } 47 | 48 | function titleIconRender() { 49 | let titIconTag: any = null 50 | if (titleIcon) { titIconTag = setIcon(icons,titleIcon) } 51 | 52 | return ( 53 | 54 | {titIconTag ? 55 |
56 | : null} 57 |
58 | ) 59 | } 60 | 61 | function valueIconRender() { 62 | let valIconTag: any = null 63 | if (valueIcon) { valIconTag = setIcon(icons,valueIcon) } 64 | 65 | return ( 66 | 67 | {valIconTag ? 68 |
69 | : null} 70 |
71 | ) 72 | } 73 | 74 | function titleRender() { 75 | return ( 76 | 77 | {props.title ? 78 |
{props.title}
: null 79 | } 80 |
81 | ) 82 | } 83 | function valueRender() { 84 | return ( 85 | 86 | {props.value ? 87 | props.alowHtml ? 88 |
: 89 |
{props.value}
: 90 | null 91 | } 92 |
93 | ) 94 | } 95 | function labelRender() { 96 | return ( 97 | 98 | {props.label ? 99 | props.alowHtml ? 100 |
: 101 |
{props.label}
102 | : null} 103 |
104 | ) 105 | } 106 | 107 | 108 | return () => { 109 | return
112 | {titleIconRender()} 113 | {titleRender()} 114 |
115 | {valueRender()} 116 | 117 | {labelRender()} 118 |
119 | {valueIconRender()} 120 |
121 | } 122 | } 123 | }) 124 | -------------------------------------------------------------------------------- /packages/slider/slider.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, 3 | Fragment, 4 | nextTick, 5 | onMounted, 6 | PropType, 7 | ref, watch 8 | } from "vue"; 9 | import { setIcon } from "../utils" 10 | import * as icons from '../icons' 11 | import { Touch } from "../utils/touch" 12 | 13 | const touch = new Touch(); 14 | 15 | export const SliderProps = { 16 | fillMaxValue: { 17 | type: Number, 18 | default: 1000, 19 | }, 20 | fillMinValue: { 21 | type: Number, 22 | default: 1000, 23 | }, 24 | min: { 25 | type: Number, 26 | default: 0, 27 | }, 28 | max: { 29 | type: Number, 30 | default: 1000, 31 | }, 32 | step: { 33 | type: Number, 34 | default: 50, 35 | }, 36 | } 37 | 38 | 39 | export default defineComponent({ 40 | name: "lan-slider", 41 | props: SliderProps, 42 | emits: ['down', 'move', 'up'], 43 | setup(props, { emit, slots }) { 44 | 45 | const sliderHeight = 30; 46 | let sliderBoxWidth = 0; 47 | let initXLeft = 0; 48 | let initRight = 0; 49 | 50 | const left = ref(0) 51 | const width = ref(100) 52 | 53 | // let percentage = 0; 54 | 55 | onMounted(() => { 56 | sliderBoxWidth = Number(document.querySelector('.lan-slider-container')?.getBoundingClientRect().width); 57 | // initWidth = sliderBoxWidth; 58 | // percentage = props.fillMaxValue / sliderBoxWidth; 59 | }) 60 | 61 | 62 | const onTouchStart = (e: TouchEvent, type: string) => { 63 | touch.start(e) 64 | if (type === 'min') { 65 | left.value = initXLeft; 66 | } 67 | 68 | if (type === 'max') { 69 | width.value = sliderBoxWidth - left.value - initRight 70 | } 71 | 72 | } 73 | const onTouchMove = (e: TouchEvent, type: string) => { 74 | touch.move(e) 75 | 76 | if (left.value < 0 || left.value > sliderBoxWidth) return; 77 | if (width.value < 0 || (width.value + left.value) > sliderBoxWidth) return; 78 | 79 | 80 | if (type === 'min') { 81 | left.value = initXLeft + touch.deltaX; 82 | } 83 | 84 | const w = sliderBoxWidth - initRight - left.value 85 | width.value = w; 86 | 87 | 88 | if (type === 'max') { 89 | width.value = w + touch.deltaX; 90 | } 91 | } 92 | const onTouchEnd = (e: TouchEvent, type: string) => { 93 | left.value = left.value <= 0 ? 0 : left.value; 94 | left.value = left.value >= sliderBoxWidth ? sliderBoxWidth : left.value; 95 | width.value = width.value <= 0 ? 0 : width.value; 96 | width.value = (width.value + left.value) >= sliderBoxWidth ? sliderBoxWidth - left.value - initRight : width.value; 97 | 98 | 99 | initXLeft = left.value; 100 | initRight = sliderBoxWidth - left.value - width.value 101 | 102 | 103 | } 104 | const onTouchCancel = (e: TouchEvent, type: string) => { 105 | console.log('onTouchCancel'); 106 | 107 | } 108 | 109 | return () => { 110 | return
111 |
112 |
{ onTouchStart(e, 'min') }} 114 | onTouchmove={(e) => { onTouchMove(e, 'min') }} 115 | onTouchend={(e) => { onTouchEnd(e, 'min') }} 116 | onTouchcancel={(e) => { onTouchCancel(e, 'min') }} 117 | >
118 |
{ onTouchStart(e, 'max') }} 120 | onTouchmove={(e) => { onTouchMove(e, 'max') }} 121 | onTouchend={(e) => { onTouchEnd(e, 'max') }} 122 | onTouchcancel={(e) => { onTouchCancel(e, 'max') }} 123 | >
124 |
125 |
126 | } 127 | } 128 | }) 129 | -------------------------------------------------------------------------------- /packages/style/animation.less: -------------------------------------------------------------------------------- 1 | @import './var'; 2 | 3 | 4 | 5 | @keyframes lan-slide-height-enter { 6 | from { 7 | height: 0; 8 | } 9 | } 10 | @keyframes lan-slide-height-leave { 11 | to { 12 | height: 0; 13 | } 14 | } 15 | 16 | 17 | 18 | @keyframes lan-slide-up-enter { 19 | from { 20 | transform: translate3d(0, 100%, 0); 21 | } 22 | } 23 | 24 | @keyframes lan-slide-up-leave { 25 | to { 26 | transform: translate3d(0, 100%, 0); 27 | } 28 | } 29 | 30 | @keyframes lan-slide-down-enter { 31 | from { 32 | transform: translate3d(0, -100%, 0); 33 | } 34 | } 35 | 36 | @keyframes lan-slide-down-leave { 37 | to { 38 | transform: translate3d(0, -100%, 0); 39 | } 40 | } 41 | 42 | @keyframes lan-slide-left-enter { 43 | from { 44 | transform: translate3d(-100%, 0, 0); 45 | } 46 | } 47 | 48 | @keyframes lan-slide-left-leave { 49 | to { 50 | transform: translate3d(-100%, 0, 0); 51 | } 52 | } 53 | 54 | @keyframes lan-slide-right-enter { 55 | from { 56 | transform: translate3d(100%, 0, 0); 57 | } 58 | } 59 | 60 | @keyframes lan-slide-right-leave { 61 | to { 62 | transform: translate3d(100%, 0, 0); 63 | } 64 | } 65 | 66 | @keyframes lan-fade-in { 67 | from { 68 | opacity: 0; 69 | } 70 | 71 | to { 72 | opacity: 1; 73 | } 74 | } 75 | 76 | @keyframes lan-fade-out { 77 | from { 78 | opacity: 1; 79 | } 80 | 81 | to { 82 | opacity: 0; 83 | } 84 | } 85 | 86 | @keyframes lan-rotate { 87 | from { 88 | transform: rotate(0deg); 89 | } 90 | 91 | to { 92 | transform: rotate(360deg); 93 | } 94 | } 95 | 96 | .lan-fade { 97 | &-enter-active { 98 | animation: var(--lan-animation-duration-base) lan-fade-in both 99 | var(--lan-animation-timing-function-enter); 100 | } 101 | 102 | &-leave-active { 103 | animation: var(--lan-animation-duration-base) lan-fade-out both 104 | var(--lan-animation-timing-function-leave); 105 | } 106 | } 107 | 108 | .lan-slide-height { 109 | &-enter-active { 110 | animation: lan-slide-height-enter var(--lan-animation-duration-base) both 111 | var(--lan-animation-timing-function-enter); 112 | } 113 | 114 | &-leave-active { 115 | animation: lan-slide-height-leave var(--lan-animation-duration-base) both 116 | var(--lan-animation-timing-function-leave); 117 | } 118 | } 119 | 120 | 121 | .lan-slide-up { 122 | &-enter-active { 123 | animation: lan-slide-up-enter var(--lan-animation-duration-base) both 124 | var(--lan-animation-timing-function-enter); 125 | } 126 | 127 | &-leave-active { 128 | animation: lan-slide-up-leave var(--lan-animation-duration-base) both 129 | var(--lan-animation-timing-function-leave); 130 | } 131 | } 132 | 133 | .lan-slide-down { 134 | &-enter-active { 135 | animation: lan-slide-down-enter var(--lan-animation-duration-base) both 136 | var(--lan-animation-timing-function-enter); 137 | } 138 | 139 | &-leave-active { 140 | animation: lan-slide-down-leave var(--lan-animation-duration-base) both 141 | var(--lan-animation-timing-function-leave); 142 | } 143 | } 144 | 145 | .lan-slide-left { 146 | &-enter-active { 147 | animation: lan-slide-left-enter var(--lan-animation-duration-base) both 148 | var(--lan-animation-timing-function-enter); 149 | } 150 | 151 | &-leave-active { 152 | animation: lan-slide-left-leave var(--lan-animation-duration-base) both 153 | var(--lan-animation-timing-function-leave); 154 | } 155 | } 156 | 157 | .lan-slide-right { 158 | &-enter-active { 159 | animation: lan-slide-right-enter var(--lan-animation-duration-base) both 160 | var(--lan-animation-timing-function-enter); 161 | } 162 | 163 | &-leave-active { 164 | animation: lan-slide-right-leave var(--lan-animation-duration-base) both 165 | var(--lan-animation-timing-function-leave); 166 | } 167 | } -------------------------------------------------------------------------------- /packages/calendar/calendar.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, watch, 3 | } from 'vue'; 4 | import moment from "moment"; 5 | import { getAllDate, DateObj, DateItem } from "./date" 6 | 7 | export default defineComponent({ 8 | name: "lan-calendar", 9 | props: { 10 | minDate: { Type: String, default: moment().format("YYYY-MM-DD") }, 11 | maxDate: { Type: String, default: moment().add(1, 'months').format("YYYY-MM-DD") }, 12 | defaultDate: { Type: [String, Array], default: moment().format("YYYY-MM-DD") }, 13 | isShowSectionBg: { Type: Boolean, default: true }, 14 | style: { type: Object, default: () => { return {} } }, 15 | monthFormatter: { type: String, default: "YYYY年MM月" }, 16 | dateFormatter: { type: String, default: "DD" }, 17 | startWeek: { type: Number, default: 1 }, // 从周几开始1,2,3,4,5,6,0 18 | className: { type: String, default: "" }, 19 | }, 20 | emits: ['choose'], 21 | setup(props, { emit, attrs, slots }) { 22 | const calendarObj: DateObj = getAllDate(moment(props.minDate), moment(props.maxDate), props.startWeek) 23 | 24 | const dateChooseFn = (DateItem: DateItem) => { 25 | if (DateItem.pass || DateItem.future) { return; } 26 | emit('choose', moment(DateItem.date).format("YYYY-MM-DD")) 27 | } 28 | 29 | let chooseDatelist: string[] = []; 30 | 31 | watch( 32 | () => props.defaultDate, 33 | (val) => { 34 | chooseDatelist = []; 35 | const defaultDate = val; 36 | if (typeof defaultDate === 'object' && Array.isArray(defaultDate)) { 37 | if ((defaultDate as any[]).length === 2) { 38 | const diffnum = Math.abs(moment(defaultDate[0]).diff(moment(defaultDate[1]), 'days')) 39 | for (let i = 1; i < diffnum; i++) { 40 | const first = moment(defaultDate[0]).clone(); 41 | chooseDatelist.push(first.add(i, 'days').format('YYYY-MM-DD')) 42 | } 43 | } 44 | } 45 | }, 46 | { deep: true } 47 | ) 48 | 49 | const renderDate = () => { 50 | const calendarRen = []; 51 | for (const key in calendarObj) { 52 | calendarRen.push(
{moment(calendarObj[key].month).format(props.monthFormatter)}
) 53 | 54 | if (calendarObj[key].date.length) { 55 | const cren: any = [] 56 | 57 | calendarObj[key].date.forEach(item => { 58 | let hasDefault; 59 | 60 | if (typeof props.defaultDate === 'string') { 61 | hasDefault = moment(item.date).format("YYYY-MM-DD") === moment(props.defaultDate).format("YYYY-MM-DD") 62 | } else { 63 | hasDefault = (props.defaultDate as string[]).includes(moment(item.date).format("YYYY-MM-DD")) 64 | } 65 | 66 | let hasChoose = props.isShowSectionBg; 67 | if (props.isShowSectionBg) { 68 | hasChoose = (chooseDatelist as string[]).includes(moment(item.date).format("YYYY-MM-DD")) 69 | } 70 | 71 | cren.push( 72 |
dateChooseFn(item)} 73 | class={`lan-calendar-con-date 74 | ${hasDefault ? 'lan-calendar-date-active' : ''} 75 | ${item.pass || ''} ${item.future || ''} 76 | ${hasChoose ? 'lan-calendar-date-choose' : ''}`}> 77 | {item.date ? moment(item.date).format(props.dateFormatter) : ""} 78 |
79 | ) 80 | }) 81 | calendarRen.push(
{...cren}
) 82 | } 83 | } 84 | return <> 85 |
86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
96 |
{calendarRen}
97 | 98 | } 99 | 100 | return () => { 101 | return ( 102 | <> 103 |
{renderDate()}
104 | 105 | ); 106 | }; 107 | }, 108 | }); -------------------------------------------------------------------------------- /packages/dialog/doalog.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, PropType, reactive, Transition 3 | } from 'vue'; 4 | import { getZIndex } from '../utils'; 5 | import { DialogAction } from "./types" 6 | 7 | 8 | 9 | const DialogPropsOptions = { 10 | title: String, 11 | width: [String, Number], 12 | message: String, 13 | callback: Function as PropType<(action?: DialogAction) => void>, 14 | className: String, 15 | allowHtml: Boolean, 16 | lockScroll: Boolean, 17 | beforeClose: Function as PropType<(action?: DialogAction) => void | Boolean>, 18 | cancelButtonText: String, 19 | confirmButtonText: String, 20 | showCancelButton: Boolean, 21 | showConfirmButton: Boolean 22 | } 23 | 24 | export default defineComponent({ 25 | name: "lan-dialog", 26 | emits: ['confirm', 'cancel', 'close'], 27 | props: DialogPropsOptions, 28 | 29 | setup(props, { emit, attrs, slots }) { 30 | console.log(props); 31 | 32 | const state = reactive({ 33 | show: true, 34 | isloadconfirm: false, 35 | isloadcancel: false 36 | }) 37 | 38 | const cancelFn = async () => { 39 | if(state.isloadconfirm) return 40 | state.isloadcancel = true; 41 | const isclose = await props.beforeClose?.('cancel') 42 | state.isloadcancel = false; 43 | if (isclose !== false) { 44 | props.callback?.('cancel'); 45 | emit('cancel') 46 | state.show = false; 47 | } 48 | } 49 | 50 | const confirmFn = async () => { 51 | if(state.isloadcancel) return 52 | state.isloadconfirm = true; 53 | const isclose = await props.beforeClose?.('confirm') 54 | state.isloadconfirm = false; 55 | 56 | 57 | if (isclose !== false) { 58 | props.callback?.('confirm'); 59 | emit('confirm') 60 | state.show = false; 61 | } 62 | } 63 | 64 | const loadingRender = (type?: string) => { 65 | if(type) return `` 66 | return 67 | } 68 | 69 | const renderTransition = () => { 70 | if (props.allowHtml) { 71 | return ( 72 | 76 |
80 |
81 |
82 |
83 | 87 | 91 |
92 |
93 |
94 | ) 95 | } 96 | return ( 97 | 101 |
104 |
{props.title}
105 |
{props.message}
106 |
107 | {state.isloadcancel ? loadingRender() : props.cancelButtonText} 110 | {state.isloadconfirm ? loadingRender() : props.confirmButtonText} 113 |
114 |
115 |
116 | ) 117 | } 118 | 119 | const renderDialogMask = () => { 120 | return ( 121 | 122 |
123 |
124 | ) 125 | } 126 | 127 | return () => { 128 | return ( 129 | <> 130 | {renderTransition()} 131 | {renderDialogMask()} 132 | 133 | ); 134 | }; 135 | }, 136 | }); 137 | -------------------------------------------------------------------------------- /packages/picker/columns.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | computed, 3 | defineComponent, 4 | nextTick, 5 | ref, unref, Ref, PropType 6 | } from "vue"; 7 | import { Touch } from "../utils/touch" 8 | const touch = new Touch(); 9 | 10 | export type ColType = { 11 | [key: string]: any 12 | } 13 | export type columnsListType = { defaultIndex: number; list: ColType } 14 | export const ColumnsProps = { 15 | columns: { type: Array as PropType, default: [] }, 16 | defaultIndex: { type: [Number, String], default: 2 }, 17 | rangeKey: { type: String, default: "name" }, 18 | 19 | onChange: { 20 | type: Function, default() { 21 | return 'Default function' 22 | } 23 | }, 24 | } 25 | 26 | export default defineComponent({ 27 | name: "lan-columns", 28 | props: ColumnsProps, 29 | setup(props, { emit, slots }) { 30 | const columnWraperRef = ref(); 31 | const columnConRef = ref(); 32 | const columnRef = ref(); 33 | const columnWraperH = 175; 34 | const columnH = 35; 35 | const columnConH = columnH * props.columns.length; 36 | 37 | let disY = 0; 38 | let isTouchend = false; 39 | 40 | const deltaY = ref(0) 41 | 42 | const TODISTANCE = 30; // 距离上下的拉扯距离 43 | 44 | let moveMinValue = 0 45 | let moveMaxValue = 0 46 | 47 | let defaultIndex = props.defaultIndex; 48 | let zoomIndex = props.defaultIndex; 49 | 50 | let deltaYTime = 0; 51 | let deltaDistance = 0; 52 | let MoveY = 0; 53 | 54 | 55 | const initH = () => { 56 | // columnWraperH = columnWraperRef.value?.getBoundingClientRect().height || 0; 57 | // columnConH = columnConRef.value?.getBoundingClientRect().height || 0; 58 | // columnH = columnRef.value?.getBoundingClientRect().height || 0; 59 | moveMinValue = columnH * 2; 60 | moveMaxValue = -columnConH + columnWraperH - columnH * 2; 61 | deltaY.value = (2 - Number(props.defaultIndex)) * columnH; 62 | } 63 | 64 | 65 | const columnsList = computed(() => { 66 | nextTick(() => { initH() }) 67 | 68 | return Array(props.columns.length).fill("").map((_, index) => { 69 | return { 70 | defaultIndex: index, 71 | list: props.columns[index] 72 | } as columnsListType 73 | }) 74 | }) 75 | 76 | const onTouchStart = (e: any) => { 77 | touch.start(e) 78 | disY = deltaY.value; 79 | 80 | isTouchend = false; 81 | deltaYTime = Date.now() 82 | deltaDistance = 0; 83 | } 84 | const onTouchMove = (e: any) => { 85 | if (deltaY.value > moveMinValue + TODISTANCE || deltaY.value < moveMaxValue - TODISTANCE) { 86 | return 87 | } 88 | touch.move(e) 89 | deltaY.value = disY + touch.deltaY; 90 | MoveY = e.touches[0].clientY 91 | const now = Date.now(); // 滑动的过程中,如果超过200毫秒就重置deltaDistance,deltaDistance越小,滑动的越快. 92 | if (now - deltaYTime > 200) { 93 | deltaYTime = now; 94 | deltaDistance = e.touches[0].clientY; 95 | } 96 | zoomIndex = Math.round(Math.abs(deltaY.value / columnH - 2)) 97 | } 98 | function onTouchEnd(e: any) { 99 | const speed = Math.abs(MoveY - deltaDistance) / (Date.now() - deltaYTime) || 0 100 | deltaY.value = disY + (touch.deltaY) * (speed > 2 ? Math.floor(speed) : 1); 101 | 102 | if (deltaY.value > moveMinValue) { 103 | deltaY.value = moveMinValue; 104 | setIndex() 105 | changeEmit() 106 | return 107 | } 108 | if (deltaY.value < moveMaxValue) { 109 | deltaY.value = moveMaxValue; 110 | setIndex() 111 | changeEmit() 112 | return 113 | } 114 | 115 | isTouchend = true; 116 | 117 | deltaY.value = Math.round(deltaY.value / columnH) * columnH 118 | setIndex() 119 | changeEmit() 120 | } 121 | const changeEmit = () => { 122 | props.onChange(defaultIndex) 123 | } 124 | 125 | const setIndex = () => { 126 | defaultIndex = Math.round(Math.abs(deltaY.value / columnH - 2)) 127 | zoomIndex = Math.round(Math.abs(deltaY.value / columnH - 2)) 128 | } 129 | 130 | function columnRender() { 131 | const z = Number(zoomIndex) 132 | 133 | return columnsList.value.map((item: { defaultIndex: number; list: ColType }, index) =>
137 | 138 | {item.list[props.rangeKey]} 139 | 140 |
) 141 | } 142 | 143 | return () => { 144 | const columnstyle = { 145 | transform: `translate3d(0px, ${deltaY.value}px, 0px)`, 146 | transitionDuration: `${isTouchend ? 500 : 0}ms`, 147 | transitionProperty: `${isTouchend ? 'all' : 'none'}`, 148 | transitionTimingFunction: 'ease-out' 149 | } 150 | 151 | return ( 152 |
153 |
160 |
163 | {columnRender()} 164 |
165 |
166 |
167 |
168 | ); 169 | } 170 | } 171 | }) 172 | -------------------------------------------------------------------------------- /packages/utils/vant-emulator.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Emulate touch event 4 | * Source:https://github.com/hammerjs/touchemulator 5 | */ 6 | 7 | (function () { 8 | if (typeof window === 'undefined') { 9 | return; 10 | } 11 | var eventTarget; 12 | var supportTouch = 'ontouchstart' in window; 13 | 14 | // polyfills 15 | if (!document.createTouch) { 16 | document.createTouch = function ( 17 | view, 18 | target, 19 | identifier, 20 | pageX, 21 | pageY, 22 | screenX, 23 | screenY 24 | ) { 25 | // auto set 26 | return new Touch( 27 | target, 28 | identifier, 29 | { 30 | pageX: pageX, 31 | pageY: pageY, 32 | screenX: screenX, 33 | screenY: screenY, 34 | clientX: pageX - window.pageXOffset, 35 | clientY: pageY - window.pageYOffset, 36 | }, 37 | 0, 38 | 0 39 | ); 40 | }; 41 | } 42 | 43 | if (!document.createTouchList) { 44 | document.createTouchList = function () { 45 | var touchList = TouchList(); 46 | for (var i = 0; i < arguments.length; i++) { 47 | touchList[i] = arguments[i]; 48 | } 49 | touchList.length = arguments.length; 50 | return touchList; 51 | }; 52 | } 53 | 54 | if (!Element.prototype.matches) { 55 | Element.prototype.matches = 56 | Element.prototype.msMatchesSelector || 57 | Element.prototype.webkitMatchesSelector; 58 | } 59 | 60 | if (!Element.prototype.closest) { 61 | Element.prototype.closest = function (s) { 62 | var el = this; 63 | 64 | do { 65 | if (el.matches(s)) return el; 66 | el = el.parentElement || el.parentNode; 67 | } while (el !== null && el.nodeType === 1); 68 | 69 | return null; 70 | }; 71 | } 72 | 73 | /** 74 | * create an touch point 75 | * @constructor 76 | * @param target 77 | * @param identifier 78 | * @param pos 79 | * @param deltaX 80 | * @param deltaY 81 | * @returns {Object} touchPoint 82 | */ 83 | 84 | var Touch = function Touch(target, identifier, pos, deltaX, deltaY) { 85 | deltaX = deltaX || 0; 86 | deltaY = deltaY || 0; 87 | 88 | this.identifier = identifier; 89 | this.target = target; 90 | this.clientX = pos.clientX + deltaX; 91 | this.clientY = pos.clientY + deltaY; 92 | this.screenX = pos.screenX + deltaX; 93 | this.screenY = pos.screenY + deltaY; 94 | this.pageX = pos.pageX + deltaX; 95 | this.pageY = pos.pageY + deltaY; 96 | }; 97 | 98 | /** 99 | * create empty touchlist with the methods 100 | * @constructor 101 | * @returns touchList 102 | */ 103 | function TouchList() { 104 | var touchList = []; 105 | 106 | touchList['item'] = function (index) { 107 | return this[index] || null; 108 | }; 109 | 110 | // specified by Mozilla 111 | touchList['identifiedTouch'] = function (id) { 112 | return this[id + 1] || null; 113 | }; 114 | 115 | return touchList; 116 | } 117 | 118 | /** 119 | * only trigger touches when the left mousebutton has been pressed 120 | * @param touchType 121 | * @returns {Function} 122 | */ 123 | 124 | var initiated = false; 125 | function onMouse(touchType) { 126 | return function (ev) { 127 | // prevent mouse events 128 | 129 | if (ev.type === 'mousedown') { 130 | initiated = true; 131 | } 132 | 133 | if (ev.type === 'mouseup') { 134 | initiated = false; 135 | } 136 | 137 | if (ev.type === 'mousemove' && !initiated) { 138 | return; 139 | } 140 | 141 | // The EventTarget on which the touch point started when it was first placed on the surface, 142 | // even if the touch point has since moved outside the interactive area of that element. 143 | // also, when the target doesnt exist anymore, we update it 144 | if ( 145 | ev.type === 'mousedown' || 146 | !eventTarget || 147 | (eventTarget && !eventTarget.dispatchEvent) 148 | ) { 149 | eventTarget = ev.target; 150 | } 151 | 152 | if (eventTarget.closest('[data-no-touch-simulate]') == null) { 153 | triggerTouch(touchType, ev); 154 | } 155 | 156 | // reset 157 | if (ev.type === 'mouseup') { 158 | eventTarget = null; 159 | } 160 | }; 161 | } 162 | 163 | /** 164 | * trigger a touch event 165 | * @param eventName 166 | * @param mouseEv 167 | */ 168 | function triggerTouch(eventName, mouseEv) { 169 | var touchEvent = document.createEvent('Event'); 170 | touchEvent.initEvent(eventName, true, true); 171 | 172 | touchEvent.altKey = mouseEv.altKey; 173 | touchEvent.ctrlKey = mouseEv.ctrlKey; 174 | touchEvent.metaKey = mouseEv.metaKey; 175 | touchEvent.shiftKey = mouseEv.shiftKey; 176 | 177 | touchEvent.touches = getActiveTouches(mouseEv); 178 | touchEvent.targetTouches = getActiveTouches(mouseEv); 179 | touchEvent.changedTouches = createTouchList(mouseEv); 180 | 181 | eventTarget.dispatchEvent(touchEvent); 182 | } 183 | 184 | /** 185 | * create a touchList based on the mouse event 186 | * @param mouseEv 187 | * @returns {TouchList} 188 | */ 189 | function createTouchList(mouseEv) { 190 | var touchList = TouchList(); 191 | touchList.push(new Touch(eventTarget, 1, mouseEv, 0, 0)); 192 | return touchList; 193 | } 194 | 195 | /** 196 | * receive all active touches 197 | * @param mouseEv 198 | * @returns {TouchList} 199 | */ 200 | function getActiveTouches(mouseEv) { 201 | // empty list 202 | if (mouseEv.type === 'mouseup') { 203 | return TouchList(); 204 | } 205 | return createTouchList(mouseEv); 206 | } 207 | 208 | /** 209 | * TouchEmulator initializer 210 | */ 211 | function TouchEmulator() { 212 | window.addEventListener('mousedown', onMouse('touchstart'), true); 213 | window.addEventListener('mousemove', onMouse('touchmove'), true); 214 | window.addEventListener('mouseup', onMouse('touchend'), true); 215 | } 216 | 217 | // start distance when entering the multitouch mode 218 | TouchEmulator['multiTouchOffset'] = 75; 219 | 220 | if (!supportTouch) { 221 | new TouchEmulator(); 222 | } 223 | })(); -------------------------------------------------------------------------------- /packages/rate/rate.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, 3 | Fragment, Ref, ref 4 | } from "vue"; 5 | import { calNum, rateCom } from "../utils" 6 | import { Touch } from "../utils/touch" 7 | 8 | import Icon from '../icons' 9 | const touch = new Touch(); 10 | 11 | /** 12 | * 整数的话会向上取整, 2.4就是3 13 | */ 14 | 15 | 16 | export const RateProps = { 17 | modelValue: { default: 0, type: Number }, // 18 | count: { default: 5, type: Number }, //图标个数 19 | size: { default: "20px", type: String }, // 图标大小 20 | space: { default: "5px", type: String }, // 间距 21 | initColor: { default: "#999", type: String }, // 颜色-未选中 22 | color: { default: "#ff6600", type: String }, // 颜色-选中 23 | disabledColor: { default: "#c8c9cc", type: String }, // 禁用颜色 24 | initIcon: { default: "", type: String }, // 图标-未选中 25 | icon: { default: "star-filled", type: String }, // 图标-选中 26 | disabled: { default: false, type: Boolean }, // 是否禁用 27 | readonly: { default: false, type: Boolean }, // 只读状态,可以设置小数 28 | ismove: { default: false, type: Boolean }, // 只读状态,可以设置小数 29 | allowHalf: { default: false, type: Boolean }, // 半星 30 | decimal: { default: false, type: Boolean }, // 小数 31 | } 32 | 33 | 34 | export default defineComponent({ 35 | name: "lan-score", 36 | props: RateProps, 37 | emits: ['click', 'update:modelValue'], 38 | setup(props, { emit, slots }) { 39 | const { disabled, disabledColor, color, initColor, readonly, icon, initIcon, size, ismove, decimal, allowHalf } = props; 40 | const isStar = icon.toUpperCase().indexOf('STAR') === 0; 41 | const isHeart = icon.toUpperCase().indexOf('heart') === 0; 42 | 43 | const untouch = disabled || readonly || !ismove; 44 | 45 | const _color = disabled ? disabledColor || color : color; 46 | 47 | let modelValue = props.modelValue; 48 | const count = [...Array(props.count).keys()] 49 | 50 | const RateRefs = count.map(item => ref()); 51 | const RateSpanRefs = count.map(item => ref()); 52 | 53 | const rateStyle = { 54 | marginRight: props.space, 55 | } 56 | 57 | let offsetXList: number[] = []; 58 | function updateRate(type?: string) { 59 | if (type === 'start') { 60 | offsetXList = []; 61 | const halfIconW = (RateSpanRefs[0].value as HTMLElement).getBoundingClientRect().width / 2 62 | 63 | RateRefs.reduce((preVal, curVal: Ref) => { 64 | const sum = preVal + curVal.value.getBoundingClientRect().width 65 | if (allowHalf) { 66 | 67 | offsetXList.push(preVal + halfIconW) 68 | } 69 | offsetXList.push(sum) 70 | return sum; 71 | }, 0) 72 | } 73 | 74 | const toParentX = (touch.initX - touch.parentOffsetX + touch.deltaX); 75 | 76 | for (let i = 0; i < offsetXList.length; i++) { 77 | const item = offsetXList[i] 78 | if (item > toParentX) { 79 | const nX = allowHalf ? (i + 1) / 2 : (i + 1) 80 | if (nX !== modelValue) { 81 | modelValue = nX 82 | emit('update:modelValue', modelValue) 83 | } 84 | break 85 | } 86 | } 87 | } 88 | 89 | // 整数 90 | function integerRender(index: number) { 91 | return 92 | {index < modelValue ? 93 | 94 | : 95 | 96 | } 97 | 98 | } 99 | 100 | // 半星 101 | function allowHalfRender(index: number) { 102 | const isdecima = /\./g.test(`${modelValue}`) 103 | 104 | const { isInteger } = calNum(modelValue); 105 | const cname = isStar ? 'star' : 'heart' 106 | const rateICON = `${cname}50` 107 | return 108 | {index < isInteger ? 109 | : 110 | index <= isInteger && isdecima ? 111 | : 112 | 113 | } 114 | 115 | } 116 | 117 | // 小数 118 | function decimalRender(index: number) { 119 | const { decimal, isInteger } = calNum(modelValue); 120 | const cname = isStar ? 'star' : 'heart' 121 | const rateICON = `${cname}${rateCom(decimal)}` 122 | return 123 | {index < isInteger ? 124 | : 125 | index <= isInteger ? 126 | : 127 | 128 | } 129 | 130 | } 131 | 132 | function renderType(index: number) { 133 | if (decimal) { 134 | return decimalRender(index) 135 | } 136 | if (allowHalf) { 137 | return allowHalfRender(index) 138 | } 139 | 140 | return integerRender(index) 141 | } 142 | 143 | const changeValue = (index: number) => { 144 | if (disabled || readonly || ismove) return; 145 | modelValue = index + 1; 146 | emit('update:modelValue', modelValue) 147 | } 148 | 149 | const onTouchStart = (event: TouchEvent) => { 150 | if (untouch) return; 151 | touch.start(event); 152 | updateRate('start'); 153 | } 154 | 155 | // TODO由于ICON组件更改导致onTouchMove移动过程中不运行,在css里面加个after防止鼠标滑动到icon上面 156 | function onTouchMove(event: any) { 157 | if (untouch) return; 158 | touch.move(event); 159 | 160 | if (!touch.isHorizontal()) return; 161 | updateRate(); 162 | } 163 | 164 | 165 | return () => { 166 | return
171 | { 172 | count.map((num, index) =>
{ changeValue(index) }} key={index} ref={RateRefs[index]}> 173 |
{renderType(index)}
174 |
) 175 | } 176 |
177 | } 178 | } 179 | }) 180 | 181 | -------------------------------------------------------------------------------- /packages/slider/slider copy.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, 3 | Fragment, 4 | nextTick, 5 | onMounted, 6 | PropType, 7 | ref, watch 8 | } from "vue"; 9 | import { setIcon } from "../utils" 10 | import * as icons from '../icons' 11 | 12 | 13 | 14 | export const SliderProps = { 15 | fillValue: { 16 | type: Number, 17 | default: 1000, 18 | }, 19 | minValue: { 20 | type: Number, 21 | default: 0, 22 | }, 23 | maxValue: { 24 | type: Number, 25 | default: 1000, 26 | }, 27 | step: { 28 | type: Number, 29 | default: 50, 30 | }, 31 | } 32 | 33 | 34 | export default defineComponent({ 35 | name: "lan-slider", 36 | props: SliderProps, 37 | emits: ['down', 'move', 'up'], 38 | setup(props, { emit, slots }) { 39 | 40 | const tipShow = ref(false) 41 | const tipLeft = ref(0) 42 | const minLeft = ref(0); 43 | const maxLeft = ref(0); 44 | 45 | const touchWidth = ref(25); 46 | 47 | let lineWidth = 0; 48 | let lineLeft = 0; 49 | 50 | const curValue = ref(0); 51 | const sMinValue = ref(0); 52 | const sMaxValue = ref(0); 53 | 54 | let percentage = 0; 55 | const multiple = 1; 56 | 57 | watch(() => props.minValue, (newVal, oldVal) => { 58 | sMinValue.value = props.minValue; 59 | minLeft.value = sMinValue.value / percentage; 60 | }) 61 | watch(() => props.maxValue, (newVal, oldVal) => { 62 | sMaxValue.value = props.maxValue; 63 | maxLeft.value = sMaxValue.value / percentage; 64 | }) 65 | 66 | onMounted(() => { 67 | nextTick(() => { 68 | // @ts-ignore 69 | touchWidth.value = document.querySelector('.fj-touch-left').getBoundingClientRect().width; 70 | // @ts-ignore 71 | const fjLine = document.querySelector('.fj-line').getBoundingClientRect() 72 | lineWidth = fjLine.width; 73 | lineLeft = fjLine.left 74 | maxLeft.value = lineWidth; 75 | percentage = (props.fillValue / lineWidth) * multiple; 76 | sMinValue.value = props.minValue; 77 | sMaxValue.value = props.maxValue; 78 | minLeft.value = sMinValue.value * multiple / percentage; 79 | maxLeft.value = sMaxValue.value * multiple / percentage; 80 | }) 81 | }) 82 | 83 | 84 | const touchstart = (e: TouchEvent, type: string) => { 85 | 86 | emit('down', { 87 | ...e, 88 | custom: { 89 | type, 90 | minValue: sMinValue.value, 91 | maxValue: sMaxValue.value 92 | } 93 | }) 94 | } 95 | function touchmove(e: TouchEvent, type: string) { 96 | const disX = e.touches[0].clientX - lineLeft 97 | if (disX < 0 || disX > lineWidth) { return; } 98 | if (type === 'min') { 99 | minLeft.value = Math.floor(disX); 100 | if (minLeft.value < 0) { minLeft.value = 0; return; } 101 | if (maxLeft.value - minLeft.value <= touchWidth.value) { minLeft.value = maxLeft.value - touchWidth.value; return; } 102 | curValue.value = Math.floor(minLeft.value * percentage / multiple); 103 | } 104 | 105 | if (type === 'max') { 106 | maxLeft.value = Math.ceil(disX); 107 | if (maxLeft.value > lineWidth) { maxLeft.value = lineWidth; return; } 108 | if (maxLeft.value - minLeft.value <= touchWidth.value) { maxLeft.value = minLeft.value + touchWidth.value; return; } 109 | curValue.value = Math.round(maxLeft.value * percentage / multiple); 110 | } 111 | tipShow.value = true; 112 | tipLeft.value = Math.round(curValue.value * multiple / percentage - 15); 113 | emit('move', { 114 | ...e, 115 | custom: { 116 | type, 117 | minValue: sMinValue.value, 118 | maxValue: sMaxValue.value, 119 | curValue: curValue.value, 120 | } 121 | }) 122 | } 123 | function touchend(e: TouchEvent, type: string) { 124 | if (type === 'min') { 125 | if (props.step === 1) { 126 | sMinValue.value = curValue.value; 127 | } else { 128 | const stepnum = Math.round((minLeft.value * percentage / multiple) / props.step); 129 | sMinValue.value = stepnum * props.step; 130 | minLeft.value = sMinValue.value * multiple / percentage; 131 | } 132 | } 133 | if (type === 'max') { 134 | if (props.step === 1) { 135 | sMaxValue.value = curValue.value; 136 | } else { 137 | const stepnum = Math.round((maxLeft.value * percentage / multiple) / props.step); 138 | sMaxValue.value = stepnum * props.step; 139 | if (props.fillValue - sMaxValue.value < props.step) { sMaxValue.value = props.fillValue } 140 | maxLeft.value = sMaxValue.value * multiple / percentage; 141 | } 142 | } 143 | tipShow.value = false; 144 | emit('up', { 145 | ...e, 146 | custom: { 147 | type, 148 | minValue: sMinValue.value, 149 | maxValue: sMaxValue.value 150 | } 151 | }) 152 | } 153 | 154 | 155 | return () => { 156 | const s = {} 157 | return
158 | 159 |
160 |
161 |
162 |
¥{sMinValue.value}
163 |
¥{sMaxValue.value}
164 |
165 |
{ touchstart(e, 'min') }} 168 | onTouchmove={(e: TouchEvent) => { touchmove(e, 'min') }} 169 | onTouchend={(e: TouchEvent) => { touchend(e, 'min') }} 170 | onTouchcancel={(e: TouchEvent) => { touchend(e, 'min') }} 171 | style={{ left: `${minLeft.value}px` }} 172 | >
173 |
{ touchstart(e, 'max') }} 176 | onTouchmove={(e: TouchEvent) => { touchmove(e, 'max') }} 177 | onTouchend={(e: TouchEvent) => { touchend(e, 'max') }} 178 | onTouchcancel={(e: TouchEvent) => { touchend(e, 'max') }} 179 | style={{ left: `${maxLeft.value}px` }} 180 | >
181 |
{curValue.value}
182 |
183 |
184 |
185 |
186 |
187 | } 188 | } 189 | }) 190 | -------------------------------------------------------------------------------- /packages/icons/heart.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from "vue"; 2 | 3 | const heartLimProp = { 4 | color: { Type: String, default: "#000000" }, 5 | defaultColor: { Type: String, default: "#ffffff" }, 6 | } 7 | 8 | export const heart = defineComponent({ 9 | name: 'heart', 10 | setup(props) { 11 | return () => { 12 | return ( 13 | 14 | 15 | 16 | ) 17 | } 18 | } 19 | }) 20 | 21 | 22 | 23 | export const heart10 = defineComponent({ 24 | name: 'heart10', 25 | setup(props) { 26 | return () => { 27 | return ( 28 | 29 | 30 | 31 | 32 | ) 33 | } 34 | } 35 | }) 36 | 37 | export const heart20 = defineComponent({ 38 | name: 'heart20', 39 | setup(props) { 40 | return () => { 41 | return ( 42 | 43 | 44 | 45 | 46 | 47 | 48 | ) 49 | } 50 | } 51 | }) 52 | export const heart30 = defineComponent({ 53 | name: 'heart30', 54 | setup(props) { 55 | return () => { 56 | return ( 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | ) 65 | } 66 | } 67 | }) 68 | export const heart40 = defineComponent({ 69 | name: 'heart40', 70 | setup(props) { 71 | return () => { 72 | return ( 73 | 74 | 75 | 76 | 77 | 78 | 79 | ) 80 | } 81 | } 82 | }) 83 | export const heart50 = defineComponent({ 84 | name: 'heart50', 85 | setup(props) { 86 | return () => { 87 | return ( 88 | 89 | 90 | 91 | 92 | ) 93 | } 94 | } 95 | }) 96 | export const heart60 = defineComponent({ 97 | name: 'heart60', 98 | setup(props) { 99 | return () => { 100 | return ( 101 | 102 | 103 | 104 | 105 | 106 | ) 107 | } 108 | } 109 | }) 110 | export const heart70 = defineComponent({ 111 | name: 'heart70', 112 | setup(props) { 113 | return () => { 114 | return ( 115 | 116 | 117 | 118 | 119 | 120 | ) 121 | } 122 | } 123 | }) 124 | export const heart80 = defineComponent({ 125 | name: 'heart80', 126 | setup(props) { 127 | return () => { 128 | return ( 129 | 130 | 131 | 132 | 133 | 134 | ) 135 | } 136 | } 137 | }) 138 | export const heart90 = defineComponent({ 139 | name: 'heart90', 140 | setup(props) { 141 | return () => { 142 | return ( 143 | 144 | 145 | 146 | 147 | 148 | ) 149 | } 150 | } 151 | }) 152 | export const heartFilled = defineComponent({ 153 | name: 'heart-filled', 154 | setup(props) { 155 | return () => { 156 | return ( 157 | 158 | 159 | 160 | ) 161 | } 162 | } 163 | }) 164 | -------------------------------------------------------------------------------- /packages/icons/star.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from "vue"; 2 | 3 | export const star0 = defineComponent({ 4 | name: 'star0', 5 | setup(props) { 6 | return () => { 7 | return ( 8 | 9 | 10 | 11 | ) 12 | } 13 | } 14 | }) 15 | 16 | 17 | 18 | export const star10 = defineComponent({ 19 | name: 'star10', 20 | setup(props) { 21 | return () => { 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | 29 | ) 30 | } 31 | } 32 | }) 33 | 34 | export const star20 = defineComponent({ 35 | name: 'star20', 36 | setup(props) { 37 | return () => { 38 | return ( 39 | 40 | 41 | 42 | 43 | 44 | 45 | ) 46 | } 47 | } 48 | }) 49 | 50 | export const star30 = defineComponent({ 51 | name: 'star30', 52 | setup(props) { 53 | return () => { 54 | return ( 55 | 56 | 57 | 58 | 59 | 60 | ) 61 | } 62 | } 63 | }) 64 | 65 | export const star40 = defineComponent({ 66 | name: 'star40', 67 | setup(props) { 68 | return () => { 69 | return ( 70 | 71 | 72 | 73 | 74 | 75 | ) 76 | } 77 | } 78 | }) 79 | 80 | 81 | export const star50 = defineComponent({ 82 | name: 'star50', 83 | 84 | setup(props) { 85 | return () => { 86 | return ( 87 | 88 | 89 | 90 | 91 | 92 | ) 93 | } 94 | } 95 | }) 96 | 97 | export const star60 = defineComponent({ 98 | name: 'star60', 99 | setup(props) { 100 | return () => { 101 | return ( 102 | 103 | 104 | 105 | 106 | 107 | ) 108 | } 109 | } 110 | }) 111 | 112 | export const star70 = defineComponent({ 113 | name: 'star70', 114 | setup(props) { 115 | return () => { 116 | return ( 117 | 118 | 119 | 120 | 121 | 122 | ) 123 | } 124 | } 125 | }) 126 | 127 | export const star80 = defineComponent({ 128 | name: 'star80', 129 | setup(props) { 130 | return () => { 131 | return ( 132 | 133 | 134 | 135 | 136 | 137 | ) 138 | } 139 | } 140 | }) 141 | 142 | export const star90 = defineComponent({ 143 | name: 'star90', 144 | setup(props) { 145 | return () => { 146 | return ( 147 | 148 | 149 | 150 | 151 | 152 | 153 | ) 154 | } 155 | } 156 | }) 157 | 158 | export const star100 = defineComponent({ 159 | name: 'star100', 160 | setup(props) { 161 | return () => { 162 | return ( 163 | 164 | 165 | 166 | 167 | ) 168 | } 169 | } 170 | }) 171 | 172 | 173 | 174 | 175 | // const star10 = 176 | 177 | 178 | 179 | // const star20 = 180 | 181 | {/* 182 | */} 183 | 184 | 185 | // const star30 = 186 | // 187 | 188 | 189 | // const star40 = 190 | // 191 | 192 | 193 | // const star50 = 194 | // 195 | 196 | 197 | // const star60 = 198 | // 199 | 200 | 201 | // const star70 = 202 | // 203 | 204 | 205 | // const star80 = 206 | // 207 | 208 | 209 | // const star90 = 210 | // 211 | 212 | 213 | // const star100 = 214 | // 215 | 216 | // 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /packages/rate/star.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from "vue"; 2 | 3 | const RateLimProp = { 4 | color: { Type: String, default: "#000000" }, 5 | defaultColor: { Type: String, default: "#ffffff" }, 6 | } 7 | 8 | export const rate0 = defineComponent({ 9 | name: 'rate0', 10 | props: RateLimProp, setup(props) { 11 | return () => { 12 | return ( 13 | 14 | 15 | 16 | ) 17 | } 18 | } 19 | }) 20 | 21 | 22 | 23 | export const rate10 = defineComponent({ 24 | name: 'rate10', 25 | props: RateLimProp, setup(props) { 26 | return () => { 27 | return ( 28 | 29 | 30 | 31 | 32 | ) 33 | } 34 | } 35 | }) 36 | 37 | export const rate20 = defineComponent({ 38 | name: 'rate20', 39 | props: RateLimProp, setup(props) { 40 | return () => { 41 | return ( 42 | 43 | 44 | 45 | 46 | 47 | 48 | ) 49 | } 50 | } 51 | }) 52 | 53 | export const rate30 = defineComponent({ 54 | name: 'rate30', 55 | props: RateLimProp, setup(props) { 56 | return () => { 57 | return ( 58 | 59 | 60 | 61 | 62 | 63 | 64 | ) 65 | } 66 | } 67 | }) 68 | 69 | export const rate40 = defineComponent({ 70 | name: 'rate40', 71 | props: RateLimProp, setup(props) { 72 | return () => { 73 | return ( 74 | 75 | 76 | 77 | 78 | 79 | 80 | ) 81 | } 82 | } 83 | }) 84 | 85 | 86 | export const rate50 = defineComponent({ 87 | name: 'rate50', 88 | props: RateLimProp, 89 | setup(props) { 90 | return () => { 91 | return ( 92 | 93 | 94 | 95 | 96 | 97 | 98 | ) 99 | } 100 | } 101 | }) 102 | 103 | export const rate60 = defineComponent({ 104 | name: 'rate60', 105 | props: RateLimProp, setup(props) { 106 | return () => { 107 | return ( 108 | 109 | 110 | 111 | 112 | 113 | 114 | ) 115 | } 116 | } 117 | }) 118 | 119 | export const rate70 = defineComponent({ 120 | name: 'rate70', 121 | props: RateLimProp, setup(props) { 122 | return () => { 123 | return ( 124 | 125 | 126 | 127 | 128 | 129 | 130 | ) 131 | } 132 | } 133 | }) 134 | 135 | export const rate80 = defineComponent({ 136 | name: 'rate80', 137 | props: RateLimProp, setup(props) { 138 | return () => { 139 | return ( 140 | 141 | 142 | 143 | 144 | 145 | 146 | ) 147 | } 148 | } 149 | }) 150 | 151 | export const rate90 = defineComponent({ 152 | name: 'rate90', 153 | props: RateLimProp, setup(props) { 154 | return () => { 155 | return ( 156 | 157 | 158 | 159 | 160 | 161 | 162 | ) 163 | } 164 | } 165 | }) 166 | 167 | export const rate100 = defineComponent({ 168 | name: 'rate100', 169 | props: RateLimProp, setup(props) { 170 | return () => { 171 | return ( 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | ) 180 | } 181 | } 182 | }) 183 | 184 | 185 | 186 | // const star10 = 187 | 188 | 189 | 190 | // const star20 = 191 | 192 | // 193 | // 194 | 195 | 196 | // const star30 = 197 | // 198 | 199 | 200 | // const star40 = 201 | // 202 | 203 | 204 | // const star50 = 205 | // 206 | 207 | 208 | // const star60 = 209 | // 210 | 211 | 212 | // const star70 = 213 | // 214 | 215 | 216 | // const star80 = 217 | // 218 | 219 | 220 | // const star90 = 221 | // 222 | 223 | 224 | // const star100 = 225 | // 226 | 227 | // 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | // const star5 = 249 | 250 | // const star10 = 251 | 252 | // const star15 = 253 | 254 | // const star20 = 255 | 256 | // const star25 = 257 | // 258 | // 259 | 260 | 261 | // const star30 = 262 | {/* 263 | */} 264 | 265 | 266 | // const star35 = 267 | // 268 | 269 | 270 | // const star40 = 271 | // 272 | 273 | // const star45 = 274 | // 275 | 276 | 277 | // const star50 = 278 | // 279 | 280 | 281 | // const star55 = 282 | // 283 | 284 | 285 | // const star60 = 286 | // 287 | 288 | // const star65 = 289 | // 290 | 291 | // const star70 = 292 | // 293 | 294 | 295 | // const star75 = 296 | // 297 | 298 | // const star80 = 299 | // 300 | 301 | 302 | // const star85 = 303 | // 304 | 305 | // const star90 = 306 | // 307 | 308 | // const star90 = 309 | // 310 | 311 | // const star100 = 312 | // 313 | --------------------------------------------------------------------------------