feat: 写组件
1. 全局config 2. logo组件 3. 实时预览 4. scss复用 未来 1. 继续开发组件
This commit is contained in:
parent
db1c5c4522
commit
9170d7efd6
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
12
index.html
Normal file
12
index.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Ni UI Preview</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/preview/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
12
indexg.html
Normal file
12
indexg.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Ni UI Preview: Global</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/preview/globalMain.ts"></script>
|
||||
</body>
|
||||
</html>
|
3099
package-lock.json
generated
3099
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@ -2,7 +2,12 @@
|
||||
"name": "ni",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"keywords": ["Vue3", "Vue UI", "Ni UI", "Ni"],
|
||||
"keywords": [
|
||||
"Vue3",
|
||||
"Vue UI",
|
||||
"Ni UI",
|
||||
"Ni"
|
||||
],
|
||||
"license": "MIT",
|
||||
"homepage": "http://uair.cc",
|
||||
"author": "星撰玉衡",
|
||||
@ -15,30 +20,30 @@
|
||||
"main": "./dist/ni.umd.js",
|
||||
"module": "./dist/ni.es.js",
|
||||
"types": "./types/index.d.ts",
|
||||
|
||||
"sideEffects": [
|
||||
"**/*.css",
|
||||
"**/*.scss"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev:g": "vite --open indexg.html",
|
||||
"build": "vite build --config vite.config.ts && tsc -p tsconfig.build.json",
|
||||
"docs:dev": "vitepress dev docs",
|
||||
"docs:build": "vitepress build docs",
|
||||
"docs:preview": "vitepress preview docs"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.3.4"
|
||||
"vue": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.5.9",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vue/tsconfig": "^0.4.0",
|
||||
"sass": "^1.89.0",
|
||||
"typescript": "~5.0.4",
|
||||
"vite": "^4.4.9",
|
||||
"vue-tsc": "^1.8.8",
|
||||
"sass": "^1.66.1",
|
||||
"vitepress": "^1.0.0-rc.44",
|
||||
"@types/node": "^20.5.9"
|
||||
"vue-tsc": "^1.8.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0"
|
||||
|
12
preview/App.vue
Normal file
12
preview/App.vue
Normal file
@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import {NiButton, NiLogo} from 'ni'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NiButton>按钮</NiButton>
|
||||
<NiLogo type="primary" size="large" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
16
preview/globalApp.vue
Normal file
16
preview/globalApp.vue
Normal file
@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { useNiConfig, setNiConfig } from 'ni'
|
||||
setNiConfig({
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NiConfigProvider>
|
||||
<NiButton>按钮</NiButton>
|
||||
<NiLogo type="primary" size="large" />
|
||||
</NiConfigProvider>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
7
preview/globalMain.ts
Normal file
7
preview/globalMain.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { createApp } from 'vue'
|
||||
import ni from 'ni'
|
||||
import App from './globalApp.vue'
|
||||
ni.setNiConfig({
|
||||
logoName: ['星', '撰']
|
||||
})
|
||||
createApp(App).use(ni).mount('#app')
|
3
preview/main.ts
Normal file
3
preview/main.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
createApp(App).mount('#app')
|
23
src/components/config/ConfigProvider.vue
Normal file
23
src/components/config/ConfigProvider.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<slot></slot>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { provide, watch } from 'vue'
|
||||
import type { NiConfig } from './types'
|
||||
import defaultConfig from './defaultConfig'
|
||||
|
||||
const props = defineProps<{
|
||||
config?: Partial<NiConfig>
|
||||
}>()
|
||||
|
||||
defaultConfig
|
||||
|
||||
// 监听配置变化
|
||||
watch(() => props.config, (newConfig) => {
|
||||
Object.assign(defaultConfig, newConfig)
|
||||
})
|
||||
|
||||
// 提供配置
|
||||
provide('ni-config', defaultConfig)
|
||||
</script>
|
17
src/components/config/defaultConfig.ts
Normal file
17
src/components/config/defaultConfig.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { reactive } from "vue"
|
||||
import { NiConfig } from "./types"
|
||||
|
||||
const defaultConfig = reactive<NiConfig>({
|
||||
logoName: ['星', '撰'],
|
||||
size: 'default',
|
||||
zIndex: 2000,
|
||||
namespace: 'ni',
|
||||
})
|
||||
|
||||
export default defaultConfig
|
||||
|
||||
function setNiConfig(config: Partial<NiConfig>) {
|
||||
Object.assign(defaultConfig, config)
|
||||
}
|
||||
|
||||
export { setNiConfig }
|
5
src/components/config/index.ts
Normal file
5
src/components/config/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import NiConfigProvider from './ConfigProvider.vue'
|
||||
export * from './types'
|
||||
export default NiConfigProvider
|
||||
export { useNiConfig } from './useConfig'
|
||||
export { setNiConfig } from './defaultConfig'
|
10
src/components/config/types.ts
Normal file
10
src/components/config/types.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export interface NiConfig {
|
||||
logoName?: [string, string]
|
||||
name?: string
|
||||
size?: 'small' | 'default' | 'large'
|
||||
zIndex?: number
|
||||
namespace?: string
|
||||
// 可以添加更多全局配置项
|
||||
}
|
||||
|
||||
export type NiConfigKey = keyof NiConfig
|
18
src/components/config/useConfig.ts
Normal file
18
src/components/config/useConfig.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { inject, computed, type Ref, Reactive } from 'vue'
|
||||
import type { NiConfig } from './types'
|
||||
|
||||
export function useNiConfig() {
|
||||
const config = inject<Reactive<NiConfig>>('ni-config')
|
||||
|
||||
if (!config) {
|
||||
throw new Error('useConfig must be used within NiConfigProvider')
|
||||
}
|
||||
|
||||
return {
|
||||
config: computed(() => config),
|
||||
get: (key: keyof NiConfig) => config[key],
|
||||
set: (key: keyof NiConfig, value: any) => {
|
||||
config[key] = value
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,19 @@
|
||||
import NiButton from './button'
|
||||
|
||||
import NiLogo from './logo'
|
||||
import NiConfigProvider, { useNiConfig } from './config'
|
||||
import { setNiConfig } from './config/defaultConfig'
|
||||
export {
|
||||
NiButton,
|
||||
NiLogo,
|
||||
NiConfigProvider,
|
||||
useNiConfig,
|
||||
setNiConfig,
|
||||
}
|
||||
|
||||
export default {
|
||||
NiButton,
|
||||
NiLogo,
|
||||
NiConfigProvider,
|
||||
useNiConfig,
|
||||
setNiConfig,
|
||||
}
|
91
src/components/logo/Logo.vue
Normal file
91
src/components/logo/Logo.vue
Normal file
@ -0,0 +1,91 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { logoProps } from './types';
|
||||
import { type NiConfig, useNiConfig } from '~/config';
|
||||
const niConfig = useNiConfig()
|
||||
const props = defineProps(logoProps);
|
||||
|
||||
// * logo内容
|
||||
const logoContent = computed<string[]>(() => {
|
||||
const name = props.logoName || niConfig.get('logoName') || ['N', 'i']
|
||||
return Array.isArray(name) ? name : [String(name)[0], String(name)[1]]
|
||||
})
|
||||
console.log(props, niConfig, logoContent);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ni-logo">
|
||||
<div class="content">
|
||||
<div class="first">{{ logoContent[0] }}</div>
|
||||
<div class="second">{{ logoContent[1] }}</div>
|
||||
</div>
|
||||
<div class="mask">
|
||||
<div class="first"><div class="maskContainer"><div class="maskContent">{{ logoContent[0] }}</div></div></div>
|
||||
<div class="second"><div class="maskContainer"><div class="maskContent">{{ logoContent[1] }}</div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use '@/styles/style.scss' as niStyle;
|
||||
.ni-logo{
|
||||
// logo样式的一些变量
|
||||
// * logo宽度
|
||||
--ni-logo-width: 6em;
|
||||
// * logo高度
|
||||
--ni-logo-height: 3em;
|
||||
// * logo遮罩层背景颜色
|
||||
--ni-logo-mask-background-color: #111;
|
||||
// * logo字体颜色
|
||||
--ni-logo-mask-font-color: #fff;
|
||||
|
||||
// = logo容器
|
||||
position: relative;
|
||||
width: var(--ni-logo-width);
|
||||
height: var(--ni-logo-height);
|
||||
user-select: none;
|
||||
.content,.mask{
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
&.content > div{
|
||||
flex: 1;
|
||||
@extend .AllCenter;
|
||||
}
|
||||
}
|
||||
.content{
|
||||
position: relative;
|
||||
}
|
||||
.mask{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
& > div.first, & > div.second{
|
||||
position: relative;
|
||||
width: 50%;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
&.first{
|
||||
}
|
||||
&.second{
|
||||
& > div.maskContainer{
|
||||
background-color: var(--ni-logo-mask-background-color);
|
||||
color: var(--ni-logo-mask-font-color);
|
||||
@extend .LogoAnimation;
|
||||
}
|
||||
}
|
||||
& > div.maskContainer{
|
||||
width: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
& > div.maskContent{
|
||||
width: calc(var(--ni-logo-width) / 2);
|
||||
height: 100%;
|
||||
@extend .AllCenter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
3
src/components/logo/index.ts
Normal file
3
src/components/logo/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import NiLogo from './Logo.vue'
|
||||
export { NiLogo }
|
||||
export default NiLogo
|
27
src/components/logo/types.ts
Normal file
27
src/components/logo/types.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { ExtractPropTypes, PropType } from 'vue'
|
||||
|
||||
export const logoProps = {
|
||||
logoName: {
|
||||
type: Array as PropType<string[]>,
|
||||
},
|
||||
type: {
|
||||
type: String as PropType<'default' | 'primary'>,
|
||||
default: 'default',
|
||||
validator: (value: string) => {
|
||||
return ['default', 'primary'].includes(value)
|
||||
}
|
||||
},
|
||||
size: {
|
||||
type: String as PropType<'small' | 'medium' | 'large'>,
|
||||
default: 'medium',
|
||||
validator: (value: string) => {
|
||||
return ['small', 'medium', 'large'].includes(value)
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
} as const
|
||||
|
||||
export type LogoProps = ExtractPropTypes<typeof logoProps>
|
11
src/index.ts
11
src/index.ts
@ -9,13 +9,18 @@ export const version = '0.0.1'
|
||||
|
||||
// 导出安装方法
|
||||
export const install = (app: App) => {
|
||||
Object.entries(components).forEach(([path, module]: [string, any]) => {
|
||||
app.component(path, module)
|
||||
Object.entries(components).forEach(([name, module]: [string, any]) => {
|
||||
if(name === 'default' || name === 'useConfig') return
|
||||
// 检查是否为Vue组件(具有render函数或template属性)
|
||||
if (module && (typeof module.render === 'function' || module.template)) {
|
||||
app.component(name, module)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 默认导出
|
||||
export default {
|
||||
version,
|
||||
install
|
||||
install,
|
||||
setNiConfig: components.setNiConfig
|
||||
}
|
185
src/styles/style.scss
Normal file
185
src/styles/style.scss
Normal file
@ -0,0 +1,185 @@
|
||||
@forward 'variables';
|
||||
|
||||
// MIXINS
|
||||
|
||||
// 弹性布局
|
||||
@mixin flex($direction: row, $justify: center, $align: center) {
|
||||
display: flex;
|
||||
flex-direction: $direction;
|
||||
justify-content: $justify;
|
||||
align-items: $align;
|
||||
}
|
||||
|
||||
// 绝对定位
|
||||
@mixin absolute($top: 0, $right: 0, $bottom: 0, $left: 0) {
|
||||
position: absolute;
|
||||
top: $top;
|
||||
right: $right;
|
||||
bottom: $bottom;
|
||||
left: $left;
|
||||
}
|
||||
|
||||
// 文本溢出省略
|
||||
@mixin text-ellipsis($lines: 1) {
|
||||
@if $lines == 1 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
} @else {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: $lines;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
// 清除浮动
|
||||
@mixin clearfix {
|
||||
&::after {
|
||||
content: '';
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式断点
|
||||
@mixin respond-to($breakpoint) {
|
||||
@if $breakpoint == 'sm' {
|
||||
@media (min-width: 576px) { @content; }
|
||||
} @else if $breakpoint == 'md' {
|
||||
@media (min-width: 768px) { @content; }
|
||||
} @else if $breakpoint == 'lg' {
|
||||
@media (min-width: 992px) { @content; }
|
||||
} @else if $breakpoint == 'xl' {
|
||||
@media (min-width: 1200px) { @content; }
|
||||
}
|
||||
}
|
||||
|
||||
// 动画相关
|
||||
@mixin animation($name, $duration: 1s, $timing: ease, $delay: 0s, $iteration: 1, $direction: normal, $fill: forwards) {
|
||||
animation: $name $duration $timing $delay $iteration $direction $fill;
|
||||
/*
|
||||
* 1. $name(必需参数)
|
||||
* 动画名称
|
||||
* 必须与 @keyframes 定义的名称匹配
|
||||
* 例如:fade-in, slide-in-right 等
|
||||
* 2. $duration(可选参数,默认值:1s)
|
||||
* 动画持续时间
|
||||
* 可选值:
|
||||
* 时间值:0.5s, 1s, 2s 等
|
||||
* 毫秒值:500ms, 1000ms 等
|
||||
* 3. $timing(可选参数,默认值:ease)
|
||||
* 动画的时间函数
|
||||
* 可选值:
|
||||
* linear:匀速
|
||||
* ease:缓入缓出(默认)
|
||||
* ease-in:缓入
|
||||
* ease-out:缓出
|
||||
* ease-in-out:缓入缓出
|
||||
* cubic-bezier(n,n,n,n):自定义贝塞尔曲线
|
||||
* steps(n):分步动画
|
||||
* 4. $delay(可选参数,默认值:0s)
|
||||
* 动画开始前的延迟时间
|
||||
* 可选值:
|
||||
* 时间值:0.5s, 1s 等
|
||||
* 毫秒值:500ms, 1000ms 等
|
||||
* 负值:提前开始动画
|
||||
* 5. $iteration(可选参数,默认值:1)
|
||||
* 动画重复次数
|
||||
* 可选值:
|
||||
* 数字:1, 2, 3 等
|
||||
* infinite:无限循环
|
||||
* 6. $direction(可选参数,默认值:normal)
|
||||
* 动画方向
|
||||
* 可选值:
|
||||
* normal:正常播放
|
||||
* reverse:反向播放
|
||||
* alternate:交替播放(正向->反向)
|
||||
* alternate-reverse:交替反向播放(反向->正向)
|
||||
* 7. $fill(可选参数,默认值:forwards)
|
||||
* 动画填充模式
|
||||
* 可选值:
|
||||
* forwards:保持最后一帧
|
||||
* backwards:应用第一帧
|
||||
* both:同时应用 forwards 和 backwards
|
||||
* none:不应用任何填充
|
||||
*/
|
||||
}
|
||||
|
||||
@mixin keyframes($name) {
|
||||
@keyframes #{$name} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// 常用动画关键帧
|
||||
@include keyframes(fade-in) {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@include keyframes(fade-out) {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@include keyframes(slide-in-right) {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@include keyframes(slide-out-right) {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
}
|
||||
to {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
// 动画类
|
||||
.fade-in {
|
||||
@include animation(fade-in);
|
||||
}
|
||||
|
||||
.fade-out {
|
||||
@include animation(fade-out);
|
||||
}
|
||||
|
||||
.slide-in-right {
|
||||
@include animation(slide-in-right);
|
||||
}
|
||||
|
||||
.slide-out-right {
|
||||
@include animation(slide-out-right);
|
||||
}
|
||||
|
||||
// PUBLIC
|
||||
// * 全屏居中
|
||||
.AllCenter{
|
||||
@include flex(row, center, center);
|
||||
}
|
||||
// * Logo动画
|
||||
@keyframes stepLogo {
|
||||
0%{
|
||||
width: 0;
|
||||
}
|
||||
100%{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.LogoAnimation{
|
||||
animation: stepLogo .5s linear forwards;
|
||||
}
|
43
src/styles/variables.scss
Normal file
43
src/styles/variables.scss
Normal file
@ -0,0 +1,43 @@
|
||||
// 颜色变量
|
||||
$primary-color: #409eff;
|
||||
$success-color: #67c23a;
|
||||
$warning-color: #e6a23c;
|
||||
$danger-color: #f56c6c;
|
||||
$info-color: #909399;
|
||||
|
||||
// 文字颜色
|
||||
$text-primary: #303133;
|
||||
$text-regular: #606266;
|
||||
$text-secondary: #909399;
|
||||
$text-placeholder: #c0c4cc;
|
||||
|
||||
// 边框颜色
|
||||
$border-color-base: #dcdfe6;
|
||||
$border-color-light: #e4e7ed;
|
||||
$border-color-lighter: #ebeef5;
|
||||
$border-color-extra-light: #f2f6fc;
|
||||
|
||||
// 背景颜色
|
||||
$background-color-base: #f5f7fa;
|
||||
|
||||
// 断点
|
||||
$breakpoints: (
|
||||
'sm': 576px,
|
||||
'md': 768px,
|
||||
'lg': 992px,
|
||||
'xl': 1200px
|
||||
);
|
||||
|
||||
// 字体大小
|
||||
$font-size-extra-large: 20px;
|
||||
$font-size-large: 18px;
|
||||
$font-size-medium: 16px;
|
||||
$font-size-base: 14px;
|
||||
$font-size-small: 13px;
|
||||
$font-size-extra-small: 12px;
|
||||
|
||||
// 圆角
|
||||
$border-radius-base: 4px;
|
||||
$border-radius-small: 2px;
|
||||
$border-radius-large: 8px;
|
||||
$border-radius-circle: 50%;
|
@ -18,9 +18,11 @@
|
||||
"types": ["node", "vite/client", "vue"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
"@/*": ["src/*"],
|
||||
"ni": ["src/index.ts"],
|
||||
"~/*": ["src/components/*"],
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "preview/**/*.ts", "preview/**/*.vue"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
@ -21,5 +21,20 @@ export default defineConfig({
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
// api: "modern-compiler", // Element Plus 中的解决办法
|
||||
silenceDeprecations: ['legacy-js-api']
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'ni': resolve(__dirname, 'src/index.ts'),
|
||||
'~': resolve(__dirname, 'src/components'),
|
||||
'@': resolve(__dirname, 'src')
|
||||
}
|
||||
}
|
||||
})
|
Loading…
Reference in New Issue
Block a user