feat: 增加openinula模板转换脚本 (#1567)
This commit is contained in:
parent
f834ca7e05
commit
61aa2cd82c
|
@ -51,7 +51,11 @@
|
|||
"build:entry-react": "esno src/index.ts build:entry-react",
|
||||
"create:mapping-react": "esno src/commands/create/create-mapping-react.ts",
|
||||
"build:react": "esno src/index.ts build:react",
|
||||
"build:chartTheme": "esno src/index.ts build:chartTheme"
|
||||
"// ----------------------OpenInula脚本---------------------- ": "",
|
||||
"build:entry-openinula": "esno src/index.ts build:entry-openinula",
|
||||
"create:mapping-openinula": "esno src/commands/create/create-mapping-openinula.ts",
|
||||
"build:openinula": "esno src/index.ts build:openinula",
|
||||
"create:ui-openinula": "esno src/index.ts create:ui-openinula"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/babel-helper-vue-jsx-merge-props": "^1.4.0",
|
||||
|
@ -62,4 +66,4 @@
|
|||
"vite-plugin-svgr": "^3.2.0",
|
||||
"vite-svg-loader": "^3.6.0"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import [[UNAME]] from './src/index[[SUFFIX]]'
|
||||
import '@opentiny/vue-theme/[[NAME]]/index.less'
|
||||
import { version } from './package.json'
|
||||
|
||||
|
||||
[[UNAME]].version = version
|
||||
|
||||
|
||||
|
||||
export default [[UNAME]]
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "@opentiny/openinula-[[NAME]]",
|
||||
"version": "3.[[MINOR]].0",
|
||||
"description": "",
|
||||
"main": "lib/index.js",
|
||||
"module": "index.ts",
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm -w build:ui-openinula $npm_package_name",
|
||||
"//postversion": "pnpm build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@opentiny-internal/vue-test-utils": "workspace:*",
|
||||
"vitest": "^0.31.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opentiny/vue-renderless": "workspace:~",
|
||||
"@opentiny/openinula-common": "workspace:~",
|
||||
"@opentiny/openinula-icon": "workspace:~",
|
||||
"@opentiny/vue-theme": "workspace:~",
|
||||
"@opentiny/vue-theme-mobile": "workspace:~"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import pc from './pc'
|
||||
import mobile from './mobile'
|
||||
import mobileFirst from './mobile-first'
|
||||
|
||||
export default function (props) {
|
||||
const { tiny_mode = 'pc' } = props
|
||||
|
||||
const S = {
|
||||
pc,
|
||||
mobile,
|
||||
'mobile-first': mobileFirst
|
||||
}[tiny_mode]
|
||||
|
||||
return S(props)
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import { renderless, api } from '@opentiny/vue-renderless/[[NAME]]/vue'
|
||||
import { vc, If, Component, Slot, Transition, useSetup, useVm } from '@opentiny/openinula-common'
|
||||
import { IconClose, IconSuccess, IconError, IconHelp, IconWarning } from '@opentiny/openinula-icon'
|
||||
|
||||
const $constants = {
|
||||
|
||||
}
|
||||
|
||||
export default function [[UNAME]](props) {
|
||||
|
||||
|
||||
const {
|
||||
_constants,
|
||||
|
||||
} = props
|
||||
|
||||
const defaultProps = Object.assign(
|
||||
{
|
||||
|
||||
},
|
||||
props
|
||||
)
|
||||
|
||||
const { ref, current: vm, parent } = useVm()
|
||||
|
||||
const { [[API]] } = useSetup({
|
||||
props: defaultProps,
|
||||
renderless,
|
||||
api,
|
||||
constants: _constants,
|
||||
vm,
|
||||
parent
|
||||
})
|
||||
|
||||
return (
|
||||
[[TEMPLATE]]
|
||||
)
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import { renderless, api } from '@opentiny/vue-renderless/[[NAME]]/vue'
|
||||
import { vc, If, Component, Slot, Transition, useSetup, useVm } from '@opentiny/openinula-common'
|
||||
import { IconClose, IconSuccess, IconError, IconHelp, IconWarning } from '@opentiny/openinula-icon'
|
||||
import '@opentiny/vue-theme-mobile/[[NAME]]/index.less'
|
||||
|
||||
const $constants = {
|
||||
|
||||
}
|
||||
|
||||
export default function [[UNAME]](props) {
|
||||
|
||||
const {
|
||||
_constants,
|
||||
|
||||
} = props
|
||||
|
||||
const defaultProps = Object.assign(
|
||||
{
|
||||
|
||||
},
|
||||
props
|
||||
)
|
||||
|
||||
|
||||
const { ref, current: vm, parent } = useVm()
|
||||
|
||||
const { [[API]] } = useSetup({
|
||||
props: defaultProps,
|
||||
renderless,
|
||||
api,
|
||||
constants: _constants,
|
||||
vm,
|
||||
parent
|
||||
})
|
||||
|
||||
return (
|
||||
[[TEMPLATE]]
|
||||
)
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import { renderless, api } from '@opentiny/vue-renderless/[[NAME]]/vue'
|
||||
import { vc, If, Component, Slot, Transition, useSetup, useVm } from '@opentiny/openinula-common'
|
||||
import { IconClose, IconSuccess, IconError, IconHelp, IconWarning } from '@opentiny/openinula-icon'
|
||||
import '@opentiny/vue-theme/[[NAME]]/index.less'
|
||||
|
||||
const $constants = {
|
||||
|
||||
}
|
||||
|
||||
export default function [[UNAME]](props) {
|
||||
|
||||
const {
|
||||
_constants,
|
||||
|
||||
} = props
|
||||
|
||||
const defaultProps = Object.assign(
|
||||
{
|
||||
|
||||
},
|
||||
props
|
||||
)
|
||||
|
||||
|
||||
const { ref, current: vm, parent } = useVm()
|
||||
|
||||
const { [[API]] } = useSetup({
|
||||
props: defaultProps,
|
||||
renderless,
|
||||
api,
|
||||
constants: _constants,
|
||||
vm,
|
||||
parent
|
||||
})
|
||||
|
||||
return (
|
||||
[[TEMPLATE]]
|
||||
)
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* 生成入口文件,包括 pc.js / mobile.js / mobile-first.js / index.js
|
||||
*/
|
||||
import fs from 'fs-extra'
|
||||
import { EOL as endOfLine } from 'node:os'
|
||||
import { pathFromWorkspaceRoot, capitalizeKebabCase, prettierFormat, logGreen } from '../../shared/utils'
|
||||
import { getAllModules2 } from './build-ui-openinula'
|
||||
import handlebarsRender from './handlebars.render'
|
||||
|
||||
const version = (({ key }) => {
|
||||
const packageJSON = fs.readJSONSync(pathFromWorkspaceRoot('packages/openinula/package.json'))
|
||||
const packageJsonOption = packageJSON[key] || packageJSON
|
||||
|
||||
return packageJsonOption
|
||||
})({ key: 'version' })
|
||||
|
||||
const outputDir = 'packages/openinula'
|
||||
|
||||
const fileNames = {
|
||||
all: 'index.ts',
|
||||
pc: 'pc.ts',
|
||||
mobile: 'mobile.ts',
|
||||
'mobile-first': 'mobile-first.ts'
|
||||
}
|
||||
|
||||
function getMainTemplate() {
|
||||
return `{{{include}}}
|
||||
import { $prefix } from '@opentiny/openinula-common'
|
||||
|
||||
const components = [{{{components}}}]
|
||||
|
||||
export const version = '${version}'
|
||||
|
||||
export {
|
||||
{{{components}}}
|
||||
}
|
||||
|
||||
export default {
|
||||
{{{components}}}
|
||||
} as any
|
||||
`
|
||||
}
|
||||
|
||||
function getComponents(mode) {
|
||||
const modules = getAllModules2()
|
||||
|
||||
const components = modules
|
||||
.filter((item) => item.type === 'component')
|
||||
.filter((item) => mode === 'all' || !item.mode || item.mode.includes(mode))
|
||||
|
||||
return components
|
||||
}
|
||||
|
||||
function createEntry(mode) {
|
||||
const OUTPUT_PATH = pathFromWorkspaceRoot(outputDir, fileNames[mode])
|
||||
const MAIN_TEMPLATE = getMainTemplate({ mode })
|
||||
const includeTemplate: string[] = []
|
||||
const componentsTemplate: string[] = []
|
||||
const components = getComponents(mode)
|
||||
const PKG_PATH = pathFromWorkspaceRoot(outputDir, 'package.json')
|
||||
const PKGContent = fs.readJSONSync(PKG_PATH)
|
||||
const PKGDeps = {
|
||||
'@opentiny/openinula-common': 'workspace:~'
|
||||
}
|
||||
|
||||
components.forEach((item) => {
|
||||
const component = capitalizeKebabCase(item.name)
|
||||
PKGDeps[item.importName] = 'workspace:~'
|
||||
componentsTemplate.push(` ${component}`)
|
||||
const importName = mode === 'all' ? item.importName : `${item.importName}/src/${mode}`
|
||||
includeTemplate.push(`import ${item.name} from '${importName}'`)
|
||||
})
|
||||
|
||||
if (mode === 'all') {
|
||||
PKGContent.dependencies = PKGDeps
|
||||
fs.writeFileSync(PKG_PATH, JSON.stringify(PKGContent, null, 2))
|
||||
}
|
||||
|
||||
const template = handlebarsRender({
|
||||
template: MAIN_TEMPLATE,
|
||||
data: {
|
||||
include: includeTemplate.join(endOfLine),
|
||||
components: componentsTemplate.join(',' + endOfLine)
|
||||
}
|
||||
})
|
||||
|
||||
const output = prettierFormat({ str: template })
|
||||
|
||||
fs.writeFileSync(OUTPUT_PATH, output)
|
||||
}
|
||||
|
||||
export function buildEntryOpenInula() {
|
||||
;['all', 'pc', 'mobile', 'mobile-first'].forEach(createEntry)
|
||||
|
||||
logGreen(
|
||||
`npm run build:entry done. [${outputDir}/index.ts,${outputDir}/pc.ts,${outputDir}/mobile.ts,${outputDir}/mobile-first.ts]`
|
||||
)
|
||||
}
|
|
@ -0,0 +1,479 @@
|
|||
import { logGreen, kebabCase, capitalizeKebabCase } from '../../shared/utils'
|
||||
import { pathFromWorkspaceRoot } from '../../shared/utils'
|
||||
import fg from 'fast-glob'
|
||||
import path from 'node:path'
|
||||
import { build, defineConfig } from 'vite'
|
||||
import { getBabelOutputPlugin } from '@rollup/plugin-babel'
|
||||
import { external } from '../../shared/config'
|
||||
import type { Plugin, NormalizedOutputOptions, OutputBundle } from 'rollup'
|
||||
import fs from 'fs-extra'
|
||||
import { sync as findUpSync } from 'find-up'
|
||||
import replace from 'rollup-plugin-replace'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
||||
const moduleMap = require(pathFromWorkspaceRoot('packages/openinula/modules.json'))
|
||||
type mode = 'pc' | 'mobile' | 'mobile-first'
|
||||
|
||||
const pathFromPackages = (...args) => pathFromWorkspaceRoot('packages', ...args)
|
||||
|
||||
let scopeName = '@opentiny'
|
||||
let buildVersion = '1.0.0'
|
||||
|
||||
export interface Module2 {
|
||||
/** 源码路径,如 vue/src/button/index.ts */
|
||||
path: string
|
||||
/** 模块类型,可选 component, template, module */
|
||||
type: 'component' | 'template' | 'module'
|
||||
/** 是否排除构建,例如组件尚未开发完,设置 true */
|
||||
exclude?: boolean
|
||||
/** 组件类型支持的模式 */
|
||||
mode?: mode[]
|
||||
/** 模块名称,如 Button */
|
||||
name: string
|
||||
/** 模块构建物路径,如 vue/button/lib/index */
|
||||
libPath: string
|
||||
/** 模块名,如 @opentiny/vue/vue/lib/button/src,@deprecated */
|
||||
libName?: string
|
||||
/** 模块的npm包名,如 @opentiny/vue-button */
|
||||
importName: string
|
||||
/** 构建物文件名,@deprecated */
|
||||
tmpName?: string
|
||||
/** 全局变量名,如 TinyButton */
|
||||
global?: string
|
||||
/** 组件名的大写形态,如 Button */
|
||||
UpperName?: string
|
||||
/** 组件名的小写形态,如 button */
|
||||
LowerName?: string
|
||||
/** 模块的路径 */
|
||||
parentDir?: string[]
|
||||
}
|
||||
|
||||
function getEntryTasks(): Module2[] {
|
||||
// 读取TinyVue组件库入口文件
|
||||
return ['index', 'pc', 'mobile'].map((mode) => ({
|
||||
path: `openinula/${mode}.ts`,
|
||||
dtsRoot: true,
|
||||
libPath: `openinula/${mode}`,
|
||||
type: 'module',
|
||||
name: kebabCase({ str: `${scopeName}/openinula` }),
|
||||
global: capitalizeKebabCase('opentinyOpenInula'),
|
||||
importName: `${scopeName}/openinula`
|
||||
}))
|
||||
}
|
||||
|
||||
export function getAllModules2(): Module2[] {
|
||||
return getSortModules({ filterIntercept: () => true })
|
||||
}
|
||||
|
||||
const getSortModules = ({ filterIntercept }: { filterIntercept: Function }) => {
|
||||
let modules: Module2[] = []
|
||||
let componentCount = 0
|
||||
const importName = `${scopeName}/openinula`
|
||||
Object.entries(moduleMap).forEach(([key, module]) => {
|
||||
let component = module as Module2
|
||||
|
||||
component.name = key
|
||||
// filterIntercept过滤筛选命令行传过来的组件名称,只输出命令行传递过来的组件
|
||||
if (filterIntercept(component) === true && component.exclude !== true) {
|
||||
const dirs = component.path.split('/')
|
||||
|
||||
// 这段逻辑暂时没有用到
|
||||
const componentName = dirs.slice(1, dirs.indexOf('src'))
|
||||
// UpperName: Todo
|
||||
component.UpperName = capitalizeKebabCase(componentName.pop() ?? '')
|
||||
|
||||
// LowerName: todo
|
||||
component.LowerName = kebabCase({ str: component.UpperName })
|
||||
|
||||
// 工程的父文件夹
|
||||
component.parentDir = componentName
|
||||
|
||||
// libPath: 'packages/todo/dist/pc.ts' 组件输出路径
|
||||
component.libPath = component.path
|
||||
.replace('openinula/src/', 'packages/')
|
||||
.replace('openinula-common/src/', 'packages/common/')
|
||||
.replace('openinula-locale/src/', 'packages/locale/')
|
||||
.replace('openinula-icon/src/', 'packages/icon/')
|
||||
.replace('/index.ts', '/src/index.js')
|
||||
.replace('/src/', '/dist/lib/')
|
||||
.replace('.jsx', '.js')
|
||||
.replace('.tsx', '.js')
|
||||
|
||||
// libName: '@opentiny/vue/todo/pc'
|
||||
component.libName = component.libPath
|
||||
.replace('packages/', '')
|
||||
.replace('/index', '')
|
||||
.replace('.js', '')
|
||||
.replace('/dist/', '/')
|
||||
.replace(/\/lib$/, '')
|
||||
|
||||
// 处理子目录
|
||||
if (componentName.length) {
|
||||
component.libName = component.libName.replace(componentName.join('/'), '').replace(/^\//, '')
|
||||
}
|
||||
|
||||
// importName: '@opentiny/vue-tag/pc'
|
||||
component.importName = `${scopeName}/openinula` + '-' + component.libName
|
||||
|
||||
// libName: '@opentiny/vue/todo/pc'
|
||||
component.libName = importName + '/' + component.libName
|
||||
|
||||
// tmpName: 'pc'
|
||||
component.tmpName = component.libPath.replace('.ts', '').split('/').slice(-1)[0]
|
||||
|
||||
// global: 'TinyTodoPc'
|
||||
component.global = 'Tiny' + key
|
||||
|
||||
component.importName = `${scopeName}/openinula-${kebabCase({ str: key })}`
|
||||
|
||||
// "vue-common/src/index.ts" ==> "vue-common/lib/index"
|
||||
if (component.type === 'module') {
|
||||
component.libPath = component.path.replace('/src/', '/lib/').replace('index.ts', 'index')
|
||||
}
|
||||
|
||||
// "vue/src/button/index.ts" ==> "button/lib/index"
|
||||
if (component.type === 'component') {
|
||||
component.libPath = component.path.replace('openinula/src/', '').replace('/index.ts', '/lib/index')
|
||||
}
|
||||
|
||||
// "vue/src/button/src/mobile-first.vue" ==> "button/lib/mobile-first"
|
||||
if (component.type === 'template') {
|
||||
component.libPath = component.path.replace('openinula/src/', '').replace('/src/', '/lib/').replace(/\..+$/, '')
|
||||
}
|
||||
|
||||
modules.push(component)
|
||||
}
|
||||
|
||||
component.type === 'component' && componentCount++
|
||||
})
|
||||
|
||||
return modules
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定条件搜索原始模块列表
|
||||
* @private
|
||||
* @param {Function} filterIntercept 搜索条件
|
||||
*/
|
||||
const getModules = (filterIntercept: Function) => {
|
||||
let modules = {}
|
||||
|
||||
if (typeof filterIntercept === 'function') {
|
||||
for (const key in moduleMap) {
|
||||
const component = moduleMap[key]
|
||||
|
||||
if (filterIntercept(component) === true && component.exclude !== true) {
|
||||
modules[key] = component
|
||||
}
|
||||
}
|
||||
} else {
|
||||
modules = moduleMap
|
||||
}
|
||||
|
||||
return modules
|
||||
}
|
||||
|
||||
const getByName = ({
|
||||
name,
|
||||
inversion = false,
|
||||
isOriginal = false
|
||||
}: {
|
||||
name: string
|
||||
inversion?: boolean
|
||||
isOriginal?: boolean
|
||||
}) => {
|
||||
const callback = (item) => {
|
||||
const result = new RegExp(`/${name}/|^openinula-${name}/`).test(item.path)
|
||||
return inversion ? !result : result
|
||||
}
|
||||
|
||||
return isOriginal ? getModules(callback) : getSortModules({ filterIntercept: callback })
|
||||
}
|
||||
|
||||
function getTasks(names: string[]) {
|
||||
// 没有指定组件,则全量构建
|
||||
if (names.length === 0) {
|
||||
return [...getAllModules2(), ...getEntryTasks()]
|
||||
}
|
||||
|
||||
return names
|
||||
.map((name) =>
|
||||
getByName({
|
||||
name: kebabCase({ str: name.replace('@opentiny/openinula-', '') })
|
||||
// isSort: false
|
||||
})
|
||||
)
|
||||
.flat()
|
||||
}
|
||||
|
||||
const getAllIcons = () => {
|
||||
const entries = fg.sync('openinula-icon*/src/*', { cwd: pathFromWorkspaceRoot('packages'), onlyDirectories: true })
|
||||
|
||||
return entries.map((item) => {
|
||||
const name = path.basename(item)
|
||||
|
||||
return {
|
||||
path: item + '/index.ts',
|
||||
libPath: item.replace('/src/', '/lib/'),
|
||||
type: 'component',
|
||||
componentType: 'icon',
|
||||
name: kebabCase({ str: name }),
|
||||
global: capitalizeKebabCase(name),
|
||||
importName: '@opentiny/openinula-' + item
|
||||
} as Module2
|
||||
})
|
||||
}
|
||||
|
||||
function toEntry(libs) {
|
||||
return libs.reduce((result, { libPath, path: file }) => {
|
||||
const tLibPath = libPath.replace('-lib/', '/lib/')
|
||||
result[tLibPath] = pathFromPackages(file)
|
||||
return result
|
||||
}, {})
|
||||
}
|
||||
|
||||
function toTsInclued(libs) {
|
||||
return new Set(
|
||||
libs
|
||||
.filter((item) => ['module', 'component'].includes(item.type))
|
||||
.map((lib) => `packages/${lib.dtsRoot ? lib.path : path.dirname(lib.path)}`)
|
||||
)
|
||||
}
|
||||
|
||||
function generatePackageJson({ beforeWriteFile }): Plugin {
|
||||
return {
|
||||
name: 'opentiny-vue:generate-package-json',
|
||||
generateBundle(output: NormalizedOutputOptions, bundle: OutputBundle) {
|
||||
const cache = {}
|
||||
Object.entries(bundle).forEach(([, item]) => {
|
||||
// 主入口文件, button/index.ts, common/src/index.ts
|
||||
if (item.type === 'chunk' && /\/index\.(js|ts)/.test(item.facadeModuleId!)) {
|
||||
// 从源文件中往上查找最近的 package.json 文件
|
||||
const packageJsonFile = findUpSync('package.json', { cwd: item.facadeModuleId! })
|
||||
|
||||
if (!packageJsonFile) return
|
||||
|
||||
if (cache[packageJsonFile]) return
|
||||
|
||||
let packageJson
|
||||
try {
|
||||
packageJson = JSON.parse(fs.readFileSync(packageJsonFile, { encoding: 'utf-8' }))
|
||||
} catch {}
|
||||
|
||||
const { filePath, content } = beforeWriteFile(path.dirname(item.fileName), packageJson)
|
||||
|
||||
if (content) {
|
||||
this.emitFile({
|
||||
type: 'asset',
|
||||
fileName: `${filePath}/package.json`,
|
||||
source: typeof content === 'string' ? content : JSON.stringify(content, null, 2)
|
||||
})
|
||||
}
|
||||
|
||||
const changelogFile = path.join(path.dirname(packageJsonFile), 'CHANGELOG.md')
|
||||
if (fs.existsSync(changelogFile)) {
|
||||
this.emitFile({
|
||||
type: 'asset',
|
||||
fileName: `${filePath}/CHANGELOG.md`,
|
||||
source: fs.readFileSync(changelogFile, { encoding: 'utf-8' })
|
||||
})
|
||||
}
|
||||
|
||||
cache[packageJsonFile] = true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// const getOpenInulaPlugins = (openinulaVersion: string) => {
|
||||
// const pluginMap = {
|
||||
// '18': () => {
|
||||
// // const openinula18Plugin = requireModules('examples/openinula-docs/node_modules/@vitejs/plugin-openinula')
|
||||
// const openinula18Plugin = requireModules('examples/openinula-docs/node_modules/@vitejs/plugin-react')
|
||||
|
||||
// const openinula18SvgPlugin = svgr
|
||||
|
||||
// return [openinula18Plugin(), openinula18SvgPlugin()]
|
||||
// }
|
||||
// }
|
||||
|
||||
// return pluginMap[openinulaVersion]()
|
||||
// }
|
||||
|
||||
function getBaseConfig() {
|
||||
return defineConfig({
|
||||
publicDir: false,
|
||||
resolve: {
|
||||
extensions: ['.js', '.ts', '.tsx', '.jsx']
|
||||
},
|
||||
define: {
|
||||
'process.env.BUILD_TARGET': JSON.stringify('component')
|
||||
},
|
||||
plugins: [
|
||||
// ...getOpenInulaPlugins('18'),
|
||||
generatePackageJson({
|
||||
beforeWriteFile: (filePath, content) => {
|
||||
const dependencies: any = {}
|
||||
|
||||
Object.entries(content.dependencies).forEach(([key, value]) => {
|
||||
// 只替换 openinula 系的依赖,方便调试
|
||||
// 其他公用的依赖,vue 之前可能发过包
|
||||
const newKey = key.replace('@opentiny/openinula', `${scopeName}/openinula`)
|
||||
if ((value as string).includes('workspace:~')) {
|
||||
dependencies[newKey] = '*'
|
||||
} else {
|
||||
dependencies[newKey] = value
|
||||
}
|
||||
})
|
||||
|
||||
if (filePath.includes('openinula-common')) {
|
||||
dependencies.openinula = '18.2.0'
|
||||
}
|
||||
|
||||
// 如果是主入口或者svg图标则直接指向相同路径
|
||||
if (filePath === 'openinula' || filePath === 'openinula-icon') {
|
||||
content.main = './index.js'
|
||||
content.module = './index.js'
|
||||
} else {
|
||||
content.main = './lib/index.js'
|
||||
content.module = './lib/index.js'
|
||||
}
|
||||
|
||||
content.version = buildVersion
|
||||
content.dependencies = dependencies
|
||||
content.name = content.name.replace('@opentiny/openinula', `${scopeName}/openinula`)
|
||||
|
||||
delete content.devDependencies
|
||||
delete content.private
|
||||
delete content.exports
|
||||
|
||||
return {
|
||||
filePath: filePath.replace(/[\\/]lib$/, ''),
|
||||
content
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
async function batchBuild({ tasks, formats, message, emptyOutDir, dts, outDir }) {
|
||||
if (tasks.length === 0) return
|
||||
logGreen(`====== 开始构建 ${message} ======`)
|
||||
const entry = toEntry(tasks)
|
||||
const dtsInclude = toTsInclued(tasks)
|
||||
|
||||
await build({
|
||||
...getBaseConfig({ dts, dtsInclude }),
|
||||
configFile: false,
|
||||
build: {
|
||||
outDir,
|
||||
emptyOutDir,
|
||||
minify: false,
|
||||
rollupOptions: {
|
||||
plugins: [
|
||||
getBabelOutputPlugin({
|
||||
presets: [['@babel/preset-env', { loose: true, modules: false }]]
|
||||
}) as any,
|
||||
replace({
|
||||
'.less': '.css'
|
||||
}),
|
||||
{
|
||||
name: 'replace-scope',
|
||||
transform(code) {
|
||||
if (scopeName === '@opentiny') return code
|
||||
|
||||
let modifiedCode = code
|
||||
while (modifiedCode.match(/@opentiny\/openinula/g)) {
|
||||
modifiedCode = modifiedCode.replace('@opentiny/openinula', `${scopeName}/openinula`)
|
||||
}
|
||||
return {
|
||||
code: modifiedCode
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
output: {
|
||||
strict: false,
|
||||
manualChunks: {}
|
||||
},
|
||||
external: (source, importer, isResolved) => {
|
||||
// vite打包入口文件或者没有解析过得包不能排除依赖
|
||||
if (isResolved || !importer) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 子图标排除周边引用, 这里注意不要排除svg图标
|
||||
if (/openinula-icon\/.+\/index/.test(importer)) {
|
||||
return !/\.svg/.test(source)
|
||||
}
|
||||
|
||||
// @opentiny/vue 总入口,需要排除所有依赖
|
||||
if (/openinula\/(index|pc|mobile|mobile-first)\.ts$/.test(importer)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (['openinula', 'openinula/jsx-runtime'].includes(source)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (source.indexOf(scopeName) === 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
return external(source)
|
||||
}
|
||||
},
|
||||
lib: {
|
||||
entry,
|
||||
formats,
|
||||
fileName: (format, entryName) => `${entryName}.js`
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function batchBuildAll({ tasks, formats, message, emptyOutDir, dts, npmScope }) {
|
||||
const rootDir = pathFromPackages('')
|
||||
const outDir = path.resolve(rootDir, `dist-openinula/${npmScope}`)
|
||||
await batchBuild({
|
||||
tasks,
|
||||
formats,
|
||||
message,
|
||||
emptyOutDir,
|
||||
dts,
|
||||
outDir
|
||||
})
|
||||
}
|
||||
|
||||
export async function buildOpenInula(
|
||||
names: string[] = [],
|
||||
{ buildTarget = '1.0.0', formats = ['es'], clean = false, dts = true, scope = '@opentiny' }
|
||||
) {
|
||||
scopeName = scope
|
||||
buildVersion = buildTarget
|
||||
// 要构建的模块
|
||||
let tasks = getTasks(names)
|
||||
|
||||
// 如果指定了打包icon或者没有传入任何组件
|
||||
if (names.some((name) => name.includes('icon')) || !names.length) {
|
||||
tasks.push(...getAllIcons())
|
||||
}
|
||||
|
||||
// 构建 @opentiny/openinula
|
||||
if (names.some((name) => [`${scopeName}/openinula`, 'openinula'].includes(name))) {
|
||||
tasks.push(...getEntryTasks())
|
||||
}
|
||||
|
||||
const message = `TINY for openinula: ${JSON.stringify(names.length ? names : '全量')}`
|
||||
|
||||
await batchBuildAll({
|
||||
tasks,
|
||||
formats,
|
||||
message,
|
||||
emptyOutDir: clean,
|
||||
dts,
|
||||
npmScope: scope
|
||||
})
|
||||
}
|
|
@ -3,4 +3,5 @@ export * from './build-entry'
|
|||
export * from './build-runtime'
|
||||
export * from './build-ui-react'
|
||||
export * from './build-entry-react'
|
||||
export * from './build-chart-theme'
|
||||
export * from './build-ui-openinula'
|
||||
export * from './build-entry-openinula'
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"Icon": {
|
||||
"path": "openinula/icon/index.ts",
|
||||
"type": "module",
|
||||
"exclude": true
|
||||
},
|
||||
"Common": {
|
||||
"path": "openinula/common/src/index.ts",
|
||||
"type": "module",
|
||||
"exclude": false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
import { capitalize, walkFileTree, pathFromWorkspaceRoot, logGreen, prettierFormat } from '../../shared/utils'
|
||||
import { quickSort } from '../../shared/module-utils'
|
||||
import path from 'node:path'
|
||||
import fs from 'fs-extra'
|
||||
import commonMappingOpenInula from './common-mapping-openinula.json'
|
||||
|
||||
const getBuildEntryFile = (file, dirs, subPath) => {
|
||||
// 模板文件(pc|mobile|mobile-first)需要同级目录有index.ts文件才能成为打包入口
|
||||
const isTemplatePath = dirs.includes('index.ts')
|
||||
const isMainEntry = file.includes('index') && dirs.includes('package.json')
|
||||
const isPcEntry = file.includes('pc.') && subPath.includes(`src${path.sep}pc.`) && isTemplatePath
|
||||
const isMobileEntry = file.includes('mobile.') && subPath.includes(`src${path.sep}mobile.`) && isTemplatePath
|
||||
const isMobileFirstEntry =
|
||||
file.includes('mobile-first.') && subPath.includes(`src${path.sep}mobile-first.`) && isTemplatePath
|
||||
return {
|
||||
isBuildEntryFile: isMainEntry || isPcEntry || isMobileEntry || isMobileFirstEntry,
|
||||
isMainEntry,
|
||||
isPcEntry,
|
||||
isMobileEntry,
|
||||
isMobileFirstEntry
|
||||
}
|
||||
}
|
||||
|
||||
const tempMap = {
|
||||
'pc.jsx': 'pc',
|
||||
'mobile.jsx': 'mobile',
|
||||
'mobile-first.jsx': 'mobile-first',
|
||||
'pc.tsx': 'pc',
|
||||
'mobile.tsx': 'mobile',
|
||||
'mobile-first.tsx': 'mobile-first'
|
||||
}
|
||||
|
||||
const getTemplateName = (currentPaths, entryObj) => {
|
||||
const entryMaps = {
|
||||
isPcEntry: 'Pc',
|
||||
isMobileEntry: 'Mobile',
|
||||
isMobileFirstEntry: 'MobileFirst',
|
||||
isMainEntry: ''
|
||||
}
|
||||
const mapKey = Object.keys(entryObj).filter((item) => entryObj[item] && item !== 'isBuildEntryFile')[0]
|
||||
const subFix = entryMaps[mapKey]
|
||||
return `${currentPaths.split('-').map(capitalize).join('')}${subFix}`
|
||||
}
|
||||
|
||||
export const writeModuleMap = (moduleMap) => {
|
||||
fs.writeFileSync(
|
||||
pathFromWorkspaceRoot('packages/openinula/modules.json'),
|
||||
prettierFormat({
|
||||
str: typeof moduleMap === 'string' ? moduleMap : JSON.stringify(moduleMap),
|
||||
options: {
|
||||
parser: 'json',
|
||||
printWidth: 10
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function makeOpenInulaModules() {
|
||||
const templates = { ...commonMappingOpenInula }
|
||||
|
||||
walkFileTree({
|
||||
isDeep: true,
|
||||
dirPath: pathFromWorkspaceRoot('packages/openinula/src'),
|
||||
fileFilter({ file }) {
|
||||
return !/node_modules/.test(file)
|
||||
},
|
||||
callback({ file, subPath, dirs }) {
|
||||
const entryObj = getBuildEntryFile(file, dirs, subPath)
|
||||
const mode: string[] = []
|
||||
|
||||
if (entryObj.isMainEntry && dirs.includes('src')) {
|
||||
const srcPath = subPath.replace(file, 'src')
|
||||
const srcFiles = fs.readdirSync(srcPath) || []
|
||||
srcFiles.forEach((item) => {
|
||||
if (tempMap[item]) {
|
||||
mode.push(tempMap[item])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (entryObj.isBuildEntryFile) {
|
||||
const modulePath = subPath.slice(subPath.lastIndexOf(`openinula${path.sep}src`)).replaceAll(path.sep, '/')
|
||||
const matchArr = modulePath.match(/.+\/(.+?)\/(index\.ts|src\/pc\.|src\/mobile\.|src\/mobile-first\.)/)
|
||||
if (matchArr?.[1]) {
|
||||
const compName = getTemplateName(matchArr[1], entryObj)
|
||||
templates[compName] = {
|
||||
path: modulePath,
|
||||
type: entryObj.isMainEntry ? 'component' : 'template',
|
||||
exclude: false
|
||||
}
|
||||
if (mode.length > 0) {
|
||||
templates[compName].mode = mode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const modulesJson = quickSort({ sortData: templates, returnType: 'object' })
|
||||
|
||||
writeModuleMap(modulesJson)
|
||||
}
|
||||
|
||||
try {
|
||||
makeOpenInulaModules()
|
||||
|
||||
logGreen('npm run create:mapping-openinula done.')
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(e)
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* pnpm create:ui-openinula 新建组件,支持格式如下:
|
||||
*
|
||||
* pnpm create:ui-openinula alert 新建组件后,请及时测试,如果报错请根据报错内容做局部修改
|
||||
*/
|
||||
import path from 'node:path'
|
||||
import fs from 'fs-extra'
|
||||
import semver from 'semver'
|
||||
import {
|
||||
walkFileTree,
|
||||
capitalizeKebabCase,
|
||||
logGreen,
|
||||
logYellow,
|
||||
templatePath,
|
||||
pathJoinFromCLI
|
||||
} from '../../shared/utils'
|
||||
import handlebarsRender from '../build/handlebars.render'
|
||||
import { parse } from '@vue/compiler-sfc'
|
||||
import { requireModules } from '../build/build-ui'
|
||||
|
||||
// 从renderless中获取组件API
|
||||
function getApi(name) {
|
||||
const { api } = requireModules(`packages/renderless/src/${name}/vue`)
|
||||
let apiStr = JSON.stringify(api)
|
||||
apiStr = apiStr.replaceAll('"', '').replace('[', '').replace(']', '')
|
||||
return apiStr
|
||||
}
|
||||
// vue index中获取组件Props,该部分待完成
|
||||
// function getProps(name) {
|
||||
// let path = pathJoinFromCLI('../../packages/vue/src/alert/src/index.ts')
|
||||
// let fileContent = fs.readFileSync(path, { encoding: 'utf8' })
|
||||
// // return props
|
||||
// }
|
||||
// 解析vue template部分
|
||||
function vueToJSX(templateSource) {
|
||||
const { descriptor } = parse(templateSource)
|
||||
if (descriptor.template) {
|
||||
const ast = descriptor.template.ast
|
||||
const jsx = JSXFromAST(ast)
|
||||
return jsx
|
||||
} else {
|
||||
throw new Error('No template found in the source.')
|
||||
}
|
||||
}
|
||||
// 遍历Vue AST解析成符合openInula规范的组件模板 E:\tinyvue\tsconfig.vue3.json
|
||||
function JSXFromAST(node) {
|
||||
let jsx = ''
|
||||
// If和For标记
|
||||
let tagName = ''
|
||||
let tagAttr = ''
|
||||
switch (node.type) {
|
||||
case 1: // Element
|
||||
if (node.props) {
|
||||
node.props.forEach((attr) => {
|
||||
if (attr.name === 'if') {
|
||||
tagName = 'If'
|
||||
tagAttr = ' v-if ={' + attr.exp.content + '}'
|
||||
}
|
||||
if (attr.name === 'for') {
|
||||
tagName = 'For'
|
||||
let list = attr.exp.content
|
||||
list = list.slice(list.indexOf('in ') + 3)
|
||||
tagAttr = ' list ={' + list + '}'
|
||||
}
|
||||
// class转换成className
|
||||
if (attr.name === 'class') {
|
||||
jsx += ' className="' + attr.value.content + '"'
|
||||
}
|
||||
// 含有:的动态属性添加大括号{},其中:class需要转换成className
|
||||
if (attr.name === 'bind') {
|
||||
if (attr.arg) {
|
||||
if (attr.arg.content === 'class') {
|
||||
jsx += ' className={vc(' + attr.exp.content + ')}'
|
||||
} else {
|
||||
jsx += ' ' + attr.arg.content + '={' + attr.exp.content + '}'
|
||||
}
|
||||
}
|
||||
}
|
||||
// 含有@的动态方法转添加on前缀
|
||||
if (attr.name === 'on') {
|
||||
// 有修饰符的exp可能为undefiend,修饰符待处理
|
||||
if (attr.exp) {
|
||||
jsx += ' on' + capitalizeKebabCase(attr.arg.content) + '={' + attr.exp.content + '}'
|
||||
}
|
||||
}
|
||||
// 含有model的处理
|
||||
// if (attr.name === 'model') {
|
||||
// }
|
||||
})
|
||||
}
|
||||
jsx = '<' + node.tag + jsx + '>'
|
||||
|
||||
// 添加If和For外层包裹,前后添加换行符更好看
|
||||
if (tagName) {
|
||||
jsx = '\n<' + tagName + tagAttr + '>\n' + jsx
|
||||
}
|
||||
if (node.children) {
|
||||
node.children.forEach((child) => {
|
||||
jsx += JSXFromAST(child)
|
||||
})
|
||||
}
|
||||
jsx += '</' + node.tag + '>'
|
||||
// 添加If和For外层包裹
|
||||
if (tagName) {
|
||||
jsx += '\n</' + tagName + '>'
|
||||
}
|
||||
break
|
||||
case 2: // Text,此处多数是换行符
|
||||
jsx += node.content
|
||||
break
|
||||
case 3: // 注释
|
||||
jsx += node.loc.source
|
||||
break
|
||||
|
||||
case 5: // 插值或组件内变量
|
||||
jsx += `{${node.content.content}}`
|
||||
break
|
||||
|
||||
// 其他节点类型...
|
||||
default:
|
||||
// 处理其他节点类型或抛出错误
|
||||
logYellow(`======================node.type=${node.type}====================`)
|
||||
logYellow(node)
|
||||
// throw new Error('Unsupported node type: ' + node.type);
|
||||
}
|
||||
// 单独处理slot
|
||||
jsx = jsx.replace('<slot', '<Slot slots={props.slots}').replace('</slot>', '</Slot>')
|
||||
// 单独处理transition
|
||||
jsx = jsx.replace('transition', 'Transition')
|
||||
// 单独处理component组件
|
||||
jsx = jsx.replace('component', 'Component')
|
||||
|
||||
// 最外层template标签替换成div并添加ref
|
||||
jsx = jsx.replace('<template>', '<div ref={ref}>').replace('</template>', '</div>')
|
||||
// windows下调整换行符
|
||||
jsx = jsx.replace('\n', '\r\n')
|
||||
return jsx
|
||||
}
|
||||
|
||||
export async function createUi(names = []) {
|
||||
const templateDir = path.join(templatePath, './openinula')
|
||||
const componetDir = pathJoinFromCLI('../../packages/openinula/src')
|
||||
const vueComponetDir = pathJoinFromCLI('../../packages/vue/src')
|
||||
|
||||
const { version } = fs.readJSONSync(pathJoinFromCLI('../../packages/openinula/package.json'))
|
||||
|
||||
names.forEach((componentName) => {
|
||||
let componentPath = path.join(componetDir, componentName)
|
||||
let vueComponentPath = path.join(vueComponetDir, componentName, 'src')
|
||||
|
||||
if (fs.existsSync(componentPath)) {
|
||||
logYellow(`The component name : ${componentName} is exist , please enter other name.`)
|
||||
return
|
||||
}
|
||||
// 获取API
|
||||
let api = getApi(componentName)
|
||||
// 定义不同模板内容
|
||||
let pcTemplate = ``
|
||||
let mobileTemplate = ``
|
||||
let mobileFirstTemplate = ``
|
||||
|
||||
walkFileTree({
|
||||
isDeep: true,
|
||||
dirPath: vueComponentPath,
|
||||
callback({ file, subPath }) {
|
||||
// 判断是否是.vue文件,否则会出错
|
||||
if (file.split('.')[1] === 'vue') {
|
||||
let fileContent = fs.readFileSync(subPath, { encoding: 'utf8' })
|
||||
let content = vueToJSX(fileContent)
|
||||
|
||||
if (file === 'pc.vue') {
|
||||
pcTemplate = content
|
||||
}
|
||||
if (file === 'mobile.vue') {
|
||||
mobileTemplate = content
|
||||
}
|
||||
if (file === 'mobile-first.vue') {
|
||||
mobileFirstTemplate = content
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
walkFileTree({
|
||||
isDeep: true,
|
||||
dirPath: templateDir,
|
||||
callback({ file, subPath }) {
|
||||
let fileName = file
|
||||
// 每次遍历需要重置组件目录,否则会报错
|
||||
componentPath = path.join(componetDir, componentName)
|
||||
const isSrcDir = path.basename(path.dirname(subPath)) === 'src'
|
||||
if (isSrcDir) {
|
||||
componentPath = path.join(componentPath, 'src')
|
||||
}
|
||||
|
||||
if (!fs.existsSync(componentPath)) {
|
||||
fs.mkdirSync(componentPath)
|
||||
}
|
||||
|
||||
let template = ''
|
||||
if (file === 'pc.jsx') {
|
||||
template = pcTemplate
|
||||
}
|
||||
if (file === 'mobile.jsx') {
|
||||
template = mobileTemplate
|
||||
}
|
||||
if (file === 'mobile-first.jsx') {
|
||||
template = mobileFirstTemplate
|
||||
}
|
||||
|
||||
let fileContent = fs.readFileSync(subPath, { encoding: 'utf8' })
|
||||
const upperComponentName = capitalizeKebabCase(componentName)
|
||||
|
||||
// 编译模板
|
||||
fileContent = handlebarsRender({
|
||||
template: fileContent,
|
||||
data: {
|
||||
NAME: componentName,
|
||||
UNAME: upperComponentName,
|
||||
MINOR: semver.minor(version),
|
||||
SUFFIX: '',
|
||||
THEME: 'theme',
|
||||
TEMPLATE: template,
|
||||
API: api
|
||||
},
|
||||
delimiter: ['\\[\\[', '\\]\\]'],
|
||||
options: { noEscape: true }
|
||||
})
|
||||
componentPath = path.join(componentPath, fileName)
|
||||
fs.writeFileSync(componentPath, fileContent)
|
||||
}
|
||||
})
|
||||
|
||||
logGreen('Create ui openinula done for ' + componentName)
|
||||
})
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
export * from './create-icon-saas'
|
||||
export * from './create-ui-openinula'
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
#!/usr/bin/env node
|
||||
import { Command, Option } from 'commander'
|
||||
import { createIconSaas } from './commands/create/index.js'
|
||||
import { buildUi, buildEntry, buildRuntime, buildReact, buildEntryReact, chartTheme } from './commands/build'
|
||||
import {
|
||||
buildUi,
|
||||
buildEntry,
|
||||
buildRuntime,
|
||||
buildReact,
|
||||
buildEntryReact,
|
||||
buildOpenInula,
|
||||
buildEntryOpenInula
|
||||
} from './commands/build'
|
||||
import { releaseAurora } from './commands/release/releaseAurora'
|
||||
|
||||
import { createUi } from './commands/create'
|
||||
|
||||
const program = new Command()
|
||||
|
||||
program.command('release:aurora').description('转换为aurora的包').action(releaseAurora)
|
||||
|
@ -12,9 +22,9 @@ program.command('create:icon-saas').description('同步生成 icon-saas').action
|
|||
|
||||
program.command('build:entry-react').description('生成 react 组件库入口').action(buildEntryReact)
|
||||
|
||||
program.command('build:entry').description('生成组件库入口').action(buildEntry)
|
||||
program.command('build:entry-openinula').description('生成 openinula 组件库入口').action(buildEntryOpenInula)
|
||||
|
||||
program.command('build:chartTheme').description('切换chart主题').action(chartTheme)
|
||||
program.command('build:entry').description('生成组件库入口').action(buildEntry)
|
||||
|
||||
program
|
||||
.command('build:ui')
|
||||
|
@ -23,7 +33,6 @@ program
|
|||
.addOption(new Option('-v --vue-versions <vueVersions...>', '目标框架,默认所有').choices(['2', '2.7', '3']))
|
||||
.addOption(new Option('-f --formats <formats...>', '目标格式,默认 ["es"]').choices(['es', 'cjs']))
|
||||
.addOption(new Option('-t --build-target <buildTarget>', '组件的目标版本'))
|
||||
.addOption(new Option('-d --design <design>', '构建的目标设计规范'))
|
||||
.option('-s, --scope <scope>', 'npm scope,默认是 opentiny,会以 @opentiny 发布到 npm')
|
||||
.option('-c, --clean', '清空构建目录')
|
||||
.option('--no-dts', '不生成 dts')
|
||||
|
@ -48,4 +57,21 @@ program
|
|||
.option('--no-dts', '不生成 dts')
|
||||
.action(buildReact)
|
||||
|
||||
program
|
||||
.command('build:openinula')
|
||||
.description('打包 openinula 组件库')
|
||||
.argument('[names...]', '构建指定组件,如 button alert;不指定则构建全量组件')
|
||||
.addOption(new Option('-f --formats <formats...>', '目标格式,默认 ["es"]').choices(['es', 'cjs']))
|
||||
.addOption(new Option('-t --build-target <buildTarget>', '组件的目标版本'))
|
||||
.option('-s, --scope <scope>', 'npm scope,默认是 opentiny,会以 @opentiny 发布到 npm')
|
||||
.option('-c, --clean', '清空构建目录')
|
||||
.option('--no-dts', '不生成 dts')
|
||||
.action(buildOpenInula)
|
||||
|
||||
program
|
||||
.command('create:ui-openinula')
|
||||
.description(' openinula 组件库代码生成')
|
||||
.argument('[names...]', '构建指定组件,如 button alert;不指定则构建全量组件')
|
||||
.action(createUi)
|
||||
|
||||
program.parse()
|
||||
|
|
32
package.json
32
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "opentiny-vue",
|
||||
"version": "3.14.0",
|
||||
"version": "3.7.12",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@8.3.1",
|
||||
"description": "An enterprise-class UI component library, support both Vue.js 2 and Vue.js 3, as well as PC and mobile.",
|
||||
|
@ -28,7 +28,7 @@
|
|||
"main": "packages/index.js",
|
||||
"engines": {
|
||||
"node": ">=16",
|
||||
"pnpm": ">=6.35"
|
||||
"pnpm": ">=7"
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
|
@ -59,28 +59,19 @@
|
|||
"build:runtime": "pnpm -C internals/cli build:runtime",
|
||||
"// ---------- 构建相关脚本 ----------": "",
|
||||
"build:ui": "pnpm create:icon-saas && pnpm create:mapping && pnpm build:entry && gulp themeConcat && pnpm -C internals/cli build:ui",
|
||||
"build:chartTheme": "pnpm -C internals/cli build:chartTheme",
|
||||
"build:renderless": "pnpm -C packages/renderless build:fast",
|
||||
"build:theme": "gulp themeConcat && pnpm -C packages/theme build:fast",
|
||||
"build:themeSaas": "pnpm -C packages/theme-saas build:fast",
|
||||
"build:themeMobile": "pnpm -C packages/theme-mobile build:fast",
|
||||
"build:themejson": "gulp themeJson",
|
||||
"build:internals": "pnpm \"--filter=./internals/*\" build",
|
||||
"build:vite-import": "pnpm --filter @opentiny/vue-vite-import build",
|
||||
"build:virtual-template": "pnpm --filter @opentiny-internal/unplugin-virtual-template build",
|
||||
"build:site": "gulp themeConcat && pnpm i -g pnpm && pnpm build:vite-import && pnpm build:virtual-template && pnpm -C examples/sites build",
|
||||
"build:site": "gulp themeConcat && pnpm -C examples/sites build",
|
||||
"release:aurora": "pnpm -C internals/cli release:aurora",
|
||||
"// ---------- 使用pnpm批量发布npm包 ----------": "",
|
||||
"pub2": "pnpm --filter=\"./packages/dist2/**\" publish --tag v2-latest --no-git-checks --access=public",
|
||||
"pub3": "pnpm --filter=\"./packages/dist3/**\" publish --no-git-checks --access=public",
|
||||
"pub2.7": "pnpm --filter=\"./packages/dist2.7/**\" publish --tag v2.7-latest --no-git-checks --access=public",
|
||||
"pub:aurora": "pnpm --filter=\"./packages/dist2/@aurora/**\" publish --no-git-checks --access=public",
|
||||
"pub:theme": "pnpm --filter=\"./packages/theme/dist\" publish --no-git-checks --access=public",
|
||||
"pub:themeMobile": "pnpm --filter=\"./packages/theme-mobile/dist/\" publish --no-git-checks --access=public",
|
||||
"pub:themeSaas": "pnpm --filter=\"./packages/theme-saas/dist\" publish --no-git-checks --access=public",
|
||||
"pub:renderless": "pnpm --filter=\"./packages/renderless/dist\" publish --no-git-checks --access=public",
|
||||
"pub:all": "pnpm pub2 && pnpm pub3 && pnpm pub:theme && pnpm pub:themeMobile && pnpm pub:themeSaas && pnpm pub:renderless",
|
||||
"pub:site": "pnpm -C examples/sites pub",
|
||||
"// ---------- unit单元测试 ----------": "",
|
||||
"test:unit2": "pnpm -C examples/vue2 test:unit",
|
||||
"test:unit2.7": "pnpm -C examples/vue2.7 test:unit",
|
||||
|
@ -89,18 +80,14 @@
|
|||
"test:e2e2": "pnpm -C examples/vue2 test:e2e --project=chromium",
|
||||
"test:e2e2.7": "pnpm -C examples/vue2.7 test:e2e --project=chromium",
|
||||
"test:e2e3": "pnpm -C examples/vue3 test:e2e --project=chromium",
|
||||
"test:e2e2:mobile": "pnpm -C examples/vue2 test:e2e --project=android",
|
||||
"test:e2e2.7:mobile": "pnpm -C examples/vue2.7 test:e2e --project=android",
|
||||
"test:e2e3:mobile": "pnpm -C examples/vue3 test:e2e --project=android",
|
||||
"// ---------- playwright下载chromium、firefox等浏览器内核 ----------": "",
|
||||
"install:browser": "pnpm -C examples/vue3 install:browser",
|
||||
"// ---------- e2e测试代码生成器 ----------": "",
|
||||
"codegen": "pnpm -C examples/vue3 codegen",
|
||||
"codegen:mobile": "pnpm -C examples/vue3 codegen --device=\"Pixel 5\" ",
|
||||
"format": "prettier --write --cache packages/**/{*.vue,*.js,*.ts,*.jsx,*.tsx,*.less} examples/**/{*.vue,*.js,*.ts,*.jsx,*.tsx} internals/**/{*.js,*.ts}",
|
||||
"lint": "eslint \"packages/**/{*.vue,*.js,*.ts}\" --quiet --fix",
|
||||
"lint:doc": "eslint \"examples/**/{*.vue,*.js,*.ts}\" --quiet --fix",
|
||||
"clean:build": "rimraf packages/dist2 packages/dist3 packages/dist2.7 packages/renderless/dist packages/theme/dist packages/theme-saas/dist packages/theme-mobile/dist",
|
||||
"clean:build": "rimraf packages/dist2 packages/dist3 packages/renderless/dist packages/dist2.7",
|
||||
"clean:dependencies": "rm -rf node_modules /**/node_modules",
|
||||
"// ---------- 构建【mf】版本 ----------": "",
|
||||
"preci:deployMfPatch": "pnpm clean:build && lerna version prepatch --conventional-prerelease --include-merged-tags --preid mf --no-push --yes",
|
||||
|
@ -138,17 +125,22 @@
|
|||
"build:react-site": "pnpm --filter @opentiny/react-site build",
|
||||
"prettier": "prettier --config .prettierrc --write .",
|
||||
"// ---------- openinula 相关脚本命令 ----------": "",
|
||||
"dev:openinula": "pnpm -C examples/openinula-docs run dev",
|
||||
"dev:openinula": "pnpm create:mapping-openinula && pnpm build:entry-openinula && pnpm -C examples/openinula-docs run dev",
|
||||
"build:entry-openinula": "pnpm -C internals/cli build:entry-openinula",
|
||||
"create:mapping-openinula": "pnpm -C internals/cli create:mapping-openinula",
|
||||
"build:openinula": "pnpm -C internals/cli build:openinula",
|
||||
"build:ui-openinula": "pnpm create:mapping-openinula && pnpm build:entry-openinula && pnpm build:openinula",
|
||||
"create:ui-openinula": "pnpm -C internals/cli create:ui-openinula",
|
||||
"// ---------- solid 相关脚本命令 ----------": "",
|
||||
"dev:solid": "pnpm -C examples/solid-docs run dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/composition-api": "1.7.2",
|
||||
"@vue/composition-api": "1.2.2",
|
||||
"color": "^4.2.3",
|
||||
"cropperjs": "1.5.12",
|
||||
"crypto-js": "4.2.0",
|
||||
"echarts": "5.4.1",
|
||||
"echarts-liquidfill": "3.1.0",
|
||||
"echarts-liquidfill": "3.0.0",
|
||||
"echarts-wordcloud": "2.0.0",
|
||||
"fastdom": "1.0.11",
|
||||
"shepherd.js": "11.0.1",
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
# 开发文档
|
||||
|
||||
## 更新日志
|
||||
|
||||
#### 2024-04-07
|
||||
|
||||
1. 修复pnpm build:openinula构建命令
|
||||
2. 增加pnpm create:ui-openinula创建openInula组件命令
|
||||
该命令主要使用了/internals/cli/src/create/create-ui-openinula的转换脚本,通过解析vue模板遍历ast,生成inula组件。目前该脚本已经实现了api和template的转换,能减少95%的人工转换工作量。
|
||||
3. 修复事件点击报错。该错误来自适配层dom和vnode(对应react的fiber)的关联。
|
||||
|
||||
## 如何将已经开发好的Vue组件快速转换为openInula组件
|
||||
|
||||
执行 `pnpm create:ui-openinula +组件名称` 可在`/packages/openinula/src/`目录下生成对应组件,然后进行测试,根据需要进行少量修改即可。
|
||||
|
||||
## 开发成果
|
||||
|
||||
1. 基本工具的开发
|
||||
|
||||
- create-ui-openinula(已完成,通过该转换工具,可以将Vue模板转换成openInula组件模板)
|
||||
- create-mapping-openinula(已完成,通过该工具,可以生成openinula组件的map文件。新增组件后需要调用)
|
||||
- build-entry-openinula(已完成,通过该工具,可以生成openinula组件的入口文件。新增组件后需要调用)
|
||||
- build-ui-openinula(已完成,通过该工具,可以打包openInula组件)
|
||||
- common-mapping-openinula.json(map文件的公共部分)
|
||||
|
||||
调用顺序为create-ui-openinula(转换组件)=>create-mapping-openinula(创建map)=>build-ui-openinula(创建入口文件)
|
||||
|
||||
实际开发过程中通过命令行,首先执行`pnpm create:ui-openinula+组件名称`,然后执行`pnpm dev:openinula`
|
||||
其中,`pnpm dev:openinula`命令已经包含了map文件生成和入口文件生成
|
||||
|
||||
2. openInula-common 适配层的开发,已完成全部API的转换
|
||||
|
||||
## 下载依赖
|
||||
|
||||
```bash
|
||||
pnpm i
|
||||
```
|
||||
|
||||
## 将Vue组件转换位 openinula 组件
|
||||
|
||||
转换后,还需检查转换的代码,将缺少的props和constants补进代码,然后进行测试
|
||||
|
||||
```bash
|
||||
pnpm create:ui-openinula +组件名称
|
||||
for example:
|
||||
pnpm create:ui-openinula button
|
||||
```
|
||||
|
||||
## 生成 openinula 组件入口,每次生成新组件后需更新组件入口
|
||||
|
||||
```bash
|
||||
pnpm build:entry-openinula
|
||||
```
|
||||
|
||||
## 本地启动 openinula 调试项目
|
||||
|
||||
```bash
|
||||
pnpm dev:openinula
|
||||
```
|
||||
|
||||
## 本地启动 openinula 文档项目
|
||||
|
||||
```bash
|
||||
pnpm dev:openinula-site
|
||||
```
|
||||
|
||||
## 打包 openinula 组件
|
||||
|
||||
```bash
|
||||
pnpm build:ui-openinula
|
||||
```
|
||||
|
||||
运行此命令后,会在 pacakges-openinula 产生打包产物
|
||||
一般是
|
||||
packages/dist-openinula/@opention/button
|
||||
... 单个组件产物
|
||||
packages/dist-openinula/@opention/openinula-common
|
||||
packages/dist-openinula/@opention/openinula
|
||||
packages/dist-openinula/@opention/openinula-icon
|
||||
|
||||
命令参数:传入字符串参数列表可以指定只打包单个组件或多个特定组件,比如
|
||||
|
||||
```bash
|
||||
pnpm build:ui-openinula button
|
||||
```
|
||||
|
||||
默认不传的话,会打包所有组件,以及公共任务,比如 openinula-common、openinula-icon
|
||||
|
||||
可以通过 -f 指定目标格式,默认 es,可选 es、cjs
|
||||
可以通过 -t 指定目标版本,默认 18,现在 openinula 只支持 18
|
||||
可以通过 -s 指定发布 npm scope,默认是 opentiny
|
||||
可以通过 -c 指定是否清空构建目录
|
||||
可以通过 --no-dts 指定不生成类型定义文件
|
||||
|
||||
## 发包 openinula 组件
|
||||
|
||||
```bash
|
||||
pnpm pub:openinula
|
||||
```
|
||||
|
||||
# 目录结构
|
||||
|
||||
## 打包 openinula 相关
|
||||
|
||||
```b
|
||||
internals/cli
|
||||
/build
|
||||
/build-entry-openinula.ts (packages/openinula 目录下生成入口)
|
||||
/build-ui-openinula.ts (packages/dist-openinula 下生成打包产物)
|
||||
/create
|
||||
/create-mapping-openinula.ts (packages 下生成构建任务列表 modules.json)
|
||||
/common-mapping-openinula.json (定义一些公共的打包任务,如 openinula-common)
|
||||
/create-ui-openinula.ts (vue到openInula的转换脚本)
|
||||
|
||||
internals/cli
|
||||
/public/template/openinula(转换脚本使用的模板文件)
|
||||
|
||||
```
|
||||
|
||||
## 开发 openinula 模版文件相关
|
||||
|
||||
packages/openinula/src/[compName] 目录
|
||||
|
||||
一个组件模版的目录结构如下
|
||||
|
||||
```b
|
||||
alert
|
||||
/node_modules
|
||||
/src
|
||||
/index.ts
|
||||
/pc.tsx
|
||||
/mobile.tsx
|
||||
/mobile-first.ts
|
||||
/index.ts
|
||||
/package.json
|
||||
```
|
||||
|
||||
alert/index 是组件入口
|
||||
pc、mobile、mobile-first 是三套模版
|
||||
|
||||
## 开发 openinula-icon 相关
|
||||
|
||||
packages/openinula-icon/src/[svgName] 目录
|
||||
|
||||
一个 svg 直接用一个 index.ts 创建
|
||||
|
||||
如:packages/openinula-icon/src/add/index.ts
|
||||
|
||||
```ts
|
||||
import { Svg } from '@opentiny/openinula-common'
|
||||
import { openinulaComponent as AddLoading } from '@opentiny/vue-theme/svgs/add.svg'
|
||||
|
||||
export default Svg({ name: 'AddLoading', component: AddLoading })
|
||||
```
|
||||
|
||||
## 开发 openinula-common openinula 适配层相关
|
||||
|
||||
openinula-common 的目录如下,主要是适配层的文件
|
||||
|
||||
```b
|
||||
packages/openinula-common
|
||||
/src
|
||||
/csscls.ts 操作样式类名的一些方法
|
||||
/event.ts 模拟 vue 事件系统
|
||||
/fiber.ts 对 fiber 的一些读取操作
|
||||
/openinulaive.ts 实现数据响应式
|
||||
/resolveProps.js 从 openinula 的 props 上解析事件或属性
|
||||
/svg-render.jsx 渲染 svg 组件的公共函数
|
||||
/utils.ts 工具函数
|
||||
/virtual-comp.jsx 虚拟组件,用于实现 vue 的指令系统
|
||||
/vm.js 用户模拟 vue 的 vm 对象
|
||||
/vue-hooks.js 用户模拟 vue 的钩子函数
|
||||
```
|
||||
|
||||
# 用户使用文档
|
||||
|
||||
## 在项目中使用所有组件
|
||||
|
||||
- 1.下载整个组件库
|
||||
|
||||
```bash
|
||||
npm i @pe-3/openinula
|
||||
```
|
||||
|
||||
- 2. 导入组件
|
||||
|
||||
```js
|
||||
import { Button as TinyButton, openinula as Tiny openinula } from '@pe-3/openinula'
|
||||
```
|
||||
|
||||
- 3. 使用组件(查看 api 文档)
|
||||
|
||||
```js
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<TinyButton type="primary">主要按钮</TinyButton>
|
||||
<TinyAlert description="提示组件" closeable={false} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 在项目中使用单个组件
|
||||
|
||||
- 1. 下载单个组件
|
||||
|
||||
```bash
|
||||
npm i @pe-3/openinula-button
|
||||
npm i @pe-3/openinula-alert
|
||||
```
|
||||
|
||||
- 2. 导入单个组件
|
||||
|
||||
```js
|
||||
import TintButton from '@pe-3/openinula-button'
|
||||
import TintAlert from '@pe-3/openinula-alert'
|
||||
```
|
||||
|
||||
- 3. 使用单个组件
|
||||
|
||||
```js
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<TinyButton type="primary">主要按钮</TinyButton>
|
||||
<TinyAlert description="提示组件" closeable={false} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 组件 api 文档地址:
|
||||
|
||||
https://opentiny.design/
|
||||
|
||||
## codesandbox
|
||||
|
||||
https://codesandbox.io/s/hungry-bash-tlch6l?file=/src/App.js
|
|
@ -1,9 +1,19 @@
|
|||
import Alert from '@opentiny/openinula-alert'
|
||||
import Badge from '@opentiny/openinula-badge'
|
||||
import Button from '@opentiny/openinula-button'
|
||||
import Icon from '@opentiny/openinula-icon'
|
||||
import Switch from '@opentiny/openinula-switch'
|
||||
|
||||
const components = [Alert, Badge, Button, Icon, Switch]
|
||||
|
||||
export const version = '1.0.0'
|
||||
|
||||
export { Alert }
|
||||
export { Alert, Badge, Button, Icon, Switch }
|
||||
|
||||
export default {
|
||||
Alert
|
||||
Alert,
|
||||
Badge,
|
||||
Button,
|
||||
Icon,
|
||||
Switch
|
||||
} as any
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import Alert from '@opentiny/openinula-alert/src/mobile-first'
|
||||
import Button from '@opentiny/openinula-button/src/mobile-first'
|
||||
import Icon from '@opentiny/openinula-icon/src/mobile-first'
|
||||
import Switch from '@opentiny/openinula-switch/src/mobile-first'
|
||||
|
||||
const components = [Alert, Button, Icon, Switch]
|
||||
|
||||
export const version = '1.0.0'
|
||||
|
||||
export { Alert, Button, Icon, Switch }
|
||||
|
||||
export default {
|
||||
Alert,
|
||||
Button,
|
||||
Icon,
|
||||
Switch
|
||||
} as any
|
|
@ -0,0 +1,19 @@
|
|||
import Alert from '@opentiny/openinula-alert/src/mobile'
|
||||
import Badge from '@opentiny/openinula-badge/src/mobile'
|
||||
import Button from '@opentiny/openinula-button/src/mobile'
|
||||
import Icon from '@opentiny/openinula-icon/src/mobile'
|
||||
import Switch from '@opentiny/openinula-switch/src/mobile'
|
||||
|
||||
const components = [Alert, Badge, Button, Icon, Switch]
|
||||
|
||||
export const version = '1.0.0'
|
||||
|
||||
export { Alert, Badge, Button, Icon, Switch }
|
||||
|
||||
export default {
|
||||
Alert,
|
||||
Badge,
|
||||
Button,
|
||||
Icon,
|
||||
Switch
|
||||
} as any
|
|
@ -0,0 +1,106 @@
|
|||
{
|
||||
"Alert": {
|
||||
"path": "openinula/src/alert/index.ts",
|
||||
"type": "component",
|
||||
"exclude": false,
|
||||
"mode": [
|
||||
"mobile-first",
|
||||
"mobile",
|
||||
"pc"
|
||||
]
|
||||
},
|
||||
"AlertMobile": {
|
||||
"path": "openinula/src/alert/src/mobile.jsx",
|
||||
"type": "template",
|
||||
"exclude": false
|
||||
},
|
||||
"AlertMobileFirst": {
|
||||
"path": "openinula/src/alert/src/mobile-first.jsx",
|
||||
"type": "template",
|
||||
"exclude": false
|
||||
},
|
||||
"AlertPc": {
|
||||
"path": "openinula/src/alert/src/pc.jsx",
|
||||
"type": "template",
|
||||
"exclude": false
|
||||
},
|
||||
"Badge": {
|
||||
"path": "openinula/src/badge/index.ts",
|
||||
"type": "component",
|
||||
"exclude": false,
|
||||
"mode": [
|
||||
"mobile",
|
||||
"pc"
|
||||
]
|
||||
},
|
||||
"BadgeMobile": {
|
||||
"path": "openinula/src/badge/src/mobile.jsx",
|
||||
"type": "template",
|
||||
"exclude": false
|
||||
},
|
||||
"BadgePc": {
|
||||
"path": "openinula/src/badge/src/pc.jsx",
|
||||
"type": "template",
|
||||
"exclude": false
|
||||
},
|
||||
"Button": {
|
||||
"path": "openinula/src/button/index.ts",
|
||||
"type": "component",
|
||||
"exclude": false,
|
||||
"mode": [
|
||||
"mobile-first",
|
||||
"mobile",
|
||||
"pc"
|
||||
]
|
||||
},
|
||||
"ButtonMobile": {
|
||||
"path": "openinula/src/button/src/mobile.jsx",
|
||||
"type": "template",
|
||||
"exclude": false
|
||||
},
|
||||
"ButtonMobileFirst": {
|
||||
"path": "openinula/src/button/src/mobile-first.jsx",
|
||||
"type": "template",
|
||||
"exclude": false
|
||||
},
|
||||
"ButtonPc": {
|
||||
"path": "openinula/src/button/src/pc.jsx",
|
||||
"type": "template",
|
||||
"exclude": false
|
||||
},
|
||||
"Common": {
|
||||
"path": "openinula/common/src/index.ts",
|
||||
"type": "module",
|
||||
"exclude": false
|
||||
},
|
||||
"Icon": {
|
||||
"path": "openinula/src/icon/index.ts",
|
||||
"type": "component",
|
||||
"exclude": false
|
||||
},
|
||||
"Switch": {
|
||||
"path": "openinula/src/switch/index.ts",
|
||||
"type": "component",
|
||||
"exclude": false,
|
||||
"mode": [
|
||||
"mobile-first",
|
||||
"mobile",
|
||||
"pc"
|
||||
]
|
||||
},
|
||||
"SwitchMobile": {
|
||||
"path": "openinula/src/switch/src/mobile.jsx",
|
||||
"type": "template",
|
||||
"exclude": false
|
||||
},
|
||||
"SwitchMobileFirst": {
|
||||
"path": "openinula/src/switch/src/mobile-first.jsx",
|
||||
"type": "template",
|
||||
"exclude": false
|
||||
},
|
||||
"SwitchPc": {
|
||||
"path": "openinula/src/switch/src/pc.jsx",
|
||||
"type": "template",
|
||||
"exclude": false
|
||||
}
|
||||
}
|
|
@ -11,6 +11,10 @@
|
|||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@opentiny/openinula-common": "workspace:~",
|
||||
"@opentiny/openinula-alert": "workspace:~"
|
||||
"@opentiny/openinula-alert": "workspace:~",
|
||||
"@opentiny/openinula-badge": "workspace:~",
|
||||
"@opentiny/openinula-button": "workspace:~",
|
||||
"@opentiny/openinula-icon": "workspace:~",
|
||||
"@opentiny/openinula-switch": "workspace:~"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import Alert from '@opentiny/openinula-alert/src/pc'
|
||||
import Badge from '@opentiny/openinula-badge/src/pc'
|
||||
import Button from '@opentiny/openinula-button/src/pc'
|
||||
import Icon from '@opentiny/openinula-icon/src/pc'
|
||||
import Switch from '@opentiny/openinula-switch/src/pc'
|
||||
|
||||
const components = [Alert, Badge, Button, Icon, Switch]
|
||||
|
||||
export const version = '1.0.0'
|
||||
|
||||
export { Alert, Badge, Button, Icon, Switch }
|
||||
|
||||
export default {
|
||||
Alert,
|
||||
Badge,
|
||||
Button,
|
||||
Icon,
|
||||
Switch
|
||||
} as any
|
|
@ -0,0 +1,3 @@
|
|||
import Badge from './src'
|
||||
|
||||
export default Badge
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "@opentiny/openinula-badge",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@opentiny/vue-renderless": "workspace:~",
|
||||
"@opentiny/openinula-common": "workspace:~",
|
||||
"@opentiny/openinula-icon": "workspace:~",
|
||||
"@opentiny/vue-theme": "workspace:~",
|
||||
"@opentiny/vue-theme-mobile": "workspace:~"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import pc from './pc'
|
||||
import mobile from './mobile'
|
||||
|
||||
export default function (props) {
|
||||
const { tiny_mode = 'pc' } = props
|
||||
|
||||
const S = {
|
||||
pc,
|
||||
mobile
|
||||
}[tiny_mode]
|
||||
|
||||
return S(props)
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
import { useSetup, useVm, vc, If, Slot } from '@opentiny/openinula-common'
|
||||
import { api, renderless } from '@opentiny/vue-renderless/badge/vue'
|
||||
import '@opentiny/vue-theme-mobile/badge/index.less'
|
||||
|
||||
export default function (props) {
|
||||
const {
|
||||
isDot = false,
|
||||
isFixed = true,
|
||||
isMini = false,
|
||||
max,
|
||||
value,
|
||||
modelValue,
|
||||
href,
|
||||
target,
|
||||
hidden = false,
|
||||
type,
|
||||
badgeClass,
|
||||
offset = [0, 0]
|
||||
} = props
|
||||
|
||||
const defaultProps = Object.assign(
|
||||
{
|
||||
isDot,
|
||||
isFixed,
|
||||
isMini,
|
||||
hidden,
|
||||
offset
|
||||
},
|
||||
props
|
||||
)
|
||||
|
||||
const { ref, parent, current: vm } = useVm()
|
||||
|
||||
const { state } = useSetup({
|
||||
props: defaultProps,
|
||||
api,
|
||||
renderless,
|
||||
vm,
|
||||
parent
|
||||
})
|
||||
|
||||
return (
|
||||
<div ref={ref} className="tiny-mobile-badge">
|
||||
<Slot parent_children={props.children}></Slot>
|
||||
<If v-if={!hidden && (value > 0 || isDot)}>
|
||||
<div
|
||||
className={vc([
|
||||
'tiny-mobile-badge__content',
|
||||
{
|
||||
'is-dot': isDot,
|
||||
'is-fixed': isFixed,
|
||||
'is-mini': isMini
|
||||
},
|
||||
value < 10 ? 'is-circle' : '',
|
||||
type ? 'tiny-mobile-badge--' + type : ''
|
||||
])}>
|
||||
<If v-if={!isDot}>
|
||||
<span>
|
||||
<Slot name="content" slots={props.slots}>
|
||||
<a href={state.href} target={target} rel="noopener noreferrer" className="tiny-mobile-badge__link">
|
||||
{state.content}
|
||||
</a>
|
||||
</Slot>
|
||||
</span>
|
||||
</If>
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import { useSetup, useVm, vc, If, Slot } from '@opentiny/openinula-common'
|
||||
import { api, renderless } from '@opentiny/vue-renderless/badge/vue'
|
||||
import '@opentiny/vue-theme/badge/index.less'
|
||||
|
||||
export default function (props) {
|
||||
const {
|
||||
isDot = false,
|
||||
isFixed = true,
|
||||
isMini = false,
|
||||
max,
|
||||
value,
|
||||
modelValue,
|
||||
href,
|
||||
target,
|
||||
hidden = false,
|
||||
type,
|
||||
badgeClass,
|
||||
offset = [0, 0],
|
||||
data
|
||||
} = props
|
||||
|
||||
const defaultProps = Object.assign(
|
||||
{
|
||||
isDot,
|
||||
isFixed,
|
||||
isMini,
|
||||
hidden,
|
||||
offset
|
||||
},
|
||||
props
|
||||
)
|
||||
|
||||
const { ref, parent, current: vm } = useVm()
|
||||
|
||||
const { state } = useSetup({
|
||||
props: defaultProps,
|
||||
api,
|
||||
renderless,
|
||||
vm,
|
||||
parent
|
||||
})
|
||||
|
||||
return (
|
||||
<div ref={ref} className="tiny-badge__wrapper">
|
||||
<If v-if={data}>
|
||||
<span>{data}</span>
|
||||
</If>
|
||||
<Slot v-if={!data} parent_children={props.children}></Slot>
|
||||
<If v-if={!hidden && (state.content || state.content === 0 || isDot)}>
|
||||
<div
|
||||
className={vc([
|
||||
'tiny-badge',
|
||||
isDot ? 'tiny-badge--default' : '',
|
||||
state.isOverstep ? 'tiny-badge--max' : '',
|
||||
type ? 'tiny-badge--' + type : '',
|
||||
badgeClass || ''
|
||||
])}
|
||||
style={{
|
||||
transform: `translate(
|
||||
${offset[0]}${typeof offset[0] === 'number' ? 'px' : ''},
|
||||
${offset[1]}${typeof offset[1] === 'number' ? 'px' : ''}
|
||||
)`
|
||||
}}>
|
||||
<Slot name="content" slots={props.slots}>
|
||||
<If v-if={state.href}>
|
||||
<a href={state.href} target={target} rel="noopener noreferrer">
|
||||
{state.content}
|
||||
</a>
|
||||
</If>
|
||||
<If v-if={!state.href}>
|
||||
<span className="tiny-badge__content-text">{state.content}</span>
|
||||
</If>
|
||||
</Slot>
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import Button from './src'
|
||||
|
||||
export default Button
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "@opentiny/openinula-button",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@opentiny/vue-renderless": "workspace:~",
|
||||
"@opentiny/openinula-common": "workspace:~",
|
||||
"@opentiny/openinula-icon": "workspace:~",
|
||||
"@opentiny/vue-theme": "workspace:~",
|
||||
"@opentiny/vue-theme-mobile": "workspace:~"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import pc from './pc'
|
||||
import mobile from './mobile'
|
||||
import mobileFirst from './mobile-first'
|
||||
|
||||
export default function (props) {
|
||||
const { tiny_mode = 'pc' } = props
|
||||
|
||||
const S = {
|
||||
pc,
|
||||
mobile,
|
||||
'mobile-first': mobileFirst
|
||||
}[tiny_mode]
|
||||
|
||||
return S(props)
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
import { renderless, api } from '@opentiny/vue-renderless/button/vue'
|
||||
import { useSetup, vc, If, Component, Slot, useVm } from '@opentiny/openinula-common'
|
||||
import { IconLoading } from '@opentiny/openinula-icon'
|
||||
import { classes } from './token'
|
||||
|
||||
const define_props = [
|
||||
'children',
|
||||
'text',
|
||||
'loading',
|
||||
'autofocus',
|
||||
'plain',
|
||||
'round',
|
||||
'circle',
|
||||
'icon',
|
||||
'size',
|
||||
'type',
|
||||
'nativeType',
|
||||
'resetTime',
|
||||
/^on/
|
||||
]
|
||||
|
||||
export default function Button(props) {
|
||||
const {
|
||||
loading,
|
||||
autofocus,
|
||||
size,
|
||||
icon,
|
||||
round,
|
||||
circle,
|
||||
href,
|
||||
buttonClass,
|
||||
tabindex,
|
||||
text,
|
||||
type,
|
||||
nativeType = 'button',
|
||||
resetTime = 1000
|
||||
} = props
|
||||
|
||||
const defaultProps = Object.assign({
|
||||
nativeType,
|
||||
resetTime,
|
||||
}, props)
|
||||
|
||||
const {
|
||||
ref,
|
||||
parent,
|
||||
current: vm
|
||||
} = useVm()
|
||||
|
||||
const {
|
||||
handleClick,
|
||||
state,
|
||||
a,
|
||||
m,
|
||||
gcls,
|
||||
} = useSetup({
|
||||
api,
|
||||
renderless,
|
||||
props: defaultProps,
|
||||
classes,
|
||||
ref,
|
||||
parent,
|
||||
vm
|
||||
})
|
||||
|
||||
const $attrs = a(props, define_props, false)
|
||||
|
||||
return (
|
||||
<button
|
||||
data-tag="tiny-button"
|
||||
onClick={handleClick}
|
||||
disabled={state.buttonDisabled || loading}
|
||||
autoFocus={autofocus}
|
||||
type={nativeType}
|
||||
className={m(
|
||||
gcls('button'),
|
||||
gcls(`size-${size || 'default'}`),
|
||||
gcls(
|
||||
`type-${type || 'default'}${icon ? '-icon' : state.plain ? '-plain' : ''}
|
||||
${state.buttonDisabled ? '-disabled' : ''}`
|
||||
),
|
||||
gcls(round ? 'is-round' : 'no-round'),
|
||||
gcls(circle ? 'is-circle' : 'no-circle'),
|
||||
gcls({ 'is-border': circle || !(type === 'text' || icon) }),
|
||||
gcls({ 'button-link': href }),
|
||||
buttonClass
|
||||
)}
|
||||
tabIndex={tabindex}
|
||||
{...a($attrs, ['class', 'style'], true)}
|
||||
>
|
||||
<If v-if={loading}>
|
||||
<IconLoading className={gcls('loading-svg')} />
|
||||
</If>
|
||||
<Component
|
||||
v-if={icon && !loading}
|
||||
is={icon}
|
||||
className={vc([
|
||||
gcls('button-icon'),
|
||||
gcls(`button-icon-${state.buttonDisabled ? 'disabled' : 'default'}`)
|
||||
])}
|
||||
/>
|
||||
<Slot slots={props.slots} parent_children={props.children}>
|
||||
<span>{text}</span>
|
||||
</Slot>
|
||||
</button>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
import { renderless, api } from '@opentiny/vue-renderless/button/vue'
|
||||
import { useSetup, vc, If, Component, Slot, useVm } from '@opentiny/openinula-common'
|
||||
import { IconLoading } from '@opentiny/openinula-icon'
|
||||
import '@opentiny/vue-theme-mobile/button/index.less'
|
||||
|
||||
const define_props = [
|
||||
'children',
|
||||
'text',
|
||||
'loading',
|
||||
'autofocus',
|
||||
'plain',
|
||||
'round',
|
||||
'circle',
|
||||
'icon',
|
||||
'size',
|
||||
'type',
|
||||
'nativeType',
|
||||
'resetTime',
|
||||
/^on/
|
||||
]
|
||||
|
||||
export default function Button(props) {
|
||||
const {
|
||||
text,
|
||||
loading,
|
||||
round,
|
||||
icon,
|
||||
size,
|
||||
type = 'default',
|
||||
nativeType = 'button',
|
||||
resetTime = 1000
|
||||
} = props
|
||||
|
||||
const defaultProps = Object.assign({
|
||||
type,
|
||||
nativeType,
|
||||
resetTime
|
||||
}, props)
|
||||
|
||||
const {
|
||||
ref,
|
||||
parent,
|
||||
current: vm
|
||||
} = useVm()
|
||||
|
||||
const {
|
||||
handleClick,
|
||||
state,
|
||||
a
|
||||
} = useSetup({
|
||||
props: defaultProps,
|
||||
renderless,
|
||||
api,
|
||||
parent,
|
||||
vm
|
||||
})
|
||||
|
||||
const $attrs = a(props, define_props, false)
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
onClick={handleClick}
|
||||
disabled={state.buttonDisabled || loading}
|
||||
type={nativeType}
|
||||
className={vc([
|
||||
'tiny-mobile-button',
|
||||
type ? 'tiny-mobile-button--' + type : '',
|
||||
size ? 'tiny-mobile-button--' + size : '',
|
||||
{
|
||||
'is-disabled': state.buttonDisabled,
|
||||
'is-loading': loading,
|
||||
'is-plain': state.plain,
|
||||
'is-round': round
|
||||
}
|
||||
])}
|
||||
{...a($attrs, ['class', 'style'], true)}
|
||||
>
|
||||
<If v-if={loading}>
|
||||
<IconLoading class='tiny-icon-loading' />
|
||||
</If>
|
||||
<Component v-if={icon && !loading} is={icon} class='tiny-icon is-icon' />
|
||||
<Slot slots={props.slots} parent_children={props.children}>
|
||||
<span style={{ marginLeft: text && (icon || loading) ? '4px' : 0 }}>{text}</span>
|
||||
</Slot>
|
||||
</button>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
import { renderless, api } from '@opentiny/vue-renderless/button/vue'
|
||||
import { useSetup, If, Component, vc, useVm } from '@opentiny/openinula-common'
|
||||
import { IconLoading } from '@opentiny/openinula-icon'
|
||||
import '@opentiny/vue-theme/button/index.less'
|
||||
|
||||
const define_props = [
|
||||
'children',
|
||||
'text',
|
||||
'loading',
|
||||
'autofocus',
|
||||
'plain',
|
||||
'round',
|
||||
'circle',
|
||||
'icon',
|
||||
'size',
|
||||
'type',
|
||||
'nativeType',
|
||||
'resetTime',
|
||||
/^on/
|
||||
]
|
||||
|
||||
export default function Button(props) {
|
||||
const {
|
||||
children,
|
||||
text,
|
||||
loading,
|
||||
autofocus,
|
||||
round,
|
||||
circle,
|
||||
icon,
|
||||
size,
|
||||
tabindex,
|
||||
type = 'default',
|
||||
nativeType = 'button',
|
||||
resetTime = 1000
|
||||
} = props
|
||||
|
||||
const defaultProps = Object.assign({
|
||||
type,
|
||||
nativeType,
|
||||
resetTime
|
||||
}, props)
|
||||
|
||||
const {
|
||||
ref,
|
||||
parent,
|
||||
current: vm
|
||||
} = useVm()
|
||||
|
||||
const {
|
||||
handleClick,
|
||||
state,
|
||||
a
|
||||
} = useSetup({
|
||||
props: defaultProps,
|
||||
renderless,
|
||||
api,
|
||||
vm,
|
||||
parent
|
||||
})
|
||||
|
||||
const $attrs = a(props, define_props, false)
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
className={vc([
|
||||
'tiny-button',
|
||||
type ? 'tiny-button--' + type : '',
|
||||
size ? 'tiny-button--' + size : '',
|
||||
{
|
||||
'is-disabled': state.buttonDisabled,
|
||||
'is-loading': loading,
|
||||
'is-plain': state.plain,
|
||||
'is-round': round,
|
||||
'is-circle': circle,
|
||||
'is-icon': icon && !loading && (text || $slots.default),
|
||||
'is-only-icon': icon && !loading && !(text || $slots.default)
|
||||
}
|
||||
])}
|
||||
onClick={handleClick}
|
||||
disabled={state.buttonDisabled || loading}
|
||||
autoFocus={autofocus}
|
||||
type={nativeType}
|
||||
tabIndex={tabindex}
|
||||
{...a($attrs, ['class', 'style'], true)}
|
||||
>
|
||||
<If v-if={loading}>
|
||||
<IconLoading className="tiny-icon-loading tiny-svg-size" />
|
||||
</If>
|
||||
<Component
|
||||
v-if={icon && !loading}
|
||||
is={icon}
|
||||
className={(text || children) ? 'is-text' : ''}
|
||||
/>
|
||||
<span>{children || text}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
export const classes = {
|
||||
'button':
|
||||
'inline-block sm:max-w-[9rem] text-center overflow-hidden overflow-ellipsis whitespace-nowrap transition-button duration-300 delay-[0ms]',
|
||||
'size-default': 'h-10 text-sm sm:h-7 sm:text-xs',
|
||||
'size-medium': 'h-10 text-sm sm:h-8 sm:text-xs',
|
||||
'size-small': 'h-8 text-sm sm:h-7 sm:text-xs',
|
||||
'size-mini': 'h-7 sm:h-6 sm:text-xs',
|
||||
'type-default':
|
||||
'text-black border-color-border hover:border-color-border-hover active:border-color-border-active sm:cursor-pointer',
|
||||
'type-primary':
|
||||
'text-white border-color-brand bg-color-brand hover:border-color-brand-hover hover:bg-color-brand-hover active:border-color-brand-active active:bg-color-brand-active sm:cursor-pointer',
|
||||
'type-success':
|
||||
'text-white border-color-success bg-color-success hover:border-color-success-hover hover:bg-color-success-hover active:border-color-success-active active:bg-color-success-active sm:cursor-pointer',
|
||||
'type-info':
|
||||
'text-white border-color-info-secondary bg-color-info-secondary hover:border-color-info-secondary-hover hover:bg-color-info-secondary-hover active:border-color-info-secondary-active active:bg-color-info-secondary-active sm:cursor-pointer',
|
||||
'type-warning':
|
||||
'text-white border-color-warning bg-color-warning hover:border-color-warning-hover hover:bg-color-warning-hover active:border-color-warning-active active:bg-color-warning-active sm:cursor-pointer',
|
||||
'type-danger':
|
||||
'text-white border-color-error bg-color-error hover:border-color-error-hover hover:bg-color-error-hover active:border-color-error-active active:bg-color-error-active sm:cursor-pointer',
|
||||
'type-text':
|
||||
'border-none bg-transparent cursor-pointer text-color-text-placeholder active:text-color-text-primary sm:hover:text-color-text-primary sm:active:!text-color-brand-active',
|
||||
'type-default-disabled': 'text-color-text-disabled bg-color-bg-3 border-transparent hover:cursor-not-allowed',
|
||||
'type-primary-disabled': 'text-white bg-color-brand-disabled border-transparent hover:cursor-not-allowed',
|
||||
'type-success-disabled': 'text-white bg-color-success-disabled border-transparent hover:cursor-not-allowed',
|
||||
'type-info-disabled': 'text-white bg-color-info-secondary-disabled border-transparent hover:cursor-not-allowed',
|
||||
'type-warning-disabled': 'text-white bg-color-alert-disabled border-transparent hover:cursor-not-allowed',
|
||||
'type-danger-disabled': 'text-white bg-color-error-disabled border-transparent hover:cursor-not-allowed',
|
||||
'type-text-disabled': 'text-color-text-disabled hover:cursor-not-allowed',
|
||||
'type-default-plain':
|
||||
'text-black border-color-border hover:border-color-border-hover active:border-color-border-active sm:cursor-pointer',
|
||||
'type-primary-plain':
|
||||
'text-color-brand border-color-brand hover:text-color-brand-hover hover:border-color-brand-hover active:text-color-brand-active active:border-color-brand-active bg-white sm:cursor-pointer',
|
||||
'type-success-plain':
|
||||
'text-color-success border-color-success hover:text-color-success-hover hover:border-color-success-hover active:text-color-success-active active:border-color-success-active bg-white sm:cursor-pointer',
|
||||
'type-info-plain':
|
||||
'text-color-info-secondary border-color-info-secondary hover:text-color-info-secondary-hover hover:border-color-info-secondary-hover active:text-color-info-secondary-active active:border-color-info-secondary-active bg-white sm:cursor-pointer',
|
||||
'type-warning-plain':
|
||||
'text-color-warning border-color-warning hover:text-color-warning-hover hover:border-color-warning-hover active:text-color-warning-active active:border-color-warning-active bg-white sm:cursor-pointer',
|
||||
'type-danger-plain':
|
||||
'text-color-error border-color-error hover:text-color-error-hover hover:border-color-error-hover active:text-color-error-active active:border-color-error-active bg-white sm:cursor-pointer',
|
||||
'type-text-plain': 'text-color-brand hover:text-color-brand-hover active:text-color-brand-active',
|
||||
'type-default-plain-disabled':
|
||||
'text-color-text-disabled bg-white border-color-text-disabled hover:cursor-not-allowed',
|
||||
'type-primary-plain-disabled':
|
||||
'text-color-brand-disabled bg-white border-color-brand-disabled hover:cursor-not-allowed',
|
||||
'type-success-plain-disabled':
|
||||
'text-color-success-disabled bg-white border-color-success-disabled hover:cursor-not-allowed',
|
||||
'type-info-plain-disabled':
|
||||
'text-color-info-secondary-disabled bg-white border-color-info-secondary-disabled hover:cursor-not-allowed',
|
||||
'type-warning-plain-disabled':
|
||||
'text-color-alert-disabled bg-white border-color-alert-disabled hover:cursor-not-allowed',
|
||||
'type-danger-plain-disabled':
|
||||
'text-color-error-disabled bg-white border-color-error-disabled hover:cursor-not-allowed',
|
||||
'type-text-plain-disabled': 'text-color-text-disabled hover:cursor-not-allowed',
|
||||
'no-round': 'rounded-sm',
|
||||
'is-round': 'rounded-full',
|
||||
'is-border': 'border-0.5 sm:border',
|
||||
'no-circle': 'sm:min-w-[4.5rem] pl-3 pr-3',
|
||||
'is-circle': 'sm:min-w-[0.4375rem] sm:rounded-full sm:pl-2 sm:pr-2',
|
||||
'button-icon': '-mt-0.5 sm:text-base fill-current',
|
||||
'button-icon-default': 'text-color-icon-primary hover:text-color-icon-hover active:text-color-icon-active',
|
||||
'button-icon-disabled': 'text-color-icon-disabled hover:cursor-not-allowed',
|
||||
'loading-svg': 'animate-spin-2 mr-1 fill-current -inset-0.5',
|
||||
'button-link':
|
||||
'text-color-link hover:text-color-link-hover active:color-link-hover active:hover:text-color-link-hover sm:hover:text-color-link-hover'
|
||||
}
|
|
@ -3,10 +3,7 @@ import { compWhiteList } from './virtual-comp'
|
|||
|
||||
export function getFiberByDom(dom) {
|
||||
const key = Object.keys(dom).find((key) => {
|
||||
return (
|
||||
key.startsWith('__openinulaFiber$') || // openinula 17+
|
||||
key.startsWith('__openinulaInternalInstance$')
|
||||
) // openinula <17
|
||||
return key.startsWith('_inula_VNode_')
|
||||
})
|
||||
|
||||
return dom[key]
|
||||
|
@ -46,13 +43,13 @@ export function creatFiberCombine(fiber) {
|
|||
const children = []
|
||||
|
||||
traverseFiber(fiber.child, [
|
||||
(fiber) => {
|
||||
if (typeof fiber.type === 'string' && fiber.stateNode.getAttribute('v_ref')) {
|
||||
refs[fiber.stateNode.getAttribute('v_ref')] = fiber.stateNode
|
||||
} else if (fiber.memoizedProps.v_ref) {
|
||||
refs[fiber.memoizedProps.v_ref] = fiber
|
||||
}
|
||||
},
|
||||
// (fiber) => {
|
||||
// if (typeof fiber.type === 'string' && fiber.stateNode.getAttribute('v_ref')) {
|
||||
// refs[fiber.stateNode.getAttribute('v_ref')] = fiber.stateNode
|
||||
// } else if (fiber.memoizedProps.v_ref) {
|
||||
// refs[fiber.memoizedProps.v_ref] = fiber
|
||||
// }
|
||||
// },
|
||||
(fiber) => {
|
||||
if (fiber.type && typeof fiber.type !== 'string') {
|
||||
children.push(fiber)
|
||||
|
@ -74,8 +71,9 @@ export function useFiber() {
|
|||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
const current_fiber = getFiberByDom(ref.current)
|
||||
setParent(getParentFiber(current_fiber.return))
|
||||
setCurrent(current_fiber.return)
|
||||
|
||||
setParent(getParentFiber(current_fiber))
|
||||
setCurrent(current_fiber)
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
|
|
@ -99,10 +99,10 @@ export const vc = VueClassName
|
|||
|
||||
export const getElementCssClass = (classes = {}, key) => {
|
||||
if (typeof key === 'object') {
|
||||
const keys = Array.isArray(key) ? key : Object.keys(key).filter((k) => key[k])
|
||||
const keys = Object.keys(key)
|
||||
let cls = ''
|
||||
keys.forEach((k) => {
|
||||
if (classes[k]) cls += `${classes[k]} `
|
||||
if (key[k] && classes[k]) cls += `${classes[k]} `
|
||||
})
|
||||
return cls
|
||||
} else {
|
||||
|
|
|
@ -100,7 +100,7 @@ export function useVm() {
|
|||
}
|
||||
return {
|
||||
ref,
|
||||
current: current.fiber && createVmProxy(current),
|
||||
parent: parent.fiber && createVmProxy(parent)
|
||||
current: current && createVmProxy(current),
|
||||
parent: parent && createVmProxy(parent)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,9 +11,9 @@ const collectRefs = (rootEl, $children) => {
|
|||
const rootFiber = getFiberByDom(rootEl)
|
||||
// 收集普通元素 ref
|
||||
traverseFiber(rootFiber, (fiber) => {
|
||||
if (typeof fiber.type === 'string' && fiber.stateNode.getAttribute('v-ref')) {
|
||||
refs[fiber.stateNode.getAttribute('v-ref')] = fiber.stateNode
|
||||
}
|
||||
// if (typeof fiber.type === 'string' && fiber.stateNode.getAttribute('v-ref')) {
|
||||
// refs[fiber.stateNode.getAttribute('v-ref')] = fiber.stateNode
|
||||
// }
|
||||
})
|
||||
// 收集组件元素 ref
|
||||
$children.forEach((child) => {
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import Switch from './src/index'
|
||||
import '@opentiny/vue-theme/switch/index.less'
|
||||
import { version } from './package.json'
|
||||
|
||||
Switch.version = version
|
||||
|
||||
export default Switch
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "@opentiny/openinula-switch",
|
||||
"version": "3.0.0",
|
||||
"description": "",
|
||||
"main": "lib/index.js",
|
||||
"module": "index.ts",
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm -w build:ui-openinula $npm_package_name",
|
||||
"//postversion": "pnpm build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@opentiny-internal/vue-test-utils": "workspace:*",
|
||||
"vitest": "^0.31.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opentiny/vue-renderless": "workspace:~",
|
||||
"@opentiny/openinula-common": "workspace:~",
|
||||
"@opentiny/openinula-icon": "workspace:~",
|
||||
"@opentiny/vue-theme": "workspace:~",
|
||||
"@opentiny/vue-theme-mobile": "workspace:~"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import pc from './pc'
|
||||
import mobile from './mobile'
|
||||
|
||||
// import mobileFirst from './mobile-first'
|
||||
|
||||
const $constants = {
|
||||
PC_PREFIXCLS: 'tiny-switch',
|
||||
MOBILE_PREFIXCLS: 'tiny-mobile-switch',
|
||||
Mode: 'pc',
|
||||
prefixcls(mode) {
|
||||
return mode === this.Mode ? this.PC_PREFIXCLS : this.MOBILE_PREFIXCLS
|
||||
}
|
||||
}
|
||||
|
||||
export default function (props) {
|
||||
const { tiny_mode = 'pc', _constants = $constants } = props
|
||||
|
||||
const defaultProps = Object.assign(
|
||||
{
|
||||
_constants,
|
||||
tiny_mode
|
||||
},
|
||||
props
|
||||
)
|
||||
const S = {
|
||||
pc,
|
||||
mobile
|
||||
}[tiny_mode]
|
||||
|
||||
return S && S(defaultProps)
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
import { renderless, api } from '@opentiny/vue-renderless/switch/vue'
|
||||
import { vc, useSetup, useVm } from '@opentiny/openinula-common'
|
||||
import '@opentiny/vue-theme-mobile/switch/index.less'
|
||||
|
||||
const $constants = {}
|
||||
|
||||
export default function Switch(props) {
|
||||
const {
|
||||
_constants,
|
||||
disabled = false,
|
||||
showText,
|
||||
falseColor,
|
||||
falseValue = false,
|
||||
mini = false,
|
||||
modelValue = false,
|
||||
size,
|
||||
tabindex = '1',
|
||||
trueColor,
|
||||
trueValue = true,
|
||||
beforeChange,
|
||||
displayOnly = false
|
||||
} = props
|
||||
|
||||
const defaultProps = Object.assign(
|
||||
{
|
||||
disabled,
|
||||
falseValue,
|
||||
mini,
|
||||
modelValue,
|
||||
tabindex,
|
||||
trueValue,
|
||||
displayOnly
|
||||
},
|
||||
props
|
||||
)
|
||||
|
||||
const { ref, current: vm, parent } = useVm()
|
||||
|
||||
const { toggle, state } = useSetup({
|
||||
props,
|
||||
renderless,
|
||||
api,
|
||||
constants: _constants,
|
||||
vm,
|
||||
parent
|
||||
})
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
{' '}
|
||||
<span className={vc(state.wrapClasses)} disabled={disabled} onClick={toggle} onKeydown={toggle}>
|
||||
<div className="tiny-mobile-switch-loading">
|
||||
{' '}
|
||||
<div className="tiny-mobile-switch-loading-inner"></div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import { useSetup, useVm, vc, If, Slot } from '@opentiny/openinula-common'
|
||||
import { api, renderless } from '@opentiny/vue-renderless/switch/vue'
|
||||
import '@opentiny/vue-theme/switch/index.less'
|
||||
|
||||
export default function (props) {
|
||||
const {
|
||||
_constants,
|
||||
disabled = false,
|
||||
showText,
|
||||
falseColor,
|
||||
falseValue = false,
|
||||
mini = false,
|
||||
modelValue = false,
|
||||
size,
|
||||
tabindex = '1',
|
||||
trueColor,
|
||||
trueValue = true,
|
||||
beforeChange,
|
||||
displayOnly = false
|
||||
} = props
|
||||
|
||||
const defaultProps = Object.assign(
|
||||
{
|
||||
disabled,
|
||||
falseValue,
|
||||
mini,
|
||||
modelValue,
|
||||
tabindex,
|
||||
trueValue,
|
||||
displayOnly
|
||||
},
|
||||
props
|
||||
)
|
||||
|
||||
const { ref, parent, current: vm } = useVm()
|
||||
|
||||
const { state, toggle } = useSetup({
|
||||
props: defaultProps,
|
||||
api,
|
||||
renderless,
|
||||
vm,
|
||||
parent,
|
||||
constants: _constants
|
||||
})
|
||||
|
||||
return (
|
||||
<span
|
||||
ref={ref}
|
||||
className={vc([state.wrapClasses, state.showText ? 'tiny-switch__text' : ''])}
|
||||
tabIndex={tabindex}
|
||||
onClick={toggle}
|
||||
onKeyDown={(e) => {
|
||||
e.key === 'Enter' && toggle(e)
|
||||
}}>
|
||||
<span className={state.innerClasses}>
|
||||
<If v-if={!mini && state.showText}>
|
||||
<Slot v-if={state.currentValue === trueValue} name="open" slots={props.slots}>
|
||||
ON
|
||||
</Slot>
|
||||
<Slot v-if={state.currentValue === falseValue} name="close" slots={props.slots}>
|
||||
OFF
|
||||
</Slot>
|
||||
</If>
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue