diff --git a/.gitignore b/.gitignore index d106651c8..c634c8af0 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,4 @@ packages/theme/scripts/themeExcel.xlsx packages/theme/src/theme/*-theme/component.js pnpm-lock.yaml +gulp/bundle.json diff --git a/count-api.js b/count-api.js new file mode 100644 index 000000000..513ae5d6a --- /dev/null +++ b/count-api.js @@ -0,0 +1,34 @@ +const fs = require('fs'); +const basePath = './examples/sites/demos/pc/app'; + +const readFile = function (path) { + let componentCount = 0; + let apiCount = 0; + const readDir = fs.readdirSync(path); + + readDir.forEach(i => { + const curDir = `${path}/${i}`; + const stat = fs.statSync(curDir); + + if (stat.isFile() && i.endsWith('.js')) { + componentCount += 1; + const data = fs.readFileSync(curDir, 'utf-8'); + let dataJson = null; + + try { + dataJson = JSON.parse(data); + } catch (err) { + console.log('err:', err); + } + + if (dataJson !== null) { + apiCount += (dataJson.attrs?.length || 0) + (dataJson.slots?.length || 0) + (dataJson.events?.length || 0) + (dataJson.methods?.length || 0); + } + } + }); + + console.log('componentCount:', componentCount); + console.log('apiCount:', apiCount); +} + +readFile(basePath); diff --git a/examples/sites/demos/mobile-first/app/alert/webdoc/alert.js b/examples/sites/demos/mobile-first/app/alert/webdoc/alert.js index f96770669..c92e644a7 100644 --- a/examples/sites/demos/mobile-first/app/alert/webdoc/alert.js +++ b/examples/sites/demos/mobile-first/app/alert/webdoc/alert.js @@ -1,16 +1,15 @@ export default { column: '2', owner: '', - demos: [ - { + demos: [{ demoId: 'type', name: { 'zh-CN': '类型', - 'en-US': 'basic usage' + 'en-US': 'type' }, desc: { 'zh-CN': '

通过type设置不同的类型。可选值:success、warning、info、error,默认值:info 。

', - 'en-US': '

button type

' + 'en-US': '

Use type to set different types. The options are success, warning, info, and error. The default value is info.

' }, codeFiles: ['type.vue'] }, @@ -18,11 +17,11 @@ export default { demoId: 'size', name: { 'zh-CN': '大尺寸', - 'en-US': 'button round' + 'en-US': 'Large size' }, desc: { 'zh-CN': '

通过 size 属性设置不同的尺寸,可选值:nomal、large,默认值:nomal 。

', - 'en-US': '

button round

' + 'en-US': '

Use the size attribute to set different sizes. The options are nomal and large. The default value is nomal.

' }, codeFiles: ['size.vue'] }, @@ -30,12 +29,11 @@ export default { demoId: 'title', name: { 'zh-CN': '自定义标题', - 'en-US': 'events' + 'en-US': 'Custom Title' }, desc: { - 'zh-CN': - '

size为 large 时显示标题,可设置 titleslot 自定义标题。默认标题根据设置的 type 显示。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

size为 large 时显示标题,可设置 titleslot 自定义标题。默认标题根据设置的 type 显示。

', + 'en-US': '

When size is set to large, the title is displayed. You can set title or slot to customize the title. The default title is displayed according to the set type.

' }, codeFiles: ['title.vue'] }, @@ -55,11 +53,11 @@ export default { demoId: 'closable', name: { 'zh-CN': '不可关闭', - 'en-US': 'events' + 'en-US': 'Can not be closed' }, desc: { - 'zh-CN': '

通过 closable 属性可设置是否显示关闭按钮,没有关闭按钮,警告框将不可关闭。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过 closable 属性可设置是否显示关闭按钮,没有关闭按钮,警告框将不可关闭。

', + 'en-US': '

The closable attribute can be used to set whether to display the Close button. If there is no Close button, the Warning dialog box cannot be closed.

' }, codeFiles: ['closable.vue'] }, @@ -67,11 +65,11 @@ export default { demoId: 'custom-description', name: { 'zh-CN': '自定义提示内容', - 'en-US': 'events' + 'en-US': 'Customized prompt content' }, desc: { - 'zh-CN': '

可通过 description 属性或插槽设置自定义提示内容。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

可通过 description 属性或插槽设置自定义提示内容。

', + 'en-US': '

You can customize the prompt content through the description property or slot.

' }, codeFiles: ['custom-description.vue'] }, @@ -79,11 +77,11 @@ export default { demoId: 'slot-default', name: { 'zh-CN': '自定义交互操作', - 'en-US': 'events' + 'en-US': 'Customizing Interactions' }, desc: { - 'zh-CN': '

size 为 large 时,设置默认插槽自定义交互操作。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

size 为 large 时,设置默认插槽自定义交互操作。

', + 'en-US': '

Set the default slot custom interaction when size is large.

' }, codeFiles: ['slot-default.vue'] }, @@ -91,11 +89,11 @@ export default { demoId: 'close', name: { 'zh-CN': '关闭事件', - 'en-US': 'events' + 'en-US': 'Close Event' }, desc: { - 'zh-CN': '

close 事件,关闭警告框时触发。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

close 事件,关闭警告框时触发。

', + 'en-US': '

close event, triggered when the warning dialog box is closed.

' }, codeFiles: ['close.vue'] }, @@ -103,12 +101,11 @@ export default { demoId: 'feedback-of-result', name: { 'zh-CN': '表单提交结果反馈', - 'en-US': 'events' + 'en-US': 'Form submission result feedback' }, desc: { - 'zh-CN': - '

size 为 large 时,属性 description 和默认插槽结合使用,可渲染提交反馈界面。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

size 为 large 时,属性 description 和默认插槽结合使用,可渲染提交反馈界面。

', + 'en-US': '

When size is set to large, the description attribute is used together with the default slot to render the submission feedback interface.

' }, codeFiles: ['feedback-of-result.vue'] }, @@ -116,11 +113,11 @@ export default { demoId: 'show-icon', name: { 'zh-CN': '是否显示图标', - 'en-US': 'events' + 'en-US': 'Indicates whether to display the icon.' }, desc: { - 'zh-CN': '

通过配置 show-icon 属性,控制图标是否显示。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过配置 show-icon 属性,控制图标是否显示。

', + 'en-US': '

You can set the show-icon attribute to control whether to display the icon.

' }, codeFiles: ['show-icon.vue'] }, @@ -128,12 +125,11 @@ export default { demoId: 'show-foldable', name: { 'zh-CN': '带标题可折叠样式', - 'en-US': 'events' + 'en-US': 'Collapsible Style with Title' }, desc: { - 'zh-CN': - '

通过 `flex-basis` 属性可以设置内容插槽的宽度,flex-basis 类型为数组,最多能传入 4 个值,支持百分比,固定宽度及固有的尺寸关键词,默认为 auto,详情见 flex 的 flex-basis 属性。
通过设置`flex-grow`属性可以设置每一项在 flex 容器中分配剩余空间的相对比例,默认为 1。如果不想自动撑满主容器,可以设为 0,详情见 flex 的 flex-grow 属性。
flex-grow 举例:`:flex-grow="[0, 0]" :flex-grow="[2, 1]" :flex-grow="[2, 1,1,1]"`
flex-basis 举例:`:flex-basis="["200px", "100px","300px"]" :flex-basis="["75%", "25%"]" :flex-basis="["40%", "20%", "20%", "20%"]"`

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过 `flex-basis` 属性可以设置内容插槽的宽度,flex-basis 类型为数组,最多能传入 4 个值,支持百分比,固定宽度及固有的尺寸关键词,默认为 auto,详情见 flex 的 flex-basis 属性。
通过设置`flex-grow`属性可以设置每一项在 flex 容器中分配剩余空间的相对比例,默认为 1。如果不想自动撑满主容器,可以设为 0,详情见 flex 的 flex-grow 属性。
flex-grow 举例:`:flex-grow="[0, 0]" :flex-grow="[2, 1]" :flex-grow="[2, 1,1,1]"`
flex-basis 举例:`:flex-basis="["200px", "100px","300px"]" :flex-basis="["75%", "25%"]" :flex-basis="["40%", "20%", "20%", "20%"]"`

', + 'en-US': '

The `flex-basis` attribute can be used to set the width of the content slot. The flex-basis type is an array. A maximum of four values can be transferred. The percentage, fixed width, and inherent size keywords are supported. The default value is auto. For details, see the flex-basis attribute of Flex.
You can set the relative proportion of the remaining space allocated to each item in the flex container by setting the `flex-grow` attribute. The default value is 1. If you do not want to automatically fill the main container, set this parameter to 0. For details, see the flex-grow attribute of flex.
Flex-grow Example: `:flex-grow="[0, 0]" :flex-grow="[2, 1]" :flex-grow="[2, 1,1,1]"`
flex-basis Example: `:flex-basis="["200px", "100px","300px"]" :flex-basis="["75%", "25%"]" :flex-basis="["40%", "20%", "20%", "20%"]"`

' }, codeFiles: ['show-foldable.vue'] }, @@ -141,7 +137,7 @@ export default { demoId: 'custom-class', name: { 'zh-CN': '自定义类名', - 'en-US': 'Custom class' + 'en-US': 'User-defined class name' }, desc: { 'zh-CN': '

通过custom-class属性设置自定义类名

', @@ -150,5 +146,50 @@ export default { codeFiles: ['custom-class.vue'] } ], - apis: [] -} + apis: [{ + 'name': 'Alert', + 'type': 'component', + 'props': [{ + 'name': 'scrolling', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '是否开启描述文字在鼠标 hover 时滚动显示的动画。', + 'en-US': 'Indicates whether to enable the animation of the description text scrolling when the mouse hovers.' + }, + 'demoId': '' + }, + { + 'name': 'show-foldable', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '展示带标题可折叠样式。', + 'en-US': 'Shows collapsible styles with titles.' + }, + 'demoId': '' + }, + { + 'name': 'single-line', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '描述内容是否一行显示,超出显示...,默认值为 false。', + 'en-US': 'Indicates whether the description is displayed in one line. The default value is false.' + }, + 'demoId': '' + } + ], + 'events': [{ + 'name': 'handleHeaderClick', + 'type': 'Function()', + 'defaultValue': '', + 'desc': { + 'zh-CN': '标题点击事件,设置 show-foldable 为 true 时有效。', + 'en-US': 'Title click event. This parameter is valid only when show-foldable is set to true.' + }, + 'demoId': '' + }], + 'slots': [] + }] +} \ No newline at end of file diff --git a/examples/sites/demos/mobile-first/app/cascader-view/webdoc/cascader-view.js b/examples/sites/demos/mobile-first/app/cascader-view/webdoc/cascader-view.js index 97044c890..4cb539249 100644 --- a/examples/sites/demos/mobile-first/app/cascader-view/webdoc/cascader-view.js +++ b/examples/sites/demos/mobile-first/app/cascader-view/webdoc/cascader-view.js @@ -9,7 +9,7 @@ export default { 'en-US': 'Basic Usage' }, desc: { - 'zh-CN': '

基本用法

', + 'zh-CN': '

基本用法

', 'en-US': '

Basic Usage

' }, codeFiles: ['basic-usage.vue'] @@ -22,9 +22,9 @@ export default { }, desc: { 'zh-CN': - '

通过 :node-config="{ lazy: true, load:() => {...} }"load 设置属性 lazy: trueloadload 数据加载方法,开启异步加载。

', + '

通过 :node-config="{ lazy: true, load:() => {...} }"load 设置属性 lazy: trueloadload 数据加载方法,开启异步加载。

', 'en-US': - '

Set the lazy: trueload attribute and loaddata loading method through :node-config="{lazy: true, load:() => {...}}"load to enable asynchronous loading.

' + '

Set the lazy: trueload attribute and loaddata loading method through :node-config="{lazy: true, load:() => {...}}"load to enable asynchronous loading.

' }, codeFiles: ['lazyload.vue'] }, @@ -36,9 +36,9 @@ export default { }, desc: { 'zh-CN': - '

通过 :node-config="{ checkStrictly: true }" 设置属性 checkStrictly: true,开启父子层级互不关联,可选择非叶子节点,默认值为 false 显示到叶子节点。

', + '

通过 :node-config="{ checkStrictly: true }" 设置属性 checkStrictly: true,开启父子层级互不关联,可选择非叶子节点,默认值为 false 显示到叶子节点。

', 'en-US': - '

You can set the checkStrictly: true attribute in :node-config="{checkStrictly: true}" to enable the disassociation between parent and child levels. You can select non-leaf nodes. The default value is false.

' + '

You can set the checkStrictly: true attribute in :node-config="{checkStrictly: true}" to enable the disassociation between parent and child levels. You can select non-leaf nodes. The default value is false.

' }, codeFiles: ['check-strictly.vue'] }, @@ -49,9 +49,9 @@ export default { 'en-US': 'Disable option' }, desc: { - 'zh-CN': '

数据源里面带 disabled: true 渲染时自动禁用选项。

', + 'zh-CN': '

数据源里面带 disabled: true 渲染时自动禁用选项。

', 'en-US': - '

The disabled: true option is automatically disabled during rendering in the data source.

' + '

The disabled: true option is automatically disabled during rendering in the data source.

' }, codeFiles: ['disabled.vue'] }, @@ -63,12 +63,251 @@ export default { }, desc: { 'zh-CN': - '

通过设置 emit-path 属性值为 false 不显示值路径,默认值为 true 显示值路径。

', + '

通过设置 emit-path 属性值为 false 不显示值路径,默认值为 true 显示值路径。

', 'en-US': - '

The value path is not displayed by setting the emit-path attribute to false. The default value is true.

' + '

The value path is not displayed by setting the emit-path attribute to false. The default value is true.

' }, codeFiles: ['emit-path.vue'] } ], - apis: [] + apis: [ + { + 'name': 'CalendarView', + 'type': 'component', + 'props': [ + { + 'name': 'day-times', + 'type': 'Array', + 'defaultValue': '', + 'desc': { + 'zh-CN': '配置时间线模式下所展示的时间范围,默认为[8,18],可配范围[0,23]。', + 'en-US': 'Time range displayed in timeline mode. The default value range is [8, 18]. The value range is [0, 23].' + }, + 'demoId': '' + }, + { + 'name': 'disabled ', + 'type': 'Function', + 'defaultValue': '', + 'desc': { + 'zh-CN': '日期禁用', + 'en-US': 'Date Disabled' + }, + 'demoId': 'disabled' + }, + { + 'name': 'events', + 'type': 'Array', + 'defaultValue': '', + 'desc': { + 'zh-CN': '日程事件', + 'en-US': 'Scheduled Events' + }, + 'demoId': '' + }, + { + 'name': 'height', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '日历高度', + 'en-US': 'Calendar height' + }, + 'demoId': '' + }, + { + 'name': 'mark-color', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '点标记的颜色', + 'en-US': 'Color of the dot marker' + }, + 'demoId': '' + }, + { + 'name': 'mode', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '显示模式,可选值有 timeline-时间线模式、schedule-日程模式、month-月模式,默认为月模式。', + 'en-US': 'Display mode. The options are timeline-timeline, schedule-schedule, and month-month. The default value is month.' + }, + 'demoId': '' + }, + { + 'name': 'modes', + 'type': 'Array', + 'defaultValue': '', + 'desc': { + 'zh-CN': '模式组,显示模式任意组合', + 'en-US': 'Pattern group, any combination of display patterns' + }, + 'demoId': '' + }, + { + 'name': 'month', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '日历当前显示月份', + 'en-US': 'The calendar currently displays the month' + }, + 'demoId': '' + }, + { + 'name': 'multi-select', + 'type': 'Boolean', + 'defaultValue': '', + 'desc': { + 'zh-CN': '日期多选', + 'en-US': 'Multiple Dates' + }, + 'demoId': '' + }, + { + 'name': 'set-day-bg-color', + 'type': 'Function', + 'defaultValue': '', + 'desc': { + 'zh-CN': '设置日期背景色', + 'en-US': 'Set Date Background Color' + }, + 'demoId': '' + }, + { + 'name': 'show-mark', + 'type': 'Function', + 'defaultValue': '', + 'desc': { + 'zh-CN': '显示点标记', + 'en-US': 'Show Point Markers' + }, + 'demoId': '' + }, + { + 'name': 'show-new-schedule', + 'type': 'Function', + 'defaultValue': '', + 'desc': { + 'zh-CN': '显示新增日程按钮', + 'en-US': 'Show the button for adding a schedule' + }, + 'demoId': '' + }, + { + 'name': 'year', + 'type': 'Number', + 'defaultValue': '', + 'desc': { + 'zh-CN': '日历当前显示年份', + 'en-US': 'Calendar Current Display Year' + }, + 'demoId': '' + }, + ], + 'events': [ + { + 'name': 'month-change', + 'type': 'Function()', + 'defaultValue': '', + 'desc': { + 'zh-CN': '月改变事件', + 'en-US': 'Month Change Event' + }, + 'demoId': '' + }, + { + 'name': 'new-schedule', + 'type': 'Function()', + 'defaultValue': '', + 'desc': { + 'zh-CN': '新增日程按钮点击事件', + 'en-US': 'Event for adding a schedule button.' + }, + 'demoId': '' + }, + { + 'name': 'next-week-click', + 'type': 'Function()', + 'defaultValue': '', + 'desc': { + 'zh-CN': '下一周按钮点击事件', + 'en-US': 'Next week button click event' + }, + 'demoId': '' + }, + { + 'name': 'prev-week-click', + 'type': 'Function()', + 'defaultValue': '', + 'desc': { + 'zh-CN': '上一周按钮点击事件', + 'en-US': 'Button click event of the previous week' + }, + 'demoId': '' + }, + { + 'name': 'selected-date-change', + 'type': 'Function()', + 'defaultValue': '', + 'desc': { + 'zh-CN': '选中日期改变事件', + 'en-US': 'Select Date Change Event' + }, + 'demoId': '' + }, + { + 'name': 'week-change', + 'type': 'Function()', + 'defaultValue': '', + 'desc': { + 'zh-CN': '周改变事件', + 'en-US': 'Week Change Event' + }, + 'demoId': '' + }, + { + 'name': 'year-change', + 'type': 'Function()', + 'defaultValue': '', + 'desc': { + 'zh-CN': '年改变事件', + 'en-US': 'Year Change Event' + }, + 'demoId': '' + }, + ], + 'slot': [ + { + 'name': 'header', + 'type': '', + 'defaultValue': '', + 'desc': { 'zh-CN': '头部插槽', 'en-US': 'Head slot' }, + 'demoId': '' + }, + { + 'name': 'timeline', + 'type': '', + 'defaultValue': '', + 'desc': { 'zh-CN': '时间线插槽,有timeline1-timeline7 7个插槽', 'en-US': 'Timeline slots, with 7 slots timeline1-timeline7' }, + 'demoId': '' + }, + { + 'name': 'tool', + 'type': '', + 'defaultValue': '', + 'desc': { 'zh-CN': '工具栏插槽', 'en-US': 'Toolbar Slot' }, + 'demoId': '' + }, + { + 'name': 'weekday', + 'type': '', + 'defaultValue': '', + 'desc': { 'zh-CN': '日程插槽,有weekday1-weekday7 7个插槽', 'en-US': 'Schedule slots, with 7 slots weekday1-weekday7' }, + 'demoId': '' + } + ] + } + ] } diff --git a/examples/sites/demos/mobile-first/app/exception/webdoc/exception.js b/examples/sites/demos/mobile-first/app/exception/webdoc/exception.js index 95f021070..f1d582c0f 100644 --- a/examples/sites/demos/mobile-first/app/exception/webdoc/exception.js +++ b/examples/sites/demos/mobile-first/app/exception/webdoc/exception.js @@ -1,16 +1,15 @@ export default { column: '2', owner: '', - demos: [ - { + demos: [{ demoId: 'message', name: { 'zh-CN': '基本用法', - 'en-US': 'events' + 'en-US': 'Basic Usage' }, desc: { - 'zh-CN': '

', - 'en-US': '

bbutton click

' + 'zh-CN': '

', + 'en-US': '

' }, codeFiles: ['message.vue'] }, @@ -18,12 +17,11 @@ export default { demoId: 'page-empty', name: { 'zh-CN': '页面级空态', - 'en-US': 'events' + 'en-US': 'Page-level void' }, desc: { - 'zh-CN': - '

通过添加`page-empty`属性展示页面级空态,其中 type 类型有`pagenoperm`、 `pageweaknet`、 `pagenothing`、 `pageservererror`
对应场景:
`pagenoperm` :无访问权限
`pageweaknet` :网络异常
`pagenothing` :你访问的页面不存在
`pageservererror`:服务器异常

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过添加`page-empty`属性展示页面级空态,其中 type 类型有`pagenoperm`、 `pageweaknet`、 `pagenothing`、 `pageservererror`
对应场景:
`pagenoperm` :无访问权限
`pageweaknet` :网络异常
`pagenothing` :你访问的页面不存在
`pageservererror`:服务器异常

', + 'en-US': '

The page-level empty state is displayed by adding the `page-empty` attribute. The type types include `pagenoperm`, `pageweaknet`, `pagenothing`, and `pageservererror`.
Scenario:
`pagenoperm`: no access permission
`pageweaknet`: network exception
`pagenothing`: The page you access does not exist
`pageservererror`: The server is abnormal

' }, codeFiles: ['page-empty.vue'] }, @@ -31,12 +29,11 @@ export default { demoId: 'component-empty', name: { 'zh-CN': '组件级空态', - 'en-US': 'events' + 'en-US': 'Component-level empty state' }, desc: { - 'zh-CN': - '

通过添加`component-empty`属性展示组件级空态,其中 type 类型有`noperm、 nodata、 weaknet、noresult、 nonews`
对应场景:
`noperm` :无访问权限
`nodata` :暂无数据
`weaknet` :网络不给力
`noresult`:无相关搜索结果
`nonews`:暂无最新消息

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过添加`component-empty`属性展示组件级空态,其中 type 类型有`noperm、 nodata、 weaknet、noresult、 nonews`
对应场景:
`noperm` :无访问权限
`nodata` :暂无数据
`weaknet` :网络不给力
`noresult`:无相关搜索结果
`nonews`:暂无最新消息

', + 'en-US': '

Add the `component-empty` attribute to display the component-level empty state. The type type can be `noperm, nodata, weaknet, noresult, or nonews`
The corresponding scenario is as follows:
`noperm`: No access
`nodata`: no data
`weaknet`: network is not powerful
`noresult`: no related search results
`nonews`: no latest news

' }, codeFiles: ['component-empty.vue'] }, @@ -44,11 +41,11 @@ export default { demoId: 'sub-message', name: { 'zh-CN': '自定义二级标题内容', - 'en-US': 'events' + 'en-US': 'User-defined level-2 title content' }, desc: { - 'zh-CN': '

通过`sub-message`自定义二级标题

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过`sub-message`自定义二级标题

', + 'en-US': '

Customizing Level-2 Titles Using `sub-message`

' }, codeFiles: ['sub-message.vue'] }, @@ -59,8 +56,8 @@ export default { 'en-US': 'button-text' }, desc: { - 'zh-CN': '

已去除`button-text`自定义按钮文本,直接可以通过插槽自定义

', - 'en-US': '

bbutton click

' + 'zh-CN': '

已去除`button-text`自定义按钮文本,直接可以通过插槽自定义

', + 'en-US': '

Customizing Level-2 Titles Using `sub-message`

' }, codeFiles: ['button-text.vue'] }, @@ -71,11 +68,86 @@ export default { 'en-US': 'content-slot' }, desc: { - 'zh-CN': '

通过命名插槽 `content`,自定义内容区

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过命名插槽 `content`,自定义内容区

', + 'en-US': '

Customize the content area by naming the slot `content`

' }, codeFiles: ['content-slot.vue'] } ], - apis: [] -} + apis: [{ + 'name': 'Exception', + 'type': 'component', + 'props': [{ + 'name': 'component-page', + 'type': 'Boolean', + 'defaultValue': '', + 'desc': { + 'zh-CN': '设置组件级空态', + 'en-US': 'Set component-level empty state' + }, + 'demoId': 'component-empty' + }, + { + 'name': 'exception-class', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '设置自定义类', + 'en-US': 'Setting Custom Classes' + }, + 'demoId': '' + }, + { + 'name': 'page-empty ', + 'type': 'Boolean', + 'defaultValue': '', + 'desc': { + 'zh-CN': '设置页面级空态', + 'en-US': 'Set page-level empty status' + }, + 'demoId': 'page-empty' + }, + { + 'name': 'sub-message', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '设置二级标题', + 'en-US': 'Set Level-2 Title' + }, + 'demoId': 'sub-message' + }, + { + 'name': 'type', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '设置页面级空态类型。默认值为nodata', + 'en-US': 'Sets the page-level empty state type. The default value is nodata.' + }, + 'demoId': 'page-empty' + } + ], + 'slots': [{ + 'name': 'content', + 'type': '', + 'defaultValue': '', + 'desc': { + 'zh-CN': '内容插槽。可以设置自定义展示内容', + 'en-US': 'Content slot. Custom display content can be set.' + }, + 'demoId': 'content-slot' + }, + { + 'name': 'default', + 'type': '', + 'defaultValue': '', + 'desc': { + 'zh-CN': '默认插槽', + 'en-US': 'Default Slot' + }, + 'demoId': '' + } + ] + }] +} \ No newline at end of file diff --git a/examples/sites/demos/mobile-first/app/flowchart/webdoc/flowchart.js b/examples/sites/demos/mobile-first/app/flowchart/webdoc/flowchart.js index c4364794a..0e6ad0793 100644 --- a/examples/sites/demos/mobile-first/app/flowchart/webdoc/flowchart.js +++ b/examples/sites/demos/mobile-first/app/flowchart/webdoc/flowchart.js @@ -1,16 +1,15 @@ export default { column: '2', owner: '', - demos: [ - { + demos: [{ demoId: 'basic-usage', name: { 'zh-CN': '垂直图形', - 'en-US': 'events' + 'en-US': 'Vertical Graphics' }, desc: { - 'zh-CN': '

', - 'en-US': '

bbutton click

' + 'zh-CN': '

节点设置在不同的列data.nodes[i].info.row,就可以创建垂直图形

', + 'en-US': '

Nodes are set in different columns data.nodes[i].info.row, and vertical graphics can be created.

' }, codeFiles: ['basic-usage.vue'] }, @@ -18,11 +17,11 @@ export default { demoId: 'horizon', name: { 'zh-CN': '水平图形', - 'en-US': 'events' + 'en-US': 'Horizontal Graph' }, desc: { - 'zh-CN': '

', - 'en-US': '

bbutton click

' + 'zh-CN': '

节点设置在不同的列data.nodes[i].info.col,就可以创建水平图形

', + 'en-US': '

Nodes are set in different columns data.nodes[i].info.col, and horizontal graphics can be created.

' }, codeFiles: ['horizon.vue'] }, @@ -30,11 +29,11 @@ export default { demoId: 'dot-vertical', name: { 'zh-CN': '点模式-垂直图形', - 'en-US': 'events' + 'en-US': 'Point Mode - Vertical Graph' }, desc: { - 'zh-CN': '

', - 'en-US': '

bbutton click

' + 'zh-CN': '

', + 'en-US': '

' }, codeFiles: ['dot-vertical.vue'] }, @@ -42,11 +41,11 @@ export default { demoId: 'dot-horizon', name: { 'zh-CN': '点模式-水平图形', - 'en-US': 'events' + 'en-US': 'Point Mode - Horizontal Graph' }, desc: { - 'zh-CN': '

', - 'en-US': '

bbutton click

' + 'zh-CN': '

', + 'en-US': '

' }, codeFiles: ['dot-horizon.vue'] }, @@ -54,11 +53,11 @@ export default { demoId: 'dot-horizon-async', name: { 'zh-CN': '点模式-水平图形-异步', - 'en-US': 'events' + 'en-US': 'Point Mode - Horizontal Graph - Asynchronous' }, desc: { - 'zh-CN': '

', - 'en-US': '

bbutton click

' + 'zh-CN': '

', + 'en-US': '

' }, codeFiles: ['dot-horizon-async.vue'] }, @@ -70,7 +69,7 @@ export default { }, desc: { 'zh-CN': '

', - 'en-US': '

bbutton click

' + 'en-US': '

Point Mode - Vertical Graph - Asynchronous

' }, codeFiles: ['dot-vertical-async.vue'] }, @@ -78,11 +77,11 @@ export default { demoId: 'holistic', name: { 'zh-CN': '全景图', - 'en-US': 'events' + 'en-US': 'Panoramic view' }, desc: { - 'zh-CN': '

', - 'en-US': '

bbutton click

' + 'zh-CN': '

', + 'en-US': '

' }, codeFiles: ['holistic.vue'] }, @@ -90,14 +89,14 @@ export default { demoId: 'holistic-fork', name: { 'zh-CN': '全景图-分叉', - 'en-US': 'events' + 'en-US': 'Panorama - Fork' }, desc: { - 'zh-CN': '

', - 'en-US': '

bbutton click

' + 'zh-CN': '

', + 'en-US': '

' }, codeFiles: ['holistic-fork.vue'] } ], apis: [] -} +} \ No newline at end of file diff --git a/examples/sites/demos/mobile-first/app/image/webdoc/image.js b/examples/sites/demos/mobile-first/app/image/webdoc/image.js index 5244d0191..a19d927cc 100644 --- a/examples/sites/demos/mobile-first/app/image/webdoc/image.js +++ b/examples/sites/demos/mobile-first/app/image/webdoc/image.js @@ -10,7 +10,7 @@ export default { }, desc: { 'zh-CN': '

通过 `image-size` 设置预览的图片尺寸大小,通过`round`设置是否展示圆形。

', - 'en-US': '

button type

' + 'en-US': '

Use `image-size` to set the size of the previewed image and use `round` to set whether to display a circle.

' }, codeFiles: ['basic-usage.vue'] }, @@ -19,11 +19,11 @@ export default { demoId: 'show-hover', name: { 'zh-CN': '图片悬浮', - 'en-US': 'events' + 'en-US': 'Image floating' }, desc: { - 'zh-CN': '

通过 `show-hover` 设置预览的图片的悬浮效果。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过 `show-hover` 设置预览的图片的悬浮效果。

', + 'en-US': '

Use `show-hover` to set the floating effect of the previewed image.

' }, codeFiles: ['show-hover.vue'] }, @@ -31,12 +31,12 @@ export default { demoId: 'auto-fit-container-size', name: { 'zh-CN': '自适应容器尺寸', - 'en-US': 'events' + 'en-US': 'Adaptive Container Size' }, desc: { 'zh-CN': - '

可通过 `fit` 属性确定图片如何适应到容器框,同原生 css 的 object-fit 属性。
object-fit 说明
fill:被替换的内容将被缩放,以在填充元素的内容框时保持其宽高比
contain:被替换的内容大小可以填充元素的内容框
cover:被替换的内容大小保持其宽高比,同时填充元素的整个内容框
none:被替换的内容尺寸不会被改变
scale-down:内容的尺寸就像是指定了 none 或 contain,取决于哪一个将导致更小的对象尺寸。

', - 'en-US': '

bbutton click

' + '

可通过 `fit` 属性确定图片如何适应到容器框,同原生 css 的 object-fit 属性。
object-fit 说明
fill:被替换的内容将被缩放,以在填充元素的内容框时保持其宽高比
contain:被替换的内容大小可以填充元素的内容框
cover:被替换的内容大小保持其宽高比,同时填充元素的整个内容框
none:被替换的内容尺寸不会被改变
scale-down:内容的尺寸就像是指定了 none 或 contain,取决于哪一个将导致更小的对象尺寸。

', + 'en-US': '

You can use the `fit` attribute to determine how an image adapts to the container box, which is the same as the object-fit attribute of the native CSS.
Object-fit Description
fill: The replaced content is scaled to maintain its aspect ratio when filling the element\'s content box.
cover: The size of the replaced content keeps its aspect ratio and fills the entire content box of the element.
none: The size of the content to be replaced is not changed.
scale-down: The size of the content is as if none or contain were specified, depending on which would result in a smaller object size.

' }, codeFiles: ['auto-fit-container-size.vue'] }, @@ -44,11 +44,11 @@ export default { demoId: 'preview-src-list', name: { 'zh-CN': '预览大图', - 'en-US': 'events' + 'en-US': 'Preview Large Image' }, desc: { - 'zh-CN': '

通过 `preview-src-list` 开启预览大图的功能,通过添加`show-index`开启图片序列号展示。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过 `preview-src-list` 开启预览大图的功能,通过添加`show-index`开启图片序列号展示。

', + 'en-US': '

Use the `preview-src-list` command to enable the function of previewing large images, and add the `show-index` command to enable the display of image sequence numbers.

' }, codeFiles: ['preview-src-list.vue'] }, @@ -56,11 +56,11 @@ export default { demoId: 'round', name: { 'zh-CN': '尺寸类型', - 'en-US': 'events' + 'en-US': 'Size Type' }, desc: { - 'zh-CN': '

通过 `image-size` 设置预览的图片尺寸大小,通过 round 设置是否展示圆形。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过 `image-size` 设置预览的图片尺寸大小,通过 round 设置是否展示圆形。

', + 'en-US': '

Use `image-size` to set the size of the previewed image and use round to set whether to display a circle.

' }, codeFiles: ['round.vue'] }, @@ -68,11 +68,11 @@ export default { demoId: 'custom-placeholder', name: { 'zh-CN': '占位内容', - 'en-US': 'events' + 'en-US': 'Placeholder Content' }, desc: { - 'zh-CN': '

可通过 `slot = placeholder` 自定义占位内容。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

可通过 `slot = placeholder` 自定义占位内容。

', + 'en-US': '

You can use `slot = placeholder` to customize the placeholder.

' }, codeFiles: ['custom-placeholder.vue'] }, @@ -80,12 +80,12 @@ export default { demoId: 'lazy-load', name: { 'zh-CN': '懒加载', - 'en-US': 'events' + 'en-US': 'Lazy loading' }, desc: { 'zh-CN': - '

可通过 `lazy` 开启懒加载功能,当图片滚动到可视范围内才会加载。可通过 `scroll-container` 来设置滚动容器,若未定义,则为最近一个 `overflow` 值为 `auto` 或 `scroll` 的父元素。

', - 'en-US': '

bbutton click

' + '

可通过 `lazy` 开启懒加载功能,当图片滚动到可视范围内才会加载。可通过 `scroll-container` 来设置滚动容器,若未定义,则为最近一个 `overflow` 值为 `auto` 或 `scroll` 的父元素。

', + 'en-US': '

You can use `lazy` to enable lazy loading. Images are loaded only when they are scrolled to the visible range. You can use `scroll-container` to set the scrolling container. If it is not defined, the scrolling container is the parent element whose latest `overflow` value is `auto` or `scroll`.

' }, codeFiles: ['lazy-load.vue'] }, @@ -93,11 +93,11 @@ export default { demoId: 'preview-z-index', name: { 'zh-CN': '图片预览层级', - 'en-US': 'events' + 'en-US': 'Image preview level' }, desc: { 'zh-CN': '

可通过 `z-index` 设置预览图片的层级。

', - 'en-US': '

bbutton click

' + 'en-US': '

You can use `z-index` to set the preview image level.

' }, codeFiles: ['preview-z-index.vue'] }, @@ -105,11 +105,11 @@ export default { demoId: 'load-event', name: { 'zh-CN': '加载成功事件', - 'en-US': 'events' + 'en-US': 'Loading success event' }, desc: { - 'zh-CN': '

图片加载成功触发 `load` 事件。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

图片加载成功触发 `load` 事件。

', + 'en-US': '

The `load` event is triggered when the image is loaded successfully.

' }, codeFiles: ['load-event.vue'] }, @@ -117,11 +117,11 @@ export default { demoId: 'load-error', name: { 'zh-CN': '加载失败事件', - 'en-US': 'events' + 'en-US': 'Loading failure event' }, desc: { - 'zh-CN': '

图片加载失败触发 `error` 事件。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

图片加载失败触发 `error` 事件。

', + 'en-US': '

The `error` event is triggered when the image fails to be loaded.

' }, codeFiles: ['load-error.vue'] }, @@ -129,11 +129,11 @@ export default { demoId: 'custom-load-failed-text', name: { 'zh-CN': '加载失败', - 'en-US': 'events' + 'en-US': 'Loading failed.' }, desc: { - 'zh-CN': '

可通过 `slot = error` 自定义加载失败内容。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

可通过 `slot = error` 自定义加载失败内容。

', + 'en-US': '

You can run the `slot = error` command to customize the content that fails to be loaded.

' }, codeFiles: ['custom-load-failed-text.vue'] }, @@ -141,11 +141,11 @@ export default { demoId: 'preview-in-dialog', name: { 'zh-CN': '对话框中预览图片', - 'en-US': 'events' + 'en-US': 'Preview picture in dialog box' }, desc: { - 'zh-CN': '

在 `dialog-box` 元素中嵌入 image 进行图片预览。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

在 `dialog-box` 元素中嵌入 image 进行图片预览。

', + 'en-US': '

Embed the image in the `dialog-box` element for image preview.

' }, codeFiles: ['preview-in-dialog.vue'] }, @@ -153,11 +153,11 @@ export default { demoId: 'thumbnail', name: { 'zh-CN': '缩略图视图(非模态)', - 'en-US': 'events' + 'en-US': 'Thumbnail view (non-modal)' }, desc: { - 'zh-CN': '

通过 `is-thumbnail` 属性开启非模态缩略图视图的功能

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过 `is-thumbnail` 属性开启非模态缩略图视图的功能

', + 'en-US': '

Enable the modeless thumbnail view through the `is-thumbnail` property<

' }, codeFiles: ['thumbnail.vue'] }, @@ -165,12 +165,12 @@ export default { demoId: 'thumbnail-modal-view', name: { 'zh-CN': '缩略图视图(模态)', - 'en-US': 'events' + 'en-US': 'Thumbnail view (modal)' }, desc: { 'zh-CN': - '

通过 `modal-view` 属性开启模态缩略图视图的功能,可以通过modal-height属性设置模态框内对应的高度,默认值为 400。

', - 'en-US': '

bbutton click

' + '

通过 `modal-view` 属性开启模态缩略图视图的功能,可以通过modal-height属性设置模态框内对应的高度,默认值为 400。

', + 'en-US': '

You can use the `modal-view` attribute to enable the modal thumbnail view function. You can use the modal-height attribute to set the height of the modal box. The default value is 400.

' }, codeFiles: ['thumbnail-modal-view.vue'] }, @@ -178,11 +178,11 @@ export default { demoId: 'menu-view', name: { 'zh-CN': '目录视图(非模态)', - 'en-US': 'events' + 'en-US': 'Catalog View (Nonmodal)' }, desc: { - 'zh-CN': '

通过 `is-menu-view` 属性开启非模态目录视图的功能

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过 `is-menu-view` 属性开启非模态目录视图的功能

', + 'en-US': '

Use the `is-menu-view` attribute to enable the non-modal catalog view.

' }, codeFiles: ['menu-view.vue'] }, @@ -190,12 +190,12 @@ export default { demoId: 'menu-modal-view', name: { 'zh-CN': '目录视图(模态)', - 'en-US': 'events' + 'en-US': 'Catalog View (modal)' }, desc: { 'zh-CN': - '

通过 `modal-view` 属性开启模态目录视图的功能,可以通过`modal-height`属性设置模态框内对应的高度,默认值为 400。

', - 'en-US': '

bbutton click

' + '

通过 `modal-view` 属性开启模态目录视图的功能,可以通过`modal-height`属性设置模态框内对应的高度,默认值为 400。

', + 'en-US': '

Use the `modal-view` attribute to enable the modal directory view function. You can use the `modal-height` attribute to set the height of the modal box. The default value is 400.

' }, codeFiles: ['menu-modal-view.vue'] }, @@ -203,11 +203,11 @@ export default { demoId: 'thumbnail-viewer-bg', name: { 'zh-CN': '缩略图视图', - 'en-US': 'events' + 'en-US': 'Thumbnail View' }, desc: { 'zh-CN': '

缩略图视图背景

', - 'en-US': '

bbutton click

' + 'en-US': '

Thumbnail View Background

' }, codeFiles: ['thumbnail-viewer-bg.vue'] }, @@ -215,11 +215,11 @@ export default { demoId: 'menu-viewer-bg', name: { 'zh-CN': '目录视图背景', - 'en-US': 'events' + 'en-US': 'Catalog View Background' }, desc: { - 'zh-CN': '

目录视图

', - 'en-US': '

bbutton click

' + 'zh-CN': '

目录视图

', + 'en-US': '

Table of Contents View

' }, codeFiles: ['menu-viewer-bg.vue'] }, @@ -227,11 +227,11 @@ export default { demoId: 'thumbnail-rename', name: { 'zh-CN': '视图文件名-缩略图', - 'en-US': 'events' + 'en-US': 'View File Name - Thumbnail' }, desc: { - 'zh-CN': '

视图文件名-缩略图

', - 'en-US': '

bbutton click

' + 'zh-CN': '

视图文件名-缩略图

', + 'en-US': '

View File Name - Thumbnail

' }, codeFiles: ['thumbnail-rename.vue'] }, @@ -239,14 +239,83 @@ export default { demoId: 'menu-rename', name: { 'zh-CN': '视图文件名-目录', - 'en-US': 'events' + 'en-US': 'View File Name - Directory' }, desc: { - 'zh-CN': '

视图文件名-目录

', - 'en-US': '

bbutton click

' + 'zh-CN': '

视图文件名-目录

', + 'en-US': '

View File Name - Directory

' }, codeFiles: ['menu-rename.vue'] } ], - apis: [] + apis: [ + { + 'name': 'Image', + 'type': 'component', + 'props': [ + { + 'name': 'image-size ', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '设置预览的图片的大小', + 'en-US': 'Sets the size of the previewed picture.' + }, + 'demoId': 'round' + }, + { + 'name': 'preview-visible', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '开启移动端预览大图的功能', + 'en-US': 'Enable the function of previewing large images on mobile devices.' + }, + 'demoId': '' + }, + { + 'name': 'round', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '通过 round 设置是否展示圆形', + 'en-US': 'Use round to set whether to display a circle.' + }, + 'demoId': 'round' + }, + { + 'name': 'show-hover', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '设置预览的图片的悬浮效果', + 'en-US': 'Sets the floating effect of the previewed image.' + }, + 'demoId': 'show-hover' + }, + { + 'name': 'show-index', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '开启图片序列号展示', + 'en-US': 'Enable the display of image serial numbers.' + }, + 'demoId': 'preview-src-list' + }, + ], + 'events': [ + { + 'name': 'delete', + 'type': 'Function()', + 'defaultValue': '', + 'desc': { + 'zh-CN': '图片删除触发', + 'en-US': 'Triggered by image deletion.' + }, + 'demoId': '' + } + ], + }, + ] } diff --git a/examples/sites/demos/mobile-first/app/pull-refresh/webdoc/pull-refresh.js b/examples/sites/demos/mobile-first/app/pull-refresh/webdoc/pull-refresh.js index 1bef565d3..52ef77a3f 100644 --- a/examples/sites/demos/mobile-first/app/pull-refresh/webdoc/pull-refresh.js +++ b/examples/sites/demos/mobile-first/app/pull-refresh/webdoc/pull-refresh.js @@ -1,8 +1,7 @@ export default { column: '2', owner: '', - demos: [ - { + demos: [{ demoId: 'basic-usage', name: { 'zh-CN': '基本用法', @@ -30,7 +29,7 @@ export default { demoId: 'loosing-text', name: { 'zh-CN': '下拉可刷新的文字提示', - 'en-US': 'basic usage' + 'en-US': 'Drop-down refreshable text prompt' }, desc: { 'zh-CN': '

通过属性 loosing-text 设置 pulling 提示文字

', @@ -42,7 +41,7 @@ export default { demoId: 'success-text', name: { 'zh-CN': '刷新成功的文字', - 'en-US': 'basic usage' + 'en-US': 'Texts that are successfully refreshed' }, desc: { 'zh-CN': '

通过属性 success-text 设置 pulling 提示文字

', @@ -54,7 +53,7 @@ export default { demoId: 'success-duration', name: { 'zh-CN': '刷新成功的文字展示时长', - 'en-US': '刷新成功的文字展示时长' + 'en-US': 'Text display duration that is successfully refreshed.' }, desc: { 'zh-CN': '

设置属性 success-duration 指定延时时间

', @@ -82,8 +81,7 @@ export default { }, desc: { 'zh-CN': `

设置属性 head-height 为数字或字符串,例如:100/'100'/'100px'/'6rem'

`, - 'en-US': `

- Set the property head-height to a number or string, for example: 100/'100'/'100px'/'6rem'

` + 'en-US': `

Set the property head-height to a number or string, for example: 100/'100'/'100px'/'6rem'

` }, codeFiles: ['head-height.vue'] }, @@ -112,5 +110,221 @@ export default { codeFiles: ['pull-refresh-slot.vue'] } ], - apis: [] -} + apis: [{ + 'name': 'pull-refresh', + 'type': 'component', + 'props': [{ + 'name': ' animationDuration', + 'type': '[Number, String]', + 'defaultValue': '默认值是300', + 'desc': { + 'zh-CN': '刷新成功后头部延时消失时间', + 'en-US': 'Header delay disappearing time after successful refresh' + }, + 'demoId': 'animation-duration' + }, + { + 'name': 'disabled', + 'type': 'Boolean', + 'defaultValue': '默认值是false', + 'desc': { + 'zh-CN': '是否禁用下拉刷新', + 'en-US': 'Whether to disable pull-down refresh' + }, + 'demoId': 'disabled' + }, + { + 'name': 'headHeight', + 'type': '[Number, String]', + 'defaultValue': '默认值是50', + 'desc': { + 'zh-CN': '组件头部提示区域的高度', + 'en-US': 'Height of the component head prompt area' + }, + 'demoId': 'head-height' + }, + { + 'name': 'loadingOptions', + 'type': 'Object', + 'defaultValue': '默认值是空对象{}', + 'desc': { + 'zh-CN': '不使用loading插槽时,内置加载组件Loading的参数:Loading.service(options)', + 'en-US': 'When the loading slot is not used, the loading parameter of the built-in loading component is Loading.service(options).' + }, + 'demoId': '' + }, + { + 'name': 'loadingText', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '数据刷新过程的提示文本', + 'en-US': 'Prompt text for the data refresh process' + }, + 'demoId': '' + }, + { + 'name': 'loosingText', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '向下拖动超过阈值后可松开进行刷新的提示文本', + 'en-US': 'Drag down to release the prompt text for refresh after the threshold is exceeded.' + }, + 'demoId': 'loosing-text' + }, + { + 'name': 'modelValue', + 'type': 'Boolean', + 'defaultValue': '默认值是false', + 'desc': { + 'zh-CN': '是否正在进行下拉刷新', + 'en-US': 'Is the pull-down refresh in progress' + }, + 'demoId': '' + }, + { + 'name': 'pullDistance', + 'type': '[Number, String]', + 'defaultValue': '默认值是属性 `headHeight` 的值', + 'desc': { + 'zh-CN': '拖拽产生刷新的距离', + 'en-US': 'Refreshing distance generated by dragging' + }, + 'demoId': '' + }, + { + 'name': 'pullingText', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '向下拖动过程的提示文本', + 'en-US': 'Drag down the prompt text for the procedure' + }, + 'demoId': 'pulling-text' + }, + { + 'name': 'selfSimulate', + 'type': 'Boolean', + 'defaultValue': '默认值是false', + 'desc': { + 'zh-CN': '是否开启自模拟Touch事件。如果开启,那么组件只处理自身产生的模拟Touch事件,忽略冒泡上来的非自身模拟事件。正常非模拟Touch事件始终不会跳过处理', + 'en-US': 'Indicates whether to enable the self-simulation Touch event. If this function is enabled, the component only processes simulated Touch events generated by itself and ignores non-simulated events generated by itself. Normal non-simulated Touch events never skip processing' + }, + 'demoId': '' + }, + { + 'name': 'successDuration', + 'type': '[Number, String]', + 'defaultValue': '默认值是500', + 'desc': { + 'zh-CN': '刷新成功提示文本延时消失时间', + 'en-US': 'Time for the text to disappear after the refresh is successful.' + }, + 'demoId': 'success-duration' + }, + { + 'name': 'successText', + 'type': 'String', + 'defaultValue': '', + 'desc': { + 'zh-CN': '刷新成功的提示文本', + 'en-US': 'Text of the message indicating that the refresh is successful' + }, + 'demoId': 'success-text' + } + ], + 'events': [{ + 'name': 'change', + 'type': 'Function({ status, distance })', + 'defaultValue': 'status:{String 组件当前状态},distance:{Number 当前的拖动距离}', + 'desc': { + 'zh-CN': '在组件状态改变时抛出', + 'en-US': 'Thrown when the component state changes' + }, + 'demoId': '' + }, + { + 'name': 'refresh', + 'type': 'Function()', + 'defaultValue': '', + 'desc': { + 'zh-CN': '在组件状态进入 loading 刷新数据时抛出', + 'en-US': 'Thrown when the component enters the loading state to refresh data' + }, + 'demoId': '' + }, + { + 'name': 'update:modelValue', + 'type': 'Function(isLoading)', + 'defaultValue': 'isLoading:{Boolean 组件是否处于 loading}', + 'desc': { + 'zh-CN': '在组件状态进入 loading 刷新数据时抛出', + 'en-US': 'Thrown when the component enters the loading state to refresh data' + }, + 'demoId': '' + } + ], + 'slots': [{ + 'name': 'default', + 'type': '', + 'defaultValue': '', + 'desc': { + 'zh-CN': '组件默认插槽', + 'en-US': 'Component Default Slot' + }, + 'demoId': 'pull-refresh-slot' + }, + { + 'name': 'loading', + 'type': '', + 'defaultValue': '', + 'desc': { + 'zh-CN': '组件 loading 插槽,自定义 loading 状态的提示', + 'en-US': 'Component loading slot, which is used to customize the loading status prompt.' + }, + 'demoId': 'pull-refresh-slot' + }, + { + 'name': 'loosing', + 'type': '', + 'defaultValue': '', + 'desc': { + 'zh-CN': '组件 loosing 插槽,自定义 loosing 状态的提示', + 'en-US': 'Component loosing slot, which is used to customize the loosing status prompt.' + }, + 'demoId': 'pull-refresh-slot' + }, + { + 'name': 'normal', + 'type': '', + 'defaultValue': '', + 'desc': { + 'zh-CN': '组件 normal 插槽,自定义 normal 状态的提示', + 'en-US': 'Indicates the normal slot of the component. The prompt for the normal status is customized.' + }, + 'demoId': 'pull-refresh-slot' + }, + { + 'name': 'pulling', + 'type': '', + 'defaultValue': '', + 'desc': { + 'zh-CN': '组件 pulling 插槽,自定义 pulling 状态的提示', + 'en-US': 'Pull slot of the component, which is used to customize the prompt of the pull status.' + }, + 'demoId': 'pull-refresh-slot' + }, + { + 'name': 'success', + 'type': '', + 'defaultValue': '', + 'desc': { + 'zh-CN': '组件 success 插槽,自定义 success 状态的提示', + 'en-US': 'Indicates the success slot of the component. The success status is customized.' + }, + 'demoId': 'pull-refresh-slot' + } + ] + }] +} \ No newline at end of file diff --git a/examples/sites/demos/mobile-first/app/tooltip/webdoc/tooltip.js b/examples/sites/demos/mobile-first/app/tooltip/webdoc/tooltip.js index a16d54236..4155bbaa2 100644 --- a/examples/sites/demos/mobile-first/app/tooltip/webdoc/tooltip.js +++ b/examples/sites/demos/mobile-first/app/tooltip/webdoc/tooltip.js @@ -1,15 +1,14 @@ export default { column: '2', owner: '', - demos: [ - { + demos: [{ demoId: 'basic-usage', name: { 'zh-CN': '基本用法', 'en-US': 'basic usage' }, desc: { - 'zh-CN': '

', + 'zh-CN': '

按钮类型

', 'en-US': '

button type

' }, codeFiles: ['basic-usage.vue'] @@ -19,11 +18,11 @@ export default { demoId: 'dynamic-disable', name: { 'zh-CN': '禁用', - 'en-US': 'events' + 'en-US': 'disabling' }, desc: { - 'zh-CN': '

通过 `disabled` 设置禁用

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过 `disabled` 设置禁用

', + 'en-US': '

Disable by setting `disabled`

' }, codeFiles: ['dynamic-disable.vue'] }, @@ -31,12 +30,11 @@ export default { demoId: 'manual-control-tip', name: { 'zh-CN': '手动控制', - 'en-US': 'events' + 'en-US': 'Manual control' }, desc: { - 'zh-CN': - '

手动控制模式,通过设置 `manual` 属性为 true 后,mouseenter 和 mouseleave 事件将不会生效,然后可以通过设置 `v-model` 动态控制显示和隐藏

', - 'en-US': '

bbutton click

' + 'zh-CN': '

手动控制模式,通过设置 `manual` 属性为 true 后,mouseenter 和 mouseleave 事件将不会生效,然后可以通过设置 `v-model` 动态控制显示和隐藏

', + 'en-US': '

Manual control mode. After the `manual` attribute is set to true, the mouseenter and mouseleave events do not take effect. You can set the `v-model` to dynamically control the display and hiding of the events.

' }, codeFiles: ['manual-control-tip.vue'] }, @@ -44,11 +42,11 @@ export default { demoId: 'tooltip-theme', name: { 'zh-CN': '主题', - 'en-US': 'events' + 'en-US': 'Subject' }, desc: { - 'zh-CN': '

通过 `effect` 属性设置主题,可选值 dark/light 两种主题。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过 `effect` 属性设置主题,可选值 dark/light 两种主题。

', + 'en-US': '

Set the theme through the `effect` property. The options are dark and light.

' }, codeFiles: ['tooltip-theme.vue'] }, @@ -56,12 +54,11 @@ export default { demoId: 'open-delay', name: { 'zh-CN': '自动隐藏和延迟时间', - 'en-US': 'events' + 'en-US': 'Auto-hide and delay time' }, desc: { - 'zh-CN': - '

通过 `hide-after` 属性设置 Tooltip 组件出现后自动隐藏延时,单位毫秒,为 0 则不会自动隐藏。通过 `open-delay` 属性设置 Tooltip 组件延迟出现的时间,单位毫秒。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过 `hide-after` 属性设置 Tooltip 组件出现后自动隐藏延时,单位毫秒,为 0 则不会自动隐藏。通过 `open-delay` 属性设置 Tooltip 组件延迟出现的时间,单位毫秒。

', + 'en-US': '

The `hide-after` attribute is used to set the automatic hiding delay of the Tooltip component, in milliseconds. If the value is 0, the Tooltip component is not automatically hidden. The `open-delay` property is used to set the delay for the Tooltip component to appear, in milliseconds.

' }, codeFiles: ['open-delay.vue'] }, @@ -69,11 +66,11 @@ export default { demoId: 'tooltip-offset', name: { 'zh-CN': '偏移量', - 'en-US': 'events' + 'en-US': 'Offset' }, desc: { - 'zh-CN': '

通过 `offset` 属性设置 Tooltip 组件出现位置的偏移量。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过 `offset` 属性设置 Tooltip 组件出现位置的偏移量。

', + 'en-US': '

Use the `offset` property to set the offset where the Tooltip component appears.

' }, codeFiles: ['tooltip-offset.vue'] }, @@ -81,11 +78,11 @@ export default { demoId: 'enterable', name: { 'zh-CN': '鼠标是否可进入', - 'en-US': 'events' + 'en-US': 'Whether the mouse can be accessed' }, desc: { - 'zh-CN': '

通过 `enterable` 属性设置鼠标是否可进入到 tooltip 中。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过 `enterable` 属性设置鼠标是否可进入到 tooltip 中。

', + 'en-US': '

Use the `enterable` attribute to set whether the mouse can access the tooltip.

' }, codeFiles: ['enterable.vue'] }, @@ -93,11 +90,11 @@ export default { demoId: 'visible-arrow', name: { 'zh-CN': '是否显示箭头', - 'en-US': 'events' + 'en-US': 'Whether to display arrows' }, desc: { - 'zh-CN': '

通过 `visible-arrow` 属性设置是否显示 Tooltip 箭头。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过 `visible-arrow` 属性设置是否显示 Tooltip 箭头。

', + 'en-US': '

Use the `visible-arrow` property to set whether to display the Tooltip arrow.

' }, codeFiles: ['visible-arrow.vue'] }, @@ -105,11 +102,11 @@ export default { demoId: 'popper-class', name: { 'zh-CN': '添加样式类名', - 'en-US': 'events' + 'en-US': 'Add Style Class Name' }, desc: { - 'zh-CN': '

通过 `popper-class` 属性为 Tooltip 的 popper 添加类名。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过 `popper-class` 属性为 Tooltip 的 popper 添加类名。

', + 'en-US': '

Use the `popper-class` attribute to add a class name to the tooltip\'s popper.

' }, codeFiles: ['popper-class.vue'] }, @@ -117,11 +114,11 @@ export default { demoId: 'tooltip-content', name: { 'zh-CN': '插槽', - 'en-US': 'events' + 'en-US': 'Slots' }, desc: { 'zh-CN': '

设置过通过 `content` 插槽添加自定义内容。滤类型

', - 'en-US': '

bbutton click

' + 'en-US': '

Set to add custom content through the `content` slot. Filter Type

' }, codeFiles: ['tooltip-content.vue'] }, @@ -129,12 +126,11 @@ export default { demoId: 'popper-options', name: { 'zh-CN': 'popper 配置', - 'en-US': 'events' + 'en-US': 'Popper Configuration' }, desc: { - 'zh-CN': - '

通过 `popper-options` 属性为 Tooltip 的 popper 配置参数,具体可参考[popper.js](https://popper.js.org/)

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过 `popper-options` 属性为 Tooltip 的 popper 配置参数,具体可参考[popper.js](https://popper.js.org/)

', + 'en-US': '

Use the `popper-options` attribute to configure popper parameters for Tooltip. For details, see [popper.js](https://popper.js.org/).

' }, codeFiles: ['popper-options.vue'] }, @@ -142,11 +138,11 @@ export default { demoId: 'popper-options-bubbling', name: { 'zh-CN': 'popper 配置通过 bubbling 控制 tip 位置', - 'en-US': 'events' + 'en-US': 'The popper configuration uses bubbling to control the tip position.' }, desc: { - 'zh-CN': '

popperOptions.bubbling 配置为 true 时,所有的父元素包含滚动条,滚动时都会动态改变 tip 的位置

', - 'en-US': '

bbutton click

' + 'zh-CN': '

popperOptions.bubbling 配置为 true 时,所有的父元素包含滚动条,滚动时都会动态改变 tip 的位置

', + 'en-US': '

When popperOptions.bubbling is set to true, all parent elements contain scroll bars. During scrolling, the position of tip is dynamically changed.

' }, codeFiles: ['popper-options-bubbling.vue'] }, @@ -154,11 +150,11 @@ export default { demoId: 'tabindex', name: { 'zh-CN': 'tabindex 配置', - 'en-US': 'events' + 'en-US': 'tabindex configuration' }, desc: { - 'zh-CN': '

通过配置 `tabindex`, 配置的属性会自动添加到该组件的触发原上。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过配置 `tabindex`, 配置的属性会自动添加到该组件的触发原上。

', + 'en-US': '

By configuring `tabindex`, the configured attributes are automatically added to the trigger source of the component.

' }, codeFiles: ['tabindex.vue'] }, @@ -166,12 +162,11 @@ export default { demoId: 'content-pre', name: { 'zh-CN': '文本预格式化', - 'en-US': 'events' + 'en-US': 'Text Preformatting' }, desc: { - 'zh-CN': - '

配置 `pre` 为 `true`,就会预格式化 `content` 文本。
被包围在 `

` 标签元素中的文本会保留空格和换行符,文本也会呈现为等宽字体。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

配置 `pre` 为 `true`,就会预格式化 `content` 文本。
被包围在 `

` 标签元素中的文本会保留空格和换行符,文本也会呈现为等宽字体。

', + 'en-US': '

If `pre` is set to `true`, the `content` text is preformatted.
Text enclosed in the `

` tag element retains spaces and newline characters, and the text is rendered in an equal-width font.

' }, codeFiles: ['content-pre.vue'] }, @@ -179,11 +174,11 @@ export default { demoId: 'content-render', name: { 'zh-CN': '自定义渲染', - 'en-US': 'events' + 'en-US': 'Custom Rendering' }, desc: { - 'zh-CN': '

使用属性 `renderContent` 配置自定义渲染方法

', - 'en-US': '

bbutton click

' + 'zh-CN': '

使用属性 `renderContent` 配置自定义渲染方法

', + 'en-US': '

Configure a custom rendering method using the property `renderContent`

' }, codeFiles: ['content-render.vue'] }, @@ -191,15 +186,14 @@ export default { demoId: 'z-index', name: { 'zh-CN': '弹出层 zIndex', - 'en-US': 'events' + 'en-US': 'z-index' }, desc: { - 'zh-CN': - '

设置`z-index`属性为`relative`时,弹出层样式属性 zIndex 值参考 Reference 及其父级 Dom。
设置`z-index`属性为默认值`next`时,弹出层样式属性 zIndex 值由组件库内部维护。

', - 'en-US': '

bbutton click

' + 'zh-CN': '

设置`z-index`属性为`relative`时,弹出层样式属性 zIndex 值参考 Reference 及其父级 Dom。
设置`z-index`属性为默认值`next`时,弹出层样式属性 zIndex 值由组件库内部维护。

', + 'en-US': '

When the `z-index` attribute is set to `relative`, the value of zIndex of the pop-up layer style attribute is Reference and its parent Dom.
When the `z-index` attribute is set to the default value `next`, the value of the pop-up layer style attribute zIndex is maintained by the component library.

' }, codeFiles: ['z-index.vue'] } ], apis: [] -} +} \ No newline at end of file diff --git a/examples/sites/demos/mobile/app/alert/basic-usage.vue b/examples/sites/demos/mobile/app/alert/basic-usage.vue new file mode 100644 index 000000000..ab0615278 --- /dev/null +++ b/examples/sites/demos/mobile/app/alert/basic-usage.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/examples/sites/demos/mobile/app/alert/center.vue b/examples/sites/demos/mobile/app/alert/center.vue new file mode 100644 index 000000000..2feb5dc64 --- /dev/null +++ b/examples/sites/demos/mobile/app/alert/center.vue @@ -0,0 +1,13 @@ + + + diff --git a/examples/sites/demos/mobile/app/alert/custom-close.vue b/examples/sites/demos/mobile/app/alert/custom-close.vue index 7f044b98b..f8fe84042 100644 --- a/examples/sites/demos/mobile/app/alert/custom-close.vue +++ b/examples/sites/demos/mobile/app/alert/custom-close.vue @@ -1,18 +1,36 @@ - + + diff --git a/examples/sites/demos/mobile/app/alert/webdoc/alert.js b/examples/sites/demos/mobile/app/alert/webdoc/alert.js index cf089f40a..93eca9452 100644 --- a/examples/sites/demos/mobile/app/alert/webdoc/alert.js +++ b/examples/sites/demos/mobile/app/alert/webdoc/alert.js @@ -2,6 +2,17 @@ export default { column: '2', owner: '', demos: [ + { + 'demoId': 'base', + 'name': { 'zh-CN': '基本用法', 'en-US': 'Basic Usage' }, + 'desc': { + 'zh-CN': + '通过 type 设置不同的类型 可选值:success | warning | info | error ,默认值:success。
同时可通过 default 默认插槽自定义提示内容。', + 'en-US': + 'Set different types through type. The options are success | warning | info | error. The default value is success.
You can customize the prompt content through the default slot. ' + }, + 'codeFiles': ['basic-usage.vue'] + }, { demoId: 'custom-close', name: { @@ -26,6 +37,15 @@ export default { }, codeFiles: ['icon.vue'] }, + { + 'demoId': 'center', + 'name': { 'zh-CN': '文字居中', 'en-US': 'Center text' }, + 'desc': { + 'zh-CN': '

通过 center 属性可使文字显示居中。

\n', + 'en-US': '

You can use the center property to center the text.

\n' + }, + 'codeFiles': ['center.vue'] + }, { demoId: 'size', name: { @@ -38,6 +58,18 @@ export default { }, codeFiles: ['size.vue'] }, + { + demoId: 'target', + name: { + 'zh-CN': '指定挂载节点', + 'en-US': 'Specify display node' + }, + desc: { + 'zh-CN': '通过 target 属性绑定要挂载的容器ref值,实现自定义展示位置', + 'en-US': 'Specify display position' + }, + codeFiles: ['target.vue'] + }, { demoId: 'slot-default', name: { @@ -56,6 +88,13 @@ export default { name: 'alert', // 组件名称展示使用 type: 'component', // API 类型 properties: [ + { + 'name': 'center', + 'type': 'boolean', + 'defaultValue': 'false', + 'desc': { 'zh-CN': '文字是否居中', 'en-US': 'Whether the text is centered' }, + 'demoId': 'center' + }, { name: 'closable', type: 'Boolean', @@ -106,6 +145,17 @@ export default { }, demoId: 'size' }, + { + name: 'target', + type: 'String', + defaultValue: '', + desc: { + 'zh-CN': '挂载容器的ref值,通过该属性可以让组件展示在对应容器的顶部', + 'en-US': + 'Mounted container reference, this property allows the component to be displayed at the top of the corresponding container.' + }, + demoId: 'target' + }, { name: 'type', type: 'String', diff --git a/examples/sites/demos/mobile/app/checkbox/basic-usage.vue b/examples/sites/demos/mobile/app/checkbox/basic-usage.vue new file mode 100644 index 000000000..4310bad18 --- /dev/null +++ b/examples/sites/demos/mobile/app/checkbox/basic-usage.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/examples/sites/demos/mobile/app/checkbox/default.vue b/examples/sites/demos/mobile/app/checkbox/checkbox-group.vue similarity index 57% rename from examples/sites/demos/mobile/app/checkbox/default.vue rename to examples/sites/demos/mobile/app/checkbox/checkbox-group.vue index 85ad23ad9..7d48ac72d 100644 --- a/examples/sites/demos/mobile/app/checkbox/default.vue +++ b/examples/sites/demos/mobile/app/checkbox/checkbox-group.vue @@ -1,19 +1,14 @@ - - diff --git a/examples/sites/demos/mobile/app/checkbox/indeterminate.vue b/examples/sites/demos/mobile/app/checkbox/indeterminate.vue index 3e116062f..3c9abacd8 100644 --- a/examples/sites/demos/mobile/app/checkbox/indeterminate.vue +++ b/examples/sites/demos/mobile/app/checkbox/indeterminate.vue @@ -7,7 +7,7 @@ - - diff --git a/examples/sites/demos/mobile/app/input/autosize.vue b/examples/sites/demos/mobile/app/input/autosize.vue deleted file mode 100644 index c79f0b3b3..000000000 --- a/examples/sites/demos/mobile/app/input/autosize.vue +++ /dev/null @@ -1,41 +0,0 @@ - - - - - diff --git a/examples/sites/demos/mobile/app/input/basic-usage.vue b/examples/sites/demos/mobile/app/input/basic-usage.vue new file mode 100644 index 000000000..5250952aa --- /dev/null +++ b/examples/sites/demos/mobile/app/input/basic-usage.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/examples/sites/demos/mobile/app/input/blur-focus.vue b/examples/sites/demos/mobile/app/input/blur-focus.vue deleted file mode 100644 index 29e2949ec..000000000 --- a/examples/sites/demos/mobile/app/input/blur-focus.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - - - diff --git a/examples/sites/demos/mobile/app/input/clearable.vue b/examples/sites/demos/mobile/app/input/clearable.vue index cfe8fec87..4adeac379 100644 --- a/examples/sites/demos/mobile/app/input/clearable.vue +++ b/examples/sites/demos/mobile/app/input/clearable.vue @@ -1,10 +1,10 @@ - - diff --git a/examples/sites/demos/mobile/app/input/cols-rows.vue b/examples/sites/demos/mobile/app/input/cols-rows.vue deleted file mode 100644 index 5b34218c9..000000000 --- a/examples/sites/demos/mobile/app/input/cols-rows.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/examples/sites/demos/mobile/app/input/counter.vue b/examples/sites/demos/mobile/app/input/counter.vue index ac606caaa..e31622b14 100644 --- a/examples/sites/demos/mobile/app/input/counter.vue +++ b/examples/sites/demos/mobile/app/input/counter.vue @@ -1,10 +1,12 @@ - - diff --git a/examples/sites/demos/mobile/app/input/disabled.vue b/examples/sites/demos/mobile/app/input/disabled.vue new file mode 100644 index 000000000..2e46ce203 --- /dev/null +++ b/examples/sites/demos/mobile/app/input/disabled.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/examples/sites/demos/mobile/app/input/events.vue b/examples/sites/demos/mobile/app/input/events.vue index 58d0a255c..bcfe7ef39 100644 --- a/examples/sites/demos/mobile/app/input/events.vue +++ b/examples/sites/demos/mobile/app/input/events.vue @@ -1,11 +1,14 @@ - - diff --git a/examples/sites/demos/mobile/app/input/form.vue b/examples/sites/demos/mobile/app/input/form.vue deleted file mode 100644 index 3c1b403dc..000000000 --- a/examples/sites/demos/mobile/app/input/form.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - - - diff --git a/examples/sites/demos/mobile/app/input/input-event.vue b/examples/sites/demos/mobile/app/input/input-event.vue deleted file mode 100644 index 4af203306..000000000 --- a/examples/sites/demos/mobile/app/input/input-event.vue +++ /dev/null @@ -1,39 +0,0 @@ - - - - - diff --git a/examples/sites/demos/mobile/app/input/max-min-length.vue b/examples/sites/demos/mobile/app/input/max-min-length.vue index ea4dfb381..49dfc94e8 100644 --- a/examples/sites/demos/mobile/app/input/max-min-length.vue +++ b/examples/sites/demos/mobile/app/input/max-min-length.vue @@ -1,10 +1,10 @@ - - diff --git a/examples/sites/demos/mobile/app/input/method-select.vue b/examples/sites/demos/mobile/app/input/method-select.vue deleted file mode 100644 index e55c460a7..000000000 --- a/examples/sites/demos/mobile/app/input/method-select.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - - - diff --git a/examples/sites/demos/mobile/app/input/methods.vue b/examples/sites/demos/mobile/app/input/methods.vue new file mode 100644 index 000000000..6d5cfb4ce --- /dev/null +++ b/examples/sites/demos/mobile/app/input/methods.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/examples/sites/demos/mobile/app/input/native-attributes.vue b/examples/sites/demos/mobile/app/input/native-attributes.vue new file mode 100644 index 000000000..4f1f0da96 --- /dev/null +++ b/examples/sites/demos/mobile/app/input/native-attributes.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/examples/sites/demos/mobile/app/input/prefix-suffix-icon.vue b/examples/sites/demos/mobile/app/input/prefix-suffix-icon.vue new file mode 100644 index 000000000..69e83a8a8 --- /dev/null +++ b/examples/sites/demos/mobile/app/input/prefix-suffix-icon.vue @@ -0,0 +1,35 @@ + + + + + \ No newline at end of file diff --git a/examples/sites/demos/mobile/app/input/prefix-suffix.vue b/examples/sites/demos/mobile/app/input/prefix-suffix.vue deleted file mode 100644 index 2d1b9be8b..000000000 --- a/examples/sites/demos/mobile/app/input/prefix-suffix.vue +++ /dev/null @@ -1,39 +0,0 @@ - - - - - diff --git a/examples/sites/demos/mobile/app/input/props-step.vue b/examples/sites/demos/mobile/app/input/props-step.vue deleted file mode 100644 index e2e03335f..000000000 --- a/examples/sites/demos/mobile/app/input/props-step.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/examples/sites/demos/mobile/app/input/props.vue b/examples/sites/demos/mobile/app/input/props.vue deleted file mode 100644 index 05fccdb03..000000000 --- a/examples/sites/demos/mobile/app/input/props.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/examples/sites/demos/mobile/app/input/show-password.vue b/examples/sites/demos/mobile/app/input/show-password.vue index 82fb6972e..552066833 100644 --- a/examples/sites/demos/mobile/app/input/show-password.vue +++ b/examples/sites/demos/mobile/app/input/show-password.vue @@ -1,5 +1,5 @@ @@ -19,10 +19,10 @@ export default { } - diff --git a/examples/sites/demos/mobile/app/input/slot-content.vue b/examples/sites/demos/mobile/app/input/slot-content.vue deleted file mode 100644 index 9fc43a8d6..000000000 --- a/examples/sites/demos/mobile/app/input/slot-content.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - - - diff --git a/examples/sites/demos/mobile/app/input/slots-append-prepend.vue b/examples/sites/demos/mobile/app/input/slots-append-prepend.vue deleted file mode 100644 index c642382e9..000000000 --- a/examples/sites/demos/mobile/app/input/slots-append-prepend.vue +++ /dev/null @@ -1,34 +0,0 @@ - - - - - diff --git a/examples/sites/demos/mobile/app/input/slots.vue b/examples/sites/demos/mobile/app/input/slots.vue new file mode 100644 index 000000000..b8df1de82 --- /dev/null +++ b/examples/sites/demos/mobile/app/input/slots.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/examples/sites/demos/mobile/app/input/suffix-icon.vue b/examples/sites/demos/mobile/app/input/suffix-icon.vue deleted file mode 100644 index 61b8d431d..000000000 --- a/examples/sites/demos/mobile/app/input/suffix-icon.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - - - diff --git a/examples/sites/demos/mobile/app/input/textarea-rows-cols.vue b/examples/sites/demos/mobile/app/input/textarea-rows-cols.vue new file mode 100644 index 000000000..efd8eec5a --- /dev/null +++ b/examples/sites/demos/mobile/app/input/textarea-rows-cols.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/examples/sites/demos/mobile/app/input/textarea-scalable.vue b/examples/sites/demos/mobile/app/input/textarea-scalable.vue new file mode 100644 index 000000000..f35171e52 --- /dev/null +++ b/examples/sites/demos/mobile/app/input/textarea-scalable.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/examples/sites/demos/mobile/app/input/type-be-form-tips.vue b/examples/sites/demos/mobile/app/input/type-be-form-tips.vue deleted file mode 100644 index 74ae03aac..000000000 --- a/examples/sites/demos/mobile/app/input/type-be-form-tips.vue +++ /dev/null @@ -1,55 +0,0 @@ - - - - - diff --git a/examples/sites/demos/mobile/app/input/type-be-form.vue b/examples/sites/demos/mobile/app/input/type-be-form.vue deleted file mode 100644 index b5c4a577f..000000000 --- a/examples/sites/demos/mobile/app/input/type-be-form.vue +++ /dev/null @@ -1,47 +0,0 @@ - - - - - diff --git a/examples/sites/demos/mobile/app/input/type-be-form-select.vue b/examples/sites/demos/mobile/app/input/type-select.vue similarity index 60% rename from examples/sites/demos/mobile/app/input/type-be-form-select.vue rename to examples/sites/demos/mobile/app/input/type-select.vue index 72801ae49..89b43c9f6 100644 --- a/examples/sites/demos/mobile/app/input/type-be-form-select.vue +++ b/examples/sites/demos/mobile/app/input/type-select.vue @@ -1,10 +1,10 @@ - - diff --git a/examples/sites/demos/mobile/app/input/type.vue b/examples/sites/demos/mobile/app/input/type.vue new file mode 100644 index 000000000..6e9a41066 --- /dev/null +++ b/examples/sites/demos/mobile/app/input/type.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/examples/sites/demos/mobile/app/input/validate-event.vue b/examples/sites/demos/mobile/app/input/validate-event.vue index a5f1308a2..800d5858f 100644 --- a/examples/sites/demos/mobile/app/input/validate-event.vue +++ b/examples/sites/demos/mobile/app/input/validate-event.vue @@ -1,5 +1,5 @@ - - diff --git a/examples/sites/demos/mobile/app/input/webdoc/input.js b/examples/sites/demos/mobile/app/input/webdoc/input.js index db1885d1a..cafda5ed8 100644 --- a/examples/sites/demos/mobile/app/input/webdoc/input.js +++ b/examples/sites/demos/mobile/app/input/webdoc/input.js @@ -3,265 +3,207 @@ export default { owner: '', demos: [ { - demoId: 'autofocus', + demoId: 'basic-usage', name: { - 'zh-CN': '自动获取焦点', - 'en-US': 'button type' + 'zh-CN': '基本用法', + 'en-US': 'basic usage' }, desc: { - 'zh-CN': '

自动获取焦点

', - 'en-US': '

button type

' + 'zh-CN': + '

通过 v-model/modelValue 属性绑定输入值,placeholder 属性显示提示文本;使用原生属性 type 指定输入框类型,如取值为 textarea 时用作文本框。

', + 'en-US': + '

Bind the input value through the v-model/modelValue property, and use placeholder to bind placeholder.Specify the input box type with the native type attribute. When the value is set to textarea, it is used as a textarea.

' }, - codeFiles: ['autofocus.vue'] + codeFiles: ['basic-usage.vue'] }, { - demoId: 'autosize', - name: { - 'zh-CN': '自适应内容高度', - 'en-US': 'button round' + 'demoId': 'clearable', + 'name': { 'zh-CN': '一键清空', 'en-US': 'Clearable' }, + 'desc': { + 'zh-CN': '

可通过 clearable 属性设置输入框显示清空图标按钮。

', + 'en-US': + '

You can set the clearable attribute to display the clear icon button in the input.

' }, - desc: { - 'zh-CN': '

自适应内容高度

', - 'en-US': '

button round

' - }, - codeFiles: ['autosize.vue'] + 'codeFiles': ['clearable.vue'] }, { - demoId: 'blur-focus', + demoId: 'type', name: { - 'zh-CN': '获取/失去焦点', - 'en-US': 'events' + 'zh-CN': '类型', + 'en-US': 'type' }, desc: { - 'zh-CN': '

获取/失去焦点

', - 'en-US': '

bbutton click

' + 'zh-CN': + '

通过对应的 type 属性,可以设置为对应的类型。默认为 text,可选值为 text,textarea 和其他 原生 input 的 type 值

', + 'en-US': + '

You can set the corresponding type through the corresponding type attribute. The default value is text. The options are text, textarea, and other type values of the native input

' }, - codeFiles: ['blur-focus.vue'] + codeFiles: ['type.vue'] }, { - demoId: 'clearable', + demoId: 'textarea-rows-cols', name: { - 'zh-CN': '可清空', - 'en-US': 'events' + 'zh-CN': '文本框行数与列数', + 'en-US': 'Rows and Columns of Textarea' }, desc: { - 'zh-CN': '

可清空

' + 'zh-CN': '

通过原生属性 rowscols 分别指定文本框的行数与列字符数。

', + 'en-US': + '

Specify the number of rows and columns of a text box using the native attributes rows and cols respectively.

' }, - codeFiles: ['clearable.vue'] + codeFiles: ['textarea-rows-cols.vue'] }, { - demoId: 'cols-rows', + demoId: 'textarea-scalable', name: { - 'zh-CN': '原生属性', - 'en-US': 'events' + 'zh-CN': '文本框可缩放', + 'en-US': 'Scalable Textarea' }, desc: { - 'zh-CN': '

宽度和高度

', - 'en-US': '

bbutton click

' + 'zh-CN': `

可通过 resize 属性设置文本框的缩放。可选值:none | both | horizontal | vertical

可通过 autosize 属性设置文本框自适应内容高度。可传入对象,如 { minRows: 2, maxRows: 6 }。`, + 'en-US': `

You can use the resize property to set the zoom of the text field. The value can be none | both | horizontal | vertical . Note: This parameter is valid only when type="textarea" is used.

+

You can use the autosize attribute to set the text field to adapt to the content height. You can transfer an object, for example, {minRows: 2, maxRows: 6} . Note: This parameter is valid only for type="textarea" .

` }, - codeFiles: ['cols-rows.vue'] - }, - { - demoId: 'counter', - name: { - 'zh-CN': '计数与输入统计', - 'en-US': 'events' - }, - desc: { - 'zh-CN': '

计数与输入统计

', - 'en-US': '

bbutton click

' - }, - codeFiles: ['counter.vue'] - }, - { - demoId: 'events', - name: { - 'zh-CN': '事件', - 'en-US': 'events' - }, - desc: { - 'zh-CN': '

事件

', - 'en-US': '

bbutton click

' - }, - codeFiles: ['events.vue'] - }, - { - demoId: 'form', - name: { - 'zh-CN': 'form', - 'en-US': 'events' - }, - desc: { - 'zh-CN': '

form

', - 'en-US': '

bbutton click

' - }, - codeFiles: ['form.vue'] - }, - { - demoId: 'input-event', - name: { - 'zh-CN': '事件', - 'en-US': 'events' - }, - desc: { - 'zh-CN': '

blur / focus / input 事件

', - 'en-US': '

bbutton click

' - }, - codeFiles: ['input-event.vue'] - }, - { - demoId: 'max-min-length', - name: { - 'zh-CN': '输入长度限制', - 'en-US': 'events' - }, - desc: { - 'zh-CN': '

输入长度限制

', - 'en-US': '

bbutton click

' - }, - codeFiles: ['max-min-length.vue'] - }, - { - demoId: 'method-select', - name: { - 'zh-CN': '选中输入框文本', - 'en-US': 'events' - }, - desc: { - 'zh-CN': '

选中输入框文本

', - 'en-US': '

bbutton click

' - }, - codeFiles: ['method-select.vue'] - }, - { - demoId: 'prefix-suffix', - name: { - 'zh-CN': '复合型输入框', - 'en-US': 'events' - }, - desc: { - 'zh-CN': '

复合型输入框

', - 'en-US': '

bbutton click

' - }, - codeFiles: ['prefix-suffix.vue'] - }, - { - demoId: 'props-step', - name: { - 'zh-CN': '步长', - 'en-US': 'events' - }, - desc: { - 'zh-CN': '

通过step设置步长,需要结合 type="number" 一起使用

', - 'en-US': '

bbutton click

' - }, - codeFiles: ['props-step.vue'] - }, - { - demoId: 'props', - name: { - 'zh-CN': '原生属性', - 'en-US': 'events' - }, - desc: { - 'zh-CN': '

是否只读

', - 'en-US': '

bbutton click

' - }, - codeFiles: ['props.vue'] + codeFiles: ['textarea-scalable.vue'] }, { demoId: 'show-password', name: { 'zh-CN': '密码框', - 'en-US': 'events' + 'en-US': 'Password Input' }, desc: { - 'zh-CN': '

密码框

', - 'en-US': '

bbutton click

' + 'zh-CN': + '

typepassword 时,可通过 show-password 属性设置输入框显示密码显示/隐藏切换图标按钮。

', + 'en-US': + '

When type is set to password, you can set the show-password attribute to display or hide the password switch button in the text box.

' }, codeFiles: ['show-password.vue'] }, { - demoId: 'slot-content', + demoId: 'disabled', + name: { + 'zh-CN': '禁用', + 'en-US': 'disabled' + }, + desc: { + 'zh-CN': '

通过 code 属性禁用输入框。

', + 'en-US': '

To disabled input through code props.

' + }, + codeFiles: ['disabled.vue'] + }, + { + demoId: 'counter', + name: { + 'zh-CN': '输入长度限制与计数', + 'en-US': 'Input Limitation and Counting' + }, + desc: { + 'zh-CN': + '

通过 maxlength 属性限制输入字符数; counter 属性显示当前输入字符数;show-word-limit 显示当前字符数与输入限制长度(仅限文本框)

', + 'en-US': + '

Limit input character count with the maxlength attribute; display current input character count with the counter attribute; show current character count and input limit length with the show-word-limit attribute(for text boxes only)."

' + }, + codeFiles: ['counter.vue'] + }, + { + demoId: 'prefix-suffix-icon', + name: { 'zh-CN': '图标', 'en-US': 'Icon' }, + desc: { + 'zh-CN': '

可通过 prefix-icon, suffix-icon 属性设置输入框头部、尾部图标

', + 'en-US': + '

You can set the header icon or the end icon of the text box through the prefix-icon, suffix-icon attribute

' + }, + codeFiles: ['prefix-suffix-icon.vue'] + }, + { + demoId: 'native-attributes', + name: { + 'zh-CN': '原生属性', + 'en-US': 'Native Attributes' + }, + desc: { + 'zh-CN': '

可设置 autofocusreadonlyname 等原生属性。

', + 'en-US': '

Set native attributes such as autofocus, readonly, name, etc.

' + }, + codeFiles: ['native-attributes.vue'] + }, + { + demoId: 'methods', + name: { + 'zh-CN': '实例方法', + 'en-US': 'Methods' + }, + desc: { + 'zh-CN': '

可使用组件的实例方法:focus | blur | select

', + 'en-US': '

Those instance methods available: focus | blur | select

' + }, + codeFiles: ['methods.vue'] + }, + { + demoId: 'events', + name: { + 'zh-CN': '事件', + 'en-US': 'Events' + }, + desc: { + 'zh-CN': ` +

输入框的事件,包括 + input(输入值时触发), + blur(失去焦点时触发), + focus(获取焦点时触发), + change(值改变时触发), + clear(清除按钮时触发) +

+ `, + 'en-US': ` +

Event of the text box, including: + input (triggered when a value is entered) + blur (triggered when the focus is lost) + focus (triggered when the focus is obtained) + change (triggered when the value changes) + clear (triggered when the button is cleared) +

+ ` + }, + codeFiles: ['events.vue'] + }, + { + demoId: 'slots', name: { 'zh-CN': '插槽', 'en-US': 'events' }, desc: { - 'zh-CN': '

通过content自定义头部内容,只对 type="textarea" 有效

', - 'en-US': '

bbutton click

' + 'zh-CN': '

Input 组件提供了丰富的插槽。

', + 'en-US': '

Input component provides a rich set of slots.

' }, - codeFiles: ['slot-content.vue'] + codeFiles: ['slots.vue'] }, { - demoId: 'slots-append-prepend', + demoId: 'type-select', name: { - 'zh-CN': '复合型输入框', + 'zh-CN': '下拉列表', 'en-US': 'events' }, desc: { - 'zh-CN': '

通过append自定义头部内容,通过 prepend自定义尾部内容,

', - 'en-US': '

bbutton click

' + 'zh-CN': '

通过使用 is-selectselect-menu 搭配让输入框变为下拉列表。

', + 'en-US': + '

By using is-select with select-menu, the input field can be turned into a dropdown list.

' }, - codeFiles: ['slots-append-prepend.vue'] - }, - { - demoId: 'suffix-icon', - name: { - 'zh-CN': '自定义后置图标', - 'en-US': 'events' - }, - desc: { - 'zh-CN': '

自定义后置图标

', - 'en-US': '

bbutton click

' - }, - codeFiles: ['suffix-icon.vue'] - }, - { - demoId: 'type-be-form-select', - name: { - 'zh-CN': '类型type为 form 时的下拉用法', - 'en-US': 'events' - }, - desc: { - 'zh-CN': '

is-selectselect-menu搭配让type为form的输入框变为下拉

', - 'en-US': '

bbutton click

' - }, - codeFiles: ['type-be-form-select.vue'] - }, - { - demoId: 'type-be-form-tips', - name: { - 'zh-CN': 'form类型的文字提示', - 'en-US': 'events' - }, - desc: { - 'zh-CN': '

类型type为 form 时的输入框下的提示

', - 'en-US': '

bbutton click

' - }, - codeFiles: ['type-be-form-tips.vue'] - }, - { - demoId: 'type-be-form', - name: { - 'zh-CN': 'type为form类型', - 'en-US': 'events' - }, - desc: { - 'zh-CN': '

类型type为 form 时的基本用法

', - 'en-US': '

bbutton click

' - }, - codeFiles: ['type-be-form.vue'] + codeFiles: ['type-select.vue'] }, { demoId: 'validate-event', name: { - 'zh-CN': '表单校正', - 'en-US': 'events' + 'zh-CN': '表单校验', + 'en-US': 'form validation' }, desc: { - 'zh-CN': '

通过validate-event属性设置输入时是否触发表单的校验,该属性默认为 true。

', - 'en-US': '

bbutton click

' + 'zh-CN': + '

可通过 validate-event 属性设置输入时触发表单校验。通过 trigger 配置触发校验规则的方式,为 change 时,当输入框值改变即触发校验,为 blur 时则失焦后触发校验

', + 'en-US': + '

You can set the validate-event attribute to trigger form validation upon input. Use trigger to configure the mode of triggering the validation rule. If change is used, the validation is triggered when the value in the text box changes. If blur is used, the validation is triggered after the focus is lost

' }, codeFiles: ['validate-event.vue'] } @@ -276,39 +218,40 @@ export default { type: 'String', defaultValue: 'off', desc: { - 'zh-CN': '

原生属性,自动补全,该属性默认为off

', - 'en-US': 'display different button' + 'zh-CN': '原生属性,自动补全', + 'en-US': 'Native autocomplete attribute' }, - demoId: '' + demoId: 'native-attributes' }, { name: 'autofocus', type: 'Boolean', defaultValue: 'false', desc: { - 'zh-CN': '

原生属性,自动获取焦点,该属性默认为false

', - 'en-US': 'display different button' + 'zh-CN': '原生属性,自动获取焦点', + 'en-US': 'Native attribute. The focus is automatically obtained during page loading.' }, - demoId: 'autofocus' + demoId: 'native-attributes' }, { name: 'autosize', type: 'Boolean | Object', defaultValue: 'false', desc: { - 'zh-CN': - '

自适应内容高度,只对 type="textarea"有效,可传入对象,如,{ minRows: 2, maxRows: 6 },该属性默认为false

', - 'en-US': 'display different button' + 'zh-CN': '自适应内容高度,只对 type="textarea"有效。可传入对象,如,{ minRows: 2, maxRows: 6 }', + 'en-US': + 'Adaptive content height. This parameter is valid only for type="textarea". Objects can be transferred, for example, { minRows: 2, maxRows: 6 }' }, - demoId: 'autosize' + demoId: 'textarea-scalable' }, { name: 'clearable', type: 'Boolean', defaultValue: 'false', desc: { - 'zh-CN': '

选中时的值

', - 'en-US': 'display different button' + 'zh-CN': '是否显示清除按钮,该属性不适用于 type="textarea"', + 'en-US': + 'Whether to display the clear button. This attribute is not applicable to type="textarea"' }, demoId: 'clearable' }, @@ -317,18 +260,20 @@ export default { type: 'Number', defaultValue: '', desc: { - 'zh-CN': '

原生属性,设置宽度,在 type ="textarea"时有效

', - 'en-US': 'display different button' + 'zh-CN': '原生属性,设置宽度,在 type ="textarea"时有效', + 'en-US': + 'Native attribute, which is used to set the width. This parameter is valid only when type is "textarea"' }, - demoId: 'cols-rows' + demoId: 'textarea-rows-cols' }, { name: 'counter', type: 'Boolean', defaultValue: 'false', desc: { - 'zh-CN': '

是否显示字数统计,只在 type = "text" 或 type = "textarea" 时有效

', - 'en-US': 'display different button' + 'zh-CN': '是否显示字数统计,只在 type = "text" 或 type = "textarea" 时有效', + 'en-US': + 'Whether to display the word count statistics. This parameter is valid only when type is "text" or type is "textarea"' }, demoId: 'counter' }, @@ -337,128 +282,148 @@ export default { type: 'Boolean', defaultValue: 'false', desc: { - 'zh-CN': '

禁用,该属性默认为false性

', - 'en-US': 'display different button' + 'zh-CN': '是否禁用', + 'en-US': 'Disabled' }, - demoId: 'autosize' - }, - { - name: 'form', - type: 'String', - defaultValue: '', - desc: { - 'zh-CN': '

原生属性

', - 'en-US': 'display different button' - }, - demoId: '' + demoId: 'disabled' }, { name: 'is-select', type: 'Boolean', defaultValue: 'false', desc: { - 'zh-CN': '

type为form时,设置输入框可下拉选择

', + 'zh-CN': '是否可下拉选择', 'en-US': 'display different button' }, - demoId: 'type-be-form-select' + demoId: 'type-select' }, { name: 'label', type: 'String', defaultValue: '', desc: { - 'zh-CN': '

输入框关联的label文字

', - 'en-US': 'display different button' + 'zh-CN': '等价于原生 aria-label 属性', + 'en-US': 'equal to aria-label attribute' }, - demoId: 'props' + demoId: '' + }, + { + name: 'max', + type: 'number', + defaultValue: '', + desc: { + 'zh-CN': '原生属性,设置最大值', + 'en-US': 'Native attribute, set the maximum value' + }, + demoId: 'native-attributes' }, { name: 'maxlength', type: 'Number', defaultValue: '', desc: { - 'zh-CN': '

原生属性,最大输入长度

', - 'en-US': 'display different button' + 'zh-CN': '原生属性,最大输入长度', + 'en-US': 'Native attribute, maximum input length' }, - demoId: 'max-min-length' + demoId: 'counter' + }, + { + name: 'min', + type: 'number', + defaultValue: '', + desc: { + 'zh-CN': '原生属性,设置最小值', + 'en-US': 'Native attribute, which sets the minimum value' + }, + demoId: 'native-attributes' }, { name: 'minlength', type: 'Number', defaultValue: '', desc: { - 'zh-CN': '

原生属性,最小输入长度

', - 'en-US': 'display different button' + 'zh-CN': '原生属性,最小输入长度', + 'en-US': 'Native attribute, minimum input length' }, - demoId: 'max-min-length' + demoId: 'counter' }, { - name: 'mobile-tips', - type: 'String', + name: 'tips', + type: 'String | Slot', defaultValue: '', desc: { - 'zh-CN': '

原生属性,设置最小值

', - 'en-US': 'display different button' + 'zh-CN': '提示信息', + 'en-US': 'tips under input' }, - demoId: 'type-be-form-tips' + demoId: 'slots' }, { name: 'name', type: 'String', defaultValue: '', desc: { - 'zh-CN': '

原生属性

', - 'en-US': 'display different button' + 'zh-CN': '原生 name 属性', + 'en-US': 'native name attribute' }, - demoId: 'props' + demoId: 'native-attributes' }, { - name: 'placeholde', + name: 'placeholder', type: 'String', defaultValue: '', desc: { - 'zh-CN': '

输入框占位文本

', - 'en-US': 'display different button' + 'zh-CN': '输入框占位文本', + 'en-US': 'placeholder text' }, - demoId: 'prefix-suffix' + demoId: 'basic-usage' + }, + { + name: 'prefix-icon', + type: 'Component', + defaultValue: '', + desc: { + 'zh-CN': '输入框头部图标', + 'en-US': 'Icon at the head' + }, + demoId: 'prefix-suffix-icon' }, { name: 'readonly', type: 'Boolean', defaultValue: 'false', desc: { - 'zh-CN': '

原生属性,是否只读,该属性默认为false

', - 'en-US': 'display different button' + 'zh-CN': '原生属性,是否只读', + 'en-US': 'Native attribute, read-only' }, - demoId: 'props' + demoId: 'native-attributes' }, { name: 'rows', type: 'Number', defaultValue: '2', desc: { - 'zh-CN': '

输入框行数,只对 type="textarea"有效,该属性默认为2

', - 'en-US': 'display different button' + 'zh-CN': '输入框行数,只对 type="textarea"有效', + 'en-US': 'Number of lines in the input box. This parameter is valid only for type="textarea"' }, - demoId: 'cols-rows' + demoId: 'textarea-rows-cols' }, { name: 'select-menu', type: 'Array', defaultValue: '', desc: { - 'zh-CN': '

type为form时,设置输入框可下拉选择,设置选择项

', - 'en-US': 'display different button' + 'zh-CN': '下拉选择的选项', + 'en-US': 'options of select' }, - demoId: 'type-be-form-select' + demoId: 'type-select' }, { name: 'show-password', type: 'Boolean', defaultValue: 'false', desc: { - 'zh-CN': '

是否显示切换密码图标,该属性默认为false

', - 'en-US': 'display different button' + 'zh-CN': '是否显示切换密码图标', + 'en-US': 'Whether to display the switchover password icon' }, demoId: 'show-password' }, @@ -467,8 +432,9 @@ export default { type: 'Boolean', defaultValue: 'false', desc: { - 'zh-CN': '

是否显示输入字数统计,只在 type = ‘text’ 或 type = ‘textarea’ 时有效,该属性默认为false

', - 'en-US': 'display different button' + 'zh-CN': '是否显示输入字数统计,只在 type = ‘text’ 或 type = ‘textarea’ 时有效,该属性默认为false', + 'en-US': + 'Whether to display the number of input words. This parameter is valid only when type is "text" or type is "textarea"' }, demoId: 'counter' }, @@ -477,70 +443,71 @@ export default { type: 'Number', defaultValue: '', desc: { - 'zh-CN': '

原生属性,设置输入字段的合法数字间隔

', - 'en-US': 'display different button' + 'zh-CN': '原生属性,设置输入字段的合法数字间隔', + 'en-US': 'Native attribute, which is used to set the valid digit interval of the input field' }, - demoId: 'props-step' + demoId: 'native-attributes' }, { name: 'suffix-icon', type: 'String', defaultValue: '', desc: { - 'zh-CN': '

输入框尾部图标

', - 'en-US': 'display different button' + 'zh-CN': '输入框尾部图标', + 'en-US': 'Icon at the right' }, - demoId: 'suffix-icon' + demoId: 'prefix-suffix-icon' }, { - name: 'textarea-title', - type: 'String', - defaultValue: '标题', + name: 'title', + type: 'String | Slot', + defaultValue: '', desc: { - 'zh-CN': '

textarea类型自定义标题,该属性默认为‘标题’

', - 'en-US': 'display different button' + 'zh-CN': '自定义标题', + 'en-US': 'custom title' }, - demoId: 'autosize' + demoId: '' }, { - name: ' type', + name: 'type', type: 'String', defaultValue: 'text', desc: { - 'zh-CN': '

input 元素的类型,该属性默认为"text"

', - 'en-US': 'display different button' + 'zh-CN': '类型,同原生 input 标签的 type 属性', + 'en-US': 'the type of input' }, - demoId: 'autofocus' + demoId: 'type' }, { name: 'validate-event', type: 'Boolean', defaultValue: 'true', desc: { - 'zh-CN': '

输入时是否触发表单的校验,该属性默认为true

', - 'en-US': 'display different button' + 'zh-CN': '输入时是否触发表单的校验', + 'en-US': ' Whether form validation is triggered when entering data' }, demoId: 'validate-event' }, { - name: 'value / v-model', + name: 'v-model / modelValue', type: 'String', defaultValue: '', desc: { - 'zh-CN': '

绑定值

', - 'en-US': 'display different button' + 'zh-CN': '绑定值', + 'en-US': 'input value' }, - demoId: 'autofocus' + demoId: 'basic-usage' }, { - name: 'vertical', - type: 'Number', + name: 'width', + type: 'Number | String', defaultValue: '', desc: { - 'zh-CN': '

原生属性,设置最小值

', - 'en-US': 'display different button' + 'zh-CN': '宽度,取值为数字或元素 width 属性有效值,如果是数字则以 px 为单位', + 'en-US': + 'width, accepts a numerical value or a valid value for the width property, and if it is a number, it is measured in pixels.' }, - demoId: 'autofocus' + demoId: '' } ], methods: [ @@ -549,30 +516,30 @@ export default { type: '', defaultValue: '', desc: { - 'zh-CN': '

使 input 失去焦点

', - 'en-US': 'display different button' + 'zh-CN': '使 input 失去焦点', + 'en-US': 'Make the input lose focus.' }, - demoId: 'blur-focus' + demoId: 'methods' }, { name: 'focus', type: '', defaultValue: '', desc: { - 'zh-CN': '

使 input 获取焦点

', - 'en-US': 'display different button' + 'zh-CN': '使 input 获取焦点', + 'en-US': 'Focus the input' }, - demoId: 'blur-focus' + demoId: 'methods' }, { name: 'select', type: '', defaultValue: '', desc: { - 'zh-CN': '

选中 input 中的文字

', - 'en-US': 'display different button' + 'zh-CN': '选中 input 中的文字', + 'en-US': 'Select the text in the input' }, - demoId: 'method-select' + demoId: 'methods' } ], events: [ @@ -581,8 +548,8 @@ export default { type: '', defaultValue: '', desc: { - 'zh-CN': '

在 Input 值改变时触发

', - 'en-US': 'Click' + 'zh-CN': '在 Input 值改变时触发', + 'en-US': 'callback when value has changed' }, demoId: 'events' }, @@ -591,40 +558,40 @@ export default { type: '', defaultValue: '', desc: { - 'zh-CN': '

在 在 Input 失去焦点时触发

', - 'en-US': 'Click' + 'zh-CN': '在 Input 失去焦点时触发', + 'en-US': 'callback when input lost focus' }, - demoId: 'blur-focus' + demoId: 'events' }, { name: 'focus', type: '', defaultValue: '', desc: { - 'zh-CN': '

在 Input 获得焦点时触发

', - 'en-US': 'Click' + 'zh-CN': '在 Input 获得焦点时触发', + 'en-US': 'callback when input gains focus' }, - demoId: 'blur-focus' + demoId: 'events' }, { name: 'clear', type: '', defaultValue: '', desc: { - 'zh-CN': '

在点击由 clearable 属性生成的清空按钮时触发

', - 'en-US': 'Click' + 'zh-CN': '在点击由 clearable 属性生成的清空按钮时触发', + 'en-US': 'callback when click clear icon' }, - demoId: 'blur-focus' + demoId: 'events' }, { name: 'input', type: '', defaultValue: '', desc: { - 'zh-CN': '

输入值时触发事件

', - 'en-US': 'Click' + 'zh-CN': '输入值时触发事件', + 'en-US': 'callback when user input' }, - demoId: 'input-event' + demoId: 'events' } ], slots: [ @@ -633,50 +600,52 @@ export default { type: '', defaultValue: '', desc: { - 'zh-CN': '

输入框后置内容,只对 type="text"有效

', - 'en-US': 'slots-append-prepend' + 'zh-CN': '输入框后置内容,只对 type="text"有效', + 'en-US': 'append content for input fields only works with type="text"' }, - demoId: '' + demoId: 'slots' }, { name: 'prepend', type: '', defaultValue: '', desc: { - 'zh-CN': '

输入框前置内容,只对 type="text"有效

', - 'en-US': 'slots-append-prepend' + 'zh-CN': '输入框前置内容,只对 type="text"有效', + 'en-US': 'prepend content for input fields only works with type="text"' }, - demoId: '' + demoId: 'slots' }, { name: 'prefix', - type: '', - defaultValue: '', desc: { - 'zh-CN': '

输入框头部内容,只对 type="text"有效

', - 'en-US': 'Click' + 'zh-CN': '输入框头部内容,只对 type="text"有效', + 'en-US': 'prefix content for input fields only works with type="text"' }, - demoId: 'prefix-suffix' + demoId: 'slots' }, { name: 'suffix', - type: '', - defaultValue: '', desc: { - 'zh-CN': '

输入框尾部内容,只对 type="text"有效

', - 'en-US': 'Click' + 'zh-CN': '输入框尾部内容,只对 type="text"有效', + 'en-US': 'suffix content for input fields only works with type="text"' }, - demoId: 'prefix-suffix' + demoId: 'slots' }, { - name: 'content', - type: '', - defaultValue: '', + name: 'title', desc: { - 'zh-CN': '

输入框头部内容插槽,只对 type="textarea"有效

', - 'en-US': 'Click' + 'zh-CN': '标题插槽', + 'en-US': 'title slot' }, - demoId: 'slot-content' + demoId: 'slots' + }, + { + name: 'tips', + desc: { + 'zh-CN': '提示信息插槽', + 'en-US': 'tips slot' + }, + demoId: 'slots' } ] } diff --git a/examples/sites/demos/mobile/app/mask/basic-usage.vue b/examples/sites/demos/mobile/app/mask/basic-usage.vue new file mode 100644 index 000000000..e6c9c76ac --- /dev/null +++ b/examples/sites/demos/mobile/app/mask/basic-usage.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/examples/sites/demos/mobile/app/mask/click-no-hide.vue b/examples/sites/demos/mobile/app/mask/click-no-hide.vue new file mode 100644 index 000000000..d39f88b09 --- /dev/null +++ b/examples/sites/demos/mobile/app/mask/click-no-hide.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/examples/sites/demos/mobile/app/mask/slot-default.vue b/examples/sites/demos/mobile/app/mask/slot-default.vue new file mode 100644 index 000000000..756f60fba --- /dev/null +++ b/examples/sites/demos/mobile/app/mask/slot-default.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/examples/sites/demos/mobile/app/mask/webdoc/mask.cn.md b/examples/sites/demos/mobile/app/mask/webdoc/mask.cn.md new file mode 100644 index 000000000..e10af4180 --- /dev/null +++ b/examples/sites/demos/mobile/app/mask/webdoc/mask.cn.md @@ -0,0 +1,11 @@ +--- +title: Mask 列表 +--- + +# Mask 遮罩层 + +
+ +遮罩层通常用于覆盖在页面上,以阻止用户对页面或元素的操作,同时可以显示一些提示信息或加载状态等。 + +
diff --git a/examples/sites/demos/mobile/app/mask/webdoc/mask.en.md b/examples/sites/demos/mobile/app/mask/webdoc/mask.en.md new file mode 100644 index 000000000..b56ca49f1 --- /dev/null +++ b/examples/sites/demos/mobile/app/mask/webdoc/mask.en.md @@ -0,0 +1,11 @@ +--- +title: Mask +--- + +# Mask + +
+ +A mask layer is typically used to cover a page, preventing users from interacting with the page or elements, while displaying some informational or loading state messages. + +
diff --git a/examples/sites/demos/mobile/app/mask/webdoc/mask.js b/examples/sites/demos/mobile/app/mask/webdoc/mask.js new file mode 100644 index 000000000..7f3c01692 --- /dev/null +++ b/examples/sites/demos/mobile/app/mask/webdoc/mask.js @@ -0,0 +1,114 @@ +export default { + column: '2', + owner: '', + demos: [ + { + demoId: 'base', + name: { + 'zh-CN': '基本使用', + 'en-US': 'left right arrow' + }, + desc: { + 'zh-CN': '

通过 v-model:visible 属性设置显示与隐藏遮罩层;click 监听点击事件。

', + 'en-US': '

double-line

' + }, + codeFiles: ['basic-usage.vue'] + }, + { + demoId: 'cancel-touch', + name: { + 'zh-CN': '点击遮罩层不隐藏', + 'en-US': 'No hide after clicking' + }, + desc: { + 'zh-CN': '

通过 cancel-touch 属性设置点击遮罩层后组件不隐藏

', + 'en-US': + '

"By setting the cancel-touch attribute, the component will not be hidden when clicking on the mask layer.

' + }, + codeFiles: ['click-no-hide.vue'] + }, + { + demoId: 'z-index', + name: { + 'zh-CN': '层叠数值', + 'en-US': 'z-index' + }, + desc: { + 'zh-CN': '

通过 z-index 属性设置层叠数值。

', + 'en-US': '

"Use the z-index attribute to set z-index of CSS property.

' + }, + codeFiles: ['z-index.vue'] + }, + { + demoId: 'slot-default', + name: { + 'zh-CN': '默认插槽', + 'en-US': 'Default slot' + }, + desc: { + 'zh-CN': '

通过 default 默认插槽自定义嵌入内容。

', + 'en-US': + '

"By setting the cancel-touch attribute, the component will not be hidden when clicking on the mask layer.

' + }, + codeFiles: ['slot-default.vue'] + } + ], + apis: [ + { + name: 'Mask', + type: 'component', + properties: [ + { + name: 'cancelTouch', + type: 'boolean', + defaultValue: 'false', + desc: { + 'zh-CN': '是否禁用touch事件,设为 true 后点击遮罩层不会关闭', + 'en-US': "whether to disable touch events, and mask wouldn't close when it was set true" + }, + demoId: 'cancel-touch' + }, + { + name: 'visible', + type: 'boolean', + defaultValue: 'false', + desc: { + 'zh-CN': '是否展示遮罩层', + 'en-US': 'whether to show mask layer' + }, + demoId: 'base' + }, + { + name: 'z-index', + type: 'number', + defaultValue: '99', + desc: { + 'zh-CN': '层叠数值', + 'en-US': 'z-index property' + }, + demoId: 'z-index' + } + ], + events: [ + { + name: 'click', + desc: { + 'zh-CN': '点击遮罩层触发', + 'en-US': 'Click' + }, + demoId: 'basic-usage' + } + ], + slots: [ + { + name: 'default', + desc: { + 'zh-CN': '默认插槽', + 'en-US': 'Default slot' + }, + demoId: 'slot-default' + } + ] + } + ] +} diff --git a/examples/sites/demos/mobile/app/mask/z-index.vue b/examples/sites/demos/mobile/app/mask/z-index.vue new file mode 100644 index 000000000..96e8d696b --- /dev/null +++ b/examples/sites/demos/mobile/app/mask/z-index.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/examples/sites/demos/mobile/menus.js b/examples/sites/demos/mobile/menus.js index c87756a0d..6957af093 100644 --- a/examples/sites/demos/mobile/menus.js +++ b/examples/sites/demos/mobile/menus.js @@ -98,7 +98,8 @@ export const cmpMenus = [ { name: 'Loading', nameCn: '加载', key: 'loading' }, { name: 'Modal', nameCn: '模态框', key: 'modal' }, { name: 'Popover', nameCn: '气泡', key: 'popover' }, - { name: 'Toast', nameCn: '轻提示', key: 'toast' } + { name: 'Toast', nameCn: '轻提示', key: 'toast' }, + { name: 'Mask', nameCn: '遮罩层', key: 'mask' } ] }, { diff --git a/examples/sites/demos/pc/app/count-api.js b/examples/sites/demos/pc/app/count-api.js new file mode 100644 index 000000000..b7dd91ae3 --- /dev/null +++ b/examples/sites/demos/pc/app/count-api.js @@ -0,0 +1,34 @@ +const fs = require('fs'); +const basePath = '.'; + +const readFile = function (path) { + let componentCount = 0; + let apiCount = 0; + const readDir = fs.readdirSync(path); + + readDir.forEach(i => { + const curDir = `${path}/${i}`; + const stat = fs.statSync(curDir); + + if (stat.isFile() && i.endsWith('.json')) { + componentCount += 1; + const data = fs.readFileSync(curDir, 'utf-8'); + let dataJson = null; + + try { + dataJson = JSON.parse(data); + } catch (err) { + console.log('err:', err); + } + + if (dataJson !== null) { + apiCount += (dataJson.attrs?.length || 0) + (dataJson.slots?.length || 0) + (dataJson.events?.length || 0) + (dataJson.methods?.length || 0); + } + } + }); + + console.log('componentCount:', componentCount); + console.log('apiCount:', apiCount); +} + +readFile(basePath); diff --git a/examples/sites/demos/pc/app/drawer/basic-usage.vue b/examples/sites/demos/pc/app/drawer/basic-usage.vue index 2d87857be..2cb7184d1 100644 --- a/examples/sites/demos/pc/app/drawer/basic-usage.vue +++ b/examples/sites/demos/pc/app/drawer/basic-usage.vue @@ -31,3 +31,10 @@ export default { } } + + diff --git a/gulp/buildLowcodeBundle.mjs b/gulp/buildLowcodeBundle.mjs new file mode 100644 index 000000000..a041127b2 --- /dev/null +++ b/gulp/buildLowcodeBundle.mjs @@ -0,0 +1,353 @@ +import gulp from 'gulp' +import minimist from 'minimist' +import shell from 'shelljs' +import path from 'path' +import { generateKey } from 'crypto' + +const build = gulp.series(init, read, write, clean) +build.description = '将本地文档的组件api, 转换为低代码需要的bundle.json 格式' +build.flags = { + '--apiPath': '设置api文档路径,默认为: examples/sites/demos/pc', + '--tinyVer': '设置生成中,tinyVer的版本号,默认为 3.11.0' +} +export default build + +const rootPath = process.cwd() +let apiPath = 'examples/sites/demos/pc' +let tinyVer = '3.11.0' +let guid = 1 +let menus = [] + +const ignoreKeys = ['color', 'font', 'icon'] +const mixinKeys = { + 'form': [{ key: 'form-item', nameCn: '表单项', desc: '表单中的一行表单域对象' }], + 'layout': [ + { key: 'row', nameCn: '行元素', desc: '行元素' }, + { key: 'col', nameCn: '列元素', desc: '列元素' } + ], + 'breadcrumb': [{ key: 'breadcrumb-item', nameCn: '面包屑项', desc: '面包屑的层级对象' }], + 'timeline': [{ key: 'timeline-item', nameCn: '时间线数据项', desc: '时间线数据项' }], + 'dropdown': [ + { key: 'dropdown-menu', nameCn: '下拉菜单', desc: '下拉菜单' }, + { key: 'dropdown-item', nameCn: '下拉菜单项', desc: '下拉菜单项' } + ], + 'carousel': [{ key: 'carousel-item', nameCn: '走马灯数据项', desc: '走马灯数据项' }], + 'checkbox': [ + { key: 'checkbox-group', nameCn: '复选框组', desc: '复选框组' }, + { key: 'checkbox-button', nameCn: '复选框按钮', desc: '复选框按钮' } + ], + 'radio': [ + { key: 'radio-group', nameCn: '单选框组', desc: '单选框组' }, + { key: 'radio-button', nameCn: '单选框按钮', desc: '单选框按钮' } + ], + 'grid': [ + { key: 'grid-column', nameCn: '表格列对象', desc: '表格列对象' }, + { key: 'grid-toolbar', nameCn: '表格工具栏', desc: '表格工具栏' } + ] +} + +const result = { + 'data': { + 'framework': 'Vue', + 'materials': { + 'components': [], + 'snippets': [], + 'blocks': [] + } + } +} + +// 1. 初始化 +function init(cb) { + const argv = minimist(process.argv.slice(2)) + if (argv.apiPath) { + apiPath = argv.apiPath + } + if (argv.tinyVer) { + tinyVer = argv.tinyVer + } + + // 读菜单 + shell.cp(path.join(apiPath, '/menus.js'), 'gulp/menus.mjs') + shell.sed('-i', 'import.meta.env.VITE_BUILD_TARGET', 'false', 'gulp/menus.mjs') + import('./menus.mjs').then((res) => { + menus = res.cmpMenus + cb() + }) +} + +// 2. 遍历菜单,处理每一个组件 +function read(cb) { + menus.forEach((group) => { + const snippetItem = { + 'group': group.label, + 'children': [] + } + group.children.forEach((component) => { + if (ignoreKeys.includes(component.key)) { + return + } + if (component.key.startsWith('grid-') || component.key.startsWith('chart-')) { + return + } + + const componentInfo = _readOneComp(component) + const componentItem = genComp(componentInfo) + const snipItem = genSnip(componentInfo) + + result.data.materials.components.push(componentItem) + snippetItem.children.push(snipItem) + + // 特殊的混合组件判断, 比如遍历到form时,要插入 form-item的定义 + if (mixinKeys[component.key]) { + mixinKeys[component.key].forEach((mixin) => { + const componentInfo = _readOneComp(component, mixin) + const componentItem = genComp(componentInfo) + const snipItem = genSnip(componentInfo) + + result.data.materials.components.push(componentItem) + snippetItem.children.push(snipItem) + }) + } + }) + result.data.materials.snippets.push(snippetItem) + }) + cb() +} + +function _readOneComp(component, mixin = '') { + if (!mixin) { + shell.echo('---正在读取组件', component.key) + const key = component.key + const camelizeKey = camelize(key) + const cmpName = component.nameCn + const desc = readMdDesc(key) + const jsStr = shell.cat(`${apiPath}/app/${key}/webdoc/${key}.js`).replace('export default', '(') + ')' + const api = eval(jsStr).apis.filter((item) => item.name === key)[0] + const props = api?.props || api?.properties || [] + const events = api?.events || [] + const slots = api?.slots || [] + guid++ + const componentInfo = { id: guid, version: tinyVer, key, camelizeKey, cmpName, desc, props, events, slots } + + return componentInfo + } else { + shell.echo('---正在读取混入的组件', component.key, mixin.key) + const key = mixin.key + const camelizeKey = camelize(key) + const cmpName = mixin.nameCn + const desc = mixin.desc + const jsStr = + shell.cat(`${apiPath}/app/${component.key}/webdoc/${component.key}.js`).replace('export default', '(') + ')' + const api = eval(jsStr).apis.filter((item) => item.name === mixin.key)[0] + const props = api?.props || api?.properties || [] + const events = api?.events || [] + const slots = api?.slots || [] + guid++ + const componentInfo = { id: guid, version: tinyVer, key, camelizeKey, cmpName, desc, props, events, slots } + + return componentInfo + } +} +// 3. 将结果写bundle.json +function write(cb) { + shell.ShellString(JSON.stringify(result, null, ' ')).to('gulp/bundle.json') + cb() +} + +// 4. 清除 +function clean(cb) { + shell.rm('gulp/menus.mjs') + cb() +} + +// 以下辅助方法 + +// 转换:button-group ==> ButtonGroup +const camelize = (str) => { + return str + .replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : '')) + .replace(/^(\w)/g, (_, c) => (c ? c.toUpperCase() : '')) +} +// 从标签中,提取文字。
常用的操作按钮。
==> 常用的操作按钮。 +const readMdDesc = (key) => { + const mdStr = shell.cat(`${apiPath}/app/${key}/webdoc/${key}.cn.md`) + const matched = mdStr.match(/
([\w|\s|\u4e00-\u9fa5]*)<\/div>/i) + return matched ? matched[1] || '' : '' +} +const genComp = ({ id, version, key, camelizeKey, cmpName, desc, props, events, slots }) => { + const item = { + 'id': id, + 'version': version, + 'name': { + 'zh_CN': cmpName + }, + 'component': 'Tiny' + camelizeKey, + 'icon': '', + 'description': desc, + 'doc_url': '', + 'screenshot': '', + 'tags': '', + 'keywords': '', + 'dev_mode': 'proCode', + 'npm': { + 'package': '@opentiny/vue', + 'exportName': camelizeKey, + 'version': version, + 'destructuring': true + }, + 'group': 'component', + 'configure': { + 'loop': true, + 'condition': true, + 'styles': true, + 'isContainer': false, + 'isModal': false, + 'nestingRule': { + 'childWhitelist': '', + 'parentWhitelist': '', + 'descendantBlacklist': '', + 'ancestorWhitelist': '' + }, + 'isNullNode': false, + 'isLayout': false, + 'rootSelector': '', + 'shortcuts': { + 'properties': [] + }, + 'contextMenu': { + 'actions': [], + 'disable': [] + }, + 'framework': 'Vue' + }, + 'schema': { + 'properties': [ + { + 'label': { + 'zh_CN': '基础信息' + }, + 'description': { + 'zh_CN': '基础信息' + }, + 'collapse': { + 'number': 6, + 'text': { + 'zh_CN': '显示更多' + } + }, + 'content': props.map((prop) => genProp(prop)) + } + ], + 'events': { + ...events.map((event) => genEvent(event)).reduce((pre, curr) => ({ ...pre, ...curr }), {}) + }, + 'slots': { + ...slots.map((slot) => genSlot(slot)).reduce((pre, curr) => ({ ...pre, ...curr }), {}) + } + } + } + + return item +} + +const genProp = ({ name, type, defaultValue, desc }) => { + const typeMap = { + string: { + component: 'MetaInput', + props: {} + }, + boolean: { + component: 'MetaSwitch', + props: {} + }, + number: { + component: 'MetaNumberic', + props: {} + }, + object: { + component: 'MetaCodeEditor', + props: { + language: 'json' + } + }, + array: { + component: 'MetaCodeEditor', + props: { + language: 'json' + } + }, + function: { + component: 'MetaCodeEditor', + props: { + language: 'javascript' + } + } + } + const normalizeType = type.trim().toLowerCase() + + return { + 'property': name, + 'type': type, + 'defaultValue': defaultValue == '--' ? '' : defaultValue, + 'label': { + 'text': { + 'zh_CN': desc['zh-CN'] + } + }, + 'cols': 12, + 'rules': [], + 'hidden': false, + 'required': true, + 'readOnly': true, + 'disabled': true, + 'widget': typeMap[normalizeType] + ? typeMap[normalizeType] // + : { 'component': '', 'props': {} }, + 'description': { + 'zh_CN': desc['zh-CN'] + } + } +} +const genEvent = ({ name, type, defaultValue, desc }) => { + return { + ['on' + camelize(name)]: { + 'label': { + 'zh_CN': desc['zh-CN'] + }, + 'description': { + 'zh_CN': desc['zh-CN'] + }, + 'type': 'event', + 'functionInfo': { + 'params': [], + 'returns': {} + }, + 'defaultValue': '' + } + } +} +const genSlot = ({ name, type, defaultValue, desc }) => { + return { + [name]: { + 'label': { + 'zh_CN': desc['zh-CN'] + }, + 'description': { + 'zh_CN': desc['zh-CN'] + } + } + } +} + +const genSnip = ({ id, version, key, camelizeKey, cmpName, desc, props, events, slots }) => { + return { + 'name': { + 'zh_CN': cmpName + }, + 'icon': key, + 'screenshot': '', + 'snippetName': 'Tiny' + camelizeKey, + 'schema': {} + } +} diff --git a/gulpfile.mjs b/gulpfile.mjs index b1213993a..f5bacadd6 100644 --- a/gulpfile.mjs +++ b/gulpfile.mjs @@ -3,5 +3,6 @@ import buildRenderless from './gulp/buildRenderless.mjs' import buildTheme from './gulp/buildTheme.mjs' import themeJson from './gulp/themeJson.mjs' import themeConcat from './gulp/themeConcat.mjs' +import lowcode from './gulp/buildLowcodeBundle.mjs' -export { themeJson, themeConcat, buildVue, buildRenderless, buildTheme } +export { themeJson, themeConcat, buildVue, buildRenderless, buildTheme, lowcode } diff --git a/packages/modules.json b/packages/modules.json index 5e68b5ab1..ffeaf73d9 100644 --- a/packages/modules.json +++ b/packages/modules.json @@ -1557,6 +1557,14 @@ "type": "template", "exclude": false }, + "Mask": { + "path": "vue/src/mask/index.ts", + "type": "component", + "exclude": false, + "mode": [ + "mobile" + ] + }, "Menu": { "path": "vue/src/menu/index.ts", "type": "component", diff --git a/packages/renderless/src/alert/index.ts b/packages/renderless/src/alert/index.ts index 3319ac28e..adbbd3ecd 100644 --- a/packages/renderless/src/alert/index.ts +++ b/packages/renderless/src/alert/index.ts @@ -9,7 +9,50 @@ * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. * */ -import type { IAlertRenderlessParams } from '@/types' +import type { CSSProperties } from 'vue' +import type { IAlertRenderlessParams, ITinyVm } from '@/types' + +export const ALERT_TIMEOUT = 2000 + +export const watchAutoHide = + ({ api, props }: Pick) => + (newVal: boolean) => { + if (props.autoHide && newVal) { + const timer = setTimeout(() => { + api.handleClose() + clearTimeout(timer) + }, ALERT_TIMEOUT) + } + } + +export const computedClass = + ({ props, mode }) => + (): string[] => { + const { type, size, center } = props + if (mode === 'mobile') { + const alertClass = ['tiny-mobile-alert', 'tiny-mobile-alert--' + type, 'tiny-mobile-alert--' + size] + if (center) { + alertClass.push('is-center') + } + + return alertClass + } + + return [] + } + +export const computedStyle = + ({ props, mode }) => + (): CSSProperties | null => { + if (mode === 'mobile') { + const style = { + top: isNaN(props.offset) ? props.offset : `${props.offset}px` + } + return style + } + + return null + } export const handleClose = ({ emit, state }: Pick) => @@ -52,3 +95,23 @@ export const handleHeaderClick = } } } + +const getEl = (node: ITinyVm): HTMLElement => { + return node.$el || node +} + +export const handlerTargetNode = + ({ props, parent, vm, nextTick }) => + () => { + const { target } = props + const { $parent } = parent + nextTick(() => { + const alertParentNode = $parent?.$refs[target] + if (!target || !alertParentNode) { + return + } + + const targetNode = Array.isArray(alertParentNode) ? alertParentNode[0] : alertParentNode + getEl(targetNode).insertBefore(vm.$el, getEl(targetNode).firstChild) + }) + } \ No newline at end of file diff --git a/packages/renderless/src/alert/vue.ts b/packages/renderless/src/alert/vue.ts index 5ef071fee..2581ef48a 100644 --- a/packages/renderless/src/alert/vue.ts +++ b/packages/renderless/src/alert/vue.ts @@ -10,40 +10,63 @@ * */ -import type { - IAlertApi, - IAlertProps, - IAlertState, - ISharedRenderlessParamHooks, - IAlertRenderlessParamUtils -} from '@/types' -import { handleClose, computedGetIcon, computedGetTitle, handleHeaderClick } from './index' +import type{ IAlertApi, IAlertProps, IAlertState, ISharedRenderlessParamHooks, IAlertRenderlessParamUtils } from '@/types' +import { + computedGetIcon, + computedGetTitle, + computedStyle, + computedClass, + handleClose, + handleHeaderClick, + watchAutoHide, + handlerTargetNode +} from './index' export const api = ['handleClose', 'state', 'handleHeaderClick'] -export const renderless = ( - props: IAlertProps, - { computed, reactive }: ISharedRenderlessParamHooks, - { t, emit, constants, vm, designConfig }: IAlertRenderlessParamUtils -): IAlertApi => { - const state: IAlertState = reactive({ +const initState = ({ api, computed, constants, reactive }): IAlertState => { + return reactive({ show: true, - getIcon: computed(() => api.computedGetIcon()), - getTitle: computed(() => api.computedGetTitle()), contentVisible: false, contentDescribeHeight: 0, contentDefaultHeight: 0, contentMaxHeight: constants.CONTENT_MAXHEUGHT, - scrollStatus: false + scrollStatus: false, + getIcon: computed(() => api.computedGetIcon()), + getTitle: computed(() => api.computedGetTitle()), + alertClass: computed(() => api.computedClass()), + alertStyle: computed(() => api.computedStyle()) }) +} - const api: IAlertApi = { +const initApi = ({ api, state, constants, props, designConfig, t, emit, vm, parent, nextTick, mode }): void => { + Object.assign(api, { state, computedGetIcon: computedGetIcon({ constants, props, designConfig }), computedGetTitle: computedGetTitle({ constants, props, t }), + computedClass: computedClass({ props, mode }), + computedStyle: computedStyle({ props, mode }), handleClose: handleClose({ emit, state }), - handleHeaderClick: handleHeaderClick({ state, props, vm }) - } + handleHeaderClick: handleHeaderClick({ state, props, vm }), + watchAutoHide: watchAutoHide({ api, props }), + handlerTargetNode: handlerTargetNode({ props, parent, vm, nextTick }) + }) +} + +const initWatcher = ({ watch, props, api }) => { + watch(() => props.autoHide, api.watchAutoHide, { immediate: true }) + watch(() => props.target, api.handlerTargetNode, { immediate: true }) +} + +export const renderless = ( + props: IAlertProps, + { computed, reactive, watch }: ISharedRenderlessParamHooks, + { t, emit, constants, vm, designConfig, parent, nextTick, mode }: IAlertRenderlessParamUtils +): IAlertApi => { + const api = {} as IAlertApi + const state: IAlertState = initState({ api, computed, constants, reactive }) + initApi({ api, state, constants, props, designConfig, t, emit, vm, parent, nextTick, mode }) + initWatcher({ watch, props, api }) return api } diff --git a/packages/renderless/src/checkbox-group/vue.ts b/packages/renderless/src/checkbox-group/vue.ts index 432c0fa5e..7284c8f54 100644 --- a/packages/renderless/src/checkbox-group/vue.ts +++ b/packages/renderless/src/checkbox-group/vue.ts @@ -38,5 +38,7 @@ export const renderless = (props, { computed, reactive, watch, provide }, { disp provide('vertical', props.vertical) + provide('iconPosition', props.iconPosition) + return api } diff --git a/packages/renderless/src/checkbox/vue.ts b/packages/renderless/src/checkbox/vue.ts index 1fe3980aa..1ad134256 100644 --- a/packages/renderless/src/checkbox/vue.ts +++ b/packages/renderless/src/checkbox/vue.ts @@ -46,6 +46,7 @@ const initState = ({ reactive, computed, parent, api, inject, props }) => { const state: ICheckboxState = reactive({ size: props.size || inject('size', null), vertical: inject('vertical', null), + iconPosition: props.iconPosition || inject('iconPosition', 'center'), focus: false, selfModel: false, showLabel: false, diff --git a/packages/renderless/src/input/index.ts b/packages/renderless/src/input/index.ts index 95cf3293c..ffe621acd 100644 --- a/packages/renderless/src/input/index.ts +++ b/packages/renderless/src/input/index.ts @@ -62,6 +62,14 @@ export const showBox = (state: IInputState) => (): void => { state.boxVisibility = true } +export const inputStyle = + ({ props }) => + () => { + return { + textAlign: props.textAlign + } + } + export const calculateNodeStyling = () => ( @@ -387,7 +395,7 @@ export const watchFormSelect = const filterData = props.selectMenu.length && props.selectMenu.filter((item) => item.id === value).shift() - state.checkedLable = filterData ? filterData.label : '' + state.checkedLabel = filterData ? filterData.label : '' } } diff --git a/packages/renderless/src/input/vue.ts b/packages/renderless/src/input/vue.ts index 5758002ce..418d58982 100644 --- a/packages/renderless/src/input/vue.ts +++ b/packages/renderless/src/input/vue.ts @@ -47,7 +47,8 @@ import { handleEnterDisplayOnlyContent, hiddenPassword, dispatchDisplayedValue, - getDisplayedValue + getDisplayedValue, + inputStyle } from './index' import useStorageBox from '../tall-storage/vue-storage-box' @@ -81,7 +82,8 @@ export const api = [ 'isMemoryStorage', 'hasSelection', 'handleEnterDisplayOnlyContent', - 'hiddenPassword' + 'hiddenPassword', + 'inputStyle' ] const initState = ({ @@ -101,7 +103,7 @@ const initState = ({ passwordVisible: false, boxVisibility: false, textareaCalcStyle: {}, - checkedLable: '', + checkedLabel: '', sheetvalue: props.modelValue, inputSize: computed(() => props.size || state.formItemSize), showClear: computed( @@ -123,9 +125,11 @@ const initState = ({ props.disabled || (parent.tinyForm || {}).disabled || state.isDisplayOnly || (parent.tinyForm || {}).displayOnly ), validateState: computed(() => (parent.formItem ? parent.formItem.validateState : '')), + inputStyle: computed(() => api.inputStyle()), textareaStyle: computed(() => ({ ...state.textareaCalcStyle, - resize: props.resize + resize: props.resize, + textAlign: props.textAlign })), needStatusIcon: computed(() => (parent.tinyForm ? parent.tinyForm.statusIcon : false)), showPwdVisible: computed( @@ -186,7 +190,8 @@ const initApi = ({ handleCompositionStart: handleCompositionStart(state), handleCompositionUpdate: handleCompositionUpdate(state), dispatchDisplayedValue: dispatchDisplayedValue({ state, props, dispatch, api }), - getDisplayedValue: getDisplayedValue({ state, props }) + getDisplayedValue: getDisplayedValue({ state, props }), + inputStyle: inputStyle({ props }) }) } diff --git a/packages/renderless/src/mask/index.ts b/packages/renderless/src/mask/index.ts new file mode 100644 index 000000000..0e83a9005 --- /dev/null +++ b/packages/renderless/src/mask/index.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2022 - present TinyVue Authors. + * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. + * + * Use of this source code is governed by an MIT-style license. + * + * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, + * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR + * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + * + */ + +export const handleTouch = + ({ props, emit }) => + (event: TouchEvent) => { + if (props.cancelTouch) { + event.preventDefault() + event.stopPropagation() + } else { + emit('update:visible', false) + } + + emit('click', props.visible) + } diff --git a/packages/renderless/src/mask/vue.ts b/packages/renderless/src/mask/vue.ts new file mode 100644 index 000000000..111fb4756 --- /dev/null +++ b/packages/renderless/src/mask/vue.ts @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2022 - present TinyVue Authors. + * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. + * + * Use of this source code is governed by an MIT-style license. + * + * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, + * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR + * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + * + */ + +import { handleTouch } from './index' + +export const api = ['state', 'handleTouch'] + +export const renderless = (props, { reactive, computed }, { emit }) => { + const api = {} + const state = reactive({ + calcStyle: computed(() => ({ zIndex: props.zIndex })) + }) + + Object.assign(api, { + state, + handleTouch: handleTouch({ props, emit }) + }) + + return api +} diff --git a/packages/renderless/types/alert.type.ts b/packages/renderless/types/alert.type.ts index ef18975b4..f7090e436 100644 --- a/packages/renderless/types/alert.type.ts +++ b/packages/renderless/types/alert.type.ts @@ -1,5 +1,5 @@ -import type { ExtractPropTypes } from 'vue' -import type { alertProps, $constants } from '@/alert/src' +import type { ExtractPropTypes, CSSProperties } from 'vue' +import { alertProps, $constants } from '@/alert/src' import type { ISharedRenderlessFunctionParams, ISharedRenderlessParamUtils } from './shared.type' export interface IAlertState { @@ -18,6 +18,7 @@ export type IAlertProps = ExtractPropTypes export type IAlertConstants = typeof $constants export type IAlertRenderlessParams = ISharedRenderlessFunctionParams & { + api: IAlertApi state: IAlertState props: IAlertProps } @@ -28,6 +29,8 @@ export interface IAlertApi { computedGetTitle: () => string handleClose: () => void handleHeaderClick: () => void + watchAutoHide: (value: boolean) => void + computedStyle: () => CSSProperties } export type IAlertRenderlessParamUtils = ISharedRenderlessParamUtils diff --git a/packages/renderless/types/input.type.ts b/packages/renderless/types/input.type.ts index e317d11bb..e54f8ae0b 100644 --- a/packages/renderless/types/input.type.ts +++ b/packages/renderless/types/input.type.ts @@ -15,7 +15,8 @@ import type { resizeTextarea, updateIconOffset, hiddenPassword, - dispatchDisplayedValue + dispatchDisplayedValue, + inputStyle } from '../src/input' export interface IInputState { @@ -26,7 +27,8 @@ export interface IInputState { passwordVisible: boolean boxVisibility: boolean textareaCalcStyle: object - checkedLable: string + checkedLabel: string + width: string sheetvalue: string | number | undefined inputSize: ComputedRef showClear: ComputedRef @@ -71,6 +73,7 @@ export interface IInputApi extends Pick calcIconOffset: ReturnType focus: ReturnType getDisplayedValue: ReturnType + inputStyle: ReturnType } export type IInputRenderlessParams = ISharedRenderlessFunctionParams & { diff --git a/packages/theme-mobile/src/alert/index.less b/packages/theme-mobile/src/alert/index.less index df60b618a..eeb0bd3d3 100644 --- a/packages/theme-mobile/src/alert/index.less +++ b/packages/theme-mobile/src/alert/index.less @@ -12,6 +12,7 @@ @import '../mixins/alert.less'; @import '../custom.less'; +@import '../base/index.less'; @import './vars.less'; @alert-prefix-cls: ~'@{css-prefix}mobile-alert'; @@ -19,16 +20,20 @@ .@{alert-prefix-cls} { clear: both; position: relative; + font-size: 0; + + .@{alert-prefix-cls}__content { + color: var(--ti-mobile-alert-content-text-color); + padding-left: 8px; + } &--normal { - height: var(--ti-mobile-alert-nomal-height, 30px); - line-height: var(--ti-mobile-alert-nomal-height, 30px); - padding: 0px 16px; + height: var(--ti-mobile-alert-height, 36px); + line-height: 1.5; + padding: var(--ti-mobile-alert-padding-top) var(--ti-mobile-alert-padding-right) var(--ti-mobile-alert-padding-bottom) var(--ti-mobile-alert-padding-left); .@{alert-prefix-cls}__content { - padding-left: 8px; font-size: var(--ti-mobile-alert-content-font-size, 12px); - color: var(--ti-mobile-alert-content-color, #999); display: inline-block; vertical-align: middle; @@ -39,50 +44,18 @@ } &&--large { - padding: 10px; - border-radius: var(--ti-mobile-alert-large-border-radius, 4px); - background: var(--ti-mobile-alert-large-bgcolor, #039be5); - - &::before { - content: ''; - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--ti-mobile-alert-large-bgcolor, #039be5); - display: block; - position: absolute; - top: -24px; - left: 24px; - } - - &::after { - content: ''; - width: 3px; - height: 24px; - background: var(--ti-mobile-alert-large-bgcolor, #039be5); - display: block; - position: absolute; - top: -24px; - left: 24px; - transform: translateX(2px); - } + padding: var(--ti-mobile-alert-large-padding-vertical) var(--ti-mobile-alert-padding-right) var(--ti-mobile-alert-large-padding-vertical) var(--ti-mobile-alert-padding-left); .@{alert-prefix-cls}__content { font-size: 15px; - color: var(--ti-mobile-alert-large-color, #fff); - padding-left: 8px; display: inline-block; vertical-align: middle; - &.is-hideicon { + &.is-hideicoalert-variantn { padding: 0; } } - .@{alert-prefix-cls}__icon { - fill: var(--ti-mobile-alert-large-color, #fff); - } - .is-custom { font-size: 15px; color: #fff; @@ -91,33 +64,33 @@ &--success { .alert-variant( - var(--ti-mobile-alert-success-icon-color, #52c41a) ; - var(--ti-mobile-alert-success-link-color, #78d53a) ; - var(--ti-mobile-alert-success-bg-color, #f6ffed) + var(--ti-mobile-alert-success-icon-color) ; + var(--ti-mobile-alert-success-link-color) ; + var(--ti-mobile-alert-success-bg-color) ); } &--info { .alert-variant( - var(--ti-mobile-alert-info-icon-color, #1890ff) ; - var(--ti-mobile-alert-info-link-color, #5ec2ff) ; - var(--ti-mobile-alert-info-bg-color, #e6f7ff) + var(--ti-mobile-alert-info-icon-color) ; + var(--ti-mobile-alert-info-link-color) ; + var(--ti-mobile-alert-info-bg-color) ); } &--warning { .alert-variant( - var(--ti-mobile-alert-warning-icon-color, #faad14) ; - var(--ti-mobile-alert-warning-link-color, #ffc833) ; - var(--ti-mobile-alert-warning-bg-color, #fef6e5) + var(--ti-mobile-alert-warning-icon-color) ; + var(--ti-mobile-alert-warning-link-color) ; + var(--ti-mobile-alert-warning-bg-color) ); } &--error { .alert-variant( - var(--ti-mobile-alert-error-icon-color, #f5222d) ; - var(--ti-mobile-alert-error-link-color, #ff4642) ; - var(--ti-mobile-alert-error-bg-color, #fff1f0) + var(--ti-mobile-alert-error-icon-color) ; + var(--ti-mobile-alert-error-link-color) ; + var(--ti-mobile-alert-error-bg-color) ); } diff --git a/packages/theme-mobile/src/alert/vars.less b/packages/theme-mobile/src/alert/vars.less index de801c6c6..1e47e9816 100644 --- a/packages/theme-mobile/src/alert/vars.less +++ b/packages/theme-mobile/src/alert/vars.less @@ -1,26 +1,48 @@ :root { - --ti-mobile-alert-warning-icon-color: var(--ti-mobile-base-color-warning-normal, #faad14); - --ti-mobile-alert-warning-link-color: #ffc833; - --ti-mobile-alert-warning-bg-color: #fef6e5; + // 警告类型提示图标色 + --ti-mobile-alert-warning-icon-color: var(--ti-mobile-color-warning, #ff8800); + // 警告类型提示链接字体色 + --ti-mobile-alert-warning-link-color: var(--ti-mobile-color-text-link, #1476ff); + // 警告类型提示背景色 + --ti-mobile-alert-warning-bg-color: var(--ti-mobile-color-warning-subtle, #ffebd1); - --ti-mobile-alert-error-icon-color: var(--ti-mobile-base-color-danger-normal, #f5222d); - --ti-mobile-alert-error-link-color: #ff4642; - --ti-mobile-alert-error-bg-color: #fff1f0; + // 异常类型提示图标色 + --ti-mobile-alert-error-icon-color: var(--ti-mobile-color-error, #f23030); + // 异常类型提示链接字体色 + --ti-mobile-alert-error-link-color: var(--ti-mobile-color-text-link, #1476ff); + // 异常类型提示背景色 + --ti-mobile-alert-error-bg-color: var(--ti-mobile-color-error-subtle, #fce3e1); - --ti-mobile-alert-success-icon-color: var(--ti-mobile-base-color-success-normal, #52c41a); - --ti-mobile-alert-success-link-color: #78d53a; - --ti-mobile-alert-success-bg-color: #f6ffed; + // 成功类型提示图标色 + --ti-mobile-alert-success-icon-color: var(--ti-mobile-color-success, #5cb300); + // 成功类型提示链接字体色 + --ti-mobile-alert-success-link-color: var(--ti-mobile-color-text-link, #1476ff); + // 成功类型提示背景色 + --ti-mobile-alert-success-bg-color: var(--ti-mobile-color-success-subtle, #e6f2d5); - --ti-mobile-alert-info-icon-color: var(--ti-mobile-base-color-primary-normal, #1890ff); - --ti-mobile-alert-info-link-color: #5ec2ff; - --ti-mobile-alert-info-bg-color: var(--ti-mobile-base-color-hover-background, #e6f7ff); + // 基础类型提示图标色 + --ti-mobile-alert-info-icon-color: var(--ti-mobile-color-brand-2, #1476ff); + // 基础类型提示链接字体色 + --ti-mobile-alert-info-link-color: var(--ti-mobile-color-text-link, #1476ff); + // 基础类型提示背景色 + --ti-mobile-alert-info-bg-color: var(--ti-mobile-color-info-subtle, #deecff); - --ti-mobile-alert-nomal-height: var(--ti-mobile-base-size-height-minor, 30px); - --ti-mobile-alert-icon-size: 16px; - --ti-mobile-alert-content-font-size: var(--ti-mobile-base-font-size-base, 12px); - --ti-mobile-alert-content-color: var(--ti-mobile-base-color-placeholder, #999); - - --ti-mobile-alert-large-color: var(--ti-mobile-base-color-light, #fff); - --ti-mobile-alert-large-bgcolor: #039be5; - --ti-mobile-alert-large-border-radius: 4px; + // 默认高度 + --ti-mobile-alert-height: var(--ti-mobile-color-info-subtle, 36px); + // 默认左内边距 + --ti-mobile-alert-padding-left: var(--ti-mobile-space-6x, 24px); + // 默认右内边距 + --ti-mobile-alert-padding-right: var(--ti-mobile-space-6x, 24px); + // 默认上内边距 + --ti-mobile-alert-padding-top: 9px; + // 默认下内边距 + --ti-mobile-alert-padding-bottom: 9px; + // 图标大小 + --ti-mobile-alert-icon-size: var(--ti-mobile-font-size-l, 16px); + // 字号 + --ti-mobile-alert-content-font-size: var(--ti-mobile-font-size-s, 12px); + // 字体色 + --ti-mobile-alert-content-text-color: var(--ti-mobile-color-text-primary, #191919); + // large提示垂直内边距 + --ti-mobile-alert-large-padding-vertical: 10px; } diff --git a/packages/theme-mobile/src/checkbox/index.less b/packages/theme-mobile/src/checkbox/index.less index 6c2270a81..93ff6783b 100644 --- a/packages/theme-mobile/src/checkbox/index.less +++ b/packages/theme-mobile/src/checkbox/index.less @@ -10,6 +10,7 @@ * */ +@import '../base/basic-var.less'; @import '../mixins/common.less'; @import '../mixins/checkbox.less'; @import '../custom.less'; @@ -21,7 +22,7 @@ position: relative; display: inline-block; white-space: nowrap; - margin-right: var(--ti-mobile-checkbox-margin-right, 30px); + margin-right: var(--ti-mobile-checkbox-margin-right); cursor: pointer; .user-select(none); @@ -36,16 +37,16 @@ vertical-align: middle; outline: 0; cursor: pointer; - width: var(--ti-mobile-checkbox-size, 20px); - height: var(--ti-mobile-checkbox-size, 20px); - line-height: var(--ti-mobile-checkbox-size, 20px); + width: var(--ti-mobile-checkbox-width); + height: var(--ti-mobile-checkbox-height); + line-height: var(--ti-mobile-checkbox-line-height); } &__inner { display: inline-block; position: relative; - background-color: var(--ti-mobile-checkbox-bg-color, #ffffff); - border: 1px solid var(--ti-mobile-checkbox-border-color, #dbdbdb); + background-color: var(--ti-mobile-checkbox-bg-color); + border: 1.5px solid var(--ti-mobile-checkbox-border-color); border-radius: var(--ti-mobile-checkbox-border-radius, 3px); box-sizing: border-box; width: 100%; @@ -56,7 +57,7 @@ background-color 0.25s cubic-bezier(0.71, -0.46, 0.29, 1.46); &:hover { - border-color: var(--ti-mobile-checkbox-checked-border-color, #4a79fe); + border-color: var(--ti-mobile-checkbox-checked-border-color); } &::after { @@ -64,11 +65,11 @@ content: ''; border-width: 2px; border-style: solid; - border-color: var(--ti-mobile-checkbox-selected-border-color, #ffffff); + border-color: var(--ti-mobile-checkbox-selected-border-color); border-left: 0; border-top: 0; - height: 8px; - width: 5px; + height: 11px; + width: 7px; position: absolute; left: 50%; top: 42%; @@ -91,19 +92,21 @@ &__label { display: inline-block; - padding-left: 16px; + padding-left: var(--ti-mobile-checkbox-label-padding-left); line-height: 24px; - color: var(--ti-mobile-checkbox-color, #595959); + color: var(--ti-mobile-checkbox-text-color); font-size: var(--ti-mobile-checkbox-font-size, 16px); vertical-align: middle; + word-break: break-all; + white-space: pre-wrap; } &.is-checked &__inner { - background-color: var(--ti-mobile-checkbox-checked-bg-color, #4a79fe); + background-color: var(--ti-mobile-checkbox-checked-bg-color); } &.is-checked &__inner::after { - border-color: var(--ti-mobile-checkbox-selected-border-color, #ffffff); + border-color: var(--ti-mobile-checkbox-selected-border-color); transform: translate(-50%, -50%) rotate(45deg) scaleY(1); opacity: 1; } @@ -114,7 +117,7 @@ position: absolute; display: block; border-radius: 2px; - background-color: var(--ti-mobile-checkbox-indeterminate-bg-color, #ffffff); + background-color: var(--ti-mobile-checkbox-indeterminate-bg-color); height: 10px; width: 10px; left: 0; @@ -131,15 +134,20 @@ &.is-checked &__inner, &.is-indeterminate &__inner { - background-color: var(--ti-mobile-checkbox-checked-bg-color, #4a79fe); - border-color: var(--ti-mobile-checkbox-checked-border-color, #4a79fe); + background-color: var(--ti-mobile-checkbox-checked-bg-color); + border-color: var(--ti-mobile-checkbox-checked-border-color); } &.is-disabled { .checkbox-input-disabled( - var(--ti-mobile-checkbox-disabled-color, #dbdbdb), - var(--ti-mobile-checkbox-disabled-bg-color, #f5f5f5), - var(--ti-mobile-checkbox-disabled-border-color, #dbdbdb) + var(--ti-mobile-checkbox-disabled-text-color), + var(--ti-mobile-checkbox-disabled-bg-color), + var(--ti-mobile-checkbox-checked-disabled-bg-color), + var(--ti-mobile-checkbox-disabled-border-color) ); } + + &.icon-position-top &__input { + vertical-align: top; + } } diff --git a/packages/theme-mobile/src/checkbox/vars.less b/packages/theme-mobile/src/checkbox/vars.less index 832073e0f..4ba017d72 100644 --- a/packages/theme-mobile/src/checkbox/vars.less +++ b/packages/theme-mobile/src/checkbox/vars.less @@ -1,17 +1,38 @@ :root { - --ti-mobile-checkbox-margin-right: 30px; - --ti-mobile-checkbox-bg-color: var(--ti-mobile-base-color-bg-3, #ffffff); - --ti-mobile-checkbox-border-radius: var(--ti-mobile-base-radius-large, 3px); - --ti-mobile-checkbox-border-color: var(--ti-mobile-base-color-common-8, #dbdbdb); - --ti-mobile-checkbox-size: 20px; - --ti-mobile-checkbox-checked-color: var(--ti-mobile-base-color-brand-1, #4a79fe); - --ti-mobile-checkbox-checked-border-color: var(--ti-mobile-base-color-brand-1, #4a79fe); - --ti-mobile-checkbox-checked-bg-color: var(--ti-mobile-base-color-brand-1, #4a79fe); - --ti-mobile-checkbox-selected-border-color: var(--ti-mobile-base-color-brand-6, #ffffff); - --ti-mobile-checkbox-indeterminate-bg-color: var(--ti-mobile-base-color-brand-6, #ffffff); - --ti-mobile-checkbox-color: var(--ti-mobile-base-color-common-5, #595959); - --ti-mobile-checkbox-font-size: 16px; - --ti-mobile-checkbox-disabled-color: var(--ti-mobile-base-color-common-8, #dbdbdb); - --ti-mobile-checkbox-disabled-bg-color: var(--ti-mobile-base-color-bg-1, #f5f5f5); - --ti-mobile-checkbox-disabled-border-color: var(--ti-mobile-base-color-common-8, #dbdbdb); + // 复选框图标宽度 + --ti-mobile-checkbox-width: var(--ti-mobile-space-6x, 24px); + // 复选框图标高度 + --ti-mobile-checkbox-height: var(--ti-mobile-checkbox-width, 24px); + // 复选框文本行高 + --ti-mobile-checkbox-line-height: 22px; + // 复选框右边距 + --ti-mobile-checkbox-margin-right: var(--ti-mobile-space-2x, 8px); + // 复选框图标与文本间距 + --ti-mobile-checkbox-label-padding-left: var(--ti-mobile-space-2x, 8px); + // 复选框图标背景色 + --ti-mobile-checkbox-bg-color: var(--ti-mobile-color-bg-container-1, #ffffff); + // 复选框图标圆角 + --ti-mobile-checkbox-border-radius: var(--ti-mobile-border-radius-s, 4px); + // 复选框图标边框色 + --ti-mobile-checkbox-border-color: var(--ti-mobile-color-border-default, #c2c2c2); + // 复选框选中图标边框色 + --ti-mobile-checkbox-checked-border-color: var(--ti-mobile-color-icon-link, #1476ff); + // 复选框选中图标背景色 + --ti-mobile-checkbox-checked-bg-color: var(--ti-mobile-color-icon-link, #1476ff); + // 复选框选中对勾图标色 + --ti-mobile-checkbox-selected-border-color: var(--ti-mobile-color-icon-inverse, #ffffff); + // 复选框半选背景色 + --ti-mobile-checkbox-indeterminate-bg-color: var(--ti-mobile-color-icon-inverse, #ffffff); + // 复选框文本色 + --ti-mobile-checkbox-text-color: var(--ti-mobile-color-text-primary, #191919); + // 复选框字号 + --ti-mobile-checkbox-font-size: var(--ti-mobile-font-size-m, 14px); + // 复选框禁用文本色 + --ti-mobile-checkbox-disabled-text-color: var(--ti-mobile-color-text-disabled, #c2c2c2); + // 复选框禁用图标背景色 + --ti-mobile-checkbox-disabled-bg-color: var(--ti-mobile-color-bg-container-2, #fafafa); + // 复选框勾选且禁用背景色 + --ti-mobile-checkbox-checked-disabled-bg-color: var(--ti-mobile-color-bg-control-disabled-1, #dbdbdb); + // 复选框禁用图标边框色 + --ti-mobile-checkbox-disabled-border-color: var(--ti-mobile-color-border-disabled, #dbdbdb); } diff --git a/packages/theme-mobile/src/input/index.less b/packages/theme-mobile/src/input/index.less index d0ad70586..25269c5a5 100644 --- a/packages/theme-mobile/src/input/index.less +++ b/packages/theme-mobile/src/input/index.less @@ -10,6 +10,7 @@ * */ +@import '../base/basic-var.less'; @import '../mixins/input.less'; @import '../mixins/common.less'; @import '../custom.less'; @@ -18,36 +19,26 @@ @textarea-prefix-cls: ~'@{css-prefix}mobile-textarea'; @input-prefix-cls: ~'@{css-prefix}mobile-input'; @input-group-prefix-cls: ~'@{css-prefix}mobile-input-group'; -@input-form-prefix-cls: ~'@{css-prefix}mobile-input-form'; .@{textarea-prefix-cls} { position: relative; display: inline-block; width: 100%; - background-color: var(--ti-mobile-input-bg-color, #fff); - padding: 12px 16px; - - &__count { - color: var(--ti-mobile-textarea-count-text-color, #999); - font-size: var(--ti-mobile-textarea-count-font-size, 12px); - margin-top: 4px; - text-align: right; - display: block; - } + line-height: var(--ti-mobile-input-line-height); &__inner { - width: 100%; - height: var(--ti-mobile-textarea-inner-height, 96px); + padding: var(--ti-mobile-textarea-padding-vertical) var(--ti-mobile-textarea-padding-horizontal); font-size: var(--ti-mobile-textarea-inner-font-size, 16px); - color: var(--ti-mobile-textarea-inner-text-color, #666); - padding: 0; - border: 0; + font-family: var(--ti-mobile-input-font-family); + color: var(--ti-mobile-textarea-inner-text-color); + border: var(--ti-mobile-input-border-width) solid var(--ti-mobile-input-border-color); + border-radius: var(--ti-mobile-input-radius); display: block; - resize: none; + resize: vertical; box-sizing: border-box; transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); .placeholder( - @color: var(--ti-mobile-textarea-inner-placeholder-color, #ccc) + @color: var(--ti-mobile-textarea-inner-placeholder-color) ); &:hover, @@ -56,42 +47,44 @@ } } - &__title { - font-size: var(--ti-mobile-textarea-title-font-size, 16px); - color: var(--ti-mobile-textarea-title-text-color, #333); - margin-bottom: 8px; + &__count { + position: absolute; + right: var(--ti-mobile-textarea-padding-horizontal); + bottom: var(--ti-mobile-textarea-padding-vertical); + color: var(--ti-mobile-input-count-text-color); + font-size: var(--ti-mobile-input-count-font-size); } - &__content { - margin-bottom: 8px; + &.is-focus { + border-color: var(--ti-mobile-input-border-color-active); } - &.is-showlimit { - padding-bottom: 8px; + &.is-showlimit &__inner { + padding-bottom: calc(var(--ti-mobile-input-count-font-size) * 1.5 + var(--ti-mobile-textarea-padding-vertical)); } - &.is-showtitle &__inner { - height: var(--ti-mobile-textarea-showlimit-inner-height, 70px); + &.is-disabled { + .@{textarea-prefix-cls}__inner { + border-color: var(--ti-mobile-border-color-disabled); + background-color: var(--ti-mobile-input-bg-color-disabled); + color: var(--ti-mobile-input-text-color-disabled); + cursor: not-allowed; + .placeholder(@color: var(--ti-mobile-input-text-color-disabled)); + } } - &.is-showcontent &__inner { - height: var(--ti-mobile-textarea-showcontent-inner-height, 66px); - } - - &.is-disabled &__inner { - background-color: var(--ti-mobile-input-bg-color-disabled, #f5f5f5); - color: var(--ti-mobile-input-color-disabled, #999); - cursor: not-allowed; - } - - &.is-exceed &__count { - color: var(--ti-mobile-input-exceed-text-color, #f5222d); + &.is-exceed { + border-color: var(); + &__count { + color: var(--ti-mobile-input-exceed-text-color); + } } } .@{input-prefix-cls} { position: relative; width: 100%; + line-height: var(--ti-mobile-input-line-height); &::-webkit-scrollbar { z-index: 11; @@ -122,76 +115,78 @@ } &.is-exceed &__suffix &__count { - color: var(--ti-mobile-input-exceed-text-color, #f5222d); + color: var(--ti-mobile-input-exceed-text-color); } - &.is-disabled &__inner { - cursor: not-allowed; - color: var(--ti-mobile-input-color-disabled, #999); - background: var(--ti-mobile-input-bg-color-disabled, #f5f5f5); - } + &.is-disabled { + .@{input-prefix-cls}__wrapper { + border-color: var(--ti-mobile-border-color-disabled); + background: var(--ti-mobile-input-bg-color-disabled); + } - &.is-disabled &__icon { - cursor: not-allowed; + .@{input-prefix-cls}__inner { + cursor: not-allowed; + color: var(--ti-mobile-input-text-color-disabled); + .placeholder(@color: var(--ti-mobile-input-text-color-disabled)); + } - &.@{css-prefix}svg { - &, - &:hover { - fill: var(--ti-mobile-input-color-disabled, #999); + .@{input-prefix-cls}__icon { + cursor: not-allowed; + + &.@{css-prefix}svg { + &, + &:hover { + fill: var(--ti-mobile-input-icon-color-disabled); + } + } + } + + .@{input-prefix-cls}__prefix, + .@{input-prefix-cls}__suffix { + .@{css-prefix}svg { + &, + &:hover { + fill: var(--ti-mobile-input-icon-color-disabled); + } } } } - &.is-disabled &__prefix, - &.is-disabled &__suffix { - .@{css-prefix}svg { - &, - &:hover { - fill: var(--ti-mobile-input-color-disabled, #999); - } - } + &.@{input-group-prefix-cls}-prepend &__wrapper { + padding-left: 0; } - & &__clear { - font-size: var(--ti-mobile-input-icon-font-size, 16px); - cursor: pointer; - transition: color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); - - &, - &:hover { - fill: var(--ti-mobile-input-color-disabled, #999); - } + &.@{input-group-prefix-cls}-append &__wrapper { + padding-right: 0; } - & &__count { - height: 100%; - display: inline-flex; + &__wrapper { + display: flex; align-items: center; - color: #909399; - font-size: 12px; - - .@{input-prefix-cls}__count-inner { - background: var(--ti-mobile-input-bg-color, #fff); - line-height: initial; - display: inline-block; - } + height: var(--ti-mobile-input-height); + overflow: hidden; + border: var(--ti-mobile-input-border-width) solid var(--ti-mobile-input-border-color); + border-radius: var(--ti-mobile-input-radius); + background: var(--ti-mobile-input-bg-color); } &__inner { + flex: 1; + order: 3; width: 100%; - height: 48px; - line-height: 48px; - font-size: 16px; - color: #666; - background: var(--ti-mobile-input-bg-color, #fff); - padding: 0 16px; + height: 100%; + padding: 0 var(--ti-mobile-input-padding-horizontal); + font-size: var(--ti-mobile-input-font-size); + font-family: var(--ti-mobile-input-font-family); + color: var(--ti-mobile-input-text-color); border: 0; outline: 0; display: inline-block; box-sizing: border-box; -webkit-appearance: none; + appearance: none; transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); - .placeholder(@color: #ccc); + .placeholder(@color: var(--ti-mobile-input-placeholder-text-color)); &:hover, &:focus, @@ -206,15 +201,54 @@ } } + & &__clear { + font-size: var(--ti-mobile-input-icon-font-size, 16px); + cursor: pointer; + transition: color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); + + &, + &:hover { + fill: var(--ti-mobile-input-icon-color); + } + } + + & &__count { + height: 100%; + display: inline-flex; + color: var(--ti-mobile-input-count-text-color); + font-size: var(--ti-mobile-input-count-font-size); + + .@{input-prefix-cls}__count-inner { + background: var(--ti-mobile-input-bg-color, #fff); + line-height: initial; + display: inline-block; + } + } + &__prefix, &__suffix { - position: absolute; - top: 50%; - transform: translateY(-50%); + display: flex; + align-items: center; + justify-content: center; transition: all 0.3s; text-align: center; - color: #d9d9d9; - line-height: calc(var(--ti-mobile-input-line-height, 30px) - 2px); + } + + &__prefix { + padding-left: var(--ti-mobile-input-padding-horizontal); + order: 2; + transition: all 0.3s; + font-size: var(--ti-mobile-input-icon-font-size); + } + + &__suffix { + order: 4; + padding-right: var(--ti-mobile-input-padding-horizontal); + transition: all 0.3s; + + &:hover { + cursor: pointer; + } } &.is-active &__inner, @@ -222,36 +256,15 @@ outline: 0; } - &__suffix { - right: 16px; - transition: all 0.3s; - pointer-events: none; - - &:hover { - cursor: pointer; - } - } - - &__suffix-inner { - pointer-events: all; - } - - &__prefix { - left: 8px; - transition: all 0.3s; - font-size: var(--ti-mobile-input-icon-font-size, 16px); - } - &__icon { - height: 100%; line-height: var(--ti-mobile-input-line-height, 30px); text-align: center; transition: all 0.3s; - font-size: var(--ti-mobile-input-icon-font-size, 16px); + font-size: var(--ti-mobile-input-icon-font-size); &, &:hover { - fill: var(--ti-mobile-input-color-disabled, #999); + fill: var(--ti-mobile-input-icon-color); } &:after { @@ -265,7 +278,7 @@ .svg-operationfaild { &, &:hover { - fill: var(--ti-mobile-input-icon-close-color, #bfbfbf); + fill: var(--ti-mobile-input-icon-color); } } } @@ -274,15 +287,6 @@ pointer-events: none; } - &-suffix &__inner { - padding-right: 48px; - } - - &-prefix &__inner { - padding-left: 30px; - padding-right: 28px; - } - &-medium { .input-size(var(--ti-mobile-input-medium-height, 42px)); } @@ -296,6 +300,10 @@ } } +.@{input-prefix-cls}__title { + margin-bottom: var(--ti-mobile-input-title-margin-bottom); +} + .@{input-group-prefix-cls} { line-height: normal; display: inline-table; @@ -310,12 +318,9 @@ &__append, &__prepend { - background-color: #f5f5f5; - color: #999; - padding: 0 20px; - width: 1px; - vertical-align: middle; - display: table-cell; + display: flex; + align-items: center; + height: 100%; position: relative; white-space: nowrap; @@ -341,39 +346,47 @@ color: inherit; } } -} -.@{input-form-prefix-cls} { - height: 48px; - line-height: 48px; - position: relative; - - &__select { - display: flex; - justify-content: flex-end; - align-items: center; - width: 100%; - height: 100%; - background: var(--ti-mobile-input-bg-color, #fff); + &__prepend { + order: 1; + padding: 0 var(--ti-mobile-input-prepend-padding-horizontal); + color: var(--ti-mobile-input-prepend-text-color); + background-color: var(--ti-mobile-input-prepend-bg-color); } - &__icon { + &__append { + order: 5; + padding: 0 var(--ti-mobile-input-append-padding-horizontal); + color: var(--ti-mobile-input-append-text-color); + background-color: var(--ti-mobile-input-append-bg-color); + } +} + +.@{input-prefix-cls}__select { + display: flex; + align-items: center; + width: 100%; + height: 100%; + background: var(--ti-mobile-input-bg-color, #fff); + + &-icon { + order: 5; height: 20px; + margin-right: var(--ti-mobile-input-padding-horizontal); line-height: 20px; width: 20px; - margin-right: 16px; font-size: 0; transform-origin: center center; transition: all linear 0.3s; svg { - fill: #999; + fill: var(--ti-mobile-input-icon-color); font-size: 20px; vertical-align: text-bottom; } } - &__label { + &-label { box-sizing: border-box; width: 35%; padding: 0 12px 0 16px; @@ -385,28 +398,15 @@ text-overflow: ellipsis; } - &__input { - float: right; - } - - &__tips { - width: 100%; - color: var(--ti-mobile-textarea-count-text-color, #999); - padding-top: 6px; - line-height: 20px; - } - &__error { width: 100%; - height: 36px; position: absolute; overflow: hidden; font-size: 13px; - line-height: 36px; - color: var(--ti-mobile-common-color-error-text-1, #e62222); + color: var(--ti-mobile-common-color-error-text-1); text-overflow: ellipsis; bottom: -36px; - background: var(--ti-mobile-input-form-bg-color-error, rgba(230, 34, 34, 0.04)); + background: var(--ti-mobile-input-form-bg-color-error); border-radius: 4px; padding: 0px 12px; &.align-right { @@ -426,6 +426,11 @@ animation: rotating 2s linear infinite; } +.@{input-prefix-cls}__tips { + margin-top: var(--ti-mobile-input-tips-margin-top); + font-size: var(--ti-mobile-input-tips-font-size); +} + @keyframes rotating { 0% { transform: rotateZ(0deg); diff --git a/packages/theme-mobile/src/input/vars.less b/packages/theme-mobile/src/input/vars.less index 4da41f2c7..33fd7daf9 100644 --- a/packages/theme-mobile/src/input/vars.less +++ b/packages/theme-mobile/src/input/vars.less @@ -1,23 +1,91 @@ :root { - --ti-mobile-input-bg-color: var(--ti-mobile-base-color-light, #fff); - --ti-mobile-input-line-height: var(--ti-mobile-base-size-height-minor, 30px); - --ti-mobile-input-medium-height: var(--ti-mobile-base-size-height-medium, 42px); - --ti-mobile-input-small-height: var(--ti-mobile-base-size-height-small, 36px); - --ti-mobile-input-mini-height: var(--ti-mobile-base-size-height-mini, 24px); - --ti-mobile-input-color-disabled: var(--ti-mobile-base-color-placeholder, #999); - --ti-mobile-input-bg-color-disabled: var(--ti-mobile-base-color-selected-background, #f5f5f5); - --ti-mobile-input-exceed-text-color: var(--ti-mobile-base-color-danger-normal, #f5222d); - --ti-mobile-input-icon-font-size: 16px; - --ti-mobile-input-icon-close-color: var(--ti-mobile-base-color-primary-disabled, #bfbfbf); - --ti-mobile-textarea-count-text-color: var(--ti-mobile-base-color-placeholder, #999); - --ti-mobile-textarea-count-font-size: var(--ti-mobile-base-font-size, 12px); - --ti-mobile-textarea-inner-height: 96px; - --ti-mobile-textarea-inner-font-size: 16px; - --ti-mobile-textarea-inner-text-color: var(--ti-mobile-base-color-secondary, #666); - --ti-mobile-textarea-inner-placeholder-color: #ccc; - --ti-mobile-textarea-title-font-size: 16px; - --ti-mobile-textarea-title-text-color: var(--ti-mobile-base-color-info-normal, #333); - --ti-mobile-textarea-showlimit-inner-height: 70px; - --ti-mobile-textarea-showcontent-inner-height: 66px; - --ti-mobile-input-form-bg-color-error: rgba(230, 34, 34, 0.04); + // 高度 + --ti-mobile-input-height: 40px; + // 水平内边距 + --ti-mobile-input-padding-horizontal: var(--ti-mobile-space-3x, 12px); + // 字号 + --ti-mobile-input-font-size: var(--ti-mobile-font-size-m, 14px); + // 字族 + --ti-mobile-input-font-family: var(--ti-mobile-font-family); + // 行高 + --ti-mobile-input-line-height: var(--ti-mobile-line-height-default, 1.5); + // 字体色 + --ti-mobile-input-text-color: var(--ti-mobile-color-text-primary, #191919); + // 禁用字体色 + --ti-mobile-input-text-color-disabled: var(--ti-mobile-color-text-disabled, #c2c2c2); + // 禁用图标色 + --ti-mobile-input-icon-color-disabled: var(--ti-mobile-color-icon-disabled, #c2c2c2); + // 禁用背景色 + --ti-mobile-input-bg-color-disabled: var(--ti-mobile-color-bg-disabled, #f0f0f0); + // 边框宽度 + --ti-mobile-input-border-width: var(--ti-mobile-border-width-1, 1px); + // 边框色 + --ti-mobile-input-border-color: var(--ti-mobile-color-border-default, #c2c2c2); + // 输入时边框色 + --ti-mobile-input-border-color-active: var(--ti-mobile-color-border-active, #191919); + // 边框禁用色 + --ti-mobile-border-color-disabled: var(--ti-mobile-color-border-disabled, #dbdbdb); + // 圆角 + --ti-mobile-input-radius: var(--ti-mobile-border-radius-m, 8px); + // 背景色 + --ti-mobile-input-bg-color: var(--ti-mobile-color, #fff); + // 占位符字体色 + --ti-mobile-input-placeholder-text-color: var(--ti-mobile-color-text-placeholder, #808080); + // 占位符字重 + --ti-mobile-input-placeholder-font-weight: var(--ti-mobile-font-weight-medium, 500); + // medium尺寸高度 + --ti-mobile-input-medium-height: 42px; + // small尺寸高度 + --ti-mobile-input-small-height: 36px; + // mini尺寸高度 + --ti-mobile-input-mini-height: 24px; + // 字数提示字体色 + --ti-mobile-input-count-text-color: var(--ti-mobile-color-text-placeholder, #808080); + // 字数提示字号 + --ti-mobile-input-count-font-size: var(--ti-mobile-font-size-s, 12px); + // 超出文本提示语字体色 + --ti-mobile-input-exceed-text-color: var(--ti-mobile-color-error, #f23030); + // 图标颜色 + --ti-mobile-input-icon-color: var(--ti-mobile-color-icon-default, #808080); + // 图标尺寸 + --ti-mobile-input-icon-font-size: var(--ti-mobile-font, 20px); + // prepend内容字体色 + --ti-mobile-input-prepend-text-color: var(--ti-mobile-color-text-secondary, #595959); + // prepend内容背景色 + --ti-mobile-input-prepend-bg-color: var(--ti-mobile-color-bg-default, #f5f5f5); + // prepend内容水平内边距 + --ti-mobile-input-prepend-padding-horizontal: var(--ti-mobile-space-5x, 20px); + // append内容字体色 + --ti-mobile-input-append-text-color: var(--ti-mobile-color-text-secondary, #595959); + // append内容背景色 + --ti-mobile-input-append-bg-color: var(--ti-mobile-color-bg-default, #f5f5f5); + // append内容水平内边距 + --ti-mobile-input-append-padding-horizontal: var(--ti-mobile-space-5x, 20px); + // prefix内容右边距 + --ti-mobile-input-prefix-padding-right: var(--ti-mobile-space-2x, 8px); + // suffix内容左边距 + --ti-mobile-input-suffix-padding-left: var(--ti-mobile-space-2x, 8px); + // 标题下边距 + --ti-mobile-input-title-margin-bottom: var(--ti-mobile-space-2x, 8px); + // 提示信息字号 + --ti-mobile-input-tips-font-size: var(--ti-mobile-font-size-s, 12px); + // 提示信息上边距 + --ti-mobile-input-tips-margin-top: var(--ti-mobile-space-2x, 8px); + + // 文本域垂直内边距 + --ti-mobile-textarea-padding-vertical: var(--ti-mobile-space-2x, 8px); + // 文本域水平内边距 + --ti-mobile-textarea-padding-horizontal: var(--ti-mobile-space-4x, 16px); + // 文本域字号 + --ti-mobile-textarea-inner-font-size: var(--ti-mobile-font-size-m, 14px); + // 文本域字体色 + --ti-mobile-textarea-inner-text-color: var(--ti-mobile-color-text-primary, #191919); + // 文本域占位符字体色 + --ti-mobile-textarea-inner-placeholder-color: var(--ti-mobile-color-text-placeholder, #808080); + // 文本域标题字号 + --ti-mobile-textarea-title-font-size: var(--ti-mobile-font-size-l, 16px); + // 文本域标题字体色 + --ti-mobile-textarea-title-text-color: var(--ti-mobile-color-text-primary, #191919); + // 错误状态背景色 + --ti-mobile-input-form-bg-color-error: var(--ti-mobile-color-error-subtle, #fce3e1); } diff --git a/packages/theme-mobile/src/mask/index.less b/packages/theme-mobile/src/mask/index.less new file mode 100644 index 000000000..4f42508d1 --- /dev/null +++ b/packages/theme-mobile/src/mask/index.less @@ -0,0 +1,59 @@ +/** +* Copyright (c) 2022 - present TinyVue Authors. +* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. +* +* Use of this source code is governed by an MIT-style license. +* +* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, +* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR +* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. +* +*/ + +@import '../custom.less'; +@import '../base/basic-var.less'; +@import './vars.less'; + +@mask-prefix-cls: ~'@{css-prefix}mobile-mask'; + +.@{mask-prefix-cls} { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: var(--tiny-mobile-mask-bg-color); + height: 100%; +} + +.mask-fade-enter-active { + animation: mask-fade-in 0.3s; +} + +.mask-fade-leave-active { + animation: mask-fade-out 0.3s; +} + +@keyframes mask-fade-in { + 0% { + transform: translate3d(0, -20px, 0); + opacity: 0; + } + + 100% { + transform: translate3d(0, 0, 0); + opacity: 1; + } +} + +@keyframes mask-fade-out { + 0% { + transform: translate3d(0, 0, 0); + opacity: 1; + } + + 100% { + transform: translate3d(0, -20px, 0); + opacity: 0; + } +} \ No newline at end of file diff --git a/packages/theme-mobile/src/mask/vars.less b/packages/theme-mobile/src/mask/vars.less new file mode 100644 index 000000000..51195053b --- /dev/null +++ b/packages/theme-mobile/src/mask/vars.less @@ -0,0 +1,4 @@ +:root { + // 遮罩层背景色 + --tiny-mobile-mask-bg-color: var(--ti-color-bg-mask); +} diff --git a/packages/theme-mobile/src/mixins/checkbox.less b/packages/theme-mobile/src/mixins/checkbox.less index 711e3214c..82f733b73 100644 --- a/packages/theme-mobile/src/mixins/checkbox.less +++ b/packages/theme-mobile/src/mixins/checkbox.less @@ -14,9 +14,9 @@ @checkbox-prefix-cls: ~'@{css-prefix}mobile-checkbox'; -.checkbox-input-disabled(@color, @bgcolor, @border-color) { +.checkbox-input-disabled(@color, @bg-color, @checked-bg-color, @border-color) { .@{checkbox-prefix-cls}__inner { - background-color: @bgcolor; + background-color: @bg-color; border-color: @border-color; cursor: not-allowed; } @@ -24,14 +24,14 @@ &.is-checked { .@{checkbox-prefix-cls}__inner, .@{checkbox-prefix-cls}__inner:hover { - background-color: @border-color; + background-color: @checked-bg-color; } } &.is-indeterminate { .@{checkbox-prefix-cls}__inner, .@{checkbox-prefix-cls}__inner:hover { - background-color: @bgcolor; + background-color: @bg-color; &::before { background-color: @border-color; diff --git a/packages/vue/package.json b/packages/vue/package.json index 7794da63b..daf3bc1e7 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -154,6 +154,7 @@ "@opentiny/vue-locales": "workspace:~", "@opentiny/vue-logon-user": "workspace:~", "@opentiny/vue-logout": "workspace:~", + "@opentiny/vue-mask": "workspace:~", "@opentiny/vue-menu": "workspace:~", "@opentiny/vue-message": "workspace:~", "@opentiny/vue-milestone": "workspace:~", diff --git a/packages/vue/src/alert/src/index.ts b/packages/vue/src/alert/src/index.ts index d2da99973..298acebe2 100644 --- a/packages/vue/src/alert/src/index.ts +++ b/packages/vue/src/alert/src/index.ts @@ -75,7 +75,19 @@ export const alertProps = { type: Boolean, default: false }, - customClass: [String, Object, Array] + customClass: [String, Object, Array], + offset: { + type: [Number, String], + default: 0 + }, + autoHide: { + type: Boolean, + default: false + }, + target: { + type: String, + default: '' + } } const aaa = {} diff --git a/packages/vue/src/alert/src/mobile.vue b/packages/vue/src/alert/src/mobile.vue index a5fd0e144..78d7e49f7 100644 --- a/packages/vue/src/alert/src/mobile.vue +++ b/packages/vue/src/alert/src/mobile.vue @@ -13,7 +13,8 @@
@@ -37,7 +38,7 @@ import type { IAlertApi } from '@opentiny/vue-renderless/types/alert.type' import '@opentiny/vue-theme-mobile/alert/index.less' export default defineComponent({ - props: [...props, 'icon', 'type', 'size', 'description', 'closable', 'showIcon', 'closeText', 'duration'], + props: [...props, 'icon', 'type', 'size', 'description', 'closable', 'showIcon', 'closeText', 'duration', 'offset', 'autoHide', 'target', 'center'], components: { IconClose: iconClose(), IconSuccess: iconSuccess(), diff --git a/packages/vue/src/charts-beta/chart-bar/index.ts b/packages/vue/src/charts-beta/chart-bar/index.ts new file mode 100644 index 000000000..b37f5d060 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-bar/index.ts @@ -0,0 +1,9 @@ +import ChartBar from './src/chart-bar.vue' + +ChartBar.install = function (Vue: any) { + Vue.component(ChartBar.name, ChartBar) +} + +export { ChartBar } + +export default ChartBar \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-bar/package.json b/packages/vue/src/charts-beta/chart-bar/package.json new file mode 100644 index 000000000..fe4e4bf01 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-bar/package.json @@ -0,0 +1,18 @@ +{ + "name": "@opentiny/vue-chart-bar-beta", + "version": "3.7.0", + "description": "", + "main": "lib/index.js", + "module": "index.ts", + "sideEffects": false, + "type": "module", + "devDependencies": {}, + "scripts": { + "build": "pnpm -w build:ui $npm_package_name", + "//postversion": "pnpm build" + }, + "dependencies": { + "@opentiny/vue-chart-core-beta": "workspace:~" + }, + "license": "MIT" +} \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-bar/src/chart-bar.vue b/packages/vue/src/charts-beta/chart-bar/src/chart-bar.vue new file mode 100644 index 000000000..33028f225 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-bar/src/chart-bar.vue @@ -0,0 +1,38 @@ + + \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-bar/src/histogram.ts b/packages/vue/src/charts-beta/chart-bar/src/histogram.ts new file mode 100644 index 000000000..35825610c --- /dev/null +++ b/packages/vue/src/charts-beta/chart-bar/src/histogram.ts @@ -0,0 +1,266 @@ +import { getFormatted, cloneDeep, getStackMap, get, set } from '@opentiny/vue-chart-core-beta/common/util' +import { isNull } from '@opentiny/vue-chart-core-beta/common/type' + +import { getRows, getTooltip } from '@opentiny/vue-chart-core-beta/utils/options' + +const VALUE_AXIS_OPACITY = 0.5 + +const getBarDimAxis = (args) => { + const { innerRows, dimAxisName, dimension, axisVisible, dimAxisType, dims } = args + return dimension.map((item) => ({ + type: 'category', + name: dimAxisName, + nameLocation: 'middle', + nameGap: 22, + data: dimAxisType === 'value' ? getValueAxisData(dims) : innerRows.map((row) => row[item]), + axisLabel: { + formatter(value) { + return String(value) + } + }, + show: axisVisible + })) +} + +const getBarMeaAxis = (args) => { + const { axisVisible, digit, max, meaAxisName = [], meaAxisType, min, scale } = args + const meaAxisBase = { type: 'value', axisTick: { show: false }, show: axisVisible } + let { meaAxis = [], i = 0, formatter } = {} + + for (; i < 2; i++) { + if (meaAxisType[i]) { + formatter = factoryFmt({ meaAxisType, i, digit }) + + meaAxis[i] = { ...meaAxisBase, axisLabel: { formatter } } + } else { + meaAxis[i] = { ...meaAxisBase } + } + + Object.assign(meaAxis[i], { + max: max[i] || null, + min: min[i] || null, + name: meaAxisName[i] || '', + scale: scale[i] || false + }) + } + + return meaAxis +} + +const factoryFmt = + ({ meaAxisType, i, digit }) => + (val) => + getFormatted(val, meaAxisType[i], digit) + +const getLegend = (args) => { + const { legendName } = args + let legendBase = { + show: true + } + let formatter = function (name) { + return !legendName[name] ? name : legendName[name] + } + return { + ...legendBase, + formatter + } +} + +const getDims = (rows, dimension) => rows.map((row) => row[dimension[0]]) + +const getValueAxisData = (dims) => { + const max = Math.max(...dims) + const min = Math.min(...dims) + let { result = [], i = min } = {} + + for (; i <= max; i++) { + result.push(i) + } + + return result +} + +const getValueData = (seriesTemp, dims) => { + const max = Math.max(...dims) + const min = Math.min(...dims) + let { result = [], i = min, index } = {} + + for (; i <= max; i++) { + index = dims.indexOf(i) + + result.push(~index ? seriesTemp[index] : null) + } + + return result +} + +const getBarSeries = (args) => { + const { axisSite, barGap, dimAxisType, dims, innerRows, isHistogram, itemStyle } = args + const { label, labelMap, metrics, opacity, showLine = [], stack } = args + let { secondAxis, secondDimAxisIndex, series = [], seriesTemp = {}, stackMap, stackNum = 0 } = {} + secondAxis = (isHistogram ? axisSite?.right : axisSite?.top) || [] + secondDimAxisIndex = isHistogram ? 'yAxisIndex' : 'xAxisIndex' + stackMap = stack && getStackMap(stack) + metrics.forEach((item) => (seriesTemp[item] = [])) + innerRows.forEach((row) => metrics.forEach((item) => seriesTemp[item].push(row[item]))) + + series = Object.keys(seriesTemp).map((item) => { + let name = !isNull(labelMap[item]) ? labelMap[item] : item + let type = ~showLine.indexOf(item) ? 'line' : 'bar' + let axisIndex = ~secondAxis.indexOf(item) ? '1' : '0' + + let seriesItem = { name, type, [secondDimAxisIndex]: axisIndex } + + const defaultItemStyle = {} + + stack && stackMap[item] && (seriesItem.stack = stackMap[item]) + + if (Object.keys(stack).length) { + // 堆叠图 + if (stackNum === Object.keys(stackMap).length - 1 || isNull(seriesItem.stack)) { + seriesItem.itemStyle = Object.assign(defaultItemStyle, seriesItem.itemStyle) + } + if (!isNull(seriesItem.stack)) { + stackNum++ + } + + seriesItem.itemStyle = { borderWidth: 2, borderColor: 'transparent', ...seriesItem.itemStyle } + } else { + // 非堆叠图 + } + + itemStyle && (seriesItem.itemStyle = itemStyle) + + let itemOpacity = opacity || get(seriesItem, 'itemStyle.opacity') + + dimAxisType === 'value' && Object.assign(seriesItem, { barGap, barCategoryGap: '1%' }) + dimAxisType === 'value' && isNull(itemOpacity) && (itemOpacity = VALUE_AXIS_OPACITY) + + !isNull(itemOpacity) && set(seriesItem, 'itemStyle.opacity', itemOpacity) + + return seriesItem + }) + + return series.length ? series : false +} + +const getDataValue = (data, dimension, metrics, innerRows, dims) => { + let dimensionData = dimension[0] + let dataTemp = {} + data.forEach((item, index) => { + dataTemp[item[dimensionData]] = item + }) + let dataItemTemp = {} + metrics.forEach((item, index) => { + dataItemTemp[item] = null + }) + + const max = Math.max(...dims) + const min = Math.min(...dims) + let { result = [], i = min, index } = {} + + for (; i <= max; i++) { + index = dims.indexOf(i) + result.push(~index ? dataTemp[i] : { [dimensionData]: i, ...dataItemTemp }) + } + + return result +} + +export const histogram = (columns, rows, settings, extra, isHistogram = true) => { + const innerRows = cloneDeep(rows) + const { axisSite = {}, dimension = [columns[0]], axisLabel = {}, axisVisible = true } = settings + + const { + digit = 2, + dataOrder = false, + scale = [false, false], + min = [null, null], + max = [null, null], + stack = {} + } = settings + + const { tooltipVisible, legendVisible } = extra + + const { labelMap = {}, legendName = {}, label, itemStyle = {}, showLine, barGap = '-100%', opacity } = settings + + let { metrics = columns.slice(), meaAxisType, dimAxisType, meaAxisName, dimAxisName = true, dims } = {} + + if (dataOrder) { + let { label, order } = dataOrder + if (label && order) { + innerRows.sort((a, b) => (order === 'desc' ? a[label] - b[label] : b[label] - a[label])) + } + } + let xAxis = {} + let yAxis = {} + dims = getDims(innerRows, dimension) + if (isHistogram) { + if (axisSite.right && axisSite.left) { + metrics = axisSite.left.concat(axisSite.right) + } else if (settings.metrics) { + metrics = settings.metrics + } else if (axisSite.left && !axisSite.right) { + metrics = axisSite.left + } else { + metrics.splice(columns.indexOf(dimension[0]), 1) + } + dimAxisType = settings.xAxisType || 'category' + meaAxisType = settings.yAxisType || ['normal', 'normal'] + dimAxisName = settings.xAxisName || '' + meaAxisName = settings.yAxisName || [] + xAxis = getBarDimAxis({ innerRows, dimAxisName, dimension, axisVisible, dimAxisType, dims }) + + yAxis = getBarMeaAxis({ meaAxisName, meaAxisType, axisVisible, digit, scale, min, max }) + } else { + if (axisSite.bottom && axisSite.top) { + metrics = axisSite.top.concat(axisSite.bottom) + } else if (!axisSite.right && axisSite.bottom) { + metrics = axisSite.bottom + } else if (settings.metrics) { + metrics = settings.metrics + } else { + metrics.splice(columns.indexOf(dimension[0]), 1) + } + dimAxisType = settings.yAxisType || 'category' + meaAxisType = settings.xAxisType || ['normal', 'normal'] + dimAxisName = settings.yAxisName || '' + xAxis = getBarDimAxis({ innerRows, dimAxisName, dimension, axisVisible, dimAxisType, dims }) + yAxis = getBarMeaAxis({ axisVisible, meaAxisType, meaAxisName, scale, digit, max, min }) + } + + if (opacity) { + const itemStyleBase = { + opacity + } + Object.assign(itemStyle, itemStyleBase) + } + let data = getRows({ columns, metrics, labelMap, rows: innerRows, dimension }) + if (dimAxisType === 'value') { + data = getDataValue(data, dimension, metrics, innerRows, dims) + } + + const lineDataName = showLine ? [...showLine] : [] + const legend = legendVisible ? getLegend({ legendName, metrics, labelMap }) : { show: false } + const tooltip = tooltipVisible ? getTooltip({ axisSite, yAxisType: meaAxisType }) : { show: false } + const tipHtml = tooltip.formatter + let args = { innerRows, metrics, stack, axisSite, isHistogram, labelMap, itemStyle, label } + Object.assign(args, { showLine, dimAxisType, dimension, barGap, opacity, dims }) + let options = { + data, + itemStyle, + tipHtml, + lineDataName, + legend, + tooltip, + yAxis, + xAxis, + label, + stack, + series: getBarSeries(args) + } + if (typeof options.stack === 'object' && options.stack !== null && Object.keys(options.stack).length > 0) { + options.type = 'stack' + } + return options +} diff --git a/packages/vue/src/charts-beta/chart-core/base/chart.ts b/packages/vue/src/charts-beta/chart-core/base/chart.ts new file mode 100644 index 000000000..504cded2e --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/chart.ts @@ -0,0 +1,58 @@ +import core from './core' +import Register from './register' +import Theme from './feature/theme' +import { isFunction } from './util/type' +import { mergeExtend } from './util/merge' +import readScreen from './feature/readScreen' +import mediaScreen from './feature/mediaScreen' + +// 图表核心对象,通过 Register 全量引入图表给 HuiCharts 渲染,打包容量较大 +export default class HuiCharts extends core { + constructor() { + super() + // 图表名称 + this.chartName + } + + // 传入简化后的icharts-option + setSimpleOption(chartName, iChartOption, plugins = {}, isInit = true) { + if (isInit) { + Theme.setDefaultTheme(iChartOption.theme) + this.mediaScreenObserver && this.mediaScreenObserver.setInitOption(iChartOption) + } + if (iChartOption.readScreen) { + readScreen(this.dom, iChartOption.readScreen) + } + if (isFunction(chartName)) { + this.redirectSelfChart(chartName, iChartOption) + return + } + this.plugins = plugins + this.chartName = chartName + this.iChartOption = iChartOption + const ChartClass = this.getChartClass(chartName) + this.ichartsIns = new ChartClass(iChartOption, this.echartsIns, this.plugins) + this.eChartOption = this.ichartsIns.getOption() + mergeExtend(this.iChartOption, this.eChartOption) + } + + // 获取图表类 + getChartClass(name) { + return Register.getRegisteredComp(name) + } + + // 开启响应式布局(类媒体查询效果) + mediaScreen(dom, screenOption) { + this.mediaScreenObserver = new mediaScreen(dom, screenOption, (option) => { + this.setSimpleOption(this.chartName, option, this.plugins, false) + this.render() + }) + } + + // 图表刷新,包括刷新配置和数据 + refresh(iChartOption) { + this.iChartOption = iChartOption + this.setSimpleOption(this.chartName, iChartOption, this.plugins) + this.render() + } +} diff --git a/packages/vue/src/charts-beta/chart-core/base/components/BarChart/chartToken.ts b/packages/vue/src/charts-beta/chart-core/base/components/BarChart/chartToken.ts new file mode 100644 index 000000000..cfc5080ab --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/BarChart/chartToken.ts @@ -0,0 +1,29 @@ +import Theme from '../../feature/theme' +import proxy from '../../util/proxy' + +function getChartToken() { + const { + itemStyle: { borderWidth, borderColor, borderRadius, color }, + label: { color: labelColor, fontSize }, + barWidth + } = Theme.config.BarChart + + const { + colorState: { errorColor } + } = Theme.config + + return { + borderWidth, + borderColor, + borderRadius, + color, + labelColor, + fontSize, + barWidth, + errorColor + } +} + +const chartToken = proxy(getChartToken) + +export default chartToken diff --git a/packages/vue/src/charts-beta/chart-core/base/components/BarChart/handleOptipn.ts b/packages/vue/src/charts-beta/chart-core/base/components/BarChart/handleOptipn.ts new file mode 100644 index 000000000..1d021a7f6 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/BarChart/handleOptipn.ts @@ -0,0 +1,84 @@ +import defendXSS from '../../util/defendXSS' +import chartToken from './chartToken' + +/** + * 给堆叠图的柱子中间加上空白缝隙, 处理柱子圆角的递进关系 + */ +export function setStack(baseOption, iChartOption, legendData, seriesData) { + const type = iChartOption.type + if (type && type === 'stack') { + // 添加堆叠图空白缝隙 + baseOption.series.forEach((item) => { + item.itemStyle.borderWidth = chartToken.borderWidth + item.itemStyle.borderColor = chartToken.borderColor + }) + // 柱子圆角,上层数值为空时,圆角递进到下层 + const direction = iChartOption.direction + iChartOption.data.forEach((item, i) => { + for (let j = legendData.length - 1; j >= 0; j--) { + const name = legendData[j] + if (item[name]) { + seriesData[name][i] = { + value: seriesData[name][i], + itemStyle: { + borderRadius: + direction === 'horizontal' + ? [0, chartToken.borderRadius, chartToken.borderRadius, 0] + : [chartToken.borderRadius, chartToken.borderRadius, 0, 0] + } + } + break + } + } + }) + } +} + +/** + * 将所有y轴的label都转为正数 + * 内置好转为正数的tooltip + */ +export function setDoubleSides(baseOption, iChartOption) { + const type = iChartOption.type + if (type && type === 'double-sides') { + const yAxis = baseOption.yAxis + yAxis.forEach((item) => { + item.axisLabel.formatter = (value) => { + return Math.abs(value) + } + }) + if (!baseOption.tooltip.formatter) { + baseOption.tooltip.formatter = (params, ticket, callback) => { + let html = '' + params.forEach((item, index) => { + if (index === 0) { + html += `
${defendXSS(item.name)}
` + } + html += `
+ + + ${defendXSS( + item.seriesName + )} + ${defendXSS(item.value ? Math.abs(item.value) : '-')} + +
` + }) + return html + } + } + } +} + +/** + * 设置柱状图的方向 + */ +export function setDirection(baseOption, direction) { + if (direction && direction === 'horizontal') { + const temp = baseOption.xAxis + baseOption.xAxis = baseOption.yAxis + baseOption.yAxis = temp + } +} diff --git a/packages/vue/src/charts-beta/chart-core/base/components/BarChart/handleSeries.ts b/packages/vue/src/charts-beta/chart-core/base/components/BarChart/handleSeries.ts new file mode 100644 index 000000000..39af9a180 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/BarChart/handleSeries.ts @@ -0,0 +1,614 @@ +import merge from '../../util/merge' +import defendXSS from '../../util/defendXSS' +import { getColor } from '../../util/color' +import cloneDeep from '../../util/cloneDeep' +import { isArray, isNumber } from '../../util/type' +import { getMarkLineDefault } from '../../option/config/mark' +import chartToken from './chartToken' + +export const seriesInit = () => { + return { + label: { + show: false, + color: chartToken.labelColor, + fontSize: chartToken.fontSize + }, + // 数据 + data: [], + // 柱形 + type: 'bar', + // 柱条宽度 + barWidth: chartToken.barWidth, + // 柱间距离 + barGap: '60%', + // 阈值线 + markLine: null, + // 峰值标志 + markPoint: null, + // 柱形的每个样式配置项 + itemStyle: { + borderRadius: [chartToken.borderRadius, chartToken.borderRadius, 0, 0] + } + } +} + +function handleWaterFall(type, seriesUnit) { + if (type && type === 'water-fall') { + // 调整堆叠柱子圆角 + seriesUnit.itemStyle.borderRadius = [ + chartToken.borderRadius, + chartToken.borderRadius, + chartToken.borderRadius, + chartToken.borderRadius + ] + // 瀑布图最有有一个总体数据 + seriesUnit.data.push( + seriesUnit.data.reduce(function (prev, curr) { + const n = Number(curr) || 0 + return prev + n + }, 0) + ) + } +} + +function handleRange(type, seriesUnit) { + if (type && type === 'range') { + // 调整堆叠柱子圆角 + seriesUnit.itemStyle.borderRadius = [ + chartToken.borderRadius, + chartToken.borderRadius, + chartToken.borderRadius, + chartToken.borderRadius + ] + } +} + +/** + * 组装echarts所需要的series + * @param {图表数据} seriesData + * @param {图例数据} legendData + * @param {主题} theme + * @param {是否面积图} isArea + * @param {是否曲线} isSmooth + * @param {是否阶梯线} isStep + * @param {阈值线} markLine + * @param {阈值箭头} markPoint + * @param {颜色集合} colors + * @returns + */ +export function setSeries(seriesData, legendData, iChartOption) { + // 柱状图类型 + const type = iChartOption.type + // 柱状图方向 + const direction = iChartOption.direction + // 覆盖用户传入的itemStyle + const seriesInit_ = handleItemStyle(direction, iChartOption.itemStyle) + // 拼装series + const series = [] + legendData.forEach((legend, index) => { + const seriesUnit = cloneDeep(seriesInit_) + // 数值显示 + handleLabel(seriesUnit, iChartOption, index) + // 聚焦效果 + handleFocus(seriesUnit, iChartOption) + // 数据 / 数据名称 + seriesUnit.name = legend + seriesUnit.data = seriesData[legend] + // 阈值线 + handleMarkLine(seriesUnit, iChartOption, direction) + // 堆叠图 + handleStack(type, seriesUnit, index, legendData, iChartOption) + // 双向图 + handleBothSides(type, seriesUnit, direction, index, legendData) + // 数据均为正数的双向图 + handleDoubleSides(type, seriesUnit, index, legendData) + // 瀑布图 + handleWaterFall(type, seriesUnit) + // 区间图 + handleRange(type, seriesUnit) + // 包含图 + handleContain(type, seriesUnit) + series.push(seriesUnit) + }) + // 配置多个series的y轴index + handleYaxis(series, iChartOption.yAxis) + return series +} + +function handleItemStyle(direction, itemStyle) { + const seriesInit_ = cloneDeep(seriesInit()) + if (direction && direction === 'horizontal') { + seriesInit_.itemStyle.borderRadius = [0, chartToken.borderRadius, chartToken.borderRadius, 0] + } + if (itemStyle?.barMinHeight) { + seriesInit_.barMinHeight = itemStyle.barMinHeight + } + if (itemStyle?.barWidth) { + seriesInit_.barWidth = itemStyle.barWidth + } + if (itemStyle?.barGap) { + seriesInit_.barGap = itemStyle.barGap + } + if (itemStyle?.color) { + seriesInit_.itemStyle.color = itemStyle.color + } + merge(seriesInit_.itemStyle, itemStyle) + return seriesInit_ +} + +function handleLabel(seriesUnit, iChartOption, index) { + const label = iChartOption.label + let labelOption + if (label && isArray(label)) { + labelOption = label[index] + } else { + labelOption = label + } + if (labelOption && labelOption.show) { + merge(seriesUnit.label, labelOption) + seriesUnit.label.show = true + seriesUnit.label.offset = labelOption.offset || [0, 0] + seriesUnit.label.position = labelOption.position || 'inside' + seriesUnit.label.formatter = labelOption.formatter + } +} + +function handleMarkLine(seriesUnit, iChartOption, direction) { + const name = seriesUnit.name + const theme = iChartOption.theme + const markLine = iChartOption.markLine + const isTopMarkLine = markLine && markLine.top && !(markLine.topUse && !markLine.topUse.includes(name)) + const isBottomMarkLine = markLine && markLine.bottom && !(markLine.bottomUse && !markLine.bottomUse.includes(name)) + if (isTopMarkLine || isBottomMarkLine) { + seriesUnit.markLine = cloneDeep(getMarkLineDefault()) + merge(seriesUnit.markLine, markLine) + seriesUnit.markLine.lineStyle.color = markLine.color || chartToken.errorColor + } + if (isTopMarkLine) { + if (direction && direction === 'horizontal') { + seriesUnit.markLine.data.push({ xAxis: markLine.top }) + } else { + seriesUnit.markLine.data.push({ yAxis: markLine.top }) + } + } + if (isBottomMarkLine) { + if (direction && direction === 'horizontal') { + seriesUnit.markLine.data.push({ xAxis: markLine.bottom }) + } else { + seriesUnit.markLine.data.push({ yAxis: markLine.bottom }) + } + } +} + +function handleFocus(seriesUnit, iChartOption) { + if (iChartOption.focus) { + seriesUnit.emphasis = { + focus: 'series', + blurScope: 'global' + } + } +} + +function handleStack(type, seriesUnit, index, legendData, iChartOption) { + if (type && type === 'stack') { + const stack = iChartOption.stack + if (stack) { + for (const name in stack) { + if (Object.hasOwnProperty.call(stack, name)) { + const stackArray = stack[name] + const seriesName = seriesUnit.name + const stackIndex = stackArray.indexOf(seriesName) + if (stackIndex !== -1) { + seriesUnit.stack = name + if (stackIndex + 1 < stackArray.length) { + delete seriesUnit.itemStyle.borderRadius + } + } + break + } + } + } else { + seriesUnit.stack = 'stack' + if (index !== legendData.length - 1) { + delete seriesUnit.itemStyle.borderRadius + } + } + } +} + +function handleBothSides(type, seriesUnit, direction, index, legendData) { + if (type && (type === 'both-sides' || type === 'double-sides')) { + seriesUnit.stack = 'stack' + // 调整堆叠柱子圆角 + if (direction && direction === 'horizontal') { + if (index === 0) { + seriesUnit.itemStyle.borderRadius = [0, chartToken.borderRadius, chartToken.borderRadius, 0] + } + if (index === legendData.length - 1) { + seriesUnit.itemStyle.borderRadius = [chartToken.borderRadius, 0, 0, chartToken.borderRadius] + } + } else { + if (index === 0) { + seriesUnit.itemStyle.borderRadius = [chartToken.borderRadius, chartToken.borderRadius, 0, 0] + } + if (index === legendData.length - 1) { + seriesUnit.itemStyle.borderRadius = [0, 0, chartToken.borderRadius, chartToken.borderRadius] + } + } + } +} + +function handleDoubleSides(type, seriesUnit, index, legendData) { + if (type && type === 'double-sides') { + if (index === legendData.length - 1) { + seriesUnit.data = seriesUnit.data.map((item) => { + if (isNumber(item)) { + return -1 * item + } else { + return item + } + }) + } + } +} + +function handleContain(type, seriesUnit) { + if (type && type === 'contain') { + seriesUnit.barGap = '-100%' + } +} + +function handleColorStops(percent, originColor, markLineColor) { + const colorStops = [ + { + offset: 0, + color: markLineColor || chartToken.errorColor + }, + { + offset: percent, + color: markLineColor || chartToken.errorColor + }, + { + offset: percent + 0.001, + color: originColor + }, + { + offset: 1, + color: originColor + } + ] + return colorStops +} + +function handleTopObj(d, direction, percent, originColor, markLineColor) { + const topObj = { + value: d, + itemStyle: { + color: { + type: 'linear', + x: direction === 'horizontal' ? 1 : 0, + y: direction === 'horizontal' ? 0 : 0, + x2: direction === 'horizontal' ? 0 : 0, + y2: direction === 'horizontal' ? 0 : 1, + colorStops: handleColorStops(percent, originColor, markLineColor) + } + } + } + return topObj +} + +function handleBottomObj(d, direction, percent, originColor, markLineColor) { + const bottomObj = { + value: d, + itemStyle: { + color: { + type: 'linear', + x: direction === 'horizontal' ? 0 : 0, + y: direction === 'horizontal' ? 0 : 1, + x2: direction === 'horizontal' ? 1 : 0, + y2: direction === 'horizontal' ? 0 : 0, + colorStops: handleColorStops(percent, originColor, markLineColor) + } + } + } + return bottomObj +} +const colorStopsOrigin = [ + { offset: 0, color: chartToken.errorColor }, + { offset: 1, color: chartToken.errorColor } +] + +function handleColorStopsTop(originColor, bottomPercent) { + const colorStops = [ + { offset: 0, color: originColor }, + { offset: bottomPercent, color: originColor }, + { offset: bottomPercent + 0.0001, color: chartToken.errorColor }, + { offset: 1, color: chartToken.errorColor } + ] + return colorStops +} + +function handleColorStopsBottom(originColor, topPercent) { + const colorStops = [ + { offset: 0, color: chartToken.errorColor }, + { offset: topPercent, color: chartToken.errorColor }, + { offset: topPercent + 0.0001, color: originColor }, + { offset: 1, color: originColor } + ] + return colorStops +} + +function handleColorStopsOther(originColor, topPercent, bottomPercent) { + const colorStops = [ + { offset: 0, color: chartToken.errorColor }, + { offset: topPercent, color: chartToken.errorColor }, + { offset: topPercent + 0.0001, color: originColor }, + { offset: bottomPercent, color: originColor }, + { offset: bottomPercent + 0.0001, color: chartToken.errorColor }, + { offset: 1, color: chartToken.errorColor } + ] + return colorStops +} + +function handleResObj(d, direction, colorStops) { + const resObj = { + value: d, + itemStyle: { + color: { + type: 'linear', + x: direction === 'horizontal' ? 1 : 0, + y: direction === 'horizontal' ? 0 : 0, + x2: direction === 'horizontal' ? 0 : 0, + y2: direction === 'horizontal' ? 0 : 1, + colorStops + } + } + } + return resObj +} + +function handleSeries(iChartOption, baseOption, exclude, colors, direction) { + // 顶部阈值 + let top = iChartOption.markLine.top + // 底部阈值 + let bottom = iChartOption.markLine.bottom + const usefullSeries = baseOption.series.filter((item) => { + return !exclude.includes(item.name) + }) + usefullSeries.forEach((item, index) => { + if (!exclude.includes(item.name)) { + const barData = item.data + const placeHolderData = baseOption.series[index * 2].data + item.data = barData.map((d, i) => { + const pd = placeHolderData[i] + if (top === undefined) { + top = pd + d + 1 + } + if (bottom === undefined) { + bottom = pd - 1 + } + const originColor = getColor(colors, index) + let topPercent = 0 + let bottomPercent = 1 + topPercent = (d + pd - top) / d + topPercent < 0 && (topPercent = 0) + topPercent > 1 && (topPercent = 1) + bottomPercent = (d + pd - bottom) / d + bottomPercent < 0 && (bottomPercent = 0) + bottomPercent > 1 && (bottomPercent = 1) + let colorStops = [] + if (topPercent === 1 || bottomPercent === 0) { + // 纯红 + colorStops = colorStopsOrigin + } else if (topPercent === 0 && bottomPercent === 1) { + // 原色 + return d + } else if (topPercent === 0) { + colorStops = handleColorStopsTop(originColor, bottomPercent) + } else if (bottomPercent === 1) { + colorStops = handleColorStopsBottom(originColor, topPercent) + } else { + colorStops = handleColorStopsOther(originColor, topPercent, bottomPercent) + } + const resObj = handleResObj(d, direction, colorStops) + return resObj + }) + } + }) +} + +// 针对阈值线以上显示红色区域的需求,图表需要进行特殊处理 +export function setMarkLine(baseOption, iChartOption) { + const type = iChartOption.type + const colors = baseOption.color + const direction = iChartOption.direction + const exclude = ['Placeholder'] + if (iChartOption.markLine && type !== 'water-fall' && type !== 'range') { + // 顶部阈值 + const top = iChartOption.markLine.top + const topUse = iChartOption.markLine.topUse + // 底部阈值 + const bottom = iChartOption.markLine.bottom + const bottomUse = iChartOption.markLine.bottomUse + // 用户自定义阈值线颜色 + const markLineColor = iChartOption.markLine.color + const usefullSeries = baseOption.series.filter((item) => { + return !exclude.includes(item.name) + }) + usefullSeries.forEach((item, index) => { + if (!exclude.includes(item.name)) { + const barData = item.data + item.data = barData.map((d) => { + const originColor = getColor(colors, index) + // 如果该柱形高度超过阈值,侧改变其颜色 + if (top && d >= 0 && top >= 0 && d > top) { + if (topUse && !topUse.includes(item.name)) { + return d + } + const percent = (d - top) / (d - 0) + const topObj = handleTopObj(d, direction, percent, originColor, markLineColor) + return topObj + // 如果该柱形高度低于阈值,侧改变其颜色 + } else if (bottom && d <= 0 && bottom <= 0 && d < bottom) { + if (bottomUse && !bottomUse.includes(item.name)) { + return d + } + const percent = (bottom - d) / (0 - d) + const bottomObj = handleBottomObj(d, direction, percent, originColor, markLineColor) + return bottomObj + } else { + return d + } + }) + } + }) + } + // + if (iChartOption.markLine && type === 'range') { + handleSeries(iChartOption, baseOption, exclude, colors, direction) + } +} + +function placeFun(index, placeholderData) { + const a = { + name: 'Placeholder', + type: 'bar', + stack: `stack${index}`, + itemStyle: { + borderColor: chartToken.borderColor, + color: chartToken.color + }, + emphasis: { + itemStyle: { + borderColor: chartToken.borderColor, + color: chartToken.color + } + }, + data: placeholderData + } + return a +} + +// 针对区间图表需求,图表需要进行特殊处理 +export function setRange(baseOption, iChartOption) { + const type = iChartOption.type + if (type && type === 'range') { + const tempArray = [] + baseOption.series.forEach((item, index) => { + const barData = item.data + const barRealData = [] + const placeholderData = [] + const placeholder = placeFun(index, placeholderData) + barData.forEach((d) => { + placeholderData.push(d[0]) + barRealData.push(d[1] - d[0]) + }) + item.stack = `stack${index}` + item.data = barRealData + tempArray.push(placeholder) + tempArray.push(item) + }) + baseOption.series = tempArray + } +} + +// 针对瀑布图表需求,图表需要进行特殊处理 +export function setWaterFall(baseOption, iChartOption) { + const type = iChartOption.type + const totalName = iChartOption.totalName || 'Total' + const totalPosition = iChartOption.totalPosition || 'end' + if (type && type === 'water-fall') { + const tempArray = [] + baseOption.series.forEach((item, index) => { + const barData = item.data + const placeholderData = [0] + const placeholder = placeFun(index, placeholderData) + if (totalPosition === 'end') { + barData.forEach((d, i) => { + if (i < barData.length - 1) { + placeholderData.push((Number(d) || 0) + placeholderData[i]) + } + }) + placeholderData[placeholderData.length - 1] = 0 + } else { + barData.unshift(barData.pop()) + placeholderData[0] = barData[0] + barData.forEach((d, i) => { + if (i > 0) { + placeholderData.push(placeholderData[i - 1] - (Number(d) || 0)) + } + }) + placeholderData[0] = 0 + } + item.stack = `stack${index}` + tempArray.push(placeholder) + tempArray.push(item) + }) + if (totalPosition === 'end') { + baseOption.xAxis[0].data.push(totalName) + } else { + baseOption.xAxis[0].data.unshift(totalName) + } + baseOption.series = tempArray + } +} + +/** + * 为了实现一些特殊的样式,增加了一些series,如柱状图中的PlaceHolder series + * 因此在 tooltip 中应该被屏蔽这些series + * 因此对 tooltip.formatter 进行二次封装 + */ +export function setLimitFormatter(baseOption, iChartOption) { + const type = iChartOption.type + const toolTipFormatter = baseOption.tooltip.formatter + const exclude = ['Placeholder'] + const colors = baseOption.color + baseOption.tooltip.formatter = (params, ticket, callback) => { + const newParams = params.filter((item) => { + return !exclude.includes(item.seriesName) + }) + if (toolTipFormatter) { + return toolTipFormatter(newParams, ticket, callback) + } + let htmlString = '' + newParams.forEach((item, index) => { + if (index === 0) { + htmlString += `
${defendXSS(item.name)}
` + } + const itemColor = typeof item.color === 'string' ? item.color : getColor(colors, index) + htmlString += ` +
+ + + + ${defendXSS( + item.seriesName + )} + + ${defendXSS( + type === 'range' + ? `${`${`[${params[index * 2].value}`}-${params[index * 2].value + item.value}`}]` + : item.value + )} + + +
+ ` + }) + return htmlString + } +} + +function handleYaxis(series, yAxis) { + if (Array.isArray(yAxis)) { + yAxis.forEach((y, index) => { + series.forEach((s, indexs) => { + if (y.dataName && y.dataName.includes(s.name)) { + series[indexs].yAxisIndex = index + } + }) + }) + } +} diff --git a/packages/vue/src/charts-beta/chart-core/base/components/BarChart/handleVisualMap.ts b/packages/vue/src/charts-beta/chart-core/base/components/BarChart/handleVisualMap.ts new file mode 100644 index 000000000..bd9eed04f --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/BarChart/handleVisualMap.ts @@ -0,0 +1,37 @@ +import min from '../../util/sort/min' +import max from '../../util/sort/max' +import { getColor } from '../../util/color' +import chartToken from './chartToken' + +export function setVisualMap(legendData, seriesData, markLine, colors) { + const visualMap = [] + if (markLine) { + const topValue = markLine.top + const bottomValue = markLine.bottom + legendData.forEach((legendName, index) => { + const data = seriesData[legendName] + const minData = min(data) + const maxData = max(data) + const bottom = bottomValue || minData - 1 + const top = topValue || maxData + 1 + // 根据数据大小映射颜色 + visualMap.push({ + show: false, + type: 'piecewise', + dimension: 1, + seriesIndex: index, + pieces: [ + { + gt: bottom, + lt: top, + color: getColor(colors, index) + } + ], + outOfRange: { + color: chartToken.errorColor + } + }) + }) + } + return visualMap +} diff --git a/packages/vue/src/charts-beta/chart-core/base/components/BarChart/index.ts b/packages/vue/src/charts-beta/chart-core/base/components/BarChart/index.ts new file mode 100644 index 000000000..dca452d17 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/BarChart/index.ts @@ -0,0 +1,112 @@ +import LineChart from '../LineChart' +import init from '../../option/init' +import { event } from '../../util/event' +import cloneDeep from '../../util/cloneDeep' +import BaseOption from '../../util/baseOption' +import { mergeVisualMap, mergeSeries } from '../../util/merge' +import RectCoordSys from '../../option/RectSys' +import { setStack, setDirection, setDoubleSides } from './handleOptipn' +import { xkey, xdata, ldata, ydata } from '../../option/RectSys' + +import { setSeries, setRange, setMarkLine, setWaterFall, setLimitFormatter } from './handleSeries' + +class BarChart { + constructor(iChartOption, chartInstance) { + this.baseOption = {} + this.baseOption = cloneDeep(BaseOption) + this.chartInstance = chartInstance + // 组装 iChartOption, 补全默认值 + this.iChartOption = init(iChartOption) + // 根据 iChartOption 组装 baseOption + this.updateOption() + } + + updateOption() { + const theme = this.iChartOption.theme + const iChartOption = this.iChartOption + // 装载除series之外的其他配置 + RectCoordSys(this.baseOption, this.iChartOption, 'BarChart') + // x轴key值 + const xAxisKey = xkey(iChartOption) + // x轴数据 + const xAxisData = xdata(iChartOption.data, xAxisKey) + // 图例数据 + const legendData = ldata(iChartOption.data, xAxisKey) + // 连线的数据 + const seriesData = ydata(iChartOption.data, legendData) + // 赋值数据 + this.baseOption.legend.data = iChartOption.legend.data || legendData + this.baseOption.xAxis.forEach((item) => { + item.data = xAxisData + }) + this.baseOption.series = setSeries(seriesData, legendData, iChartOption) + // 给堆叠图的柱子中间加上空白缝隙,并覆盖图例的点击事件 + setStack(this.baseOption, iChartOption, legendData, seriesData) + // 针对数据均为正数的双向柱状图进行一些特殊处理 + setDoubleSides(this.baseOption, iChartOption) + // 针对瀑布图表需求,图表需要进行特殊处理 + setWaterFall(this.baseOption, iChartOption) + // 针对区间图表需求,图表需要进行特殊处理 + setRange(this.baseOption, iChartOption) + // 针对阈值线变色,图表需要进行特殊处理 + setMarkLine(this.baseOption, iChartOption) + // 设置柱状图的方向 + setDirection(this.baseOption, iChartOption.direction) + // 对 tooltip.formatter 进行二次封装 + setLimitFormatter(this.baseOption, iChartOption) + // 配置图表事件 + if (iChartOption.event) { + event(this.chartInstance, iChartOption.event) + } + // 是否关闭hover态的效果,默认为false + if (iChartOption.silent) { + this.baseOption.tooltip = {} + } + // 合并用户自定义series + mergeSeries(iChartOption, this.baseOption) + // 合并用户自定义visualMap + mergeVisualMap(iChartOption, this.baseOption) + console.log({ ...this.baseOption }, '==-====') + } + + // 根据渲染出的结果,二次计算option + updateOptionAgain(YAxiMax, YAxiMin) { + const baseOption = this.baseOption + const iChartOption = this.iChartOption + const lineDataName = iChartOption.lineDataName + // 折柱混合 + if (lineDataName && lineDataName.length > 0) { + const lineChartBaseOpt = new LineChart(iChartOption, {}, this.chartInstance) + const lineBaseOption = lineChartBaseOpt.getOption() + const lineSeries = lineBaseOption.series + const barSeries = baseOption.series + lineDataName.forEach((lineName) => { + let bar = null + let line = null + for (let i = 0; i < lineSeries.length; i++) { + if (lineSeries[i].name === lineName) { + line = lineSeries[i] + break + } + } + for (let i = 0; i < barSeries.length; i++) { + if (barSeries[i].name === lineName) { + bar = barSeries[i] + break + } + } + for (const key in line) { + bar[key] = line[key] + } + }) + } + } + + getOption() { + return this.baseOption + } + + setOption() {} +} + +export default BarChart diff --git a/packages/vue/src/charts-beta/chart-core/base/components/BaseChart/index.ts b/packages/vue/src/charts-beta/chart-core/base/components/BaseChart/index.ts new file mode 100644 index 000000000..e6d22a31c --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/BaseChart/index.ts @@ -0,0 +1,113 @@ +import { insertStateDom, removeStateDom } from '../../util/init/insert' +export default class BaseChart { + // 图表渲染容器 + dom + // 图表渲染配置 + option + + constructor() { + // 必须实现 init 方法,初始化图表渲染容器 + if (this.init === undefined) { + throw new Error('This chart has not overwrite "init" callback!') + } + // 必须实现 setSimpleOption 方法,初始化图表渲染配置 + if (this.setSimpleOption === undefined) { + throw new Error('This chart has not overwrite "setSimpleOption" callback!') + } + // 必须实现 render 方法,调用时开始渲染图表 + if (this.render === undefined) { + throw new Error('This chart has not overwrite "render" callback!') + } + // 必须实现 onRenderReady 方法 + if (this.onRenderReady === undefined) { + throw new Error('This chart has not overwrite "onRenderReady" callback!') + } + // 必须实现 refresh 方法,传入新的option,刷新图表 + if (this.refresh === undefined) { + throw new Error('This chart has not overwrite "refresh" callback!') + } + // 必须实现 refreshData 方法,传入新的data,刷新图表 + if (this.refreshData === undefined) { + throw new Error('This chart has not overwrite "refreshData" callback!') + } + // 必须实现 setResize 方法,该方法一般手动调用,实现图表自适应宽度 + if (this.setResize === undefined) { + throw new Error('This chart has not overwrite "setResize" callback!') + } + // 必须实现 uninstall 方法,清空图表容器,卸载所有监听事件 + if (this.uninstall === undefined) { + throw new Error('This chart has not overwrite "uninstall" callback!') + } + } + + // 加载状态 + showLoading(option) { + insertStateDom(this.dom, 'loading', option) + } + + hideLoading() { + removeStateDom(this.dom, 'loading') + } + + closeLoading() { + removeStateDom(this.dom, 'loading') + } + + // 错误状态 + showError(option) { + insertStateDom(this.dom, 'error', option) + } + + closeError() { + removeStateDom(this.dom, 'error') + } + + // 空数据状态 + showEmpty(option) { + insertStateDom(this.dom, 'empty', option) + } + + closeEmpty() { + removeStateDom(this.dom, 'empty') + } + + // 阶段空数据状态 + showStageEmpty(option) { + insertStateDom(this.dom, 'stage_empty', option) + } + + closeStageEmpty() { + removeStateDom(this.dom, 'stage_empty') + } + + // 自定义数据状态 + showState(option) { + insertStateDom(this.dom, 'custom', option) + } + + closeState() { + removeStateDom(this.dom, 'custom') + } + + // 传入自定义DOM + showCustomDom(callback) { + if (this.dom.getElementsByClassName('huicharts-custom-dom').length > 0) return + if (getComputedStyle(this.dom).position == ('static' || '')) { + this.dom.style.position = 'relative' + } + let customContainer = document.createElement('div') + customContainer.className = 'huicharts-custom-dom' + customContainer.setAttribute( + 'style', + 'position:absolute;width:100%;height:100%;top:0px;left:0px;display:flex;justify-content:center;align-items:center' + ) + this.dom.appendChild(customContainer) + callback(customContainer) + } + + // 删除自定义DOM + closeCustomDom() { + let customContainer = this.dom.getElementsByClassName('huicharts-custom-dom') + this.dom.removeChild(customContainer[0]) + } +} diff --git a/packages/vue/src/charts-beta/chart-core/base/components/LineChart/AreaChart/bottomArea.ts b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/AreaChart/bottomArea.ts new file mode 100644 index 000000000..d18f6092e --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/AreaChart/bottomArea.ts @@ -0,0 +1,173 @@ +import min from '../../../util/sort/min' +import { isNumber } from '../../../util/type' +import { defaultFormatter } from '../handleOptipn' +import { getColor, codeToRGB } from '../../../util/color' +import chartToken from './chartToken' + +/** + * 为series添加areaStyle + */ +function bottomArea(baseOption, iChartOption, YAxiMax) { + // 添加markLine的areaStyle + markLineArea(baseOption, iChartOption, YAxiMax) + // 添加split的areaStyle + splitArea(baseOption, iChartOption, YAxiMax) + // tooltip屏蔽假的同名Series + formatter(baseOption, iChartOption) +} + +function markLineArea(baseOption, iChartOption, YAxiMax) { + if ( + iChartOption.area && + iChartOption.markLine && + iChartOption.markLine.bottom && + isNumber(iChartOption.markLine.bottom) + ) { + const temp = [] + baseOption.series.forEach((item) => { + const bottomColor = codeToRGB(iChartOption.markLine.bottomColor, 0.15) || chartToken.errorColor + const minValue = min(item.data) + const percent = (iChartOption.markLine.bottom - minValue) / (YAxiMax - minValue) + if (iChartOption.markLine.bottom >= minValue) { + // 该series是为了实现红色特殊area的样式而加的,因此在tooltip中应该被屏蔽 + const newSeries = pureBottomArea(item, percent, bottomColor) + temp.push(newSeries) + } + }) + baseOption.series = baseOption.series.concat(temp) + } +} + +/** + * 为series添加split分割区域的底部areaStyle, + */ +function splitArea(baseOption, iChartOption, YAxiMax) { + if (iChartOption.area && iChartOption.splitLine) { + const temp = [] + const colors = baseOption.color + baseOption.series.forEach((item, index) => { + const minValue = min(item.data) + const percent = (iChartOption.splitLine - minValue) / (YAxiMax - minValue) + const color = getColor(colors, index) + const colorTo = codeToRGB(color, 0.15) + const colorFrom = codeToRGB(color, 0.15) + // 该series是为了实现红色特殊area的样式而加的,因此在tooltip中应该被屏蔽 + const newSeries = gradientBottomArea(item, percent, colorTo, colorFrom) + temp.push(newSeries) + }) + baseOption.series = baseOption.series.concat(temp) + } +} + +// 创建一个渐变色-同名的Series,用来显示分割渐变区域 +function gradientBottomArea(item, percent, colorTo, colorFrom) { + const newSeries = { + type: item.type, + name: item.name, + data: item.data, + smooth: item.smooth, + step: item.step, + lineStyle: { + width: 0 + }, + symbol: 'none', + areaStyle: { + origin: 'end', + color: { + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { + offset: 0, + color: chartToken.colorAreaTP + }, + { + offset: 1 - percent - 0.00001, + color: chartToken.colorAreaTP + }, + { + offset: 1 - percent, + color: colorTo + }, + { + offset: 1, + color: colorFrom + } + ] + } + } + } + return newSeries +} + +// 创建一个纯色-同名Series,用来显示红色阈值区域 +function pureBottomArea(itemx, percentx, bottomColorx) { + const seriesObj = { + type: itemx.type, + name: itemx.name, + data: itemx.data, + smooth: itemx.smooth, + step: itemx.step, + symbol: 'none', + areaStyle: { + color: { + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { + offset: 0, + color: chartToken.colorAreaTP + }, + { + offset: 1 - percentx - 0.00001, + color: chartToken.colorAreaTP + }, + { + offset: 1 - percentx, + color: bottomColorx + }, + { + offset: 1, + color: bottomColorx + } + ], + type: 'linear' + }, + origin: 'end' + }, + lineStyle: { + width: 0 + } + } + return seriesObj +} + +/** + * 面积图的bottom阈值区域功能是使用植入假的同名Series来实现的 + * 因此在 tooltip 中应该被屏蔽这些假的同名Series + */ +function formatter(baseOption, iChartOption) { + if (iChartOption.area) { + if ( + iChartOption.splitLine || + (iChartOption.markLine && iChartOption.markLine.bottom && isNumber(iChartOption.markLine.bottom)) + ) { + const dataLength = baseOption.legend.data.length + const tipFormatter = baseOption.tooltip.formatter + baseOption.tooltip.formatter = (params, ticket, callback) => { + if (tipFormatter) { + return tipFormatter(params.slice(0, dataLength), ticket, callback) + } else { + return defaultFormatter(params, dataLength) + } + } + } + } +} + +export default bottomArea diff --git a/packages/vue/src/charts-beta/chart-core/base/components/LineChart/AreaChart/chartToken.ts b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/AreaChart/chartToken.ts new file mode 100644 index 000000000..5627cbc9c --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/AreaChart/chartToken.ts @@ -0,0 +1,19 @@ +import Theme from '../../../feature/theme' +import proxy from '../../../util/proxy' + +function getChartToken() { + const { + areaStyle: { color: colorAreaTP } + } = Theme.config.AreaChart + + const { errorColor } = Theme.config.colorState + + return { + colorAreaTP, + errorColor + } +} + +const chartToken = proxy(getChartToken) + +export default chartToken diff --git a/packages/vue/src/charts-beta/chart-core/base/components/LineChart/AreaChart/index.ts b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/AreaChart/index.ts new file mode 100644 index 000000000..4368ea7ff --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/AreaChart/index.ts @@ -0,0 +1,4 @@ +import topArea from './topArea' +import bottomArea from './bottomArea' + +export { topArea, bottomArea } diff --git a/packages/vue/src/charts-beta/chart-core/base/components/LineChart/AreaChart/topArea.ts b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/AreaChart/topArea.ts new file mode 100644 index 000000000..574ebb7d2 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/AreaChart/topArea.ts @@ -0,0 +1,122 @@ +import * as echarts from 'echarts' +import max from '../../../util/sort/max' +import { isNumber } from '../../../util/type' +import { getColor, codeToRGB } from '../../../util/color' +import chartToken from './chartToken' + +/** + * 为series添加areaStyle + */ +function topArea(baseOption, iChartOption, YAxiMin) { + // 添加默认areaStyle + defaultArea(baseOption, iChartOption, YAxiMin) + // 添加markLine的areaStyle + markLineArea(baseOption, iChartOption, YAxiMin) + // 添加split的areaStyle + splitArea(baseOption, iChartOption, YAxiMin) +} + +function splitArea(baseOption, iChartOption, YAxiMin) { + if (iChartOption.area && iChartOption.splitLine) { + const colors = baseOption.color + const splitLine = iChartOption.splitLine + baseOption.series.forEach((item, index) => { + const maxValue = max(item.data) + const color = getColor(colors, index) + const colorTo = codeToRGB(color, 0.15) + const colorFrom = codeToRGB(color, 0.15) + const percent = (maxValue - splitLine) / (maxValue - YAxiMin) + item.areaStyle = { + color: { + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { offset: 0, color: colorFrom }, + { offset: percent, color: colorTo }, + { offset: percent + 0.00001, color: chartToken.colorAreaTP }, + { offset: 1, color: chartToken.colorAreaTP } + ] + } + } + }) + } +} + +function markLineArea(baseOption, iChartOption, YAxiMin) { + if ( + iChartOption.area && + iChartOption.markLine && + iChartOption.markLine.top && + isNumber(iChartOption.markLine.top) && + iChartOption.markLine.topChangeColor !== false + ) { + const colors = baseOption.color + const markLine = iChartOption.markLine + const topColor = codeToRGB(markLine.topColor, 0.15) || chartToken.errorColor + baseOption.series.forEach((item, index) => { + const maxValue = max(item.data) + const color = getColor(colors, index) + const colorTo = codeToRGB(color, 0) + const colorFrom = codeToRGB(color, 0.15) + const percent = (maxValue - markLine.top) / (maxValue - YAxiMin) + if (maxValue > markLine.top) { + item.areaStyle = { + color: { + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { + offset: 0, + color: topColor + }, + { + offset: percent, + color: topColor + }, + { + offset: percent + 0.00001, + color: colorFrom + }, + { + offset: 1, + color: colorTo + } + ] + } + } + } + }) + } +} + +function defaultArea(baseOption, iChartOption, YAxiMin) { + if (iChartOption.area) { + const colors = baseOption.color + baseOption.series.forEach((item, index) => { + const color = getColor(colors, index) + const colorTo = codeToRGB(color, 0) + const colorFrom = codeToRGB(color, 0.15) + item.areaStyle = { + opacity: 1, + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: colorFrom + }, + { + offset: 1, + color: colorTo + } + ]) + } + }) + } +} + +export default topArea diff --git a/packages/vue/src/charts-beta/chart-core/base/components/LineChart/chartToken.ts b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/chartToken.ts new file mode 100644 index 000000000..759fe31d3 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/chartToken.ts @@ -0,0 +1,37 @@ +import Theme from '../../feature/theme' +import proxy from '../../util/proxy' + +const getChartToken = () => { + const { + symbolSize: { symbolSizeXS, symbolSize }, + itemStyle: { borderZero, borderLG, border, borderColor }, + lineStyle: { lineWidthLG, lineWidth }, + markLine: { + lineStyle: { color } + } + } = Theme.config.LineChart + + const { errorColor } = Theme.config.colorState + + const { tooltipFontColor, visualMapPiecesColor, visualMapDashColor } = Theme.config + + return { + symbolSizeXS, + borderZero, + lineWidthLG, + symbolSize, + borderLG, + lineWidth, + color, + border, + borderColor, + errorColor, + tooltipFontColor, + visualMapPiecesColor, + visualMapDashColor + } +} + +const chartToken = proxy(getChartToken) + +export default chartToken diff --git a/packages/vue/src/charts-beta/chart-core/base/components/LineChart/handleOptipn.ts b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/handleOptipn.ts new file mode 100644 index 000000000..e74d79140 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/handleOptipn.ts @@ -0,0 +1,92 @@ +import cloneDeep from '../../util/cloneDeep' +import defendXSS from '../../util/defendXSS' +import chartToken from './chartToken' + +// 给图例和x轴赋值 +export function handleData(baseOpt, legendData, xAxisData) { + if (!baseOpt.legend.data) { + baseOpt.legend.data = legendData + } + baseOpt.xAxis.forEach((item) => { + item.data = xAxisData + }) +} + +export function onlyOnePoint(baseOption) { + baseOption.series.forEach((item) => { + if (item.data.length === 1) { + item.showSymbol = true + } + }) +} + +// 针对离散数据, 创建同名Series, 显示离散数据的单个点 +export function discrete(iChartOption, baseOption) { + if (iChartOption.discrete) { + // 创建同名Series + baseOption.series.forEach((series) => { + const newSeries = cloneDeep(series) + newSeries.symbol = 'circle' + newSeries.symbolSize = chartToken.symbolSizeXS + newSeries.itemStyle.borderWidth = chartToken.borderZero + newSeries.showSymbol = true + newSeries.showAllSymbol = true + newSeries.emphasis = { + itemStyle: { + opacity: 0 + } + } + const discreteData = [] + for (let index = 0; index < newSeries.data.length; index++) { + const pre = newSeries.data[index - 1] + const next = newSeries.data[index] + const cur = newSeries.data[index + 1] + if (!isNullValue(pre) || !isNullValue(cur)) { + discreteData.push(null) + } else { + discreteData.push(next) + } + } + newSeries.data = discreteData + baseOption.series.push(newSeries) + }) + // 覆盖tipHtml,过滤同名Series + const dataLength = baseOption.legend.data.length + const tipFormatter = baseOption.tooltip.formatter + baseOption.tooltip.formatter = (params, ticket, callback) => { + if (tipFormatter) { + return tipFormatter(params.slice(0, dataLength), ticket, callback) + } else { + return defaultFormatter(params, dataLength) + } + } + } +} + +function isNullValue(value) { + return value === '' || value === undefined || value === null +} + +export function defaultFormatter(params, dataLength) { + let htmlString = '' + params.forEach((item, index) => { + // 只显示实线数据的tooltip + if (index < dataLength) { + if (index === 0) { + htmlString += `
${defendXSS(item.name)}
` + } + htmlString += `
+ + ${defendXSS( + item.seriesName + )} + ${defendXSS(item.value)} + +
` + } + }) + return htmlString +} diff --git a/packages/vue/src/charts-beta/chart-core/base/components/LineChart/handlePredict.ts b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/handlePredict.ts new file mode 100644 index 000000000..986f86d49 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/handlePredict.ts @@ -0,0 +1,100 @@ +import cloneDeep from '../../util/cloneDeep' +import defendXSS from '../../util/defendXSS' +import chartToken from './chartToken' + +function setDashedLineVisualMap(seriesIndex, lineColor, predictIndex) { + const vm = { + type: 'piecewise', + seriesIndex, + dimension: 0, + show: false, + pieces: [ + { + gte: 0, + lte: predictIndex, + color: chartToken.visualMapPiecesColor + }, + { + gt: predictIndex, + color: lineColor + } + ] + } + return vm +} + +// htmlString中判断item.color.colorStops是判读是否为折柱混合的图表,如果是需要从item.color.colorStops对象中获取颜色 +function setToolTip(dataLength, fontColor, selfFormatter) { + if (selfFormatter) { + return selfFormatter + } + const tipHtml = (params) => { + let htmlString = '' + params.forEach((item, index) => { + // 只显示实线数据的tooltip + if (index < dataLength) { + if (index === 0) { + htmlString += `${defendXSS(item.name)}
` + } + const { colorStops: color_ } = item.color + htmlString += ` +
+ + + + ${defendXSS(item.seriesName)} + ${defendXSS(item.value)} + +
+ ` + } + }) + return htmlString + } + return tipHtml +} + +/** + * 针对预测值图表需求,图表需要进行特殊处理 + */ +export function handlePredict(option, predict, tipHtml, lineStyle) { + if (predict) { + // 取出数据 + const data = option.series + const dataLength = data.length + const xAxisDataLength = option.xAxis[0].data.length + const predictIndex = option.xAxis[0].data.indexOf(predict) + const colorBase = Theme.color.base + const fontColor = colorBase.font + // 制作虚线的series(只有匹配成功即predictIndex>-1时,才设置阈值线的样式) + if (predictIndex > -1) { + for (let index = 0; index < dataLength; index++) { + const temp = cloneDeep(data[index]) + temp.lineStyle = { + width: chartToken.lineWidthLG, + type: [5, 8] + } + temp.itemStyle = { + opacity: 0 + } + temp.silent = true + temp.showSymbol = false + temp.showAllSymbol = false + // 插入虚线的series + option.series.push(temp) + // VisualMap只能处理线的颜色,不能处理面积的颜色 + let dashColor = chartToken.visualMapDashColor + if (lineStyle && lineStyle.dashColor) { + dashColor = lineStyle.dashColor + } + // 虚线颜色 + option.visualMap.push(setDashedLineVisualMap(dataLength + index, dashColor, predictIndex, xAxisDataLength)) + } + } + // 修改tooltip,不显示虚线的tooltip + option.tooltip.formatter = setToolTip(dataLength, chartToken.tooltipFontColor, tipHtml) + } +} diff --git a/packages/vue/src/charts-beta/chart-core/base/components/LineChart/handleSeries.ts b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/handleSeries.ts new file mode 100644 index 000000000..d0151b303 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/handleSeries.ts @@ -0,0 +1,232 @@ +import cloneDeep from '../../util/cloneDeep' +import { isString, isObject } from '../../util/type' +import { getMarkLineDefault, getMarkPointDefault } from '../../option/config/mark' +import chartToken from './chartToken' + +export const seriesInit = () => { + return { + label: { show: false }, + // 连线上的小圆点样式 + symbol: 'emptyCircle', + symbolSize: chartToken.symbolSize, + showSymbol: false, + // 数据 + data: [], + // 线形 + type: 'line', + // 线条样式 + lineStyle: { + width: chartToken.lineWidth + }, + // 折线阶梯 + step: false, + // 折线平滑 + smooth: false, + // 阈值线 + markLine: null, + // 峰值标志 + markPoint: null, + // 折线点的每个样式配置项 + itemStyle: {} + } +} + +/** + * 组装echarts所需要的series + */ +export function setSeries(params) { + const { yAxis, theme, itemStyle } = params + // 防止同一个页面的不同lineChart之间出现样式串通 + const localSeriesUnit = cloneDeep(seriesInit()) + // 根据不同的theme,生成不同的itemStyle + handleItemStyleWithTheme(localSeriesUnit, theme) + // 覆盖用户传入的itemStyle + handleItemStyle(localSeriesUnit, itemStyle) + // 拼装series + const series = [] + handleSeries({ ...params, series, localSeriesUnit }) + // 配置多个series的y轴index + handleYaxis(series, yAxis) + return series +} + +function handleItemStyleWithTheme(seriesUnit, theme) { + if (theme.toLowerCase().includes('cloud-light')) { + seriesUnit.symbol = 'emptyCircle' + seriesUnit.itemStyle = {} + } else { + seriesUnit.symbol = 'circle' + seriesUnit.itemStyle = { + shadowBlur: 2, + borderWidth: chartToken.border, + borderColor: chartToken.borderColor, + shadowColor: 'rgba(0, 0, 0, .2)' + } + } +} + +function handleItemStyle(seriesUnit, itemStyle) { + if (!itemStyle) return + // 兼容老版本的 itemStyle.symbolSize + if (itemStyle.symbolSize) { + seriesUnit.symbolSize = itemStyle.symbolSize + } + for (const key in itemStyle) { + seriesUnit.itemStyle[key] = itemStyle[key] + } +} + +function handleSeries(params) { + const { + theme, + stack, + focus, + colors, + isStep, + isArea, + series, + isSmooth, + markLine, + markPoint, + splitLine, + legendData, + seriesData, + localSeriesUnit + } = params + legendData.forEach((legend, index) => { + const seriesUnit = cloneDeep(localSeriesUnit) + // 折线平滑 + seriesUnit.smooth = isSmooth || false + // 折线阶梯 + seriesUnit.step = (isStep && 'end') || false + // 阈值线 + markLine && handleMarkLine(markLine, seriesUnit, theme, legend) + // 峰值标记 + markPoint && handleMarkPoint(markPoint, seriesUnit, theme, legend) + // 聚焦效果 + focus && handleFocus(focus, seriesUnit) + // 数据 / 数据名称 + seriesUnit.name = legend + seriesUnit.data = seriesData[legend] + // 堆叠效果 + stack && handleStack(stack, seriesUnit) + series.push(seriesUnit) + }) +} + +function handleYaxis(series, yAxis) { + if (Array.isArray(yAxis)) { + yAxis.forEach((y, index) => { + series.forEach((s, indexs) => { + if (y.dataName && y.dataName.includes(s.name)) { + series[indexs].yAxisIndex = index + } + }) + }) + } +} + +function handleMarkLine(markLine, seriesUnit, theme, seriesName) { + seriesUnit.markLine = cloneDeep(getMarkLineDefault()) + if (markLine.top && !(markLine.topUse && !markLine.topUse.includes(seriesName))) { + var markLineData = {} + if (isString(markLine.top)) { + markLineData = { type: markLine.top } + } else { + markLineData = { yAxis: markLine.top } + } + markLineData.label = { show: false, position: 'insideEndTop' } + markLineData.lineStyle = {} + markLine.topPosition && (markLineData.label.position = markLine.topPosition || 'insideStartTop') + markLine.topLabel && (markLineData.label.show = true) + markLine.topLabel && (markLineData.label.formatter = markLine.topLabel) + if (!markLine.topColor) { + markLine.topColor = chartToken.errorColor + } + if (markLine.topColor === 'auto') { + markLine.topColor = undefined + } + markLine.topColor && (markLineData.label.color = markLine.topColor) + markLine.topColor && (markLineData.lineStyle.color = markLine.topColor) + if (markLine.topLine === false) { + markLineData.lineStyle.color = chartToken.color + } + seriesUnit.markLine.data.push(markLineData) + } + if (markLine.bottom && !(markLine.bottomUse && !markLine.bottomUse.includes(seriesName))) { + var markLineData = {} + if (isString(markLine.bottom)) { + markLineData = { type: markLine.bottom } + } else { + markLineData = { yAxis: markLine.bottom } + } + markLineData.label = { show: false, position: 'insideEndTop' } + markLineData.lineStyle = {} + markLine.bottomPosition && (markLineData.label.position = markLine.bottomPosition || 'insideStartTop') + markLine.bottomLabel && (markLineData.label.show = true) + markLine.bottomLabel && (markLineData.label.formatter = markLine.bottomLabel) + if (!markLine.bottomColor) { + markLine.bottomColor = chartToken.errorColor + } + if (markLine.bottomColor === 'auto') { + markLine.bottomColor = undefined + } + markLine.bottomColor && (markLineData.label.color = markLine.bottomColor) + markLine.bottomColor && (markLineData.lineStyle.color = markLine.bottomColor) + if (markLine.bottomLine === false) { + markLineData.lineStyle.color = chartToken.color + } + seriesUnit.markLine.data.push(markLineData) + } +} + +function handleMarkPoint(markPoint, seriesUnit, theme, seriesName) { + seriesUnit.markPoint = cloneDeep(getMarkPointDefault()) + if (markPoint.max && !(markPoint.maxUse && !markPoint.maxUse.includes(seriesName))) { + const max = { + type: 'max', + symbolOffset: [0, -11], + itemStyle: { color: markPoint.maxColor || chartToken.errorColor } + } + if (markPoint.maxColor == 'auto') delete max.itemStyle + seriesUnit.markPoint.data.push(max) + } + if (markPoint.min && !(markPoint.minUse && !markPoint.minUse.includes(seriesName))) { + const min = { + type: 'min', + symbolOffset: [0, 11], + symbolRotate: 180, + itemStyle: { color: markPoint.minColor || chartToken.errorColor } + } + if (markPoint.minColor == 'auto') delete min.itemStyle + seriesUnit.markPoint.data.push(min) + } +} + +function handleStack(stack, seriesUnit) { + if (stack === true) { + seriesUnit.stack = 'Total' + return + } + if (isObject(stack)) { + for (const name in stack) { + if (Object.hasOwnProperty.call(stack, name)) { + const stackArray = stack[name] + const seriesName = seriesUnit.name + if (stackArray.includes(seriesName)) { + seriesUnit.stack = name + } + break + } + } + } +} + +function handleFocus(focus, seriesUnit) { + if (focus) { + seriesUnit.emphasis = { + focus: 'series', + blurScope: 'global' + } + } +} diff --git a/packages/vue/src/charts-beta/chart-core/base/components/LineChart/handleVisualMap.ts b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/handleVisualMap.ts new file mode 100644 index 000000000..fd63b2574 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/handleVisualMap.ts @@ -0,0 +1,84 @@ +import min from '../../util/sort/min' +import max from '../../util/sort/max' +import { isNumber } from '../../util/type' +import { getColor } from '../../util/color' +import chartToken from './chartToken' + +export function setVisualMap(legendData, seriesData, markLine, colors, theme) { + const visualMap = [] + if (markLine) { + let topValue = markLine.top + let bottomValue = markLine.bottom + const vmColor = chartToken.errorColor + const topColor = markLine.topColor || vmColor + const bottomColor = markLine.bottomColor || vmColor + if (!isNumber(topValue)) { + topValue = undefined + } + if (!isNumber(bottomValue)) { + bottomValue = undefined + } + if (topValue === undefined && bottomValue === undefined) { + return visualMap + } + if (topValue !== undefined && bottomValue !== undefined && bottomValue >= topValue) { + throw new Error('阈值线bottom的值必须小于阈值线top的值') + } + legendData.forEach((legendName, index) => { + const data = seriesData[legendName] + const minData = min(data) + const maxData = max(data) + let bottom = bottomValue + let top = topValue + if (markLine.topUse && !markLine.topUse.includes(legendName)) { + top = undefined + } + if (markLine.bottomUse && !markLine.bottomUse.includes(legendName)) { + bottom = undefined + } + if (top === undefined && bottom === undefined) { + return + } + // 阈值无下限 + if (bottom === undefined) { + bottom = Math.min(top - 0.01, minData - 0.01) + } + // 阈值无上限 + if (top === undefined) { + top = Math.max(bottom + 0.01, maxData + 0.01) + } + // 根据数据大小映射颜色 + const visualMapItem = handleVisualMapItem({ index, topColor, top, bottom, colors, bottomColor, vmColor }) + visualMap.push(visualMapItem) + }) + } + return visualMap +} + +function handleVisualMapItem({ index, topColor, top, bottom, colors, bottomColor, vmColor }) { + const visualMapItem = { + show: false, + type: 'piecewise', + dimension: 1, + seriesIndex: index, + pieces: [ + { + gte: top, // 大于 top 的 + color: topColor + }, + { + gt: bottom, + lt: top, // 小于 top, 大于 bottom 的,为正常颜色 + color: getColor(colors, index) + }, + { + lte: bottom, // 小于 bottom + color: bottomColor + } + ], + outOfRange: { + color: vmColor + } + } + return visualMapItem +} diff --git a/packages/vue/src/charts-beta/chart-core/base/components/LineChart/index.ts b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/index.ts new file mode 100644 index 000000000..98e19a2b5 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/LineChart/index.ts @@ -0,0 +1,104 @@ +import init from '../../option/init' +import mini from '../../feature/mini' +import { event } from '../../util/event' +import { setSeries } from './handleSeries' +import cloneDeep from '../../util/cloneDeep' +import BaseOption from '../../util/baseOption' +import { setVisualMap } from './handleVisualMap' +import { handlePredict } from './handlePredict' +import { topArea, bottomArea } from './AreaChart' +import { mergeVisualMap, mergeSeries } from '../../util/merge' +import { handleData, onlyOnePoint, discrete } from './handleOptipn' +import RectCoordSys from '../../option/RectSys' +import { xkey, xdata, ldata, ydata } from '../../option/RectSys' + +class LineChart { + constructor(iChartOption, chartInstance) { + this.baseOption = {} + this.baseOption = cloneDeep(BaseOption) + this.iChartOption = {} + // 组装 iChartOption, 补全默认值 + this.iChartOption = init(iChartOption) + // 根据 iChartOption 组装 baseOption + this.updateOption(chartInstance) + } + + updateOption(chartInstance) { + const theme = this.iChartOption.theme + const iChartOption = this.iChartOption + // 装载除series之外的其他配置 + RectCoordSys(this.baseOption, this.iChartOption, 'LineChart') + // x轴key值 + const xAxisKey = xkey(iChartOption) + // x轴数据 + const xAxisData = xdata(iChartOption.data, xAxisKey) + // 图例数据 + const legendData = ldata(iChartOption.data, xAxisKey) + // 连线的数据 + const seriesData = ydata(iChartOption.data, legendData) + // 给图例和x轴赋值 + handleData(this.baseOption, legendData, xAxisData) + // 组装series + this.baseOption.series = setSeries({ + theme, + seriesData, + legendData, + yAxis: iChartOption.yAxis, + focus: iChartOption.focus, + stack: iChartOption.stack, + isStep: iChartOption.step, + isArea: iChartOption.area, + colors: this.baseOption.color, + isSmooth: iChartOption.smooth, + markLine: iChartOption.markLine, + markPoint: iChartOption.markPoint, + splitLine: iChartOption.splitLine, + labelHtml: iChartOption.labelHtml, + itemStyle: iChartOption.itemStyle + }) + // 设置VisualMap,通过数值映射颜色 + this.baseOption.visualMap = setVisualMap( + legendData, + seriesData, + iChartOption.markLine, + iChartOption.color, + iChartOption.theme + ) + // 针对预测值图表需求,图表需要进行特殊处理 + handlePredict(this.baseOption, iChartOption.predict, iChartOption.tipHtml, iChartOption.lineStyle) + // 配置图表事件 + if (iChartOption.event) { + event(chartInstance, iChartOption.event) + } + // 是否关闭hover态的效果,默认为false + if (iChartOption.silent) { + this.baseOption.tooltip = {} + } + // 动效时长 + this.baseOption.animationDuration = this.iChartOption.animationDuration || 1000 + // 当数据只有一条时,显示数据点 + onlyOnePoint(this.baseOption) + // 针对离散数据, 创建同名Series, 显示离散数据的单个点 + discrete(iChartOption, this.baseOption) + // 合并用户自定义series + mergeSeries(iChartOption, this.baseOption) + // 合并用户自定义visualMap + mergeVisualMap(iChartOption, this.baseOption) + // 处理特性 + mini(iChartOption, this.baseOption) + } + + // 根据渲染出的结果,二次计算option + updateOptionAgain(YAxiMax, YAxiMin) { + // 面积图上部红色阈值区域需要在二次计算中实现 -- 在原有Series上添加areaStyle + topArea(this.baseOption, this.iChartOption, YAxiMin) + // 面积图下部红色阈值区域需要在二次计算中实现 -- 植入假的同名Series + bottomArea(this.baseOption, this.iChartOption, YAxiMax) + } + + getOption() { + return this.baseOption + } +} + +export default LineChart diff --git a/packages/vue/src/charts-beta/chart-core/base/components/PieChart/chartToken.ts b/packages/vue/src/charts-beta/chart-core/base/components/PieChart/chartToken.ts new file mode 100644 index 000000000..f66b83a57 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/PieChart/chartToken.ts @@ -0,0 +1,27 @@ +import Theme from '../../feature/theme' +import proxy from '../../util/proxy' + +function getChartToken() { + const { + itemStyle: { borderWidth, borderColor, borderWidthZero, borderRadius }, + label: { distance, color: labelColor, fontSize }, + lineStyle: { color: lineColor }, + emptyCircleStyle: { color: emptyColor } + } = Theme.config.PieChart + + return { + borderWidth, + borderColor, + borderWidthZero, + distance, + labelColor, + lineColor, + emptyColor, + fontSize, + borderRadius + } +} + +const chartToken = proxy(getChartToken) + +export default chartToken diff --git a/packages/vue/src/charts-beta/chart-core/base/components/PieChart/handleMulti.ts b/packages/vue/src/charts-beta/chart-core/base/components/PieChart/handleMulti.ts new file mode 100644 index 000000000..a6afa6326 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/PieChart/handleMulti.ts @@ -0,0 +1,119 @@ +import { getColor, codeToRGB, changeRgbaOpacity } from '../../util/color' +import cloneDeep from '../../util/cloneDeep' + +function handleResObj(item, colors, index) { + const resObj = cloneDeep(item) + resObj.color = { + rgba: codeToRGB(getColor(colors, index), 1), + from: 1, + to: 0.2 + } + return resObj +} + +function handleTempLegend(tempLegend, legendOffset, index) { + if (tempLegend.orient === 'horizontal') { + const offset = legendOffset || 30 + const bottom = parseFloat(tempLegend.bottom) - index * parseFloat(offset) + if (tempLegend.bottom.toString().includes('%')) { + tempLegend.bottom = `${bottom}%` + } else { + tempLegend.bottom = bottom + } + } else { + const offset = legendOffset || 120 + const left = parseFloat(tempLegend.left) + index * parseFloat(offset) + if (tempLegend.left.toString().includes('%')) { + tempLegend.left = `${left}%` + } else { + tempLegend.left = left + } + } +} + +function handleTempSeriesObj(item) { + const tempSeriesObj = cloneDeep(item) + tempSeriesObj.itemStyle = { + color: item.color.rgba + } + return tempSeriesObj +} + +/** + * 递归遍历数据 + * @param {*} data + * @param {*} innerData + * @param {*} innerIndex + */ +function installInnerData(data, innerData, innerIndex) { + data.children && + data.children.forEach((citem, cindex) => { + if (!innerData[innerIndex]) { + innerData[innerIndex] = [] + } + const colorFrom = + data.color.from - ((data.color.from - data.color.to) / (data.children.length + 1)) * (cindex + 1) + const colorTo = data.color.from - ((data.color.from - data.color.to) / (data.children.length + 1)) * (cindex + 2) + citem.color = { + rgba: changeRgbaOpacity(data.color.rgba, colorFrom), + from: colorFrom, + to: colorTo + } + innerData[innerIndex].push(citem) + // 如果还有子层,则继续递归遍历 + if (citem.children) { + installInnerData(citem, innerData, innerIndex + 1) + } + }) +} + +// 针对多重圆环图表需求,图表需要进行特殊处理 +function handleMulti(type, baseOption, legend, data) { + if (type === 'multi-circle') { + const colors = baseOption.color + // 给源数据添加颜色属性 + const outer = data.map((item, index) => { + const resObj = handleResObj(item, colors, index) + return resObj + }) + // 组装子层数据,给每个子数据赋值颜色 + const inner = [] + const innerDiff = 6 + const innerIndex = 0 + outer.forEach((data) => { + installInnerData(data, inner, innerIndex) + }) + // 组装series + inner.forEach((innerData, innerIndex) => { + const tempSeries = cloneDeep(baseOption.series[0]) + tempSeries.data = innerData.map((item) => { + const tempSeriesObj = handleTempSeriesObj(item) + return tempSeriesObj + }) + tempSeries.radius = tempSeries.radius.map((item) => { + return `${parseFloat(item) + innerDiff * (innerIndex + 1)}%` + }) + baseOption.series.push(tempSeries) + }) + baseOption.series.forEach((i) => { + i.label = { show: false } + i.labelLine = { show: false } + }) + // 组装legend + const dataArray = [outer].concat(inner) + const originLegend = baseOption.legend + const legendOffset = legend.offset + baseOption.legend = [] + dataArray.forEach((array, index) => { + const tempLegend = cloneDeep(originLegend) + const tempLegendData = array.map((item) => { + return item.name + }) + tempLegend.data = tempLegendData + handleTempLegend(tempLegend, legendOffset, index) + baseOption.legend.push(tempLegend) + }) + } +} + +export default handleMulti diff --git a/packages/vue/src/charts-beta/chart-core/base/components/PieChart/handleSeries.ts b/packages/vue/src/charts-beta/chart-core/base/components/PieChart/handleSeries.ts new file mode 100644 index 000000000..efcca1a56 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/PieChart/handleSeries.ts @@ -0,0 +1,245 @@ +import merge from '../../util/merge' +import cloneDeep from '../../util/cloneDeep' +import chartToken from './chartToken' + +export const seriesInit = () => { + return { + type: 'pie', + roundCap: true, + radius: ['0%', '50%'], + center: ['50%', '50%'], + avoidLabelOverlap: true, + itemStyle: { + borderWidth: chartToken.borderWidth, + borderColor: chartToken.borderColor, + borderRadius: chartToken.borderRadius + }, + selectedMode: false, + roseType: false, + label: {}, + labelLine: {}, + data: [] + } +} + +function handleHasLabel(hasLabel, seriesUnit, theme) { + if (hasLabel) { + seriesUnit.label = merge({ color: chartToken.labelColor, fontSize: chartToken.fontSize }, seriesUnit.label) + } else { + seriesUnit.label = { show: false } + } +} + +function handleHasLabelLine(hasLabelLine, seriesUnit, label, theme) { + const lineColor = label?.lineColor + const lineLength = + label !== undefined ? (label.distance !== undefined ? label.distance : chartToken.distance) : chartToken.distance + if (hasLabelLine) { + seriesUnit.labelLine = merge( + { + show: true, + lineStyle: { + color: lineColor || chartToken.lineColor + }, + smooth: 0.3, + length: lineLength, + length2: lineLength + }, + seriesUnit.labelLine + ) + } else { + seriesUnit.labelLine = { + show: false, + length: lineLength, + length2: lineLength + } + } +} + +function hasLabelFormatterFun(labelFormatterType, seriesUnit, sum) { + switch (labelFormatterType) { + case 'percent': + seriesUnit.label.formatter = (params) => { + if (params.value === 0) { + return '0(0 %)' + } else { + return `${params.value}(${Math.round(((params.value * 100) / sum) * 100) / 100} %)` + } + } + break + case 'value': + seriesUnit.label.formatter = (params) => { + return `${params.value}` + } + break + default: + break + } +} + +function handleHasLabelFormatter(hasLabel, hasLabelFormatter, seriesUnit, labelFormatterType, sum) { + if (hasLabel && hasLabelFormatter) { + seriesUnit.label.formatter = hasLabelFormatter + } else { + hasLabelFormatterFun(labelFormatterType, seriesUnit, sum) + } +} + +/** + * 配置圆盘图的label + */ +function setLabel(theme, seriesUnit, label, data) { + const hasLabel = !(label && label.show === false) + const hasLabelLine = !(label && label.line === false) + const hasLabelFormatter = label && label.labelHtml + const labelFormatterType = (label && label.type) || '' + let sum = data.reduce((x, y) => ({ value: x.value + y.value }), { value: 0 }) + sum = sum.value + handleHasLabel(hasLabel, seriesUnit, theme, label) + handleHasLabelLine(hasLabelLine, seriesUnit, label, theme) + handleHasLabelFormatter(hasLabel, hasLabelFormatter, seriesUnit, labelFormatterType, sum) + // 合并label其他属性 + merge(seriesUnit.label, label) +} + +/** + * 根据参数计算出圆盘图的半径 + */ +function setPieRadius(pieType, radius) { + if (radius) { + return radius + } else { + let radius = [] + switch (pieType) { + case 'pie': + radius = ['0%', '50%'] + break + case 'circle': + radius = ['44%', '50%'] + break + case 'multi-circle': + // 待修改 + radius = ['44%', '50%'] + break + default: + radius = ['0%', '50%'] + break + } + return radius + } +} + +/** + * 数据为零时添加背景 + */ +function handleEmptyData(data, series, theme, center, radius) { + const total = data.reduce((pre, cur) => { + pre = pre + cur.value + return pre + }, 0) + if (total === 0) { + series.forEach((item) => { + item.stillShowZeroSum = false + item.itemStyle.borderWidth = chartToken.borderWidthZero + }) + series.push({ + type: 'pie', + radius, + center, + label: { + show: false + }, + emptyCircleStyle: { + color: chartToken.emptyColor + }, + silent: true, + animation: false + }) + } +} + +// 合并默认值到series +function mergeDefaultSeries(seriesUnit) { + for (const key in seriesInit()) { + if (Object.hasOwnProperty.call(seriesInit(), key)) { + if (key === 'itemStyle') { + const series = cloneDeep(seriesInit()) + seriesUnit[key] = merge(series.itemStyle, seriesUnit.itemStyle) + } + if (seriesUnit[key] === undefined) { + seriesUnit[key] = seriesInit()[key] + } + } + } +} + +/** + * 组装echarts所需要的series + * @param {主题} theme + * @param {数据} data + * @returns + */ +const config = [ + 'label', + 'labelLine', + 'itemStyle', + 'radius', + 'center', + 'silent', + 'minAngle', + 'emphasis', + 'stillShowZeroSum', + 'selectedMode', + 'roseType' +] + +function handleSeries(pieType, theme, iChartOption, position) { + const { data, itemStyle, stillShowZeroSum } = iChartOption + position = position || {} + iChartOption.center = position?.center + iChartOption.radius = position?.radius + + // 更改扇面边框样式 + if (itemStyle && itemStyle.borderColor) { + seriesInit().itemStyle.borderColor = itemStyle.borderColor + } else { + seriesInit().itemStyle.borderColor = chartToken.borderColor + } + if (itemStyle && itemStyle.borderRadius) { + seriesInit().itemStyle.borderRadius = itemStyle.borderRadius + } + if (itemStyle && itemStyle.borderWidth) { + seriesInit().itemStyle.borderWidth = itemStyle.borderWidth + } + + // 组装数据 + let series = [] + let selfSeries = iChartOption.series + if (selfSeries === undefined) { + selfSeries = [{}] + } + selfSeries.forEach((seriesItem) => { + const seriesUnit = seriesItem + const temp = cloneDeep(iChartOption) + // 处理属性的优先级 + config.forEach((name) => { + const existValue = merge(temp[name], seriesUnit[name]) + if (existValue !== undefined) { + seriesUnit[name] = existValue + } + }) + seriesUnit.data = seriesUnit.data || iChartOption.data + seriesUnit.radius = setPieRadius(pieType, seriesUnit.radius) + setLabel(theme, seriesUnit, seriesUnit.label, seriesUnit.data) + // 默认样式合并 + mergeDefaultSeries(seriesUnit) + }) + // 数据和为0时不显示扇区 数据和为0时series属性都是一级 + if (stillShowZeroSum === false) { + handleEmptyData(data, selfSeries, theme, selfSeries[0].center, selfSeries[0].radius) + } + series = selfSeries + return series +} + +export default handleSeries diff --git a/packages/vue/src/charts-beta/chart-core/base/components/PieChart/index.ts b/packages/vue/src/charts-beta/chart-core/base/components/PieChart/index.ts new file mode 100644 index 000000000..4be60d5f8 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/PieChart/index.ts @@ -0,0 +1,46 @@ +import init from '../../option/init' +import { event } from '../../util/event' +import handleMulti from './handleMulti' +import handleSeries from './handleSeries' +import PolarCoordSys from '../../option/PolarSys' + +class PieChart { + constructor(iChartOption, chartInstance) { + this.baseOption = {} + this.iChartOption = {} + // 组装 iChartOption, 补全默认值 + this.iChartOption = init(iChartOption) + // 根据 iChartOption 组装 baseOption + this.updateOption(chartInstance) + } + + updateOption(chartInstance) { + const iChartOption = this.iChartOption + const theme = iChartOption.theme + const type = iChartOption.type || 'circle' + // 装载除series之外的其他配置 + PolarCoordSys(this.baseOption, this.iChartOption, 'PieChart') + // 兼容旧属性chartPosition + const position = iChartOption.position || iChartOption.chartPosition + // 赋值数据 + this.baseOption.series = handleSeries(type, theme, iChartOption, position) + // 针对多重圆环图表需求,图表需要进行特殊处理 + handleMulti(type, this.baseOption, iChartOption.legend, iChartOption.data) + // 配置图表事件 + if (iChartOption.event) { + event(chartInstance, iChartOption.event) + } + // 是否关闭hover态的效果,默认为false + if (iChartOption.silent) { + this.baseOption.tooltip = {} + } + } + + getOption() { + return this.baseOption + } + + setOption() {} +} + +export default PieChart diff --git a/packages/vue/src/charts-beta/chart-core/base/components/RadarChart/BaseOption.ts b/packages/vue/src/charts-beta/chart-core/base/components/RadarChart/BaseOption.ts new file mode 100644 index 000000000..b4525af7b --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/RadarChart/BaseOption.ts @@ -0,0 +1,11 @@ +import BaseOption from '../../util/baseOption' + +BaseOption.tooltip = { + show: true, + trigger: 'item', + formatter: '{b}: {c} ({d}%)' +} +BaseOption.legend = {} +BaseOption.series = [] +BaseOption.radar = [] +export default BaseOption diff --git a/packages/vue/src/charts-beta/chart-core/base/components/RadarChart/chartToken.ts b/packages/vue/src/charts-beta/chart-core/base/components/RadarChart/chartToken.ts new file mode 100644 index 000000000..6a5ba9ca2 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/RadarChart/chartToken.ts @@ -0,0 +1,60 @@ +import Theme from '../../feature/theme' +import proxy from '../../util/proxy' + +function getChartToken() { + const { + symbolSize, + symbolSizeSM, + symbolSizeLG, + areaStyle: { color: areaStyleColor }, + rich: { fontSize, lineHeight, lineHeightSM }, + itemStyle: { + borderWidth: { border, borderSM }, + borderColor + }, + lineStyle: { + width: { lineWidth, lineWidthLG, lineWidthZero } + } + } = Theme.config.RadarChart + + const { errorColor } = Theme.config.colorState + const { + radarAxisLineWidth, + radarSplitLineWidth, + radiusAxisLineColor, + radarSplitLineColor, + radarAxisLabelColor, + lineColorTp, + radarAxisLineType, + radarSplitLineType + } = Theme.config + + return { + symbolSize, + areaStyleColor, + fontSize, + lineHeight, + lineHeightSM, + symbolSizeSM, + symbolSizeLG, + border, + borderSM, + lineWidth, + lineWidthLG, + lineWidthZero, + errorColor, + radarAxisLineWidth, + radarSplitLineWidth, + radiusAxisLineColor, + radarSplitLineColor, + radarAxisLabelColor, + lineColorTp, + borderColor, + radarAxisLineType, + radarSplitLineType + } +} + +const chartToken = proxy(getChartToken) + +export default chartToken diff --git a/packages/vue/src/charts-beta/chart-core/base/components/RadarChart/handleOptipn.ts b/packages/vue/src/charts-beta/chart-core/base/components/RadarChart/handleOptipn.ts new file mode 100644 index 000000000..db3c9a894 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/RadarChart/handleOptipn.ts @@ -0,0 +1,450 @@ +import { isArray, isObject } from '../../util/type' +import cloneDeep from '../../util/cloneDeep' +import defendXSS from '../../util/defendXSS' +import merge from '../../util/merge' +import chartToken from './chartToken' + +/** + * 从数据中拿出雷达的所有维度 + */ +export function getRadarKeys(data) { + const radarKeys = [] + const seriesNames = Object.keys(data) + for (let i = 0; i < seriesNames.length; i++) { + const seriesName = seriesNames[i] + const seriesData = data[seriesName] + const dataNames = Object.keys(seriesData) + for (let j = 0; j < dataNames.length; j++) { + const dataName = dataNames[j] + if (!radarKeys.includes(dataName)) { + radarKeys.push(dataName) + } + } + } + return radarKeys +} + +function handleFormatter(formatter, tooltip, radarKeys, markLine, alarmColor) { + const isThreshold = !!(isObject(markLine) && markLine?.threshold) + if (formatter) { + tooltip.formatter = (params, ticket, callback) => { + return formatter(params, radarKeys, ticket, callback) + } + } else { + tooltip.formatter = (params) => { + const data = params.data + const dataName = data.name + let htmlString = `
${defendXSS(dataName)}
` + if (markLine) { + data.value.forEach((item, index) => { + let color + if (isThreshold) { + const markVal = markLine.threshold[radarKeys[index]] + if (markVal) { + color = item >= markVal ? alarmColor : params.color + } else { + color = params.color + } + } else { + color = item >= markLine ? alarmColor : params.color + } + htmlString += `
+ + ${defendXSS(radarKeys[index])} + ${defendXSS(item || '-')} +
` + }) + } else { + data.value.forEach((item, index) => { + htmlString += `
+ + ${defendXSS(radarKeys[index])} + ${defendXSS(item || '-')} +
` + }) + } + return htmlString + } + } +} + +/** + * 配置鼠标悬浮提示框 + */ +export function setTooltip(baseOption, iChartOption, radarKeys) { + const formatter = iChartOption.tipHtml + const markLine = iChartOption.markLine + // 阈值告警色 + const alarmColor = chartToken.errorColor + handleFormatter(formatter, baseOption.tooltip, radarKeys, markLine, alarmColor) +} + +/** + * 配置图表图例 + */ + +const splitArea = { + show: false +} +function handleAxisLine(colorBase) { + const axisLine = { + lineStyle: { + color: chartToken.radiusAxisLineColor, + width: chartToken.radarAxisLineWidth, + type: chartToken.radarAxisLineType + } + } + return axisLine +} + +function handleAxisLabel(radarMark) { + const axisLabel = { + show: radarMark !== false, + margin: -20 + } + return axisLabel +} + +function handleSplitLine(colorBase) { + const splitLine = { + lineStyle: { + color: chartToken.radarSplitLineColor, + width: chartToken.radarSplitLineWidth, + type: chartToken.radarSplitLineType + } + } + return splitLine +} +function handleRich(alarmColor, colorBase) { + const rich = { + a: { + // 指示器name的样式 + color: chartToken.radarAxisLabelColor, + align: 'center', + fontSize: chartToken.fontSize, + lineHeight: chartToken.lineHeightSM + }, + b: { + // 数据value样式 + color: chartToken.radarAxisLabelColor, + fontSize: chartToken.fontSize, + align: 'center', + lineHeight: chartToken.lineHeight, + padding: [0, 0, 4, 0] + }, + c: { + // 高亮数据value样式 + color: alarmColor, + fontSize: chartToken.fontSize, + align: 'center', + lineHeight: chartToken.lineHeight, + padding: [0, 0, 4, 0] + } + } + return rich +} + +function handleRaderOption({ + chartPosition, + axisLine, + axisLabel, + splitArea, + splitLine, + indicator, + dataLength, + rich, + markLineValue, + data, + unit +}) { + const radar = { + // 雷达坐标系位置 + center: chartPosition.center || ['50%', '50%'], + // 雷达坐标系半径 + radius: chartPosition.radius || '50%', + // 指示器名称与轴的距离 + axisNameGap: 15, + // 指示器轴的分割段数 + splitNumber: 4, + shape: 'circle', + // 坐标轴的标签是否响应和触发鼠标事件 + triggerEvent: false, + // 坐标轴射线 + axisLine, + // 坐标轴射线的刻度,只显示一条射线的刻度,其他射线的刻度需要在指示器数据indicator中每项单独配置axisLabel: { show: false } + axisLabel, + // 坐标轴圆环分隔区域填充 + splitArea, + // 坐标轴圆环分隔线 + splitLine, + // 坐标轴数据 + indicator, + // 指示器名称样式设置 + axisName: { + rich, + formatter: (indicatorName) => { + // 只有一组数据时,显示“名称 + 数值” + if (dataLength === 1) { + const d = data[Object.keys(data)[0]] + const v = d[indicatorName] + if (markLineValue && v >= markLineValue) { + return `{c|${v}}{c|${unit}}\n{a|${indicatorName}}` + } else { + return `{b|${v}}{b|${unit}}\n{a|${indicatorName}}` + } + } else if (dataLength > 1) { + return `{a|${indicatorName}}` + } + } + } + } + return radar +} + +function handleUnit(iChartOption) { + if (iChartOption.unit || iChartOption.unit === '') return iChartOption.unit + return '%' +} + +function handleRader(iChartOption, indicator, dataLength, data) { + const chartPosition = iChartOption.chartPosition || iChartOption.position || {} + const { radar } = iChartOption + // 阈值告警色 + const alarmColor = chartToken.errorColor + const axisLine = handleAxisLine() + const axisLabel = handleAxisLabel(iChartOption.radarMark) + const splitLine = handleSplitLine() + const rich = handleRich(alarmColor) + const markLineValue = iChartOption.markLine + const unit = handleUnit(iChartOption) + const inerRadar = handleRaderOption({ + iChartOption, + chartPosition, + axisLine, + axisLabel, + splitArea, + splitLine, + indicator, + dataLength, + markLineValue, + rich, + data, + unit + }) + + if (radar) { + merge(inerRadar, radar) + } + // 坐标轴的相关配置 + if (radar?.indicator) { + const mixinIndicator = inerRadar.indicator.map((i) => { + const coveredIndicator = radar.indicator.find((indicate) => indicate.name === i.name) + return coveredIndicator || i + }) + inerRadar.indicator = mixinIndicator + } + + return inerRadar +} + +/** + * 配置雷达地图 + */ +export function setRadar(radarKeys, iChartOption, isCustomMaxVal) { + // 数据 + const data = iChartOption.data + // 数据key值 + const dataLength = Object.keys(data).length + // 雷达图坐标系最外圈代表的数值 + const radarMax = iChartOption.radarMax + const isRadarMaxArr = isArray(radarMax) + // 雷达图坐标系数据 + const indicator = radarKeys.map((name, index) => { + if (!isRadarMaxArr) { + // 非数组的形式默认所有维度共享一个最大值,只显示一个维度的刻度 + if (index === 0) { + return { name, max: radarMax } + } else { + return { name, max: radarMax, axisLabel: { show: false } } + } + } else { + const { radarMark } = iChartOption + const inerMax = getRadarMax(data, iChartOption, isCustomMaxVal) + const isName = radarMax.find((item) => item.name === name) + const cusIndicator = isName || {} + return { name, max: inerMax, axisLabel: { show: !!radarMark }, ...cusIndicator } + } + }) + const radar = handleRader(iChartOption, indicator, dataLength, data) + return radar +} +// 阈值线的专用radar +const marklineRadar = { + center: undefined, + // 阈值线的radius应该是一个百分比,或者是实际的像素值 + radius: undefined, + splitNumber: 1, + shape: 'circle', + axisName: { + show: false + }, + axisLine: { + show: false + }, + axisLabel: { + show: false + }, + splitArea: { + show: false + }, + splitLine: { + lineStyle: { + width: chartToken.radarSplitLineWidth, + color: undefined, + type: 'dashed' + } + }, + indicator: undefined +} + +// 处理单独配置的阈值线的series +function handleCusMarLineSer(baseOption, markLine, radarKeys) { + const { threshold } = markLine + const alarmColor = chartToken.errorColor + const thresholdData = radarKeys.map((item) => { + const data = threshold[item] || 0 + return data + }) + const thresholdSeries = { + type: 'radar', + symbol: 'none', + // 拐点大小 + symbolSize: chartToken.symbolSize, + silent: true, + // 使用的雷达坐标系的索引 + radarIndex: 1, + areaStyle: { + color: chartToken.areaStyleColor + }, + lineStyle: { + color: alarmColor, + type: 'dashed' + }, + data: [] + } + thresholdSeries.data = [{ name: '', value: thresholdData }] + baseOption.series.push(thresholdSeries) +} + +/** + * 配置阈值线 + */ +export function setMarkLine(baseOption, iChartOption, radarKeys) { + const { markLine } = iChartOption + const chartPosition = iChartOption.chartPosition || iChartOption.position || {} + // 阈值线告警色 + const alarmColor = chartToken.errorColor + // 雷达图半径 + const radius = chartPosition.radius || '50%' + // 雷达图有中心文本的情况下radius需要写成数组 + const radiusIsArray = isArray(radius) + // 雷达图最大值,可能为undefined + const radarMax = iChartOption.radarMax + // 阈值 + let markLineValue = iChartOption.markLine + if (markLineValue !== undefined) { + const markRadar = cloneDeep(marklineRadar) + markRadar.center = chartPosition.center || ['50%', '50%'] + markRadar.radius = chartPosition.radius || '50%' + // 判断markLine是否是 + if (isObject(markLine) && markLine?.threshold) { + markRadar.indicator = cloneDeep(baseOption.radar[0].indicator) + handleCusMarLineSer(baseOption, markLine, radarKeys) + } else { + markLineValue = parseFloat(markLineValue) + let marklineRadius + if (radiusIsArray) { + const disRadius = parseFloat(radius[1]) - parseFloat(radius[0]) + marklineRadius = + disRadius * (markLineValue / radarMax) + parseFloat(radius[0]) + (radius.toString().includes('%') ? '%' : '') + } else { + marklineRadius = (markLineValue / radarMax) * parseFloat(radius) + (radius.toString().includes('%') ? '%' : '') + } + // 阈值线 + markRadar.radius = marklineRadius + markRadar.splitLine.lineStyle.color = alarmColor + markRadar.indicator = new Array(radarKeys.length).fill({ name: '' }) + } + baseOption.radar.push(markRadar) + } +} + +function handleCusMax(iChartOpt) { + let max = 0 + const { radarMax, radar } = iChartOpt + if (isArray(radarMax)) { + radarMax.forEach((item) => { + if (item.max > max) max = item.max + }) + } + if (radar?.indicator) { + radar.indicator.forEach((el) => { + if (el.max > max) max = el.max + }) + } + return max +} + +function findDataMax(keys, data) { + let max + for (let j = 0; j < keys.length; j++) { + const key = keys[j] + if (max === undefined) { + max = data[key] + } + max = Math.max(max, data[key]) + } + return max +} + +/** + * 或得雷达图数据的最大值 + */ +export function getRadarMax(data, iChartOpt, isCustomMaxVal) { + let max + // 处理自定义max情况下的雷达图的默认最大值为未自定义的系列数据的最大值 + let cusDataName + const { radarMax, radar } = iChartOpt + if (isCustomMaxVal) { + if (isArray(radarMax)) { + cusDataName = radarMax.map((item) => item.name) + } + if (radar?.indicator) { + cusDataName = radar.indicator.map((i) => i.name) + } + } + const names = Object.keys(data) + for (let i = 0; i < names.length; i++) { + const name = names[i] + const keys = Object.keys(data[name]) + if (isCustomMaxVal) { + // 过滤出自定义的系列 + const dataKeys = keys.filter((name) => { + if (!cusDataName.includes(name)) return name + }) + max = findDataMax(dataKeys, data[name]) + // 所有系列都自定义的情况,最大值取自定义里面的最大值 + if (max === undefined) { + max = handleCusMax(iChartOpt) + } + } else { + max = findDataMax(keys, data[name]) + } + } + return max +} diff --git a/packages/vue/src/charts-beta/chart-core/base/components/RadarChart/handleSeries.ts b/packages/vue/src/charts-beta/chart-core/base/components/RadarChart/handleSeries.ts new file mode 100644 index 000000000..9c75e13be --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/RadarChart/handleSeries.ts @@ -0,0 +1,224 @@ +import { isObject } from '../../util/type' +import cloneDeep from '../../util/cloneDeep' +import chartToken from './chartToken' + +export const handleSeriesInit = () => { + return { + type: 'radar', + name: 'data', + // 使用的雷达坐标系 + radarIndex: 0, + // 拐点的标记大小 + symbolSize: chartToken.symbolSize, + // 拐点样式 + itemStyle: { + borderWidth: chartToken.border + }, + // 填充面积 + areaStyle: { + opacity: 0.2 + }, + lineStyle: { + width: chartToken.lineWidth + }, + // 高亮的样式 + emphasis: { + focus: 'none', + areaStyle: { + opacity: 0.5 + } + }, + data: [] + } +} + +/** + * 组装echarts所需要的series + * @param {主题} theme + * @param {数据} data + * @returns + */ +export function setSeries(iChartOption, radarKeys, data) { + const { isWaveRadar, theme } = iChartOption + // 组装数据 + const series = [] + const dataNames = Object.keys(data) + const seriesUnit = cloneDeep(handleSeriesInit()) + // 更改拐点边框样式 + seriesUnit.itemStyle.borderColor = chartToken.borderColor + // 华为云的主题下的特殊处理,之后通过token解决,暂时这样处理 + if (theme.toLowerCase().includes('cloud-dark') || theme.toLowerCase().includes('cloud-light')) { + // 普通 + seriesUnit.symbolSize = chartToken.symbolSizeSM + seriesUnit.itemStyle.borderWidth = chartToken.borderSM + seriesUnit.areaStyle.opacity = 0.1 + seriesUnit.emphasis.areaStyle.opacity = 0.2 + // 异型 + if (isWaveRadar) { + seriesUnit.symbolSize = chartToken.symbolSizeLG + seriesUnit.itemStyle.borderWidth = chartToken.border + seriesUnit.areaStyle.opacity = 0.2 + seriesUnit.lineStyle.width = chartToken.lineWidthLG + seriesUnit.emphasis.areaStyle.opacity = 0.4 + } + } + dataNames.forEach((name) => { + const radarData = { + name, + value: radarKeys.map((key) => { + return data[name][key] + }) + } + seriesUnit.data.push(radarData) + }) + series.push(seriesUnit) + return series +} + +function handleRedPointerRadar(chartPosition, radarKeys, dataNameIndex, radarMax, dataName, baseOption) { + const max = baseOption.radar[0].indicator.find((item) => item.name === dataName).max + const redPointerRadar = { + // 雷达位置,保持和初始坐标系一致, + center: chartPosition.center || ['50%', '50%'], + // 雷达半径,保持和初始坐标系一致, + radius: chartPosition.radius || '50%', + // 控制指示器名称是否显示 + axisName: { + show: false + }, + startAngle: 90 + (360 / radarKeys.length) * dataNameIndex, // 轴线的角度根据需求自己定义,起始点是水平方向 + // 指示器轴的分割段数,和初始坐标轴一致 + splitNumber: 4, + shape: 'circle', + // 坐标轴圆环分隔区域填充 + splitArea: { + show: false + }, + // 坐标轴射线 + axisLine: { + show: true, + lineStyle: { + width: chartToken.radarAxisLineWidth, + color: chartToken.lineColorTp + } + }, + // 坐标轴圆环分隔线 + splitLine: { + lineStyle: { + width: chartToken.radarSplitLineWidth, + color: chartToken.lineColorTp + } + }, + indicator: [{ name: '', max }] + } + return redPointerRadar +} + +function handleRedPointerSeries(index, theme, dataValue, seriesName, noMarkLine) { + const alarmColor = chartToken.errorColor + const redPointerSeries = { + nmae: 'threshold', + type: 'radar', + // 拐点大小 + symbolSize: chartToken.symbolSizeLG, + silent: true, + z: 99, + // 使用的雷达坐标系的索引 + radarIndex: 2 + index, + // 拐点的样式 + itemStyle: { + color: alarmColor, + borderColor: chartToken.borderColor, + borderWidth: chartToken.border, + shadowBlur: 15, + shadowColor: alarmColor + }, + // 隐藏标示线 + lineStyle: { + width: chartToken.lineWidthZero, + labelLine: { + show: false + } + }, + data: [ + { + value: dataValue, + name: seriesName // 数据的名称,为了图例的点击消失生效,和系列的名字保持一致 + } + ] + } + return redPointerSeries +} + +// 计算出大于等于阈值的数据 +function getExceededMarkLineValue(data, markLineValue, isThreshold) { + const obj = [] + const names = Object.keys(data) + for (let i = 0; i < names.length; i++) { + const name = names[i] + const keys = Object.keys(data[name]) + for (let j = 0; j < keys.length; j++) { + const key = keys[j] + if (isThreshold) { + const cusMark = markLineValue?.threshold[key] + if (cusMark && data[name][key] >= cusMark) { + obj.push({ + seriesName: name, + dataName: key, + dataValue: data[name][key] + }) + } + } else { + if (data[name][key] >= markLineValue) { + obj.push({ + seriesName: name, + dataName: key, + dataValue: data[name][key] + }) + } + } + } + } + + return obj +} + +/** + * 根据参数计算出圆盘图的半径 + */ +export function setMarkLineSeries(baseOption, iChartOption, radarKeys, isCustomMaxVal) { + // 阈值 + const markLineValue = iChartOption.markLine + if (markLineValue !== undefined) { + // 主题 + const theme = iChartOption.theme + // 数据 + const data = iChartOption.data + const isThreshold = !!(isObject(markLineValue) && markLineValue?.threshold) + // 超过阈值的数据 + const exceeded = getExceededMarkLineValue(data, markLineValue, isThreshold) + // 图表位置 + const chartPosition = iChartOption.chartPosition || iChartOption.position || {} + // 雷达图坐标系最外圈代表的数值 + const radarMax = iChartOption.radarMax + exceeded.forEach((item, index) => { + const seriesName = item.seriesName + const dataName = item.dataName + const dataNameIndex = radarKeys.indexOf(dataName) + const dataValue = item.dataValue + // 需要高亮红点的坐标系,一个红点对应一个坐标系,需要去修改相应的数据 + const redPointerRadar = handleRedPointerRadar( + chartPosition, + radarKeys, + dataNameIndex, + radarMax, + dataName, + baseOption + ) + // 红点数据 + const redPointerSeries = handleRedPointerSeries(index, theme, dataValue, seriesName, isThreshold) + baseOption.radar.push(redPointerRadar) + baseOption.series.push(redPointerSeries) + }) + } +} diff --git a/packages/vue/src/charts-beta/chart-core/base/components/RadarChart/index.ts b/packages/vue/src/charts-beta/chart-core/base/components/RadarChart/index.ts new file mode 100644 index 000000000..c7cff8c7b --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/components/RadarChart/index.ts @@ -0,0 +1,69 @@ +import BaseOption from './BaseOption' +import { event } from '../../util/event' +import init from '../../option/init' +import { insertCenterDom, resizeCenterDom } from '../../util/centerDom' +import { isArray } from '../../util/type' +import cloneDeep from '../../util/cloneDeep' +import { setSeries, setMarkLineSeries } from './handleSeries' +import PolarCoordSys from '../../option/PolarSys' +import { setRadar, setTooltip, setMarkLine, getRadarMax, getRadarKeys } from './handleOptipn' +import { mergeSeries } from '../../util/merge' + +class RadarChart { + constructor(iChartOption, chartInstance) { + this.baseOption = {} + this.iChartOption = {} + this.chartInstance = chartInstance + this.baseOption = cloneDeep(BaseOption) + // 组装 iChartOption, 补全默认值 + this.iChartOption = init(iChartOption) + // 根据 iChartOption 组装 baseOption + this.updateOption(chartInstance) + } + + updateOption(chartInstance) { + const iChartOption = this.iChartOption + const theme = iChartOption.theme + const isCustomMaxVal = !!(isArray(iChartOption.radarMax) || iChartOption?.radar?.indicator) + // 雷达坐标系最大值 + iChartOption.radarMax = iChartOption.radarMax || getRadarMax(iChartOption.data, iChartOption, isCustomMaxVal) + // 在配置不同系列的最大值的时候。数据不在同一维度,不显示markLine + PolarCoordSys(this.baseOption, iChartOption, 'RadarChart') + // 雷达所有维度 + const radarKeys = getRadarKeys(iChartOption.data) + // 绘制雷达地图 + this.baseOption.radar.push(setRadar(radarKeys, iChartOption, isCustomMaxVal)) + // 赋值数据 + this.baseOption.series = setSeries(iChartOption, radarKeys, iChartOption.data) + // 阈值线 + setMarkLine(this.baseOption, iChartOption, radarKeys, isCustomMaxVal) + // 图表鼠标悬浮提示框 + setTooltip(this.baseOption, iChartOption, radarKeys) + // 设置阈值红点 + setMarkLineSeries(this.baseOption, iChartOption, radarKeys, isCustomMaxVal) + // 目前只允许合并基础的雷达图的series,对于阈值线和红点所在的series不做处理,普通雷达图用series.name='data'标识,目前本接口只给opentinty和aui使用 + mergeSeries(iChartOption, this.baseOption) + if (iChartOption.event) { + event(chartInstance, iChartOption.event) + } + } + + // 根据渲染出的结果,二次计算option + updateOptionAgain(YAxiMax, YAxiMin) { + const container = this.chartInstance.getDom() + insertCenterDom(container, this.iChartOption) + } + + resize() { + const container = this.chartInstance.getDom() + resizeCenterDom(container, this.iChartOption) + } + + getOption() { + return this.baseOption + } + + setOption() {} +} + +export default RadarChart diff --git a/packages/vue/src/charts-beta/chart-core/base/core.ts b/packages/vue/src/charts-beta/chart-core/base/core.ts new file mode 100644 index 000000000..a0862bb7a --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/core.ts @@ -0,0 +1,306 @@ +import tips from './util/tips' +import merge from './util/merge' +import * as echarts from 'echarts' +import Theme from './feature/theme' +import throttle from './util/throttle' +import { mergeExtend } from './util/merge' +import BaseChart from './components/BaseChart' +import readScreen from './feature/readScreen' +import mediaScreen from './feature/mediaScreen' + +const SELF_CHART = [ + 'FlowChart', + 'WaveChart', + 'RiverChart', + 'GanttChart', + 'BaiduMapChart', + 'HoneycombChart', + 'OrganizationChart', + 'AutonaviMapChart' +] + +// 图表核心对象,按需引入图表 class 给 CoreChart 渲染,打包容量较小 +export default class CoreChart extends BaseChart { + constructor() { + super() + // 图表echarts实例 + this.echartsIns = null + // 图表icharts实例 + this.ichartsIns = null + // 图表echarts配置项 + this.eChartOption = null + // 图表icharts配置项 + this.iChartOption = null + // 图表所在容器 + this.dom = null + // 图表类型 + this.chartClass + // 图表依赖的三方插件 + this.plugins = {} + // 图表还没有执行render()方法 + this.hasRender = false + // 图表渲染完毕的回调 + this.renderCallBack = null + // 图表resize节流时间 + this.resizeThrottle = 0 + // 图表容器的宽高变化监听器 + this.resizeObserver = null + // 响应式布局的监听器 + this.mediaScreenObserver = undefined + } + + // 注册主题 + static registerTheme(name, config) { + if (!config) { + tips.error('The second parameter config is required') + return + } + Theme.set(name, config) + } + + // 注册配置 + static registerConfig(name, config) { + if (!config) { + tips.error('The second parameter config is required') + return + } + Theme.setConfig(name, config) + } + + // 设置主题 + static theme(name) { + Theme.setDefaultTheme(name) + } + + static resetThemeCongfig() { + Theme.resetThemeCongfig() + } + + // 开启响应式布局(类媒体查询效果) + mediaScreen(dom, screenOption) { + this.mediaScreenObserver = new mediaScreen(dom, screenOption, (option) => { + this.setSimpleOption(this.chartClass, option, this.plugins, false) + this.render() + }) + } + + // 初始化echarts,并同时监听容器和窗口的大小变化 + init(chartDom, initOpts) { + const defaultInit = { + domResize: true, + windowResize: true, + resizeThrottle: this.resizeThrottle + } + initOpts = merge(defaultInit, initOpts) + this.dom = chartDom + this.echartsIns = echarts.init(chartDom, {}, initOpts) + // resize节流函数 + this.throttleResize = + initOpts.resizeThrottle === 0 + ? this.setResize.bind(this) + : throttle(initOpts.resizeThrottle, this.setResize.bind(this)) + // 容器大小变化监听 + initOpts.domResize && this.setResizeObserver() + // 页面大小变化监听 + initOpts.windowResize && window.addEventListener('resize', this.throttleResize) + } + + setResizeObserver() { + this.resizeObserver = new ResizeObserver((entries) => { + window.requestAnimationFrame(() => { + this.throttleResize() + }) + }) + this.resizeObserver.observe(this.dom) + } + + // 图表宽高自适应 + setResize() { + this.mediaScreenObserver && this.mediaScreenObserver.observe() + this.echartsIns && this.echartsIns.resize && this.echartsIns.resize({ width: 'auto' }) + this.ichartsIns && this.ichartsIns.resize && this.ichartsIns.resize() + } + + // 传入简化后的icharts-option + setSimpleOption(chartClass, iChartOption, plugins = {}, isInit = true) { + if (isInit) { + Theme.setDefaultTheme(iChartOption.theme) + this.mediaScreenObserver && this.mediaScreenObserver.setInitOption(iChartOption) + } + if (iChartOption.readScreen) { + readScreen(this.dom, iChartOption.readScreen) + } + if (this.isSelfChart(chartClass)) { + this.redirectSelfChart(chartClass, iChartOption) + return + } + this.plugins = plugins + this.chartClass = chartClass + this.iChartOption = iChartOption + this.ichartsIns = new chartClass(iChartOption, this.echartsIns, this.plugins) + this.eChartOption = this.ichartsIns.getOption() + mergeExtend(this.iChartOption, this.eChartOption) + } + + // 若自研图表,走自研图表路径,并更改this指向 + redirectSelfChart(selfChart, option) { + const stateDom = this.dom.getElementsByClassName('huicharts-state-container')[0] + this.uninstall() + this.dom.innerHTML = '' + const instance = new selfChart() + instance.init(this.dom) + instance.setSimpleOption(selfChart, option) + instance.renderCallBack = this.renderCallBack + if (stateDom) { + this.dom.appendChild(stateDom) + } + this.__proto__ = instance + for (const key in this) { + if (Object.hasOwnProperty.call(this, key)) { + delete this[key] + } + } + } + + // 判断是否为若自研图表 + isSelfChart(chartClass) { + return SELF_CHART.includes(chartClass.name) + } + + // 渲染图表 + render(option) { + // 已经开始渲染 + this.hasRender = true + // 第一次渲染 + this.setOption(this.eChartOption, option) + // 第二次渲染 + this.setOptionAgain(this.eChartOption) + // 图表渲染完成时回调 + this.renderCallBack && this.renderCallBack(this.echartsIns) + } + + // 第一次渲染: 调用echarts原生的setOption + setOption(eChartOption, option) { + option = merge({ notMerge: true }, option) + eChartOption && this.echartsIns.setOption(eChartOption, option) + console.log('eChartOption: ', eChartOption) + } + + // 第二次渲染: 有些图表需要根据第一次渲染出来的结果进行二次计算 + setOptionAgain() { + if (this.ichartsIns && this.ichartsIns.updateOptionAgain) { + const YAxiMax = this.getYAxisMaxValue(0) + const YAxiMin = this.getYAxisMinValue(0) + // 根据网格重新计算option + this.ichartsIns.updateOptionAgain(YAxiMax, YAxiMin) + // 再次渲染 + this.setOption(this.eChartOption) + } + } + + // 图表刷新,包括刷新配置和数据 + refresh(iChartOption) { + this.iChartOption = iChartOption + this.setSimpleOption(this.chartClass, iChartOption, this.plugins) + this.render() + } + + // 图表刷新,仅刷新数据 + refreshData(data) { + this.iChartOption.data = data + this.refresh(this.iChartOption) + } + + // 图表渲染完成时回调 + onRenderReady(callback) { + this.renderCallBack = callback + } + + // 给echarts单独绑定事件 + on() { + this.echartsIns && this.echartsIns.on(...arguments) + } + + // 给echarts单独解绑事件 + off() { + this.echartsIns && this.echartsIns.off(...arguments) + } + + // 给echarts实例绑定事件 + bindEvents(events) { + if (events && events.length !== 0) { + events.forEach((item) => { + if (item.query) { + this.off(item.eventName, item.handler) + this.on(item.eventName, item.query, item.handler) + } else { + this.off(item.eventName, item.handler) + this.on(item.eventName, item.handler) + } + }) + } + } + + // 批量给echarts实例解绑事件 + unbindEvents(events) { + if (events && events.length !== 0) { + events.forEach((item) => { + if (item.handler) { + this.off(item.eventName, item.handler) + } else { + this.off(item.eventName) + } + }) + } + } + + // 卸载图表时, + uninstall() { + // 卸载window resize监听功能 + window.removeEventListener('resize', this.throttleResize) + // 卸载container容器变化监听 + if (this.resizeObserver) { + this.resizeObserver.unobserve(this.dom) + this.resizeObserver.disconnect() + this.resizeObserver = null + } + // 销毁ECharts实例 + if (this.echartsIns && !this.echartsIns.isDisposed()) { + this.echartsIns.dispose() + } + this.echartsIns = null + } + + // 获取到ECharts实例 + getEchartsInstance() { + return this.echartsIns + } + + // 直接传入ECharts的原生配置项 + setEchartsOption(option) { + option && (this.eChartOption = option) + } + + // 获取到ECharts配置项 + getEchartsOption() { + return this.eChartOption + } + + /** + * 图表渲染完毕后,获得y刻度的最大值 + * @param {index为yAxis数组下标} index + * _extent是一个数组,_extent[0]为该轴上最小值,_extent[1]为该轴上最大值 + */ + getYAxisMaxValue(index) { + return this.echartsIns.getModel().getComponent('yAxis', index)?.axis.scale._extent[1] + } + + /** + * 图表渲染完毕后,获得y轴刻度的最小值 + * @param {index为yAxis数组下标} index + * _extent是一个数组,_extent[0]为该轴上最小值,_extent[1]为该轴上最大值 + */ + getYAxisMinValue(index) { + return this.echartsIns.getModel().getComponent('yAxis', index)?.axis.scale._extent[0] + } +} diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/fluctuation/index.ts b/packages/vue/src/charts-beta/chart-core/base/feature/fluctuation/index.ts new file mode 100644 index 000000000..3ddaa26ef --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/fluctuation/index.ts @@ -0,0 +1,54 @@ +import { isArray } from '../../util/type' + +// 获取新的数据格式 +const transformData = (origindata) => { + const newData = {} + const count = Object.keys(origindata[0]).length + for (let i = 1; i < count; i++) { + let arr = [] + origindata.forEach((key) => { + let firstKey = Object.keys(key)[i] + let firstValue = key[firstKey] + arr.push(firstValue) + newData[firstKey] = arr + }) + } + return newData +} + +// 修改轴数据 data:图表数据 allowRange:允许轴的范围 +const fluctuation = (data, allowRange) => { + let min = Infinity + let max = -Infinity + const range = allowRange || [-Infinity, Infinity] + const dataKeys = Object.keys(data) + + dataKeys.forEach((key) => { + data[key].forEach((item) => { + if (isArray(item)) { + const arr = item.filter((t) => !isNaN(Number(t)) && Number(t) > range[0] && Number(t) < range[1]) + const curMin = Math.min(...arr) + const curMax = Math.max(...arr) + min = Math.min(min, curMin) + max = Math.max(max, curMax) + } else { + const num = Number(item) + if (num < range[0] || num > range[1]) { + return + } + min = Math.min(min, num) + max = Math.max(max, num) + } + }) + }) + + let axisMin = Math.floor(min - ((max - min) / 5) * 4) + if (min > 0 && axisMin < 0) { + axisMin = 0 + } + const axisMax = Math.ceil(max + (max - min) / 5) + return [axisMin, axisMax] +} + +export default fluctuation +export { transformData } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/mediaScreen/index.ts b/packages/vue/src/charts-beta/chart-core/base/feature/mediaScreen/index.ts new file mode 100644 index 000000000..608c272a7 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/mediaScreen/index.ts @@ -0,0 +1,72 @@ +import merge from '../../util/merge' +import cloneDeep from '../../util/cloneDeep' +import { isArrayEqual } from '../../util/equal' + +class mediaScreen { + constructor(dom, option, callback) { + // 媒体查询监听的dom + this.dom = dom + // 媒体查询的响应式配置 + this.option = option || [] + // 媒体查询命中时的回调函数 + this.callback = callback + // 当前宽度下响应式配置命中的区间 + this.curRange = [] + // 图表初始配置 + this.initOption = {} + } + + // 监听dom宽度变化 + observe() { + if (!this.dom) return + let domWidth = this.dom.getBoundingClientRect().width + let range = this.getRange(domWidth, this.option) + if (!isArrayEqual(this.curRange, range)) { + let tempOption = cloneDeep(this.initOption) + this.curRange = range + this.curRange.forEach((rangeIndex) => { + merge(tempOption, this.option[rangeIndex].option) + }) + this.callback(tempOption) + } + } + + // 设置图表初始配置 + setInitOption(option) { + this.initOption = cloneDeep(option) + } + + // 计算命中区间 + getRange(domWidth, option) { + let range = [] + option.forEach((item, index) => { + // maxWidth/minWidth都存在 + if (item.maxWidth !== undefined && item.minWidth !== undefined) { + if (domWidth <= item.maxWidth && domWidth >= item.minWidth) { + range.push(index) + } + } + // maxWidth/minWidth都不存在,直接push到this.initRange + else if (item.maxWidth === undefined && item.minWidth === undefined) { + range.push(index) + } + // minWidth不存在 + else if (item.maxWidth !== undefined && item.minWidth === undefined) { + if (domWidth <= item.maxWidth) { + range.push(index) + } + } + // maxWidth不存在 + else if (item.maxWidth === undefined && item.minWidth !== undefined) { + if (domWidth >= item.minWidth) { + range.push(index) + } + } + }) + // 数组去重 + range = [...new Set(range)] + return range + } +} + +export default mediaScreen diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/mini/index.ts b/packages/vue/src/charts-beta/chart-core/base/feature/mini/index.ts new file mode 100644 index 000000000..d6f0f56d0 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/mini/index.ts @@ -0,0 +1,46 @@ +import { isArray } from '../../util/type' + +// 微型场景下,隐藏许多不必要的图元 +function mini(iChartOption, baseOption) { + if (iChartOption.mini) { + baseOption.grid.forEach((item) => { + Object.assign(item, { + top: 2, + left: 0, + right: 0, + bottom: 1, + containLabel: false + }) + }) + baseOption.legend = Object.assign(baseOption.legend, { + show: false + }) + baseOption.title = Object.assign(baseOption.title, { + show: false + }) + baseOption.tooltip = Object.assign(baseOption.tooltip, { + show: false + }) + if (!isArray(baseOption.xAxis)) { + baseOption.xAxis = [baseOption.xAxis] + } + if (!isArray(baseOption.yAxis)) { + baseOption.yAxis = [baseOption.yAxis] + } + baseOption.xAxis.forEach((item) => { + Object.assign(item, { + show: false, + boundaryGap: false + }) + }) + baseOption.yAxis.forEach((item) => { + Object.assign(item, { + show: false, + max: 'dataMax', + min: 'dataMin' + }) + }) + } +} + +export default mini diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/readScreen/index.ts b/packages/vue/src/charts-beta/chart-core/base/feature/readScreen/index.ts new file mode 100644 index 000000000..6571c0c51 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/readScreen/index.ts @@ -0,0 +1,8 @@ +function readScreen(dom, readScreenText) { + if (readScreenText) { + dom.setAttribute('tabindex', '0') + dom.setAttribute('aria-label', readScreenText) + } +} + +export default readScreen diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/basicToken/border.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/basicToken/border.ts new file mode 100644 index 000000000..4123d0f00 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/basicToken/border.ts @@ -0,0 +1,64 @@ +// 基础圆角数值 +const basicBorderRadius = 6 + +// 圆角梯度处理函数 +function getRadius(radiusBase) { + let radiusLG = radiusBase + let radiusSM = radiusBase + let radiusXS = radiusBase + + // radiusLG + if (radiusBase >= 16) { + radiusLG = 16 + } else if (radiusBase >= 6) { + radiusLG = radiusBase + 2 + } else if (radiusBase >= 5) { + radiusLG = radiusBase + 1 + } + + // radiusSM + if (radiusBase >= 16) { + radiusSM = 8 + } else if (radiusBase >= 14) { + radiusSM = 7 + } else if (radiusBase >= 8) { + radiusSM = 6 + } else if (radiusBase >= 7) { + radiusSM = 5 + } else if (radiusBase >= 5) { + radiusSM = 4 + } + + // radiusXS + if (radiusBase >= 6) { + radiusXS = 2 + } else if (radiusBase >= 2) { + radiusXS = 1 + } + + return { + borderRadiusZero: 0, + borderRadius: radiusBase, // 6 + borderRadiusXS: radiusXS, // 2 + borderRadiusSM: radiusSM, // 4 + borderRadiusLG: radiusLG // 8 + } +} + +const borderRadius = { + ...getRadius(basicBorderRadius) +} + +const border = { + borderZero: 0, + borderSM: 1, + border: 2, + borderLG: 3, + borderXL: 4, + borderXXL: 5, + ...borderRadius +} + +export default border + +export { getRadius } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/basicToken/font.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/basicToken/font.ts new file mode 100644 index 000000000..da0407bb0 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/basicToken/font.ts @@ -0,0 +1,44 @@ +const basicFont = 14 + +// 字体大小的具体梯度处理 +function getFontSizes(base) { + const fontSizes = new Array(10).fill(null).map((_, index) => { + const i = index - 1 + const baseSize = base * 2.71828 ** (i / 5) + const intSize = index > 1 ? Math.floor(baseSize) : Math.ceil(baseSize) + // Convert to even + return Math.floor(intSize / 2) * 2 + }) + fontSizes[1] = base + return fontSizes.map((size) => { + const height = size + 8 + return { + size, + lineHeight: height / size + } + }) +} + +// 字体的梯度函数 +function getFont(fontSize) { + const fontSizePairs = getFontSizes(fontSize) + const fontSizes = fontSizePairs.map((pair) => pair.size) + const lineHeights = fontSizePairs.map((pair) => pair.lineHeight) + return { + fontSizeSM: fontSizes[0], // 12 + fontSize: fontSizes[1], // 14 + fontSizeLG: fontSizes[2], // 16 + fontSizeXL: fontSizes[3], // 20 + lineHeight: lineHeights[1], // 1.57 + lineHeightLG: lineHeights[2], // 1.5 + lineHeightSM: lineHeights[0] // 1.67 + } +} + +const font = { + ...getFont(basicFont) +} + +export default font + +export { getFont } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/basicToken/index.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/basicToken/index.ts new file mode 100644 index 000000000..f1b5576f1 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/basicToken/index.ts @@ -0,0 +1,13 @@ +import border from './border' +import font from './font' +import line from './line' +import space from './space' + +const basic = { + ...border, + ...font, + ...line, + ...space +} + +export default basic diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/basicToken/line.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/basicToken/line.ts new file mode 100644 index 000000000..fe28bb6e8 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/basicToken/line.ts @@ -0,0 +1,15 @@ +// 线条基础数值 +const line = { + lineWidthZero: 0, + lineWidthSM: 1, + lineWidth: 2, + lineWidthLG: 3, + lineWidthXL: 8, + lineWidthXXL: 10, + lineWidthXXXL: 20, + lineTypeSolid: 'solid', + lineTypeDashed: 'dashed', + lineTypeDashedLG: [4, 4], + lineLength: 25 +} +export default line diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/basicToken/space.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/basicToken/space.ts new file mode 100644 index 000000000..26df8b217 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/basicToken/space.ts @@ -0,0 +1,26 @@ +// 基础间距 +const basicSpace = 4 + +// 间距的放大梯度 +const zoomGradient = [1, 2, 3, 4, 5] + +// 间距梯度算法函数 +function getSpace(base) { + const spaces = zoomGradient.map((item) => item * base) + + return { + space: spaces[0], // 4 + spaceLG: spaces[1], // 8 + spaceXL: spaces[2], // 12 + spaceXXL: spaces[3], // 16 + spaceXXXL: spaces[4] // 20 + } +} + +const space = { + ...getSpace(basicSpace) +} + +export default space + +export { getSpace } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/AreaChart.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/AreaChart.ts new file mode 100644 index 000000000..b86da1cb9 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/AreaChart.ts @@ -0,0 +1,10 @@ +const AreaChart = (modelToken) => { + const { areaColorTP } = modelToken + return { + areaStyle: { + color: areaColorTP + } + } +} + +export default AreaChart diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/BarChart.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/BarChart.ts new file mode 100644 index 000000000..3e4f548f0 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/BarChart.ts @@ -0,0 +1,27 @@ +const BarChart = (modelToken) => { + const { + itemBorderWidthSM, + itemBorderRadiusSM, + labelColor, + labelFontSizeSM, + itemColorTP, + barWidth, + itemBorderColorTP + } = modelToken + + return { + itemStyle: { + borderWidth: itemBorderWidthSM, + borderColor: itemBorderColorTP, + borderRadius: itemBorderRadiusSM, + color: itemColorTP + }, + label: { + color: labelColor, + fontSize: labelFontSizeSM + }, + barWidth + } +} + +export default BarChart diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/BubbleChart.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/BubbleChart.ts new file mode 100644 index 000000000..2e47c3ade --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/BubbleChart.ts @@ -0,0 +1,20 @@ +const BubbleChart = (basicToken, mapToken) => { + const { colorGray0, fontSize, borderSM } = basicToken + + return { + emphasis: { + label: { + color: colorGray0, + fontSize + } + }, + itemStyle: { + borderWidth: borderSM + }, + label: { + fontSize + } + } +} + +export default BubbleChart diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/FunnelChart.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/FunnelChart.ts new file mode 100644 index 000000000..227f8bf60 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/FunnelChart.ts @@ -0,0 +1,11 @@ +const FunnelChart = (basicToken, mapToken) => { + const { borderSM } = basicToken + + return { + itemStyle: { + borderWidth: borderSM + } + } +} + +export default FunnelChart diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/GraphTreeChart.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/GraphTreeChart.ts new file mode 100644 index 000000000..fb5094bf8 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/GraphTreeChart.ts @@ -0,0 +1,10 @@ +const GraphTreeChart = (basicToken, mapToken) => { + const { lineWidthSM } = basicToken + return { + lineStyle: { + width: lineWidthSM + } + } +} + +export default GraphTreeChart diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/HillChart.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/HillChart.ts new file mode 100644 index 000000000..6003532a9 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/HillChart.ts @@ -0,0 +1,21 @@ +const HillChart = (basicToken, mapToken) => { + const { lineWidthSM, fontSizeSM } = basicToken + + return { + markLine: { + lineStyle: { + width: lineWidthSM + }, + emphasis: { + lineStyle: { + width: lineWidthSM + } + } + }, + textStyle: { + fontSize: fontSizeSM + } + } +} + +export default HillChart diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/JadeJueChart.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/JadeJueChart.ts new file mode 100644 index 000000000..49d8b4e52 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/JadeJueChart.ts @@ -0,0 +1,11 @@ +const JadeJueChart = (basicToken, mapToken) => { + const { barWidth } = mapToken + const { border } = basicToken + return { + barWidth, + itemStyle: { + borderWidth: border + } + } +} +export default JadeJueChart diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/LineChart.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/LineChart.ts new file mode 100644 index 000000000..e4a59a2a5 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/LineChart.ts @@ -0,0 +1,37 @@ +const LineChart = (modelToken) => { + const { + symbolSize, + symbolSizeXS, + lineStyleWidthLG, + lineStyleWidth, + itemBorderWidthLG, + itemBorderWidthZero, + itemBorderWidth, + itemBorderColor, + markLineColorTP + } = modelToken + + return { + symbolSize: { + symbolSizeXS, + symbolSize + }, + lineStyle: { + lineWidthLG: lineStyleWidthLG, + lineWidth: lineStyleWidth + }, + itemStyle: { + borderZero: itemBorderWidthZero, + borderLG: itemBorderWidthLG, + border: itemBorderWidth, + borderColor: itemBorderColor + }, + markLine: { + lineStyle: { + color: markLineColorTP + } + } + } +} + +export default LineChart diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/PieChart.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/PieChart.ts new file mode 100644 index 000000000..a6d759b92 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/PieChart.ts @@ -0,0 +1,35 @@ +const PieChart = (modelToken) => { + const { + itemBorderWidth, + itemBorderWidthZero, + itemBorderColor, + labelDistanceLG, + labelColor, + lineColor, + circleColor, + labelFontSizeSM, + itemBorderRadiusZero + } = modelToken + + return { + itemStyle: { + borderWidth: itemBorderWidth, + borderColor: itemBorderColor, + borderWidthZero: itemBorderWidthZero, + borderRadius: itemBorderRadiusZero + }, + label: { + distance: labelDistanceLG, + color: labelColor, + fontSize: labelFontSizeSM + }, + lineStyle: { + color: lineColor + }, + emptyCircleStyle: { + color: circleColor + } + } +} + +export default PieChart diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/PolarBarChart.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/PolarBarChart.ts new file mode 100644 index 000000000..084d66638 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/PolarBarChart.ts @@ -0,0 +1,15 @@ +const PolarBarChart = (basicToken, mapToken) => { + const { borderRadiusLG, fontSizeSM, colorTP } = basicToken + + return { + itemStyle: { + borderRadius: borderRadiusLG, + color: colorTP + }, + label: { + fontSize: fontSizeSM + } + } +} + +export default PolarBarChart diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/ProcessChart.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/ProcessChart.ts new file mode 100644 index 000000000..8c8b0d376 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/ProcessChart.ts @@ -0,0 +1,20 @@ +const ProcessChart = (basicToken, mapToken) => { + const { fontSize, borderRadiusSM } = basicToken + const { barWidth, barWidthStack } = mapToken + + return { + barWidth: { + barWidthNorm: barWidth, + barWidthStack + }, + label: { + fontSize, + lineHeight: fontSize + }, + itemStyle: { + borderRadius: borderRadiusSM + } + } +} + +export default ProcessChart diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/RadarChart.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/RadarChart.ts new file mode 100644 index 000000000..b5a86aedf --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/RadarChart.ts @@ -0,0 +1,46 @@ +const RadarChart = (modelToken) => { + const { + symbolSize, + symbolSizeSM, + symbolSizeLG, + areaColorTP, + richFontSizeSM, + richLineHeight, + richLineHeightSM, + itemBorderWidth, + itemBorderWidthSM, + lineStyleWidth, + lineStyleWidthLG, + lineStyleWidthZero, + itemBorderColor + } = modelToken + + return { + symbolSize, + symbolSizeSM, + symbolSizeLG, + areaStyle: { + color: areaColorTP + }, + rich: { + fontSize: richFontSizeSM, + lineHeight: richLineHeight, + lineHeightSM: richLineHeightSM + }, + itemStyle: { + borderWidth: { + border: itemBorderWidth, + borderSM: itemBorderWidthSM + }, + borderColor: itemBorderColor + }, + lineStyle: { + width: { + lineWidth: lineStyleWidth, + lineWidthLG: lineStyleWidthLG, + lineWidthZero: lineStyleWidthZero + } + } + } +} +export default RadarChart diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/SankeyChart.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/SankeyChart.ts new file mode 100644 index 000000000..66a9e8cd2 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/SankeyChart.ts @@ -0,0 +1,12 @@ +const SankeyChart = (basicToken, mapToken) => { + const { fontSizeSM, space } = basicToken + + return { + label: { + fontSize: fontSizeSM, + distance: space + } + } +} + +export default SankeyChart diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/SunburstChart.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/SunburstChart.ts new file mode 100644 index 000000000..8578d8a6f --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/SunburstChart.ts @@ -0,0 +1,12 @@ +const SunburstChart = (basicToken, mapToken) => { + const { borderRadiusSM, border } = basicToken + + return { + itemStyle: { + borderRadius: borderRadiusSM, + borderWidth: border + } + } +} + +export default SunburstChart diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/TreeChart.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/TreeChart.ts new file mode 100644 index 000000000..d6d043f7a --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/TreeChart.ts @@ -0,0 +1,16 @@ +const TreeChart = (basicToken, mapToken) => { + const { fontSizeSM, lineWidthSM } = basicToken + const { symbolSize, labelDistance } = mapToken + + return { + label: { + distance: labelDistance, + fontSize: fontSizeSM + }, + lineStyle: { + width: lineWidthSM + }, + symbolSize + } +} +export default TreeChart diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/index.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/index.ts new file mode 100644 index 000000000..e1627a698 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/chartsToken/index.ts @@ -0,0 +1,37 @@ +import BarChart from './BarChart' +import BubbleChart from './BubbleChart' +import FunnelChart from './FunnelChart' +import GraphTreeChart from './GraphTreeChart' +import HillChart from './HillChart' +import JadeJueChart from './JadeJueChart' +import TreeChart from './TreeChart' +import SunburstChart from './SunburstChart' +import SankeyChart from './SankeyChart' +import RadarChart from './RadarChart' +import ProcessChart from './ProcessChart' +import PolarBarChart from './PolarBarChart' +import PieChart from './PieChart' +import AreaChart from './AreaChart' +import LineChart from './LineChart' + +function getChartsToken(basicToken, mapToken, modelToken) { + return { + BarChart: BarChart(modelToken), + BubbleChart: BubbleChart(basicToken, mapToken), + FunnelChart: FunnelChart(basicToken, mapToken), + GraphTreeChart: GraphTreeChart(basicToken, mapToken), + HillChart: HillChart(basicToken, mapToken), + JadeJueChart: JadeJueChart(basicToken, mapToken), + TreeChart: TreeChart(basicToken, mapToken), + SunburstChart: SunburstChart(basicToken, mapToken), + SankeyChart: SankeyChart(basicToken, mapToken), + RadarChart: RadarChart(modelToken), + ProcessChart: ProcessChart(basicToken, mapToken), + PolarBarChart: PolarBarChart(basicToken, mapToken), + PieChart: PieChart(modelToken), + AreaChart: AreaChart(modelToken), + LineChart: LineChart(modelToken) + } +} + +export default getChartsToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/index.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/index.ts new file mode 100644 index 000000000..b7206295d --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/basic/index.ts @@ -0,0 +1,8 @@ +import basic from './basicToken' +import getChartsToken from './chartsToken' +import { getRadius } from './basicToken/border' +import { getFont } from './basicToken/font' +import { getSpace } from './basicToken/space' + +export default basic +export { getChartsToken, getRadius, getFont, getSpace } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/dark/basicToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/dark/basicToken.ts new file mode 100644 index 000000000..53c43b67f --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/dark/basicToken.ts @@ -0,0 +1,10 @@ +import basic from '../../basic' +import dark from '../../color/bpit/dark' + +// 覆盖基础的色值 +const basicToken = { + ...basic, + ...dark +} + +export default basicToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/dark/chartsToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/dark/chartsToken.ts new file mode 100644 index 000000000..b698a9142 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/dark/chartsToken.ts @@ -0,0 +1,10 @@ +import basicToken from './basicToken' +import mapToken from './mapToken' +import modelToken from './modelToken' +import { getChartsToken } from '../../basic' + +const chartsToken = { + ...getChartsToken(basicToken, mapToken, modelToken) +} + +export default chartsToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/dark/index.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/dark/index.ts new file mode 100644 index 000000000..bbfe9e473 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/dark/index.ts @@ -0,0 +1,14 @@ +import basicToken from './basicToken' +import modelToken from './modelToken' +import chartsToken from './chartsToken' +import { colorState, colorGroup } from '../../color/bpit/dark' + +const bpitDark = { + colorGroup, + colorState, + ...modelToken, + ...chartsToken +} + +export default bpitDark +export { basicToken as bpitDarkBasicToken } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/dark/mapToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/dark/mapToken.ts new file mode 100644 index 000000000..02878f794 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/dark/mapToken.ts @@ -0,0 +1,9 @@ +import basicToken from './basicToken' +import getMapToken from '../getMapToken' + +// 获取新的mapToken +const mapToken = { + ...getMapToken(basicToken, false) +} + +export default mapToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/dark/modelToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/dark/modelToken.ts new file mode 100644 index 000000000..80ca08321 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/dark/modelToken.ts @@ -0,0 +1,8 @@ +import mapToken from './mapToken' +import getModelToken from '../getModelToken' + +const modelToken = { + ...getModelToken(mapToken) +} + +export default modelToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/getMapToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/getMapToken.ts new file mode 100644 index 000000000..262b872f0 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/getMapToken.ts @@ -0,0 +1,149 @@ +import { codeToRGB } from '../../../util/color' + +function getMapToken(basicToken, light = true) { + const { + colorGray0, + colorGray30, + colorGray50, + colorGray60, + colorGray90, + colorTP, + fontSizeSM, + fontSize, + lineWidth, + lineWidthXL, + lineWidthXXL, + lineWidthSM, + lineLength, + spaceLG, + space, + lineTypeSolid, + borderZero, + lineWidthXXXL, + borderSM, + borderRadiusSM, + lineHeightSM, + border, + lineWidthLG, + lineWidthZero, + borderLG, + borderRadiusZero, + fontSizeLG + } = basicToken + + return { + // 主色 + colorPrimary: light ? colorGray0 : colorGray0, // 确定 + // 初级底色 + colorBg: light ? colorGray0 : colorGray0, // 确定 + // 次级背景色 + colorSubg: light ? colorGray0 : colorGray0, + // 主要文本色 + colorText: light ? colorGray90 : colorGray90, // 确定 + // 次级文本色 + colorSubtext: light ? colorGray30 : colorGray30, // 确定 + // 禁用文本色 + colorDisabledText: light ? colorGray50 : colorGray50, // ---- + // 控件激活色(legend相关的颜色) + colorActive: light ? colorGray30 : colorGray30, // 确定 + // 控件失效色 + colorInactive: light ? colorGray50 : colorGray50, // --- + // 坐标轴线颜色 + colorAxisLine: light ? codeToRGB(colorGray90, 0.1) : codeToRGB(colorGray90, 0.1), // 确定 + // 刻度线颜色 + colorAxisTickLine: light ? codeToRGB(colorGray90, 0.1) : codeToRGB(colorGray90, 0.1), // 确定 + // 分隔线颜色 + colorAxisSplitLine: light ? codeToRGB(colorGray90, 0.1) : codeToRGB(colorGray90, 0.1), // 确定 + // 坐标轴指示器悬浮线 + colorAxisPointerLine: light ? colorGray60 : colorGray60, // 确定 + // 透明边框色 + colorBorderTP: light ? colorTP : colorTP, + // 基础边框色 + colorBorder: light ? colorGray0 : colorGray0, + // 文本透明 + colorTextTP: light ? colorTP : colorTP, + // 面积区域透明 + colorAreaTP: light ? colorTP : colorTP, + // 图元透明 + colorItemTP: light ? colorTP : colorTP, + // 指示器阴影 + colorAxisPointerShadow: light ? codeToRGB(colorGray90, 0.08) : codeToRGB(colorGray90, 0.08), + // 主文本字号 + textFontSize: fontSize, // 确定 + // 次级文本字号 + subtextFontSize: fontSizeSM, // 确定 + titleFontSize: fontSize * 2, + subtitleFontSize: fontSizeLG, + // 坐标轴 2 + axisLineWidth: lineWidth, // 确定 + // 刻度线 2 + axisTickLineWidth: lineWidth, // 确定 + // 分隔线1 + axisSplitLineWidth: lineWidth, // 确定 + + axisPointerLineWidth: lineWidthSM, // 确定 + + markLineWidth: lineWidthSM, + + markLineEmphasisWidth: lineWidthSM, + + axisLineType: lineTypeSolid, + + axisTickLineType: lineTypeSolid, + + axisSplitLineType: lineTypeSolid, + + axisPointerLineType: lineTypeSolid, + + borderWidthZero: borderZero, + + // 名称的间距 + nameGap: spaceLG, + axisLabelGap: space, + titleGap: spaceLG, + // 容器的间距 + containerGap: spaceLG, + containerBoderRadius: space, + // 图例的间距 + legendGap: space * 7, + // 图元大小 + symbolSize: 10, + symbolSizeSM: 8, + symbolSizeLG: 12, + symbolSizeXS: 6, + // 文本距离 + labelDistance: 10, + labelDistanceLG: 24, + // 柱条的宽度 + barWidth: lineWidthXL, + // 柱条的宽度大 + barWidthLG: lineWidthXXL, + // 堆叠进度图宽度 20 + barWidthStack: lineWidthXXXL, + barBorderRadiusZero: borderRadiusZero, + // series里面的labelLine的长度 + labelLineLength: lineLength, + + // series.itemStyle + itemBorderWidthSM: borderSM, + itemBorderWidth: border, + itemBorderWidthLG: borderLG, + itemBorderWidthZero: borderZero, + itemBorderRadiusSM: borderRadiusSM, + itemBorderColor: light ? colorGray0 : colorGray0, + // series.label + labelColor: light ? colorGray90 : colorGray90, + labelFontSizeSM: fontSizeSM, + // series.rich + richFontSizeSM: fontSizeSM, + richLineHeight: lineHeightSM * fontSizeSM, + richLineHeightSM: fontSizeSM, + // series.lineStyle + lineStyleWidth: lineWidth, + lineStyleWidthLG: lineWidthLG, + lineStyleWidthZero: lineWidthZero + } +} + +export default getMapToken +export { getMapToken as getBpitMapToken } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/getModelToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/getModelToken.ts new file mode 100644 index 000000000..e5f63c4a1 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/getModelToken.ts @@ -0,0 +1,264 @@ +function getModelToken(mapToken) { + const { + colorPrimary, + colorText, + colorSubtext, + colorAxisLine, + colorAxisTickLine, + colorAxisSplitLine, + axisLineWidth, + axisTickLineWidth, + axisSplitLineWidth, + axisLineType, + axisTickLineType, + axisSplitLineType, + textFontSize, + subtextFontSize, + nameGap, + titleGap, + axisLabelGap, + containerGap, + labelLineLength, + borderWidthZero, + colorSubg, + colorAxisPointerShadow, + colorAxisPointerLine, + axisPointerLineWidth, + axisPointerLineType, + legendGap, + markLineWidth, + markLineEmphasisWidth, + colorTextTP, + colorActive, + colorInactive, + itemBorderWidthSM, + itemBorderRadiusSM, + labelColor, + labelFontSizeSM, + colorItemTP, + barWidth, + colorBorderTP, + symbolSize, + symbolSizeSM, + symbolSizeLG, + colorAreaTP, + richFontSizeSM, + richLineHeight, + richLineHeightSM, + itemBorderWidth, + lineStyleWidth, + lineStyleWidthLG, + lineStyleWidthZero, + itemBorderWidthLG, + itemBorderWidthZero, + itemBorderColor, + labelDistanceLG, + symbolSizeXS, + containerBoderRadius, + barBorderRadiusZero, + titleFontSize, + subtitleFontSize + } = mapToken + return { + // 主文本 + + tooltipAxisPointerTextColor: colorText, + // 圆盘图Serieslabel + seriesLabelTextColor: colorText, + + // 次要文本 + + titleTextColor: colorText, + titleSubTextColor: colorSubtext, + legendTextColor: colorSubtext, + legendTextRichColor: colorSubtext, + legendRichTextColor: colorSubtext, + xAxisNameColor: colorSubtext, + xAxisLabelColor: colorSubtext, + yAxisNameColor: colorSubtext, + yAxisLabelColor: colorSubtext, + radarAxisNameColor: colorSubtext, + radarAxisLabelColor: colorSubtext, + angleAxisLabelColor: colorSubtext, + // 轴线的name字号 + + xAxisNameFontSize: textFontSize, + yAxisNameFontSize: textFontSize, + radiusAxisLabelColor: colorSubtext, + tooltipTextFontSize: textFontSize, + titleTextFontSize: titleFontSize, + titleSubtextFontSize: subtitleFontSize, + // 轴线的类型 + + xAxisLineType: axisLineType, + xAxisTickLineType: axisTickLineType, + xAxisSplitLineType: axisSplitLineType, + + yAxisLineType: axisLineType, + yAxisTickLineType: axisTickLineType, + yAxisSplitLineType: axisSplitLineType, + + angleAxisLineType: axisLineType, + angleAxisTickLineType: axisTickLineType, + angleAxisSplitLineType: axisSplitLineType, + radiusAxisLineType: axisLineType, + radiusAxisTickLineType: axisTickLineType, + radiusAxisSplitLineType: axisSplitLineType, + tooltipAxisPointerLineType: axisPointerLineType, + + // 次要文本的字号 + + xAxisLabelFontSize: subtextFontSize, + yAxisLabelFontSize: subtextFontSize, + legendTextFontSize: subtextFontSize, + legendTextRichFontSize: subtextFontSize, + angleAxisLabelFontSize: subtextFontSize, + radiusAxisLabelFontSize: subtextFontSize, + + // 轴线的颜色 + + xAxisLineColor: colorAxisLine, + xAxisTickLineColor: colorAxisTickLine, + xAxisSplitLineColor: colorAxisSplitLine, + + yAxisLineColor: colorAxisLine, + yAxisTickLineColor: colorAxisTickLine, + yAxisSplitLineColor: colorAxisSplitLine, + + radarAxisLineColor: colorAxisLine, + radarAxisTickLineColor: colorAxisTickLine, + radarSplitLineColor: colorAxisSplitLine, + + angleAxisLineColor: colorAxisLine, + angleAxisTickLineColor: colorAxisTickLine, + angleAxisSplitLineColor: colorAxisSplitLine, + + radiusAxisLineColor: colorAxisLine, + radiusAxisTickLineColor: colorAxisTickLine, + radiusAxisSplitLineColor: colorAxisSplitLine, + + tooltipAxisPointerLineColor: colorAxisPointerLine, + // 圆盘图的serieslabel线 + seriesLabelLineColor: colorAxisSplitLine, + + // 轴线的粗细 + + xAxisLineWidth: axisLineWidth, + xAxisTickLineWidth: axisTickLineWidth, + xAxisSplitLineWidth: axisSplitLineWidth, + + yAxisLineWidth: axisLineWidth, + yAxisTickLineWidth: axisTickLineWidth, + yAxisSplitLineWidth: axisSplitLineWidth, + + radarAxisLineWidth: axisLineWidth, + radarAxisTickLineWidth: axisTickLineWidth, + radarSplitLineWidth: axisSplitLineWidth, + + radiusAxisLineWidth: axisLineWidth, + radiusAxisTickLineWidth: axisTickLineWidth, + radiusAxisSplitLineWidth: axisSplitLineWidth, + + angleAxisLineWidth: axisLineWidth, + angleAxisTickLineWidth: axisTickLineWidth, + angleAxisSplitLineWidth: axisSplitLineWidth, + + tooltipAxisPointerLineWidth: axisPointerLineWidth, + + // 轴线名称的间距 + + xAxisNameGap: nameGap, + yAxisNameGap: nameGap, + titleItemGap: titleGap, + radiusAxisLabelGap: axisLabelGap, + // 圆盘图的labelline的长度 + seriesLabelLineLength: labelLineLength, // 待讨论 + + // tooltip的间距 + + // 垂直 + tooltipSpaceVertical: containerGap, + // 水平 + tooltipSpaceHorizontal: containerGap * 2, + + // 背景色 + + tooltipBg: colorSubg, + tooltipFontColor: colorText, + tooltipBorderRaduis: containerBoderRadius, + // 边框相关 + + // 圆盘图 + seriesItemStyleBorderWidthZero: borderWidthZero, + + legendInactiveBorderWidth: borderWidthZero, + legendBorderWidth: borderWidthZero, + legendItemStyleBorderWidth: borderWidthZero, + + // 图例相关 + + legendItemGap: legendGap, + legendCircleItemWidth: 24, + legendCircleItemHeight: 6, + legendReactItemWidth: 12, + legendReactItemHeight: 2, + legendInactiveColor: colorInactive, + legendTextPadding: [containerGap / 2, 0, 0, 0], + legendTextRichPadding: [containerGap / 2, 0, 0, 0], + legendPageTextColor: colorText, + legendInactiveBorderColor: colorPrimary, + legendPageIconInactiveColor: colorInactive, + legendPageIconColor: colorActive, + + tooltipAxisPointerShadow: colorAxisPointerShadow, + + // 圆盘图 + seriesEmptyCircleColor: colorSubg, + + seriesMarkLineWidth: markLineWidth, + + seriesMarkLineEmphasisWidth: markLineEmphasisWidth, + + markPointLabelColor: colorTextTP, + + lineColorTp: colorTextTP, + + // visualMap + visualMapPiecesColor: colorTextTP, + visualMapDashColor: colorPrimary, + + colorPrimary, + + // series系列 + itemBorderWidthSM, + itemBorderWidthZero, + itemBorderWidth, + itemBorderWidthLG, + itemBorderColor, + itemBorderColorTP: colorBorderTP, + itemBorderRadiusSM, + itemBorderRadiusZero: barBorderRadiusZero, + itemColorTP: colorItemTP, + symbolSize, + symbolSizeSM, + symbolSizeLG, + symbolSizeXS, + labelColor, + labelFontSizeSM, + labelDistanceLG, + richFontSizeSM, + richLineHeight, + richLineHeightSM, + lineStyleWidth, + lineStyleWidthLG, + lineStyleWidthZero, + lineColor: colorText, + barWidth, + areaColorTP: colorAreaTP, + circleColor: colorAxisLine, + markLineColorTP: colorBorderTP + } +} + +export default getModelToken +export { getModelToken as getBpitModelToken } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/light/basicToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/light/basicToken.ts new file mode 100644 index 000000000..9a0951c3f --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/light/basicToken.ts @@ -0,0 +1,10 @@ +import basic from '../../basic' +import light from '../../color/bpit/light' + +// 覆盖基础的色值 +const basicToken = { + ...basic, + ...light +} + +export default basicToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/light/chartsToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/light/chartsToken.ts new file mode 100644 index 000000000..b698a9142 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/light/chartsToken.ts @@ -0,0 +1,10 @@ +import basicToken from './basicToken' +import mapToken from './mapToken' +import modelToken from './modelToken' +import { getChartsToken } from '../../basic' + +const chartsToken = { + ...getChartsToken(basicToken, mapToken, modelToken) +} + +export default chartsToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/light/index.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/light/index.ts new file mode 100644 index 000000000..08b8e60e3 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/light/index.ts @@ -0,0 +1,14 @@ +import basicToken from './basicToken' +import modelToken from './modelToken' +import chartsToken from './chartsToken' +import { colorState, colorGroup } from '../../color/bpit/light' + +const bpitLight = { + colorGroup, + colorState, + ...modelToken, + ...chartsToken +} + +export default bpitLight +export { basicToken as bpitLightBasicToken } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/light/mapToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/light/mapToken.ts new file mode 100644 index 000000000..79d0e2258 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/light/mapToken.ts @@ -0,0 +1,9 @@ +import basicToken from './basicToken' +import getMapToken from '../getMapToken' + +// 获取新的mapToken +const mapToken = { + ...getMapToken(basicToken) +} + +export default mapToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/light/modelToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/light/modelToken.ts new file mode 100644 index 000000000..80ca08321 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpit/light/modelToken.ts @@ -0,0 +1,8 @@ +import mapToken from './mapToken' +import getModelToken from '../getModelToken' + +const modelToken = { + ...getModelToken(mapToken) +} + +export default modelToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpitDark.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpitDark.ts new file mode 100644 index 000000000..33f0db085 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpitDark.ts @@ -0,0 +1,55 @@ +// 推荐色组 +const group = [ + '#1f55b5', // rgba(31 ,85 ,181,1) + '#278661', // rgba(39 ,134,97 ,1) + '#8a21bc', // rgba(138,33 ,188,1) + '#26616b', // rgba(38 ,97 ,107,1) + '#b98c1d', // rgba(185,140,29 ,1) + '#745ef7' // rgba(116,94 ,247,1) +] + +// 图表基础色组 +const base = { + // 主色 + main: '#191919', + // 主文本色 + font: '#f5f5f5', + // 次级文本色 + subfont: '#4e4e4e', + // 刻度线 + axis: 'rgba(238,238,238,0.1)', + // 次级刻度线 + subaxis: 'rgba(255,255,255,0.2)', + // 刻度文本 + axislabel: '#bbbbbb', + // 初级底色,颜色最亮 + bg: '#272727', + // 次级底色,颜色稍亮 + subg: '#2e2e2e' +} + +// 图表状态色组 +const state = { + // 错误色 + error: '#F43146', + // 告警色 + warning: '#EC6F1A', + // 次要告警色 + subwarning: '#EEBA18', + // 成功色 + success: '#0D9458', + // 提示色 + prompt: '#5990FD', + // 失效 + fail: '#818181' +} + +const theme = { + color: { + group, + base, + state + } +} + +export default theme diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpitLight.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpitLight.ts new file mode 100644 index 000000000..303eb7c38 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/bpitLight.ts @@ -0,0 +1,61 @@ +// 推荐色组 +const group = [ + '#2070F3', + '#55CCD9', + '#715AFB', + '#8AC8F3', + '#EB74DF', + '#87C859', + '#D19F00', + '#36C18D', + '#CB8EFB', + '#ED448A', + '#F7C45C', + '#E7434A' +] + +// 图表基础色组 +const base = { + // 主色 + main: '#FFFFFF', + // 主文本色 + font: '#191919', + // 次级文本色 + subfont: '#bbbbbb', + // 刻度线 + axis: '#eeeeee', + // 次级刻度线 + subaxis: 'rgba(0,0,0,0.08)', + // 刻度文本 + axislabel: '#4e4e4e', + // 初级底色,颜色最亮 + bg: '#ffffff', + // 次级底色,颜色稍亮 + subg: '#e8e8e8' +} + +// 图表状态色组 +const state = { + // 错误色 + error: '#E02128', + // 告警色 + warning: '#F4840C', + // 次要告警色 + subwarning: '#FCC800', + // 成功色 + success: '#09AA71', + // 提示色 + prompt: '#2070F3', + // 失效 + fail: '#AEAEAE' +} + +const theme = { + color: { + group, + base, + state + } +} + +export default theme diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/dark/basicToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/dark/basicToken.ts new file mode 100644 index 000000000..1b8ee43d2 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/dark/basicToken.ts @@ -0,0 +1,10 @@ +import basic from '../../basic' +import dark from '../../color/cloud/dark' + +// 覆盖基础的色值 +const basicToken = { + ...basic, + ...dark +} + +export default basicToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/dark/chartsToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/dark/chartsToken.ts new file mode 100644 index 000000000..b698a9142 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/dark/chartsToken.ts @@ -0,0 +1,10 @@ +import basicToken from './basicToken' +import mapToken from './mapToken' +import modelToken from './modelToken' +import { getChartsToken } from '../../basic' + +const chartsToken = { + ...getChartsToken(basicToken, mapToken, modelToken) +} + +export default chartsToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/dark/index.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/dark/index.ts new file mode 100644 index 000000000..f0c79dc6e --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/dark/index.ts @@ -0,0 +1,19 @@ +import basicToken from './basicToken' +import modelToken from './modelToken' +import chartsToken from './chartsToken' +import { colorState, colorGroup } from '../../color/cloud/dark' + +// import { getMapToken } from './mapToken'; + +const cloudDark = { + colorGroup, + colorState, + ...modelToken, + ...chartsToken +} + +export default cloudDark +export { + basicToken as cloudDarkBasicToken + // getMapToken as getCloudDarkMapToken +} diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/dark/mapToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/dark/mapToken.ts new file mode 100644 index 000000000..02878f794 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/dark/mapToken.ts @@ -0,0 +1,9 @@ +import basicToken from './basicToken' +import getMapToken from '../getMapToken' + +// 获取新的mapToken +const mapToken = { + ...getMapToken(basicToken, false) +} + +export default mapToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/dark/modelToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/dark/modelToken.ts new file mode 100644 index 000000000..80ca08321 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/dark/modelToken.ts @@ -0,0 +1,8 @@ +import mapToken from './mapToken' +import getModelToken from '../getModelToken' + +const modelToken = { + ...getModelToken(mapToken) +} + +export default modelToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/getMapToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/getMapToken.ts new file mode 100644 index 000000000..2c6884aca --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/getMapToken.ts @@ -0,0 +1,154 @@ +import { codeToRGB } from '../../../util/color' + +function getMapToken(basicToken, light = true) { + const { + colorGray0, + colorGray30, + colorGray40, + colorGray50, + colorGray60, + colorGray80, + colorGray70, + colorGray90, + colorTP, + fontSizeSM, + fontSize, + lineWidthXL, + lineWidthXXL, + lineWidthSM, + lineLength, + spaceLG, + space, + lineTypeSolid, + lineTypeDashedLG, + borderZero, + lineWidthXXXL, + borderSM, + borderRadiusSM, + lineHeightSM, + border, + lineWidthLG, + lineWidthZero, + borderLG, + lineWidth, + borderRadiusZero, + fontSizeLG + } = basicToken + + return { + // 主色 + colorPrimary: light ? colorGray0 : colorGray0, // D + // 初级底色 + colorBg: light ? colorGray0 : colorGray0, // D + // 次级底色 + colorSubg: light ? colorGray0 : colorGray0, // D + // 主要文本色 + colorText: light ? colorGray90 : colorGray40, // 确定 + // 次级文本色 + colorSubtext: light ? colorGray60 : colorGray50, // 确定 + // 禁用文本色 + colorDisabledText: light ? colorGray50 : colorGray50, // D + // 控件激活色(legend相关的颜色) + colorActive: light ? colorGray60 : colorGray60, // D + // 控件失效色 + colorInactive: light ? colorGray50 : colorGray70, // 确定 + // 坐标轴线颜色 + colorAxisLine: light ? colorGray30 : colorGray80, // 确定 + // 刻度线颜色 + colorAxisTickLine: light ? colorGray30 : colorGray80, // 确定 + // 分隔线颜色 + colorAxisSplitLine: light ? colorGray30 : colorGray80, // 确定 + // 坐标轴指示器悬浮线 + colorAxisPointerLine: light ? colorGray60 : colorGray70, // 确定 + // 透明边框色 + colorBorderTP: colorTP, + // 基础边框色 + colorBorder: light ? colorGray0 : colorGray0, // D + // 文本透明 + colorTextTP: light ? colorTP : colorTP, // D + // 面积区域透明 + colorAreaTP: light ? colorTP : colorTP, // D + // 图元透明 + colorItemTP: light ? colorTP : colorTP, // D + // 指示器阴影 + colorAxisPointerShadow: light ? codeToRGB(colorGray90, 0.08) : codeToRGB(colorGray90, 0.08), // D + // 主文本字号 + textFontSize: fontSize, // 确定 + // 次级文本字号 + subtextFontSize: fontSizeSM, // 确定 + titleFontSize: fontSize * 2, + subtitleFontSize: fontSizeLG, + // 坐标轴 1 + axisLineWidth: lineWidthSM, + // 刻度线 1 + axisTickLineWidth: lineWidthSM, + // 分隔线 + axisSplitLineWidth: lineWidthSM, + + axisPointerLineWidth: lineWidthSM, + + markLineWidth: lineWidthSM, + + markLineEmphasisWidth: lineWidthSM, + + axisLineType: lineTypeSolid, + + axisTickLineType: lineTypeSolid, + + axisSplitLineType: lineTypeDashedLG, + + axisPointerLineType: lineTypeSolid, + + borderWidthZero: borderZero, + + // 名称的间距 + nameGap: spaceLG, + axisLabelGap: space, + titleGap: spaceLG, + // 容器的间距 + containerGap: spaceLG, + containerBoderRadius: space, + + // 图例的间距 + legendGap: space * 7, + // 图元大小 + symbolSize: 10, + symbolSizeSM: 8, + symbolSizeLG: 12, + symbolSizeXS: 6, + // 文本距离 + labelDistance: 10, + labelDistanceLG: 24, + // 柱条的宽度 + barWidth: lineWidthXL, + // 柱条的宽度大 + barWidthLG: lineWidthXXL, + // 堆叠进度图宽度 20 + barWidthStack: lineWidthXXXL, + barBorderRadiusZero: borderRadiusZero, + // series里面的labelLine的长度 + labelLineLength: lineLength, + + // series.itemStyle + itemBorderWidthSM: borderSM, + itemBorderWidth: border, + itemBorderWidthLG: borderLG, + itemBorderWidthZero: borderZero, + itemBorderRadiusSM: borderRadiusSM, + itemBorderColor: light ? colorGray0 : colorGray90, // 确定 + // series.label + labelColor: light ? colorGray90 : colorGray40, // 确定 + labelFontSizeSM: fontSizeSM, + // series.rich + richFontSizeSM: fontSizeSM, + richLineHeight: lineHeightSM * fontSizeSM, + richLineHeightSM: fontSizeSM, + // series.lineStyle + lineStyleWidth: lineWidth, + lineStyleWidthLG: lineWidthLG, + lineStyleWidthZero: lineWidthZero + } +} + +export default getMapToken +export { getMapToken as getCloudMapToken } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/getModelToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/getModelToken.ts new file mode 100644 index 000000000..70d0aa8c3 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/getModelToken.ts @@ -0,0 +1,266 @@ +function getModelToken(mapToken) { + const { + colorPrimary, + colorText, + colorSubtext, + colorAxisLine, + colorAxisTickLine, + colorAxisSplitLine, + axisLineWidth, + axisTickLineWidth, + axisSplitLineWidth, + axisLineType, + axisTickLineType, + axisSplitLineType, + textFontSize, + subtextFontSize, + nameGap, + titleGap, + axisLabelGap, + containerGap, + labelLineLength, + borderWidthZero, + colorSubg, + colorAxisPointerShadow, + colorAxisPointerLine, + axisPointerLineWidth, + axisPointerLineType, + legendGap, + markLineWidth, + markLineEmphasisWidth, + colorTextTP, + colorActive, + colorInactive, + itemBorderWidthSM, + itemBorderRadiusSM, + labelColor, + labelFontSizeSM, + colorItemTP, + barWidth, + colorBorderTP, + symbolSize, + symbolSizeSM, + symbolSizeLG, + colorAreaTP, + richFontSizeSM, + richLineHeight, + richLineHeightSM, + itemBorderWidth, + lineStyleWidth, + lineStyleWidthLG, + lineStyleWidthZero, + itemBorderWidthLG, + itemBorderWidthZero, + itemBorderColor, + labelDistanceLG, + symbolSizeXS, + containerBoderRadius, + barBorderRadiusZero, + titleFontSize, + subtitleFontSize + } = mapToken + return { + // 主文本 + + tooltipAxisPointerTextColor: colorText, + // 圆盘图Serieslabel + seriesLabelTextColor: colorText, + + // 次要文本 + + titleTextColor: colorText, + titleSubTextColor: colorSubtext, + legendTextColor: colorSubtext, + legendTextRichColor: colorSubtext, + legendRichTextColor: colorSubtext, + xAxisNameColor: colorSubtext, + xAxisLabelColor: colorSubtext, + yAxisNameColor: colorSubtext, + yAxisLabelColor: colorSubtext, + radarAxisNameColor: colorSubtext, + radarAxisLabelColor: colorSubtext, + angleAxisLabelColor: colorSubtext, + // 轴线的name字号 + + xAxisNameFontSize: textFontSize, + yAxisNameFontSize: textFontSize, + radiusAxisLabelColor: colorSubtext, + tooltipTextFontSize: textFontSize, + titleTextFontSize: titleFontSize, + titleSubtextFontSize: subtitleFontSize, + // 轴线的类型 + + xAxisLineType: axisLineType, + xAxisTickLineType: axisTickLineType, + xAxisSplitLineType: axisSplitLineType, + + yAxisLineType: axisLineType, + yAxisTickLineType: axisTickLineType, + yAxisSplitLineType: axisSplitLineType, + + angleAxisLineType: axisLineType, + angleAxisTickLineType: axisTickLineType, + //* *********备注极坐标系的角度轴的分割线需要特别写成实线,和直角坐标系的使用有区别****** + angleAxisSplitLineType: axisLineType, + radiusAxisLineType: axisLineType, + radiusAxisTickLineType: axisTickLineType, + radiusAxisSplitLineType: axisSplitLineType, + tooltipAxisPointerLineType: axisPointerLineType, + + // 次要文本的字号 + + xAxisLabelFontSize: subtextFontSize, + yAxisLabelFontSize: subtextFontSize, + legendTextFontSize: subtextFontSize, + legendTextRichFontSize: subtextFontSize, + angleAxisLabelFontSize: subtextFontSize, + radiusAxisLabelFontSize: subtextFontSize, + + // 轴线的颜色 + + xAxisLineColor: colorAxisLine, + xAxisTickLineColor: colorAxisTickLine, + xAxisSplitLineColor: colorAxisSplitLine, + + yAxisLineColor: colorAxisLine, + yAxisTickLineColor: colorAxisTickLine, + yAxisSplitLineColor: colorAxisSplitLine, + + radarAxisLineColor: colorAxisLine, + radarAxisTickLineColor: colorAxisTickLine, + radarSplitLineColor: colorAxisSplitLine, + + angleAxisLineColor: colorAxisLine, + angleAxisTickLineColor: colorAxisTickLine, + angleAxisSplitLineColor: colorAxisSplitLine, + + radiusAxisLineColor: colorAxisLine, + radiusAxisTickLineColor: colorAxisTickLine, + radiusAxisSplitLineColor: colorAxisSplitLine, + + tooltipAxisPointerLineColor: colorAxisPointerLine, + // 圆盘图的serieslabel线 + seriesLabelLineColor: colorAxisSplitLine, + + // 轴线的粗细 + + xAxisLineWidth: axisLineWidth, + xAxisTickLineWidth: axisTickLineWidth, + xAxisSplitLineWidth: axisSplitLineWidth, + + yAxisLineWidth: axisLineWidth, + yAxisTickLineWidth: axisTickLineWidth, + yAxisSplitLineWidth: axisSplitLineWidth, + + radarAxisLineWidth: axisLineWidth, + radarAxisTickLineWidth: axisTickLineWidth, + radarSplitLineWidth: axisSplitLineWidth, + + radiusAxisLineWidth: axisLineWidth, + radiusAxisTickLineWidth: axisTickLineWidth, + radiusAxisSplitLineWidth: axisSplitLineWidth, + + angleAxisLineWidth: axisLineWidth, + angleAxisTickLineWidth: axisTickLineWidth, + angleAxisSplitLineWidth: axisSplitLineWidth, + + tooltipAxisPointerLineWidth: axisPointerLineWidth, + + // 轴线名称的间距 + + xAxisNameGap: nameGap, + yAxisNameGap: nameGap, + titleItemGap: titleGap, + radiusAxisLabelGap: axisLabelGap, + // 圆盘图的labelline的长度 + seriesLabelLineLength: labelLineLength, // 待讨论 + + // tooltip的间距 + + // 垂直 + tooltipSpaceVertical: containerGap, + // 水平 + tooltipSpaceHorizontal: containerGap * 2, + + // 背景色 + + tooltipBg: colorSubg, + tooltipFontColor: colorText, + tooltipBorderRaduis: containerBoderRadius, + // 边框相关 + + // 圆盘图 + seriesItemStyleBorderWidthZero: borderWidthZero, + // seriesItemStyleBorderWidth: + + legendInactiveBorderWidth: borderWidthZero, + legendBorderWidth: borderWidthZero, + legendItemStyleBorderWidth: borderWidthZero, + + // 图例相关 + + legendItemGap: legendGap, + legendCircleItemWidth: 24, + legendCircleItemHeight: 6, + legendReactItemWidth: 12, + legendReactItemHeight: 2, + legendInactiveColor: colorInactive, + legendTextPadding: [containerGap / 2, 0, 0, 0], + legendTextRichPadding: [containerGap / 2, 0, 0, 0], + legendPageTextColor: colorText, + legendInactiveBorderColor: colorPrimary, + legendPageIconInactiveColor: colorInactive, + legendPageIconColor: colorActive, + + tooltipAxisPointerShadow: colorAxisPointerShadow, + + // 圆盘图 + seriesEmptyCircleColor: colorSubg, + + seriesMarkLineWidth: markLineWidth, + + seriesMarkLineEmphasisWidth: markLineEmphasisWidth, + + markPointLabelColor: colorTextTP, + + lineColorTp: colorTextTP, + + // visualMap + visualMapPiecesColor: colorTextTP, + visualMapDashColor: colorPrimary, + + colorPrimary, + + // series系列 + itemBorderWidthSM, + itemBorderWidthZero, + itemBorderWidth, + itemBorderWidthLG, + itemBorderColor, + itemBorderColorTP: colorBorderTP, + itemBorderRadiusSM, + itemBorderRadiusZero: barBorderRadiusZero, + itemColorTP: colorItemTP, + symbolSize, + symbolSizeSM, + symbolSizeLG, + symbolSizeXS, + labelColor, + labelFontSizeSM, + labelDistanceLG, + richFontSizeSM, + richLineHeight, + richLineHeightSM, + lineStyleWidth, + lineStyleWidthLG, + lineStyleWidthZero, + lineColor: colorText, + barWidth, + areaColorTP: colorAreaTP, + circleColor: colorAxisLine, + markLineColorTP: colorBorderTP + } +} + +export default getModelToken +export { getModelToken as getCloudModelToken } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/light/basicToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/light/basicToken.ts new file mode 100644 index 000000000..313020184 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/light/basicToken.ts @@ -0,0 +1,10 @@ +import basic from '../../basic' +import light from '../../color/cloud/light' + +// 覆盖基础的色值 +const basicToken = { + ...basic, + ...light +} + +export default basicToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/light/chartsToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/light/chartsToken.ts new file mode 100644 index 000000000..b698a9142 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/light/chartsToken.ts @@ -0,0 +1,10 @@ +import basicToken from './basicToken' +import mapToken from './mapToken' +import modelToken from './modelToken' +import { getChartsToken } from '../../basic' + +const chartsToken = { + ...getChartsToken(basicToken, mapToken, modelToken) +} + +export default chartsToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/light/index.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/light/index.ts new file mode 100644 index 000000000..c3d68fd9b --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/light/index.ts @@ -0,0 +1,14 @@ +import basicToken from './basicToken' +import modelToken from './modelToken' +import chartsToken from './chartsToken' +import { colorState, colorGroup } from '../../color/cloud/light' + +const cloudLight = { + colorGroup, + colorState, + ...modelToken, + ...chartsToken +} + +export default cloudLight +export { basicToken as cloudLightBasicToken } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/light/mapToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/light/mapToken.ts new file mode 100644 index 000000000..79d0e2258 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/light/mapToken.ts @@ -0,0 +1,9 @@ +import basicToken from './basicToken' +import getMapToken from '../getMapToken' + +// 获取新的mapToken +const mapToken = { + ...getMapToken(basicToken) +} + +export default mapToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/light/modelToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/light/modelToken.ts new file mode 100644 index 000000000..80ca08321 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloud/light/modelToken.ts @@ -0,0 +1,8 @@ +import mapToken from './mapToken' +import getModelToken from '../getModelToken' + +const modelToken = { + ...getModelToken(mapToken) +} + +export default modelToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloudDark.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloudDark.ts new file mode 100644 index 000000000..ef7ee73a6 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloudDark.ts @@ -0,0 +1,55 @@ +// 推荐色组 ----- 当前华为云图表深色主题为暂行,后续待刷新 +const group = [ + '#1f55b5', // rgba(31 ,85 ,181,1) + '#278661', // rgba(39 ,134,97 ,1) + '#8a21bc', // rgba(138,33 ,188,1) + '#26616b', // rgba(38 ,97 ,107,1) + '#b98c1d', // rgba(185,140,29 ,1) + '#745ef7' // rgba(116,94 ,247,1) +] + +// 图表基础色组 +const base = { + // 主色 + main: '#191919', + // 主文本色 + font: '#f5f5f5', + // 次级文本色 + subfont: '#4e4e4e', + // 刻度线 + axis: 'rgba(238,238,238,0.1)', + // 次级刻度线 + subaxis: 'rgba(255,255,255,0.2)', + // 刻度文本 + axislabel: '#bbbbbb', + // 初级底色,颜色最亮 + bg: '#272727', + // 次级底色,颜色稍亮 + subg: '#2e2e2e' +} + +// 图表状态色组 +const state = { + // 错误色 + error: '#F43146', + // 告警色 + warning: '#EC6F1A', + // 次要告警色 + subwarning: '#EEBA18', + // 成功色 + success: '#0D9458', + // 提示色 + prompt: '#5990FD', + // 失效 + fail: '#818181' +} + +const theme = { + color: { + base, + group, + state + } +} + +export default theme diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloudLight.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloudLight.ts new file mode 100644 index 000000000..a1bc14665 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/cloudLight.ts @@ -0,0 +1,65 @@ +const tiBaseColor1 = '#1476FF' +const tiBaseColor2 = '#0BB8B2' +const tiBaseColor3 = '#6E51E0' +const tiBaseColor4 = '#5CB300' +const tiBaseColor5 = '#FCBE1E' +const tiBaseColor6 = '#33BCF2' +const tiBaseColor7 = '#BA53E6' +const tiBaseColor8 = '#EB4696' +// 推荐色组 +const group = [ + tiBaseColor1, + tiBaseColor2, + tiBaseColor3, + tiBaseColor4, + tiBaseColor5, + tiBaseColor6, + tiBaseColor7, + tiBaseColor8 +] + +// 图表基础色组 +const base = { + // 主色 + main: '#FFFFFF', + // 主文本色 + font: '#191919', + // 次级文本色 + subfont: '#bbbbbb', + // 刻度线 + axis: '#eeeeee', + // 次级刻度线 + subaxis: 'rgba(0,0,0,0.06)', + // 刻度文本 + axislabel: '#4e4e4e', + // 初级底色,颜色最亮 + bg: '#ffffff', + // 次级底色,颜色稍亮 + subg: '#f2f2f2' +} + +// 图表状态色组 +const state = { + // 错误色 + error: '#F23030', + // 告警色 + warning: '#FF8800', + // 次要告警色 + subwarning: '#F7D916', + // 成功色 + success: '#5CB300', + // 提示色 + prompt: '#1476FF', + // 失效 + fail: '#EBEBEB' +} + +const theme = { + color: { + group, + base, + state + } +} + +export default theme diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/bpit/dark.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/bpit/dark.ts new file mode 100644 index 000000000..0b3688113 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/bpit/dark.ts @@ -0,0 +1,30 @@ +/** bpit主题没有黑色主题,颜色中的灰阶暂行ict的灰阶,内置颜色组和状态色延用白色主题的 */ +import gray from './gray' + +// 图表的状态颜色 +const colorState = { + // 紧急色 + errorColor: '#E02128', + // 重要告警色 + warningColor: '#F4840C', + // 次要告警色 + subwarningColor: '#FCC800', + // 成功色 + successColor: '#09AA71', + // 提示色 + infoColor: '#2070F3', + // 失效 '#AEAEAE' + disabledColor: '#AEAEAE' +} + +// 图表内置的颜色组 +const colorGroup = ['#2070F3', '#55CCD9', '#715AFB', '#8AC8F3', '#EB74DF', '#87C859', '#D19F00'] + +const dark = { + ...gray, + colorGroup, + colorState +} + +export default dark +export { colorGroup, colorState } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/bpit/gray.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/bpit/gray.ts new file mode 100644 index 000000000..9e751829d --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/bpit/gray.ts @@ -0,0 +1,18 @@ +// bpit灰阶色彩(用于文本及相关颜色的选取) +const gray = { + colorGray0: '#FFFFFF', + colorGray5: '#F3F3F3', + colorGray10: '#DFDFDF', + colorGray20: '#C9C9C9', + colorGray30: '#AEAEAE', + colorGray40: '#939393', + colorGray50: '#777777', + colorGray60: '#595959', + colorGray70: '#393939', + colorGray80: '#2A2A2A', + colorGray90: '#191919', + colorGray100: '#000000', + colorTP: 'transparent' +} + +export default gray diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/bpit/light.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/bpit/light.ts new file mode 100644 index 000000000..638f126b8 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/bpit/light.ts @@ -0,0 +1,29 @@ +import gray from './gray' + +// 图表的状态颜色 +const colorState = { + // 紧急色 + errorColor: '#E02128', + // 重要告警色 + warningColor: '#F4840C', + // 次要告警色 + subwarningColor: '#FCC800', + // 成功色 + successColor: '#09AA71', + // 提示色 + infoColor: '#2070F3', + // 失效 '#AEAEAE' + disabledColor: gray.colorGray30 +} + +// 图表内置的颜色组 +const colorGroup = ['#2070F3', '#55CCD9', '#715AFB', '#8AC8F3', '#EB74DF', '#87C859', '#D19F00'] + +const light = { + ...gray, + colorGroup, + colorState +} + +export default light +export { colorGroup, colorState } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/cloud/dark.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/cloud/dark.ts new file mode 100644 index 000000000..70e48bd9f --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/cloud/dark.ts @@ -0,0 +1,30 @@ +/** cloud主题没有黑色主题,颜色中的灰阶暂行ict的灰阶,内置颜色组和状态色延用白色主题的 */ +import gray from './gray' + +// 图表的状态颜色 +const colorState = { + // 紧急色 + errorColor: '#F23030', + // 重要告警色 + warningColor: '#FF8800', + // 次要告警色 + subwarningColor: '#F7D916', + // 成功色 + successColor: '#5CB300', + // 提示色 + infoColor: '#1476FF', + // 失效 '#DBDBDB' + disabledColor: '#DBDBDB' +} + +// 图表内置的颜色组 +const colorGroup = ['#1476FF', '#0BB8B2', '#6E51E0', '#5CB300', '#FCBE1E', '#33BCF2', '#BA53E6', '#EB4696'] + +const dark = { + ...gray, + colorGroup, + colorState +} + +export default dark +export { colorGroup, colorState } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/cloud/gray.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/cloud/gray.ts new file mode 100644 index 000000000..0f69cc739 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/cloud/gray.ts @@ -0,0 +1,18 @@ +// cloud灰阶色彩(用于文本及相关颜色的选取) +const gray = { + colorGray0: '#FFFFFF', + colorGray5: '#FAFAFA', + colorGray10: '#F5F5F5', + colorGray20: '#F0F0F0', + colorGray30: '#E6E6E6', + colorGray40: '#DBDBDB', + colorGray50: '#C2C2C2', + colorGray60: '#808080', + colorGray70: '#595959', + colorGray80: '#333333', + colorGray90: '#191919', + colorGray100: '#000000', + colorTP: 'transparent' +} + +export default gray diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/cloud/light.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/cloud/light.ts new file mode 100644 index 000000000..2374a5dc7 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/cloud/light.ts @@ -0,0 +1,29 @@ +import gray from './gray' + +// 图表的状态颜色 +const colorState = { + // 紧急色 + errorColor: '#F23030', + // 重要告警色 + warningColor: '#FF8800', + // 次要告警色 + subwarningColor: '#F7D916', + // 成功色 + successColor: '#5CB300', + // 提示色 + infoColor: '#1476FF', + // 失效 '#C2C2C2' + disabledColor: gray.colorGray40 +} + +// 图表内置的颜色组 +const colorGroup = ['#1476FF', '#0BB8B2', '#6E51E0', '#5CB300', '#FCBE1E', '#33BCF2', '#BA53E6', '#EB4696'] + +const light = { + ...gray, + colorGroup, + colorState +} + +export default light +export { colorGroup, colorState } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/ict/dark.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/ict/dark.ts new file mode 100644 index 000000000..fe198b02e --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/ict/dark.ts @@ -0,0 +1,29 @@ +import gray from './gray' + +// 图表的状态颜色 +const colorState = { + // 紧急色 + errorColor: '#F43146', + // 重要告警色 + warningColor: '#EC6F1A', + // 次要告警色 + subwarningColor: '#EEBA18', + // 成功色 + successColor: '#0D9458', + // 提示色 + infoColor: '#5990FD', + // 失效 '#939393' + disabledColor: gray.colorGray40 +} + +// 图表内置的颜色组 +const colorGroup = ['#1F55B5', '#278661', '#8A21BC', '#26616B', '#B98C1D', '#745EF7', '#2A8290'] + +const dark = { + ...gray, + colorState, + colorGroup +} + +export default dark +export { colorState, colorGroup } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/ict/gray.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/ict/gray.ts new file mode 100644 index 000000000..92a0ce5fc --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/ict/gray.ts @@ -0,0 +1,18 @@ +/** 本色彩规范为ICT基础设施通用UI规范3.0**/ +const gray = { + colorGray0: '#FFFFFF', + colorGray5: '#F5F5F5', + colorGray10: '#EEEEEE', + colorGray20: '#BBBBBB', + colorGray30: '#939393', + colorGray40: '#818181', + colorGray50: '#676767', + colorGray60: '#4E4E4E', + colorGray70: '#393939', + colorGray80: '#272727', + colorGray90: '#191919', + colorGray100: '#000000', + colorTP: 'transparent' +} + +export default gray diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/ict/light.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/ict/light.ts new file mode 100644 index 000000000..c566b5dd9 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/color/ict/light.ts @@ -0,0 +1,29 @@ +import gray from './gray' + +// 图表的状态颜色 +const colorState = { + // 紧急色 + errorColor: '#F43146', + // 重要告警色 + warningColor: '#EC6F1A', + // 次要告警色 + subwarningColor: '#EEBA18', + // 成功色 + successColor: '#2DA769', + // 提示色 + infoColor: '#5990FD', + // 失效 '#818181' + disabledColor: gray.colorGray30 +} + +// 图表内置的颜色组 +const colorGroup = ['#6D8FF0', '#00A874', '#BD72F0', '#54BCCE', '#FDC000', '#9185F0', '#00A2B5'] + +const light = { + ...gray, + colorState, + colorGroup +} + +export default light +export { colorState, colorGroup } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/constants.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/constants.ts new file mode 100644 index 000000000..4e687d222 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/constants.ts @@ -0,0 +1,20 @@ +// 当前支持主题 +const THEMES = { + DARK: 'dark', + LIGHT: 'light', + BPIT_LIGHT: 'bpit-light', + BPIT_DARK: 'bpit-dark', + CLOUD_DARK: 'cloud-dark', + CLOUD_LIGHT: 'cloud-light' +} + +// 该值表示当前主题键名,内部使用,防止外部更改 +const CURRENT_THEME = Symbol('current_theme') + +// 默认主题 +const DEFAULT_THEME_NAME = THEMES.LIGHT + +const THEME_ERROR_TIP_MESSAGE = + 'Theme must be one of dark, light, cloud-light,cloud-dark, or the theme name registered for calling HuiCharts.registerTheme()' + +export { THEMES, CURRENT_THEME, DEFAULT_THEME_NAME, THEME_ERROR_TIP_MESSAGE } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/dark/basicToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/dark/basicToken.ts new file mode 100644 index 000000000..d36cd3837 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/dark/basicToken.ts @@ -0,0 +1,10 @@ +import basic from '../../basic' +import dark from '../../color/ict/dark' + +// 覆盖基础的色值 +const basicToken = { + ...basic, + ...dark +} + +export default basicToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/dark/chartsToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/dark/chartsToken.ts new file mode 100644 index 000000000..b698a9142 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/dark/chartsToken.ts @@ -0,0 +1,10 @@ +import basicToken from './basicToken' +import mapToken from './mapToken' +import modelToken from './modelToken' +import { getChartsToken } from '../../basic' + +const chartsToken = { + ...getChartsToken(basicToken, mapToken, modelToken) +} + +export default chartsToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/dark/index.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/dark/index.ts new file mode 100644 index 000000000..25f8ea427 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/dark/index.ts @@ -0,0 +1,14 @@ +import basicToken from './basicToken' +import modelToken from './modelToken' +import chartsToken from './chartsToken' +import { colorState, colorGroup } from '../../color/ict/dark' + +const ictDark = { + colorGroup, + colorState, + ...modelToken, + ...chartsToken +} + +export default ictDark +export { basicToken as ictDarkBasicToken } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/dark/mapToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/dark/mapToken.ts new file mode 100644 index 000000000..2bc05b21d --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/dark/mapToken.ts @@ -0,0 +1,8 @@ +import basicToken from './basicToken' +import getMapToken from '../getMapToken' + +const mapToken = { + ...getMapToken(basicToken, false) +} + +export default mapToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/dark/modelToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/dark/modelToken.ts new file mode 100644 index 000000000..80ca08321 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/dark/modelToken.ts @@ -0,0 +1,8 @@ +import mapToken from './mapToken' +import getModelToken from '../getModelToken' + +const modelToken = { + ...getModelToken(mapToken) +} + +export default modelToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/getMapToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/getMapToken.ts new file mode 100644 index 000000000..52cdd5d30 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/getMapToken.ts @@ -0,0 +1,152 @@ +import { codeToRGB } from '../../../util/color' + +function getMapToken(basicToken, light = true) { + const { + colorGray0, + colorGray5, + colorGray10, + colorGray20, + colorGray50, + colorGray60, + colorGray70, + colorGray90, + fontSizeSM, + fontSize, + lineWidth, + lineWidthXL, + lineWidthXXL, + lineWidthXXXL, + lineWidthSM, + lineLength, + spaceLG, + space, + lineTypeSolid, + colorTP, + borderZero, + borderSM, + borderRadiusSM, + lineHeightSM, + border, + lineWidthLG, + lineWidthZero, + borderLG, + fontSizeLG, + borderRadiusZero + } = basicToken + + return { + // 主色 + colorPrimary: light ? colorGray0 : colorGray90, // 确定 + // 初级底色 + colorBg: light ? colorGray0 : colorGray90, // 确定 + // 次级背景色 + colorSubg: light ? colorGray0 : colorGray70, + // 主要文本色 + colorText: light ? colorGray90 : colorGray5, // 确定 + // 次级文本色 + colorSubtext: light ? colorGray60 : colorGray20, // 确定 + // 禁用文本色 + colorDisabledText: light ? codeToRGB(colorGray90, 0.3) : codeToRGB(colorGray10, 0.3), // 确定 + // 控件激活色(legend相关的文本图标的颜色) + colorActive: light ? colorGray60 : colorGray20, // 确定 + // 控件失效色(legend相关的文本图标失效的颜色) + colorInactive: light ? codeToRGB(colorGray90, 0.3) : codeToRGB(colorGray10, 0.3), // 确定 + // 坐标轴线颜色 + colorAxisLine: light ? colorGray10 : codeToRGB(colorGray10, 0.1), // 确定 + // 刻度线颜色 + colorAxisTickLine: light ? colorGray10 : codeToRGB(colorGray10, 0.1), // 确定 + // 分隔线颜色 + colorAxisSplitLine: light ? codeToRGB(colorGray90, 0.1) : codeToRGB(colorGray10, 0.1), // 确定 + // 坐标轴指示器悬浮线 + colorAxisPointerLine: light ? colorGray20 : colorGray50, // 确定 + // 透明边框色 + colorBorderTP: colorTP, + // 基础边框色 + colorBorder: colorGray0, + // 文本透明 + colorTextTP: colorTP, + // 面积区域透明 + colorAreaTP: colorTP, + // 图元透明 + colorItemTP: colorTP, + // 指示器阴影 + colorAxisPointerShadow: light ? codeToRGB(colorGray90, 0.08) : codeToRGB(colorGray10, 0.08), + // 主文本字号 + textFontSize: fontSize, // 确定 + // 次级文本字号 + subtextFontSize: fontSizeSM, // 确定 + titleFontSize: fontSize * 2, + subtitleFontSize: fontSizeLG, + // 坐标轴线宽 2 + axisLineWidth: lineWidth, // 确定 + // 刻度线线宽 2 + axisTickLineWidth: lineWidth, // 确定 + // 分隔线线宽 1 + axisSplitLineWidth: lineWidthSM, // 确定 + // 坐标轴指示器的标线线宽 1 + axisPointerLineWidth: lineWidthSM, + + markLineWidth: lineWidthSM, + + markLineEmphasisWidth: lineWidthSM, + + axisLineType: lineTypeSolid, + + axisTickLineType: lineTypeSolid, + + axisSplitLineType: lineTypeSolid, + + axisPointerLineType: lineTypeSolid, + + borderWidthZero: borderZero, + + // 名称的间距 + nameGap: spaceLG, + axisLabelGap: space, + titleGap: spaceLG, + // 容器的间距 + containerGap: spaceLG, + containerBoderRadius: space, + // 图例的间距 + legendGap: space * 7, + // 图元大小 + symbolSize: 10, + symbolSizeSM: 8, + symbolSizeLG: 12, + symbolSizeXS: 6, + // 文本距离 + labelDistance: 10, + labelDistanceLG: 24, + // 柱条的宽度 + barWidth: lineWidthXL, + // 柱条的宽度大 + barWidthLG: lineWidthXXL, + // 堆叠进度图宽度 20 + barWidthStack: lineWidthXXXL, + barBorderRadiusZero: borderRadiusZero, + // series里面的labelLine的长度 + labelLineLength: lineLength, + + // series.itemStyle + itemBorderWidthSM: borderSM, + itemBorderWidth: border, + itemBorderWidthLG: borderLG, + itemBorderWidthZero: borderZero, + itemBorderRadiusSM: borderRadiusSM, + itemBorderColor: light ? colorGray0 : colorGray90, + // series.label + labelColor: light ? colorGray90 : colorGray5, + labelFontSizeSM: fontSizeSM, + // series.rich + richFontSizeSM: fontSizeSM, + richLineHeight: lineHeightSM * fontSizeSM, + richLineHeightSM: fontSizeSM, + // series.lineStyle + lineStyleWidth: lineWidth, + lineStyleWidthLG: lineWidthLG, + lineStyleWidthZero: lineWidthZero + } +} + +export default getMapToken +export { getMapToken as getIctMapToken } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/getModelToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/getModelToken.ts new file mode 100644 index 000000000..3da3c9bbd --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/getModelToken.ts @@ -0,0 +1,253 @@ +function getModelToken(mapToken) { + const { + colorPrimary, + colorText, + colorSubtext, + colorAxisLine, + colorAxisTickLine, + colorAxisSplitLine, + axisLineWidth, + axisTickLineWidth, + axisSplitLineWidth, + axisLineType, + axisTickLineType, + axisSplitLineType, + textFontSize, + subtextFontSize, + nameGap, + titleGap, + axisLabelGap, + containerGap, + labelLineLength, + borderWidthZero, + colorSubg, + colorAxisPointerShadow, + colorAxisPointerLine, + axisPointerLineWidth, + axisPointerLineType, + legendGap, + markLineWidth, + markLineEmphasisWidth, + colorTextTP, + colorActive, + colorInactive, + itemBorderWidthSM, + itemBorderRadiusSM, + labelColor, + labelFontSizeSM, + colorItemTP, + barWidth, + colorBorderTP, + symbolSize, + symbolSizeSM, + symbolSizeLG, + colorAreaTP, + richFontSizeSM, + richLineHeight, + richLineHeightSM, + itemBorderWidth, + lineStyleWidth, + lineStyleWidthLG, + lineStyleWidthZero, + itemBorderWidthLG, + itemBorderWidthZero, + itemBorderColor, + labelDistanceLG, + symbolSizeXS, + containerBoderRadius, + barBorderRadiusZero, + titleFontSize, + subtitleFontSize + } = mapToken + + return { + // 主文本 + tooltipAxisPointerTextColor: colorText, + // 圆盘图Serieslabel + seriesLabelTextColor: colorText, + // 次要文本 + titleTextColor: colorText, + titleSubTextColor: colorSubtext, + legendTextColor: colorSubtext, + legendTextRichColor: colorSubtext, + legendRichTextColor: colorSubtext, + xAxisNameColor: colorSubtext, + xAxisLabelColor: colorSubtext, + yAxisNameColor: colorSubtext, + yAxisLabelColor: colorSubtext, + radarAxisNameColor: colorSubtext, + radarAxisLabelColor: colorSubtext, + angleAxisLabelColor: colorSubtext, + xAxisNameFontSize: textFontSize, + yAxisNameFontSize: textFontSize, + radiusAxisLabelColor: colorSubtext, + tooltipTextFontSize: textFontSize, + titleTextFontSize: titleFontSize, + titleSubtextFontSize: subtitleFontSize, + // 轴线的类型 + xAxisLineType: axisLineType, + xAxisTickLineType: axisTickLineType, + xAxisSplitLineType: axisSplitLineType, + + yAxisLineType: axisLineType, + yAxisTickLineType: axisTickLineType, + yAxisSplitLineType: axisSplitLineType, + + angleAxisLineType: axisLineType, + angleAxisTickLineType: axisTickLineType, + angleAxisSplitLineType: axisSplitLineType, + radiusAxisLineType: axisLineType, + radiusAxisTickLineType: axisTickLineType, + radiusAxisSplitLineType: axisSplitLineType, + tooltipAxisPointerLineType: axisPointerLineType, + + radarAxisLineType: axisLineType, + radarAxisTickType: axisTickLineType, + radarSplitLineType: axisSplitLineType, + + xAxisLabelFontSize: subtextFontSize, + yAxisLabelFontSize: subtextFontSize, + legendTextFontSize: subtextFontSize, + legendTextRichFontSize: subtextFontSize, + angleAxisLabelFontSize: subtextFontSize, + radiusAxisLabelFontSize: subtextFontSize, + + xAxisLineColor: colorAxisLine, + xAxisTickLineColor: colorAxisTickLine, + xAxisSplitLineColor: colorAxisSplitLine, + + yAxisLineColor: colorAxisLine, + yAxisTickLineColor: colorAxisTickLine, + yAxisSplitLineColor: colorAxisSplitLine, + + radarAxisLineColor: colorAxisLine, + radarAxisTickLineColor: colorAxisTickLine, + radarSplitLineColor: colorAxisSplitLine, + + angleAxisLineColor: colorAxisLine, + angleAxisTickLineColor: colorAxisTickLine, + angleAxisSplitLineColor: colorAxisSplitLine, + + radiusAxisLineColor: colorAxisLine, + radiusAxisTickLineColor: colorAxisTickLine, + radiusAxisSplitLineColor: colorAxisSplitLine, + + tooltipAxisPointerLineColor: colorAxisPointerLine, + // 圆盘图的serieslabel线 + seriesLabelLineColor: colorAxisSplitLine, + + xAxisLineWidth: axisLineWidth, + xAxisTickLineWidth: axisTickLineWidth, + xAxisSplitLineWidth: axisSplitLineWidth, + + yAxisLineWidth: axisLineWidth, + yAxisTickLineWidth: axisTickLineWidth, + yAxisSplitLineWidth: axisSplitLineWidth, + + radarAxisLineWidth: axisLineWidth, + radarAxisTickLineWidth: axisTickLineWidth, + radarSplitLineWidth: axisSplitLineWidth, + + radiusAxisLineWidth: axisLineWidth, + radiusAxisTickLineWidth: axisTickLineWidth, + radiusAxisSplitLineWidth: axisSplitLineWidth, + // 轴线的颜色 + angleAxisLineWidth: axisLineWidth, + angleAxisTickLineWidth: axisTickLineWidth, + angleAxisSplitLineWidth: axisSplitLineWidth, + + // 轴线的粗细 + tooltipAxisPointerLineWidth: axisPointerLineWidth, + // 轴线名称的间距 + xAxisNameGap: nameGap, + yAxisNameGap: nameGap, + titleItemGap: titleGap, + radiusAxisLabelGap: axisLabelGap, + // 圆盘图的labelline的长度 + seriesLabelLineLength: labelLineLength, // 待讨论 + + // 垂直 + tooltipSpaceVertical: containerGap, + // 水平 + tooltipSpaceHorizontal: containerGap * 2, + + // 背景色 + + tooltipBg: colorSubg, + tooltipFontColor: colorText, + tooltipBorderRaduis: containerBoderRadius, + // 边框相关 + + // 圆盘图 + seriesItemStyleBorderWidthZero: borderWidthZero, + // seriesItemStyleBorderWidth: + + legendInactiveBorderWidth: borderWidthZero, + legendBorderWidth: borderWidthZero, + legendItemStyleBorderWidth: borderWidthZero, + + // 图例相关 + + legendItemGap: legendGap, + legendCircleItemWidth: 24, + legendCircleItemHeight: 12, + legendReactItemWidth: 12, + legendReactItemHeight: 2, + legendInactiveColor: colorInactive, + legendTextPadding: [containerGap / 2, 0, 0, 0], + legendTextRichPadding: [containerGap / 2, 0, 0, 0], + legendPageTextColor: colorText, + legendInactiveBorderColor: colorPrimary, + legendPageIconInactiveColor: colorInactive, + legendPageIconColor: colorActive, + // 主色 + colorPrimary, + + tooltipAxisPointerShadow: colorAxisPointerShadow, + + // 圆盘图 + seriesEmptyCircleColor: colorSubg, + + seriesMarkLineWidth: markLineWidth, + + seriesMarkLineEmphasisWidth: markLineEmphasisWidth, + + markPointLabelColor: colorTextTP, + lineColorTp: colorTextTP, + // visualMap + visualMapPiecesColor: colorTextTP, + visualMapDashColor: colorPrimary, + + // series系列 + itemBorderWidthSM, + itemBorderWidthZero, + itemBorderWidth, + itemBorderWidthLG, + itemBorderColor, + itemBorderColorTP: colorBorderTP, + itemBorderRadiusSM, + itemBorderRadiusZero: barBorderRadiusZero, + itemColorTP: colorItemTP, + symbolSize, + symbolSizeSM, + symbolSizeLG, + symbolSizeXS, + labelColor, + labelFontSizeSM, + labelDistanceLG, + richFontSizeSM, + richLineHeight, + richLineHeightSM, + lineStyleWidth, + lineStyleWidthLG, + lineStyleWidthZero, + lineColor: colorText, + barWidth, + areaColorTP: colorAreaTP, + circleColor: colorAxisLine, + markLineColorTP: colorBorderTP + } +} + +export default getModelToken +export { getModelToken as getIctModelToken } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/light/baiscToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/light/baiscToken.ts new file mode 100644 index 000000000..371da25b6 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/light/baiscToken.ts @@ -0,0 +1,10 @@ +import basic from '../../basic' +import light from '../../color/ict/light' + +// 覆盖基础的色值 +const basicToken = { + ...basic, + ...light +} + +export default basicToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/light/chartsToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/light/chartsToken.ts new file mode 100644 index 000000000..0cb9d54e4 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/light/chartsToken.ts @@ -0,0 +1,10 @@ +import basicToken from './baiscToken' +import mapToken from './mapToken' +import modelToken from './modelToken' +import { getChartsToken } from '../../basic' + +const chartsToken = { + ...getChartsToken(basicToken, mapToken, modelToken) +} + +export default chartsToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/light/index.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/light/index.ts new file mode 100644 index 000000000..fa30f99cd --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/light/index.ts @@ -0,0 +1,14 @@ +import basicToken from './baiscToken' +import modelToken from './modelToken' +import chartsToken from './chartsToken' +import { colorState, colorGroup } from '../../color/ict/light' + +const ictLight = { + colorGroup, + colorState, + ...modelToken, + ...chartsToken +} + +export default ictLight +export { basicToken as ictLightBasicToken } diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/light/mapToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/light/mapToken.ts new file mode 100644 index 000000000..c8c9bda38 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/light/mapToken.ts @@ -0,0 +1,8 @@ +import basicToken from './baiscToken' +import getMapToken from '../getMapToken' + +const mapToken = { + ...getMapToken(basicToken) +} + +export default mapToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/light/modelToken.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/light/modelToken.ts new file mode 100644 index 000000000..80ca08321 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ict/light/modelToken.ts @@ -0,0 +1,8 @@ +import mapToken from './mapToken' +import getModelToken from '../getModelToken' + +const modelToken = { + ...getModelToken(mapToken) +} + +export default modelToken diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/ictDark.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ictDark.ts new file mode 100644 index 000000000..33f0db085 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ictDark.ts @@ -0,0 +1,55 @@ +// 推荐色组 +const group = [ + '#1f55b5', // rgba(31 ,85 ,181,1) + '#278661', // rgba(39 ,134,97 ,1) + '#8a21bc', // rgba(138,33 ,188,1) + '#26616b', // rgba(38 ,97 ,107,1) + '#b98c1d', // rgba(185,140,29 ,1) + '#745ef7' // rgba(116,94 ,247,1) +] + +// 图表基础色组 +const base = { + // 主色 + main: '#191919', + // 主文本色 + font: '#f5f5f5', + // 次级文本色 + subfont: '#4e4e4e', + // 刻度线 + axis: 'rgba(238,238,238,0.1)', + // 次级刻度线 + subaxis: 'rgba(255,255,255,0.2)', + // 刻度文本 + axislabel: '#bbbbbb', + // 初级底色,颜色最亮 + bg: '#272727', + // 次级底色,颜色稍亮 + subg: '#2e2e2e' +} + +// 图表状态色组 +const state = { + // 错误色 + error: '#F43146', + // 告警色 + warning: '#EC6F1A', + // 次要告警色 + subwarning: '#EEBA18', + // 成功色 + success: '#0D9458', + // 提示色 + prompt: '#5990FD', + // 失效 + fail: '#818181' +} + +const theme = { + color: { + group, + base, + state + } +} + +export default theme diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/ictLight.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ictLight.ts new file mode 100644 index 000000000..c0b4a803f --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/ictLight.ts @@ -0,0 +1,55 @@ +// 推荐色组 +const group = [ + '#6d8ff0', // rgba(109,143,240,1) + '#00a874', // rgba(0 ,168,116,1) + '#bd72f0', // rgba(189,114,240,1) + '#c6e5ec', // rgba(198,229,236,1) + '#fdc000', // rgba(253,192,0 ,1) + '#9185f0' // rgba(145,133,240,1) +] + +// 图表基础色组 +const base = { + // 主色 + main: '#FFFFFF', + // 主文本色 + font: '#191919', + // 次级文本色 + subfont: '#bbbbbb', + // 刻度线 + axis: '#eeeeee', + // 次级刻度线 + subaxis: 'rgba(0,0,0,0.08)', + // 刻度文本 + axislabel: '#4e4e4e', + // 初级底色,颜色最亮 + bg: '#ffffff', + // 次级底色,颜色稍亮 + subg: '#e8e8e8' +} + +// 图表状态色组 +const state = { + // 错误色 + error: '#F43146', + // 告警色 + warning: '#EC6F1A', + // 次要告警色 + subwarning: '#EEBA18', + // 成功色 + success: '#2DA769', + // 提示色 + prompt: '#5990FD', + // 失效 + fail: '#939393' +} + +const theme = { + color: { + group, + base, + state + } +} + +export default theme diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/index.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/index.ts new file mode 100644 index 000000000..38b9165bd --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/index.ts @@ -0,0 +1,135 @@ +import merge from '../../util/merge' +import ictDark from './ictDark' +import ictLight from './ictLight' +import bpitLight from './bpitLight' +import bpitDark from './bpitDark' +import cloudDark from './cloudDark' +import cloudLight from './cloudLight' +import HashMap from '../../util/hashMap' +import cloneDeep from '../../util/cloneDeep' +import tips from '../../util/tips' +import ictLight2 from './ict/light' +import ictDark2 from './ict/dark' +import cloudLight2 from './cloud/light' +import cloudDark2 from './cloud/dark' +import bpitDark2 from './bpit/dark' +import bpitLight2 from './bpit/light' +import { THEMES, CURRENT_THEME, DEFAULT_THEME_NAME, THEME_ERROR_TIP_MESSAGE } from './constants' +import mergeThemeConfig from './mergeThemeConfig' + +const theme = new HashMap({ + [THEMES.DARK]: ictDark, + [THEMES.LIGHT]: ictLight, + [THEMES.BPIT_LIGHT]: bpitLight, + [THEMES.BPIT_DARK]: bpitDark, + [THEMES.CLOUD_DARK]: cloudDark, + [THEMES.CLOUD_LIGHT]: cloudLight, + [CURRENT_THEME]: ictLight +}) + +const themeToken = new HashMap({ + [THEMES.LIGHT]: cloneDeep(ictLight2), + [THEMES.DARK]: cloneDeep(ictDark2), + [THEMES.BPIT_LIGHT]: cloneDeep(bpitLight2), + [THEMES.BPIT_DARK]: cloneDeep(bpitDark2), + [THEMES.CLOUD_LIGHT]: cloneDeep(cloudLight2), + [THEMES.CLOUD_DARK]: cloneDeep(cloudDark2), + [CURRENT_THEME]: cloneDeep(ictLight2) +}) + +class Theme { + // 当前主题名称 + static themeName = undefined + // 当前主题颜色 + static color + + static config = cloneDeep(ictLight2) + + static set(name, config) { + const defaultConfig = cloneDeep(this.get(DEFAULT_THEME_NAME)) + merge(defaultConfig, config) + theme.set(name, defaultConfig) + } + + // 以下待确认 + static setConfig(name, config) { + // 以下为新的逻辑 + const themeKeys = this.getThemeKeys() + const validate = themeKeys.includes(name) + // 修改已有主题的配置 + if (validate) { + const newConfig = mergeThemeConfig(name, config) + const oldConfig = this.getConfig(name) + merge(oldConfig, newConfig) + } else { + const newConfig = mergeThemeConfig(DEFAULT_THEME_NAME, config) + themeToken.set(name, newConfig) + } + } + + // 以下待确认 + static getThemeKeys() { + return themeToken.keys() + } + + // 以下待确认 + static resetThemeCongfig() { + themeToken.set(THEMES.LIGHT, cloneDeep(ictLight2)) + themeToken.set(THEMES.DARK, cloneDeep(ictDark2)) + themeToken.set(THEMES.BPIT_LIGHT, cloneDeep(bpitLight2)) + themeToken.set(THEMES.BPIT_DARK, cloneDeep(bpitDark2)) + themeToken.set(THEMES.CLOUD_LIGHT, cloneDeep(cloudLight2)) + themeToken.set(THEMES.CLOUD_DARK, cloneDeep(cloudDark2)) + themeToken.set(CURRENT_THEME, cloneDeep(ictLight2)) + } + + static get(name) { + return theme.get(name) + } + + static getConfig(name) { + return themeToken.get(name) + } + + static setDefaultTheme(name) { + const tempTheme = this.validate(name) + if (this.themeName !== tempTheme) { + this.themeName = tempTheme + theme.set(CURRENT_THEME, theme.get(tempTheme)) + this.color = this.get(CURRENT_THEME).color + // 以下config功能待完善 + themeToken.set(CURRENT_THEME, themeToken.get(tempTheme)) + this.config = this.getConfig(CURRENT_THEME) + console.log(this.config) + } + } + + // 校验主题的合法性,将不合法的主题值重置为默认的light主题,并给与告警 + static validate(name) { + // 如果没传并且没有全局注册过直接就是light主题 + let tempTheme = name || this.themeName || DEFAULT_THEME_NAME + + if (tempTheme.toLowerCase().includes('cloud-light')) { + tempTheme = THEMES.CLOUD_LIGHT + } + + if (tempTheme.toLowerCase().includes('cloud-dark')) { + tempTheme = THEMES.CLOUD_DARK + } + + const themeKeys = theme.keys() + + const validate = themeKeys.includes(tempTheme) + + if (!validate) { + tips.error(THEME_ERROR_TIP_MESSAGE) + tempTheme = THEMES.LIGHT + } + + return tempTheme + } +} + +export { THEMES } + +export default Theme diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/mergeThemeConfig.ts b/packages/vue/src/charts-beta/chart-core/base/feature/theme/mergeThemeConfig.ts new file mode 100644 index 000000000..4e7d3369d --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/mergeThemeConfig.ts @@ -0,0 +1,125 @@ +import { THEMES } from './constants' +import { ictLightBasicToken } from './ict/light' +import { ictDarkBasicToken } from './ict/dark' +import { getIctMapToken } from './ict/getMapToken' +import { getIctModelToken } from './ict/getModelToken' +import { cloudLightBasicToken } from './cloud/light' +import { cloudDarkBasicToken } from './cloud/dark' +import { getCloudMapToken } from './cloud/getMapToken' +import { getCloudModelToken } from './cloud/getModelToken' +import { bpitLightBasicToken } from './bpit/light' +import { bpitDarkBasicToken } from './bpit/dark' +import { getBpitMapToken } from './bpit/getMapToken' +import { getBpitModelToken } from './bpit/getModelToken' +import { getFont, getRadius, getSpace, getChartsToken } from './basic' +import merge from '../../util/merge' +import cloneDeep from '../../util/cloneDeep' + +const SPECIALTOKEN = [ + 'BarChart', + 'BubbleChart', + 'FunnelChart', + 'GraphTreeChart', + 'HillChart', + 'JadeJueChart', + 'TreeChart', + 'SunburstChart', + 'SankeyChart', + 'RadarChart', + 'ProcessChart', + 'PolarBarChart', + 'PieChart', + 'AreaChart', + 'LineChart' +] + +const themeBaisToken = { + [THEMES.LIGHT]: ictLightBasicToken, + [THEMES.DARK]: ictDarkBasicToken, + [THEMES.BPIT_LIGHT]: bpitLightBasicToken, + [THEMES.BPIT_DARK]: bpitDarkBasicToken, + [THEMES.CLOUD_LIGHT]: cloudLightBasicToken, + [THEMES.CLOUD_DARK]: cloudDarkBasicToken +} + +const themeMapToken = { + [THEMES.LIGHT]: getIctMapToken, + [THEMES.DARK]: (config) => getIctMapToken(config, false), + [THEMES.BPIT_LIGHT]: getBpitMapToken, + [THEMES.BPIT_DARK]: (config) => getBpitMapToken(config, false), + [THEMES.CLOUD_LIGHT]: getCloudMapToken, + [THEMES.CLOUD_DARK]: (config) => getCloudMapToken(config, false) +} + +const themeModelToken = { + [THEMES.LIGHT]: getIctModelToken, + [THEMES.DARK]: getIctModelToken, + [THEMES.BPIT_LIGHT]: getBpitModelToken, + [THEMES.BPIT_DARK]: getBpitModelToken, + [THEMES.CLOUD_LIGHT]: getCloudModelToken, + [THEMES.CLOUD_DARK]: getCloudModelToken +} + +// 获取basic中需要根据基础派生的相关token变量 +function getDerivationBasicToken(config) { + let newConfig = {} + const { radiusBase, fontSize, space } = config + if (radiusBase) newConfig = { ...newConfig, ...getRadius(radiusBase) } + if (fontSize) newConfig = { ...newConfig, ...getFont(fontSize) } + if (space) newConfig = { ...newConfig, ...getSpace(space) } + return newConfig +} + +function getBasicToken(name, config) { + return { + ...themeBaisToken[name], + ...getDerivationBasicToken(config), + ...config + } +} + +function getMapToken(name, config) { + return { + ...themeMapToken[name](config), + ...config + } +} + +function getModelToken(name, config) { + return { + ...themeModelToken[name](config), + ...config + } +} + +function mergeSpecialToken(name, chartsConfig, config) { + SPECIALTOKEN.forEach((item) => { + if (config[item]) merge(chartsConfig, config[item]) + }) + // 针对colorState特殊处理 + const { colorState } = config + if (colorState) { + const newColorState = merge(cloneDeep(themeBaisToken[name].colorState), colorState) + return { ...chartsConfig, colorState: newColorState } + } + return { ...chartsConfig } +} + +/** + * + * @param {string} name 传入的主题名称 + * @param {object} config 传入的主题配置 + */ + +export default function mergeThemeConfig(name, config) { + const basicConfig = getBasicToken(name, config) + const mapConfig = { ...getMapToken(name, basicConfig), ...config } + const modelConfig = { ...getModelToken(name, mapConfig), ...config } + const chartsConfig = getChartsToken(basicConfig, mapConfig, modelConfig) + const specialConfig = mergeSpecialToken(name, chartsConfig, config) + return { + colorGroup: basicConfig.colorGroup, + ...modelConfig, + ...specialConfig + } +} diff --git a/packages/vue/src/charts-beta/chart-core/base/feature/theme/readme.md b/packages/vue/src/charts-beta/chart-core/base/feature/theme/readme.md new file mode 100644 index 000000000..9415f6462 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/feature/theme/readme.md @@ -0,0 +1,15 @@ +### baiscToken + +图表使用的基础变量,包含 font,border,color,space 等 + +### mapToken + +基于 baiscToken 衍生的二级变量,取值从 baiscToken 中取,实现黑白主题的色值切换 + +### modelToken + +基于 mapToken 衍生的三级变量,取值从 mapToken 中取,实现不同主题之间的差异覆盖 + +### chartsToken + +目前是基于 baiscToken,mapToken,modelToken 衍生的变量,用于按照图表名称的具体 option 配置 diff --git a/packages/vue/src/charts-beta/chart-core/base/index.ts b/packages/vue/src/charts-beta/chart-core/base/index.ts new file mode 100644 index 000000000..68d0a75f1 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/index.ts @@ -0,0 +1,9 @@ +import HuiChart from './chart' + +export { default as core } from './core' +export { default as BarChart } from './components/BarChart' +export { default as PieChart } from './components/PieChart' +export { default as LineChart } from './components/LineChart' +export { default as RadarChart } from './components/RadarChart' + +export default HuiChart diff --git a/packages/vue/src/charts-beta/chart-core/base/option/PolarSys/index.ts b/packages/vue/src/charts-beta/chart-core/base/option/PolarSys/index.ts new file mode 100644 index 000000000..351257bc7 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/PolarSys/index.ts @@ -0,0 +1,32 @@ +import title from '../config/polarTitle' +import legend from '../config/legend' +import tooltip from '../config/tooltip' +import angleAxis from '../config/angleAxis' +import radiusAxis from '../config/radiusAxis' +import polar from '../config/polar' + +// 极坐标系所需的基础配置 +function PolarCoordSys(baseOpt, iChartOpt, chartName) { + // 图表基础颜色 + baseOpt.color = iChartOpt.color + + // 图表的圆环角度轴 + baseOpt.angleAxis = angleAxis(iChartOpt, chartName) + + // 图表的圆环径向轴 + baseOpt.radiusAxis = radiusAxis(iChartOpt, chartName) + + // 图表的圆环位置 + baseOpt.polar = polar(iChartOpt, chartName) + + // 图表鼠标悬浮提示框 + baseOpt.tooltip = tooltip(iChartOpt, chartName) + + // 图表中间的文字使用 title 属性实现 + baseOpt.title = title(iChartOpt, chartName) + + // 图表图例 + baseOpt.legend = legend(iChartOpt) +} + +export default PolarCoordSys diff --git a/packages/vue/src/charts-beta/chart-core/base/option/RectSys/index.ts b/packages/vue/src/charts-beta/chart-core/base/option/RectSys/index.ts new file mode 100644 index 000000000..ed1966ed2 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/RectSys/index.ts @@ -0,0 +1,40 @@ +import grid from '../config/grid' +import xAxis from '../config/xAxis' +import yAxis from '../config/yAxis' +import legend from '../config/legend' +import tooltip from '../config/tooltip' +import datazoom from '../config/datazoom' + +// 暴露出去的方法 +import xkey from '../config/xAxis/xkey' +import xdata from '../config/xAxis/xdata' +import ydata from '../config/yAxis/ydata' +import ldata from '../config/legend/ldata' + +// 组装直角坐标系所需的基础配置 +function RectCoordSys(baseOpt, iChartOpt, chartName) { + // 图表padding + baseOpt.grid = grid(iChartOpt) + // 图表基础颜色 + baseOpt.color = iChartOpt.color + // 图表x坐标轴 + baseOpt.xAxis = xAxis(iChartOpt, chartName) + // 图表y坐标轴 + baseOpt.yAxis = yAxis(baseOpt, iChartOpt, chartName) + // 图表鼠标悬浮提示框 + baseOpt.tooltip = tooltip(iChartOpt, chartName) + // 图表图例 + baseOpt.legend = legend(iChartOpt, chartName) + // 图表datazoom + baseOpt.dataZoom = datazoom(iChartOpt) +} + +export default RectCoordSys + +/** + * xkey: 获取到x轴的key name + * xdata: 获取到x轴的数据 + * ydata: 获取到y轴的数据 + * ldata: 获取到legend的数据 + */ +export { xkey, xdata, ldata, ydata } diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/angleAxis/base.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/angleAxis/base.ts new file mode 100644 index 000000000..5c513d62b --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/angleAxis/base.ts @@ -0,0 +1,46 @@ +import Theme from '../../../feature/theme' + +function getBaseAngleAxis() { + return { + splitNumber: 6, + startAngle: 90, + axisLine: { + show: false, + lineStyle: { + color: Theme.config.angleAxisLineColor, + width: Theme.config.angleAxisLineWidth, + type: Theme.config.angleAxisLineType + } + }, + splitLine: { + show: false, + lineStyle: { + color: Theme.config.angleAxisSplitLineColor, + width: Theme.config.angleAxisSplitLineWidth, + type: Theme.config.angleAxisSplitLineType + } + }, + axisTick: { + show: false, + length: 5, + lineStyle: { + color: Theme.config.angleAxisTickLineColor, + width: Theme.config.angleAxisTickLineWidth, + type: Theme.config.angleAxisTickLineType + } + }, + axisLabel: { + show: true, + fontSize: Theme.config.angleAxisLabelFontSize, + color: Theme.config.angleAxisLabelColor, + formatter: undefined + } + } +} + +function base(chartName) { + const angleAxisOption = getBaseAngleAxis() + return angleAxisOption +} + +export default base diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/angleAxis/index.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/angleAxis/index.ts new file mode 100644 index 000000000..d2a0eb47e --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/angleAxis/index.ts @@ -0,0 +1,31 @@ +import base from './base' +import merge from '../../../util/merge' + +function angleAxis(iChartOption, chartName) { + let angleAxisOpt + switch (chartName) { + case 'JadeJueChart': + angleAxisOpt = base(chartName) + merge(angleAxisOpt, iChartOption.angleAxis) + break + case 'PolarBarChart': + angleAxisOpt = base(chartName) + angleAxisOpt.type = 'category' + angleAxisOpt.axisLine.show = true + angleAxisOpt.axisLabel.show = false + angleAxisOpt.splitLine.show = true + merge(angleAxisOpt, iChartOption.angleAxis) + break + case 'CircleProcessChart': + angleAxisOpt = { + max: 100, + show: false + } + break + default: + break + } + return angleAxisOpt +} + +export default angleAxis diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/datazoom/base.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/datazoom/base.ts new file mode 100644 index 000000000..5529bd738 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/datazoom/base.ts @@ -0,0 +1,113 @@ +import cloneDeep from '../../../util/cloneDeep' +import Theme from '../../../feature/theme' + +const baseOption = [ + { + end: 100, + start: 0, + height: 24, + bottom: 18, + show: false, + type: 'slider', + left: 'center', + xAxisIndex: [0], + zoomLock: false, + borderColor: 'none', // 边框 + backgroundColor: undefined, // 背景颜色 + fillerColor: undefined, // 选中范围填充颜色 + handleSize: '85%', // 控制手柄的尺寸 + handleIcon: 'path://M0 0.2 Q 0 0 0.2 0 L0.8 0 Q1 0 1 0.2 L1 2.8 Q1 3 0.8 3L0.2 3 Q0 3 0 2.8 Z', // 手柄形状 + handleStyle: { + color: undefined, // 手柄颜色 + shadowBlur: 5, // 阴影模糊大小 + shadowColor: undefined, + shadowOffsetX: 0, // 阴影偏移x轴多少 + shadowOffsetY: 0, // 阴影偏移y轴多少 + opacity: 1, // 透明度 + borderColor: undefined, // 手柄边框颜色 + borderWidth: 5, // 手柄边框宽度 + borderJoin: 'round' // 手柄边框圆角 + }, + dataBackground: { + lineStyle: { + color: undefined, // 线条颜色 + join: 'round', + cap: 'round' + }, + areaStyle: { + opacity: 1, // 阴影的透明度 + color: undefined // 填充的颜色 + } + }, + selectedDataBackground: { + // 选中部分样式 + lineStyle: { + color: undefined // 线条颜色 + }, + areaStyle: { + opacity: 0.9, // 阴影的透明度 + color: undefined // 填充的颜色 + } + }, + moveHandleSize: '0', // 移动手柄的尺寸高度 + emphasis: { + handleStyle: { + color: undefined, + borderColor: undefined + } + } + } +] + +function base(theme) { + const option = cloneDeep(baseOption) + const colorBase = Theme.color.base + if (theme.includes('dark')) { + // 手柄中心填充色 + option[0].handleStyle.color = colorBase.subfont + // 手柄外框色 + option[0].handleStyle.borderColor = colorBase.main + // 手柄阴影色 + option[0].handleStyle.shadowColor = '#444444' + // 手柄hover时中心填充色 + option[0].emphasis.handleStyle.color = colorBase.font + // 手柄hover时外框色 + option[0].emphasis.handleStyle.borderColor = colorBase.main + + // 空白部分底色 + option[0].backgroundColor = '#090909' + // 空白遮罩颜色 + option[0].fillerColor = 'rgba(255,255,255,0)' + // 选中区域外的线条颜色和面积颜色 + option[0].dataBackground.lineStyle.color = '#282828' + option[0].dataBackground.areaStyle.color = '#282828' + // 选中区域内的线条颜色和面积颜色 + option[0].selectedDataBackground.lineStyle.color = '#555555' + option[0].selectedDataBackground.areaStyle.color = '#555555' + } else { + // 手柄中心填充色 + option[0].handleStyle.color = '#ffffff' + // 手柄外框色 + option[0].handleStyle.borderColor = '#1476ff' + // 手柄阴影色 + option[0].handleStyle.shadowColor = 'transparent' + // 手柄hover时中心填充色 + option[0].emphasis.handleStyle.color = '#ffffff' + // 手柄hover时外框色 + option[0].emphasis.handleStyle.borderColor = '#1476ff' + + // 空白部分底色 + option[0].backgroundColor = '#f3f3f3' + // 空白遮罩颜色 + option[0].fillerColor = 'rgba(175,218,245,.5)' + // 选中区域外的线条颜色和面积颜色 + option[0].dataBackground.lineStyle.color = '#c2c2c2' + option[0].dataBackground.areaStyle.color = '#e8e8e8' + // 选中区域内的线条颜色和面积颜色 + option[0].selectedDataBackground.lineStyle.color = '#9fb3ce' + option[0].selectedDataBackground.areaStyle.color = '#bcd0eb' + } + return option +} + +export default base diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/datazoom/index.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/datazoom/index.ts new file mode 100644 index 000000000..9748b7294 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/datazoom/index.ts @@ -0,0 +1,45 @@ +import base from './base' +import merge from '../../../util/merge' + +function datazoom(iChartOption) { + const theme = iChartOption.theme + const self = iChartOption.dataZoom + const dataZoom = base(theme) + const { show, position, start, end, startValue, endValue, style, zoomOnMouseWheel, height } = self + if (show) { + end && (dataZoom[0].end = end) + start && (dataZoom[0].start = start) + if (startValue !== undefined) { + delete dataZoom[0].start + dataZoom[0].startValue = startValue + } + if (endValue !== undefined) { + delete dataZoom[0].end + dataZoom[0].endValue = endValue + } + height && (dataZoom[0].height = height) + dataZoom[0].show = true + dataZoom[0].top = position.top || 'auto' + dataZoom[0].left = position.left || 'auto' + dataZoom[0].right = position.right || 'auto' + dataZoom[0].bottom = position.bottom || 'auto' + // 用户自定义样式 + if (style) { + const { backgroundColor, dataBackground, selectedDataBackground, fillerColor } = dataZoom[0] + dataZoom[0].backgroundColor = style.backgroundColor || backgroundColor + dataZoom[0].dataBackground.areaStyle.color = style.unSelectDataColor || dataBackground.areaStyle.color + dataZoom[0].selectedDataBackground.areaStyle.color = + style.selectDataColor || selectedDataBackground.areaStyle.color + dataZoom[0].fillerColor = style.middleFillerColor || fillerColor + // 用户自定义手柄样式 + if (style.handleStyle) { + dataZoom[0].handleStyle = Object.assign(dataZoom[0].handleStyle, style.handleStyle) + } + } + merge(dataZoom[0], self) + } + // dataZoom = self; + return dataZoom +} + +export default datazoom diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/grid/base.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/grid/base.ts new file mode 100644 index 000000000..bcd626393 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/grid/base.ts @@ -0,0 +1,16 @@ +import cloneDeep from '../../../util/cloneDeep' + +const baseGridOption = { + top: 0, + left: 0, + right: 0, + bottom: 0, + containLabel: true +} + +function base() { + const option = cloneDeep(baseGridOption) + return option +} + +export default base diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/grid/index.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/grid/index.ts new file mode 100644 index 000000000..eee3de15e --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/grid/index.ts @@ -0,0 +1,21 @@ +import base from './base' +import merge from '../../../util/merge' +import { isArray } from '../../../util/type' + +function grid(iChartOpt) { + const gridUnit = base() + gridUnit.top = iChartOpt.padding[0] + gridUnit.left = iChartOpt.padding[3] + gridUnit.right = iChartOpt.padding[1] + gridUnit.bottom = iChartOpt.padding[2] + let gridResult = iChartOpt.grid || [gridUnit] + if (!isArray(gridResult)) { + gridResult = [gridResult] + } + gridResult.forEach((item) => { + merge(gridUnit, item) + }) + return gridResult +} + +export default grid diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/legend/base.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/legend/base.ts new file mode 100644 index 000000000..a71f7638c --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/legend/base.ts @@ -0,0 +1,60 @@ +import Theme from '../../../feature/theme' + +function getBaseOption() { + return { + data: [], + icon: 'circle', + left: 'center', + bottom: 12, + inactiveColor: Theme.config.legendInactiveColor, + inactiveBorderColor: Theme.config.legendInactiveBorderColor, + inactiveBorderWidth: Theme.config.legendInactiveBorderWidth, + borderWidth: Theme.config.legendBorderWidth, + formatter: undefined, + textStyle: { + fontSize: Theme.config.legendTextFontSize, + color: Theme.config.legendTextColor, + align: 'left', + verticalAlign: 'top', + padding: Theme.config.legendTextPadding, + rich: { + a: { + fontSize: Theme.config.legendTextRichFontSize, + color: Theme.config.legendTextRichColor, + align: 'left', + verticalAlign: 'top', + padding: Theme.config.legendTextRichPadding + }, + b: { + fontSize: Theme.config.legendTextRichFontSize, + color: Theme.config.legendTextRichColor, + align: 'left', + verticalAlign: 'top', + padding: Theme.config.legendTextRichPadding + } + }, + overflow: 'none', + width: undefined + }, + width: undefined, + pageTextStyle: { + color: Theme.config.legendPageTextColor + }, + pageIconColor: undefined, + pageIconInactiveColor: Theme.config.legendPageIconInactiveColor, + selectedMode: true, + align: 'left', + itemGap: Theme.config.legendItemGap, + itemWidth: Theme.config.legendCircleItemWidth, + itemHeight: Theme.config.legendCircleItemHeight, + itemStyle: { + borderWidth: Theme.config.legendItemStyleBorderWidth + } + } +} + +function base() { + return getBaseOption() +} + +export default base diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/legend/icon.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/legend/icon.ts new file mode 100644 index 000000000..ab2a24230 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/legend/icon.ts @@ -0,0 +1,11 @@ +import Theme from '../../../feature/theme' + +function icon(legend, iChartOption) { + if (iChartOption.legend.icon == 'line') { + legend.icon = 'rect' + legend.itemHeight = Theme.config.legendReactItemHeight + legend.itemWidth = Theme.config.legendReactItemWidth + } +} + +export default icon diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/legend/index.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/legend/index.ts new file mode 100644 index 000000000..fdb49a627 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/legend/index.ts @@ -0,0 +1,45 @@ +import base from './base' +import icon from './icon' +import size from './size' +import position from './position' +import textStyle from './textStyle' +import itemStyle from './itemStyle' + +function legend(iChartOption, chartName) { + const selfLegend = iChartOption.legend + const { data, show, orient, formatter, selectedMode } = selfLegend + const legend = base() + // 控制显示 + if (!show) { + legend.show = false + } + // 图例排布方向 + if (orient) { + legend.orient = orient + } + // 图例选择模式, 当 selectedMode == false 时表示不可点击 + legend.selectedMode = selectedMode !== undefined ? selectedMode : true + // 图例位置 + position(legend, selfLegend) + // 图例整体的宽高 + size(legend, selfLegend) + // 图例样式 + itemStyle(legend, chartName) + // 图例文本格式化 + legend.formatter = formatter + // 图例图标大小 + legend.itemWidth = selfLegend.itemWidth || legend.itemWidth + legend.itemHeight = selfLegend.itemHeight || legend.itemHeight + // 图例每项之间的间隔 + legend.itemGap = selfLegend.itemGap || legend.itemGap + // 数据 + legend.data = data + // 配置图例富文本样式 + textStyle(legend, selfLegend.textStyle) + Object.assign(legend, iChartOption.legend) + // 自定义icon + icon(legend, iChartOption) + return legend +} + +export default legend diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/legend/itemStyle.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/legend/itemStyle.ts new file mode 100644 index 000000000..bd486b4af --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/legend/itemStyle.ts @@ -0,0 +1,5 @@ +export default function itemStyle(legend, chartName) { + if (chartName === 'BubbleChart') { + legend.itemStyle.borderWidth = 1 + } +} diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/legend/ldata.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/legend/ldata.ts new file mode 100644 index 000000000..55bfd4e9f --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/legend/ldata.ts @@ -0,0 +1,17 @@ +/** + * 从数据中拿出legend-data + */ +function ldata(data, xAxisKey) { + const legendData = [] + if (data.length > 0) { + const temp = data[0] + for (const key in temp) { + if (key !== xAxisKey) { + legendData.push(key) + } + } + } + return legendData +} + +export default ldata diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/legend/position.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/legend/position.ts new file mode 100644 index 000000000..660213437 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/legend/position.ts @@ -0,0 +1,8 @@ +function position(legend, selfLegend) { + legend.top = selfLegend.position.top || 'auto' + legend.left = selfLegend.position.left || 'auto' + legend.right = selfLegend.position.right || 'auto' + legend.bottom = selfLegend.position.bottom || 'auto' +} + +export default position diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/legend/size.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/legend/size.ts new file mode 100644 index 000000000..da20339b1 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/legend/size.ts @@ -0,0 +1,6 @@ +function size(legend, selfLegend) { + legend.width = selfLegend.width || 'auto' + legend.height = selfLegend.height || 'auto' +} + +export default size diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/legend/textStyle.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/legend/textStyle.ts new file mode 100644 index 000000000..9559021c1 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/legend/textStyle.ts @@ -0,0 +1,6 @@ +export default function textStyle(legend, textStyle) { + if (textStyle) { + legend.textStyle.rich = Object.assign(legend.textStyle.rich, textStyle.rich) + legend.textStyle = Object.assign(legend.textStyle, textStyle) + } +} diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/mark/index.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/mark/index.ts new file mode 100644 index 000000000..345853d0b --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/mark/index.ts @@ -0,0 +1,34 @@ +import Theme from '../../../feature/theme' + +export function getMarkLineDefault() { + return { + symbol: 'none', + silent: true, + label: { + show: false + }, + lineStyle: { + width: Theme.config.seriesMarkLineWidth + }, + emphasis: { + label: { + show: false + }, + lineStyle: { + width: Theme.config.seriesMarkLineEmphasisWidth + } + }, + data: [] + } +} + +export function getMarkPointDefault() { + return { + symbol: 'path://M50 0 L0 50 L100 50 Z', + symbolSize: [10, 6], + label: { + color: Theme.config.markPointLabelColor + }, + data: [] + } +} diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/polar/base.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/polar/base.ts new file mode 100644 index 000000000..1866a562e --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/polar/base.ts @@ -0,0 +1,15 @@ +import cloneDeep from '../../../util/cloneDeep' + +const basePolar = { + center: ['50%', '50%'], + radius: ['8%', '70%'] +} + +function base(position, chartName) { + const PolarOption = cloneDeep(basePolar) + PolarOption.center = (position && position.center) || basePolar.center + PolarOption.radius = (position && position.radius) || basePolar.radius + return PolarOption +} + +export default base diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/polar/index.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/polar/index.ts new file mode 100644 index 000000000..401ed9582 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/polar/index.ts @@ -0,0 +1,22 @@ +import base from './base' + +function polar(iChartOption, chartName) { + const { position } = iChartOption + let polarOption = base(position, chartName) + switch (chartName) { + case 'PolarBarChart': + break + case 'JadeJueChart': + polarOption.radius = ['20%', '60%'] + break + case 'CircleProcessChart': + polarOption.radius = ['44%', '50%'] + break + default: + polarOption = undefined + break + } + return polarOption +} + +export default polar diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/polarTitle/base.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/polarTitle/base.ts new file mode 100644 index 000000000..d9dd1128f --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/polarTitle/base.ts @@ -0,0 +1,29 @@ +import Theme from '../../../feature/theme' + +function getBaseTitle() { + return { + text: '', + subtext: '', + top: 'center', + left: 'center', + itemGap: Theme.config.titleItemGap, + textStyle: { + color: Theme.config.titleTextColor, + fontSize: Theme.config.titleTextFontSize, + lineHeight: 28, + fontWeight: 'normal' + }, + subtextStyle: { + color: Theme.config.titleSubTextColor, + fontSize: Theme.config.titleSubtextFontSize, + lineHeight: 24, + fontWeight: 'normal' + } + } +} + +function base() { + return getBaseTitle() +} + +export default base diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/polarTitle/index.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/polarTitle/index.ts new file mode 100644 index 000000000..769813040 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/polarTitle/index.ts @@ -0,0 +1,40 @@ +import base from './base' +import textStyle from './textStyle' +import subtextStyle from './subtextStyle' + +function title(iChartOption) { + const { text, title } = iChartOption + if (!title && !text) return {} + const titleOption = base() + // 传入title时忽略旧属性text + if (title) { + Object.assign(titleOption, title) + return titleOption + } + // 旧属性text + const { main: pieMain, sub: pieSub, position: textPosition, itemGap } = text + // 兼容旧属性text + titleOption.textAlign = 'center' + titleOption.left = '49.5%' + // 设置标题文本 + titleOption.text = pieMain?.text || text?.text || '' + // 设置标题文本 + titleOption.subtext = pieSub?.text || text?.subtext || '' + // 设置主副文本间距 + if (itemGap) { + titleOption.itemGap = itemGap + } + // 配置标题样式 + textStyle(titleOption, pieMain) + // 配置副标题样式 + subtextStyle(titleOption, pieSub) + // 兼容所有的title属性 + Object.assign(titleOption, text) + // 设置位置 + if (textPosition) { + Object.assign(titleOption, textPosition) + } + return titleOption +} + +export default title diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/polarTitle/subtextStyle.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/polarTitle/subtextStyle.ts new file mode 100644 index 000000000..dc361ef4d --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/polarTitle/subtextStyle.ts @@ -0,0 +1,10 @@ +function subtextStyle(titleOption, pieSub) { + if (pieSub?.textSize) { + titleOption.subtextStyle.fontSize = pieSub.textSize + titleOption.subtextStyle.color = pieSub.color || titleOption.subtextStyle.color + titleOption.subtextStyle.fontWeight = pieSub.fontWeight || 'normal' + titleOption.subtextStyle.lineHeight = pieSub.textSize * 1.2 + } +} + +export default subtextStyle diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/polarTitle/textStyle.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/polarTitle/textStyle.ts new file mode 100644 index 000000000..3858a201a --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/polarTitle/textStyle.ts @@ -0,0 +1,11 @@ +function textStyle(titleOption, pieMain) { + if (pieMain?.textSize) { + titleOption.textStyle.fontSize = pieMain.textSize + titleOption.textStyle.color = pieMain.color || titleOption.textStyle.color + titleOption.textStyle.fontWeight = pieMain.fontWeight || 'normal' + titleOption.textStyle.lineHeight = Number(pieMain.textSize) + } + Object.assign(titleOption.textStyle, pieMain) +} + +export default textStyle diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/radiusAxis/base.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/radiusAxis/base.ts new file mode 100644 index 000000000..d175fa5a5 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/radiusAxis/base.ts @@ -0,0 +1,45 @@ +import Theme from '../../../feature/theme' + +function getBaseRadiusAxis() { + return { + axisLine: { + show: false, + lineStyle: { + color: Theme.config.radiusAxisLineColor, + width: Theme.config.radiusAxisLineWidth, + type: Theme.config.radiusAxisLineType + } + }, + axisTick: { + length: 5, + show: false, + lineStyle: { + color: Theme.config.radiusAxisTickLineColor, + width: Theme.config.radiusAxisTickLineWidth, + type: Theme.config.radiusAxisTickLineType + } + }, + axisLabel: { + show: true, + color: Theme.config.radiusAxisLabelColor, + fontSize: Theme.config.radiusAxisLabelFontSize, + align: 'right', + margin: Theme.config.radiusAxisLabelGap * 5, + interval: 0 + }, + splitLine: { + lineStyle: { + type: Theme.config.radiusAxisSplitLineType, + color: Theme.config.radiusAxisSplitLineColor, + width: Theme.config.radiusAxisSplitLineWidth + } + } + } +} + +function base(chartName) { + const radiusAxisOption = getBaseRadiusAxis() + return radiusAxisOption +} + +export default base diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/radiusAxis/index.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/radiusAxis/index.ts new file mode 100644 index 000000000..9de7f64e8 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/radiusAxis/index.ts @@ -0,0 +1,29 @@ +import base from './base' +import merge from '../../../util/merge' +import Theme from '../../../feature/theme' + +function radiusAxis(iChartOption, chartName) { + let radiusAxisOpt + switch (chartName) { + case 'JadeJueChart': + radiusAxisOpt = base(chartName) + break + case 'PolarBarChart': + radiusAxisOpt = base(chartName) + radiusAxisOpt.axisLabel.margin = Theme.config.radiusAxisLabelGap + merge(radiusAxisOpt, iChartOption.radiusAxis) + break + case 'CircleProcessChart': + radiusAxisOpt = { + type: 'category', + show: false + } + break + default: + break + } + + return radiusAxisOpt +} + +export default radiusAxis diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/rectTitle/base.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/rectTitle/base.ts new file mode 100644 index 000000000..3391fceb1 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/rectTitle/base.ts @@ -0,0 +1,20 @@ +import Theme from '../../../feature/theme' + +// 直角坐标系中的title用于y轴名称 +function getBaseOption() { + return { + text: '', + textStyle: { + fontWeight: 'normal', + color: Theme.config.yAxisNameColor, + fontSize: Theme.config.yAxisNameFontSize + }, + padding: [0, 0, 0, 0] + } +} + +function base() { + return getBaseOption() +} + +export default base diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/rectTitle/index.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/rectTitle/index.ts new file mode 100644 index 000000000..0c6ea321f --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/rectTitle/index.ts @@ -0,0 +1,37 @@ +import base from './base' +import { getTextWidth } from '../../../util/dom' + +/** + * 配置纵轴名称 + */ +function title(iChartOption, chartName) { + let name = '' + if (iChartOption.yAxisName) { + name = iChartOption.yAxisName + } + if (iChartOption.yAxis && iChartOption.yAxis.name) { + name = iChartOption.yAxis.name + } + if (iChartOption.yAxis && iChartOption.yAxis.length === 1 && iChartOption.yAxis[0].name) { + name = iChartOption.yAxis[0].name + } + let theme = iChartOption.theme + let padding = iChartOption.padding + let title = base(theme) + // 名称 + title.text = name + // 如果图表为柱状图,并且为横向 + if (chartName == 'BarChart' && iChartOption.direction === 'horizontal') { + const nameLength = getTextWidth(name, 12) + title.right = padding[1] - nameLength - 24 + title.bottom = padding[2] + title.textAlign = 'left' + } else { + // 位置 + title.padding[0] = padding[0] - 30 + title.padding[3] = padding[3] + } + return title +} + +export default title diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/tooltip/axisPointer.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/tooltip/axisPointer.ts new file mode 100644 index 000000000..25ee71b1b --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/tooltip/axisPointer.ts @@ -0,0 +1,19 @@ +import Theme from '../../../feature/theme' + +function axisPointer(tooltip, chartName) { + switch (chartName) { + case 'ProcessBarChart': + case 'BarChart': + tooltip.axisPointer = { + type: 'shadow', + shadowStyle: { + color: Theme.config.tooltipAxisPointerShadow + } + } + break + default: + break + } +} + +export default axisPointer diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/tooltip/base.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/tooltip/base.ts new file mode 100644 index 000000000..8546e4eed --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/tooltip/base.ts @@ -0,0 +1,49 @@ +import Theme from '../../../feature/theme' + +const chartType = [ + 'CircleProcessChart', + 'GaugeChart', + 'RadarChart', + 'FunnelChart', + 'JadeJueChart', + 'LiquidfillChart', + 'PieChart', + 'PolarBarChart', + 'SunburstChart' +] + +function getbaseOption(chartName) { + const trigger = chartName && chartType.includes(chartName) ? 'item' : 'axis' + return { + trigger, + confine: true, + borderRadius: Theme.config.tooltipBorderRaduis, + axisPointer: { + z: 0, + type: 'line', + lineStyle: { + type: Theme.config.tooltipAxisPointerLineType, + width: Theme.config.tooltipAxisPointerLineWidth, + color: Theme.config.tooltipAxisPointerLineColor + }, + shadowStyle: { + color: Theme.config.tooltipAxisPointerShadow + } + }, + textStyle: { + color: Theme.config.tooltipAxisPointerTextColor, + fontSize: Theme.config.tooltipTextFontSize + }, + borderWidth: 0, + // 待定 + padding: [14, 16], + backgroundColor: Theme.config.tooltipBg, + formatter: undefined + } +} + +function base(chartName) { + return getbaseOption(chartName) +} + +export default base diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/tooltip/index.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/tooltip/index.ts new file mode 100644 index 000000000..985c760c3 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/tooltip/index.ts @@ -0,0 +1,24 @@ +import base from './base' +import axisPointer from './axisPointer' +import merge from '../../../util/merge' + +/** + * tipHtml 和 tipHtmlStyle 为旧属性,后续逐步废弃 + */ +function tooltip(iChartOption, chartName) { + const formatter = iChartOption.tipHtml + const formatterStyle = iChartOption.tipHtmlStyle + const tooltip = base(chartName) + if (formatter) { + tooltip.formatter = formatter + } + if (formatterStyle) { + tooltip.padding = formatterStyle.padding || tooltip.padding + tooltip.backgroundColor = formatterStyle.backgroundColor || tooltip.backgroundColor + } + axisPointer(tooltip, chartName) + merge(tooltip, iChartOption.tooltip) + return tooltip +} + +export default tooltip diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/axisLabel.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/axisLabel.ts new file mode 100644 index 000000000..6a795a334 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/axisLabel.ts @@ -0,0 +1,17 @@ +// 坐标轴刻度 +function axisLabel(xAxisUnit, xAxisItem, iChartOption) { + // 自定义formatter + xAxisUnit.axisLabel.formatter = xAxisItem.formatter || undefined + // 显示间隔 + xAxisUnit.axisLabel.interval = iChartOption.xAxisInterval || xAxisItem.interval + // 旋转角度 + xAxisUnit.axisLabel.rotate = iChartOption.xAxisLabelRotate || xAxisItem.labelRotate + // 超出省略 + if (iChartOption.xAxisEllipsis || xAxisItem.ellipsis) { + const xEllipsis = iChartOption.xAxisEllipsis || xAxisItem.ellipsis + xAxisUnit.axisLabel.overflow = xEllipsis.overflow || 'none' + xAxisUnit.axisLabel.width = xEllipsis.labelWidth + } +} + +export default axisLabel diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/axisLine.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/axisLine.ts new file mode 100644 index 000000000..4de804900 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/axisLine.ts @@ -0,0 +1,18 @@ +function axisLine(xAxisUnit, xAxisItem, iChartOption) { + if (iChartOption.xAxisLine || xAxisItem.line) { + const xAxisLine = iChartOption.xAxisLine || xAxisItem.line + if (xAxisLine.show === false) { + xAxisUnit.axisLine.show = false + xAxisUnit.axisTick.show = false + xAxisUnit.axisLabel.show = false + } + if (xAxisLine.lineStyle) { + Object.assign(xAxisUnit.axisLine.lineStyle, xAxisLine.lineStyle) + } + if (xAxisLine.textStyle) { + Object.assign(xAxisUnit.axisLabel, xAxisLine.textStyle) + } + } +} + +export default axisLine diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/axisMargin.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/axisMargin.ts new file mode 100644 index 000000000..56d5e4ee5 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/axisMargin.ts @@ -0,0 +1,19 @@ +function axisMargin(xAxisUnit, xAxisItem, iChartOption) { + if (xAxisItem && xAxisItem.axisMargin) { + let axisMargin = xAxisItem.axisMargin + let min = axisMargin.left + let max = axisMargin.right + if (min) { + xAxisUnit.min = function (value) { + return value.min - min + } + } + if (max) { + xAxisUnit.max = function (value) { + return value.max + max + } + } + } +} + +export default axisMargin diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/base.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/base.ts new file mode 100644 index 000000000..7feb65e40 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/base.ts @@ -0,0 +1,54 @@ +import Theme from '../../../feature/theme' + +function getBaseOption() { + return { + data: [], + // 坐标轴类型 + type: 'category', + // 坐标轴两边留白策略 + boundaryGap: true, + // 坐标轴在grid区域中分隔线 + splitLine: { + show: false, + lineStyle: { + width: Theme.config.xAxisSplitLineWidth, + color: Theme.config.xAxisSplitLineColor, + type: Theme.config.xAxisSplitLineType + } + }, + // 坐标轴名称样式配置 + nameTextStyle: { + color: Theme.config.xAxisNameColor, + fontSize: Theme.config.xAxisNameFontSize + }, + // 坐标轴线配置 + axisLine: { + lineStyle: { + width: Theme.config.xAxisLineWidth, + color: Theme.config.xAxisLineColor, + type: Theme.config.xAxisLineType + } + }, + // 坐标轴刻度配置 + axisTick: { + alignWithLabel: true, + lineStyle: { + width: Theme.config.xAxisTickLineWidth, + color: Theme.config.xAxisTickLineColor, + type: Theme.config.xAxisTickLineType + } + }, + // 坐标轴刻度标签配置 + axisLabel: { + color: Theme.config.xAxisLabelColor, + fontSize: Theme.config.xAxisLabelFontSize + } + } +} + +function base() { + const option = getBaseOption() + return option +} + +export default base diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/boundaryGap.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/boundaryGap.ts new file mode 100644 index 000000000..3f8196994 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/boundaryGap.ts @@ -0,0 +1,6 @@ +function boundaryGap(xAxisUnit, xAxisItem, iChartOption) { + let xAxisFullGrid = iChartOption.xAxisFullGrid || xAxisItem.fullGrid + xAxisUnit.boundaryGap = xAxisFullGrid !== true +} + +export default boundaryGap diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/index.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/index.ts new file mode 100644 index 000000000..b936ba7b6 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/index.ts @@ -0,0 +1,34 @@ +import base from './base' +import name from './name' +import axisLine from './axisLine' +import axisLabel from './axisLabel' +import axisMargin from './axisMargin' +import boundaryGap from './boundaryGap' +import merge from '../../../util/merge' +import { toArray } from '../../../util/type' + +function xAxis(iChartOpt, chartName) { + let xAxisResult = iChartOpt.xAxis || {} + xAxisResult = toArray(xAxisResult).map((xAxisItem) => { + const xAxisUnit = base() + // 坐标轴名称 + name(xAxisUnit, xAxisItem, iChartOpt) + // 坐标轴两边留白策略 + boundaryGap(xAxisUnit, xAxisItem, iChartOpt) + // 刻度标签 + axisLabel(xAxisUnit, xAxisItem, iChartOpt) + // 坐标轴刻度 + axisLine(xAxisUnit, xAxisItem, iChartOpt) + // 坐标轴前后留白 + axisMargin(xAxisUnit, xAxisItem, iChartOpt) + // 覆盖属性 + merge(xAxisUnit, xAxisItem) + return xAxisUnit + }) + return xAxisResult +} + +/** + * 配置横轴样式 + */ +export default xAxis diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/name.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/name.ts new file mode 100644 index 000000000..10633ea58 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/name.ts @@ -0,0 +1,7 @@ +function name(xAxisUnit, xAxisItem, iChartOpt) { + xAxisUnit.name = iChartOpt.xAxisName || xAxisItem.name + xAxisUnit.nameLocation = xAxisItem.nameLocation || 'end' + xAxisUnit.nameTextStyle = Object.assign(xAxisUnit.nameTextStyle, xAxisItem.nameTextStyle) +} + +export default name diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/xdata.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/xdata.ts new file mode 100644 index 000000000..135d4d673 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/xdata.ts @@ -0,0 +1,12 @@ +/** + * 从数据中拿出x轴的坐标数据 + */ +function xdata(data, xAxisKey) { + const xAxisData = [] + data.forEach((item) => { + xAxisData.push(item[xAxisKey]) + }) + return xAxisData +} + +export default xdata diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/xkey.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/xkey.ts new file mode 100644 index 000000000..e1917f64d --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/xAxis/xkey.ts @@ -0,0 +1,9 @@ +function xkey(iChartOption) { + let xAxisKey + iChartOption.xAxis.forEach((xAxisItem) => { + xAxisKey = xAxisItem.keyName + }) + return xAxisKey +} + +export default xkey diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/yAxis/base.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/yAxis/base.ts new file mode 100644 index 000000000..e9739baff --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/yAxis/base.ts @@ -0,0 +1,47 @@ +import Theme from '../../../feature/theme' + +function getBaseOption() { + return { + type: 'value', + axisLine: { + show: false, + lineStyle: { + width: Theme.config.yAxisLineWidth, + type: Theme.config.yAxisLineType, + color: Theme.config.yAxisLineColor + } + }, + axisTick: { + show: false, + lineStyle: { + width: Theme.config.yAxisTickLineWidth, + type: Theme.config.yAxisTickLineType, + color: Theme.config.yAxisTickLineColor + } + }, + axisLabel: { + color: Theme.config.yAxisLabelColor, + fontSize: Theme.config.yAxisLabelFontSize + }, + nameTextStyle: { + color: Theme.config.yAxisNameColor, + fontSize: Theme.config.yAxisNameFontSize + }, + splitLine: { + show: true, + lineStyle: { + width: Theme.config.yAxisSplitLineWidth, + type: Theme.config.yAxisSplitLineType, + color: Theme.config.yAxisSplitLineColor + }, + minInterval: undefined, + maxInterval: undefined + } + } +} + +function base() { + return getBaseOption() +} + +export default base diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/yAxis/index.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/yAxis/index.ts new file mode 100644 index 000000000..67de3ae3e --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/yAxis/index.ts @@ -0,0 +1,70 @@ +import base from './base' +import title from '../rectTitle' +import { isArray } from '../../../util/type' +import fluctuation from '../../../feature/fluctuation/index' +import { transformData } from '../../../feature/fluctuation/index' +import merge from '../../../util/merge' + +function yAxis(baseOpt, iChartOpt, chartName) { + let yAxisOpt = iChartOpt.yAxis + const yAxisName = iChartOpt.yAxisName + const data = iChartOpt.data + if (!isArray(yAxisOpt)) { + yAxisOpt = [yAxisOpt] + } + if (isNeedTitle(yAxisOpt, yAxisName)) { + baseOpt.title = title(iChartOpt, chartName) + } + // 循环y轴配置 + const yAxis = [] + yAxisOpt.forEach((item, index) => { + let temp = base() + if (item && item.unit) { + temp.axisLabel.formatter = `{value} ${item.unit}` + } + if (item && item.formatter) { + temp.axisLabel.formatter = item.formatter + } + if (item && item.name) { + item.nameTextStyle = Object.assign(temp.nameTextStyle, item.nameTextStyle) + } + if (item && item.labelTextStyle) { + item.labelTextStyle = Object.assign(temp.axisLabel, item.labelTextStyle) + } + if (item && item.splitLine) { + item.splitLine = Object.assign(temp.splitLine, item.splitLine) + } + // 静态给定y轴优化范围 + if (item && item.fluctuation == true) { + const newdata = transformData(data) + const value = fluctuation(newdata) + temp.min = value[0] + temp.max = value[1] + } + // 动态优化y轴范围 + if (item && item.allowRange) { + const newdata = transformData(data) + const value = fluctuation(newdata, item.allowRange) + temp.min = value[0] + temp.max = value[1] + } + temp = merge(temp, item) + if (index === 0 && yAxisOpt.length === 1 && temp.position !== 'right') { + delete temp.name + } + yAxis.push(temp) + }) + return yAxis +} + +function isNeedTitle(yAxisOpt, yAxisName) { + if (yAxisName) { + return true + } + if (yAxisOpt.length === 1 && yAxisOpt[0] && yAxisOpt[0].position !== 'right') { + return true + } + return false +} + +export default yAxis diff --git a/packages/vue/src/charts-beta/chart-core/base/option/config/yAxis/ydata.ts b/packages/vue/src/charts-beta/chart-core/base/option/config/yAxis/ydata.ts new file mode 100644 index 000000000..f544b51c9 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/config/yAxis/ydata.ts @@ -0,0 +1,17 @@ +/** + * 从数据中拿出y轴的坐标数据 + */ +function ydata(data, legendData) { + const seriesData = {} + legendData.forEach((legend) => { + seriesData[legend] = [] + }) + data.forEach((item) => { + legendData.forEach((legend) => { + seriesData[legend].push(item[legend]) + }) + }) + return seriesData +} + +export default ydata diff --git a/packages/vue/src/charts-beta/chart-core/base/option/init/index.ts b/packages/vue/src/charts-beta/chart-core/base/option/init/index.ts new file mode 100644 index 000000000..be2398040 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/option/init/index.ts @@ -0,0 +1,148 @@ +import { isArray, isString } from '../../util/type' +import Theme, { THEMES } from '../../feature/theme' + +/** + * 设置默认主题 + * @param {外部传入的配置} iChartOption + */ +function setDefaultTheme(iChartOption) { + iChartOption.theme = iChartOption.theme || Theme.themeName || THEMES.LIGHT +} + +/** + * 设置默认颜色集合 + * @param {外部传入的配置} iChartOption + */ +function setDefaultColor(iChartOption) { + if (!iChartOption.color) { + iChartOption.color = Theme.config.colorGroup + } +} + +/** + * 线性图/条形图专用,设置默认x轴Key值,默认取第一个数据的第一个Key + * @param {外部传入的配置} iChartOption + */ +function setDefaultXAxis(iChartOption) { + const data = iChartOption.data + let keyName + if (data && data.length > 0) { + const keys = Object.keys(data[0]) + if (keys.length > 0) { + keyName = keys[0] + } + } + if (isArray(iChartOption.xAxis)) { + iChartOption.xAxis.forEach((xAxisItem) => { + setXAxisKeyName(xAxisItem, keyName) + }) + } else if (typeof iChartOption.xAxis == 'object') { + setXAxisKeyName(iChartOption.xAxis, keyName) + iChartOption.xAxis = [iChartOption.xAxis] + } else { + iChartOption.xAxis = [{ keyName }] + } +} + +/** + * 给x轴赋值key,兼容旧版本属性 + */ +function setXAxisKeyName(xAxisItem, defaultKey) { + let keyName = defaultKey + if (isString(xAxisItem.data)) { + keyName = xAxisItem.data + delete xAxisItem.data + } + if (xAxisItem.keyName) { + keyName = xAxisItem.keyName + } + xAxisItem.keyName = keyName +} + +/** + * 线性图/条形图专用---设置图表的四周padding值 + * @param {外部传入的配置} iChartOption + */ +function setChartPadding(iChartOption) { + const defaultPadding = [50, 20, 50, 20] + const padding = iChartOption.padding || iChartOption.chartPadding + if (!padding) { + iChartOption.padding = defaultPadding + } else if (padding.length === 1) { + iChartOption.padding = [padding[0], 20, padding[0], 20] + } else if (padding.length === 2) { + iChartOption.padding = [padding[0], padding[1], padding[0], padding[1]] + } else if (padding.length === 3) { + iChartOption.padding = [padding[0], padding[1], padding[2], padding[1]] + } else { + iChartOption.padding = padding + } + delete iChartOption.chartPadding +} + +/** + * 设置图表的Legend属性 + * @param {外部传入的配置} iChartOption + */ +function setDefaultLegend(iChartOption) { + if (!iChartOption.legend) { + iChartOption.legend = { + show: true, + position: { + left: 'center', + bottom: 14 + }, + orient: 'horizontal' + } + } + if (iChartOption.legend.show === undefined) { + iChartOption.legend.show = false + } + if (!iChartOption.legend.orient) { + iChartOption.legend.orient = 'horizontal' + } + if (!iChartOption.legend.position) { + iChartOption.legend.position = { + left: 'center', + bottom: 15 + } + } +} + +/** + * 设置图表区域缩放功能 + * @param {外部传入的配置} iChartOption + */ +function setDefaultDataZoom(iChartOption) { + if (!iChartOption.dataZoom) { + iChartOption.dataZoom = { + show: false, + position: { + left: 40, + bottom: 18 + } + } + } + if (iChartOption.dataZoom.show === undefined) { + iChartOption.dataZoom.show = false + } + if (!iChartOption.dataZoom.position) { + iChartOption.dataZoom.position = { + left: 'center', + bottom: 20 + } + } +} + +// 初始化 iChartOption 的默认配置 +function init(iChartOption) { + setDefaultTheme(iChartOption) + setDefaultColor(iChartOption) + setDefaultXAxis(iChartOption) + setChartPadding(iChartOption) + setDefaultLegend(iChartOption) + setDefaultDataZoom(iChartOption) + return iChartOption +} + +export default init diff --git a/packages/vue/src/charts-beta/chart-core/base/register.ts b/packages/vue/src/charts-beta/chart-core/base/register.ts new file mode 100644 index 000000000..62703e367 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/register.ts @@ -0,0 +1,52 @@ +import LineChart from './components/LineChart' +import BarChart from './components/BarChart' +import PieChart from './components/PieChart' +import RadarChart from './components/RadarChart' + +function Register() { + this.registeredComp = {} +} + +function registerComp(options) { + this.registeredComp[options.name] = options.component +} + +function getRegisteredComp(name) { + if (name in this.registeredComp) { + return this.registeredComp[name] + } + return null +} + +Register.prototype.registerComp = registerComp +Register.prototype.getRegisteredComp = getRegisteredComp + +const register = new Register() + +const components = [ + { + name: 'AreaChart', + component: LineChart + }, + { + name: 'LineChart', + component: LineChart + }, + { + name: 'BarChart', + component: BarChart + }, + { + name: 'PieChart', + component: PieChart + }, + { + name: 'RadarChart', + component: RadarChart + } +] + +components.forEach((comp) => { + register.registerComp(comp) +}) +export default register diff --git a/packages/vue/src/charts-beta/chart-core/base/util/baseOption.ts b/packages/vue/src/charts-beta/chart-core/base/util/baseOption.ts new file mode 100644 index 000000000..d2ffbfbb5 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/util/baseOption.ts @@ -0,0 +1,13 @@ +const BaseOption = { + grid: { + top: '0', + left: '0', + right: '0', + bottom: '0', + containLabel: true + }, + legend: {}, + tooltip: {} +} + +export default BaseOption diff --git a/packages/vue/src/charts-beta/chart-core/base/util/centerDom.ts b/packages/vue/src/charts-beta/chart-core/base/util/centerDom.ts new file mode 100644 index 000000000..92cdbf3ee --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/util/centerDom.ts @@ -0,0 +1,80 @@ +import { isString, isArray, isDOM } from './type' +import { appendHTML, appendDom } from './dom' + +export function insertCenterDom(container, option) { + if (!option.centerDom) return + // 用户传入的中心dom渲染回调 + const { centerDom } = option + let containerWidth = Math.min(container.clientWidth, container.clientHeight) + let position = getComputedStyle(container).position + if (position === 'static') { + container.style.position = 'relative' + } + // 计算出中心圆宽度 + let radius = getCenterDomRadius(option) + // 生成中心dom的容器 + let centerDomContainer = createCenterDomContainer(containerWidth, radius) + // 生成中心自定义dom + let customDom = centerDom(centerDomContainer) + isString(customDom) && appendHTML(centerDomContainer, customDom) + isDOM(customDom) && appendDom(centerDomContainer, customDom) + appendDom(container, centerDomContainer) +} + +export function removeCenterDom(container) { + let centerDomContainer = container.getElementsByClassName('hui_center_dom_container') + Array.prototype.slice.call(centerDomContainer).forEach((element) => { + element.remove() + }) +} + +export function resizeCenterDom(container, option) { + let centerDomContainer = container?.getElementsByClassName('hui_center_dom_container') + let containerWidth = Math.min(container?.clientWidth, container?.clientHeight) + let radius = getCenterDomRadius(option) + if (centerDomContainer) { + Array.prototype.slice.call(centerDomContainer).forEach((element) => { + setSize(element, radius, containerWidth) + }) + } +} + +const getCenterDomRadius = (option) => { + let radius = option.position?.radius || '50%' + if (isArray(radius)) { + radius = percentToDecimal(radius[0]) + } else { + radius = percentToDecimal(radius) * 0.5 + } + return radius +} + +const createCenterDomContainer = (containerWidth, radius) => { + let centerDomContainer = document.createElement('div') + centerDomContainer.classList.add('hui_center_dom_container') + centerDomContainer.style.cssText = ` + top: 50%; + left: 50%; + display: flex; + color: #191919; + position: absolute; + border-radius: 50%; + align-items: center; + flex-direction: column; + justify-content: center; + transform: translate(-50%, -50%); + background: rgb(255, 255, 255); + box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.1)` + // 动态计算大小 + setSize(centerDomContainer, radius, containerWidth) + return centerDomContainer +} + +const setSize = (dom, radius, width) => { + dom.style.width = width * radius + 'px' + dom.style.height = width * radius + 'px' +} + +const percentToDecimal = (percent) => { + return parseFloat(percent) / 100 +} diff --git a/packages/vue/src/charts-beta/chart-core/base/util/cloneDeep.ts b/packages/vue/src/charts-beta/chart-core/base/util/cloneDeep.ts new file mode 100644 index 000000000..aa185a4f5 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/util/cloneDeep.ts @@ -0,0 +1,18 @@ +// 深拷贝 +function cloneDeep(obj) { + // 判断拷贝的要进行深拷贝的是数组还是对象,是数组的话进行数组拷贝,对象的话进行对象拷贝 + const objClone = Array.isArray(obj) ? [] : {} + // 进行深拷贝的不能为空,并且是对象或者是数组 + if (obj && typeof obj === 'object') { + for (const key in obj) { + if (obj[key] && typeof obj[key] === 'object') { + objClone[key] = cloneDeep(obj[key]) + } else { + objClone[key] = obj[key] + } + } + } + return objClone +} + +export default cloneDeep diff --git a/packages/vue/src/charts-beta/chart-core/base/util/color.ts b/packages/vue/src/charts-beta/chart-core/base/util/color.ts new file mode 100644 index 000000000..902c159a5 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/util/color.ts @@ -0,0 +1,144 @@ +/** + * 循环取出颜色 + */ +function getColor(colors, index) { + return colors[index % colors.length] +} + +/** + * 十六进制转rgba, 如codeToRGB("#6d8ff0",0.5) --> 'rgba(109,143,240,0.5)' + */ +function codeToRGB(code, opacity) { + if (code === undefined) { + return undefined + } + const result = [] + result.push(parseInt(code.substring(1, 3), 16)) + result.push(parseInt(code.substring(3, 5), 16)) + result.push(parseInt(code.substring(5), 16)) + return `rgba(${result.join(',')},${opacity})` +} + +/** + * rgba转十六进制 codeToHex('rgba(255,0,0,.5)') --> '#fffcfc' + * 将red、blue等转换为十六进制 + */ +function codeToHex(color) { + switch (color) { + case 'red': + return '#ff0000' + case 'blue': + return '#0000ff' + case 'green': + return '#00ff00' + case 'pink': + return '#FFC0CB' + case 'yellow': + return '#FFFF00' + case 'orange': + return '#FFA500' + case 'black': + return '#000000' + case 'white': + return '#ffffff' + case 'gray': + return '#808080' + case 'purple': + return '#800080' + } + if (color.includes('#')) { + if (color.length === 7) { + return color + } else if (color.length === 4) { + return color[0] + color[1] + color[1] + color[2] + color[2] + color[3] + color[3] + } + } + const values = color + .replace(/rgba?\(/, '') + .replace(/\)/, '') + .replace(/[\s+]/g, '') + .split(',') + const a = parseFloat(values[3] || 1) + const r = Math.floor(a * parseInt(values[0]) + (1 - a) * 255) + const g = Math.floor(a * parseInt(values[1]) + (1 - a) * 255) + const b = Math.floor(a * parseInt(values[2]) + (1 - a) * 255) + return `#${`0${r.toString(16)}`.slice(-2)}${`0${g.toString(16)}`.slice(-2)}${`0${b.toString(16)}`.slice(-2)}` +} + +/** + * 修改 'rgba(109,143,240,0.5)' 格式下的颜色透明度 + */ +function changeRgbaOpacity(rgba, opacity) { + const [r, g, b] = rgba.match(/\d+(\.\d+)?/g) + return `rgba(${r},${g},${b},${opacity})` +} + +/** + * 生成过渡色的方法(c1,c2必须为十六进制颜色 c1为初始颜色,c2为末尾颜色,n表示过渡色数量,返回值为 length === n+2 的数组 + * 如:colorsBetween('#ff0000', '#ffffff', 10) --> ['#ff0000','#ff1717','#ff2e2e','#ff4646','#ff5d5d','#ff7474','#ff8b8b','#ffa2a2','#ffb9b9','#ffd1d1','#ffe8e8','#ffffff' ] + */ +function colorsBetween(c1, c2, n) { + const Color = function Color(r, g, b) { + this.r = Math.abs(r) + this.g = Math.abs(g) + this.b = Math.abs(b) + if (typeof r === 'string') { + function fromHex(str) { + return str + .substr(1) + .match(/.{1,2}/g) + .map(function (n) { + return parseInt(n, 16) + }) + } + const v = fromHex(r) + this.r = v[0] + this.g = v[1] + this.b = v[2] + } + } + Color.prototype.diff = function (c) { + return new Color(this.r - c.r, this.g - c.g, this.b - c.b) + } + Color.prototype.dividedBy = function (n) { + return new Color(this.r / n, this.g / n, this.b / n) + } + Color.prototype.approach = function (c, c2) { + return new Color( + this.r > c.r ? this.r - c2.r : this.r + c2.r, + this.g > c.g ? this.g - c2.g : this.g + c2.g, + this.b > c.b ? this.b - c2.b : this.b + c2.b + ) + } + Color.prototype.toHex = function () { + function pad(n) { + const str = `${n}` + const pad = '00' + return pad.substring(0, pad.length - str.length) + str + } + const newR = Math.round(this.r).toString(16) + const newG = Math.round(this.g).toString(16) + const newB = Math.round(this.b).toString(16) + return `#${pad(newR)}${pad(newG)}${pad(newB)}` + } + // 简写16进制变为全写 + if (c1.length === 4) { + c1 = c1[0] + c1[1] + c1[1] + c1[2] + c1[2] + c1[3] + c1[3] + } + if (c2.length === 4) { + c2 = c2[0] + c2[1] + c2[1] + c2[2] + c2[2] + c2[3] + c2[3] + } + c1 = new Color(c1) + c2 = new Color(c2) + const diff = c1.diff(c2).dividedBy(n + 1) + const out = [c1] + for (let i = 0; i < n; i++) { + out.push(out[i].approach(c2, diff)) + } + out.push(c2) + return out.map(function (c) { + return c.toHex() + }) +} + +export { getColor, codeToRGB, codeToHex, colorsBetween, changeRgbaOpacity } diff --git a/packages/vue/src/charts-beta/chart-core/base/util/defendXSS.ts b/packages/vue/src/charts-beta/chart-core/base/util/defendXSS.ts new file mode 100644 index 000000000..bf2b39388 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/util/defendXSS.ts @@ -0,0 +1,91 @@ +/** + * 转义字符 防止xss攻击 + */ +const matchHtmlRegExp = /["'&<>/]/ +function escapeHtml(string) { + const str = `${string}` + const match = matchHtmlRegExp.exec(str) + if (!match) { + return str + } + let escape + let html = '' + let index + let lastIndex = 0 + + for (index = match.index; index < str.length; index++) { + switch (str.charCodeAt(index)) { + case 34: // " + escape = '"' + break + case 38: // & + escape = '&' + break + case 39: // ' + escape = ''' // modified from escape-html; used to be ''' + break + case 60: // < + escape = '<' + break + case 62: // > + escape = '>' + break // / + case 47: + escape = '/' + break + default: + continue + } + + if (lastIndex !== index) { + html += str.substring(lastIndex, index) + } + + lastIndex = index + 1 + html += escape + } + + return lastIndex !== index ? html + str.substring(lastIndex, index) : html +} + +const escapeHtmlAHO = (value) => { + if (typeof value === 'string') { + if (value.includes('&') && !value.includes('&')) { + value = value.replace(/&/g, '&') + } + if (value.includes('<')) { + value = value.replace(/')) { + value = value.replace(/>/g, '>') + } + if (value.includes('/')) { + value = value.replace(/\//g, '/') + } + if (value.includes('"')) { + value = value.replace(/"/g, '"') + } + if (value.includes("'")) { + value = value.replace(/'/g, ''') + } + } + return value +} + +const defendXSS = (obj) => { + if (typeof obj === 'string') { + return escapeHtml(obj) + } else if (typeof obj === 'number') { + return obj + } else if (typeof obj === 'object') { + for (const key in obj) { + obj[key] = defendXSS(obj[key]) + } + return obj + } else { + return obj + } +} + +export default defendXSS +export { escapeHtml, escapeHtmlAHO } diff --git a/packages/vue/src/charts-beta/chart-core/base/util/dom.ts b/packages/vue/src/charts-beta/chart-core/base/util/dom.ts new file mode 100644 index 000000000..98173f711 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/util/dom.ts @@ -0,0 +1,36 @@ +// 插入HTML元素 +const appendHTML = (dom, child) => { + dom.insertAdjacentHTML('beforeend', child) +} + +// 插入dom元素 +const appendDom = (dom, child) => { + dom.insertAdjacentElement('beforeend', child) +} + +// 判断父元素是否为指定的 class +const isParent = (targetElement, parentClass) => { + if (targetElement === document.body) { + return false + } else if (targetElement.parentNode.classList.contains(parentClass)) { + return targetElement.parentNode + } else { + return isParent(targetElement.parentNode, parentClass) + } +} + +// 计算一段字符的像素长度 +const getTextWidth = (text, fontSize = 12) => { + let result = 0 + const ele = document.createElement('span') + // 字符串中带有换行符时,会被自动转换成
标签,若需要考虑这种情况,可以替换成空格,以获取正确的宽度 + ele.innerText = text + // 不同的大小和不同的字体都会导致渲染出来的字符串宽度变化,可以传入尽可能完备的样式信息 + ele.setAttribute('style', `font-size: ${fontSize}px`) + document.documentElement.append(ele) + result = ele.offsetWidth + document.documentElement.removeChild(ele) + return result +} + +export { appendHTML, appendDom, isParent, getTextWidth } diff --git a/packages/vue/src/charts-beta/chart-core/base/util/equal.ts b/packages/vue/src/charts-beta/chart-core/base/util/equal.ts new file mode 100644 index 000000000..fe49fca3e --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/util/equal.ts @@ -0,0 +1,47 @@ +import { isObject, isArray } from './type' +import { getItemCount } from './math' + +/** + * 深比较两个对象 + * 如果对比的是两个数组,值一致,顺序改变,如[1,2]和[2,1],算不等 + * 如果对比的是两个对象,值一致,顺序改变。如{a:1,b:2}和{b:2,a:1},算相等 + */ +export function isObjEqual(obj1, obj2) { + if (!isObject(obj1) || !isObject(obj2)) { + return obj1 === obj2 + } + if (obj1 === obj2) { + return true + } + const obj1Keys = Object.keys(obj1) + const obj2Keys = Object.keys(obj2) + if (obj1Keys.length !== obj2Keys.length) { + return false + } + for (let key in obj1) { + const res = isObjEqual(obj1[key], obj2[key]) + if (!res) { + return false + } + } + return true +} + +/** + * 对比两个简单类型数组的值和长度是否完全一样(值的顺序允许改变) + * 值一致,顺序不一致,如[1,2]和[2,1],算相等 + */ +export function isArrayEqual(arr1, arr2) { + if (!isArray(arr1) || !isArray(arr2)) { + return false + } + if (arr1.length !== arr2.length) { + return false + } + for (let key of arr1) { + if (getItemCount(arr1, key) !== getItemCount(arr2, key)) { + return false + } + } + return true +} diff --git a/packages/vue/src/charts-beta/chart-core/base/util/event.ts b/packages/vue/src/charts-beta/chart-core/base/util/event.ts new file mode 100644 index 000000000..8a0cfe2a6 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/util/event.ts @@ -0,0 +1,14 @@ +// 绑定图表事件 +export function event(chartInstance, event) { + if (!event) return + const queryKeys = Object.keys(event) + queryKeys.forEach((qrKey) => { + const eKeys = Object.keys(event[qrKey]) + eKeys.forEach((key) => { + chartInstance.off(key) + chartInstance.on(key, qrKey, function (params) { + event[qrKey][key](params) + }) + }) + }) +} diff --git a/packages/vue/src/charts-beta/chart-core/base/util/hashMap.ts b/packages/vue/src/charts-beta/chart-core/base/util/hashMap.ts new file mode 100644 index 000000000..edf4ba6bd --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/util/hashMap.ts @@ -0,0 +1,100 @@ +class HashMap { + data + + constructor(initObj) { + this.data = newMap() + for (const key in initObj) { + if (Object.hasOwnProperty.call(initObj, key)) { + this.set(key, initObj[key]) + } + } + } + + hasKey(key) { + return this.data.has(key) + } + + get(key) { + return this.data.get(key) + } + + set(key, value) { + this.data.set(key, value) + return value + } + + each(callback, context) { + this.data.forEach((value, key) => { + callback.call(context, value, key) + }) + } + + keys() { + const keys = this.data.keys() + return typeof Map === 'function' ? Array.from(keys) : keys + } + + removeKey(key) { + this.data.delete(key) + } +} + +function newMap() { + return typeof Map === 'function' ? new Map() : new MapPolyfill() +} + +class MapPolyfill { + data + + delete(key) { + const existed = this.has(key) + if (existed) { + delete this.data[key] + } + return existed + } + + has(key) { + return this.data.hasOwnProperty(key) + } + + get(key) { + return this.data[key] + } + + set(key, value) { + this.data[key] = value + return this + } + + keys() { + return keys(this.data) + } + + forEach(callback) { + const data = this.data + for (const key in data) { + if (data.hasOwnProperty(key)) { + callback(data[key], key) + } + } + } +} + +function keys(obj) { + if (!obj) { + return [] + } + if (Object.keys) { + return Object.keys(obj) + } + let keyList = [] + for (let key in obj) { + if (obj.hasOwnProperty(key)) { + keyList.push(key) + } + } + return keyList +} + +export default HashMap diff --git a/packages/vue/src/charts-beta/chart-core/base/util/init/insert.ts b/packages/vue/src/charts-beta/chart-core/base/util/init/insert.ts new file mode 100644 index 000000000..248e92d74 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/util/init/insert.ts @@ -0,0 +1,158 @@ +import defendXSS from '../defendXSS' + +const ERROR_SVG = (fillColor) => { + return ` + + + + + + + + + + + + + + + + + + + + + + + ` +} + +const STAGE_EMPTY_SVG = (fillColor) => { + return ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ` +} + +const LOADING_SVG = (fillColor) => { + return ` + + + + + + + + + + + + + + + + + + +` +} + +function insertStateDom(container, state, option = { theme: 'light' }) { + let text = '' + let image = '' + let theme = option.theme || 'light' + let textSize = option.textSize || 14 + let textShow = option.textShow !== false + let imageSize = option.imageSize || 'auto' + let imageShow = option.imageShow !== false + let textColor = option.textColor || (theme.includes('dark') ? '#FFFFFF' : '#808080') + let imageColor = option.imageColor || (theme.includes('dark') ? '#FFFFFF' : '#191919') + let backgroundColor = option.backgroundColor || (theme.includes('dark') ? '#191919' : '#FFFFFF') + if (hasStateDom(container, state)) return + switch (state) { + case 'error': + image = ERROR_SVG(defendXSS(imageColor)) + text = '加载失败' + break + case 'empty': + image = STAGE_EMPTY_SVG(defendXSS(imageColor)) + text = '暂无数据' + break + case 'loading': + image = LOADING_SVG(defendXSS(imageColor)) + backgroundColor = option.backgroundColor || (theme.includes('dark') ? 'rgba(0,0,0,0.8)' : 'rgba(255,255,255,0.8)') + text = '加载中...' + break + case 'stage_empty': + image = STAGE_EMPTY_SVG(defendXSS(imageColor)) + backgroundColor = option.backgroundColor || 'transparent' + text = '没有符合所选时间内的数据' + break + case 'custom': + image = `` + text = option.text + break + } + image = option.image + ? `` + : image + text = option.text || text + let stateDom = `
+
${image}
+
${text}
+
` + container.insertAdjacentHTML('beforeend', stateDom) +} + +function removeStateDom(container, state) { + let doms = container.getElementsByClassName(`huicharts-${state}`) + for (let index = 0; index < doms.length; index++) { + let item = doms[index] + container.removeChild(item) + } +} + +function hasStateDom(container, state) { + let doms = container.getElementsByClassName(`huicharts-${state}`) + if (doms.length >= 1) { + return true + } else { + return false + } +} + +export { insertStateDom, removeStateDom } diff --git a/packages/vue/src/charts-beta/chart-core/base/util/math.ts b/packages/vue/src/charts-beta/chart-core/base/util/math.ts new file mode 100644 index 000000000..159ab77f6 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/util/math.ts @@ -0,0 +1,105 @@ +/** + * 百分比变成小数 + */ +const percentToDecimal = (percent) => { + return parseFloat(percent) / 100 +} + +/** + * 已知两条边和他们的夹角,求另一条边的长度 + * @param {边长} a + * @param {边长} b + * @param {a&b的夹角} angle + * return 另一条边的长度 + */ +const getEdge = (a, b, angle) => { + let edgeSqrt = a ** 2 + b ** 2 - 2 * a * b * Math.cos((angle / 180) * Math.PI) + return Math.sqrt(edgeSqrt) +} + +/** + * 已知三条边的长度,求任意角的大小 + * @param {边长} a + * @param {边长} b + * @param {边长} c + * return [b和c的夹角,a和c的夹角,b和a的夹角] + */ +const getAngle = (a, b, c) => { + let cosA = (b * b + c * c - a * a) / (2 * b * c) + let cosB = (a * a + c * c - b * b) / (2 * a * c) + let cosC = (a * a + b * b - c * c) / (2 * a * b) + let angleA = (Math.acos(cosA) * 180) / Math.PI + let angleB = (Math.acos(cosB) * 180) / Math.PI + let angleC = (Math.acos(cosC) * 180) / Math.PI + return [angleA, angleB, angleC] +} + +/** + * 已知三个点位置,求任意角的大小 + * @param {点} a + * @param {点} b + * @param {点} c + * return [c点所在角,a点所在角,b点所在角] + */ +const getAngleByPoints = (a, b, c) => { + let edgeA = Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2) + let edgeB = Math.sqrt((b.x - c.x) ** 2 + (b.y - c.y) ** 2) + let edgeC = Math.sqrt((a.x - c.x) ** 2 + (a.y - c.y) ** 2) + return getAngle(edgeA, edgeB, edgeC) +} + +/** + * 假设有三个点A(x1, y1), B(x2, y2), C(x3, y3),则可以通过计算向量AB和向量AC的叉积来判断C点在AB线段的顺时针方向还是逆时针方向。 + * 如果AB × AC > 0,则C在AB的逆时针方向;如果AB × AC < 0,则C在AB的顺时针方向。 此处的顺逆针指的是ABC的连线方向 + * console.log(pointsDirection(0, 0, 1, 1, 2, 0)); // "顺时针方向" + * console.log(pointsDirection(0, 0, 1, 1, 0, 2)); // "逆时针方向" + * console.log(pointsDirection(0, 0, 1, 1, 1, 1)); // "点C在线段AB上" + * 由于web中的坐标系和数学坐标系相反,因此顺逆的结论需要反过来 + */ +const pointsDirection = (a, b, c) => { + let { x: x1, y: y1 } = a + let { x: x2, y: y2 } = b + let { x: x3, y: y3 } = c + // 计算向量AB和向量AC的坐标差 + const ABx = x2 - x1 + const ABy = y2 - y1 + const ACx = x3 - x1 + const ACy = y3 - y1 + // 计算向量AB和向量AC的叉积 + const crossProduct = ABx * ACy - ABy * ACx + if (crossProduct > 0) { + // "顺时针方向" + return true + } else if (crossProduct < 0) { + // "逆时针方向" + return false + } else { + // "点C在线段AB上" + return false + } +} + +/** + * 获取数组中的每项出现的次数 + */ +const getItemCount = (arr, item) => { + let count = 0 + arr.forEach((key) => { + if (key === item) { + count++ + } + }) + return count +} + +/** + * 生成安全随机数算法 + */ +const getRandom = () => { + const array = new Uint32Array(1) + self.crypto.getRandomValues(array) + const random = parseFloat(`0.${array[0].toString()}`) + return random +} + +export { getEdge, getAngle, getRandom, getItemCount, getAngleByPoints, pointsDirection, percentToDecimal } diff --git a/packages/vue/src/charts-beta/chart-core/base/util/merge.ts b/packages/vue/src/charts-beta/chart-core/base/util/merge.ts new file mode 100644 index 000000000..57215bee5 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/util/merge.ts @@ -0,0 +1,64 @@ +import { isObject, isArray } from '../util/type' + +// 将 task 中所有属性合并到 target +function merge(target, task) { + if (!target) { + target = task + return target + } + if (isObject(task)) { + for (const key in task) { + if (target[key] === undefined || target[key] === null) { + target[key] = task[key] + } else if (isObject(task[key]) && !isArray(task[key])) { + merge(target[key], task[key]) + } else { + target[key] = task[key] + } + } + } + return target +} + +// 覆盖Series +function mergeSeries(iChartOption, baseOption) { + const userSeries = iChartOption.series + const baseSeries = baseOption.series + userSeries && + userSeries.forEach((uitem) => { + let isNewSeries = true + baseSeries.forEach((bitem) => { + if (bitem.name === uitem.name) { + isNewSeries = false + merge(bitem, uitem) + } + }) + if (isNewSeries) { + baseSeries.push(uitem) + } + }) +} + +// 覆盖VisualMap,采用直接替换的形式 +function mergeVisualMap(iChartOption, baseOption) { + let userVisualMap = iChartOption.visualMap + if (userVisualMap) { + baseOption.visualMap = userVisualMap + } +} + +// extend属性,采用直接替换的形式 +function mergeExtend(iChartOption, baseOption) { + if (!iChartOption) return + let extend = iChartOption.extend + if (!extend) return + for (const key in extend) { + if (Object.hasOwnProperty.call(extend, key)) { + baseOption[key] = extend[key] + } + } +} + +export { mergeSeries, mergeExtend, mergeVisualMap } + +export default merge diff --git a/packages/vue/src/charts-beta/chart-core/base/util/proxy.ts b/packages/vue/src/charts-beta/chart-core/base/util/proxy.ts new file mode 100644 index 000000000..285a6e5b7 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/util/proxy.ts @@ -0,0 +1,11 @@ +// 代理obj,实现obj的响应式更新 +export default function proxy(callBack) { + return new Proxy( + {}, + { + get(_, prop) { + return callBack()[prop] + } + } + ) +} diff --git a/packages/vue/src/charts-beta/chart-core/base/util/sort/max.ts b/packages/vue/src/charts-beta/chart-core/base/util/sort/max.ts new file mode 100644 index 000000000..810ade94e --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/util/sort/max.ts @@ -0,0 +1,53 @@ +export default function max(arr) { + function handle(arr) { + return arr.filter((item) => { + return Object.prototype.toString.call(item) === '[object Number]' + }) + } + + function heapAjust(data, i, length) { + let child = 2 * i + 1 + while (child <= length) { + const temp = data[i] + if (child + 1 <= length && data[child] < data[child + 1]) { + child = child + 1 + } + if (data[i] < data[child]) { + data[i] = data[child] + data[child] = temp + i = child + child = 2 * i + 1 + } else { + break + } + } + } + + function buildHeap(data) { + for (let i = Math.floor(data.length / 2); i >= 0; i--) { + heapAjust(data, i, data.length) + } + } + + function swap(arr, i, j) { + const temp = arr[i] + arr[i] = arr[j] + arr[j] = temp + } + + function heapSort(arr) { + const data = handle(arr).slice(0) + if (!Array.isArray(data)) return + if (Array.isArray(data) && data.length === 1) { + return data + } + buildHeap(data) + for (let i = data.length - 1; i >= 0; i--) { + swap(data, i, 0) + heapAjust(data, 0, i - 1) + } + return data[data.length - 1] + } + + return heapSort(arr) +} diff --git a/packages/vue/src/charts-beta/chart-core/base/util/sort/min.ts b/packages/vue/src/charts-beta/chart-core/base/util/sort/min.ts new file mode 100644 index 000000000..fa0dcf3fe --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/util/sort/min.ts @@ -0,0 +1,53 @@ +export default function min(arrMin) { + function handleMin(arrMin) { + return arrMin.filter((item) => { + return Object.prototype.toString.call(item) === '[object Number]' + }) + } + + function heapJustMin(dataMin, iMin, length) { + let childMin = 2 * iMin + 1 + while (childMin <= length) { + const temp = dataMin[iMin] + if (childMin + 1 <= length && dataMin[childMin] < dataMin[childMin + 1]) { + childMin = childMin + 1 + } + if (dataMin[iMin] < dataMin[childMin]) { + dataMin[iMin] = dataMin[childMin] + dataMin[childMin] = temp + iMin = childMin + childMin = 2 * iMin + 1 + } else { + break + } + } + } + + function buildHeapMin(dataMin) { + for (let iMin = Math.floor(dataMin.length / 2); iMin >= 0; iMin--) { + heapJustMin(dataMin, iMin, dataMin.length) + } + } + + function swap(arrMin, iMin, jMin) { + const temp = arrMin[iMin] + arrMin[iMin] = arrMin[jMin] + arrMin[jMin] = temp + } + + function heapSort(arrMin) { + const dataMin = handleMin(arrMin).slice(0) + if (!Array.isArray(dataMin)) return + if (Array.isArray(dataMin) && dataMin.length === 1) { + return dataMin + } + buildHeapMin(dataMin) + for (let iMin = dataMin.length - 1; iMin >= 0; iMin--) { + swap(dataMin, iMin, 0) + heapJustMin(dataMin, 0, iMin - 1) + } + return dataMin[0] + } + + return heapSort(arrMin) +} diff --git a/packages/vue/src/charts-beta/chart-core/base/util/throttle.ts b/packages/vue/src/charts-beta/chart-core/base/util/throttle.ts new file mode 100644 index 000000000..9d55c9693 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/util/throttle.ts @@ -0,0 +1,50 @@ +function throttle(delay, callback, options) { + const { noTrailing = false, noLeading = false, debounceMode = undefined } = options || {} + let timeoutID + let cancelled = false + let lastExec = 0 + function clearExistingTimeout() { + if (timeoutID) { + clearTimeout(timeoutID) + } + } + function cancel(options) { + const { upcomingOnly = false } = options || {} + clearExistingTimeout() + cancelled = !upcomingOnly + } + function wrapper(...arguments_) { + const self = this + const elapsed = Date.now() - lastExec + if (cancelled) { + return + } + function exec() { + lastExec = Date.now() + callback.apply(self, arguments_) + } + function clear() { + timeoutID = undefined + } + if (!noLeading && debounceMode && !timeoutID) { + exec() + } + clearExistingTimeout() + if (debounceMode === undefined && elapsed > delay) { + if (noLeading) { + lastExec = Date.now() + if (!noTrailing) { + timeoutID = setTimeout(debounceMode ? clear : exec, delay) + } + } else { + exec() + } + } else if (noTrailing !== true) { + timeoutID = setTimeout(debounceMode ? clear : exec, debounceMode === undefined ? delay - elapsed : delay) + } + } + wrapper.cancel = cancel + return wrapper +} + +export default throttle diff --git a/packages/vue/src/charts-beta/chart-core/base/util/tips.ts b/packages/vue/src/charts-beta/chart-core/base/util/tips.ts new file mode 100644 index 000000000..81e616ade --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/util/tips.ts @@ -0,0 +1,14 @@ +const packageName = 'HUI-Charts' + +function warn(tip) { + console.warn(`[${packageName}] Warning: ${tip}`) +} + +function error(tip) { + console.error(`[${packageName}] Error: ${tip}`) +} + +export default { + warn, + error +} diff --git a/packages/vue/src/charts-beta/chart-core/base/util/type.ts b/packages/vue/src/charts-beta/chart-core/base/util/type.ts new file mode 100644 index 000000000..f346752f1 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/base/util/type.ts @@ -0,0 +1,32 @@ +export function isArray(value) { + return Object.prototype.toString.call(value) === '[object Array]' +} + +export const isObject = (value) => value !== null && typeof value === 'object' + +export const isFunction = (value) => typeof value === 'function' + +export const isString = (value) => typeof value === 'string' + +export const isBoolean = (value) => typeof value === 'boolean' + +export const isNumber = (value) => typeof value === 'number' + +export const isUndef = (value) => typeof value === 'undefined' + +export const isDOM = + typeof HTMLElement === 'object' + ? function (dom) { + return dom instanceof HTMLElement + } + : function (dom) { + return dom && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string' + } + +export const toArray = (value) => { + if (!isArray(value)) { + return [value] + } else { + return value + } +} diff --git a/packages/vue/src/charts-beta/chart-core/common/constants.ts b/packages/vue/src/charts-beta/chart-core/common/constants.ts new file mode 100644 index 000000000..23b6ebbff --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/common/constants.ts @@ -0,0 +1,119 @@ +const SF = () => ({ show: false }) +const DEFAULT_THEME = { + categoryAxis: { axisLine: SF(), axisTick: SF(), splitLine: SF() }, + valueAxis: { axisLine: SF() }, + line: { smooth: true }, + grid: { containLabel: true, left: 10, right: 10 } +} + +let DEFAULT_COLORS = ['#19d4ae', '#5ab1ef', '#fa6e86', '#ffb980', '#0067a6', '#c4b4e4'] + +DEFAULT_COLORS = DEFAULT_COLORS.concat(['#d87a80', '#9cbbff', '#d9d0c7', '#87a997', '#d49ea2', '#5b4947', '#7ba3a8']) + +const HEAT_MAP_COLOR = ['#5990FD', '#2DA769', '#EEBA18', '#F43146'] + +const HEAT_BMAP_COLOR = ['blue', 'blue', 'green', 'yellow', 'red'] + +const _SAAS_DEFAULT_COLORS1 = ['#2070F3', '#00A874', '#745EF7', '#8BC2FF', '#1B3F86', '#FDC000', '#00A2B5'] +const _SAAS_DEFAULT_COLORS2 = ['#FC916E', '#1F55B5', '#2D94FF', '#82DBB1', '#F36900', '#2A8290', '#EE8DDB'] +const _SAAS_DEFAULT_COLORS3 = ['#278661', '#4FA700', '#5CACFF', '#9185F0', '#B62BF7', '#26616B', '#F46465'] +const _SAAS_DEFAULT_COLORS4 = ['#5531EB', '#FCDBAA', '#112857', '#E61866', '#B98C1D', '#D41DBC', '#87D5E5'] +const SAAS_DEFAULT_COLORS = _SAAS_DEFAULT_COLORS1 + .concat(_SAAS_DEFAULT_COLORS2) + .concat(_SAAS_DEFAULT_COLORS3) + .concat(_SAAS_DEFAULT_COLORS4) + +const SAAS_DEFAULT_SAME_COLORS = { + blue: [ + { color: '#2E94FF', idx: 3 }, + { color: '#8BC3FF', idx: 5 }, + { color: '#1F55B5', idx: 1 }, + { color: '#2070F3', idx: 2 }, + { color: '#5CACFF', idx: 4 }, + { color: '#B9DBFF', idx: 6 }, + { color: '#1B3F86', idx: 0 } + ], + green: [ + { color: '#00A874', idx: 3 }, + { color: '#82DBB1', idx: 5 }, + { color: '#236549', idx: 1 }, + { color: '#278661', idx: 2 }, + { color: '#50C291', idx: 4 }, + { color: '#AFEDCE', idx: 6 }, + { color: '#1D4A37', idx: 0 } + ] +} + +const DEFAULT_CONFIG = { + tooltip: { + backgroundColor: '#fff', + borderColor: '#fff', + padding: [8, 16], + textStyle: { + color: '#191919', + fontSize: '14' + }, + extraCssText: 'box-shadow:0 8px 20px rgba(0, 0, 0, 0.16);' + }, + legend: { + icon: 'circle', + bottom: 0, + itemGap: 24, + itemWidth: 10, + itemHeight: 10, + itemStyle: { + borderColor: 'transparent' + } + } +} + +const itemPoint = (color) => { + return [ + '' + ].join('') +} + +const itemLabel = (seriesName, onlyLabel) => { + return [ + '${seriesName}` + ].join('') +} + +const itemContent = (content) => { + return ['${content}`].join('') +} + +const STATIC_PROPS = ['initOptions', 'loading', 'dataEmpty', 'judgeWidth', 'widthChangeDelay'] + +let ECHARTS_SETTINGS = ['grid', 'dataZoom', 'visualMap', 'toolbox', 'title', 'legend', 'xAxis'] + +ECHARTS_SETTINGS = ECHARTS_SETTINGS.concat(['yAxis', 'radar', 'tooltip', 'axisPointer', 'brush', 'geo']) +ECHARTS_SETTINGS = ECHARTS_SETTINGS.concat(['timeline', 'graphic', 'series', 'backgroundColor', 'textStyle']) + +export { + DEFAULT_THEME, + DEFAULT_COLORS, + HEAT_MAP_COLOR, + HEAT_BMAP_COLOR, + itemPoint, + itemLabel, + itemContent, + STATIC_PROPS, + ECHARTS_SETTINGS, + DEFAULT_CONFIG, + SAAS_DEFAULT_COLORS, + SAAS_DEFAULT_SAME_COLORS +} diff --git a/packages/vue/src/charts-beta/chart-core/common/defendXSS.ts b/packages/vue/src/charts-beta/chart-core/common/defendXSS.ts new file mode 100644 index 000000000..c798e70ac --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/common/defendXSS.ts @@ -0,0 +1,68 @@ +/** + * 转义字符 防止xss攻击 + */ +const matchHtmlRegExp = /["'&<>/]/; +function escapeHtml(string) { + const str = `${string}`; + const match = matchHtmlRegExp.exec(str); + if (!match) { + return str; + } + let escape; + let html = ''; + let index; + let lastIndex = 0; + + for (index = match.index; index < str.length; index++) { + switch (str.charCodeAt(index)) { + case 34: // " + escape = '"'; + break; + case 38: // & + escape = '&'; + break; + case 39: // ' + escape = '''; + break; + case 60: // < + escape = '<'; + break; + case 62: // > + escape = '>'; + break; // / + case 47: + escape = '/'; + break; + default: + continue; + } + + if (lastIndex !== index) { + html += str.substring(lastIndex, index); + } + + lastIndex = index + 1; + html += escape; + } + + return lastIndex !== index ? html + str.substring(lastIndex, index) : html; +} + + +const defendXSS = obj => { + if (typeof obj === 'string') { + return escapeHtml(obj); + } else if (typeof obj === 'number') { + return obj; + } else if (typeof obj === 'object') { + for (const key in obj) { + obj[key] = defendXSS(obj[key]); + } + return obj; + } else { + return obj; + } +}; + +export default defendXSS; +export { escapeHtml }; diff --git a/packages/vue/src/charts-beta/chart-core/common/extend.ts b/packages/vue/src/charts-beta/chart-core/common/extend.ts new file mode 100644 index 000000000..de91d433a --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/common/extend.ts @@ -0,0 +1,68 @@ +import { merge } from './util' +import { isObject } from './type' +import { setObj as set } from './object' + +const isArr = Array.isArray + +function removeNullKeys(obj) { + if (typeof obj !== 'object' || obj === null) { + return obj; + } + for (let key in obj) { + if (obj[key] === null) { + delete obj[key]; + } else { + obj[key] = removeNullKeys(obj[key]); + if (typeof obj[key] === 'object' && Object.keys(obj[key]).length === 0) { + obj[key] = undefined + } + } + } + return obj; +} + +export default ({ option, extend }) => { + const options = removeNullKeys(option) + if (!extend) { + return + } + + Object.keys(extend).forEach((key) => { + const value = extend[key] + + if (~key.indexOf('.')) { // (其他类型)属性名中带. 为true + set(options, key, value) + } else if (typeof value === 'function') { // 当属性为函数时,设置的是函数的返回值 + options[key] = value(options[key]) + } else if (isArr(options[key]) && isArr(value)) { // 当被修改值和替换值都为数组时,key符合attrList就合并 + const attrList = [ + 'series', + 'yAxis', + 'xAxis', + 'color', + 'dataZoom', + 'legend', + 'toolbox', + 'grid', + 'graphic', + 'timeline', + 'visualMap', + 'brush' + ] + if (~attrList.indexOf(key)) { // attrList,指定的key才能合并处理 + options[key] = merge(options[key], value) + } + } else { // 属性为对象(eg: tooltip)或包含对象的数组(eg: series) + if (isArr(options[key]) && isObject(options[key][0])) { // 属性值是包含对象数组 + options[key].forEach((option, i) => (options[key][i] = { ...option, ...value })) + } else if (isObject(options[key])) { // 被替换属性值是对象 + let optionBase = options[key] + + options[key] = { ...optionBase, ...value } // 两者合并,后者覆盖前者 + } else { // 直接覆盖替换 + options[key] = value + } + } + }) + return options +} diff --git a/packages/vue/src/charts-beta/chart-core/common/numerify.ts b/packages/vue/src/charts-beta/chart-core/common/numerify.ts new file mode 100644 index 000000000..1d5331f5f --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/common/numerify.ts @@ -0,0 +1,317 @@ +/** + * Copyright (c) 2022 - present TinyVue Authors. + * Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. + * + * Use of this source code is governed by an MIT-style license. + * + * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, + * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR + * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + * + */ + +let ABBR = { th: 3, mi: 6, bi: 9, tr: 12 } +let abbrLabel = { th: 'k', mi: 'm', bi: 'b', tr: 't' } +let DEFAULT_OPTIONS = { zeroFormat: null, nullFormat: null, defaultFormat: '0,0', scalePercentBy100: true, abbrLabel } +let [TRILLION, BILLION, MILLION, THOUSAND] = [1e12, 1e9, 1e6, 1e3] + +const numIsNaN = (value) => typeof value === 'number' && isNaN(value) +let options = {} +let formats = {} + +function getBoundedPrecision(value, maxDecimals, optionals) { + let splitValue = value.toString().split('e-') + let exponent = splitValue.length === 2 ? Number(splitValue[1]) : 0 + + splitValue = splitValue[0].split('.') + exponent = splitValue.length === 2 ? splitValue[1].length + exponent : exponent + + let minDecimals = maxDecimals - (optionals || 0) + return Math.min(Math.max(exponent, minDecimals), maxDecimals) +} + +function toFixed(value, maxDecimals, roundingFunction, optionals) { + let boundedPrecision = getBoundedPrecision(value, maxDecimals, optionals) + let power = 10 ** boundedPrecision + + let output = (roundingFunction(value * `1e+${boundedPrecision}`) / power).toFixed(boundedPrecision) + + if (optionals > maxDecimals - boundedPrecision) { + let optionalsRegExp = new RegExp('\\.?0{1,' + (optionals - (maxDecimals - boundedPrecision)) + '}$') + + output = output.replace(optionalsRegExp, '') + } + + return output +} + +function judgForFunc(num, abs, abbrForce) { + let flag + + if (num === 0) { + flag = (abs >= TRILLION && !abbrForce) || abbrForce === 't' + } else if (num === 1) { + flag = (abs < TRILLION && abs >= BILLION && !abbrForce) || abbrForce === 'b' + } else if (num === 2) { + flag = (abs < BILLION && abs >= MILLION && !abbrForce) || abbrForce === 'm' + } else if (num === 3) { + flag = (abs < MILLION && abs >= THOUSAND && !abbrForce) || abbrForce === 'k' + } + + return flag +} + +const negativeSigned = function ({ format, value }) { + return ~format.indexOf('+') ? format.indexOf('+') : value < 0 ? format.indexOf('-') : -1 +} + +const updateAbbrV = function (params) { + let { abbr, abbrForce, format, value, abs } = params + abbrForce = format.match(/a(k|m|b|t)?/) + abbrForce = abbrForce ? abbrForce[1] : false + + if (~format.indexOf(' a')) { + abbr = ' ' + } + + format = format.replace(new RegExp(abbr + 'a[kmbt]?'), '') + + if (judgForFunc(0, abs, abbrForce)) { + abbr += options.abbrLabel.tr + value = value / TRILLION + } else if (judgForFunc(1, abs, abbrForce)) { + abbr += options.abbrLabel.bi + value = value / BILLION + } else if (judgForFunc(2, abs, abbrForce)) { + abbr += options.abbrLabel.mi + value = value / MILLION + } else if (judgForFunc(3, abs, abbrForce)) { + abbr += options.abbrLabel.th + value = value / THOUSAND + } + + Object.assign(params, { abbr, abbrForce, format, value, abs }) +} + +const handlePrecision = function (params) { + let { roundingFunction, value, format } = params + let number = value.toString().split('.')[0] + let precision = format.split('.')[1] + if (precision) { + if (~precision.indexOf('[')) { + precision = precision.replace(']', '') + precision = precision.split('[') + + params.decimal = toFixed(value, precision[0].length + precision[1].length, roundingFunction, precision[1].length) + } else { + params.decimal = toFixed(value, precision.length, roundingFunction) + } + + number = params.decimal.split('.')[0] + params.decimal = ~params.decimal.indexOf('.') ? '.' + params.decimal.split('.')[1] : '' + + if (params.optDec && Number(params.decimal.slice(1)) === 0) { + params.decimal = '' + } + } else { + number = toFixed(value, 0, roundingFunction) + } + return number +} + +function formatNumber(params) { + let number = handlePrecision(params) + let thousands = params.format.indexOf(',') + + let leadingCount = (params.format.split('.')[0].split(',')[0].match(/0/g) || []).length + + if (params.abbr && !params.abbrForce && Number(number) >= 1000 && params.abbr !== ABBR.trillion) { + number = String(Number(number) / 1000) + params.abbr = ABBR.million + } + + if (~number.indexOf('-')) { + number = number.slice(1) + params.neg = true + } + + if (number.length < leadingCount) { + for (let i = leadingCount - number.length; i > 0; i--) { + number = '0' + number + } + } + + if (thousands > -1) { + number = number.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,') + } + + if (!params.format.indexOf('.')) { + number = '' + } + return number +} + +function getOutStr({ decimal, negFlag, neg, signed, abbr }, number) { + let outStr = number + decimal + (abbr || '') + + if (negFlag) { + outStr = negFlag && neg ? `(${outStr})` : outStr + } else { + if (signed >= 0) { + const symbol = neg ? '-' : '+' + outStr = signed === 0 ? symbol + outStr : outStr + symbol + } else if (neg) { + outStr = '-' + outStr + } + } + return outStr +} + +function numberToFormat(options, value, format, roundingFunction) { + let params = { + abs: Math.abs(value), + negFlag: false, + optDec: false, + abbr: '', + decimal: '', + neg: false, + abbrForce: void 0, + signed: void 0, + format: format || '', + value: value || 0, + roundingFunction + } + + if (~format.indexOf('(')) { + params.negFlag = true + params.format = format.replace(/[(|)]/g, '') + } else if (~params.format.indexOf('+') || ~params.format.indexOf('-')) { + params.signed = negativeSigned(params) + + params.format = format.replace(/[+|-]/g, '') + } + + if (~params.format.indexOf('a')) { + updateAbbrV(params) + } + + if (~params.format.indexOf('[.]')) { + params.optDec = true + params.format = format.replace('[.]', '.') + } + + const number = formatNumber(params) + + return getOutStr(params, number) +} + +function extend(target, sub) { + Object.keys(sub).forEach((key) => { + target[key] = sub[key] + }) +} + +let numerifyPercent = { + regexp: /%/, + + format: function format(value, formatType, roundingFunction, numerify) { + let space = ~formatType.indexOf(' %') ? ' ' : '' + let outStr = void 0 + + if (numerify.options.scalePercentBy100) { + value = value * 100 + } + + formatType = formatType.replace(/\s?%/, '') + outStr = numerify._numberToFormat(value, formatType, roundingFunction) + + if (~outStr.indexOf(')')) { + outStr = outStr.split('') + outStr.splice(-1, 0, space + '%') + outStr = outStr.join('') + } else { + outStr = outStr + space + '%' + } + + return outStr + } +} + +extend(options, DEFAULT_OPTIONS) + +let numerify + +function format(value, formatType, roundingFunc) { + let { zeroFormat, nullFormat, defaultFormat } = options + + formatType = formatType || defaultFormat + roundingFunc = roundingFunc || Math.round + + let { output, fmtFunc } = {} + + if (value === 0 && zeroFormat !== null) { + output = zeroFormat + } else if (value === null && nullFormat !== null) { + output = nullFormat + } else { + for (let key in formats) { + if (formats[key] && formatType.match(formats[key].regexp)) { + fmtFunc = formats[key].format + break + } + } + + fmtFunc = fmtFunc || numberToFormat.bind(null, options) + output = fmtFunc(value, formatType, roundingFunc, numerify) + } + + return output +} + +numerify = function (input, formatType, roundingFunc) { + let { zeroFormat, nullFormat } = options + let value = Number(input) || null + + if (typeof input === 'undefined' || input === 0) { + value = 0 + } else if (numIsNaN(input) || input === null) { + value = null + } else if (typeof input === 'string') { + value = Number(input) + + if (input === zeroFormat && zeroFormat) { + value = 0 + } else if ((input === nullFormat && nullFormat) || !input.replace(/[^0-9]+/g, '').length) { + value = null + } + } + + return format(value, formatType, roundingFunc) +} + +const _register = (name, format) => { + formats[name] = format +} + +const _unregister = (name) => { + formats[name] = null +} + +const _setOptions = (opts) => { + extend(options, opts) +} + +const _reset = () => { + extend(options, DEFAULT_OPTIONS) +} + +numerify.options = options +numerify._numberToFormat = numberToFormat.bind(null, options) +numerify.register = _register +numerify.unregister = _unregister +numerify.setOptions = _setOptions +numerify.reset = _reset + +numerify.register('percentage', numerifyPercent) + +export default numerify diff --git a/packages/vue/src/charts-beta/chart-core/common/object.ts b/packages/vue/src/charts-beta/chart-core/common/object.ts new file mode 100644 index 000000000..c58f55d0b --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/common/object.ts @@ -0,0 +1,376 @@ + +/** +* Copyright (c) 2022 - present TinyVue Authors. +* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. +* +* Use of this source code is governed by an MIT-style license. +* +* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, +* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR +* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. +* +*/ + +import { hasOwn, typeOf, isObject, isPlainObject, isNull } from './type' + +/** + * 将对象的每个属性值进行循环处理。 + * + * @param {Object} obj 要处理的对象 + * @param {Function} handle 进行循环处理的函数,参数为对象属性名及属性值 + */ +export const each = (obj, handle) => { + if (typeof handle !== 'function') { + return + } + for (const name in obj) { + if (hasOwn.call(obj, name)) { + if (handle(name, obj[name]) === false) { + break + } + } + } +} + +let extend + +/** + * 获得指定的命名空间的值对象。 + * + * + * @param {Object} data 查找数据源 + * @param {String} names 查找属性命名空间字符串 + * @param {Boolean} [isExceptRoot] 是否排除 names 的第一个节点,默认 false + * @returns {Object} + */ +export const getObj = (data, names, isExceptRoot) => { + if (!data || !isPlainObject(data) || !names || typeof names !== 'string') { + return + } + + names = names.split('.') + + let obj = data + const len = names.length + + if (len > 1) { + const startIndex = isExceptRoot ? 1 : 0 + + for (let i = startIndex; i < len; i++) { + obj = obj[names[i]] + + if (isNull(obj)) { + return obj + } + } + + return obj + } else { + return obj[names[0]] + } +} + +/** + * 设置指定的命名空间的值对象。 + * + * @param {Object} data 设置数据源 + * @param {String} names 查找属性命名空间字符串 + * @param {Object} value 设置的值 + * @param {boolean} [isMerge] 是否覆盖还是合并,默认覆盖 + * @returns {Object} + */ +export const setObj = (data, names, value, isMerge) => { + if (!data || !isPlainObject(data) || !names || typeof names !== 'string') { + return data + } + + names = names.split('.') + + const obj = data + let len = names.length + let item = names[0] + + if (len > 1) { + len-- + + let tmpl = obj + let name, target + + for (let i = 0; i < len; i++) { + name = names[i] + target = tmpl[name] + + if (target === null || !isPlainObject(target)) { + tmpl[name] = {} + target = tmpl[name] + } + + tmpl = target + } + + item = names[len] + + isMerge ? (isPlainObject(tmpl[item]) ? extend(true, tmpl[item], value) : (tmpl[item] = value)) : (tmpl[item] = value) + } else { + isMerge ? (isPlainObject(obj[item]) ? extend(true, obj[item], value) : (obj[item] = value)) : (obj[item] = value) + } + + return obj +} + +/** + * 根据指定的字段属性名,复制对应的数据。 + * + * @param {Object} data 源数据,合并数据源 + * @param {Array} [fields] 指定的值得命名空间字符串的数值 + * @param {Boolean} [isMerge] 是否覆盖还是合并,默认false覆盖 + * @param {Boolean} [isExclude] 是否排除指定的fields复制,默认false + * @returns {Array} + */ +export const copyField = (data, fields, isMerge, isExclude) => { + const setValue = (obj, result, name, key, isMerge) => { + const include = key.indexOf(name) === 0 + const keySplit = key.split(name) + const hasNextDot = keySplit[1] && keySplit[1].indexOf('.') === 0 + + if (name === key || (include && hasNextDot)) { + if (name !== key) { + each(getObj(obj, name), (field) => { + setValue(obj, result, `${name}.${field}`, key) + }) + } + } else { + if (!fields.includes(name)) { + setObj(result, name, getObj(obj, name), isMerge) + } + } + } + const innerCopyFields = (obj, fields, isMerge, isExclude) => { + const result = {} + + if (isExclude) { + each(obj, (name) => fields.forEach((key) => setValue(obj, result, name, key, isMerge))) + } else { + fields.forEach((field) => setObj(result, field, getObj(obj, field), isMerge)) + } + + return result + } + + if (isPlainObject(data)) { + return Array.isArray(fields) ? innerCopyFields(data, fields, isMerge, isExclude) : extend(isMerge !== false, {}, data) + } + + return data +} + +/** + * 复制数组数据,数据如包含对象,则深度复制,并返回一个新数组,如果不是数组则直接返回原对象。 + * + * @param {Array} arr 要复制的数组 + * @returns {Array} + */ +export const copyArray = (arr) => (Array.isArray(arr) ? arr.map((item) => copyField(item)) : arr) + +/** + * 对象复制,支持深度复制,修复 $.extend 数组复制的问题, 参数同 $.extend。 + * + * @param {Boolean} deep 如果是 true,合并成为递归(又叫做深拷贝)。仅支持 true | 空 + * @param {Object} target 对象扩展,这将接收新的属性。 + * @param {Object} object1 一个对象,它包含额外的属性合并到第一个参数。 + * @param {Object} objectN 包含额外的属性合并到第一个参数 + * @returns {Object} + */ + +const deepCopy = (target, name, deep, copy, src) => { + let copyIsArray + if (deep && copy && (isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) { + if (copyIsArray) { + copyIsArray = false + target[name] = copyArray(copy) + } else { + const clone = src && isPlainObject(src) ? src : {} + target[name] = extend(deep, clone, copy) + } + } else if (copy !== undefined) { + try { + target[name] = copy + } catch (e) { + // do nothing + } + } +} + +extend = function () { + const args = arguments + const length = args.length + let target = args[0] || {} + let i = 1 + let deep = false + + if (typeOf(target) === 'boolean') { + deep = target + target = args[i] || {} + i++ + } + + if (!isObject(target) && typeOf(target) !== 'function') { + target = {} + } + + if (i === length) { + target = this + i-- + } + + for (; i < length; i++) { + const options = args[i] + + if (options !== null && isObject(options)) { + const names = Object.keys(options) + + for (const name of names) { + const src = target[name] + const copy = options[name] + + if (target !== copy) { + deepCopy(target, name, deep, copy, src) + } + } + } + } + + return target +} + +let isEachEqual + +/** + * 比较两个对象是否相等。 + * + * isEqual({ a: { b: 1 } }, { a: { b: 1, c: 2 } }, false, [ 'a.b' ]) // false + * isEqual({ a: { b: 1 } }, { a: { b: 1, c: 2 } }, true, [ 'a.b' ]) // true + * + * @param {Object} sourceData 源对象 + * @param {Object} targetData 目标对象 + * @param {Boolean} [deep] 是否深度比较,默认深度比较, 只有指定false才不进行深度比较 + * @param {Array} [fields] 指定需要比较的字段的数组 + * @returns {Boolean} + */ +export const isEqual = (sourceData, targetData, deep, fields) => { + if (typeOf(sourceData) === typeOf(targetData)) { + deep = deep !== false + + if (Array.isArray(fields)) { + const _sourceData = copyField(sourceData, fields) + const _targetData = copyField(targetData, fields) + + return isEqual(_sourceData, _targetData, deep) + } + + const source = isEachEqual(sourceData, targetData, deep) + const target = isEachEqual(targetData, sourceData, deep) + + return source === target && source !== false + } + + return false +} + +/** + * 循环遍历两个对象,判断对象的属性是否完全相等。 + * + * isEachEqual({a: 1}, {a: 1}) // true + * isEachEqual({a: 1, b: {c: 3, d: 4}}, {a: 1, b: {c: 3, d: 5}}) // false + * + * @param {Object} data1 数据源对象 + * @param {Object} data2 对比目标对象 + * @param {Boolean} [deep] 是否深度遍历 + * @returns {Boolean} + */ +isEachEqual = (data1, data2, deep) => { + if (!isPlainObject(data1)) { + if (!Array.isArray(data1)) { + return data1 === data2 + } + if (data1.length !== data2.length) { + return false + } + + for (let i = 0, length = data1.length; i < length; i++) { + const result = isEqual(data1[i], data2[i], deep) + + if (!result) { + return false + } + } + + return true + } + + let bEqual = true + const names = Object.keys(data1) + + for (const name of names) { + if (hasOwn.call(data2, name)) { + const _data1 = data1[name] + const _data2 = data2[name] + + if ((deep && isObject(_data1)) || Array.isArray(_data1)) { + bEqual = isEachEqual(_data1, _data2, deep) + } else { + bEqual = _data1 === _data2 + } + } else { + bEqual = false + } + + if (bEqual === false) { + break + } + } + + return bEqual +} + +export { isEachEqual, extend } + +/** + * 将json对象序列化为字符串。 + + * + * @param {Object} obj + * @returns {String} + */ +export const toJsonStr = (obj) => { + try { + return JSON.stringify(obj) + } catch (e) { + return undefined + } +} + +/** + * 将一个或多个源对象简单合并到目标对象中,合并时排除非 OwnProperty 及 undefined 属性。 + * + * @param {Object} target 目标对象 + * @param {Object} [source] 源对象 + * @returns {Object} + */ +export const merge = function (target) { + for (let i = 1, len = arguments.length; i < len; i++) { + const source = arguments[i] || {} + + for (const prop in source) { + if (hasOwn.call(source, prop)) { + const value = source[prop] + + if (value !== undefined) { + target[prop] = value + } + } + } + } + + return target +} diff --git a/packages/vue/src/charts-beta/chart-core/common/type.ts b/packages/vue/src/charts-beta/chart-core/common/type.ts new file mode 100644 index 000000000..bf354691d --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/common/type.ts @@ -0,0 +1,148 @@ +/** +* Copyright (c) 2022 - present TinyVue Authors. +* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd. +* +* Use of this source code is governed by an MIT-style license. +* +* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, +* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR +* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. +* +*/ + +export const toString = Object.prototype.toString +export const hasOwn = Object.prototype.hasOwnProperty + +const getProto = Object.getPrototypeOf +const fnToString = hasOwn.toString +const ObjectFunctionString = fnToString.call(Object) + +const class2type = { + '[object Error]': 'error', + '[object Object]': 'object', + '[object RegExp]': 'regExp', + '[object Date]': 'date', + '[object Array]': 'array', + '[object Function]': 'function', + '[object String]': 'string', + '[object Number]': 'number', + '[object Boolean]': 'boolean' +} + +export const isNull = (x) => x === null || x === undefined || x === 'undefined' + +/** + * 返回 JavaScript 对象的类型。 + * + * 如果对象是 undefined 或 null,则返回相应的'undefined'或'null'。 + * + * 其他一切都将返回它的类型'object'。 + * + * @param {Object} obj 对象 + * @returns {String} + */ +export const typeOf = (obj) => (isNull(obj) ? String(obj) : class2type[toString.call(obj)] || 'object') + +/** + * 判断对象是否为 object 类型。 + * + * isObject({}) // true + * + * @param {Object} obj 对象 + * @returns {Boolean} + */ +export const isObject = (obj) => typeOf(obj) === 'object' + +/** + * 判断对象是否为 function 类型。 + * + * isObject(function (){) // true + * + * @param {Object} fn 函数 + * @returns {Boolean} + */ +export const isFunction = (fn) => typeOf(fn) === 'function' + +/** + * 判断对象是否为简单对象。 + * + * 即不是 HTML 节点对象,也不是 window 对象,而是纯粹的对象(通过 '{}' 或者 'new Object' 创建的)。 + * + * + * @param {Object} obj 对象 + * @returns {Boolean} + */ +export const isPlainObject = (obj) => { + if (!obj || toString.call(obj) !== '[object Object]') { + return false + } + + const proto = getProto(obj) + if (!proto) { + return true + } + + const Ctor = hasOwn.call(proto, 'constructor') && proto.constructor + return typeof Ctor === 'function' && fnToString.call(Ctor) === ObjectFunctionString +} + +/** + * 检查对象是否为空(不包含任何属性)。 + * + * @param {Object} obj 对象 + * @returns {Boolean} + */ +export const isEmptyObject = (obj) => { + const type = typeOf(obj) + + if (type === 'object' || type === 'array') { + for (const name in obj) { + if (hasOwn.call(obj, name)) { + return false + } + } + } + + return true +} + +/** + * 判断对象是否为数字类型。 + * + * isNumber(369) // true + * + * @param {Object} value 对象 + * @returns {Boolean} + */ +export const isNumber = (value) => typeof value === 'number' && isFinite(value) + +/** + * 判断对象是否代表一个数值。 + * + * @param {Object} value 对象 + * @returns {Boolean} + */ +export const isNumeric = (value) => value - parseFloat(value) >= 0 + +/** + * 判断对象是否为日期类型。 + * + * + * @param {Object} value 对象 + * @returns {Boolean} + */ +export const isDate = (value) => typeOf(value) === 'date' + +/** + * 判断两个值是否值相同且类型相同。 + * + * 注:在 JavaScript 里 NaN === NaN 为 false,因此不能简单的用 === 来判断。 + * + * + * @param {Object} x 对象 + * @param {Object} y 对象 + * @returns {Boolean} + */ +export const isSame = (x, y) => x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y)) + +export const isRegExp = (value) => typeOf(value) === 'regExp' diff --git a/packages/vue/src/charts-beta/chart-core/common/util.ts b/packages/vue/src/charts-beta/chart-core/common/util.ts new file mode 100644 index 000000000..f8cbceeae --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/common/util.ts @@ -0,0 +1,130 @@ +import { typeOf as getType, isNull, isObject } from './type' +import { copyArray } from './object' +export { setObj as set, getObj as get } from './object' + +import _numerify from './numerify' +export const $prefix = 'Tiny' + +export function toUpperCase(str) { + return str.charAt(0).toUpperCase() + str.slice(1) +} + + + +export const getFormatted = (value, type, digit, defaultVal = '-') => { + if (isNaN(value)) { + return defaultVal + } + + if (!type) { + return value + } + + if (getType(type) === 'function') { + return type(value, _numerify) + } + + digit = !isNaN(digit) ? ++digit : 0 + + const digitStr = `.[${new Array(digit).join(0)}]` + let formatter = type + + if (type === 'KMB') { + formatter = digit ? `0,0${digitStr}a` : '0,0a' + } else if (type === 'normal') { + formatter = digit ? `0,0${digitStr}` : '0,0' + } else if (type === 'percent') { + formatter = digit ? `0,0${digitStr}%` : '0,0.[00]%' + } + return _numerify(value, formatter) +} + +export const cloneDeep = (data) => { + if (isObject(data)) { + return extend(true, data) + } else if (Array.isArray(data)) { + return copyArray(data) + } else { + return data + } +} + +export const getStackMap = (stack) => { + const result = {} + + Object.keys(stack).forEach((item) => { + stack[item].forEach((name) => { + result[name] = item + }) + }) + + return result +} + +// 合并对象或数组 +export const merge = (source, other) => { + if (typeof source !== 'object' || typeof other !== 'object') { + return other === undefined ? source : other + } + + return Object.keys({ ...source, ...other }).reduce( + (acc, key) => { + acc[key] = merge(source[key], other[key]) + return acc + }, + Array.isArray(source) ? [] : {} + ) +} + +const mapPromise = {} + +export const getMapJSON = ({ position, positionJsonLink, beforeRegisterMapOnce, mapURLProfix = '' }) => { + const link = positionJsonLink || `${mapURLProfix}${position}.json` + + if (!mapPromise[link]) { + mapPromise[link] = $get(link).then((res) => { + if (beforeRegisterMapOnce) { + res = beforeRegisterMapOnce(res) + } + + return res + }) + } + + return mapPromise[link] +} + +let { amapPromise = null, bmapPromise = null } = {} + +export const getBmap = ({ key, version, url }) => { + if (!bmapPromise) { + bmapPromise = new Promise((resolve) => { + let cbName = 'bmap' + Date.now() + let script = document.createElement('script') + let ver = version || '2.0' + + window[cbName] = resolve + script.src = [`${url}?v=${ver}`, `ak=${key}`, `callback=${cbName}`].join('&') + document.body.appendChild(script) + }) + } + return bmapPromise +} + +export const getAmap = ({ key, version, url }) => { + if (!amapPromise) { + amapPromise = new Promise((resolve) => { + let cbName = 'amap' + Date.now() + let script = document.createElement('script') + let ver = version || '1.4.3' + + window[cbName] = resolve + script.src = [`${url}?v=${ver}`, `key=${key}`, `callback=${cbName}`].join('&') + + document.body.appendChild(script) + }) + } + + return amapPromise +} + diff --git a/packages/vue/src/charts-beta/chart-core/index.ts b/packages/vue/src/charts-beta/chart-core/index.ts new file mode 100644 index 000000000..5798ff144 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/index.ts @@ -0,0 +1,8 @@ +import Core from './src/chart-core' + + +Core.install = function (Vue: any) { + Vue.component(Core.name, Core) +} + +export default Core \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-core/package.json b/packages/vue/src/charts-beta/chart-core/package.json new file mode 100644 index 000000000..9c1cbc4f7 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/package.json @@ -0,0 +1,16 @@ +{ + "name": "@opentiny/vue-chart-core-beta", + "version": "3.7.0", + "description": "", + "main": "lib/index.js", + "module": "index.ts", + "sideEffects": false, + "type": "module", + "devDependencies": {}, + "scripts": { + "build": "pnpm -w build:ui $npm_package_name", + "//postversion": "pnpm build" + }, + "dependencies": {}, + "license": "MIT" +} \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-core/src/chart-core.ts b/packages/vue/src/charts-beta/chart-core/src/chart-core.ts new file mode 100644 index 000000000..9ee0eaac3 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/src/chart-core.ts @@ -0,0 +1,387 @@ +import { $prefix } from '../common/util' +import { isObject } from '../common/type' +import setExtend from '../common/extend' +import IntegrateChart from '../base' + +export default { + name: $prefix + 'ChartCore', + emits: ['ready', 'readyOnce'], + props: { + data: { + type: Object, + default() { + return {} + } + }, + settings: { + type: Object, + default() { + return {} + } + }, + width: { type: String, default: 'auto' }, + height: { type: String, default: '400px' }, + events: { type: Object, default: () => { } }, + initOptions: { + type: Object, + default() { + return {} + } + }, + tooltipVisible: { type: Boolean, default: true }, + legendVisible: { type: Boolean, default: true }, + legendPosition: { type: String }, + theme: Object, + themeName: [Object, String], + judgeWidth: { + type: Boolean, + default: false + }, + widthChangeDelay: { + type: Number, + default: 200 + }, + resizeable: { + type: Boolean, + default: true + }, + changeDelay: { type: Number, default: 0 }, + dataEmpty: Boolean, + + beforeConfig: { + type: Function, + }, + afterConfig: { + type: Function, + }, + afterSetOption: { + type: Function, + }, + afterSetOptionOnce: { + type: Function + }, + + + + loading: { + type: Boolean, + default: false + }, + extend: Object, + tooltipFormatter: { type: Function }, + + markArea: { + type: Object + }, + markLine: { + type: Object + }, + markPoint: { + type: Object + }, + + grid: { type: [Object, Array] }, + colors: { + type: Array, + default() { + return ['#114411'] + } + }, + visualMap: [Object, Array], + dataZoom: [Object, Array], + toolbox: [Object, Array], + title: Object, + legend: [Object, Array], + xAxis: [Object, Array], + yAxis: [Object, Array], + radar: Object, + tooltip: Object, + axisPointer: Object, + brush: [Object, Array], + geo: Object, + timeline: [Object, Array], + graphic: [Object, Array], + series: [Object, Array], + backgroundColor: [Object, String], + textStyle: Object, + animation: Object, + }, + data() { + return { + option: {}, + renderOption: {}, + initOpts: {}, + watchToPropsEchartOptions: [], + selfChart: [ + // 'FlowChart', + // 'WaveChart', + // 'RiverChart', + // 'GanttChart', + // 'HoneycombChart', + // 'OrganizationChart', + // 'AutonaviMapChart', + ], + isSelfChart: false, + chartList: [] + } + }, + computed: { + delay() { + return { + widthChangeDelay: this.widthChangeDelay, + resizeDelay: this.resizeDelay + } + } + }, + watch: { + data: { + handler(val) { + this.refreshChart() + }, + deep: true + }, + events: { + handler(val, oldVal) { + this.addEvents(val) + this.removeEvents(oldVal) + }, + deep: true, + }, + initOptions: { + handler(val) { + this.initOpts = { + ...initOpts, + ...val + } + this.renderChart(this.option) + }, + deep: true, + }, + tooltipVisible: { + handler() { + this.refreshChart() + } + }, + legendVisible: { + handler() { + this.refreshChart() + }, + }, + judgeWidth: { + handler(val) { + this.initOpts.domResize = val + this.renderChart(this.option) + } + }, + delay: { + handler(val) { + this.initOpts.resizeThrottle = val + this.renderChart(this.option) + }, + deep: true + }, + resizeable: { + handler(val) { + this.initOpts.windowResize = val + this.renderChart(this.option) + }, + }, + setOptionOpts: { + handler(val) { + this.renderOption = val + this.renderChart(this.option) + }, + deep: true + }, + loading(val) { + this.$nextTick(() => { + if (val) { + this.integrateChart.showLoading() + } else { + this.integrateChart.closeLoading() + } + }) + }, + dataEmpty(val) { + this.$nextTick(() => { + if (val) { + this.integrateChart.showEmpty() + } else { + this.integrateChart.closeEmpty() + } + }) + }, + }, + methods: { + selfSetting(options) { + const echartsSettings = [ + 'grid', 'dataZoom', 'visualMap', 'toolbox', 'title', 'legend', 'xAxis', + 'yAxis', 'radar', 'tooltip', 'axisPointer', 'brush', 'geo', + 'timeline', 'graphic', 'series', 'backgroundColor', 'textStyle' + ] + echartsSettings.forEach((setting, index) => { + const unwatch = this.watchToPropsEchartOptions[index] + if (this[setting]) { + if (!options.extend) { + options.extend = {} + } + options.extend[setting] = this[setting] + !unwatch && this.$watch(setting, () => { + this.refreshChart() + }, { + deep: true + }) + } else { + unwatch && unwatch() + } + }) + }, + setAnimation(options) { + if (this.animation) { + Object.keys(this.animation).forEach((key) => { + options.extend[key] = this.animation[key] + }) + } + }, + applyMarks(options) { + if (this.markArea || this.markLine || this.markPoint) { + const marks = { + markArea: this.markArea, + markLine: this.markLine, + markPoint: this.markPoint + } + const series = options.series + const setMark = (seriesItem, marks) => { + Object.keys(marks).forEach((key) => { + if (marks[key]) { + seriesItem[key] = marks[key] + } + }) + } + + if (Array.isArray(series)) { + series.forEach((item) => { + setMark(item, marks) + }) + } else if (isObject(series)) { + setMark(series, marks) + } + } + }, + applyExtend(option) { + if (this.extend) { + return setExtend({ + option, + extend: this.extend + }) + } + }, + refreshChart() { + let data = this.data + this.updateChart(data) + let option = this.option + setTimeout(() => { + if (this.afterConfig) { + option = this.afterConfig(option) + } + this.selfSetting(option) + this.setAnimation(option) + this.applyMarks(this.integrateChart.eChartOption) + this.integrateChart.refresh(option) + this.applyExtend(this.integrateChart.eChartOption) + option.extend = this.integrateChart.eChartOption + this.integrateChart.refresh(option) + if (this.afterSetOption) { + this.afterSetOption(this.integrateChart.echartsIns) + } + this.$emit('ready', this.integrateChart.echartsIns) + }, this.changeDelay) + }, + renderChart(option) { + if (!this.isSelfChart) { + this.selfSetting(option) + this.setAnimation(option) + this.integrateChart.init(this.$refs.chartRef, this.initOpts) + this.integrateChart.setSimpleOption(this.iChartName, option) + this.applyMarks(this.integrateChart.eChartOption) + this.applyExtend(this.integrateChart.eChartOption) + this.integrateChart.render(this.renderOption) + } else { + this.integrateChart.init(this.$refs.chartRef); + this.integrateChart.setSimpleOption(this.chartList[this.iChartName], option, {}); + this.integrateChart.render(); + } + }, + addEvents(val) { + if (typeof val === 'object' && val !== null && Object.keys(val).length > 0) { + const events = Object.keys(val) + this.$nextTick(() => { + events.forEach((item) => { + this.integrateChart.on(item, val[item]) + }) + }) + } + }, + removeEvents(oldVal) { + if (typeof oldVal === 'object' && oldVal !== null && Object.keys(oldVal).length > 0) { + const events = Object.keys(oldVal) + this.$nextTick(() => { + events.forEach((item) => { + this.integrateChart.on(item, oldVal[item]) + }) + }) + } + } + }, + created() { + this.option = {} + if (this.selfChart.indexOf(this.iChartName) === -1) { + this.isSelfChart = false + this.integrateChart = new IntegrateChart() + } else { + this.isSelfChart = true + this.chartList = {} + this.integrateChart = new this.chartList[this.iChartName]() + } + }, + mounted() { + this.$nextTick(() => { + this.addEvents(this.events) + if (this.loading) { + this.integrateChart.showLoading() + } + if (this.dataEmpty) { + this.integrateChart.showEmpty() + } + }) + this.initOpts = { + ...this.initOptions, + domResize: this.judgeWidth, + resizeThrottle: this.widthChangeDelay, + + } + let data = this.data + if (this.beforeConfig) { + data = this.beforeConfig(data) + } + this.updateChart(data) + let option = this.option + if (this.afterConfig) { + option = this.afterConfig(option) + this.option = option + } + this.renderChart(option) + if (this.afterSetOption) { + this.afterSetOption(this.integrateChart.echartsIns) + } + if (this.afterSetOptionOnce) { + this.afterSetOptionOnce(this.integrateChart.echartsIns) + } + this.$emit('ready', this.integrateChart.echartsIns) + }, + beforeUnmount() { + this.watchToPropsEchartOptions.forEach(unwatch => { + unwatch && unwatch() + }) + } +} \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-core/utils/deep-clone.ts b/packages/vue/src/charts-beta/chart-core/utils/deep-clone.ts new file mode 100644 index 000000000..a7ef7a09c --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/utils/deep-clone.ts @@ -0,0 +1,26 @@ +import { typeOf } from './type' + +// deepCopy +export function deepCopy(val) { + let o + const t = typeOf(val) + + if (t === 'array') { + o = [] + } else if (t === 'object') { + o = {} + } else { + return val + } + + if (t === 'array') { + for (let i = 0; i < val.length; i++) { + o.push(deepCopy(val[i])) + } + } else if (t === 'object') { + Object.keys(val).forEach((i) => { + o[i] = deepCopy(val[i]) + }) + } + return o +} \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-core/utils/options.ts b/packages/vue/src/charts-beta/chart-core/utils/options.ts new file mode 100644 index 000000000..21e703da8 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/utils/options.ts @@ -0,0 +1,101 @@ +import { getFormatted } from '../common/util' +import { itemPoint, itemLabel, itemContent, SAAS_DEFAULT_COLORS } from '../common/constants' +export const getRows = (args) => { + const { columns, metrics, labelMap, rows, dimension = columns[0] } = args + + let rowData = [] + + const data = labelMap ? metrics.map((item) => (!(labelMap[item]) ? item : labelMap[item])) : metrics + + + dimension.forEach((item) => { + data.unshift(item) + }) + + + rows.forEach(item => { + + let obj = {}; + for (var i = 0; i < data.length; i++) { + obj[data[i]] = item[data[i]]; + } + rowData.push(obj) + + }) + + + return rowData +} + +export const getYAxis = (args) => { + const { yAxisType, yAxisName, scale = [false, false], axisVisible, max, min, metrics, axisSite, digit = 2 } = args + let yAxis = [] + const yAxisBase = { + scale + } + for (let k = 0; k < 2; k++) { + yAxis[k] = { + ...yAxisBase, + } + if (yAxisType[k]) { + yAxis[k] = { + ...yAxisBase, + axisLabel: { + formatter(val) { + return getFormatted(val, yAxisType[k], digit) + } + }, + } + } else { + yAxis[k] = { ...yAxisBase } + } + yAxis[k].name = yAxisName[k] + yAxis[k].min = min[k] || null + yAxis[k].max = max[k] || null + } + + metrics.forEach((item) => { + if (axisSite.right && axisSite.right.indexOf(item) !== -1) { + yAxis[1].dataName = [...axisSite.right] + } else { + yAxis[0].dataName = item + } + }) + + + return yAxis +} + +export const getTooltip = (args) => { + const { axisSite, yAxisType, labelMap, digit = 2 } = args + const rightItemsArr = axisSite?.right || [] + + const rightListArr = labelMap + ? rightItemsArr.map((item) => (labelMap[item] === undefined ? item : labelMap[item])) + : rightItemsArr + + let formatter = function (items) { + + let template = [] + const { name, axisValueLabel } = items[0] + const title = name || axisValueLabel + + template.push(`${title}
`) + + items.forEach(({ seriesName, data, color }) => { + if (color === 'transparent' && items.length === 1) { + color = '#6d8ff0' + } + let showData = null + const type = ~rightListArr.indexOf(seriesName) ? yAxisType[1] : yAxisType[0] + const itemData = Array.isArray(data) ? data[1] : data + showData = getFormatted(itemData, type, digit) + template.push(itemPoint(color)) + template.push(`${itemLabel(seriesName)}${itemContent(showData)}`) + template.push('
') + }) + return template.join('') + } + + return { trigger: 'axis', formatter } +} \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-core/utils/throttle.ts b/packages/vue/src/charts-beta/chart-core/utils/throttle.ts new file mode 100644 index 000000000..fc46dd591 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/utils/throttle.ts @@ -0,0 +1,53 @@ +export function throttle(delay, callback, options) { + const { noTrailing = false, noLeading = false, debounceMode = undefined } = options || {} + let timeoutID + let cancelled = false + let lastExec = 0 + function clearExistingTimeout() { + if (timeoutID) { + clearTimeout(timeoutID) + } + } + function cancel(options) { + const { upcomingOnly = false } = options || {} + clearExistingTimeout() + cancelled = !upcomingOnly + } + function wrapper(...arguments_) { + let self = this + let elapsed = Date.now() - lastExec + if (cancelled) { + return + } + function exec() { + lastExec = Date.now() + callback.apply(self, arguments_) + } + function clear() { + timeoutID = undefined + } + if (!noLeading && debounceMode && !timeoutID) { + exec() + } + clearExistingTimeout() + if (debounceMode === undefined && elapsed > delay) { + if (noLeading) { + lastExec = Date.now() + if (!noTrailing) { + timeoutID = setTimeout(debounceMode ? clear : exec, delay) + } + } else { + exec() + } + } else if (noTrailing !== true) { + timeoutID = setTimeout(debounceMode ? clear : exec, debounceMode === undefined ? delay - elapsed : delay) + } + } + wrapper.cancel = cancel + return wrapper +} + +export function debounce(delay, callback, options) { + const { atBegin = false } = options || {} + return throttle(delay, callback, { debounceMode: atBegin !== false }) +} \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-core/utils/type.ts b/packages/vue/src/charts-beta/chart-core/utils/type.ts new file mode 100644 index 000000000..fb976bfee --- /dev/null +++ b/packages/vue/src/charts-beta/chart-core/utils/type.ts @@ -0,0 +1,42 @@ +// 判断参数是否是其中之一 +export function oneOf(value, validList) { + for (let i = 0; i < validList.length; i++) { + if (value === validList[i]) { + return true + } + } + return false +} + +export function typeOf(obj) { + const map = { + '[object Object]': 'object', + '[object Null]': 'null', + '[object Date]': 'date', + '[object Undefined]': 'undefined', + '[object RegExp]': 'regExp', + '[object Array]': 'array', + '[object Function]': 'function', + '[object String]': 'string', + '[object Number]': 'number', + '[object Boolean]': 'boolean', + '[object BigInt]': 'bigint' + } + const toString = Object.prototype.toString + return map[toString.call(obj)] +} + +/** + * 判断任意类型的值是否为undefined或者null + */ +export function isNotUndefined(value) { + return typeof value !== 'undefined' && value !== null +} + +export function isUndefined(value) { + return typeof value === 'undefined' || value === null +} + +export const isDefined = (val) => { + return val !== undefined && val !== null +} \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-histogram/index.ts b/packages/vue/src/charts-beta/chart-histogram/index.ts new file mode 100644 index 000000000..d4688af2a --- /dev/null +++ b/packages/vue/src/charts-beta/chart-histogram/index.ts @@ -0,0 +1,9 @@ +import ChartHistogram from './src/chart-histogram.vue' + +ChartHistogram.install = function (Vue: any) { + Vue.component(ChartHistogram.name, ChartHistogram) +} + + +export { ChartHistogram } +export default ChartHistogram \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-histogram/package.json b/packages/vue/src/charts-beta/chart-histogram/package.json new file mode 100644 index 000000000..151f0d8b0 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-histogram/package.json @@ -0,0 +1,18 @@ +{ + "name": "@opentiny/vue-chart-histogram-beta", + "version": "3.7.0", + "description": "", + "main": "lib/index.js", + "module": "index.ts", + "sideEffects": false, + "type": "module", + "devDependencies": {}, + "scripts": { + "build": "pnpm -w build:ui $npm_package_name", + "//postversion": "pnpm build" + }, + "dependencies": { + "@opentiny/vue-chart-core-beta": "workspace:~" + }, + "license": "MIT" +} \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-histogram/src/chart-histogram.vue b/packages/vue/src/charts-beta/chart-histogram/src/chart-histogram.vue new file mode 100644 index 000000000..5dd2f8540 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-histogram/src/chart-histogram.vue @@ -0,0 +1,37 @@ + + \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-histogram/src/histogram.ts b/packages/vue/src/charts-beta/chart-histogram/src/histogram.ts new file mode 100644 index 000000000..35825610c --- /dev/null +++ b/packages/vue/src/charts-beta/chart-histogram/src/histogram.ts @@ -0,0 +1,266 @@ +import { getFormatted, cloneDeep, getStackMap, get, set } from '@opentiny/vue-chart-core-beta/common/util' +import { isNull } from '@opentiny/vue-chart-core-beta/common/type' + +import { getRows, getTooltip } from '@opentiny/vue-chart-core-beta/utils/options' + +const VALUE_AXIS_OPACITY = 0.5 + +const getBarDimAxis = (args) => { + const { innerRows, dimAxisName, dimension, axisVisible, dimAxisType, dims } = args + return dimension.map((item) => ({ + type: 'category', + name: dimAxisName, + nameLocation: 'middle', + nameGap: 22, + data: dimAxisType === 'value' ? getValueAxisData(dims) : innerRows.map((row) => row[item]), + axisLabel: { + formatter(value) { + return String(value) + } + }, + show: axisVisible + })) +} + +const getBarMeaAxis = (args) => { + const { axisVisible, digit, max, meaAxisName = [], meaAxisType, min, scale } = args + const meaAxisBase = { type: 'value', axisTick: { show: false }, show: axisVisible } + let { meaAxis = [], i = 0, formatter } = {} + + for (; i < 2; i++) { + if (meaAxisType[i]) { + formatter = factoryFmt({ meaAxisType, i, digit }) + + meaAxis[i] = { ...meaAxisBase, axisLabel: { formatter } } + } else { + meaAxis[i] = { ...meaAxisBase } + } + + Object.assign(meaAxis[i], { + max: max[i] || null, + min: min[i] || null, + name: meaAxisName[i] || '', + scale: scale[i] || false + }) + } + + return meaAxis +} + +const factoryFmt = + ({ meaAxisType, i, digit }) => + (val) => + getFormatted(val, meaAxisType[i], digit) + +const getLegend = (args) => { + const { legendName } = args + let legendBase = { + show: true + } + let formatter = function (name) { + return !legendName[name] ? name : legendName[name] + } + return { + ...legendBase, + formatter + } +} + +const getDims = (rows, dimension) => rows.map((row) => row[dimension[0]]) + +const getValueAxisData = (dims) => { + const max = Math.max(...dims) + const min = Math.min(...dims) + let { result = [], i = min } = {} + + for (; i <= max; i++) { + result.push(i) + } + + return result +} + +const getValueData = (seriesTemp, dims) => { + const max = Math.max(...dims) + const min = Math.min(...dims) + let { result = [], i = min, index } = {} + + for (; i <= max; i++) { + index = dims.indexOf(i) + + result.push(~index ? seriesTemp[index] : null) + } + + return result +} + +const getBarSeries = (args) => { + const { axisSite, barGap, dimAxisType, dims, innerRows, isHistogram, itemStyle } = args + const { label, labelMap, metrics, opacity, showLine = [], stack } = args + let { secondAxis, secondDimAxisIndex, series = [], seriesTemp = {}, stackMap, stackNum = 0 } = {} + secondAxis = (isHistogram ? axisSite?.right : axisSite?.top) || [] + secondDimAxisIndex = isHistogram ? 'yAxisIndex' : 'xAxisIndex' + stackMap = stack && getStackMap(stack) + metrics.forEach((item) => (seriesTemp[item] = [])) + innerRows.forEach((row) => metrics.forEach((item) => seriesTemp[item].push(row[item]))) + + series = Object.keys(seriesTemp).map((item) => { + let name = !isNull(labelMap[item]) ? labelMap[item] : item + let type = ~showLine.indexOf(item) ? 'line' : 'bar' + let axisIndex = ~secondAxis.indexOf(item) ? '1' : '0' + + let seriesItem = { name, type, [secondDimAxisIndex]: axisIndex } + + const defaultItemStyle = {} + + stack && stackMap[item] && (seriesItem.stack = stackMap[item]) + + if (Object.keys(stack).length) { + // 堆叠图 + if (stackNum === Object.keys(stackMap).length - 1 || isNull(seriesItem.stack)) { + seriesItem.itemStyle = Object.assign(defaultItemStyle, seriesItem.itemStyle) + } + if (!isNull(seriesItem.stack)) { + stackNum++ + } + + seriesItem.itemStyle = { borderWidth: 2, borderColor: 'transparent', ...seriesItem.itemStyle } + } else { + // 非堆叠图 + } + + itemStyle && (seriesItem.itemStyle = itemStyle) + + let itemOpacity = opacity || get(seriesItem, 'itemStyle.opacity') + + dimAxisType === 'value' && Object.assign(seriesItem, { barGap, barCategoryGap: '1%' }) + dimAxisType === 'value' && isNull(itemOpacity) && (itemOpacity = VALUE_AXIS_OPACITY) + + !isNull(itemOpacity) && set(seriesItem, 'itemStyle.opacity', itemOpacity) + + return seriesItem + }) + + return series.length ? series : false +} + +const getDataValue = (data, dimension, metrics, innerRows, dims) => { + let dimensionData = dimension[0] + let dataTemp = {} + data.forEach((item, index) => { + dataTemp[item[dimensionData]] = item + }) + let dataItemTemp = {} + metrics.forEach((item, index) => { + dataItemTemp[item] = null + }) + + const max = Math.max(...dims) + const min = Math.min(...dims) + let { result = [], i = min, index } = {} + + for (; i <= max; i++) { + index = dims.indexOf(i) + result.push(~index ? dataTemp[i] : { [dimensionData]: i, ...dataItemTemp }) + } + + return result +} + +export const histogram = (columns, rows, settings, extra, isHistogram = true) => { + const innerRows = cloneDeep(rows) + const { axisSite = {}, dimension = [columns[0]], axisLabel = {}, axisVisible = true } = settings + + const { + digit = 2, + dataOrder = false, + scale = [false, false], + min = [null, null], + max = [null, null], + stack = {} + } = settings + + const { tooltipVisible, legendVisible } = extra + + const { labelMap = {}, legendName = {}, label, itemStyle = {}, showLine, barGap = '-100%', opacity } = settings + + let { metrics = columns.slice(), meaAxisType, dimAxisType, meaAxisName, dimAxisName = true, dims } = {} + + if (dataOrder) { + let { label, order } = dataOrder + if (label && order) { + innerRows.sort((a, b) => (order === 'desc' ? a[label] - b[label] : b[label] - a[label])) + } + } + let xAxis = {} + let yAxis = {} + dims = getDims(innerRows, dimension) + if (isHistogram) { + if (axisSite.right && axisSite.left) { + metrics = axisSite.left.concat(axisSite.right) + } else if (settings.metrics) { + metrics = settings.metrics + } else if (axisSite.left && !axisSite.right) { + metrics = axisSite.left + } else { + metrics.splice(columns.indexOf(dimension[0]), 1) + } + dimAxisType = settings.xAxisType || 'category' + meaAxisType = settings.yAxisType || ['normal', 'normal'] + dimAxisName = settings.xAxisName || '' + meaAxisName = settings.yAxisName || [] + xAxis = getBarDimAxis({ innerRows, dimAxisName, dimension, axisVisible, dimAxisType, dims }) + + yAxis = getBarMeaAxis({ meaAxisName, meaAxisType, axisVisible, digit, scale, min, max }) + } else { + if (axisSite.bottom && axisSite.top) { + metrics = axisSite.top.concat(axisSite.bottom) + } else if (!axisSite.right && axisSite.bottom) { + metrics = axisSite.bottom + } else if (settings.metrics) { + metrics = settings.metrics + } else { + metrics.splice(columns.indexOf(dimension[0]), 1) + } + dimAxisType = settings.yAxisType || 'category' + meaAxisType = settings.xAxisType || ['normal', 'normal'] + dimAxisName = settings.yAxisName || '' + xAxis = getBarDimAxis({ innerRows, dimAxisName, dimension, axisVisible, dimAxisType, dims }) + yAxis = getBarMeaAxis({ axisVisible, meaAxisType, meaAxisName, scale, digit, max, min }) + } + + if (opacity) { + const itemStyleBase = { + opacity + } + Object.assign(itemStyle, itemStyleBase) + } + let data = getRows({ columns, metrics, labelMap, rows: innerRows, dimension }) + if (dimAxisType === 'value') { + data = getDataValue(data, dimension, metrics, innerRows, dims) + } + + const lineDataName = showLine ? [...showLine] : [] + const legend = legendVisible ? getLegend({ legendName, metrics, labelMap }) : { show: false } + const tooltip = tooltipVisible ? getTooltip({ axisSite, yAxisType: meaAxisType }) : { show: false } + const tipHtml = tooltip.formatter + let args = { innerRows, metrics, stack, axisSite, isHistogram, labelMap, itemStyle, label } + Object.assign(args, { showLine, dimAxisType, dimension, barGap, opacity, dims }) + let options = { + data, + itemStyle, + tipHtml, + lineDataName, + legend, + tooltip, + yAxis, + xAxis, + label, + stack, + series: getBarSeries(args) + } + if (typeof options.stack === 'object' && options.stack !== null && Object.keys(options.stack).length > 0) { + options.type = 'stack' + } + return options +} diff --git a/packages/vue/src/charts-beta/chart-line/index.ts b/packages/vue/src/charts-beta/chart-line/index.ts new file mode 100644 index 000000000..d9ba7aba8 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-line/index.ts @@ -0,0 +1,9 @@ +import ChartLine from './src/chart-line.vue' + + +ChartLine.install = function (Vue:any) { + Vue.component(ChartLine.name, ChartLine) +} + +export {ChartLine} +export default ChartLine diff --git a/packages/vue/src/charts-beta/chart-line/package.json b/packages/vue/src/charts-beta/chart-line/package.json new file mode 100644 index 000000000..716e1cc7c --- /dev/null +++ b/packages/vue/src/charts-beta/chart-line/package.json @@ -0,0 +1,18 @@ +{ + "name": "@opentiny/vue-chart-line-beta", + "version": "3.7.0", + "description": "", + "main": "lib/index.js", + "module": "index.ts", + "sideEffects": false, + "type": "module", + "devDependencies": {}, + "scripts": { + "build": "pnpm -w build:ui $npm_package_name", + "//postversion": "pnpm build" + }, + "dependencies": { + "@opentiny/vue-chart-core-beta": "workspace:~" + }, + "license": "MIT" +} \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-line/src/chart-line.vue b/packages/vue/src/charts-beta/chart-line/src/chart-line.vue new file mode 100644 index 000000000..00451c727 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-line/src/chart-line.vue @@ -0,0 +1,34 @@ + + \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-line/src/line.ts b/packages/vue/src/charts-beta/chart-line/src/line.ts new file mode 100644 index 000000000..dc989463f --- /dev/null +++ b/packages/vue/src/charts-beta/chart-line/src/line.ts @@ -0,0 +1,144 @@ +import { getRows, getYAxis, getTooltip } from '@opentiny/vue-chart-core-beta/utils/options' +import { isNull } from '@opentiny/vue-chart-core-beta/common/type' + +const getStackMap = (stack) => { + const result = {} + + Object.keys(stack).forEach((item) => { + stack[item].forEach((name) => { + result[name] = item + }) + }) + + return result +} + +const getLineXAxis = (args) => { + const { axisVisible, axisLabel, xAxisType } = args + + return { + show: axisVisible, + axisLabel, + type: xAxisType + } +} + +const getLegend = (args) => { + const { legendName } = args + let legendBase = { + show: true, + orient: 'horizontal', + position: { left: 'center', bottom: 15 } + } + let formatter = function (name) { + return !legendName[name] ? name : legendName[name] + } + return { + show: true, + formatter + } +} +export const line = (columns, rows, settings, extra) => { + rows = Array.isArray(rows) ? rows : [] + columns = Array.isArray(columns) ? columns : [] + const { + axisSite = {}, + yAxisType = ['normal', 'normal'], + xAxisType = 'category', + yAxisName = [], + axisLabel = {} + } = settings + + const { dimension = [columns[0]], xAxisName = [], axisVisible = true, area = false, stack } = settings + + const { min = [null, null], max = [null, null], nullAddZero = false, digit = 2, scale = false } = settings + + const { legendName = {}, labelMap = {}, label, itemStyle, lineStyle, areaStyle, smooth } = settings + + const { tooltipVisible, legendVisible, extend } = extra + + let metrics = columns.slice() + + if (axisSite.right && axisSite.left) { + metrics = axisSite.left.concat(axisSite.right) + } else if (settings.metrics) { + metrics = settings.metrics + } else if (axisSite.left && !axisSite.right) { + metrics = axisSite.left + } else { + metrics.splice(columns.indexOf(dimension[0]), 1) + } + + const legend = legendVisible ? getLegend({ legendName, metrics, labelMap }) : { show: false } + + const tooltip = tooltipVisible ? getTooltip({ axisSite, yAxisType, digit }) : { show: false } + + const yAxis = getYAxis({ max, min, yAxisType, yAxisName, metrics, axisSite, digit, scale }) + + const xAxis = getLineXAxis({ axisVisible, axisLabel, xAxisType }) + + const seriesParam = { areaStyle, area, axisSite, dimension, itemStyle, lineStyle } + + Object.assign(seriesParam, { label, labelMap, metrics, nullAddZero, rows, xAxisType, stack, smooth, extend }) + const series = getLineSeries(seriesParam) + + const data = getRows({ columns, metrics, labelMap, rows, dimension }) + let options = { + data, + xAxis, + yAxis, + metrics, + series, + legend, + tooltip, + stack, + tipHtml: tooltip.formatter, + itemStyle, + axisSite, + area + } + return { ...options } +} + +const getLineSeries = (args) => { + const { areaStyle, axisSite, area, dimension, itemStyle, label } = args + const { labelMap, lineStyle, metrics, nullAddZero, rows, stack, color, smooth } = args + + const dataTempObj = {} + const stackMapData = stack && getStackMap(stack) + let series = [] + + metrics.forEach((item) => (dataTempObj[item] = [])) + + rows.forEach((row) => { + metrics.forEach((item) => { + let value = null + + if (!isNull(row[item])) { + value = row[item] + } else if (nullAddZero) { + value = 0 + } + + dataTempObj[item].push([row[dimension[0]], value]) + }) + }) + + metrics.forEach((item, i) => { + let name = !isNull(labelMap[item]) ? labelMap[item] : item + const isSmooth = !(smooth === false) + let seriesItem = { smooth: isSmooth, name, type: 'line', data: dataTempObj[item] } + const emphasis = { itemStyle: { borderColor: '#fff', borderWidth: 2 } } + let defaultSeriesItem = { symbol: 'circle', symbolSize: 8, showSymbol: false, emphasis } + metrics.length === 1 && !isSmooth && setSingLineSeries({ defaultSeriesItem, seriesItem }) + seriesItem = Object.assign(defaultSeriesItem, seriesItem) + axisSite.right && (seriesItem.yAxisIndex = ~axisSite.right.indexOf(item) ? 1 : 0) + stack && stackMapData[item] && (seriesItem.stack = stackMapData[item]) + label && (seriesItem.label = label) + itemStyle && (seriesItem.itemStyle = itemStyle) + lineStyle && (seriesItem.lineStyle = lineStyle) + areaStyle && (seriesItem.areaStyle = areaStyle) + series.push(seriesItem) + }) + return series +} diff --git a/packages/vue/src/charts-beta/chart-line/src/types.ts b/packages/vue/src/charts-beta/chart-line/src/types.ts new file mode 100644 index 000000000..7fdbc8128 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-line/src/types.ts @@ -0,0 +1,30 @@ +/** + * + * @title Props + */ + export type TinyLineProps = { + + /** + * @zh 数据由指标和维度组成,“维度” 指的是数据的属性,“指标” 是量化衡量标准 + * + */ + dimension?: object; + + /** + * @zh 配置项,各图表 Settings 属性配置请查阅各图表详情页面 + * + */ + settings?: Object; + + /** + * @zh 设置图表容器的宽度。 + * + */ + width?: string; + + /** + * @zh 设置图表容器的高度 + * + */ + height?: string; +}; diff --git a/packages/vue/src/charts-beta/chart-pie/index.ts b/packages/vue/src/charts-beta/chart-pie/index.ts new file mode 100644 index 000000000..429c3c405 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-pie/index.ts @@ -0,0 +1,11 @@ +import ChartPie from './src/chart-pie.vue' + + +ChartPie.install = function (Vue:any) { + Vue.component(ChartPie.name, ChartPie) +} + + + +export {ChartPie} +export default ChartPie diff --git a/packages/vue/src/charts-beta/chart-pie/package.json b/packages/vue/src/charts-beta/chart-pie/package.json new file mode 100644 index 000000000..34a0e69e8 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-pie/package.json @@ -0,0 +1,18 @@ +{ + "name": "@opentiny/vue-chart-pie-beta", + "version": "3.7.0", + "description": "", + "main": "lib/index.js", + "module": "index.ts", + "sideEffects": false, + "type": "module", + "devDependencies": {}, + "scripts": { + "build": "pnpm -w build:ui $npm_package_name", + "//postversion": "pnpm build" + }, + "dependencies": { + "@opentiny/vue-chart-core-beta": "workspace:~" + }, + "license": "MIT" +} \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-pie/src/chart-pie.vue b/packages/vue/src/charts-beta/chart-pie/src/chart-pie.vue new file mode 100644 index 000000000..bcff9ac5f --- /dev/null +++ b/packages/vue/src/charts-beta/chart-pie/src/chart-pie.vue @@ -0,0 +1,31 @@ + + \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-pie/src/pie.ts b/packages/vue/src/charts-beta/chart-pie/src/pie.ts new file mode 100644 index 000000000..b5e4617f9 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-pie/src/pie.ts @@ -0,0 +1,306 @@ +/** settings: + * dimension string 维度,默认 columns 第一项为维度 + * metrics string 指标,默认 columns 第二项为指标 + * dataType string 数据类型,可选值: KMB, normal, percent + * legendLimit number legend 显示数量限制,legend 数量过多会导致饼图样式错误,限制 legend 最大值并且当超过此值时,隐藏 legend 可以解决这个问题 + * selectedMode string 选中模式,可选值:single, multiple,默认为 false + * hoverAnimation hover 是否开启,在扇区上的放大动画效果 boolean 默认值为 true // aui有tiny没有 + * radius number / string 饼图半径,支持数值和百分比 + * offsetY number / string 纵向偏移量,支持数值和百分比 + * digit number 设置数据类型为 percent 时保留的位数,默认为 2 + * roseType string 显示为南丁格尔玫瑰图,默认不展示为南丁格尔玫瑰图,可设置为'radius', 'area' + * label object 饼图图形上的文本标签,内容参考文档 + * labelLine object 标签的视觉引导线样式,内容参考文档 + * itemStyle object 图形样式,内容参考文档 + * level array 多圆饼图时设置,level 的值接受二维数组,例如:[['a', 'b'], ['c', 'd']], 表示的含义是内层展示的是维度中的'a', 'b'的指标加在一起组成的饼图,外层为'c', 'd'的指标加在一起组成的环图 + * limitShowNum number 设置超过此数字时使用‘其他’代替,此时数据会按照由大到小顺序显示 + * + * emphasis 高亮的扇区和标签样式。 + * percentShow label引导线边的文本显示百分比数据 + */ + +/** extra: + * legendVisible boolean 是否显示图例,true + * tooltipVisible boolean 是否显示提示框,true + */ + +import { deepCopy } from '@opentiny/vue-chart-core-beta/utils/deep-clone' +import { getFormatted } from '@opentiny/vue-chart-core-beta/common/util' +import { itemPoint, itemLabel, itemContent } from '@opentiny/vue-chart-core-beta/common/constants' +import { isDefined } from '@opentiny/vue-chart-core-beta/utils/type' + +const PIE_RADIUS = 100 +const RING_RADIUS = [90, 100] +const ROSE_RING_RADIUS = [20, 100] +const PIE_OFFSET = '50%' + +const getTooltip = (args) => { + const { dataType, digit, dimension, innerRows, limitShowNum, metrics } = args + let { localeOther = '其他', remainArr, sum = 0 } = {} + let mapHandler = (row) => { + sum += row[metrics] + return { name: row[dimension], value: row[metrics] } + } + + remainArr = innerRows.map(mapHandler).slice(limitShowNum, innerRows.length) + + let formatter = function (item) { + let tplt = [itemPoint(item.color)] + let percent + + if (!limitShowNum || item.name !== localeOther) { + tplt.push(itemLabel(item.name)) + tplt.push(itemContent(getFormatted(item.value, dataType, digit))) + tplt.push(itemContent(`(${item.percent}%)`)) + } else { + tplt.push(localeOther + ':') + + remainArr.forEach(({ name, value }) => { + percent = '(' + getFormatted(value / sum, 'percent') + ')' + tplt.push(`
${itemLabel(name)}`) + tplt.push(itemContent(getFormatted(value, dataType, digit))) + tplt.push(itemContent(percent)) + }) + } + + return tplt.join('') + } + + return formatter +} + +const getLabel = (args) => { + const { label, labelLine, percentShow, dataType, digit } = args + let labelObj = {} + + if (label) { + if (isDefined(label.show)) { + labelObj.show = label.show + } + if (isDefined(label.formatter)) { + labelObj.labelHtml = label.formatter + } + } + if (percentShow) { + labelObj.labelHtml = (params) => { + let tplt = [] + tplt.push(`${params.name}:`) + tplt.push(getFormatted(params.value, dataType, digit)) + tplt.push(`(${params.percent}%)`) + return tplt.join('') + } + } + + if (labelLine) { + if (isDefined(labelLine.show)) { + labelObj.line = labelLine.show + } + let color + if (labelLine && labelLine.lineStyle) { + color = labelLine.lineStyle.color + } + if (isDefined(color)) { + labelObj.lineColor = color + } + if (isDefined(labelLine.length)) { + labelObj.distance = labelLine.length + } + } + return labelObj +} + +const getPosition = (args) => { + const { radius, offsetY, level } = args + const position = {} + if (isDefined(offsetY)) { + position.center = [PIE_OFFSET, offsetY] + } + if (Array.isArray(radius)) { + position.radius = radius + } else if (!level || level.length === 0) { + position.radius = [0, radius] + } + return position +} + +// 数据先转成{name,value}格式 +const getInnerData = (args) => { + const { dimension, metrics, innerRows } = args + return innerRows.map((row) => { + return { + name: row[dimension], + value: row[metrics] + } + }) +} + +const getDataOrSeries = (args) => { + const { innerData, isRing, radius, level, limitShowNum } = args + let series + + const getLimitData = (data) => { + let tempData = data + if (limitShowNum && limitShowNum < tempData.length) { + // 多出数据合并为其他 + const remainArr = tempData.slice(limitShowNum, innerData.length) + const sum = remainArr.reduce((a, c) => a + c.value, 0) + tempData.splice(limitShowNum, Infinity, { name: '其他', value: sum }) + } + return tempData + } + + const levelFlag = level && level.length + if (levelFlag) { + // level是个二维数组 + const levelObj = {} + let maxLevel = 0 + let levelData = [] + level.forEach((levelItems, index) => { + levelItems.forEach((item) => { + if (levelObj[item] === undefined) { + levelObj[item] = [index] + } else { + levelObj[item].push(index) + } + if (maxLevel < index + 1) { + maxLevel = index + 1 + } + }) + }) + levelData = Array.from({ length: maxLevel }, () => []) + innerData.forEach((data) => { + Array.isArray(levelObj[data.name]) && + levelObj[data.name].forEach((levelIdx) => { + levelData[levelIdx].push({ ...data }) + }) + }) + + let rowsCount = levelData.length + let centerWidth = radius / rowsCount + series = levelData.map((data, index) => { + let itemRadius + if (index === 0) { + itemRadius = isRing ? radius : centerWidth + } else { + let outerWidth = centerWidth + (radius / (2 * rowsCount)) * (2 * index - 1) + let innerWidth = outerWidth + radius / (2 * rowsCount) + itemRadius = [outerWidth, innerWidth] + } + return { + name: `PIE-${index}`, + type: 'pie', + radius: itemRadius, + data: getLimitData(data) + } + }) + } + + return levelFlag ? { series } : { data: getLimitData(innerData) } +} + +const getLegend = (args) => { + const { dimension, innerRows, legendVisible, legendLimit, level, limitShowNum, innerData } = args + if (!legendVisible) { + return { + show: false + } + } + let { legend = [], levelTemp = [] } = {} + if (level) { + level.forEach((levelItem) => levelItem.forEach((item) => levelTemp.push(item))) + legend = levelTemp + } else if (limitShowNum && limitShowNum < innerRows.length) { + for (let i = limitShowNum - 1; i >= 0; i--) { + legend.unshift(innerRows[i][dimension]) + } + } else { + legend = innerRows.map((row) => row[dimension]) + } + let show = false + if (legend.length) { + show = legend.length < legendLimit + } + + if (level && level.length) { + return { + show, + data: innerData + } + } + + return { + show + } +} + +// ichart配置项 +export const pie = (columns, rows, settings, extra, isRing) => { + const innerRows = deepCopy(rows) + + const { dataType = 'normal', digit = 2, dimension = columns[0], emphasis } = settings + + const { itemStyle, label = false, labelLine, legendLimit = 30, legendName = {}, level = false } = settings + + const { limitShowNum = 0, metrics = columns[1], offsetY = PIE_OFFSET, percentShow } = settings + + const { roseType = false, selectedMode = false, hoverAnimation } = settings + + const { radius = isRing ? (roseType ? ROSE_RING_RADIUS : RING_RADIUS) : PIE_RADIUS } = settings + + const { legendVisible, tooltipVisible } = extra + + // 设置 limitShowNum 时,数据会按照由大到小顺序显示 + limitShowNum && innerRows.sort((a, b) => b[metrics] - a[metrics]) + + // 数据先转成{name,value}格式 + const innerData = getInnerData({ dimension, metrics, innerRows }) + + // 圆盘图类型 + const type = isRing ? 'circle' : 'pie' + + // 图表位置及大小 + const position = getPosition({ radius, offsetY, level }) + + // 外侧文本配置 + const ichartLabel = getLabel({ label, labelLine, percentShow, dataType, digit }) + + // 图例配置 + const ichartLegend = getLegend({ legendVisible, dimension, innerRows, legendLimit, level, limitShowNum }) + + // 图表数据(必填) + const dataOrSeries = getDataOrSeries({ innerData, isRing, radius, level, limitShowNum }) + + // ichart 配置项 + let ichartOption = { + legend: ichartLegend, + type, + position, + label: ichartLabel, + itemStyle, + emphasis: emphasis || { scale: hoverAnimation === undefined ? true : hoverAnimation }, + roseType, + selectedMode, + ...dataOrSeries + } + + // 悬浮提示框内容配置 + if (tooltipVisible) { + const tipHtml = getTooltip({ dataType, innerRows, limitShowNum, digit, metrics, dimension }) + ichartOption.tipHtml = tipHtml + } else { + ichartOption.tooltip = { + show: false + } + } + + let ichartExtend = {} + if (label) { + ichartExtend.label = label + } + if (labelLine) { + ichartExtend.labelLine = labelLine + } + ichartOption.extend = { ...ichartExtend } + + return ichartOption +} diff --git a/packages/vue/src/charts-beta/chart-pie/src/types.ts b/packages/vue/src/charts-beta/chart-pie/src/types.ts new file mode 100644 index 000000000..13499ddd5 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-pie/src/types.ts @@ -0,0 +1,30 @@ +/** + * + * @title Props + */ + export type TinyPieProps = { + + /** + * @zh 数据由指标和维度组成,“维度” 指的是数据的属性,“指标” 是量化衡量标准 + * + */ + dimension?: object; + + /** + * @zh 配置项,各图表 Settings 属性配置请查阅各图表详情页面 + * + */ + settings?: Object; + + /** + * @zh 设置图表容器的宽度。 + * + */ + width?: string; + + /** + * @zh 设置图表容器的高度 + * + */ + height?: string; +}; diff --git a/packages/vue/src/charts-beta/chart-radar/index.ts b/packages/vue/src/charts-beta/chart-radar/index.ts new file mode 100644 index 000000000..8e863d773 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-radar/index.ts @@ -0,0 +1,10 @@ +import ChartRadar from './src/chart-radar.vue' + + +ChartRadar.install = function (Vue:any) { + Vue.component(ChartRadar.name, ChartRadar) +} + + +export {ChartRadar} +export default ChartRadar diff --git a/packages/vue/src/charts-beta/chart-radar/package.json b/packages/vue/src/charts-beta/chart-radar/package.json new file mode 100644 index 000000000..98f5a9529 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-radar/package.json @@ -0,0 +1,18 @@ +{ + "name": "@opentiny/vue-chart-radar-beta", + "version": "3.7.0", + "description": "", + "main": "lib/index.js", + "module": "index.ts", + "sideEffects": false, + "type": "module", + "devDependencies": {}, + "scripts": { + "build": "pnpm -w build:ui $npm_package_name", + "//postversion": "pnpm build" + }, + "dependencies": { + "@opentiny/vue-chart-core-beta": "workspace:~" + }, + "license": "MIT" +} \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-radar/src/chart-radar.vue b/packages/vue/src/charts-beta/chart-radar/src/chart-radar.vue new file mode 100644 index 000000000..6a43582b2 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-radar/src/chart-radar.vue @@ -0,0 +1,44 @@ + + \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-radar/src/radar.ts b/packages/vue/src/charts-beta/chart-radar/src/radar.ts new file mode 100644 index 000000000..ff9cb59f0 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-radar/src/radar.ts @@ -0,0 +1,127 @@ +import { getFormatted } from '@opentiny/vue-chart-core-beta/common/util' +import { itemPoint, itemLabel, itemContent } from '@opentiny/vue-chart-core-beta/common/constants' +import { isNull } from '@opentiny/vue-chart-core-beta/common/type' + +const getRadarLegend = (rows, dimension, legendName, legendVisible) => { + let legendData = rows.map((row) => row[dimension]) + + function formatter(value) { + return isNull(legendName[value]) ? value : legendName[value] + } + + return { show: legendVisible, data: legendData, formatter } +} + +const getRadarTooltip = (dataType, radar, digit) => { + const { typeTemp = [], nameTemp = [] } = {} + + radar.indicator.forEach((item, i) => { + typeTemp[i] = dataType[item.name] + nameTemp[i] = item.name + }) + + function formatter(item) { + const tplt = [] + tplt.push(itemPoint(item.color)) + tplt.push(`${item.name}
`) + item.data.value.forEach((val, i) => { + tplt.push(`${itemLabel(nameTemp[i])}`) + tplt.push(`${itemContent(getFormatted(val, typeTemp[i], digit))}
`) + }) + return tplt.join('') + } + + return { formatter } +} + +const getRadarSetting = (rows, metrics, labelMap) => { + const settingBase = { + indicator: [] + } + let indicatorTemp = {} + + rows.forEach((items) => { + metrics.forEach((item) => { + const key = isNull(labelMap[item]) ? item : labelMap[item] + if (indicatorTemp[key]) { + indicatorTemp[key].push(items[item]) + } else { + indicatorTemp[key] = [items[item]] + } + }) + }) + + settingBase.indicator = Object.keys(indicatorTemp).map((key) => ({ + name: key, + max: Math.max.apply(null, indicatorTemp[key]) + })) + + settingBase.data = indicatorTemp + settingBase.axisLabel = false + return settingBase +} + +const getRadarSeries = (args) => { + const { areaStyle, dimension, itemStyle, label, labelMap, lineStyle, metrics, radar, rows } = args + let radarIndexObj = {} + + radar.indicator.forEach((item, i) => (radarIndexObj[item.name] = i)) + + const seriesData = rows.map((row) => { + const serieData = { + value: [], + name: row[dimension] + } + + Object.keys(row).forEach((key) => { + if (~metrics.indexOf(key)) { + let k = isNull(labelMap[key]) ? radarIndexObj[key] : radarIndexObj[labelMap[key]] + serieData.value[k] = row[key] + } + }) + + return serieData + }) + + const result = { data: seriesData, name: 'data', type: 'radar' } + + label && (result.label = label) + itemStyle && (result.itemStyle = itemStyle) + lineStyle && (result.lineStyle = lineStyle) + areaStyle && (result.areaStyle = areaStyle) + + return [result] +} + +export const radar = (columns, rows, settings, extra) => { + const { dataType = {}, legendName = {}, labelMap = {}, dimension = columns[0] } = settings + const { digit = 2, label, itemStyle, lineStyle, areaStyle } = settings + const { tooltipVisible, legendVisible } = extra + let metrics = columns.slice() + + if (!settings.metrics) { + metrics.splice(columns.indexOf(dimension), 1) + } else { + metrics = settings.metrics + } + + const legend = legendVisible ? getRadarLegend(rows, dimension, legendName, legendVisible) : { show: false } + const radar = getRadarSetting(rows, metrics, labelMap) + const tooltip = tooltipVisible ? getRadarTooltip(dataType, radar, digit) : { show: false } + const seriesParams = { rows, dimension, metrics, radar } + + Object.assign(seriesParams, { label, itemStyle, lineStyle, labelMap, areaStyle }) + + const series = getRadarSeries(seriesParams) + let dataTemp = {} + + rows.forEach((items, index) => { + const tempKey = items[dimension] + dataTemp[tempKey] = {} + metrics.forEach((item) => { + const key = isNull(labelMap[item]) ? item : labelMap[item] + dataTemp[tempKey][key] = items[item] + }) + }) + return { legend, data: dataTemp, radar, tipHtml: tooltip.formatter, series } +} diff --git a/packages/vue/src/charts-beta/chart-ring/index.ts b/packages/vue/src/charts-beta/chart-ring/index.ts new file mode 100644 index 000000000..48cd076bc --- /dev/null +++ b/packages/vue/src/charts-beta/chart-ring/index.ts @@ -0,0 +1,10 @@ +import ChartRing from './src/chart-ring.vue' + + +ChartRing.install = function (Vue:any) { + Vue.component(ChartRing.name, ChartRing) +} + + +export {ChartRing} +export default ChartRing diff --git a/packages/vue/src/charts-beta/chart-ring/package.json b/packages/vue/src/charts-beta/chart-ring/package.json new file mode 100644 index 000000000..acece20ea --- /dev/null +++ b/packages/vue/src/charts-beta/chart-ring/package.json @@ -0,0 +1,18 @@ +{ + "name": "@opentiny/vue-chart-ring-beta", + "version": "3.7.0", + "description": "", + "main": "lib/index.js", + "module": "index.ts", + "sideEffects": false, + "type": "module", + "devDependencies": {}, + "scripts": { + "build": "pnpm -w build:ui $npm_package_name", + "//postversion": "pnpm build" + }, + "dependencies": { + "@opentiny/vue-chart-core-beta": "workspace:~" + }, + "license": "MIT" +} \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-ring/src/chart-ring.vue b/packages/vue/src/charts-beta/chart-ring/src/chart-ring.vue new file mode 100644 index 000000000..db143129e --- /dev/null +++ b/packages/vue/src/charts-beta/chart-ring/src/chart-ring.vue @@ -0,0 +1,29 @@ + + \ No newline at end of file diff --git a/packages/vue/src/charts-beta/chart-ring/src/ring.ts b/packages/vue/src/charts-beta/chart-ring/src/ring.ts new file mode 100644 index 000000000..b5e4617f9 --- /dev/null +++ b/packages/vue/src/charts-beta/chart-ring/src/ring.ts @@ -0,0 +1,306 @@ +/** settings: + * dimension string 维度,默认 columns 第一项为维度 + * metrics string 指标,默认 columns 第二项为指标 + * dataType string 数据类型,可选值: KMB, normal, percent + * legendLimit number legend 显示数量限制,legend 数量过多会导致饼图样式错误,限制 legend 最大值并且当超过此值时,隐藏 legend 可以解决这个问题 + * selectedMode string 选中模式,可选值:single, multiple,默认为 false + * hoverAnimation hover 是否开启,在扇区上的放大动画效果 boolean 默认值为 true // aui有tiny没有 + * radius number / string 饼图半径,支持数值和百分比 + * offsetY number / string 纵向偏移量,支持数值和百分比 + * digit number 设置数据类型为 percent 时保留的位数,默认为 2 + * roseType string 显示为南丁格尔玫瑰图,默认不展示为南丁格尔玫瑰图,可设置为'radius', 'area' + * label object 饼图图形上的文本标签,内容参考文档 + * labelLine object 标签的视觉引导线样式,内容参考文档 + * itemStyle object 图形样式,内容参考文档 + * level array 多圆饼图时设置,level 的值接受二维数组,例如:[['a', 'b'], ['c', 'd']], 表示的含义是内层展示的是维度中的'a', 'b'的指标加在一起组成的饼图,外层为'c', 'd'的指标加在一起组成的环图 + * limitShowNum number 设置超过此数字时使用‘其他’代替,此时数据会按照由大到小顺序显示 + * + * emphasis 高亮的扇区和标签样式。 + * percentShow label引导线边的文本显示百分比数据 + */ + +/** extra: + * legendVisible boolean 是否显示图例,true + * tooltipVisible boolean 是否显示提示框,true + */ + +import { deepCopy } from '@opentiny/vue-chart-core-beta/utils/deep-clone' +import { getFormatted } from '@opentiny/vue-chart-core-beta/common/util' +import { itemPoint, itemLabel, itemContent } from '@opentiny/vue-chart-core-beta/common/constants' +import { isDefined } from '@opentiny/vue-chart-core-beta/utils/type' + +const PIE_RADIUS = 100 +const RING_RADIUS = [90, 100] +const ROSE_RING_RADIUS = [20, 100] +const PIE_OFFSET = '50%' + +const getTooltip = (args) => { + const { dataType, digit, dimension, innerRows, limitShowNum, metrics } = args + let { localeOther = '其他', remainArr, sum = 0 } = {} + let mapHandler = (row) => { + sum += row[metrics] + return { name: row[dimension], value: row[metrics] } + } + + remainArr = innerRows.map(mapHandler).slice(limitShowNum, innerRows.length) + + let formatter = function (item) { + let tplt = [itemPoint(item.color)] + let percent + + if (!limitShowNum || item.name !== localeOther) { + tplt.push(itemLabel(item.name)) + tplt.push(itemContent(getFormatted(item.value, dataType, digit))) + tplt.push(itemContent(`(${item.percent}%)`)) + } else { + tplt.push(localeOther + ':') + + remainArr.forEach(({ name, value }) => { + percent = '(' + getFormatted(value / sum, 'percent') + ')' + tplt.push(`
${itemLabel(name)}`) + tplt.push(itemContent(getFormatted(value, dataType, digit))) + tplt.push(itemContent(percent)) + }) + } + + return tplt.join('') + } + + return formatter +} + +const getLabel = (args) => { + const { label, labelLine, percentShow, dataType, digit } = args + let labelObj = {} + + if (label) { + if (isDefined(label.show)) { + labelObj.show = label.show + } + if (isDefined(label.formatter)) { + labelObj.labelHtml = label.formatter + } + } + if (percentShow) { + labelObj.labelHtml = (params) => { + let tplt = [] + tplt.push(`${params.name}:`) + tplt.push(getFormatted(params.value, dataType, digit)) + tplt.push(`(${params.percent}%)`) + return tplt.join('') + } + } + + if (labelLine) { + if (isDefined(labelLine.show)) { + labelObj.line = labelLine.show + } + let color + if (labelLine && labelLine.lineStyle) { + color = labelLine.lineStyle.color + } + if (isDefined(color)) { + labelObj.lineColor = color + } + if (isDefined(labelLine.length)) { + labelObj.distance = labelLine.length + } + } + return labelObj +} + +const getPosition = (args) => { + const { radius, offsetY, level } = args + const position = {} + if (isDefined(offsetY)) { + position.center = [PIE_OFFSET, offsetY] + } + if (Array.isArray(radius)) { + position.radius = radius + } else if (!level || level.length === 0) { + position.radius = [0, radius] + } + return position +} + +// 数据先转成{name,value}格式 +const getInnerData = (args) => { + const { dimension, metrics, innerRows } = args + return innerRows.map((row) => { + return { + name: row[dimension], + value: row[metrics] + } + }) +} + +const getDataOrSeries = (args) => { + const { innerData, isRing, radius, level, limitShowNum } = args + let series + + const getLimitData = (data) => { + let tempData = data + if (limitShowNum && limitShowNum < tempData.length) { + // 多出数据合并为其他 + const remainArr = tempData.slice(limitShowNum, innerData.length) + const sum = remainArr.reduce((a, c) => a + c.value, 0) + tempData.splice(limitShowNum, Infinity, { name: '其他', value: sum }) + } + return tempData + } + + const levelFlag = level && level.length + if (levelFlag) { + // level是个二维数组 + const levelObj = {} + let maxLevel = 0 + let levelData = [] + level.forEach((levelItems, index) => { + levelItems.forEach((item) => { + if (levelObj[item] === undefined) { + levelObj[item] = [index] + } else { + levelObj[item].push(index) + } + if (maxLevel < index + 1) { + maxLevel = index + 1 + } + }) + }) + levelData = Array.from({ length: maxLevel }, () => []) + innerData.forEach((data) => { + Array.isArray(levelObj[data.name]) && + levelObj[data.name].forEach((levelIdx) => { + levelData[levelIdx].push({ ...data }) + }) + }) + + let rowsCount = levelData.length + let centerWidth = radius / rowsCount + series = levelData.map((data, index) => { + let itemRadius + if (index === 0) { + itemRadius = isRing ? radius : centerWidth + } else { + let outerWidth = centerWidth + (radius / (2 * rowsCount)) * (2 * index - 1) + let innerWidth = outerWidth + radius / (2 * rowsCount) + itemRadius = [outerWidth, innerWidth] + } + return { + name: `PIE-${index}`, + type: 'pie', + radius: itemRadius, + data: getLimitData(data) + } + }) + } + + return levelFlag ? { series } : { data: getLimitData(innerData) } +} + +const getLegend = (args) => { + const { dimension, innerRows, legendVisible, legendLimit, level, limitShowNum, innerData } = args + if (!legendVisible) { + return { + show: false + } + } + let { legend = [], levelTemp = [] } = {} + if (level) { + level.forEach((levelItem) => levelItem.forEach((item) => levelTemp.push(item))) + legend = levelTemp + } else if (limitShowNum && limitShowNum < innerRows.length) { + for (let i = limitShowNum - 1; i >= 0; i--) { + legend.unshift(innerRows[i][dimension]) + } + } else { + legend = innerRows.map((row) => row[dimension]) + } + let show = false + if (legend.length) { + show = legend.length < legendLimit + } + + if (level && level.length) { + return { + show, + data: innerData + } + } + + return { + show + } +} + +// ichart配置项 +export const pie = (columns, rows, settings, extra, isRing) => { + const innerRows = deepCopy(rows) + + const { dataType = 'normal', digit = 2, dimension = columns[0], emphasis } = settings + + const { itemStyle, label = false, labelLine, legendLimit = 30, legendName = {}, level = false } = settings + + const { limitShowNum = 0, metrics = columns[1], offsetY = PIE_OFFSET, percentShow } = settings + + const { roseType = false, selectedMode = false, hoverAnimation } = settings + + const { radius = isRing ? (roseType ? ROSE_RING_RADIUS : RING_RADIUS) : PIE_RADIUS } = settings + + const { legendVisible, tooltipVisible } = extra + + // 设置 limitShowNum 时,数据会按照由大到小顺序显示 + limitShowNum && innerRows.sort((a, b) => b[metrics] - a[metrics]) + + // 数据先转成{name,value}格式 + const innerData = getInnerData({ dimension, metrics, innerRows }) + + // 圆盘图类型 + const type = isRing ? 'circle' : 'pie' + + // 图表位置及大小 + const position = getPosition({ radius, offsetY, level }) + + // 外侧文本配置 + const ichartLabel = getLabel({ label, labelLine, percentShow, dataType, digit }) + + // 图例配置 + const ichartLegend = getLegend({ legendVisible, dimension, innerRows, legendLimit, level, limitShowNum }) + + // 图表数据(必填) + const dataOrSeries = getDataOrSeries({ innerData, isRing, radius, level, limitShowNum }) + + // ichart 配置项 + let ichartOption = { + legend: ichartLegend, + type, + position, + label: ichartLabel, + itemStyle, + emphasis: emphasis || { scale: hoverAnimation === undefined ? true : hoverAnimation }, + roseType, + selectedMode, + ...dataOrSeries + } + + // 悬浮提示框内容配置 + if (tooltipVisible) { + const tipHtml = getTooltip({ dataType, innerRows, limitShowNum, digit, metrics, dimension }) + ichartOption.tipHtml = tipHtml + } else { + ichartOption.tooltip = { + show: false + } + } + + let ichartExtend = {} + if (label) { + ichartExtend.label = label + } + if (labelLine) { + ichartExtend.labelLine = labelLine + } + ichartOption.extend = { ...ichartExtend } + + return ichartOption +} diff --git a/packages/vue/src/charts-beta/chart-ring/src/types.ts b/packages/vue/src/charts-beta/chart-ring/src/types.ts new file mode 100644 index 000000000..a567b90bd --- /dev/null +++ b/packages/vue/src/charts-beta/chart-ring/src/types.ts @@ -0,0 +1,30 @@ +/** + * + * @title Props + */ + export type TinyRingProps = { + + /** + * @zh 数据由指标和维度组成,“维度” 指的是数据的属性,“指标” 是量化衡量标准 + * + */ + dimension?: object; + + /** + * @zh 配置项,各图表 Settings 属性配置请查阅各图表详情页面 + * + */ + settings?: Object; + + /** + * @zh 设置图表容器的宽度。 + * + */ + width?: string; + + /** + * @zh 设置图表容器的高度 + * + */ + height?: string; +}; diff --git a/packages/vue/src/charts-beta/index.ts b/packages/vue/src/charts-beta/index.ts new file mode 100644 index 000000000..f3adbc3a8 --- /dev/null +++ b/packages/vue/src/charts-beta/index.ts @@ -0,0 +1,16 @@ +import Chart from './src/index' +import { version } from './package.json' + +Chart.install = function (Vue) { + Vue.component(Chart.name, Chart) +} + +Chart.version = version + +if (process.env.BUILD_TARGET === 'runtime') { + if (typeof window !== 'undefined' && window.Vue) { + Chart.install(window.Vue) + } +} + +export default Chart diff --git a/packages/vue/src/charts-beta/package.json b/packages/vue/src/charts-beta/package.json new file mode 100644 index 000000000..731252103 --- /dev/null +++ b/packages/vue/src/charts-beta/package.json @@ -0,0 +1,34 @@ +{ + "name": "@opentiny/vue-chart-beta", + "version": "3.7.0", + "description": "", + "main": "lib/index.js", + "module": "index.ts", + "files": [ + "index.js", + "lib", + "index.d.ts", + "src" + ], + "sideEffects": false, + "type": "module", + "devDependencies": { + "@opentiny-internal/vue-test-utils": "workspace:*", + "vitest": "^0.31.0" + }, + "scripts": { + "build": "pnpm -w build:ui $npm_package_name", + "//postversion": "pnpm build" + }, + "dependencies": { + "echarts": "5.4.1", + "@opentiny/vue-chart-bar-beta": "workspace:~", + "@opentiny/vue-chart-core-beta": "workspace:~", + "@opentiny/vue-chart-histogram-beta": "workspace:~", + "@opentiny/vue-chart-line-beta": "workspace:~", + "@opentiny/vue-chart-pie-beta": "workspace:~", + "@opentiny/vue-chart-radar-beta": "workspace:~", + "@opentiny/vue-chart-ring-beta": "workspace:~" + }, + "license": "MIT" +} \ No newline at end of file diff --git a/packages/vue/src/charts-beta/src/index.ts b/packages/vue/src/charts-beta/src/index.ts new file mode 100644 index 000000000..0a29a1482 --- /dev/null +++ b/packages/vue/src/charts-beta/src/index.ts @@ -0,0 +1,26 @@ +import { ChartBar } from '@opentiny/vue-chart-bar-beta/index' +import { ChartHistogram } from '@opentiny/vue-chart-histogram-beta/index' +import { ChartLine } from '@opentiny/vue-chart-line-beta/index' +import { ChartPie } from '@opentiny/vue-chart-pie-beta/index' +import { ChartRing } from '@opentiny/vue-chart-ring-beta/index' +import { ChartRadar } from '@opentiny/vue-chart-radar-beta/index' + +import { $prefix, defineComponent } from '@opentiny/vue-common' + +export default defineComponent({ + ...{ + name: $prefix + 'Chart', + data() { + this.chartLib = { + ChartBar, + ChartHistogram, + ChartLine, + ChartPie, + ChartRing, + ChartRadar + } + this.chartHandler = this.chartLib[this.settings.type] + return {} + } + } +}) diff --git a/packages/vue/src/checkbox-group/src/index.ts b/packages/vue/src/checkbox-group/src/index.ts index 540405533..4c55489e6 100644 --- a/packages/vue/src/checkbox-group/src/index.ts +++ b/packages/vue/src/checkbox-group/src/index.ts @@ -10,6 +10,8 @@ * */ import { $props, $prefix, $setup, defineComponent } from '@opentiny/vue-common' +import type { PropType } from '@opentiny/vue-common' +import type { IconPosition } from '@opentiny/vue-checkbox/src' import template from 'virtual-template?pc|mobile|mobile-first' const $constants = { @@ -46,7 +48,8 @@ export default defineComponent({ displayOnly: { type: Boolean, default: false - } + }, + iconPosition: String as PropType }, setup(props, context) { return $setup({ props, context, template }) diff --git a/packages/vue/src/checkbox-group/src/mobile.vue b/packages/vue/src/checkbox-group/src/mobile.vue index 7a7a609c2..ce10557f3 100644 --- a/packages/vue/src/checkbox-group/src/mobile.vue +++ b/packages/vue/src/checkbox-group/src/mobile.vue @@ -35,7 +35,7 @@ export default defineComponent({ components: { Checkbox }, - props: [...props, 'modelValue', 'type', 'options', 'disabled', 'vertical', 'max', 'min'], + props: [...props, 'modelValue', 'type', 'options', 'disabled', 'vertical', 'max', 'min', 'iconPosition'], setup(props, context) { return setup({ props, context, renderless, api }) } diff --git a/packages/vue/src/checkbox/src/index.ts b/packages/vue/src/checkbox/src/index.ts index 96fb44b21..0e3ddeca3 100644 --- a/packages/vue/src/checkbox/src/index.ts +++ b/packages/vue/src/checkbox/src/index.ts @@ -10,8 +10,11 @@ * */ import { $props, $prefix, $setup, defineComponent } from '@opentiny/vue-common' +import type { PropType } from '@opentiny/vue-common' import template from 'virtual-template?pc|mobile|mobile-first' +export type IconPosition = 'center' | 'top' + export const $constants = { FORM_ITEM: 'FormItem', FORM_CHANGE: 'form.change', @@ -63,7 +66,8 @@ export const checkboxProps = { displayOnly: { type: Boolean, default: false - } + }, + iconPosition: String as PropType } export default defineComponent({ diff --git a/packages/vue/src/checkbox/src/mobile.vue b/packages/vue/src/checkbox/src/mobile.vue index 88e60fd31..882327fd3 100644 --- a/packages/vue/src/checkbox/src/mobile.vue +++ b/packages/vue/src/checkbox/src/mobile.vue @@ -16,7 +16,8 @@ 'is-disabled': state.isDisabled, 'is-checked': state.isChecked, 'is-indeterminate': indeterminate, - 'is-focus': state.focus + 'is-focus': state.focus, + 'icon-position-top': state.iconPosition === 'top' }" :id="id" > @@ -80,7 +81,8 @@ export default defineComponent({ 'name', 'trueLabel', 'falseLabel', - 'id' + 'id', + 'iconPosition', ], setup(props, context) { return setup({ props, context, renderless, api }) diff --git a/packages/vue/src/input/src/index.ts b/packages/vue/src/input/src/index.ts index 5e6cb8cee..b66fa3485 100644 --- a/packages/vue/src/input/src/index.ts +++ b/packages/vue/src/input/src/index.ts @@ -80,7 +80,7 @@ export const inputProps = { type: Boolean, default: false }, - mobileTips: String, + tips: String, counter: { type: Boolean, default: false @@ -116,7 +116,7 @@ export const inputProps = { // mobile特有属性 textareaTitle: { type: String, - default: '标题' + default: '' }, displayOnly: { type: Boolean, @@ -137,6 +137,13 @@ export const inputProps = { showEmptyValue: { type: Boolean, default: false + }, + textAlign: { + type: String, + default: 'left' + }, + width: { + type: [String, Number] as PropType } } diff --git a/packages/vue/src/input/src/mobile.vue b/packages/vue/src/input/src/mobile.vue index 60a39ca2f..3d70d6b4d 100644 --- a/packages/vue/src/input/src/mobile.vue +++ b/packages/vue/src/input/src/mobile.vue @@ -15,123 +15,63 @@ type === 'textarea' ? 'tiny-mobile-textarea' : 'tiny-mobile-input', state.inputSize ? 'tiny-mobile-input-' + state.inputSize : '', { + 'is-focus': state.focused, 'is-disabled': state.inputDisabled, 'is-exceed': state.inputExceed, - 'is-showtitle': showTitle && type === 'textarea', - 'is-showcontent': slots.content && type === 'textarea', 'is-showlimit': state.isWordLimitVisible && type === 'textarea', 'tiny-mobile-input-group': slots.prepend || slots.append, 'tiny-mobile-input-group-append': slots.append, - 'tiny-mobile-input-group-prepend': slots.prepend, - 'tiny-mobile-input-prefix': slots.prefix || prefixIcon, - 'tiny-mobile-input-suffix': slots.suffix || suffixIcon || clearable || showPassword + 'tiny-mobile-input-group-prepend': slots.prepend } ]" + :style="$attrs.style" @mouseenter="state.hovering = true" @mouseleave="state.hovering = false" > - - +
-
- {{ textareaTitle }} -
-
- -
+ + {{ state.showWordLimit ? `${state.textLength}/${state.upperLimit}` : state.textLength }} + + +
+ {{ tips }} +
+ + +