feat: init project US2022111700422

Signed-off-by:  <zenglingka@>

Match-id-9ac6cf2948c4663b3ce91406b4ddbc6045cbd57e
This commit is contained in:
OpenTiny 2023-02-16 19:11:16 +08:00
parent c2ccf682cb
commit 0527f9519a
3567 changed files with 187263 additions and 1 deletions

View File

@ -0,0 +1,23 @@
version: 0.1.0
name: tiny-opentiny-vue
language: nodejs
# 构建工具
dependencies:
base:
nodejs: best
# 构建机器
machine:
standard:
euler:
- default
# 构建脚本
scripts:
- sh build.sh
# 构建产物
artifacts:
npm_deploy:
- config_path: ./dist/vue/package.json

9
.codecheck/check.yml Normal file
View File

@ -0,0 +1,9 @@
version: 2.0
steps:
pre_codecheck:
- checkout
tool_params:
secsolar:
source_dir: ./

2
.eslintignore Normal file
View File

@ -0,0 +1,2 @@
dist
runtime

94
.eslintrc Normal file
View File

@ -0,0 +1,94 @@
{
"root": true,
"env": {
"es6": true,
"browser": true,
"node": true,
"jest": true
},
"extends": ["eslint:recommended", "plugin:vue/vue3-essential"],
"parserOptions": {
"parser": ["vue-eslint-parser", "@babel/eslint-parser"],
"requireConfigFile": false,
"sourceType": "module",
"ecmaVersion": "latest",
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"no-debugger": "off",
"no-var": "error",
"no-tabs": "error",
"no-trailing-spaces": "error",
"no-mixed-spaces-and-tabs": "error",
"no-undef": "error",
"no-extra-semi": "error",
"no-empty": "error",
"no-console": "off",
"semi": [2, "never"],
"max-len": [
"warn",
{
"code": 160
}
],
"function-paren-newline": ["off"],
"object-property-newline": [
"warn",
{
"allowAllPropertiesOnSameLine": true
}
],
"newline-per-chained-call": [
"warn",
{
"ignoreChainWithDepth": 4
}
],
"comma-dangle": "off",
"semi-style": ["warn", "last"],
"max-lines": ["error", 2400],
"max-lines-per-function": ["error", 200],
"complexity": ["error", 26],
"max-depth": ["warn", 4],
"max-nested-callbacks": ["error", 4],
"no-multi-assign": "off",
"no-undef-init": "warn",
"no-shadow": "off",
"max-params": ["warn", 5],
"no-param-reassign": "off",
"prefer-rest-params": "off",
"prefer-arrow-callback": "error",
"arrow-body-style": ["warn", "as-needed"],
"no-this-before-super": "error",
"quotes": ["warn", "single"],
"prefer-template": "off",
"no-multi-str": "warn",
"object-shorthand": "warn",
"dot-notation": "error",
"accessor-pairs": "error",
"no-prototype-builtins": "error",
"guard-for-in": "error",
"eqeqeq": "off",
"no-fallthrough": "error",
"no-case-declarations": "error",
"no-unsafe-finally": "error",
"no-eval": "error",
"no-with": "error",
"no-implicit-coercion": [
"error",
{
"allow": ["!!", "~"]
}
],
"vue/multi-word-component-names": "off",
"vue/valid-v-slot": "off",
"vue/no-deprecated-v-on-native-modifier": "off",
"vue/no-reserved-component-names": "off",
"vue/no-deprecated-dollar-listeners-api": "off",
"vue/no-deprecated-slot-attribute": "off",
"vue/no-use-computed-property-like-method": "off",
"vue/no-mutating-props": "off"
}
}

61
.github/ISSUE_TEMPLATE/bug-report.yml vendored Normal file
View File

@ -0,0 +1,61 @@
name: '🐛 Bug report'
description: Create a report to help us improve Tiny Vue
title: '🐛 [Bug]: '
labels: ['🐛 bug']
body:
- type: markdown
attributes:
value: |
Please fill out the following carefully in order to better fix the problem.
- type: input
id: tiny-vue-version
attributes:
label: Version
description: |
### **Check if the issue is reproducible with the latest stable version.**
You can use the command `npm ls @opentiny/vue` to view it
placeholder: latest
validations:
required: true
- type: input
id: vue-version
attributes:
label: Vue Version
placeholder: latest
validations:
required: true
- type: textarea
id: minimal-repo
attributes:
label: Link to minimal reproduction
description: |
**Provide a streamlined CodePen / CodeSandbox or GitHub repository link as much as possible. Please don't fill in a link randomly, it will only close your issue directly.**
placeholder: Please Input
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Step to reproduce
description: |
**After the replay is turned on, what actions do we need to perform to make the bug appear? Simple and clear steps can help us locate the problem more quickly. Please clearly describe the steps of reproducing the issue. Issues without clear reproducing steps will not be repaired. If the issue marked with 'need reproduction' does not provide relevant steps within 7 days, it will be closed directly.**
placeholder: Please Input
validations:
required: true
- type: textarea
id: expected
attributes:
label: What is expected
placeholder: Please Input
- type: textarea
id: actually
attributes:
label: What is actually happening
placeholder: Please Input
- type: textarea
id: additional-comments
attributes:
label: Any additional comments (optional)
description: |
**Some background / context of how you ran into this bug.**
placeholder: Please Input

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Questions or need help
url: https://github.com/opentiny/ui-vue/discussions
about: Add this WeChat(opentiny), we will invite you to the WeChat discussion group later.

View File

@ -0,0 +1,23 @@
name: ✨ Feature Request
description: Propose new features to @opentiny/vue to improve it.
title: '✨ [Feature]: '
labels: ['✨ feature']
body:
- type: textarea
id: feature-solve
attributes:
label: What problem does this feature solve
description: |
Explain your use case, context, and rationale behind this feature request. More importantly, what is the end user experience you are trying to build that led to the need for this feature?
placeholder: Please Input
validations:
required: true
- type: textarea
id: feature-api
attributes:
label: What does the proposed API look like
description: |
Describe how you propose to solve the problem and provide code samples of how the API would work once implemented. Note that you can use Markdown to format your code blocks.
placeholder: Please Input
validations:
required: true

41
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,41 @@
# PR
## PR Checklist
Please check if your PR fulfills the following requirements:
- [ ] The commit message follows our [Commit Message Guidelines](https://github.com/opentiny/ui-vue/blob/main/CONTRIBUTING.md)
- [ ] Tests for the changes have been added (for bug fixes / features)
- [ ] Docs have been added / updated (for bug fixes / features)
## PR Type
What kind of change does this PR introduce?
<!-- Please check the one that applies to this PR using "x". -->
- [ ] Bugfix
- [ ] Feature
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] CI related changes
- [ ] Documentation content changes
- [ ] Other... Please describe:
## What is the current behavior?
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->
Issue Number: N/A
## What is the new behavior?
## Does this PR introduce a breaking change?
- [ ] Yes
- [ ] No
<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->
## Other information

41
.gitignore vendored Normal file
View File

@ -0,0 +1,41 @@
.DS_Store
node_modules
dist/
allDist/
packages/**/runtime/
coverage/
/packages/base.js
/packages/chart.js
/packages/core.js
/packages/index.js
/packages/pc.js
/packages/mobile.js
# local env
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.history
.vscode
.cloudbuild
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.log
*.stackdump
yarn.lock
package-lock.json
tgzs
*.tgz

4
.husky/commit-msg Normal file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn commitlint --edit $1

4
.husky/pre-commit Normal file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

3
.prettierignore Normal file
View File

@ -0,0 +1,3 @@
# Except myapp folder:
dist/
public/

4
.prettierrc Normal file
View File

@ -0,0 +1,4 @@
semi: false
singleQuote: true
printWidth: 160
trailingComma: none

20
CHANGELOG.md Normal file
View File

@ -0,0 +1,20 @@
# 更新日志
## v2.0.0/v3.0.0
`2022/09/15`
### 📢破坏性变更
### ✨新特性
- Search 组件:增加 input 事件
- Select 组件:增加 scroll 事件
### 🐞缺陷修复
- Tabs 组件:修复 tab 组件套 tab 组件,内部的 tab 项会显示在外部 tab 上的bug
- PopEditor 组件:解决弹出框里的查询条件不能输入的问题
- Cascader 组件:解决点击已选中选项无法关闭选择器的问题

81
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,81 @@
# 贡献指南
很高兴你有意愿参与 TinyVue 开源项目的贡献,参与贡献的形式有很多种,你可以根据自己的特长和兴趣选择其中的一个或多个:
- 报告[新缺陷](https://github.com/opentiny/tiny-vue/issues/new?template=bug-report.yml)
- 为[已有缺陷](https://github.com/opentiny/tiny-vue/labels/bug)提供更详细的信息比如补充截图、提供更详细的复现步骤、提供最小可复现demo链接等
- 提交 Pull requests 修复文档中的错别字或让文档更清晰和完善
- 添加官方小助手微信 opentiny加入技术交流群参与讨论
当你亲自使用 TinyVue 组件库,并参与多次以上形式的贡献,对 TinyVue 逐渐熟悉之后,可以尝试做一些更有挑战的事情,比如:
- 修复缺陷,可以先从 [Good-first issue](https://github.com/opentiny/tiny-vue/labels/good%20first%20issue) 开始
- 实现新特性
- 完善单元测试
- 翻译文档
- 参与代码检视
## 提交 Issue
如果你在使用 TinyVue 组件过程中遇到问题,欢迎给我们提交 Issue提交 Issue 之前,请先仔细阅读相关的[官方文档](https://opentiny.design),确认这是一个缺陷还是尚未实现的功能。
如果是一个缺陷,创建新 Issue 时选择 [Bug report](https://github.com/opentiny/tiny-vue/issues/new?template=bug-report.yml) 模板,标题遵循 `[componentName]缺陷简述` 的格式,比如:`[select]选择框内容太长展示不下时希望能支持配置tips提示`。
报告缺陷的 Issue 主要需要填写以下信息:
- tiny-vue 和 vue 的版本号
- 缺陷的表现,可截图辅助说明,如果有报错可贴上报错信息
- 缺陷的复现步骤,最好能提供一个最小可复现 demo 链接
如果是一个新特性,则选择 [Feature request](https://github.com/opentiny/tiny-vue/issues/new?template=feature-request.yml) 模板,标题遵循 `[componentName]新特性简述` 的格式,比如:`[select]过滤功能中,选中的选项退格删除后,无法再次选中该选项`。
新特性的 Issue 主要需要填写以下信息:
- 该特性主要解决用户的什么问题
- 该特性的 api 是什么样的
## 提交 PR
提交 PR 之前,请先确保你提交的内容是符合 TinyVue 整体规划的,一般已经标记为 [bug](https://github.com/opentiny/tiny-vue/labels/bug) 的 Issue 是鼓励提交 PR 的,如果你不是很确定,可以创建一个 [Discussion](https://github.com/opentiny/tiny-vue/discussions) 进行讨论。
本地启动步骤:
- 点击 [TinyVue](https://github.com/opentiny/tiny-vue) 代码仓库右上角的 Fork 按钮,将上游仓库 Fork 到个人仓库
- Clone 个人仓库到本地
- 在 Tiny Vue 根目录下运行 npm i, 安装 node 依赖
- 运行 npm run dev:vue3启动组件库网站
- 打开浏览器访问:[http://127.0.0.1:5173/](http://127.0.0.1:5173/)
```shell
# username 为用户名,执行前请替换
git clone git@github.com:username/tiny-vue.git
cd tiny-vue
git remote add upstream git@github.com:opentiny/tiny-vue.git
npm i
# 启动 Vue3 项目
npm run dev:vue3
# 启动 Vue2 项目
npm run dev:vue2
```
提交 PR 的步骤:
- 请确保你已经完成本地启动中的步骤,并能正常访问:[http://127.0.0.1:5173/](http://127.0.0.1:5173/)
- 创建新分支 `git checkout -b username/feature1`,分支名字建议为 `username/feat-xxx` / `username/fix-xxx`
- 本地编码
- 遵循 Commit Message Format 规范进行提交,不符合提交规范的 PR 将不会被合并
- 提交到远程仓库git push origin branchName
- (可选)同步上游仓库 dev 分支最新代码git pull upstream dev
- 打开 TinyVue 代码仓库的 [Pull requests](https://github.com/opentiny/tiny-vue/pulls) 链接,点击 New pull request 按钮提交 PR
- 项目 Committer 进行 Code Review并提出意见
- PR 作者根据意见调整代码,请注意一个分支发起了 PR 后,后续的 commit 会自动同步,无需重新提交 PR
- 项目管理员合并 PR
贡献流程结束,感谢你的贡献!
## 加入开源社区
如果你对我们的开源项目感兴趣,欢迎通过以下方式加入我们的开源社区。
- 添加官方小助手微信opentiny加入我们的技术交流群
- 加入邮件列表opentiny@googlegroups.com

22
LICENSE Normal file
View File

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2022 - present TinyVue Authors.
Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,2 +1,75 @@
# tiny-vue
<p align="center">
<a href="https://tinyuidesign.cloudbu.huawei.com/" target="_blank" rel="noopener noreferrer">
<img alt="TinyVue Logo" src="logo.svg" height="100" style="max-width:100%;">
</a>
</p>
<p align="center">Tiny Vue 是一个基于 Vue 的 UI 组件库,可以同时支持 Vue 2.0 和 Vue 3.0。</p>
特性:
- 包含 69 个简洁、易用、功能强大的组件
- 同时支持 Vue2 和 Vue3
- 支持国际化
- 支持主题定制
- 组件内部支持配置式开发,特别适合低代码平台可视化组件配置
- 采用模板、样式、逻辑分离的跨端跨框架架构,保障灵活性和可移植性
## 如何使用
### 1. 安装
执行以下命令,安装 Vue 3.0 版本的 Tiny Vue 组件库:
```shell
npm i @opentiny/vue
```
执行以下命令,安装 Vue 2.0 版本的 Tiny Vue 组件库
```shell
npm i @opentiny/vue@2
```
### 2. 引入和使用
在`App.vue`文件中使用 Tiny Vue 组件。
```vue
<script lang="ts" setup>
import { Button as TinyButton } from '@opentiny/vue'
</script>
<template>
<tiny-button>Tiny Vue</tiny-button>
</template>
```
## 本地开发
```shell
git clone git@github.com:opentiny/tiny-vue.git
cd tiny-vue
npm i
# 启动 Vue3 项目
npm run dev:vue3
# 启动 Vue2 项目
npm run dev:vue2
```
打开浏览器访问:[http://127.0.0.1:5173/](http://127.0.0.1:5173/)
## 参与贡献
如果你对我们的开源项目感兴趣,欢迎加入我们!
参与贡献之前请先阅读[贡献指南](CONTRIBUTING.md)。
- 添加官方小助手微信 opentiny加入技术交流群
- 加入邮件列表 opentiny@googlegroups.com
## 开源协议
[MIT](LICENSE)

18
build.sh Normal file
View File

@ -0,0 +1,18 @@
#!/bin/bash
if [ ! $version ];
then npm version 0.1.0-`date "+%Y%m%d%H%M%S"`;
else npm version ${version};
fi
npm run bootstrap
npm run build:vue3
npm run release3
if [ $? -ne 0 ]
then
echo "[ERROR] build falid!"
exit 1
fi
echo '[INFO] build completed'

6
build/.eslintrc Normal file
View File

@ -0,0 +1,6 @@
{
"rules": {
"no-empty": "off",
"no-console": "off"
}
}

55
build/build-entry-app.js Normal file
View File

@ -0,0 +1,55 @@
/**
* 生成全量运行时入口文件
*/
const fs = require('fs-extra')
const endOfLine = require('os').EOL
const utils = require('./utils')
const runtimeUtils = require('./runtime-utils')
const version = utils.getTinyVersion()
const outputDir = 'packages'
const IMPORT_TEMPLATE = 'import {{name}} from "@opentiny/vue-{{package}}"'
const MAIN_TEMPLATE = `{{include}}
const version = '${version}'
export {
version,
{{components}}
}
`
const buildFullRuntime = (buildType) => {
const outputPath = utils.pathJoin('..', outputDir, buildType + '.js')
const includeTemplate = []
const componentsTemplate = []
const render = utils.renderTemplate()
let coreLibs = []
coreLibs = runtimeUtils.getFullRuntime(buildType === 'base' ? ['base', 'business'] : buildType)
coreLibs.forEach((name) => {
includeTemplate.push(
render(IMPORT_TEMPLATE, {
name,
package: utils.kebabCase({ str: name })
})
)
componentsTemplate.push(name)
})
const template = render(MAIN_TEMPLATE, {
include: includeTemplate.join(endOfLine),
components: componentsTemplate.join(',' + endOfLine)
})
const output = utils.prettierFormat({
str: template
})
fs.writeFileSync(outputPath, output)
utils.logGreen(`npm run build:entry done. [${outputDir}/${buildType}.js]`)
}
;['core', 'base', 'chart'].forEach(buildFullRuntime)

144
build/build-entry.js Normal file
View File

@ -0,0 +1,144 @@
/**
* 生成入口文件包括 pc.js / mobile.js / index.js
*/
const fs = require('fs-extra')
const endOfLine = require('os').EOL
const utils = require('./utils')
const moduleUtils = require('./module-utils')
const version = utils.getTinyVersion()
const outputDir = 'packages'
const fileNames = {
all: 'index.js',
pc: 'pc.js',
mobile: 'mobile.js'
}
const getMainTemplate = (mode) => {
const template = `{{include}}
import { $prefix } from '@opentiny/vue-common'
const components = [{{components}}]
export const install = (app, opts = {}) => {
const regex = new RegExp('^' + $prefix)
${
mode === 'all'
? ''
: `
if (typeof app.unmount === 'function') {
app.config.globalProperties.tiny_mode = { value: '${mode}' }
} else {
app.prototype.tiny_mode = { value: '${mode}' }
}
`
}
components.filter(component=> component.name !== 'TinyPicker').forEach((component) => {
let name = component.name
let alias = opts.alias || opts.prefix
if (typeof component.install !== 'function') { return }
if (name && alias) {
app.component(name.replace(regex, alias), component)
} else {
component.install(app)
}
})
}
const version = '${version}'
export {
version,
{{components}}
}
export default {
version,
{{components}},
install
}
`
return template
}
const forEachCompoents = ({ components, componentsTemplate, render, INSTALL_COMPONENT_TEMPLATE, includeTemplate, IMPORT_TEMPLATE }) => {
components.forEach((item) => {
let exportsComponents = ''
const childrenComponents = []
// 增加组件内部抛出子组件,针对父子组件不能拆分的情况
if (Array.isArray(item.exports) && item.exports.length > 0) {
item.exports.indexOf(item.name) === -1 && item.exports.push(item.name)
} else {
item.exports = [item.name]
}
item.exports.forEach((component) => {
if (component !== item.name) {
component = utils.capitalizeKebabCase(component)
childrenComponents.push(component)
}
componentsTemplate.push(
render(INSTALL_COMPONENT_TEMPLATE, {
name: component
})
)
})
if (childrenComponents.length) {
exportsComponents = `,{${childrenComponents.join(',')}}`
}
includeTemplate.push(
render(IMPORT_TEMPLATE, {
name: item.name,
exports: exportsComponents,
package: item.importName
})
)
})
}
const createEntry = (mode) => {
const OUTPUT_PATH = utils.pathJoin('..', outputDir, fileNames[mode])
const IMPORT_TEMPLATE = 'import {{name}} {{exports}} from "{{package}}"'
const INSTALL_COMPONENT_TEMPLATE = ' {{name}}'
const MAIN_TEMPLATE = getMainTemplate(mode)
const includeTemplate = []
const componentsTemplate = []
const render = utils.renderTemplate()
let components
if (mode === 'pc') {
components = moduleUtils.getPcComponents()
} else if (mode === 'mobile') {
components = moduleUtils.getMobileComponents()
} else {
components = moduleUtils.getComponents()
}
forEachCompoents({
components,
componentsTemplate,
render,
INSTALL_COMPONENT_TEMPLATE,
includeTemplate,
IMPORT_TEMPLATE
})
const template = render(MAIN_TEMPLATE, {
include: includeTemplate.join(endOfLine),
components: componentsTemplate.join(`,${endOfLine}`)
})
const output = utils.prettierFormat({
str: template
})
fs.writeFileSync(OUTPUT_PATH, output)
}
;['all', 'pc', 'mobile'].forEach(createEntry)
utils.logGreen(`npm run build:entry done. [${outputDir}/index.js,${outputDir}/pc.js,${outputDir}/mobile.js]`)

92
build/build-icon.js Normal file
View File

@ -0,0 +1,92 @@
const vue = require('rollup-plugin-vue')
const { babel } = require('@rollup/plugin-babel')
const commonjs = require('@rollup/plugin-commonjs')
const { nodeResolve } = require('@rollup/plugin-node-resolve')
const { pathJoin, logGreen, logRed } = require('./utils')
const rollup = require('rollup')
const svg = require('rollup-plugin-vue-inline-svg')
const fs = require('fs-extra')
const path = require('path')
const inputOptions = {
plugins: [
vue({
css: true
}),
svg({
svgoConfig: {
plugins: [{ removeDoctype: true }, { removeComments: true }, { removeViewBox: false }],
removeViewBox: false
}
}),
nodeResolve(),
babel({
exclude: /node_modules/,
configFile: false, // 必须为 false 不然会取根文件的 babel.config.js 配置,产生一堆 runtime 代码
babelrc: false,
babelHelpers: 'bundled',
plugins: ['@babel/plugin-proposal-export-default-from', '@babel/plugin-proposal-export-namespace-from'],
presets: ['@babel/preset-env'],
extensions: ['.js', '.vue']
}),
// 如果打包文件中包含 jsx 语法, commonjs 必须放置在 babel 配置下面,否则会报错 PLUGIN_ERROR
commonjs()
],
external: (deps) => /^@opentiny[\\/]-vue-common/.test(deps)
}
const outputOptions = {
format: 'es',
exports: 'named'
}
const build = (components) => {
components.forEach((component) => {
const inputs = { ...inputOptions }
inputs.input = pathJoin('..', 'packages', 'icon', component.path)
if (component.path === 'index.js') {
inputs.external = (deps) => !deps.includes('index.js')
} else {
inputs.external = (deps) => /^@opentiny[\\/]vue-common/.test(deps)
}
rollup
.rollup(inputs)
.then((bundle) => {
const outs = { ...outputOptions }
outs.file = pathJoin('..', 'packages', 'icon', component.libPath)
bundle.write(outs)
logGreen(`${component.path} compile icon done`)
})
.catch((e) => {
logRed(e)
})
})
}
function createComponentMap(dir) {
const components = []
fs.readdirSync(dir).forEach((file) => {
if (['dist', 'runtime'].includes(file)) {
return
}
if (fs.statSync(path.join(dir, file)).isDirectory()) {
components.push({
path: `${file}/index.js`,
libPath: `dist/lib/${file}.js`
})
} else {
file.endsWith('.js') &&
components.push({
path: `${file}`,
libPath: `dist/lib/${file}`
})
}
})
return components
}
build(createComponentMap(pathJoin('..', 'packages', 'icon')))

115
build/build-ui.js Normal file
View File

@ -0,0 +1,115 @@
const rollup = require('rollup')
const utils = require('./utils')
const replace = require('@rollup/plugin-replace')
const moduleUtils = require('./module-utils')
const fs = require('fs-extra')
const isSingle = process.env.BUILD_TARGET === 'single'
const config = require('./config')
const outputOptions = {
format: 'es',
globals: config.globals,
exports: 'named'
}
const inputOptions = {
plugins: config.plugins,
external: config.external
}
const replaceConstant = {
'process.env.BUILD_TARGET': JSON.stringify(process.env.BUILD_TARGET),
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}
if (process.env.tiny_mode === 'pc') {
outputOptions.format = 'umd'
replaceConstant['process.env.TINY_MODE'] = JSON.stringify(process.env.tiny_mode)
}
/**
* 编译单个组件
* @param {Object} component 组件 module 信息 modules.json
* @param {Function} 回调函数
*/
const build = ({ component, callback }) => {
inputOptions.input = utils.pathJoin('..', component.path)
inputOptions.plugins.push(replace(replaceConstant))
rollup
.rollup(inputOptions)
.then((bundle) => {
outputOptions.file = utils.pathJoin('..', component.libPath)
if (outputOptions.format === 'umd') {
outputOptions.name = component.global
}
bundle.write(outputOptions).finally(() => {
const filePath = utils.pathJoin('..', component.libPath)
if (filePath.endsWith('index.js')) {
const indexStr = fs.readFileSync(filePath).toString('UTF-8')
const resStr = indexStr.replace('./src/pc', './pc').replace('./src/mobile', './mobile')
fs.writeFileSync(filePath, resStr)
}
callback()
})
})
.catch((e) => {
utils.logRed(e)
callback()
})
}
let components = []
/**
* 递归执行 Rollup 编译
* @param {Number} count 起始索引
*/
const buildAll = (count = 0) => {
let component = components[count++]
if (component) {
if (!isSingle) {
component.libPath = 'dist/' + component.libName.replace('@opentiny/vue/', '')
component.libPath += (component.type === 'component' ? '/index' : '') + '.js'
}
build({
component,
callback() {
buildAll(count)
}
})
} else {
utils.logGreen(`npm run build:ui${isSingle ? '-single' : ''} done.`)
}
}
if (isSingle) {
const inputName = utils.getInputCmd()
if (inputName.length > 0) {
inputName.forEach((input) => {
const activeComponentName = utils.kebabCase({ str: input })
if (activeComponentName) {
components.push(
...moduleUtils.getByName({
name: activeComponentName,
isSort: false
})
)
}
})
} else {
const activeComponentName = utils.getComponentName()
components = moduleUtils.getByName({
name: activeComponentName,
isSort: false
})
}
} else {
components = moduleUtils.getAllModules(false)
}
if (components.length > 0) {
buildAll()
} else {
utils.logYellow('please enter the component name after command.')
}

123
build/build-version.js Normal file
View File

@ -0,0 +1,123 @@
/**
* 批量更新组件版本号一般用于大版本发布
* npm run build:version ${targetVersion} ${tag} ${single-components}|null[build all pkg]
* example:
* 发布 @opentiny/vue-alert@next @opentiny/vue-button@next 命令:
* npm run build:version '0.1.0' 'next' 'alert button'
* 发布全量包@next:
* npm run build:version '0.1.0' 'next'
*/
const fs = require('fs-extra')
const path = require('path')
const ROOT_PATH = path.join(__dirname, '..')
const TYPE = process.env.TYPE
const isFullVersionUpdate = true
const TAG = process.argv[3] === 'false' ? '' : process.argv[3]
const packages = path.join(ROOT_PATH, 'packages')
const pkgJsonFileName = 'package.json'
const { logGreen } = require('./utils')
const tinyVueReg = /@opentiny\//
const targetVersion = process.argv[2] || JSON.parse(fs.readFileSync(path.join(ROOT_PATH, pkgJsonFileName)).toString()).version
const isUpdateDependenciesVersion = true // process.argv[3] === 'true'
const single = process.argv[4]
const targetVersionArr = targetVersion.split('.')
const oldVersion = `${targetVersionArr[0]}.${targetVersionArr[1] - 1}.0`
const targetVersionDependencies = `${targetVersionArr[0]}.${targetVersionArr[1]}.0`
const isTinyVuePkg = (str) => tinyVueReg.test(str || '')
const replaceDependenciesForTag = (obj) => {
for (let key in obj) {
if (isTinyVuePkg(key)) {
obj[key] = TAG
}
}
return obj
}
const replaceDependencies = (obj) => {
for (let key in obj) {
if (isTinyVuePkg(key) && /~/.test(obj[key])) {
obj[key] = obj[key].replace(isUpdateDependenciesVersion ? /[0-9]+\.[0-9]+\.[0-9]+/ : oldVersion, targetVersionDependencies)
}
}
return obj
}
const buidPackages = (dirs) => {
let uiArr
if (single) {
uiArr = single.trim().split(' ')
}
fs.readdirSync(dirs).forEach((name) => {
if (uiArr && !uiArr.includes(name)) {
return
}
const component = path.join(dirs, name)
if (name === 'src' || !fs.statSync(component).isDirectory()) {
return
}
let pkgJsonFile, pkgJson
try {
pkgJsonFile = path.join(component, pkgJsonFileName)
pkgJson = JSON.parse(fs.readFileSync(pkgJsonFile).toString())
} catch (error) {
return
}
if (isTinyVuePkg(pkgJson.name)) {
const arr = ['dependencies', 'devDependencies']
pkgJson.version = single || isFullVersionUpdate ? targetVersion : targetVersionDependencies
arr.forEach((key) => {
if (pkgJson[key]) {
pkgJson[key] = TAG ? replaceDependenciesForTag(pkgJson[key]) : replaceDependencies(pkgJson[key])
}
})
fs.writeFileSync(pkgJsonFile, JSON.stringify(pkgJson, null, 2))
}
if (name === 'chart') {
buidPackages(component)
}
})
}
if (!targetVersion) {
throw new Error('targetVersion is null, cmd example:\n npm run build:version 3.10.0 true "alert grid"')
}
const buildCommon = () => {
const pkgJsonFile = path.join(ROOT_PATH, pkgJsonFileName)
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonFile).toString())
pkgJson.version = targetVersion
pkgJson.uiVersion = isFullVersionUpdate ? targetVersion : targetVersionDependencies
pkgJson.srcVersion = isFullVersionUpdate ? targetVersion : targetVersionDependencies
fs.writeFileSync(pkgJsonFile, JSON.stringify(pkgJson, null, 2))
logGreen('update src version done')
}
const build = () => {
buildCommon()
if (TYPE === 'ui') {
buidPackages(packages)
logGreen('update all component version done')
}
}
build()

82
build/config.js Normal file
View File

@ -0,0 +1,82 @@
const fs = require('fs-extra')
const vue = require('rollup-plugin-vue')
const { babel } = require('@rollup/plugin-babel')
const alias = require('@rollup/plugin-alias')
const commonjs = require('@rollup/plugin-commonjs')
const postcss = require('rollup-plugin-postcss')
const { nodeResolve } = require('@rollup/plugin-node-resolve')
const { pathJoin } = require('./utils')
const { getAllModules } = require('./module-utils')
const external = ['vue', './pc', './mobile', '@vue/composition-api', '@opentiny/vue-common', '@opentiny/vue-locale', '@opentiny/vue-renderless']
const globals = {
vue: 'Vue',
'@vue/composition-api': 'vueCompositionApi',
'@opentiny/vue-common': 'TinyVueCommon',
'@opentiny/vue-locale': 'TinyVueLocale',
'@opentiny/vue-renderless': 'TinyRenderLess'
}
const aliasList = {}
const components = getAllModules(false)
components.forEach((item) => {
aliasList[item.libName] = pathJoin('../' + item.path)
if (item.private) {
return
}
const isComponent = item.type === 'component'
external.push(item.importName) // @opentiny/vue-todo
external.push(item.libName) // @opentiny/vue/todo
globals[item.libName] = item.global // TinyTodo
if (isComponent) {
if (fs.existsSync(pathJoin('../../vue-theme3'))) {
aliasList[`@opentiny/vue-theme/${item.LowerName}/index.css`] = pathJoin(`../../vue-theme3/style/${item.LowerName}/index.css`)
aliasList[`@opentiny/vue-theme/${item.LowerName}/index.js`] = pathJoin(`../../vue-theme3/style/${item.LowerName}/index.js`)
}
external.push(item.libName + '/index.js')
} else {
external.push(item.libName + '.js')
}
})
exports.aliasList = aliasList
exports.external = (deps) => external.includes(deps) || /^@opentiny[\\/](vue-renderless|vue-theme|vue-common|vue-icon)|cropperjs/.test(deps)
exports.globals = globals
exports.plugins = [
alias({
resolve: ['.js', '.vue', '.css'],
'@opentiny/vue-locale': pathJoin('../packages/locale/index'),
'@opentiny/vue-common': pathJoin('../packages/common/index'),
...aliasList
}),
postcss({
extract: false
}),
vue({
css: true
}),
nodeResolve({
extensions: ['.js', '.jsx', '.vue', '.css']
}),
babel({
exclude: /node_modules/,
babelrc: false,
configFile: false, // 必须为 false 不然会取根文件的 babel.config.js 配置,产生一堆 runtime 代码
babelHelpers: 'bundled',
comments: false,
extensions: ['.js', '.vue', '.jsx'],
presets: ['@babel/preset-env', '@vue/babel-preset-jsx'],
plugins: ['@babel/plugin-syntax-dynamic-import']
}),
// 如果打包文件中包含 jsx 语法, commonjs 必须放置在 babel 配置下面,否则会报错 PLUGIN_ERROR
commonjs()
]

68
build/create-mapping.js Normal file
View File

@ -0,0 +1,68 @@
const { sep } = require('path')
const utils = require('./utils')
const { addModule, writeModuleMap, quickSort, readModuleMap } = require('./module-utils')
const isDeepFn = (file, dirs, subPath) =>
// 如果底层文件夹内没有找到 vue 文件,找到 src//index.js 文件也被认可为组件
(file.endsWith('.vue') && (dirs.includes('index.js') || dirs.includes('index.vue'))) || ~subPath.indexOf(['src', 'index.js'].join(sep))
const getTemplateName = (currentPaths) => currentPaths.slice(2).map(utils.capitalize).join('/').split('.')[0].replace('/', '')
/**
* 扫描指定目录下面的组件目录查找非 index.vue 文件模板生成 modules.json 中的对象
*/
const makeModules = () => {
const templates = {}
const oldModules = readModuleMap()
const packagesStr = 'packages'
utils.walkFileTree({
isDeep: true,
dirPath: utils.pathJoin('..', packagesStr),
fileFilter({ file }) {
return !/node_modules|helper|common|assets/.test(file)
},
callback({ file, subPath, dirs }) {
const isDeep = isDeepFn(file, dirs, subPath)
// NEXT: 针对 option 的模板做特殊处理
if (isDeep && ['template.vue'].indexOf(file) === -1) {
const isEntry = file.startsWith('index')
const subPaths = subPath.split(sep)
const currentPaths = subPaths.slice(subPaths.indexOf(packagesStr) + 1)
const templateName = getTemplateName(currentPaths)
const templatePath = currentPaths[currentPaths.length - 1].split('.')[0]
const componentName = []
currentPaths.every((dirName) => {
if (dirName === 'src') {
return false
}
componentName.push(dirName)
return true
})
const globalName = componentName[componentName.length - 1].split('-').map(utils.capitalize).join('')
const moduleName = globalName + (isEntry ? '' : templateName)
const oldModuleItem = oldModules[moduleName] || {}
const oldKeys = Object.keys(oldModuleItem)
const newModuleItem = addModule({
componentName: componentName.join('/'),
templateName: templatePath
})
oldKeys.forEach((key) => {
if (typeof newModuleItem[key] === 'undefined' || key === 'onlyMode') {
newModuleItem[key] = oldModuleItem[key]
}
})
newModuleItem.exclude = oldModuleItem.exclude || false
templates[moduleName] = newModuleItem
}
}
})
writeModuleMap(quickSort({ sortData: templates, returnType: 'object' }))
}
try {
makeModules()
utils.logGreen('npm run create:mapping done.')
} catch (e) {
utils.logRed('npm run create:mapping failed.', e)
}

229
build/create-ui.js Normal file
View File

@ -0,0 +1,229 @@
/**
* yarn create:ui 新建组件支持格式如下
* yarn create:ui img-preview
* yarn create:ui img-preview -single 输出纯净模板没有 pc 等模板/单层组件
* yarn create:ui img-preview -mobile 创建纯移动组件
*/
const path = require('path')
const fs = require('fs-extra')
const semver = require('semver')
const utils = require('./utils')
const { createModuleMapping } = require('./module-utils')
const args = utils.getInputCmd()
const getTemplate = (upperComponentName) => `<template>
<div>
<tiny-[[NAME]] _mode="mobile"></tiny-[[NAME]]>
</div>
</template>
<script>
import { [[UNAME]] } from '@opentiny/vue'
export default {
name: '[[UNAME]]',
components: {
Tiny${upperComponentName}:${upperComponentName}
}
}
</script>`
const getDcoTemplate = () => `<div class="demo-header">
<p class="overviewicon">
<span class="wapi-ui-[[NAME]]"/>
</p>
## [[UNAME]]
<mobile-uxlink widget-name="[[UNAME]]"></mobile-uxlink>
</div>
### [[UNAME]]
<mobile-view link="[[NAME]]/base"></mobile-view>`
const getTemp = (componentName) => `const router = [
{
path: '${componentName}',
meta: {
title: 'test',
lang: 'zh-CN',
sign: 'component'
},
component: () =>
import(
/* webpackChunkName: 'v3-${componentName}' */ './docs/mobile/${componentName}/base.md'
)
},`
const doWorkTreeFn = ({ templateDir, componentPath, componetDir, componentName, isSingle, render, version, isMobile }) => {
utils.walkFileTree({
isDeep: true,
dirPath: templateDir,
callback({ file, subPath }) {
let fileName = file
const isSingleTemplate = file === 'single.vue'
const isSrcDir = path.basename(path.dirname(subPath)) === 'src'
componentPath = path.join(componetDir, componentName)
// 单层组件处理逻辑
if (isSrcDir) {
componentPath = path.join(componentPath, 'src')
if (isSingle) {
if (!isSingleTemplate) {
return
}
fileName = 'index.vue'
} else {
if (isSingleTemplate) {
return
}
}
}
if (!fs.existsSync(componentPath)) {
fs.mkdirSync(componentPath)
}
componentPath = path.join(componentPath, fileName)
let fileContent = fs.readFileSync(subPath, { encoding: 'utf8' })
const upperComponentName = utils.capitalizeKebabCase(componentName)
// 编译模板
fileContent = render(fileContent, {
NAME: componentName,
UNAME: upperComponentName,
MINOR: semver.minor(version),
SUFFIX: isSingle ? '.vue' : '',
THEME: isMobile ? 'theme-mobile' : 'theme'
})
fs.writeFileSync(componentPath, fileContent)
}
})
}
const createRouter = (json, componentName, navPath, router) => {
const Navs = JSON.parse(json)
Navs.component.push({
name: 'New Component',
children: [
{
path: `/${componentName}`,
name: `${componentName}`
}
]
})
fs.writeFileSync(path.join(navPath, 'nav.config.comp.mobile.json'), JSON.stringify(Navs, null, 2), { encoding: 'utf-8' })
fs.writeFileSync(path.join(navPath, 'route.config.comp.mobile.js'), router, {
encoding: 'utf8'
})
}
if (args.length > 0) {
const commands = []
const components = []
const render = utils.renderTemplate({ leftChar: '[[', rightChar: ']]' })
const templateDir = utils.pathJoin('..', 'template', 'component')
const componetDir = utils.pathJoin('..', 'packages')
const demoDir = utils.pathJoin('..', 'example', 'src', 'demo', 'mobile')
const docDir = utils.pathJoin('..', 'example', 'src', 'docs', 'mobile')
const { version } = fs.readJSONSync(utils.pathJoin('..', 'package.json'))
const navPath = utils.pathJoin('..', 'example', 'src')
args.forEach((item) => {
if (item.indexOf('-') === 0) {
commands.push(item.replace(/-/g, '').toLowerCase())
} else {
components.push(item)
}
})
const isSingle = commands.includes('single')
const isMobile = commands.includes('mobile')
const createDemo = (filePath, componentName, fileName, template) => {
if (!fs.existsSync(filePath)) {
fs.mkdirSync(filePath)
}
const upperComponentName = utils.capitalizeKebabCase(componentName)
// 生成测试demo
filePath = path.join(filePath, fileName)
const outString = render(template, {
NAME: componentName,
UNAME: upperComponentName
})
const outputDemo = require('prettier').format(outString, {
printWidth: 160,
jsxBracketSameLine: false,
tabWidth: 2,
useTabs: false,
singleQuote: true,
semi: false,
trailingComma: 'none',
bracketSpacing: true,
parser: 'vue'
})
fs.writeFileSync(filePath, outputDemo)
}
components.forEach((componentName) => {
let componentPath = path.join(componetDir, componentName)
let demoPath = path.join(demoDir, componentName)
let docPath = path.join(docDir, componentName)
const upperComponentName = utils.capitalizeKebabCase(componentName)
if (fs.existsSync(componentPath)) {
utils.logYellow(`The component name : ${componentName} is exist , please enter other name.`)
return
}
const json = fs.readFileSync(path.join(navPath, 'nav.config.comp.mobile.json'), { encoding: 'utf8' })
let router = fs.readFileSync(path.join(navPath, 'route.config.comp.mobile.js'), { encoding: 'utf8' })
const templ = getTemp(componentName)
router = router.replace('const router = [', templ)
createRouter(json, componentName, navPath, router)
doWorkTreeFn({
templateDir,
componentPath,
componetDir,
componentName,
isSingle,
render,
version,
isMobile
})
// 生成测试demo
const template = getTemplate(upperComponentName)
createDemo(demoPath, componentName, 'base.vue', template)
// 生成doc
const dcoTemplate = getDcoTemplate()
createDemo(docPath, componentName, 'base.md', dcoTemplate)
componentName && createModuleMapping(componentName, componentPath, isMobile)
})
utils.logYellow('npm run create:ui done.')
} else {
utils.logYellow('please enter the component name after command.')
}

403
build/module-utils.js Normal file
View File

@ -0,0 +1,403 @@
/**
* 专门用于 modules.json 配置的通用方法
* modules.json 作为单组件的清单列表记录组件类型路径是否排除引用仅支持某种[pc/mobile]模式等
*/
const { writeFileSync } = require('fs')
const moduleMap = require('../modules.json')
const { kebabCase, pathJoin, capitalize, prettierFormat, logGreen, capitalizeKebabCase } = require('./utils')
const realNameMap = {
realValue: '__real_value'
}
/**
* 默认的组件模板
*/
const defaultMode = ['index', 'pc']
/**
* 组件组包名前缀@opentiny/vue-tag | @opentiny/vue/tag
*/
const importName = '@opentiny/vue'
const exampleBase = 'basic-usage.vue'
/**
* 查询没有模板的组件单层组件
*/
const getNoTemplateComponents = () => {
const components = []
const templates = ['Pc', 'Mobile']
Object.keys(moduleMap).forEach((key) => {
if (!key.endsWith(templates[0]) && !key.endsWith(templates[1]) && !key.startsWith('Chart')) {
if (!moduleMap[key + templates[0]] && !moduleMap[key + templates[1]]) {
components.push(moduleMap[key])
}
}
})
return components
}
/**
* 判断是否为 PC 组件
*/
const isPcComponent = (name) => {
const componentName = capitalizeKebabCase(name)
const moduleInfo = moduleMap[componentName]
return moduleInfo && moduleInfo.onlyMode !== 'mobile'
}
/**
* 判断是否为 Mobile 组件
*/
const isMobileComponent = (name) => {
const componentName = capitalizeKebabCase(name)
const moduleInfo = moduleMap[componentName]
return moduleInfo && moduleInfo.onlyMode === 'mobile'
}
const setIndex = (obj, key, maxNumberLength, indexArr) => {
// 一个字母用3位数代替没有达到3位用0填充到前面为了减少差值在前面设置1
const priority =
'1' +
obj[key]
.split('')
.map((chart) => String(chart.charCodeAt()).padStart(3, '0'))
.join('')
.padEnd(maxNumberLength, '0')
.substr(0, maxNumberLength)
// 分段比较 javascript 超过15位比较失效
obj.priority1 = Number(priority.substr(0, 15))
obj.priority2 = Number(priority.substr(15, 30))
obj.priority3 = Number(priority.substr(30, 45))
obj.priority4 = Number(priority.substr(45, maxNumberLength))
obj.priority = priority
indexArr.push(obj)
}
const arrayToObject = (arr, key) => {
const sortObj = {}
for (let i = 0, len = arr.length; i < len; i++) {
if (arr[i][realNameMap.realValue]) {
sortObj[arr[i][key]] = arr[i][realNameMap.realValue]
} else {
delete arr[i].priority
delete arr[i].priority1
delete arr[i].priority2
delete arr[i].priority3
delete arr[i].priority4
sortObj[arr[i][key]] = arr[i]
delete sortObj[arr[i][key]][key]
}
}
return sortObj
}
const sortArray = (arr) => {
if (Array.isArray(arr) && arr.length > 1) {
const middleIndex = Math.floor(arr.length / 2)
const middleValue = arr.splice(middleIndex, 1)[0]
const leftArr = []
const rightArr = []
for (let i = 0, len = arr.length; i < len; i++) {
const left = arr[i].priority1 - middleValue.priority1
const right = arr[i].priority2 - middleValue.priority2
const right2 = arr[i].priority3 - middleValue.priority3
const right3 = arr[i].priority4 - middleValue.priority4
let isLeft = false
if (left === 0 && (right < 0 || (right === 0 && right2 < 0) || (right === 0 && right2 === 0 && right3 < 0))) {
isLeft = true
}
if (left < 0 || isLeft) {
leftArr.push(arr[i])
} else {
rightArr.push(arr[i])
}
}
return sortArray(leftArr).concat([middleValue], sortArray(rightArr))
} else {
return arr
}
}
/**
* 将模块数组按字母的 ASCII 值进行排序目前只支持16位字母排序
* @private
* @param {Array|Object} sortData 模块数组
* @param {String} key 排序字段
* @param {String} returnType 返回类型 Array|Object
* @returns 排序后的数组或对象
*/
const quickSort = ({ sortData, key = 'name', returnType = 'array' }) => {
const maxNumberLength = 59
let indexArr = []
if (sortData instanceof Array) {
sortData.forEach((item) => {
setIndex(item, key, maxNumberLength, indexArr)
})
} else if (typeof sortData === 'object') {
for (const sortKey in sortData) {
if (Object.prototype.hasOwnProperty.call(sortData, sortKey)) {
const dataItem = sortData[sortKey]
let sortItem = {}
if (typeof dataItem === 'object') {
sortItem = {
...sortData[sortKey]
}
} else {
sortItem[realNameMap.realValue] = dataItem
}
sortItem[key] = sortData[key] || sortKey
setIndex(sortItem, key, maxNumberLength, indexArr)
}
}
} else {
return sortData
}
return returnType === 'array' ? sortArray(indexArr) : arrayToObject(sortArray(indexArr), key)
}
/**
* 根据指定条件搜索模块列表并排序与生成必要字段
* @private
* @param {Function} filterIntercept 搜索条件
* @param {Boolean} isSort 是否需要排序
*/
const getSortModules = ({ filterIntercept, isSort = true }) => {
let modules = []
let componentCount = 0
if (typeof filterIntercept === 'function') {
Object.keys(moduleMap).forEach((key) => {
const component = moduleMap[key]
component.name = key
if (filterIntercept(component) === true && component.exclude !== true) {
const dirs = component.path.split('/')
const componentName = dirs.slice(1, dirs.indexOf('src'))
component.UpperName = componentName.pop().split('-').map(capitalize).join('')
component.LowerName = kebabCase({ str: component.UpperName })
// 工程的父文件夹
component.parentDir = componentName
// libPath: 'packages/todo/dist/pc.js' 组件输出路径
component.libPath = component.path.replace('/index.js', '/src/index.js').replace('/src/', '/dist/lib/').replace('.vue', '.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 = importName + '-' + component.libName
// libName: '@opentiny/vue/todo/pc'
component.libName = importName + '/' + component.libName
// tmpName: 'pc'
component.tmpName = component.libPath.replace('.js', '').split('/').slice(-1)[0]
// global: 'TinyTodoPc'
component.global = 'Tiny' + key
modules.push(component)
}
component.type === 'component' && componentCount++
})
logGreen(`[Contain Components]: Total(${componentCount}) `)
} else {
modules = moduleMap
}
return isSort ? quickSort({ sortData: modules }) : modules
}
/**
* 获取所有模块并排序格式化
* @param {Boolean} isSort 是否需要排序
* @returns 模块对象
*/
const getAllModules = (isSort) => getSortModules({ filterIntercept: () => true, isSort })
/**
* 获取所有组件并排序格式化
* @param {Boolean} isSort 是否需要排序
* @returns 组件对象
*/
const getComponents = (isSort) =>
getSortModules({
filterIntercept: (item) => !['template'].includes(item.type),
isSort
})
/**
* @param {String} key 根据模块对象的 Key 获取对应的值
* @returns 模块对象
*/
const getModuleInfo = (key) => moduleMap[key] || {}
/**
* 将输入的 Map 写入到 modules.json 文件中并格式化
* @param {String|Object} moduleMap 模块 Json 对象集合或 Json 字符串
*/
const writeModuleMap = (moduleMap) => {
writeFileSync(
pathJoin('..', 'modules.json'),
prettierFormat({
str: typeof componentMap === 'string' ? moduleMap : JSON.stringify(moduleMap),
options: {
parser: 'json',
printWidth: 10
}
})
)
}
/**
* 读取 modules.json 中的模块信息
* @returns 模块对象
*/
const readModuleMap = () => moduleMap || {}
/**
* 获取模块项的模块
* @param {String} componentName 组件名称大写例如ImgPreview
* @param {String} templateName 模板名称/路径
* @param {Oject} newObj 新增对象
* @returns 模块对象
*/
const addModule = ({ componentName, templateName, newObj = {} }) => {
const isEntry = templateName.endsWith('index')
return {
path: `packages/${componentName}/` + (isEntry ? `${templateName}.js` : `src/${templateName}.vue`),
type: isEntry ? 'component' : 'template',
exclude: false,
...newObj
}
}
/**
* 根据指定条件搜索原始模块列表
* @private
* @param {Function} filterIntercept 搜索条件
*/
const getModules = (filterIntercept) => {
let modules = {}
if (typeof filterIntercept === 'function') {
for (const key in moduleMap) {
if (Object.prototype.hasOwnProperty.call(moduleMap, key)) {
const component = moduleMap[key]
if (filterIntercept(component) === true && component.exclude !== true) {
modules[key] = component
}
}
}
} else {
modules = moduleMap
}
return modules
}
/**
* 根据组件名称查找模块列表
* @private
* @param {String} name 组件名称
* @param {Boolean} inversion 是否取反
* @param {Boolean} isOriginal 是否取原始数据
* @param {Boolean} isSort 是否需要排序
*/
const getByName = ({ name, inversion = false, isOriginal = false, isSort = true }) => {
const callback = (item) => (inversion ? item.path.indexOf(`/${name}/`) === -1 : item.path.indexOf(`/${name}/`) > -1)
return isOriginal ? getModules(callback) : getSortModules({ filterIntercept: callback, isSort })
}
const getMobileComponents = () => {
const modules = getAllModules()
const componentNames = modules.filter((item) => item.tmpName === 'mobile').map((item) => item.UpperName)
const components = modules.filter(
(item) => item.onlyMode === 'mobile' || item.onlyMode === 'all' || (item.type === 'component' && componentNames.includes(item.UpperName))
)
return components
}
const getPcComponents = () => {
const modules = getAllModules()
const components = modules.filter((item) => item.type === 'component' && item.onlyMode !== 'mobile')
return components
}
const getOnlyMobileComponents = () => getAllModules().filter((item) => item.onlyMode === 'mobile')
/**
* 动态创建 modules.json 适配新建组件
* @param {String} componentName 组件名称 驼峰大写img-preview
* @param {Boolean} isMobile 是否为移动组件
*/
const createModuleMapping = (componentName, templateName, isMobile = false) => {
const upperName = capitalizeKebabCase(componentName)
// 生成 modules.json 文件
moduleMap[upperName] = addModule({
isMobile,
templateName,
componentName
})
const moduleJson = quickSort({ sortData: moduleMap, returnType: 'object' })
writeFileSync(
pathJoin('..', 'modules.json'),
prettierFormat({
str: JSON.stringify(moduleJson),
options: {
parser: 'json',
printWidth: 10
}
})
)
}
module.exports = {
quickSort,
getByName,
addModule,
importName,
exampleBase,
defaultMode,
createModuleMapping,
isPcComponent,
getAllModules,
getComponents,
getModuleInfo,
readModuleMap,
writeModuleMap,
getPcComponents,
isMobileComponent,
getMobileComponents,
getOnlyMobileComponents,
getNoTemplateComponents
}

152
build/release-ui.js Normal file
View File

@ -0,0 +1,152 @@
/**
* 用于发布单组件包
*/
const fs = require('fs-extra')
const path = require('path')
const { execSync } = require('child_process')
const utils = require('./utils')
const { logGreen } = require('./utils')
const sourcePkg = 'packages'
const packages = 'dist'
const tgzs = 'tgzs'
const packageName = 'package.json'
const NPM_TAG = process.env.NPM_TAG || '~0.1.0'
const VERSION_TAG = process.env.VERSION_TAG || '0.1.0'
const NPM_WAREHOUSE = process.env.NPM_WAREHOUSE
const targetVersion = utils.getTinyVersion('themeVersion')
const targetVersionArr = targetVersion.split('.')
const themeVersionDependencies = `~${targetVersionArr[0]}.${targetVersionArr[1]}.0`
const packPackages = (p, packagePath) => {
execSync('npm pack -q', { cwd: path.join(packages, p) })
fs.readdirSync(path.join(packages, p)).forEach((item) => {
if (item.endsWith('.tgz')) {
const tgzPath = path.join(packages, p, item)
fs.moveSync(tgzPath, path.join(tgzs, item), { overwrite: true })
fs.unlinkSync(packagePath)
}
})
}
// 处理每个组件包的package.json文件
const dealPackage = (p, packageJSON) => {
const packageDeps = packageJSON.dependencies || []
Object.keys(packageDeps).forEach((key) => {
if (key.includes('@opentiny/vue')) {
packageDeps[key] = NPM_TAG
}
})
let dependencies = {
'@opentiny/vue-renderless': themeVersionDependencies,
'@opentiny/vue-common': NPM_TAG,
'@opentiny/vue-icon': NPM_TAG,
'@opentiny/vue-theme': themeVersionDependencies,
'@opentiny/vue-theme-mobile': themeVersionDependencies
}
if (p === 'icon') {
packageJSON.dependencies = Object.assign(packageJSON.dependencies || {}, {
'@opentiny/vue-common': NPM_TAG,
'@opentiny/vue-theme': themeVersionDependencies
})
} else if (p === 'locale') {
dependencies = {
'@opentiny/vue-renderless': themeVersionDependencies
}
} else if (p === 'common') {
dependencies = {
'@opentiny/vue-renderless': themeVersionDependencies,
'@opentiny/vue-theme': themeVersionDependencies
}
if (VERSION_TAG.startsWith('2')) {
dependencies['@vue/composition-api'] = '1.2.2'
}
}
packageJSON.dependencies = Object.assign(packageJSON.dependencies || {}, dependencies)
packageJSON.sideEffects = false
packageJSON.version = VERSION_TAG
}
const release = (p) => {
const packagePath = path.join(packages, p, packageName)
const packageJSON = fs.readJSONSync(packagePath)
const componentEntry = path.join(packagePath, '../lib', 'index.js')
dealPackage(p, packageJSON)
fs.writeFileSync(packagePath, JSON.stringify(packageJSON, null, 2))
if (fs.existsSync(componentEntry)) {
let componentEntryContent = fs.readFileSync(componentEntry).toString('UTF-8')
componentEntryContent = componentEntryContent.replace('process.env.COMPONENT_VERSION', `'${VERSION_TAG}'`)
fs.writeFileSync(componentEntry, componentEntryContent)
}
logGreen(`${p} pack done`)
// 测试情况下可以打包成压缩包
if (NPM_WAREHOUSE === 'test') {
packPackages(p, packagePath)
}
}
const dealFile = (componentDir, distDir) => {
if (componentDir.includes('common')) {
// 如果是vue3.0的打包命令下
if (VERSION_TAG.startsWith('3')) {
// 删除 vue2.0文件
fs.removeSync(path.join(distDir, 'adapter/vue2.js'))
// 重写adapter/index.js
fs.copySync(path.join(__dirname, '../', 'template/common/vue3.js'), path.join(distDir, 'adapter/index.js'))
} else {
// 删除 vue3.0文件
fs.removeSync(path.join(distDir, 'adapter/vue3.js'))
// 重写adapter/index.js
fs.copySync(path.join(__dirname, '../', 'template/common/vue2.js'), path.join(distDir, 'adapter/index.js'))
}
} else if (componentDir.includes('locale')) {
// 如果是vue3.0的打包命令下
if (VERSION_TAG.startsWith('3')) {
// 删除 vue2.0文件
fs.removeSync(path.join(distDir, 'vue2.js'))
// 重写index.js
fs.copySync(path.join(__dirname, '../', 'template/locale/vue3.js'), path.join(distDir, 'index.js'))
} else {
// 删除 vue3.0文件
fs.removeSync(path.join(distDir, 'vue3.js'))
// 重写index.js
fs.copySync(path.join(__dirname, '../', 'template/locale/vue2.js'), path.join(distDir, 'index.js'))
}
}
}
// 读取packages文件夹下的所有组件并执行copy操作
const releaseAll = () => {
fs.readdirSync(path.join(sourcePkg)).forEach((item) => {
const componentDir = path.join(sourcePkg, item)
const stat = fs.statSync(componentDir)
if (stat.isDirectory()) {
const distPath = path.join(sourcePkg, item, packages)
const packageJson = path.join(sourcePkg, item, packageName)
if (fs.existsSync(distPath)) {
fs.copySync(distPath, path.join(packages, item), {
overwrite: true
})
fs.copySync(packageJson, path.join(packages, item, packageName), {
overwrite: true
})
} else {
fs.copySync(componentDir, path.join(packages, item), {
overwrite: true
})
// 处理common和locale包分别针对vue2和vue3
dealFile(componentDir, path.join(packages, item))
}
release(item)
}
})
logGreen('-- all release done --')
}
releaseAll()

113
build/release.js Normal file
View File

@ -0,0 +1,113 @@
/**
* 用于发布 @opentiny/vue
*/
const fs = require('fs-extra')
const path = require('path')
const semver = require('semver')
const { execSync } = require('child_process')
const sourcePkg = 'packages'
const source = 'dist'
const packageName = 'package.json'
const packagePath = path.join(source, packageName)
const packageJSON = fs.readJSONSync(packageName)
const keys = ['name', 'version', 'description', 'main', 'files', 'sideEffects', 'author', 'license', 'repository', 'dependencies', 'engines', 'browserslist']
const allDist = 'allDist'
const toOneZip = process.env.tiny_mode === 'pc'
const NPM_TAG = process.env.NPM_TAG
// 命令行中指定的版本号
const VERSION_TAG = process.env.VERSION_TAG
const NPM_WAREHOUSE = process.env.NPM_WAREHOUSE
for (let key in packageJSON) {
if (Object.prototype.hasOwnProperty.call(packageJSON, key)) {
!~keys.indexOf(key) && delete packageJSON[key]
}
}
// 配置指定的版本号
if (VERSION_TAG) {
packageJSON.version = VERSION_TAG
}
// 根据modules.json生成所有组件列表信息
const genDependencies = () => {
const { getComponents } = require('./module-utils')
let dependencies = {}
getComponents(false).forEach((component) => {
const packPath = component.path.replace(/index\.js$/, 'package.json')
if (!fs.existsSync(packPath)) {
return
}
if (NPM_TAG) {
dependencies[component.importName] = NPM_TAG
} else {
let { version } = fs.readJSONSync(packPath)
if (version) {
const major = semver.major(version)
const minor = semver.minor(version)
version = `${major}.${minor}.0`
dependencies[component.importName] = '~' + version
}
}
})
return dependencies
}
// 根据组件列表信息重新package.json的dependencies信息
packageJSON.dependencies = Object.assign(packageJSON.dependencies || {}, genDependencies())
fs.writeFileSync(packagePath, JSON.stringify(packageJSON, null, 2))
fs.copySync(packagePath, path.join(source, 'vue', packageName), {
overwrite: true
})
if (toOneZip) {
const vuePackage = path.join(allDist, 'vue')
if (!fs.existsSync(allDist)) {
fs.mkdirSync(allDist)
}
if (!fs.existsSync(vuePackage)) {
fs.mkdirSync(vuePackage)
}
fs.copyFileSync(packageName, path.join(vuePackage, packageName))
fs.readdirSync(source).forEach((item) => {
const stat = fs.statSync(path.join(source, item))
if (!stat.isDirectory()) {
fs.copyFileSync(path.join(source, item), path.join(vuePackage, item))
}
})
} else {
const entrys = ['pc.js', 'mobile.js', 'index.js']
entrys.forEach((name) => {
fs.copyFileSync(path.join(sourcePkg, name), path.join(source, name))
fs.copySync(path.join(sourcePkg, name), path.join(source, 'vue', name), {
overwrite: true
})
})
// 只有在发布npm测试仓库的时候才执行以下压缩包的逻辑
if (NPM_WAREHOUSE === 'test') {
execSync('npm pack', { cwd: source })
fs.readdirSync(source).forEach((item) => {
if (item.endsWith('.tgz')) {
const tgzPath = path.join(source, item)
fs.moveSync(tgzPath, path.join('tgzs', item), { overwrite: true })
}
})
}
}

341
build/runtime-utils.js Normal file
View File

@ -0,0 +1,341 @@
/**
* 打包运行时通用配置
*
* 全量运行时组件分组
* 运行时版本号
* 运行时外部依赖路径
*/
const fs = require('fs-extra')
const { getVersion, pathJoin, logRed } = require('./utils')
const moduleUtils = require('./module-utils')
const runtimeComponents = {
core: [
'Common',
'Icon',
'Locale',
'Alert',
'Col',
'Container',
'DialogBox',
'Layout',
'Loading',
'Popover',
'Row',
'Tooltip',
'Carousel',
'CarouselItem',
'Collapse',
'CollapseItem',
'Split',
'TimeLine',
'Milestone',
'Floatbar',
'Steps',
'TabItem',
'Tabs',
'Breadcrumb',
'BreadcrumbItem',
'FallMenu',
'NavMenu',
'Rate',
'Tag',
'TopBox',
'Notify',
'Image',
'ImageViewer',
'ScrollText',
'Scrollbar',
'UserHead',
'SlideBar',
'Slider',
'Link',
'Progress',
'Crop'
],
base: [
'Autocomplete',
'BulletinBoard',
'Button',
'ButtonGroup',
'Calendar',
'Cascader',
'CascaderMenu',
'CascaderNode',
'CascaderPanel',
'Checkbox',
'CheckboxButton',
'CheckboxGroup',
'DatePanel',
'DatePicker',
'DateRange',
'DateTable',
'DetailPage',
'DropTimes',
'FileUpload',
'Form',
'FormItem',
'Grid',
'GridColumn',
'GridManager',
'GridToolbar',
'Input',
'IpAddress',
'LinkMenu',
'Modal',
'MonthRange',
'MonthTable',
'Numeric',
'Option',
'OptionGroup',
'Pager',
'PagerItem',
'Picker',
'PopUpload',
'Popeditor',
'Radio',
'RadioButton',
'RadioGroup',
'Search',
'Select',
'SelectDropdown',
'Switch',
'Table',
'TextPopup',
'Time',
'TimePanel',
'TimePicker',
'TimeRange',
'TimeSelect',
'TimeSpinner',
'ToggleMenu',
'Transfer',
'TransferPanel',
'Tree',
'TreeMenu',
'Upload',
'UploadDragger',
'UploadList',
'YearTable'
],
business: ['UserContact', 'Wizard'],
chart: [
'BaiduMap',
'ChartBar',
'ChartCandle',
'ChartCore',
'ChartFunnel',
'ChartGauge',
'ChartWaterfall',
'ChartGraph',
'ChartHeatmap',
'ChartHistogram',
'ChartLine',
'ChartMap',
'ChartPie',
'ChartRadar',
'ChartRing',
'ChartSankey',
'ChartScatter',
'ChartSunburst',
'ChartTree',
'Chart',
'ChartBoxplot'
],
external: [
'CardTemplate',
'CreditCard',
'CreditCardForm',
'SvgIcon',
'AutonaviMap',
'ChartWordcloud',
'ChartLiquidfill'
]
}
const echartsVersion = getVersion('echarts')
const auroraVueVersion = getVersion('@opentiny/vue')
const echartsSource = 'lib/echarts.min.js' + echartsVersion
const dependencies = {
vue: 'node_modules/vue/dist/vue.min.js',
'vue-i18n': 'node_modules/vue-i18n/dist/vue-i18n.min.js',
axios: 'node_modules/axios/dist/axios.min.js',
'axios-mock-adapter':
'node_modules/axios-mock-adapter/dist/axios-mock-adapter.min.js',
'@vue/composition-api':
'node_modules/@vue/composition-api/dist/vue-composition-api.prod.js',
'@aurora/core': 'node_modules/@aurora/core/dist/aurora.min.js',
'@aurora/service': 'node_modules/@aurora/service/dist/aurora.service.min.js',
cropperjs: 'node_modules/cropperjs/dist/cropper.min.js',
vue3: 'example/node_modules/vue/dist/vue.global.prod.js',
'vue3-i18n': 'example/node_modules/vue-i18n/dist/vue-i18n.global.js'
}
const runtimeDeps = {
base: {
vue: 'lib/vue.min.js' + getVersion('vue'),
axios: 'lib/axios.min.js' + getVersion('axios'),
'vue-i18n': 'lib/vue-i18n.min.js' + getVersion('vue-i18n'),
'axios-mock-adapter':
'lib/axios-mock-adapter.min.js' + getVersion('axios-mock-adapter')
},
aurora: {
'@aurora/core': 'lib/aurora.min.js' + getVersion('@aurora/core'),
'@aurora/service':
'lib/aurora.service.min.js' + getVersion('@aurora/service')
},
aui3Lib: {
'@vue/composition-api':
'lib/vue-composition-api.prod.js' + getVersion('@vue/composition-api'),
echarts: echartsSource,
'echarts/lib/echarts': echartsSource,
'echarts/lib/chart/bar': echartsSource,
'echarts/lib/chart/boxplot': echartsSource,
'echarts/lib/chart/candlestick': echartsSource,
'echarts/lib/chart/chord': echartsSource,
'echarts/lib/chart/custom': echartsSource,
'echarts/lib/chart/effectScatter': echartsSource,
'echarts/lib/chart/funnel': echartsSource,
'echarts/lib/chart/gauge': echartsSource,
'echarts/lib/chart/graph': echartsSource,
'echarts/lib/chart/heatmap': echartsSource,
'echarts/lib/chart/line': echartsSource,
'echarts/lib/chart/lines': echartsSource,
'echarts/lib/chart/map': echartsSource,
'echarts/lib/chart/parallel': echartsSource,
'echarts/lib/chart/pictorialBar': echartsSource,
'echarts/lib/chart/pie': echartsSource,
'echarts/lib/chart/radar': echartsSource,
'echarts/lib/chart/sankey': echartsSource,
'echarts/lib/chart/scatter': echartsSource,
'echarts/lib/chart/sunburst': echartsSource,
'echarts/lib/chart/themeRiver': echartsSource,
'echarts/lib/chart/tree': echartsSource,
'echarts/lib/chart/treemap': echartsSource,
'echarts/lib/component/legend': echartsSource,
'echarts/lib/component/tooltip': echartsSource,
'echarts/lib/component/dataZoom': echartsSource,
'echarts/lib/component/visualMap': echartsSource,
cropperjs: 'lib/cropper.min.js' + getVersion('cropperjs'),
'@opentiny/vue-renderless-common':
'aui/common/renderless.js' +
getVersion('@opentiny/vue-renderless')
},
aui3Component: {
'@opentiny/vue-locale':
'COMPONENT_DIR/locale.js' + auroraVueVersion,
'@opentiny/vue-icon': 'COMPONENT_DIR/icon.js' + auroraVueVersion,
'@opentiny/vue-common': 'COMPONENT_DIR/common.js' + auroraVueVersion
}
}
const getPartDeps = (keys = []) => {
const tempDeps = {}
for (let key in runtimeDeps) {
if (Object.prototype.hasOwnProperty.call(runtimeDeps, key)) {
keys.includes(key) && Object.assign(tempDeps, runtimeDeps[key])
}
}
return tempDeps
}
const getAllDeps = () => {
return getPartDeps(Object.keys(runtimeDeps))
}
const getAllComponents = () => {
const componentMap = moduleUtils.getPcComponents(true)
const systemMap = {}
for (let i = 0, len = componentMap.length; i < len; i++) {
const libName = componentMap[i].LowerName
const libEntry = componentMap[i].path
let version = auroraVueVersion
try {
version =
'?v=' +
require('../' +
libEntry.replace('index.js', 'package.json')).version.replace(
/[\^|~]/g,
''
)
} catch (e) {
logRed(e)
}
systemMap[componentMap[i].importName] =
'COMPONENT_DIR/' + libName + '.js' + version
}
return systemMap
}
const getFullRuntimeDeps = () => {
return { ...getAllDeps(), ...getAllComponents() }
}
const getComponentRuntimeDeps = () => {
return {
...getPartDeps(['theme', 'aui3Lib', 'aui3Component']),
...getAllComponents()
}
}
const getFullRuntime = (name) => {
const arr =
typeof name === 'string' ? [name] : name || Object.keys(runtimeComponents)
return [].concat(...arr.map((key) => runtimeComponents[key]))
}
/**
* 提取 @opentiny/vue-renderless 中的公共代码打成独立包避免组件运行不正常
*/
const getRenderlessExports = () => {
const RENDERLESS_PATH = pathJoin(
'..',
'node_modules',
'@aurora',
'renderless',
'common',
'runtime.js'
)
let EXTERNAL_RENDERLESS = []
// 获取需要排除的依赖
if (fs.existsSync(RENDERLESS_PATH)) {
const runtimeExport = fs.readFileSync(RENDERLESS_PATH).toString('UTF-8')
EXTERNAL_RENDERLESS = runtimeExport.match(/import(.*)from(.*)/g) || []
EXTERNAL_RENDERLESS = EXTERNAL_RENDERLESS.map((item) => {
let moduleLine = item.replace(/^import\s+/g, '').split(/\s+from\s+/)
return {
libraryName: moduleLine.pop().replace(/'/g, '').replace(';', ''),
exportName: moduleLine.pop().split(/\s/).pop()
}
})
}
return {
EXTERNAL_RENDERLESS,
RENDERLESS_PATH
}
}
module.exports = {
dependencies,
getAllDeps,
getPartDeps,
getFullRuntime,
getAllComponents,
getFullRuntimeDeps,
getRenderlessExports,
getComponentRuntimeDeps,
getExternalComponents: () => runtimeComponents.external
}

88
build/sync-icons.js Normal file
View File

@ -0,0 +1,88 @@
/**
* 初始化/创建 ICON 组件 @opentiny/vue-theme/svgs 中提取 SVG 图标创建对应的 ICON 组件
*/
const path = require('path')
const fs = require('fs-extra')
const utils = require('./utils')
const semver = require('semver')
const { EOL } = require('os')
const svgRE = /\.svg$/
const svgDir = utils.pathJoin('../../', 'vue-theme', 'theme', 'svgs')
const iconDir = utils.pathJoin('..', 'packages', 'icon')
const packageJson = 'package.json'
const templatePath = utils.pathJoin('..', 'template')
const render = utils.renderTemplate({ leftChar: '[[', rightChar: ']]' })
// 检查是否按照依赖包
if (!fs.existsSync(svgDir)) {
utils.logYellow('The @opentiny/vue-theme is not exist , please npm install @opentiny/vue-theme.')
}
// 是否包含 package/icon 目录
if (!fs.existsSync(iconDir)) {
fs.mkdirSync(iconDir)
const version = utils.getTinyVersion()
const iconTemplate = fs.readJSONSync(path.join(templatePath, 'component', packageJson))
// 删除多余的依赖
if (iconTemplate.dependencies) {
delete iconTemplate.dependencies['@opentiny/vue-renderless']
}
const packageContent = render(JSON.stringify(iconTemplate), {
NAME: 'icon',
MINOR: semver.minor(version)
})
fs.writeFileSync(path.join(iconDir, packageJson), packageContent)
}
const exportComponents = []
const exportIcons = []
const componentTemplate = fs.readFileSync(path.join(templatePath, 'icon', 'index.js'), { encoding: 'utf8' })
// 根据 @opentiny/vue-theme/svgs 中的 svg 图片创建对应的 icon 组件
fs.readdirSync(svgDir).forEach((fileName) => {
if (svgRE.test(fileName)) {
const svgName = fileName.replace(svgRE, '')
const iconPath = path.join(iconDir, svgName)
const iconName = utils.capitalizeKebabCase(svgName)
const fullIconName = `Icon${iconName}`
if (!fs.existsSync(iconPath)) {
fs.mkdirSync(iconPath)
const iconEntryContent = render(componentTemplate, {
CNAME: iconName,
SNAME: fileName
})
fs.writeFileSync(path.join(iconPath, 'index.js'), utils.prettierFormat({ str: iconEntryContent }))
}
exportComponents.push(`import ${fullIconName} from './${svgName}'`)
exportIcons.push(fullIconName)
}
})
if (exportComponents.length) {
fs.writeFileSync(
path.join(iconDir, 'index.js'),
utils.prettierFormat({
str: `${exportComponents.join(EOL)}
export {
${exportIcons.join(',' + EOL)}
}
export default {
${exportIcons.join(',' + EOL)}
}
`
})
)
utils.logGreen('npm run create:icon done.')
} else {
utils.logRed('npm run create:icon fail.')
}

309
build/utils.js Normal file
View File

@ -0,0 +1,309 @@
const fs = require('fs-extra')
const path = require('path')
const chalk = require('chalk')
const { execSync } = require('child_process')
const log = global.console
const logger = log.log
/**
* 根据运行上下文获取路径运行时打包用
* @returns 文件绝对路径
*/
const resolveCwd = (...args) => path.join(process.cwd(), ...args)
/**
* 获取模板替换路径动态
* @param {String} posixPath 路径
* @returns 文件绝对路径
*/
const assetsPath = (posixPath) => path.posix.join('static', posixPath)
/**
* 根据运行上下文获取当前运行组件的名称
* @returns 当前运行组件目录名称
*/
const getComponentName = () => process.cwd().split(path.sep).pop()
/**
* 获取当前上下文的路径
* @returns 文件绝对路径
*/
const pathJoin = (...args) => path.join(__dirname, ...args)
/**
* 获取用户输入命令参数
* @returns 参数数组
*/
const getInputCmd = () => {
const args = []
const argv = process.argv || []
argv.forEach((item) => {
if (item.indexOf(path.sep) === -1) {
args.push(item)
}
})
return args
}
/**
* 获取当前执行 cli 命令的工具node\npm\yarn
* @returns node\npm\yarn
*/
const getCurrentCliTool = () => {
const npmExecpaths = process.env.npm_execpaths
if (!npmExecpaths) {
return 'node'
}
return npmExecpaths.substring(npmExecpaths.lastIndexOf(path.sep) + 1).replace(/.js|-cli/g, '')
}
/**
* 执行 node 命令
* @param {String} cmdStr 命令字符串
*/
const execCmd = (cmdStr) => {
cmdStr && execSync(cmdStr, { stdio: 'inherit' })
}
/**
* 首字母大写
* @param {String} str 字符串
* @returns 字符串
*/
const capitalize = (str) => (typeof str === 'string' ? str.slice(0, 1).toUpperCase() + str.slice(1) : str)
/**
* 首字母大写
* @param {String} str 字符串
* @returns 字符串
*/
const capitalizeKebabCase = (str, splitChar = '-') => (typeof str === 'string' ? str.split(splitChar).map(capitalize).join('') : str)
/**
* @description 将驼峰字符串转化为以指定字符分割的小写字符串
* @example kebabCase({ str : 'ImgPreviewItem' } )
* @example 输出结果img-preview-item
*
* @param str 字符串
* @param splitChar 分隔符
*/
const kebabCase = ({ str, splitChar = '-' }) => {
if (!str || typeof str !== 'string') {
return str
}
return str
.split('')
.map((char, index) => {
const charCod = char.charCodeAt(0)
if (charCod < 65 || charCod > 122) {
return char
}
return (charCod >= 65 && charCod) <= 90 ? (index === 0 ? '' : splitChar) + char.toLowerCase() : char
})
.join('')
}
/**
* 采用 prettier 美化字符串
* @param {String} str 格式字符
* @param {Object} options 格式字符
*/
const prettierFormat = ({ str, options = {} }) =>
require('prettier').format(str, {
printWidth: 100,
jsxBracketSameLine: false,
tabWidth: 2,
useTabs: false,
singleQuote: true,
semi: false,
trailingComma: 'none',
bracketSpacing: true,
parser: 'babel',
...options
})
/**
* @private
* @param {String} path dotted to indicate levels in an object.
* @param {Object} view for the data.
*/
function extractValue(path, view) {
if (view && view[path]) {
return view[path]
}
const parts = path.split('.')
let part = ''
while (view && (part = parts.shift())) {
view = typeof view === 'object' && part in view ? view[part] : undefined
}
return view
}
/**
* 渲染字符串模板
* @param {String} leftChar 匹配左边字符
* @param {String} rightChar 匹配右边字符
*/
const renderTemplate = ({ leftChar = '{{', rightChar = '}}' } = {}) => {
const specialChar = ['[', ']']
const _leftChar = leftChar.split('').map((item) => (specialChar.includes(item) ? '\\' : '') + item)
const _rightChar = rightChar.split('').map((item) => (specialChar.includes(item) ? '\\' : '') + item)
const REGEX = new RegExp(`${_leftChar.join('')}([a-zA-Z.-_0-9]+)${_rightChar.join('')}`, 'g')
return (input, view) => {
if (input.indexOf(leftChar) === -1) {
return input
}
let result
const replaced = input.replace(REGEX, (original, path) => {
const value = extractValue(path, view)
if (undefined === value || value === null) {
return original
}
if (typeof value === 'object') {
result = value
return
}
return value
})
return undefined === result ? replaced : result
}
}
/**
* 扫描指定目录下面的组件目录
* @param {String} dirPath 绝对路径
* @param {Boolean} isDeep 是否深度遍历
* @param {Function} fileFilter 文件筛选拦截函数
* @param {Function} callback 遍历回调
*/
const walkFileTree = ({ dirPath, isDeep = false, fileFilter, callback }) => {
if (!dirPath || typeof callback !== 'function') {
return
}
const dirs = fs.readdirSync(path.isAbsolute(dirPath) ? dirPath : path.join(__dirname, dirPath))
if (Array.isArray(dirs) && dirs.length > 0) {
dirs.forEach((file) => {
let isFind = true
const subPath = path.join(dirPath, file)
const isDirectory = fs.statSync(subPath).isDirectory()
if (typeof fileFilter === 'function') {
isFind = fileFilter({ file, subPath, dirs, isDirectory }) === true
}
if (isFind && isDirectory) {
if (isDeep) {
walkFileTree({ isDeep, dirPath: subPath, fileFilter, callback })
return
}
}
callback({ file, subPath, dirs, isDirectory })
})
}
}
/**
* 获取根目录 package.json 中的 version
* @param {String} 对象的 Key
*/
const getTinyVersion = (key = 'version') => {
const packageJson = fs.readJsonSync(pathJoin('..', 'package.json'))
return packageJson[key] || packageJson
}
/**
* 获取指定包名的版本号
*
* @param {String} name NPM 包名
* @param {String} context 上下文
* @returns
*/
const getVersion = (name, context = '..') => {
let version
const packageJSON = getTinyVersion('full')
try {
version = fs.readJsonSync(pathJoin(context, 'node_modules', name, 'package.json')).version
} catch (e) {
version = (packageJSON.devDependencies || packageJSON.dependencies || {})[name] || packageJSON.version
}
return '?v=' + version.replace(/[\^|~]/g, '')
}
/**
* 在控制台显示绿色提示
* @param {String} 提示内容
*/
const logGreen = (str) => {
logger(chalk.green('### ' + str))
}
/**
* 在控制台显示黄色提示
* @param {String} 提示内容
*/
const logYellow = (str) => {
logger(chalk.yellow('### ' + str))
}
/**
* 在控制台显示青色提示
* @param {String} 提示内容
*/
const logCyan = (str) => {
logger(chalk.cyan('### ' + str))
}
/**
* 在控制台显示红色提示
* @param {String} 提示内容
*/
const logRed = (str) => {
logger(chalk.red('### ' + str))
}
module.exports = {
logRed,
execCmd,
logCyan,
pathJoin,
logGreen,
logYellow,
kebabCase,
assetsPath,
capitalize,
getVersion,
resolveCwd,
getInputCmd,
walkFileTree,
renderTemplate,
prettierFormat,
getTinyVersion,
getComponentName,
getCurrentCliTool,
capitalizeKebabCase
}

1
commitlint.config.js Normal file
View File

@ -0,0 +1 @@
module.exports = { extends: ['@commitlint/config-conventional'] }

33
deleteDist.js Normal file
View File

@ -0,0 +1,33 @@
const path = require('path')
const fs = require('fs-extra')
const ROOTPATH = path.join(__dirname, './')
const publish = () => {
const publishDir = path.join(ROOTPATH, 'packages')
fs.readdirSync(publishDir).forEach((item) => {
const childPath = path.join(publishDir, item)
const stat = fs.statSync(childPath)
if (stat.isDirectory()) {
const distPath = path.join(childPath, './dist')
if (fs.existsSync(distPath) && fs.statSync(distPath).isDirectory()) {
fs.removeSync(distPath)
}
if (item.startsWith('chart')) {
fs.readdirSync(childPath).forEach((value) => {
const chartChildPath = path.join(childPath, value)
const chartStat = fs.statSync(chartChildPath)
if (value.includes('-') && chartStat.isDirectory()) {
const distPath = path.join(chartChildPath, './dist')
fs.existsSync(distPath) &&
fs.statSync(distPath).isDirectory() &&
fs.removeSync(distPath)
}
})
}
}
})
}
publish()

6
example/.eslintrc Normal file
View File

@ -0,0 +1,6 @@
{
"rules": {
"no-console": "off",
"no-unused-vars": "off"
}
}

View File

@ -0,0 +1,88 @@
const commonjs = require('@rollup/plugin-commonjs')
const { nodeResolve } = require('@rollup/plugin-node-resolve')
const vue = require('rollup-plugin-vue')
const { babel } = require('@rollup/plugin-babel')
const { pathJoin, logGreen, logRed } = require('../../build/utils')
const rollup = require('rollup')
const svgVue = require('./rollup-vue3-svg')
const fs = require('fs-extra')
const path = require('path')
const inputOptions = {
plugins: [
vue({
css: true
}),
svgVue({}),
nodeResolve(),
babel({
exclude: /node_modules/,
babelrc: false,
configFile: false, // 必须为 false 不然会取根文件的 babel.config.js 配置,产生一堆 runtime 代码
babelHelpers: 'bundled',
extensions: ['.js', '.vue'],
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-proposal-export-default-from', '@babel/plugin-proposal-export-namespace-from']
}),
// 如果打包文件中包含 jsx 语法, commonjs 必须放置在 babel 配置下面,否则会报错 PLUGIN_ERROR
commonjs()
]
}
const outputOptions = {
format: 'es',
exports: 'named'
}
const build = (icons) => {
icons.forEach((itconComponent) => {
const inputs3 = { ...inputOptions }
inputs3.input = pathJoin('..', 'packages', 'icon', itconComponent.path)
if (itconComponent.path === 'index.js') {
inputs3.external = (deps) => !deps.includes('index.js')
} else if (itconComponent.path === 'lowercase.js') {
inputs3.external = (deps) => !deps.includes('lowercase.js')
} else {
inputs3.external = (deps) => !/@opentiny[\\/]vue-theme/.test(deps) && !deps.includes('index.js')
}
rollup
.rollup(inputs3)
.then((bundle) => {
const outs = { ...outputOptions }
outs.file = pathJoin('..', 'packages', 'icon', itconComponent.libPath)
bundle.write(outs)
logGreen(`${itconComponent.path} compile icon done`)
})
.catch((e) => {
logRed(e)
})
})
}
function createComponentMap(iconDir) {
const components = []
fs.readdirSync(iconDir).forEach((iconFile) => {
if (['dist', 'runtime'].includes(iconFile)) {
return
}
if (fs.statSync(path.join(iconDir, iconFile)).isDirectory()) {
components.push({
path: `${iconFile}/index.js`,
libPath: `dist/lib/${iconFile}.js`
})
} else {
iconFile.endsWith('.js') &&
components.push({
path: `${iconFile}`,
libPath: `dist/lib/${iconFile}`
})
}
})
return components
}
build(createComponentMap(pathJoin('..', 'packages', 'icon')))

107
example/build/build-ui.js Normal file
View File

@ -0,0 +1,107 @@
const rollup = require('rollup')
const replace = require('@rollup/plugin-replace')
const { readJSONSync } = require('fs-extra')
const utils = require('../../build/utils')
const config = require('./config')
const moduleUtils = require('../../build/module-utils')
const fs = require('fs-extra')
const isSingle = process.env.BUILD_TARGET === 'single'
const inputOptions = {
plugins: config.plugins,
external: config.external
}
const outputOptions = {
format: 'es',
globals: config.globals,
exports: 'named'
}
const replaceConstant = {
'process.env.BUILD_TARGET': JSON.stringify(process.env.BUILD_TARGET),
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}
if (process.env.TINY_MODE === 'pc') {
outputOptions.format = 'umd'
replaceConstant['process.env.TINY_MODE'] = JSON.stringify(process.env.tiny_mode)
}
const build = ({ comp, callbackFn }) => {
inputOptions.input = utils.pathJoin('..', comp.path)
inputOptions.plugins.push(replace(replaceConstant))
rollup
.rollup(inputOptions)
.then((bundle) => {
outputOptions.file = utils.pathJoin('..', comp.libPath)
if (outputOptions.format === 'umd') {
outputOptions.name = comp.global
}
bundle.write(outputOptions).finally(() => {
const filePath = utils.pathJoin('..', comp.libPath)
if (filePath.endsWith('index.js')) {
const indexStr = fs.readFileSync(filePath).toString('UTF-8')
const resStr = indexStr.replace('./src/pc', './pc').replace('./src/mobile', './mobile')
fs.writeFileSync(filePath, resStr)
}
callbackFn()
})
})
.catch((e) => {
utils.logRed(e)
callbackFn()
})
}
let componentsArr = []
const buildAll = (count = 0) => {
let comp = componentsArr[count++]
if (comp) {
if (!isSingle) {
comp.libPath = 'dist/' + comp.libName.replace('@opentiny/vue/', '')
comp.libPath += (comp.type === 'component' ? '/index' : '') + '.js'
}
build({
comp,
callbackFn() {
buildAll(count)
}
})
} else {
utils.logGreen(`npm run build:ui${isSingle ? '-single' : ''} done.`)
}
}
if (isSingle) {
const inputNameArr = utils.getInputCmd()
if (inputNameArr.length > 0) {
inputNameArr.forEach((input) => {
const activeComName = utils.kebabCase({ str: input })
if (activeComName) {
componentsArr.push(
...moduleUtils.getByName({
name: activeComName,
isSort: false
})
)
}
})
} else {
const activeCompName = utils.getComponentName()
componentsArr = moduleUtils.getByName({
name: activeCompName,
isSort: false
})
}
} else {
componentsArr = moduleUtils.getAllModules(false)
}
if (componentsArr.length > 0) {
buildAll()
} else {
utils.logYellow('please enter the component name after command.')
}

88
example/build/config.js Normal file
View File

@ -0,0 +1,88 @@
const fs = require('fs-extra')
const { babel } = require('@rollup/plugin-babel')
const vue = require('rollup-plugin-vue')
const alias = require('@rollup/plugin-alias')
const commonjs = require('@rollup/plugin-commonjs')
const postcss = require('rollup-plugin-postcss')
const { nodeResolve } = require('@rollup/plugin-node-resolve')
const { pathJoin } = require('../../build/utils')
const { getAllModules } = require('../../build/module-utils')
const external = ['vue', './pc', './mobile', '@opentiny/vue-common', '@opentiny/vue-locale', '@vue/composition-api', '@opentiny/vue-renderless']
const globals = {
vue: 'Vue',
'@vue/composition-api': 'vueCompositionApi',
'@opentiny/vue-locale': 'TinyVueLocale',
'@opentiny/vue-common': 'TinyVueCommon',
'@opentiny/vue-renderless': 'TinyRenderLess'
}
const aliasList = {}
const components = getAllModules(false)
components.forEach((itemComponent) => {
aliasList[itemComponent.libName] = pathJoin(`../${itemComponent.path}`)
if (itemComponent.private) {
return
}
const isComponent = itemComponent.type === 'component'
external.push(itemComponent.importName)
external.push(itemComponent.libName)
globals[itemComponent.libName] = itemComponent.global // TinyTodo
if (isComponent) {
if (fs.existsSync(pathJoin('../../tiny-vue-theme'))) {
aliasList[`@opentiny/vue-theme/${itemComponent.LowerName}/index.css`] = pathJoin(`../../tiny-vue-theme/src/${itemComponent.LowerName}/index.css`)
aliasList[`@opentiny/vue-theme/${itemComponent.LowerName}/index.js`] = pathJoin(`../../tiny-vue-theme/src/${itemComponent.LowerName}/index.js`)
}
external.push(`${itemComponent.libName}/index.js`)
} else {
external.push(`${itemComponent.libName}.js`)
}
})
exports.aliasList = aliasList
exports.external = (deps) => external.includes(deps) || /^@opentiny[\\/](vue-common|vue-renderless|vue-theme|vue-icon|cropperjs)/.test(deps)
exports.globalsMap = globals
const op = {
resolve: ['.js', '.vue', '.css'],
extract: false,
css: true,
extensions: ['.js', '.jsx', '.vue', '.css']
}
exports.plugins = [
alias({
resolve: op.resolve,
'@opentiny/vue-locale': pathJoin('../packages/locale/index'),
'@opentiny/vue-common': pathJoin('../packages/common/index'),
...aliasList
}),
postcss({
extract: op.extract
}),
vue({
css: op.css
}),
nodeResolve({
extensions: op.extensions
}),
babel({
configFile: false,
babelrc: false,
exclude: /node_modules/,
comments: false,
presets: ['@babel/preset-env'],
babelHelpers: 'bundled',
extensions: ['.js', '.vue', '.jsx'],
plugins: ['@babel/plugin-syntax-dynamic-import', '@vue/babel-plugin-jsx']
}),
commonjs()
]

View File

@ -0,0 +1,25 @@
let compilerDom = require('@vue/compiler-dom')
function vue3SvgInline() {
return {
name: 'vue3SvgInline',
transform(source, id) {
if (id.indexOf('vue-theme') === -1) {
return null
}
const parsedSvg = source.match(/<svg([\s\S]*?)<\/svg>/)[0]
const { code } = compilerDom.compile(parsedSvg, {
mode: 'module'
})
return `
${code}
export default {
render
}`
}
}
}
module.exports = vue3SvgInline

13
example/index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tiny Vue</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main3.js"></script>
</body>
</html>

45
example/package.json Normal file
View File

@ -0,0 +1,45 @@
{
"name": "vue-example",
"version": "0.1.0",
"scripts": {
"dev": "vite",
"test": "vitest test",
"build:icon3": "cross-env NODE_ENV=production node build/build-icon.js",
"coverage": "vitest run --coverage"
},
"devDependencies": {
"@vue/babel-plugin-jsx": "^1.1.0",
"rollup-plugin-postcss": "^2.0.3",
"rollup-plugin-vue": "^6.0.0",
"vue": "^3.2.11",
"@vue/test-utils": "^2.0.0",
"jsdom": "16.4.0",
"vue-i18n": "^9.1.7",
"vitest": "^0.22.1",
"vue-router": "^4.0.11",
"@vitejs/plugin-vue": "^3.0.3",
"@vitejs/plugin-vue-jsx": "^2.0.0",
"vite-plugin-markdown-vue": "^0.1.2"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {
"no-debugger": "off"
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

BIN
example/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

19
example/public/index.html Normal file
View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="./favicon.ico" />
<title> TinyVue </title>
</head>
<body>
<noscript>
<strong>We're sorry but TINY next - Originjs doesn't work properly without
JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="head"></div>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

3
example/src/App.vue Normal file
View File

@ -0,0 +1,3 @@
<template>
<router-view />
</template>

22
example/src/DemoView.vue Normal file
View File

@ -0,0 +1,22 @@
<template>
<div style="width: 870px">
<component :is="template"></component>
</div>
</template>
<script>
import { defineAsyncComponent } from '@opentiny/vue-common'
export default {
props: {
link: String
},
setup(props) {
const mode = localStorage.getItem('vue-example-mode') || 'pc'
return {
template: defineAsyncComponent(() => import(/* @vite-ignore */ `./demo/${mode}/${props.link}`))
}
}
}
</script>

BIN
example/src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -0,0 +1,559 @@
.hljs {
margin: 12px 0 25px 0;
}
.highlight .hljs {
margin: 0;
}
code {
background-color: #faffff;
border-radius: 4px;
padding: 1px 3px;
font-family: Menlo, YaHei Consolas Hybrid, Consolas, Courier New, monospace;
font-size: 13px;
}
button,
input,
select,
textarea {
font-family: inherit;
font-size: inherit;
line-height: inherit;
color: inherit;
}
a {
display: inline-block;
white-space: nowrap;
cursor: pointer;
background-image: none;
text-decoration: none;
outline: 0;
color: var(--ti-common-color-line-active);
}
input::-ms-clear,
input::-ms-reveal {
display: none;
}
.main-cnt {
padding: 80px 0 40px;
box-sizing: border-box;
height: 100%;
}
.page-container {
width: 1140px;
padding: 0;
margin: 0 auto;
}
.page-container {
padding-top: 0px;
}
.page-container h2 {
font-size: 28px;
color: #1f2d3d;
margin: 0;
padding-bottom: 10px;
display: inline-block;
}
.page-container h2 + p {
display: inline-block;
}
.displaywrap .page-container h2 + p {
display: block;
}
.page-container h3 {
font-size: 22px;
}
.page-container h2,
.page-container h3,
.page-container h4,
.page-container h5 {
font-weight: 400;
color: #1f2f3d;
}
.page-container h2 a,
.page-container h3 a,
.page-container h4 a,
.page-container h5 a {
float: left;
margin-left: -20px;
opacity: 0;
cursor: pointer;
}
.page-container h2 a:hover,
.page-container h2:hover a,
.page-container h3 a:hover,
.page-container h3:hover a,
.page-container h4 a:hover,
.page-container h4:hover a,
.page-container h5 a:hover,
.page-container h5:hover a {
opacity: 0.4;
}
.page-container p {
font-size: 16px;
color: #5e6d82;
line-height: 1.7em;
}
.page-container .tip {
padding: 8px 16px;
background-color: #ecf8ff;
border-radius: 4px;
border-left: 5px solid #50bfff;
margin: 20px 0;
font-size: 14px;
color: #5e6d82;
}
.page-container .tip .highlight-lines,
.page-container .tip .line-numbers-wrapper {
font-size: 12px;
}
.page-container .tip code {
font-size: 14px;
line-height: 2em;
}
.page-container .warn {
padding: 8px 16px;
background-color: #fffbe6;
border-radius: 4px;
border-left: 5px solid #ffd666;
margin: 20px 0;
}
.page-container .warn code {
background-color: hsla(0, 0%, 100%, 0.7);
color: #445368;
}
.page-container .error {
padding: 8px 16px;
background-color: #fff1f0;
border-radius: 4px;
border-left: 5px solid #ff7875;
margin: 20px 0;
}
.page-container .attach-icon .hae-icon.error,
.page-container .attach-icon .hae-icon.warn {
padding: 0;
margin: 0;
border: 0;
border-radius: 0;
background: none;
}
.page-container .error code {
background-color: hsla(0, 0%, 100%, 0.7);
color: #445368;
}
.page-container.academy-container {
width: 100%;
padding: 0;
margin: 0;
}
.page-container.academy-container .main-left h2,
.page-container.academy-container .main-left h2 + p {
display: block;
}
.page-container.academy-container .main-left h2 {
padding: 10px 0;
}
.page-container.academy-container .main-left h3 {
margin: 40px 0 20px 0;
}
.page-container.academy-container .main-left p > img,
.page-container.academy-container .main-left img {
max-width: 1200px;
margin: 10px 0;
}
.page-container.academy-container .main-left code.hljs {
max-width: 720px;
}
.page-container.academy-container .main-left hr {
max-width: 1200px;
margin-left: 0;
}
.page-container.academy-container .main-left ul {
list-style: disc inside;
padding-left: 12px;
margin: 12px 0;
}
.page-container.academy-container .main-left ul > li {
font-size: 14px;
line-height: 36px;
}
.page-container.academy-container .main-left ul > li > p {
display: inline-block;
}
.page-container.academy-container .main-left ul > li > ul {
padding-left: 24px;
}
/* vue-press */
.content code {
color: #476582;
padding: 0.25rem 0.5rem;
margin: 0;
font-size: 0.85em;
background-color: rgba(27, 31, 35, 0.05);
border-radius: 3px;
}
.content code,
.content pre,
.content kbd,
.content samp {
font-family: 'source-code-pro', 'Menlo', 'Monaco', 'Consolas', 'Courier New',
'monospace';
}
.custom-block .custom-block-title {
font-weight: 600;
margin-bottom: -0.4rem;
}
.page-container .content .table {
width: 100%;
text-align: left;
font-size: 14px;
margin: 10px 0;
border-collapse: collapse;
}
.page-container .content .table th,
.page-container .content .table td {
color: #333;
padding: 12px;
border: 1px solid #ddd;
vertical-align: top;
}
.page-container .content .table th {
background-color: #eee;
}
.page-container .content .table td:first-child {
width: 15%;
}
.page-container .content blockquote {
border-left: 4px solid var(--theme-color, #42b983);
color: #858585;
margin: 2em 0;
padding-left: 20px;
}
.demo-popover-class1 {
background: #eee !important;
}
.demo-popover-class2 {
background: #999 !important;
}
/* hightline */
.page-main.noborder .content > h2:first-of-type {
border-bottom: 0;
margin-bottom: 0;
}
.page-main.noborder .content > p.overviewicon {
margin-right: 20px;
}
.page-main:not(.noborder) .content > h2 {
width: 100%;
}
.content > h2:not(:first-of-type) {
margin-top: 36px;
}
.content .badge.warn {
border-left: 0;
margin: 0;
}
.content > h2 {
font-size: 1.65rem;
padding-bottom: 0.3rem;
margin-bottom: 16px;
text-indent: -1px;
}
.content > h3 {
margin: 40px 0 20px 0;
padding-bottom: 0.3rem;
border-bottom: 1px solid #eaecef;
}
.content > h4 {
font-size: 20px;
padding-top: 24px;
}
.content > p > code {
font-size: 14px;
}
.content > p > strong {
color: #2c3e50;
}
.content pre code {
font-size: 14px;
font-family: 'source-code-pro', 'Menlo', 'Monaco', 'Consolas', 'Courier New',
'monospace';
}
.content pre code.hljs {
padding: 18px;
border-radius: 6px;
}
.content .apiBox pre.preview-code {
background-color: transparent;
}
.content .visual-editor pre {
background-color: transparent;
padding: 0;
font-size: 14px;
}
.content > ul > li {
color: #5e6d82;
}
.content .apiContainer ul,
.content .visual-editor ul,
.content .attrContainer ul {
list-style: none;
margin: 0;
padding: 0;
}
.content .float-css ul {
font-size: 14px;
margin: 0;
padding: 0;
}
.content .box_all ul {
margin: 0;
}
.content .logBox ul {
list-style: none;
}
.content ul ul {
list-style-type: circle;
}
.content .visual-editor ul {
font-size: 12px;
}
.content .hae-carousel ul {
padding: 0 4px;
line-height: 1.42857143;
font-family: Helvetica, Arial, 'microsoft yahei';
}
.content a.hae-icon.outer-link:hover,
.content p a:hover {
text-decoration: underline;
}
.content a.hae-icon.outer-link::after {
content: '\E840';
font-size: 12px;
color: #999;
position: relative;
top: -6px;
left: 2px;
}
.content > .custom-block > p {
margin: 16px 0;
}
.content div[class*='language-'] {
position: relative;
background-color: #282c34;
border-radius: 6px;
}
.custom-block.danger,
.custom-block.tip,
.custom-block.warning {
padding: 0.1rem 1.5rem;
border-left-width: 0.5rem;
border-left-style: solid;
margin: 1rem 0;
}
.content .custom-block.tip {
background-color: #f3f5f7;
border-color: #42b983;
border-radius: 0;
}
.content .custom-block.warning {
background-color: rgba(255, 229, 100, 0.3);
border-color: #e7c000;
color: #6b5900;
}
.content .custom-block.warning .custom-block-title {
color: #b29400;
}
.content .custom-block.danger {
background-color: #ffe6e6;
border-color: #c00;
color: #4d0000;
}
.content .custom-block.danger .custom-block-title {
color: #900;
}
div[class*='language-'] .highlight-lines {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
padding-top: 1.3rem;
position: absolute;
top: 0;
left: 0;
width: 100%;
line-height: 1.7;
}
div[class*='language-'] .highlight-lines .highlighted {
background-color: rgba(0, 0, 0, 0.66);
}
div[class*='language-'] pre,
div[class*='language-'] pre[class*='language-'] {
background: transparent;
position: relative;
z-index: 1;
}
.content pre,
.content pre[class*='language-'] {
line-height: 1.4;
padding: 1.25rem 1.5rem;
margin: 0.85rem 0;
background-color: #282c34;
border-radius: 6px;
overflow: auto;
}
.content pre[class*='language-'] code,
.content pre code {
color: #fff;
padding: 0;
background-color: transparent;
border-radius: 0;
line-height: 1.42857143;
}
.token.atrule,
.token.builtin,
.token.important,
.token.keyword,
.token.selector {
color: #cc99cd;
}
.token.punctuation {
color: #ccc;
}
.token.boolean,
.token.function,
.token.number {
color: #f08d49;
}
.token.attr-value,
.token.char,
.token.regex,
.token.string,
.token.variable {
color: #7ec699;
}
.token.entity,
.token.operator,
.token.url {
color: #67cdcc;
}
.token.block-comment,
.token.cdata,
.token.comment,
.token.doctype,
.token.prolog {
color: #999;
}
.token.attr-name,
.token.deleted,
.token.namespace,
.token.tag {
color: #e2777a;
}
.token.function-name {
color: #6196cc;
}
.token.class-name,
.token.constant,
.token.property,
.token.symbol {
color: #f8c555;
}
.token.bold,
.token.important {
font-weight: 700;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
.token.inserted {
color: green;
}

View File

@ -0,0 +1,132 @@
.content .tiny-mobile-tabbar-demo {
position: relative;
width: 100%;
height: 100%;
}
.content .tiny-mobile-tabbar-demo .tiny-mobile-tabbar--fixed {
position: absolute;
}
.content .tiny-mobile-dialog-box-demo {
position: relative;
height: 100%;
padding: 20px;
}
.content .tiny-mobile-dialog-box-demo .v-modal,
.content .tiny-mobile-dialog-box__wrapper {
position: absolute;
}
.content .tiny-mobile-dropdown-menu-demo {
height: 610px;
position: relative;
}
.content .tiny-mobile-dropdown-menu-demo.mobile-dropdown-menu-wrap {
height: auto;
}
.content .tiny-mobile-dropdown-menu-demo.mobile-dropdown-menu-direction {
height: 300px;
top: 380px;
}
.content .tiny-mobile-dropdown-menu-demo.mobile-dropdown-menu-filter {
justify-content: left;
}
.content .tiny-mobile-dropdown-menu-demo .tiny-mobile-dropdown-item {
position: absolute;
width: 351px;
left: 0;
top: 42px !important;
}
.content
.tiny-mobile-dropdown-menu-demo.mobile-dropdown-menu-direction
.tiny-mobile-dropdown-item {
position: fixed;
left: 0;
right: 648px;
bottom: 0 !important;
top: 40px !important;
overflow: inherit;
}
.content
.tiny-mobile-dropdown-menu-demo.mobile-dropdown-menu-direction
.tiny-mobile-dropdown-item__content {
position: relative;
}
.content
.tiny-mobile-dropdown-menu-demo.mobile-dropdown-menu-wrap
.tiny-mobile-dropdown-item {
overflow: visible;
}
.content .tiny-mobile-dropdown-menu-demo .tiny-popup {
width: 351px;
}
.content .tiny-mobile-dropdown-menu-demo.mobile-dropdown-menu-wrap .tiny-popup {
height: auto;
max-height: initial;
}
.content .tiny-mobile-dropdown-menu-demo .tiny-overlay {
position: absolute;
}
.content
.tiny-mobile-dropdown-menu-demo.mobile-dropdown-menu-direction
.tiny-overlay {
top: 407px !important;
}
.content .tiny-mobile-image-viewer-demo {
height: 100%;
position: relative;
}
.content .tiny-mobile-image-viewer-demo .tiny-mobile-image-viewer__wrapper {
position: absolute;
}
.content .tiny-mobile-exception-demo {
height: 610px;
background: #ccc;
position: relative;
}
.content .tiny-mobile-exception-demo .tiny-mobile-exception {
position: absolute;
z-index: 99;
}
.content .tiny-mobile-exception-demo .tiny-mobile-exception__footer {
position: absolute;
}
.tiny-mobile-action-sheet-demo {
width: 100%;
height: 100%;
position: relative;
}
.tiny-mobile-action-sheet-demo .tiny-mobile-action-sheet {
height: 100%;
position: relative;
}
.tiny-mobile-action-sheet-demo.action-sheet-wrap .tiny-mobile-action-sheet {
height: calc(100% - 180px);
}
.tiny-mobile-action-sheet-demo.action-sheet-slot .tiny-mobile-action-sheet {
height: calc(100% - 195px);
}
.tiny-mobile-action-sheet-demo .tiny-mobile-action-sheet__mask,
.tiny-mobile-action-sheet-demo .tiny-mobile-action-sheet__content {
position: absolute;
/* height: 100%; */
bottom: 45px;
}
.tiny-mobile-mini-picker-demo {
width: 100%;
height: 100%;
}
.tiny-mobile-mini-picker-demo .tiny-mobile-mini-picker {
height: calc(100% - 48px);
position: relative;
}
.tiny-mobile-mini-picker-demo.mini-picker-wrap .tiny-mobile-mini-picker {
height: calc(100% - 200px);
position: relative;
}
.tiny-mobile-mini-picker-demo .tiny-mobile-button,
.tiny-mobile-mini-picker-demo .tiny-mobile-mini-picker__mask,
.tiny-mobile-mini-picker-demo .tiny-mobile-mini-picker__content {
position: absolute;
}
.tiny-mobile-mini-picker-demo .tiny-mobile-input-form__input {
text-align: right;
}

View File

@ -0,0 +1,118 @@
<template>
<div class="panel page-container">
<div class="left">
<tiny-tree-menu
_mode="pc"
:data="MenuData.component"
node-key="path"
accordion
:show-filter="false"
@node-click="nodeClick"
:default-expanded-keys="[defaultexpandedkeys]"
:current-node-key="defaultexpandedkeys"
>
<template #default="{ data }">
<span>{{ data.name }}</span>
</template>
</tiny-tree-menu>
</div>
<div class="right">
<router-view class="content"></router-view>
<div @click="switchMode" class="switch-mode">切换到 {{ isPc ? 'Mobile' : 'PC' }} 示例</div>
</div>
</div>
</template>
<script>
import { TreeMenu } from '@opentiny/vue'
import MenuDataMob from '@/nav.config.comp.mobile.json'
import MenuData from '@/nav.config.comp.json'
import '@/assets/markdown.css'
import '@/assets/tiny-mobile-demo.css'
export default {
components: {
TinyTreeMenu: TreeMenu
},
methods: {
nodeClick(node) {
node.path && this.$route.path !== node.path && this.$router.push(node.path)
},
switchMode() {
localStorage.setItem('vue-example-mode', this.isPc ? 'mobile' : 'pc')
location.hash = '#/'
location.reload()
}
},
computed: {
defaultexpandedkeys() {
return this.$route.path
},
isPc() {
return this.$root.tiny_mode.value !== 'mobile'
},
MenuData() {
return this.isPc ? MenuData : MenuDataMob
}
}
}
</script>
<style scoped>
.panel {
min-width: 800px;
width: 100%;
}
.left {
width: 250px;
float: left;
text-align: left;
font-size: 14px;
white-space: nowrap;
overflow-x: hidden;
background: #fff;
}
.left > div {
width: 250px;
position: fixed;
font-size: 12px;
overflow-y: scroll;
height: 100vh;
}
.right {
float: right;
text-align: left;
margin: 20px auto;
width: calc(100% - 260px);
}
.content {
width: 900px;
margin: auto;
}
</style>
<style>
.right section .demo-header > h2 {
font-weight: 400;
color: #1f2f3d;
}
.right section > h3 {
margin: 40px 0 20px 0;
padding-bottom: 0.3rem;
border-bottom: 1px solid #eaecef;
font-weight: 400;
color: #1f2f3d;
}
.switch-mode {
position: fixed;
top: 10px;
right: 20px;
color: var(--ti-common-color-line-active);
cursor: pointer;
}
</style>

16
example/src/const.js Normal file
View File

@ -0,0 +1,16 @@
import { tinyImpressionTheme, tinyInfinityTheme, tinyDeepTheme, tinyGalaxyTheme } from '@opentiny/vue-theme/theme'
export const CURRENT_THEME_KEY = 'tiny-current-theme'
export const DEFAULT_THEME = 'tiny-default-theme'
export const IMPRESSION_THEME = 'tiny-impression-theme'
export const INFINITY_THEME = 'tiny-infinity-theme'
export const DEEP_THEME = 'tiny-deep-theme'
export const GALAXY_THEME = 'tiny-galaxy-theme'
export const THEME_MAP = {
[DEFAULT_THEME]: null,
[IMPRESSION_THEME]: tinyImpressionTheme,
[INFINITY_THEME]: tinyInfinityTheme,
[DEEP_THEME]: tinyDeepTheme,
[GALAXY_THEME]: tinyGalaxyTheme
}

View File

@ -0,0 +1,76 @@
<template>
<div class="tiny-mobile-action-sheet-demo action-sheet-wrap">
<div class="page__hd">
<h1 class="page__title">上滑列表</h1>
<p class="page__desc">弹出式菜单</p>
</div>
<tiny-button _mode="mobile" @click="fn" type="primary" size="large"
>上滑列表</tiny-button
>
<tiny-action-sheet
_mode="mobile"
v-model="activeName"
:menus="menus"
:visible="boxVisibility"
@update:visible="boxVisibility = $event"
></tiny-action-sheet>
</div>
</template>
<script>
import { ActionSheet, Button } from '@opentiny/vue'
export default {
components: {
TinyActionSheet: ActionSheet,
TinyButton: Button
},
data() {
return {
activeName: 1,
boxVisibility: false,
menus: [
{
id: 1,
label:
'我是小花,我是小花,我是小花,我是小花,我是小花,我是小花,我是小花'
},
{
id: 2,
label: '我是小树'
},
{
id: 3,
label: '我是小草'
},
{
id: 4,
label: '我是小叶',
warn: true
}
]
}
},
methods: {
fn() {
this.boxVisibility = true
}
}
}
</script>
<style>
.page__hd {
padding: 40px;
}
.page__title {
font-weight: 400;
font-size: 21px;
text-align: left;
}
.page__desc {
margin-top: 5px;
color: #888;
font-size: 14px;
text-align: left;
}
</style>

View File

@ -0,0 +1,58 @@
<template>
<div class="tiny-mobile-action-sheet-demo">
<tiny-button _mode="mobile" @click="fn">cliclk me!</tiny-button>
<tiny-action-sheet
_mode="mobile"
v-model="activeName"
:menus="menus"
:visible="boxVisibility"
@update:visible="boxVisibility = $event"
@click="clickItem"
></tiny-action-sheet>
</div>
</template>
<script>
import { ActionSheet, Button } from '@opentiny/vue'
export default {
components: {
TinyActionSheet: ActionSheet,
TinyButton: Button
},
data() {
return {
activeName: '',
boxVisibility: false,
menus: [
{
id: 1,
label:
'我是小花,我是小花,我是小花,我是小花,我是小花,我是小花,我是小花'
},
{
id: 2,
label: '我是小树'
},
{
id: 3,
label: '我是小草'
},
{
id: 4,
label: '我是小叶',
warn: true
}
]
}
},
methods: {
fn() {
this.boxVisibility = true
},
clickItem(value) {
console.log(value, '获取了当前点击的返回值')
}
}
}
</script>

View File

@ -0,0 +1,55 @@
<template>
<div class="tiny-mobile-action-sheet-demo">
<tiny-button _mode="mobile" @click="fn">cliclk me!</tiny-button>
<tiny-action-sheet
_mode="mobile"
v-model="activeName"
:menus="menus"
ellipsis
:visible="boxVisibility"
@update:visible="boxVisibility = $event"
></tiny-action-sheet>
</div>
</template>
<script>
import { ActionSheet, Button } from '@opentiny/vue'
export default {
components: {
TinyActionSheet: ActionSheet,
TinyButton: Button
},
data() {
return {
activeName: '',
boxVisibility: false,
menus: [
{
id: 1,
label:
'我是小花,我是小花,我是小花,我是小花,我是小花,我是小花,我是小花'
},
{
id: 2,
label: '我是小树'
},
{
id: 3,
label: '我是小草'
},
{
id: 4,
label: '我是小叶',
warn: true
}
]
}
},
methods: {
fn() {
this.boxVisibility = true
}
}
}
</script>

View File

@ -0,0 +1,83 @@
<template>
<div class="tiny-mobile-action-sheet-demo action-sheet-wrap">
<div class="page__hd">
<h1 class="page__title">上滑列表</h1>
<p class="page__desc">弹出式菜单</p>
</div>
<tiny-button @click="fn" type="primary" size="large">上滑列表</tiny-button>
<tiny-action-sheet
v-model="activeName"
ref="action"
:menus="menus"
:visible="boxVisibility"
@update:visible="boxVisibility = $event"
>
<template #action>
<tiny-button @click="visibleHandle" type="primary" size="large"
>取消</tiny-button
>
</template>
</tiny-action-sheet>
</div>
</template>
<script>
import { ActionSheet, Button } from '@opentiny/vue'
export default {
components: {
TinyActionSheet: ActionSheet,
TinyButton: Button
},
data() {
return {
activeName: 1,
boxVisibility: false,
menus: [
{
id: 1,
label:
'我是小花,我是小花,我是小花,我是小花,我是小花,我是小花,我是小花'
},
{
id: 2,
label: '我是小树'
},
{
id: 3,
label: '我是小草'
},
{
id: 4,
label: '我是小叶',
warn: true
}
]
}
},
methods: {
visibleHandle() {
this.$refs.action.visibleHandle()
},
fn() {
this.boxVisibility = true
}
}
}
</script>
<style>
.page__hd {
padding: 40px;
}
.page__title {
font-weight: 400;
font-size: 21px;
text-align: left;
}
.page__desc {
margin-top: 5px;
color: #888;
font-size: 14px;
text-align: left;
}
</style>

View File

@ -0,0 +1,79 @@
<template>
<div class="tiny-mobile-action-sheet-demo action-sheet-slot">
<div class="page__hd">
<h1 class="page__title">上滑列表</h1>
<p class="page__desc">弹出式菜单</p>
</div>
<tiny-button @click="fn" type="primary" size="large">上滑列表</tiny-button>
<tiny-action-sheet
v-model="activeName"
:menus="menus"
:visible="boxVisibility"
@update:visible="boxVisibility = $event"
>
<template #item="data">
<div>
{{ data.item.label }}
</div>
</template>
</tiny-action-sheet>
</div>
</template>
<script>
import { ActionSheet, Button } from '@opentiny/vue'
export default {
components: {
TinyActionSheet: ActionSheet,
TinyButton: Button
},
data() {
return {
activeName: 1,
boxVisibility: false,
menus: [
{
id: 1,
label:
'我是小花,我是小花,我是小花,我是小花,我是小花,我是小花,我是小花'
},
{
id: 2,
label: '我是小树'
},
{
id: 3,
label: '我是小草'
},
{
id: 4,
label: '我是小叶',
warn: true
}
]
}
},
methods: {
fn() {
this.boxVisibility = true
}
}
}
</script>
<style>
.page__hd {
padding: 40px;
}
.page__title {
font-weight: 400;
font-size: 21px;
text-align: left;
}
.page__desc {
margin-top: 5px;
color: #888;
font-size: 14px;
text-align: left;
}
</style>

View File

@ -0,0 +1,23 @@
<template>
<div class="alert-wrap">
<tiny-alert>type 为默认值 info</tiny-alert>
<tiny-alert type="error">type error</tiny-alert>
<tiny-alert type="success">type success</tiny-alert>
<tiny-alert type="warning">type warning</tiny-alert>
</div>
</template>
<script>
import { Alert } from '@opentiny/vue'
export default {
components: {
TinyAlert: Alert
}
}
</script>
<style>
.alert-wrap .tiny-mobile-alert {
margin-bottom: 8px;
}
</style>

View File

@ -0,0 +1,28 @@
<template>
<div class="alert-wrap">
<tiny-alert>默认关闭按钮</tiny-alert>
<tiny-alert :closable="false">不可关闭</tiny-alert>
<tiny-alert close-text="关闭">自定义关闭按钮为文本</tiny-alert>
<tiny-alert @close="close">关闭时触发回调</tiny-alert>
</div>
</template>
<script>
import { Alert } from '@opentiny/vue'
export default {
components: {
TinyAlert: Alert
},
methods: {
close() {
console.log('已关闭!')
}
}
}
</script>
<style>
.alert-wrap .tiny-mobile-alert {
margin-bottom: 8px;
}
</style>

View File

@ -0,0 +1,26 @@
<template>
<div class="alert-wrap">
<tiny-alert :icon="IconCustom">自定义图标</tiny-alert>
</div>
</template>
<script>
import { Alert } from '@opentiny/vue'
import { iconCustom } from '@opentiny/vue-icon'
export default {
components: {
TinyAlert: Alert
},
data() {
return {
IconCustom: iconCustom()
}
}
}
</script>
<style>
.alert-wrap .tiny-mobile-alert {
margin-bottom: 8px;
}
</style>

View File

@ -0,0 +1,22 @@
<template>
<div class="alert-wrap">
<tiny-alert>默认大小</tiny-alert>
<tiny-alert size="normal">size normal</tiny-alert>
<tiny-alert size="large">size large</tiny-alert>
</div>
</template>
<script>
import { Alert } from '@opentiny/vue'
export default {
components: {
TinyAlert: Alert
}
}
</script>
<style>
.alert-wrap .tiny-mobile-alert {
margin-bottom: 8px;
}
</style>

View File

@ -0,0 +1,22 @@
<template>
<div class="alert-wrap">
<tiny-alert>
<span style="color: red">根据 default slot 自定义内容</span>
</tiny-alert>
</div>
</template>
<script>
import { Alert } from '@opentiny/vue'
export default {
components: {
TinyAlert: Alert
}
}
</script>
<style>
.alert-wrap .tiny-mobile-alert {
margin-bottom: 8px;
}
</style>

View File

@ -0,0 +1,58 @@
<template>
<div class="avatar-wrap">
<div class="page__hd">
<h1 class="page__title">HeadIcon</h1>
<p class="page__desc">头像图标</p>
</div>
<tiny-avatar :src="circleUrl" size="large"></tiny-avatar>
<tiny-avatar :src="circleUrl" size="medium"></tiny-avatar>
<tiny-avatar :src="circleUrl" size="small"></tiny-avatar>
<tiny-avatar :src="circleUrl" :size="320"></tiny-avatar>
<tiny-avatar :src="circleUrl" :size="80"></tiny-avatar>
<tiny-avatar :src="circleUrl" :size="56"></tiny-avatar>
<tiny-avatar :src="circleUrl" :size="48"></tiny-avatar>
<tiny-avatar :src="circleUrl" :size="40"></tiny-avatar>
<tiny-avatar :src="circleUrl" :size="36"></tiny-avatar>
<tiny-avatar :src="circleUrl" :size="32"></tiny-avatar>
<tiny-avatar :src="circleUrl" :size="30"></tiny-avatar>
<tiny-avatar :src="circleUrl" :size="20"></tiny-avatar>
</div>
</template>
<script>
import { Avatar } from '@opentiny/vue'
export default {
components: {
TinyAvatar: Avatar
},
data() {
return {
circleUrl: 'static/images/watercolor.png'
}
}
}
</script>
<style>
.page__hd {
padding: 40px;
}
.page__title {
font-weight: 400;
font-size: 21px;
text-align: left;
}
.page__desc {
margin-top: 5px;
color: #888;
font-size: 14px;
text-align: left;
}
.avatar-wrap {
padding: 20px;
width: 100%;
height: calc(100% - 118px);
overflow: hidden;
overflow-y: auto;
}
</style>

View File

@ -0,0 +1,40 @@
<template>
<div class="avatar-wrap">
<tiny-avatar :src="circleUrl" :src-set="srcSet" size="large"></tiny-avatar>
<tiny-avatar :icon="IconClockWork" size="large"></tiny-avatar>
<tiny-avatar :src="circleUrl" fit="contain" size="large"></tiny-avatar>
<tiny-avatar :src="errorUrl" :error="onError" fit="none" size="large"></tiny-avatar>
<tiny-avatar :src="circleUrl" shape="square" fit="none" size="large"></tiny-avatar>
</div>
</template>
<script>
import { Avatar } from '@opentiny/vue'
import { iconClockWork } from '@opentiny/vue-icon'
export default {
components: {
TinyAvatar: Avatar
},
data() {
return {
IconClockWork: iconClockWork(),
circleUrl: 'static/images/floral.png',
srcSet: 'static/images/watercolor.png 100w,static/images/floral.png 300w,static/images/fruit.jpg 500w',
errorUrl: 'error.png', //使
onError() {
console.log('加载失败')
}
}
}
}
</script>
<style>
.avatar-wrap {
padding: 20px;
}
.avatar-wrap svg {
height: 100%;
width: 100%;
}
</style>

View File

@ -0,0 +1,97 @@
<template>
<div class="avatar-wrap">
<div class="page__hd">
<h1 class="page__title">HeadIcon</h1>
<p class="page__desc">头像图标</p>
</div>
<div class="boyx">
<tiny-avatar :src="dogUrl" :size="320"></tiny-avatar>
<p>320x320</p>
<div>
<tiny-avatar :src="dogUrl" :size="80"></tiny-avatar>
<tiny-avatar :src="circleUrl" :size="80"></tiny-avatar>
<span>80x80</span>
</div>
<div>
<tiny-avatar :src="dogUrl" :size="56"></tiny-avatar>
<tiny-avatar :src="circleUrl" :size="56"></tiny-avatar>
<span>56x56</span>
</div>
<div>
<tiny-avatar :src="dogUrl" :size="48"></tiny-avatar>
<tiny-avatar :src="circleUrl" :size="48"></tiny-avatar>
<span>48x48</span>
</div>
<div>
<tiny-avatar :src="dogUrl" :size="40"></tiny-avatar>
<tiny-avatar :src="circleUrl" :size="40"></tiny-avatar>
<span>40x40</span>
</div>
<div>
<tiny-avatar :src="dogUrl" :size="36"></tiny-avatar>
<tiny-avatar :src="circleUrl" :size="36"></tiny-avatar>
<span>36x36</span>
</div>
<div>
<tiny-avatar :src="dogUrl" :size="32"></tiny-avatar>
<tiny-avatar :src="circleUrl" :size="32"></tiny-avatar>
<span>32x32</span>
</div>
<div>
<tiny-avatar :src="dogUrl" :size="30"></tiny-avatar>
<tiny-avatar :src="circleUrl" :size="30"></tiny-avatar>
<span>30x30</span>
</div>
<div>
<tiny-avatar :src="dogUrl" :size="20"></tiny-avatar>
<tiny-avatar :src="circleUrl" :size="20"></tiny-avatar>
<span>20x20</span>
</div>
</div>
</div>
</template>
<script>
import { Avatar } from '@opentiny/vue'
export default {
components: {
TinyAvatar: Avatar
},
data() {
return {
circleUrl: 'static/images/floral.png',
dogUrl: 'static/images/fruit.jpg'
}
}
}
</script>
<style>
.boyx {
padding: 20px;
}
.boyx > div,
.boyx > p {
margin-bottom: 30px;
}
.page__hd {
padding: 40px;
}
.page__title {
font-weight: 400;
font-size: 21px;
text-align: left;
}
.page__desc {
margin-top: 5px;
color: #888;
font-size: 14px;
text-align: left;
}
.avatar-wrap {
height: calc(100% - 0px);
width: 100%;
overflow: hidden;
overflow-y: auto;
}
</style>

View File

@ -0,0 +1,25 @@
<template>
<div class="badge-wrap">
<tiny-badge :value="value1" :max="1"> 我的待办 </tiny-badge>
</div>
</template>
<script>
import { Badge } from '@opentiny/vue'
export default {
components: {
TinyBadge: Badge
},
data() {
return {
value1: 2
}
}
}
</script>
<style>
.badge-wrap {
padding: 20px;
}
</style>

View File

@ -0,0 +1,28 @@
<template>
<div class="badge-wrap">
<tiny-badge :value="value1">
我的待办
<template #content>{{ value1 }}</template>
</tiny-badge>
</div>
</template>
<script>
import { Badge } from '@opentiny/vue'
export default {
components: {
TinyBadge: Badge
},
data() {
return {
value1: 2
}
}
}
</script>
<style>
.badge-wrap {
padding: 20px;
}
</style>

View File

@ -0,0 +1,37 @@
<template>
<div class="badge-wrap">
<tiny-badge :value="unread" :hidden="unread === 0">我的待办</tiny-badge>
<br />
<tiny-button :disabled="unread === 0" @click="read"
>读取一条消息</tiny-button
>
</div>
</template>
<script>
import { Badge, Button } from '@opentiny/vue'
export default {
components: {
TinyBadge: Badge,
TinyButton: Button
},
data() {
return {
unread: 2
}
},
methods: {
read() {
if (this.unread > 0) {
this.unread = this.unread - 1
}
}
}
}
</script>
<style>
.badge-wrap {
padding: 20px;
}
</style>

View File

@ -0,0 +1,31 @@
<template>
<div class="badge-wrap">
<tiny-badge :value="2" :href="'/'" target="_self"
>当前标签页打开</tiny-badge
>
<br />
<tiny-badge :value="2" :href="'/'" target="_blank"
>新建标签页打开</tiny-badge
>
</div>
</template>
<script>
import { Badge } from '@opentiny/vue'
export default {
components: {
TinyBadge: Badge
},
data() {
return {
value1: 2
}
}
}
</script>
<style>
.badge-wrap {
padding: 20px;
}
</style>

View File

@ -0,0 +1,25 @@
<template>
<div class="badge-wrap">
<tiny-badge :value="value1" is-dot> 小圆点标记 </tiny-badge>
</div>
</template>
<script>
import { Badge } from '@opentiny/vue'
export default {
components: {
TinyBadge: Badge
},
data() {
return {
value1: 2
}
}
}
</script>
<style>
.badge-wrap {
padding: 20px;
}
</style>

View File

@ -0,0 +1,27 @@
<template>
<div class="badge-wrap">
<tiny-badge :value="value1" :max="1" is-mini>小尺寸</tiny-badge>
<br />
<tiny-badge :value="value1" :max="1">默认尺寸</tiny-badge>
</div>
</template>
<script>
import { Badge } from '@opentiny/vue'
export default {
components: {
TinyBadge: Badge
},
data() {
return {
value1: 2
}
}
}
</script>
<style>
.badge-wrap {
padding: 20px;
}
</style>

View File

@ -0,0 +1,35 @@
<template>
<div class="badge-wrap">
<tiny-badge :value="value1" :max="1">默认主题</tiny-badge>
<br />
<tiny-badge :value="value1" :max="1" type="primary">primary</tiny-badge>
<br />
<tiny-badge :value="value1" :max="1" type="success">success</tiny-badge>
<br />
<tiny-badge :value="value1" :max="1" type="warning">warning</tiny-badge>
<br />
<tiny-badge :value="value1" :max="1" type="danger">danger</tiny-badge>
<br />
<tiny-badge :value="value1" :max="1" type="info">info</tiny-badge>
</div>
</template>
<script>
import { Badge } from '@opentiny/vue'
export default {
components: {
TinyBadge: Badge
},
data() {
return {
value1: 2
}
}
}
</script>
<style>
.badge-wrap {
padding: 20px;
}
</style>

View File

@ -0,0 +1,143 @@
<template>
<div class="button-wrap">
<div class="body">
<div class="page__hd">
<h1 class="page__title">Button</h1>
<p class="page__desc">按钮</p>
</div>
<tiny-button size="large">默认按钮</tiny-button>
<tiny-button type="primary" size="large">主要按钮</tiny-button>
<tiny-button type="success" size="large">成功按钮</tiny-button>
<tiny-button type="info" size="large">信息按钮</tiny-button>
<tiny-button type="warning" size="large">警告按钮</tiny-button>
<tiny-button type="danger" size="large">危险按钮</tiny-button>
<tiny-button type="primary" disabled size="large">主要按钮</tiny-button>
<tiny-button type="success" disabled size="large">成功按钮</tiny-button>
<tiny-button type="info" disabled size="large">信息按钮</tiny-button>
<tiny-button type="warning" disabled size="large">警告按钮</tiny-button>
<tiny-button type="danger" disabled size="large">危险按钮</tiny-button>
<div class="btb">
<tiny-button type="primary" size="small" plain>默认按钮</tiny-button>
<tiny-button type="primary" size="small" plain disabled
>默认按钮</tiny-button
>
</div>
<div class="btb">
<tiny-button type="primary" size="mini">按钮</tiny-button>
</div>
<div class="btb">
<tiny-button :icon="IconMessageCircle" type="primary" size="mini" plain
>加入会议</tiny-button
>
</div>
<tiny-button type="primary" size="medium">主要按钮</tiny-button>
<tiny-button type="success" size="medium">成功按钮</tiny-button>
<tiny-button type="info" size="medium">信息按钮</tiny-button>
<tiny-button type="primary" size="medium" plain>主要按钮</tiny-button>
<div class="btb">
<span>底部悬浮按钮</span>
</div>
<div class="btb">
<tiny-button type="primary" size="small" plain @click="dat = 0"
>有间距双按钮</tiny-button
>
<tiny-button type="primary" size="small" plain @click="dat = 1"
>无间距双按钮</tiny-button
>
</div>
<div class="btb">
<tiny-button type="primary" size="small" plain @click="dat = 2"
>确定单按钮</tiny-button
>
<tiny-button type="primary" size="small" plain @click="dat = 3"
>删除单按钮</tiny-button
>
</div>
</div>
<div class="test" v-if="dat === 1">
<tiny-button size="large">取消</tiny-button>
<tiny-button type="primary" size="large">确认</tiny-button>
</div>
<div class="test" v-if="dat === 0">
<div style="width: calc(50% - 10px); margin-right: 10px">
<tiny-button size="large">取消</tiny-button>
</div>
<div style="width: calc(50% - 10px)">
<tiny-button type="primary" size="large">确认</tiny-button>
</div>
</div>
<div class="test" v-if="dat === 3">
<tiny-button size="large" type="danger">删除</tiny-button>
</div>
<div class="test" v-if="dat === 2">
<tiny-button type="primary" size="large">确认</tiny-button>
</div>
</div>
</template>
<script>
import { Button } from '@opentiny/vue'
import { iconMessageCircle } from '@opentiny/vue-icon'
export default {
components: {
TinyButton: Button
},
data() {
return {
dat: 0,
IconMessageCircle: iconMessageCircle()
}
}
}
</script>
<style scoped>
.body {
height: calc(100% - 60px);
width: 100%;
overflow: hidden;
overflow-y: auto;
}
.page__hd {
padding: 40px;
}
.page__title {
font-weight: 400;
font-size: 21px;
text-align: left;
}
.page__desc {
margin-top: 5px;
color: #888;
font-size: 14px;
text-align: left;
}
.test {
position: absolute;
display: flex;
height: 60px;
width: 100%;
align-items: center;
justify-content: center;
bottom: 0px;
left: 0px;
background-color: white;
}
.btb {
display: flex;
justify-content: center;
width: 100%;
}
.button-wrap {
height: calc(100% - 118px);
position: relative;
}
.button-wrap .tiny-mobile-button {
margin-bottom: 16px;
}
.test .tiny-mobile-button {
margin-bottom: 0px;
}
</style>

View File

@ -0,0 +1,30 @@
<template>
<div class="button-wrap">
<tiny-button @click="click">默认按钮</tiny-button>
</div>
</template>
<script>
import { Button } from '@opentiny/vue'
export default {
components: {
TinyButton: Button
},
methods: {
click() {
console.log('click event!!!')
}
}
}
</script>
<style scoped>
.button-wrap {
padding: 0 10px;
}
.button-wrap .tiny-mobile-button:not(:nth-child(3n)) {
margin-right: 16px;
margin-bottom: 16px;
}
</style>

View File

@ -0,0 +1,48 @@
<template>
<div class="button-wrap">
<tiny-button :icon="IconSearch"></tiny-button>
<tiny-button type="primary" :icon="IconEdit"></tiny-button>
<tiny-button type="success" :icon="IconYes"></tiny-button>
<tiny-button type="info" :icon="IconMail"></tiny-button>
<tiny-button type="warning" :icon="IconStarO"></tiny-button>
<tiny-button type="danger" :icon="IconDel"></tiny-button>
</div>
</template>
<script>
import { Button } from '@opentiny/vue'
import {
iconDel,
iconYes,
iconEdit,
iconMail,
iconStarO,
iconSearch
} from '@opentiny/vue-icon'
export default {
components: {
TinyButton: Button
},
data() {
return {
IconDel: iconDel(),
IconYes: iconYes(),
IconEdit: iconEdit(),
IconMail: iconMail(),
IconStarO: iconStarO(),
IconSearch: iconSearch()
}
}
}
</script>
<style scoped>
.button-wrap {
padding: 0 10px;
}
.button-wrap .tiny-mobile-button {
margin-right: 16px;
margin-bottom: 16px;
}
</style>

View File

@ -0,0 +1,33 @@
<template>
<div class="button-wrap">
<tiny-button loading>加载中</tiny-button>
<tiny-button type="primary" loading>加载中</tiny-button>
<tiny-button type="success" loading>加载中</tiny-button>
<tiny-button type="info" loading>加载中</tiny-button>
<tiny-button type="warning" loading>加载中</tiny-button>
<tiny-button type="danger" loading>加载中</tiny-button>
</div>
</template>
<script>
import { Button } from '@opentiny/vue'
export default {
components: {
TinyButton: Button
},
data() {
return {}
}
}
</script>
<style scoped>
.button-wrap {
padding: 0 10px;
}
.button-wrap .tiny-mobile-button:not(:nth-child(3n)) {
margin-right: 16px;
margin-bottom: 16px;
}
</style>

View File

@ -0,0 +1,100 @@
<template>
<div class="demo-form-native-type">
<div class="page__hd">
<h1 class="page__title">Form</h1>
<p class="page__desc">表单纯展示</p>
</div>
<div class="demo-padds-native-type">
<tiny-form ref="ruleForm" :model="createData" :rules="rules">
<tiny-form-item label="优秀" prop="users">
<tiny-input
v-model="createData.users"
placeholder="请输入内容"
type="form"
></tiny-input>
</tiny-form-item>
<tiny-form-item>
<tiny-button @click="handleSubmit('ruleForm')">重置</tiny-button>
</tiny-form-item>
</tiny-form>
</div>
<tiny-dialog-box
:visible="boxVisibility"
@update:visible="boxVisibility = $event"
:modal-append-to-body="false"
title="消息提示"
>
<span>reset</span>
</tiny-dialog-box>
</div>
</template>
<script>
import { Form, FormItem, Input, Button, DialogBox } from '@opentiny/vue'
export default {
components: {
TinyForm: Form,
TinyFormItem: FormItem,
TinyInput: Input,
TinyButton: Button,
TinyDialogBox: DialogBox
},
data() {
return {
createData: {
users: ''
},
rules: {
users: [{ required: true, message: '必填', trigger: 'change' }]
},
boxVisibility: false
}
},
methods: {
handleSubmit(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.boxVisibility = true
this.$refs[formName].resetFields()
}
})
}
}
}
</script>
<style scoped>
.demo-padds-native-type {
padding: 15px;
background: white;
margin-bottom: 15px;
}
.page__hd {
padding: 40px;
}
.page__title {
font-weight: 400;
font-size: 21px;
text-align: left;
}
.page__desc {
margin-top: 5px;
color: #888;
font-size: 14px;
text-align: left;
}
.demo-form-native-type {
height: 100%;
background: #f4f4f4;
}
</style>
<style>
.demo-form-native-type .tiny-form-item__label {
background: #fff;
}
.demo-padds-native-type .tiny-mobile-input-form__input {
text-align: right;
}
</style>

View File

@ -0,0 +1,33 @@
<template>
<div class="button-wrap">
<tiny-button plain>朴素按钮</tiny-button>
<tiny-button type="primary" plain>主要按钮</tiny-button>
<tiny-button type="success" plain>成功按钮</tiny-button>
<tiny-button type="info" plain>信息按钮</tiny-button>
<tiny-button type="warning" plain>警告按钮</tiny-button>
<tiny-button type="danger" plain>危险按钮</tiny-button>
</div>
</template>
<script>
import { Button } from '@opentiny/vue'
export default {
components: {
TinyButton: Button
},
data() {
return {}
}
}
</script>
<style scoped>
.button-wrap {
padding: 0 10px;
}
.button-wrap .tiny-mobile-button:not(:nth-child(3n)) {
margin-right: 16px;
margin-bottom: 16px;
}
</style>

View File

@ -0,0 +1,30 @@
<template>
<div class="button-wrap">
<tiny-button round>默认1秒</tiny-button>
<tiny-button type="primary" round :reset-time="2000">禁用2秒</tiny-button>
<tiny-button type="success" round :reset-time="5000">禁用5秒</tiny-button>
</div>
</template>
<script>
import { Button } from '@opentiny/vue'
export default {
components: {
TinyButton: Button
},
data() {
return {}
}
}
</script>
<style scoped>
.button-wrap {
padding: 10px 20px;
}
.button-wrap .tiny-mobile-button:not(:nth-child(3n)) {
margin-right: 16px;
margin-bottom: 16px;
}
</style>

View File

@ -0,0 +1,33 @@
<template>
<div class="button-wrap">
<tiny-button round>圆角按钮</tiny-button>
<tiny-button type="primary" round>主要按钮</tiny-button>
<tiny-button type="success" round>成功按钮</tiny-button>
<tiny-button type="info" round>信息按钮</tiny-button>
<tiny-button type="warning" round>警告按钮</tiny-button>
<tiny-button type="danger" round>危险按钮</tiny-button>
</div>
</template>
<script>
import { Button } from '@opentiny/vue'
export default {
components: {
TinyButton: Button
},
data() {
return {}
}
}
</script>
<style scoped>
.button-wrap {
padding: 0 10px;
}
.button-wrap .tiny-mobile-button:not(:nth-child(3n)) {
margin-right: 16px;
margin-bottom: 16px;
}
</style>

View File

@ -0,0 +1,32 @@
<template>
<div class="button-wrap">
<tiny-button>默认尺寸</tiny-button>
<tiny-button type="primary" size="large">large</tiny-button>
<tiny-button type="success" size="medium">medium</tiny-button>
<tiny-button type="info" size="small">small</tiny-button>
<tiny-button type="warning" size="mini">mini</tiny-button>
</div>
</template>
<script>
import { Button } from '@opentiny/vue'
export default {
components: {
TinyButton: Button
},
data() {
return {}
}
}
</script>
<style scoped>
.button-wrap {
padding: 0 10px;
}
.button-wrap .tiny-mobile-button {
margin-right: 16px;
margin-bottom: 16px;
}
</style>

View File

@ -0,0 +1,28 @@
<template>
<div class="button-wrap">
<tiny-button type="text">文字按钮</tiny-button>
</div>
</template>
<script>
import { Button } from '@opentiny/vue'
export default {
components: {
TinyButton: Button
},
data() {
return {}
}
}
</script>
<style scoped>
.button-wrap {
padding: 0 10px;
}
.button-wrap .tiny-mobile-button:not(:nth-child(3n)) {
margin-right: 16px;
margin-bottom: 16px;
}
</style>

View File

@ -0,0 +1,35 @@
<template>
<div class="checkbox-group-wrap">
<tiny-checkbox-group v-model="checkboxGroup">
<tiny-checkbox
v-for="(city, index) in cities"
:label="city"
:key="index"
>{{ city }}</tiny-checkbox
>
</tiny-checkbox-group>
</div>
</template>
<script>
import { Checkbox, CheckboxGroup } from '@opentiny/vue'
export default {
components: {
TinyCheckbox: Checkbox,
TinyCheckboxGroup: CheckboxGroup
},
data() {
return {
checkboxGroup: ['北京'],
cities: ['上海', '北京', '广州', '深圳']
}
}
}
</script>
<style scoped>
.checkbox-group-wrap {
padding: 20px;
}
</style>

View File

@ -0,0 +1,36 @@
<template>
<div class="checkbox-group-wrap">
<tiny-checkbox-group v-model="checkboxGroup">
<tiny-checkbox
v-for="(city, index) in cities"
:label="city"
:key="index"
:disabled="city === '北京'"
>{{ city }}</tiny-checkbox
>
</tiny-checkbox-group>
</div>
</template>
<script>
import { Checkbox, CheckboxGroup } from '@opentiny/vue'
export default {
components: {
TinyCheckbox: Checkbox,
TinyCheckboxGroup: CheckboxGroup
},
data() {
return {
checkboxGroup: ['北京'],
cities: ['上海', '北京', '广州', '深圳']
}
}
}
</script>
<style scoped>
.checkbox-group-wrap {
padding: 20px;
}
</style>

View File

@ -0,0 +1,40 @@
<template>
<div class="checkbox-group-wrap">
<tiny-checkbox-group v-model="checkboxGroup" @change="handleChange">
<tiny-checkbox
v-for="(city, index) in cities"
:label="city"
:key="index"
>{{ city }}</tiny-checkbox
>
</tiny-checkbox-group>
</div>
</template>
<script>
import { Checkbox, CheckboxGroup } from '@opentiny/vue'
export default {
components: {
TinyCheckbox: Checkbox,
TinyCheckboxGroup: CheckboxGroup
},
data() {
return {
checkboxGroup: ['北京'],
cities: ['上海', '北京', '广州', '深圳']
}
},
methods: {
handleChange(val) {
console.log('当前值为:' + val)
}
}
}
</script>
<style scoped>
.checkbox-group-wrap {
padding: 20px;
}
</style>

View File

@ -0,0 +1,35 @@
<template>
<div class="checkbox-group-wrap">
<tiny-checkbox-group v-model="checkboxGroup" :min="1" :max="3">
<tiny-checkbox
v-for="(city, index) in cities"
:label="city"
:key="index"
>{{ city }}</tiny-checkbox
>
</tiny-checkbox-group>
</div>
</template>
<script>
import { Checkbox, CheckboxGroup } from '@opentiny/vue'
export default {
components: {
TinyCheckbox: Checkbox,
TinyCheckboxGroup: CheckboxGroup
},
data() {
return {
checkboxGroup: ['北京'],
cities: ['上海', '北京', '广州', '深圳']
}
}
}
</script>
<style scoped>
.checkbox-group-wrap {
padding: 20px;
}
</style>

View File

@ -0,0 +1,35 @@
<template>
<div class="checkbox-group-wrap">
<tiny-checkbox-group v-model="checkboxGroup" vertical>
<tiny-checkbox
v-for="(city, index) in cities"
:label="city"
:key="index"
>{{ city }}</tiny-checkbox
>
</tiny-checkbox-group>
</div>
</template>
<script>
import { Checkbox, CheckboxGroup } from '@opentiny/vue'
export default {
components: {
TinyCheckbox: Checkbox,
TinyCheckboxGroup: CheckboxGroup
},
data() {
return {
checkboxGroup: ['北京'],
cities: ['上海', '北京', '广州', '深圳']
}
}
}
</script>
<style scoped>
.checkbox-group-wrap {
padding: 20px;
}
</style>

View File

@ -0,0 +1,99 @@
<template>
<div class="checkbox-wrap">
<div class="page__hd">
<h1 class="page__title">Checkbox</h1>
<p class="page__desc">勾选框-多选</p>
</div>
<p class="padds">样例1</p>
<div class="padds">
<tiny-radio v-model="value" label="1" border>主文本 1</tiny-radio>
</div>
<div class="borders"></div>
<div class="padds">
<tiny-radio v-model="value1" label="3" border>主文本 2</tiny-radio>
</div>
<p class="padds">样例2</p>
<div class="padds">
<tiny-checkbox v-model="checked.a">A非常喜欢</tiny-checkbox>
</div>
<div class="padds">
<tiny-checkbox v-model="checked.b">B喜欢</tiny-checkbox>
</div>
<div class="padds">
<tiny-checkbox v-model="checked.c">C一般</tiny-checkbox>
</div>
<div class="padds">
<tiny-checkbox v-model="checked.d">D不喜欢</tiny-checkbox>
</div>
</div>
</template>
<script>
import { Checkbox, Radio } from '@opentiny/vue'
export default {
components: {
TinyCheckbox: Checkbox,
TinyRadio: Radio
},
data() {
return {
value: '1',
value1: '2',
checked: {
a: true,
b: false,
c: false,
d: false,
e: false,
f: false,
g: false,
h: false
},
dataList: [
{
id: 1,
content: '主文本1',
subtext: '次文本1',
contentdes: '这是描述文本'
}
]
}
},
methods: {}
}
</script>
<style>
.borders {
height: 1px;
width: calc(100% - 52px);
margin-left: 52px;
background: #ddd;
}
.padds {
padding: 8px 16px;
}
.page__hd {
padding: 40px;
}
.page__title {
font-weight: 400;
font-size: 21px;
text-align: left;
}
.page__desc {
margin-top: 5px;
color: #888;
font-size: 14px;
text-align: left;
}
.checkbox-wrap {
padding: 20px;
display: flex;
flex-direction: column;
width: 356px;
height: calc(100% - 0px);
overflow: auto;
}
</style>

Some files were not shown because too many files have changed in this diff Show More