cardbuddy 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +211 -0
- package/dist/cli.js +76 -0
- package/dist/config.js +63 -0
- package/dist/core.js +2 -0
- package/dist/preview-editor/assets/apple-touch-icon-BQcES9QK.png +0 -0
- package/dist/preview-editor/assets/default-image-wzZYQ86K.png +0 -0
- package/dist/preview-editor/assets/favicon-16-Cg6Nrosk.png +0 -0
- package/dist/preview-editor/assets/favicon-32-B_1744sp.png +0 -0
- package/dist/preview-editor/assets/index-BYB0hngG.js +72 -0
- package/dist/preview-editor/assets/index-CpBZEQDy.css +1 -0
- package/dist/preview-editor/assets/site-icon-CFMif8z4.png +0 -0
- package/dist/preview-editor/assets/svg-symbols-B2exBrAr.svg +118 -0
- package/dist/preview-editor/index.html +17 -0
- package/dist/server-core/_vendor/finmall-env.mjs +96 -0
- package/dist/server-core/assetProxy.js +154 -0
- package/dist/server-core/bbox.js +153 -0
- package/dist/server-core/browser.js +108 -0
- package/dist/server-core/buildDataFromSchema.js +50 -0
- package/dist/server-core/cardFiles.js +188 -0
- package/dist/server-core/cardHistory.js +156 -0
- package/dist/server-core/cardImage.js +85 -0
- package/dist/server-core/cardImportMeta.js +139 -0
- package/dist/server-core/cardListCache.js +68 -0
- package/dist/server-core/cardOpenFolder.js +135 -0
- package/dist/server-core/cardSave.js +127 -0
- package/dist/server-core/cardSidecar.js +72 -0
- package/dist/server-core/cardSync.js +259 -0
- package/dist/server-core/cardValidate.js +134 -0
- package/dist/server-core/index.js +82 -0
- package/dist/server-core/logger.js +241 -0
- package/dist/server-core/nodeModels.js +52 -0
- package/dist/server-core/renderStatus.js +207 -0
- package/dist/server-core/screenshot.js +123 -0
- package/dist/server-core/sliceStatic.js +44 -0
- package/dist/standalone.js +447 -0
- package/dist/vendored/App.vue +2330 -0
- package/dist/vendored/api.js +142 -0
- package/dist/vendored/assets/apple-touch-icon.png +0 -0
- package/dist/vendored/assets/default-image.png +0 -0
- package/dist/vendored/assets/favicon-16.png +0 -0
- package/dist/vendored/assets/favicon-32.png +0 -0
- package/dist/vendored/assets/logo.png +0 -0
- package/dist/vendored/assets/site-icon.png +0 -0
- package/dist/vendored/assets/svg-symbols.svg +118 -0
- package/dist/vendored/assets/svgIcons/icons.svg +54 -0
- package/dist/vendored/assets/svgIcons/index.js +39 -0
- package/dist/vendored/components/AbsoluteLayout.vue +42 -0
- package/dist/vendored/components/AlertCardInstance.vue +58 -0
- package/dist/vendored/components/AlertCardLayer.vue +59 -0
- package/dist/vendored/components/ApiPayloadTree.vue +168 -0
- package/dist/vendored/components/AppToast.vue +37 -0
- package/dist/vendored/components/Button.vue +37 -0
- package/dist/vendored/components/Canvas.vue +65 -0
- package/dist/vendored/components/CardGallery.vue +243 -0
- package/dist/vendored/components/CardPreview.vue +72 -0
- package/dist/vendored/components/CardSelect.vue +200 -0
- package/dist/vendored/components/CheckGroup.vue +131 -0
- package/dist/vendored/components/Column.vue +25 -0
- package/dist/vendored/components/ComponentPalette.vue +294 -0
- package/dist/vendored/components/ConfirmDialog.vue +39 -0
- package/dist/vendored/components/CustomPlaceholder.vue +33 -0
- package/dist/vendored/components/DatePickerOverlay.vue +345 -0
- package/dist/vendored/components/DebugPanel.vue +806 -0
- package/dist/vendored/components/DeviceOverlay.vue +70 -0
- package/dist/vendored/components/Divider.vue +22 -0
- package/dist/vendored/components/Dropdown.vue +191 -0
- package/dist/vendored/components/EventEditor.vue +247 -0
- package/dist/vendored/components/GridLayout.vue +67 -0
- package/dist/vendored/components/HoverGuides.vue +122 -0
- package/dist/vendored/components/Icon.vue +34 -0
- package/dist/vendored/components/Image.vue +34 -0
- package/dist/vendored/components/Input.vue +90 -0
- package/dist/vendored/components/JsonTree.vue +516 -0
- package/dist/vendored/components/ListLayout.vue +68 -0
- package/dist/vendored/components/LogValue.vue +51 -0
- package/dist/vendored/components/Map.vue +110 -0
- package/dist/vendored/components/MultiColumn.vue +49 -0
- package/dist/vendored/components/MultiText.vue +58 -0
- package/dist/vendored/components/NodeInspector.vue +295 -0
- package/dist/vendored/components/PickerOverlay.vue +131 -0
- package/dist/vendored/components/ProgressSlider.vue +167 -0
- package/dist/vendored/components/PropControl.vue +241 -0
- package/dist/vendored/components/PropTree.vue +128 -0
- package/dist/vendored/components/RadioButton.vue +74 -0
- package/dist/vendored/components/RemotePreview.vue +171 -0
- package/dist/vendored/components/RenderTree.vue +437 -0
- package/dist/vendored/components/Root.vue +30 -0
- package/dist/vendored/components/Row.vue +25 -0
- package/dist/vendored/components/SelectionOverlay.vue +105 -0
- package/dist/vendored/components/StructureTree.vue +949 -0
- package/dist/vendored/components/SvgCode.vue +19 -0
- package/dist/vendored/components/SvgSymbols.vue +30 -0
- package/dist/vendored/components/Switch.vue +38 -0
- package/dist/vendored/components/Text.vue +32 -0
- package/dist/vendored/components/Video.vue +70 -0
- package/dist/vendored/components/ViewContainer.vue +52 -0
- package/dist/vendored/components/base.js +12 -0
- package/dist/vendored/components/icons.js +47 -0
- package/dist/vendored/components/index.js +58 -0
- package/dist/vendored/components/setters/SetBgPosition.vue +64 -0
- package/dist/vendored/components/setters/SetBgSize.vue +90 -0
- package/dist/vendored/components/setters/SetCode.vue +90 -0
- package/dist/vendored/components/setters/SetColor.vue +244 -0
- package/dist/vendored/components/setters/SetCustomSelect.vue +58 -0
- package/dist/vendored/components/setters/SetExpr.vue +107 -0
- package/dist/vendored/components/setters/SetGradient.vue +125 -0
- package/dist/vendored/components/setters/SetGrid9.vue +68 -0
- package/dist/vendored/components/setters/SetIconSelect.vue +67 -0
- package/dist/vendored/components/setters/SetIfForm.vue +61 -0
- package/dist/vendored/components/setters/SetImage.vue +43 -0
- package/dist/vendored/components/setters/SetList.vue +139 -0
- package/dist/vendored/components/setters/SetNumber.vue +118 -0
- package/dist/vendored/components/setters/SetNumberStyle.vue +113 -0
- package/dist/vendored/components/setters/SetPadding.vue +77 -0
- package/dist/vendored/components/setters/SetSegment.vue +160 -0
- package/dist/vendored/components/setters/SetSelect.vue +51 -0
- package/dist/vendored/components/setters/SetSwitch.vue +49 -0
- package/dist/vendored/components/setters/SetText.vue +52 -0
- package/dist/vendored/components/setters/props.js +23 -0
- package/dist/vendored/fidelity/baseline-messageOverview.json +87 -0
- package/dist/vendored/fidelity/calibration.json +29 -0
- package/dist/vendored/fidelity/fidelity-messageOverview-after.html +114 -0
- package/dist/vendored/fidelity/fidelity-messageOverview-after.json +272 -0
- package/dist/vendored/fidelity/fidelity-messageOverview.html +185 -0
- package/dist/vendored/fidelity/fidelity-messageOverview.json +336 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_v2.html +262 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_v2.json +502 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_v3.html +201 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_v3.json +417 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_visual.html +125 -0
- package/dist/vendored/fidelity/fidelity-messageOverview_visual.json +338 -0
- package/dist/vendored/index.html +16 -0
- package/dist/vendored/main.js +36 -0
- package/dist/vendored/meta/action-meta.json +141 -0
- package/dist/vendored/meta/component-meta.json +2714 -0
- package/dist/vendored/meta/directive-meta.json +40 -0
- package/dist/vendored/meta/style-meta.json +1086 -0
- package/dist/vendored/runtime/StyleParser.js +179 -0
- package/dist/vendored/runtime/adapter.js +166 -0
- package/dist/vendored/runtime/alert-card.js +125 -0
- package/dist/vendored/runtime/app-toast.js +33 -0
- package/dist/vendored/runtime/assets.js +16 -0
- package/dist/vendored/runtime/canvas-context.js +141 -0
- package/dist/vendored/runtime/card-runtime.js +289 -0
- package/dist/vendored/runtime/clipboard-node.js +83 -0
- package/dist/vendored/runtime/clipboard.js +29 -0
- package/dist/vendored/runtime/evaluate.js +152 -0
- package/dist/vendored/runtime/event-model.js +300 -0
- package/dist/vendored/runtime/hoverGuide.js +31 -0
- package/dist/vendored/runtime/jsapi-mock.js +256 -0
- package/dist/vendored/runtime/node-model.js +217 -0
- package/dist/vendored/runtime/property.js +364 -0
- package/dist/vendored/runtime/selectionOverlay.js +53 -0
- package/dist/vendored/runtime/toPreview.js +648 -0
- package/dist/vendored/standalone-main.js +36 -0
- package/dist/vendored/standalone.html +12 -0
- package/dist/vendored/styles/app.css +2565 -0
- package/dist/vendored/styles/card-gallery.css +234 -0
- package/dist/vendored/styles/card-select.css +281 -0
- package/dist/vendored/styles/components.css +383 -0
- package/dist/vendored/styles/dropdown.css +337 -0
- package/dist/vendored/styles/json-tree.css +308 -0
- package/dist/vendored/styles/node-inspector.css +1003 -0
- package/dist/vendored/styles/setters.css +962 -0
- package/dist/vendored/styles/standalone.css +27 -0
- package/dist/vendored/styles/structure-tree.css +320 -0
- package/package.json +42 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 移植自官方 inner_card chunk 4 的模块 182(StyleParser)。
|
|
3
|
+
*
|
|
4
|
+
* 输入是「样式分组对象」(rect / flex / background / padding / margin /
|
|
5
|
+
* borderRadius / border / shadow / transform / other / font / image),
|
|
6
|
+
* 输出是可以直接绑到 `:style` 上的对象。
|
|
7
|
+
*
|
|
8
|
+
* 用法与官方一致:`new StyleParser(this, this.styleProps).parse()`
|
|
9
|
+
* (第一个参数是组件实例,内部取 `vm.adapter`)。
|
|
10
|
+
*/
|
|
11
|
+
export default class StyleParser {
|
|
12
|
+
constructor(vm, styleProps) {
|
|
13
|
+
this.vm = vm
|
|
14
|
+
this.styleProps = styleProps || {}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get adapter() {
|
|
18
|
+
// 兼容直接传 adapter 的用法
|
|
19
|
+
return this.vm && this.vm.adapter ? this.vm.adapter : this.vm
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** 解析顺序与官方严格一致,后面的分组会覆盖前面的同名属性 */
|
|
23
|
+
parse() {
|
|
24
|
+
const s = this.styleProps
|
|
25
|
+
const out = {}
|
|
26
|
+
this.parseRect(s.rect || {}, out)
|
|
27
|
+
this.parseFlex(s.flex || {}, out)
|
|
28
|
+
this.parseBackground(s.background || {}, out)
|
|
29
|
+
this.parsePadding(s.padding || {}, out)
|
|
30
|
+
this.parseMargin(s.margin || {}, out)
|
|
31
|
+
this.parseBorderRadius(s.borderRadius || {}, out)
|
|
32
|
+
this.parseBorder(s.border || {}, out)
|
|
33
|
+
this.parseShadow(s.shadow || {}, out)
|
|
34
|
+
this.parseTransform(s.transform || {}, out)
|
|
35
|
+
this.parseOther(s.other || {}, out)
|
|
36
|
+
out.boxSizing = 'border-box'
|
|
37
|
+
return out
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 字体不在 parse() 里,由 Text / MultiText 等组件单独调用 */
|
|
41
|
+
parseFont(t = {}, e = {}) {
|
|
42
|
+
if (t.color) e.color = this.adapter.doColor(t.color)
|
|
43
|
+
if (t.fontSize) e.fontSize = this.adapter.doSize(t.fontSize, 'font')
|
|
44
|
+
if (t.lineHeight) e.lineHeight = this.adapter.doSize(t.lineHeight)
|
|
45
|
+
if (t.textAlign) e.textAlign = t.textAlign
|
|
46
|
+
if (t.fontWeight) e.fontWeight = t.fontWeight
|
|
47
|
+
if (t.fontStyle) e.fontStyle = t.fontStyle
|
|
48
|
+
if (t.textDecoration) e.textDecoration = t.textDecoration
|
|
49
|
+
if (t.isMaxLine == '1') {
|
|
50
|
+
e.lineClamp = t.maxLine || '1'
|
|
51
|
+
e['-webkit-line-clamp'] = t.maxLine || '1'
|
|
52
|
+
}
|
|
53
|
+
return e
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 图片裁剪模式,只有 Image 组件用 */
|
|
57
|
+
parseImage(t = {}, e = {}) {
|
|
58
|
+
if (t.cropMode == '01') {
|
|
59
|
+
e.backgroundSize = 'contain'
|
|
60
|
+
e.aspectRatio = '1/1'
|
|
61
|
+
} else {
|
|
62
|
+
e.backgroundSize = 'cover'
|
|
63
|
+
e.aspectRatio = '16/9'
|
|
64
|
+
}
|
|
65
|
+
return e
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
parseRect(t, e) {
|
|
69
|
+
e.width = this.adapter.doSize(t.width)
|
|
70
|
+
e.height = this.adapter.doSize(t.height)
|
|
71
|
+
e.maxWidth = this.adapter.doSize(t.maxWidth)
|
|
72
|
+
e.maxHeight = this.adapter.doSize(t.maxHeight)
|
|
73
|
+
e.minWidth = this.adapter.doSize(t.minWidth)
|
|
74
|
+
e.minHeight = this.adapter.doSize(t.minHeight)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
parseFlex(t, e) {
|
|
78
|
+
if (t.flexDirection) e.flexDirection = t.flexDirection
|
|
79
|
+
if (t.justifyContent) e.justifyContent = t.justifyContent
|
|
80
|
+
if (t.alignItems) e.alignItems = t.alignItems
|
|
81
|
+
if (t.flexWrap) e.flexWrap = t.flexWrap
|
|
82
|
+
if (t.flexShrink) e.flexShrink = t.flexShrink
|
|
83
|
+
if (t.gap) e.gap = this.adapter.doSize(t.gap)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
parseBackground(t, e) {
|
|
87
|
+
if (t.backgroundType == '01' && t.backgroundColor) {
|
|
88
|
+
e.backgroundColor = this.adapter.doColor(t.backgroundColor)
|
|
89
|
+
} else if (t.backgroundType == '02' && t.backgroundImage) {
|
|
90
|
+
e.backgroundImage = `url("${this.adapter.doReplaceUrl(
|
|
91
|
+
this.adapter.doI18n(t.backgroundImage)
|
|
92
|
+
)}")`
|
|
93
|
+
if (t.backgroundPosition) {
|
|
94
|
+
const p = t.backgroundPosition.split(/[ ]+/)
|
|
95
|
+
e.backgroundPosition = `${this.adapter.doSize(p[0] || '')} ${this.adapter.doSize(
|
|
96
|
+
p[1] || ''
|
|
97
|
+
)}`
|
|
98
|
+
} else {
|
|
99
|
+
e.backgroundPosition = '50% 50%'
|
|
100
|
+
}
|
|
101
|
+
if (t.backgroundSize) {
|
|
102
|
+
const sz = t.backgroundSize.split(/[ ]+/)
|
|
103
|
+
e.backgroundSize =
|
|
104
|
+
sz.length >= 2
|
|
105
|
+
? `${this.adapter.doSize(sz[0] || '')} ${this.adapter.doSize(sz[1] || '')}`
|
|
106
|
+
: t.backgroundSize
|
|
107
|
+
} else {
|
|
108
|
+
e.backgroundSize = 'cover'
|
|
109
|
+
}
|
|
110
|
+
e.backgroundRepeat = t.backgroundRepeat || 'no-repeat'
|
|
111
|
+
} else if (t.backgroundType == '03' && t.backgroundGradient) {
|
|
112
|
+
e.backgroundImage = t.backgroundGradient
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
parsePadding(t, e) {
|
|
117
|
+
if (t.paddingType == '0') {
|
|
118
|
+
e.padding = this.adapter.doSize(t.padding)
|
|
119
|
+
} else if (t.paddingType == '1') {
|
|
120
|
+
e.paddingLeft = this.adapter.doSize(t.paddingLeft)
|
|
121
|
+
e.paddingTop = this.adapter.doSize(t.paddingTop)
|
|
122
|
+
e.paddingRight = this.adapter.doSize(t.paddingRight)
|
|
123
|
+
e.paddingBottom = this.adapter.doSize(t.paddingBottom)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
parseMargin(t, e) {
|
|
128
|
+
if (t.marginType == '0') {
|
|
129
|
+
e.margin = this.adapter.doSize(t.margin)
|
|
130
|
+
} else if (t.marginType == '1') {
|
|
131
|
+
e.marginLeft = this.adapter.doSize(t.marginLeft)
|
|
132
|
+
e.marginTop = this.adapter.doSize(t.marginTop)
|
|
133
|
+
e.marginRight = this.adapter.doSize(t.marginRight)
|
|
134
|
+
e.marginBottom = this.adapter.doSize(t.marginBottom)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
parseBorderRadius(t, e) {
|
|
139
|
+
if (t.borderRadiusType == '0') {
|
|
140
|
+
e.borderRadius = this.adapter.doSize(t.borderRadius)
|
|
141
|
+
} else if (t.borderRadiusType == '1') {
|
|
142
|
+
e.borderTopLeftRadius = this.adapter.doSize(t.borderTopLeftRadius)
|
|
143
|
+
e.borderTopRightRadius = this.adapter.doSize(t.borderTopRightRadius)
|
|
144
|
+
e.borderBottomRightRadius = this.adapter.doSize(t.borderBottomRightRadius)
|
|
145
|
+
e.borderBottomLeftRadius = this.adapter.doSize(t.borderBottomLeftRadius)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
parseBorder(t, e) {
|
|
150
|
+
if (t.isBorder == '1') {
|
|
151
|
+
e.borderWidth = this.adapter.doSize(t.borderWidth || '1')
|
|
152
|
+
e.borderStyle = t.borderStyle || 'solid'
|
|
153
|
+
e.borderColor = this.adapter.doColor(t.borderColor || '#e7e7ee')
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
parseShadow(t, e) {
|
|
158
|
+
if (t.isShadow == '1') {
|
|
159
|
+
const parts = []
|
|
160
|
+
parts.push(this.adapter.doSize(t.shadowX || '0'))
|
|
161
|
+
parts.push(this.adapter.doSize(t.shadowY || '0'))
|
|
162
|
+
if (t.shadowBlur) parts.push(this.adapter.doSize(t.shadowBlur))
|
|
163
|
+
if (t.shadowSpread) parts.push(this.adapter.doSize(t.shadowSpread))
|
|
164
|
+
parts.push(this.adapter.doColor(t.shadowColor || '#e7e7ee'))
|
|
165
|
+
e.boxShadow = parts.join(' ')
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
parseTransform(t, e) {
|
|
170
|
+
const parts = []
|
|
171
|
+
if (t.rotate) parts.push(`rotate(${t.rotate}deg)`)
|
|
172
|
+
if (parts.length) e.transform = parts.join(' ')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
parseOther(t, e) {
|
|
176
|
+
e.overflow = t.overflow ? t.overflow : 'hidden'
|
|
177
|
+
if (t.opacity) e.opacity = `${Number(t.opacity) / 100}`
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 移植自官方 inner_card chunk 14 的模块 2191(adapter)。
|
|
3
|
+
*
|
|
4
|
+
* adapter 通过 Vue 的 provide/inject 下发给每个组件,组件里所有尺寸/颜色/URL
|
|
5
|
+
* 的换算都走它。这里逐行对齐原实现,只在 doReplaceUrl 上做了本地化:
|
|
6
|
+
* 官方是 `app.vm.$replaceUrl(t)`,实现在宿主 framework 里,是一张前缀别名表 +
|
|
7
|
+
* 全局正则替换。别名表取自 /ubomp-h5-sub/js/main.js。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// 前后分离后,站内资源统一经「本地开发服务(8787)」的资源代理回源 FinMall。
|
|
11
|
+
// 这里引入 resolveApiBase,把本地化出的站内路径改写为指向 8787 的绝对 URL,
|
|
12
|
+
// 修复部署态(静态编辑器 + 本机 8787,无 Vite)资源缺失的问题。
|
|
13
|
+
import { resolveApiBase } from '@/api.js'
|
|
14
|
+
|
|
15
|
+
/** 宿主 sub/main.js 里的 url.paths 别名表 */
|
|
16
|
+
export const URL_PREFIX_ALIAS = {
|
|
17
|
+
'@staticfile/': '/fastdfs/',
|
|
18
|
+
'@filestore/': '/minio/',
|
|
19
|
+
'@minio/': '/minio/',
|
|
20
|
+
'@floorstatic/': '/floorstatic/',
|
|
21
|
+
'@flamestore/': '/',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 卡片 data.json 里有不少写死的完整 CDN 地址,直接请求会跨域也吃不到本地缓存。
|
|
26
|
+
* 统一改写成同源路径,交给 vite 的资源代理中间件。
|
|
27
|
+
*
|
|
28
|
+
* 域名不硬编码:从 .env.finmall 的 FINMALL_BASE_URL 读取(vite 以
|
|
29
|
+
* envPrefix FINMALL_ 暴露到 import.meta.env),未配置时回退 UAT 域名。
|
|
30
|
+
*/
|
|
31
|
+
const BASE_URL = import.meta.env.FINMALL_BASE_URL || 'https://uat-pkg.finmall.com'
|
|
32
|
+
const BASE_HOST = BASE_URL.replace(/^https?:/, '') // '//uat-pkg.finmall.com'
|
|
33
|
+
const ABSOLUTE_ASSET_HOSTS = [...new Set([
|
|
34
|
+
BASE_URL,
|
|
35
|
+
BASE_URL.replace(/^https:/, 'http:'),
|
|
36
|
+
BASE_HOST,
|
|
37
|
+
])]
|
|
38
|
+
|
|
39
|
+
export function toLocalAssetUrl(url) {
|
|
40
|
+
if (typeof url !== 'string' || !url) return url
|
|
41
|
+
for (const host of ABSOLUTE_ASSET_HOSTS) {
|
|
42
|
+
if (url.startsWith(host)) return url.slice(host.length) || '/'
|
|
43
|
+
}
|
|
44
|
+
return url
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default class Adapter {
|
|
48
|
+
constructor(options = {}) {
|
|
49
|
+
// availableWidth 非 0 时 doSize 会按 availableWidth/styleWidth 整体缩放。
|
|
50
|
+
// 官方 web 端固定给 0(即 1:1 使用设计稿 px),这里保留可注入以备后用。
|
|
51
|
+
this.options = {
|
|
52
|
+
availableWidth: 0,
|
|
53
|
+
applyUnit: 'px',
|
|
54
|
+
applyUnitRatio: 1,
|
|
55
|
+
fontRatio: 1,
|
|
56
|
+
styleWidth: 375,
|
|
57
|
+
// 独立渲染器可显式传入空串,使站内资源(如 /slices/*)保持同源相对路径,
|
|
58
|
+
// 不强制走开发服务 8787。undefined 时维持既有 resolveApiBase() 行为。
|
|
59
|
+
apiBase: undefined,
|
|
60
|
+
...options,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getSystemInfo() {
|
|
65
|
+
const o = this.options
|
|
66
|
+
return {
|
|
67
|
+
screenWidth: typeof window !== 'undefined' ? window.innerWidth : o.styleWidth,
|
|
68
|
+
availableWidth: o.availableWidth,
|
|
69
|
+
applyUnit: o.applyUnit,
|
|
70
|
+
applyUnitRatio: o.applyUnitRatio,
|
|
71
|
+
fontRatio: o.fontRatio,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getDesignInfo() {
|
|
76
|
+
return { styleWidth: this.options.styleWidth }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 资源地址归一。规则:
|
|
81
|
+
* - `http(s)://` 开头的绝对地址:视为真实远程资源,**原样保留**,不做任何别名替换 / host 截取。
|
|
82
|
+
* - `@` 开头的站内别名(如 `@filestore/x`):走 URL_PREFIX_ALIAS 替换 + 绝对 host 截取,
|
|
83
|
+
* 转成同源站内路径,交给本地资源代理回源。
|
|
84
|
+
*
|
|
85
|
+
* 历史实现里 toLocalAssetUrl 对所有入参(含 http 图链)都截取 FinMall 等 host,导致
|
|
86
|
+
* 背景图 / 图片一旦命中这些 host 就被剥掉域名、改走本地代理,与「远程地址原样直连」的预期不符。
|
|
87
|
+
*/
|
|
88
|
+
doReplaceUrl(url) {
|
|
89
|
+
if (typeof url !== 'string') return url
|
|
90
|
+
// http(s) 绝对地址:真实远程资源,原样返回,不做任何处理
|
|
91
|
+
if (/^https?:\/\//i.test(url)) return url
|
|
92
|
+
let out = url
|
|
93
|
+
try {
|
|
94
|
+
for (const key of Object.keys(URL_PREFIX_ALIAS)) {
|
|
95
|
+
out = out.replace(new RegExp(key, 'g'), URL_PREFIX_ALIAS[key])
|
|
96
|
+
}
|
|
97
|
+
} catch (e) {
|
|
98
|
+
/* 与官方一致:吞掉异常返回原值 */
|
|
99
|
+
}
|
|
100
|
+
const local = toLocalAssetUrl(out)
|
|
101
|
+
// 前后分离:站内资源改写为指向「本地开发服务(8787)」资源代理的绝对 URL,
|
|
102
|
+
// 这样部署态(静态编辑器,无 Vite)也能正常取图;DEV 下 apiBase 默认 8787,
|
|
103
|
+
// 与部署态同源,预览一致。无 apiBase(部署态且未配置开发服务)时回退相对路径,
|
|
104
|
+
// 由上层提示用户配置(与 resolveApiBase 的语义保持一致)。
|
|
105
|
+
const base =
|
|
106
|
+
this.options.apiBase !== undefined ? this.options.apiBase : resolveApiBase()
|
|
107
|
+
if (base && typeof local === 'string' && local.startsWith('/')) {
|
|
108
|
+
return base + local
|
|
109
|
+
}
|
|
110
|
+
return local
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 裸数字按设计稿 px 处理。
|
|
115
|
+
* - 空值且不是 0 -> ""
|
|
116
|
+
* - "auto" 或含 "%" -> 原样返回
|
|
117
|
+
* - 其余剥离非数字字符后拼单位
|
|
118
|
+
*/
|
|
119
|
+
doSize(value, kind) {
|
|
120
|
+
if (!value && value !== 0) return ''
|
|
121
|
+
if (value === 'auto' || String(value).indexOf('%') > -1) return value
|
|
122
|
+
const sys = this.getSystemInfo() || {}
|
|
123
|
+
const design = this.getDesignInfo() || {}
|
|
124
|
+
const num = String(value).replace(/[^0-9\-.]/g, '')
|
|
125
|
+
let ratio = 1
|
|
126
|
+
if (sys.availableWidth) {
|
|
127
|
+
if (kind === 'font') ratio = Number(sys.fontRatio || 1)
|
|
128
|
+
const scale = parseFloat(sys.availableWidth) / parseFloat(design.styleWidth)
|
|
129
|
+
return parseFloat(num) * scale * ratio + sys.applyUnit
|
|
130
|
+
}
|
|
131
|
+
return num + sys.applyUnit
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* `#AARRGGBB` 八位色值:首字节是「不透明度百分比」的十六进制,
|
|
136
|
+
* 例如 #64FF0000 -> alpha = 0x64/100 = 1。其余格式原样返回。
|
|
137
|
+
*/
|
|
138
|
+
doColor(value = '') {
|
|
139
|
+
const v = String(value)
|
|
140
|
+
if (/^#[a-fA-F0-9]{8}$/.test(v)) {
|
|
141
|
+
const [a, r, g, b] = v.substring(1).match(/[a-fA-F0-9]{2}/g)
|
|
142
|
+
return `rgba(${parseInt(r, 16)}, ${parseInt(g, 16)}, ${parseInt(b, 16)}, ${
|
|
143
|
+
parseInt(a, 16) / 100
|
|
144
|
+
})`
|
|
145
|
+
}
|
|
146
|
+
return v
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
doRemoveUnit(value = '') {
|
|
150
|
+
return String(value).replace(/[^0-9.]/g, '')
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
doApplyUnit(value) {
|
|
154
|
+
return value + (this.getSystemInfo() || {}).applyUnit
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** 官方就是原样返回,没有实际的国际化 */
|
|
158
|
+
doI18n(value) {
|
|
159
|
+
return value
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** 官方返回空对象;预览里由 ynet mock 接管 */
|
|
163
|
+
async doRpc() {
|
|
164
|
+
return {}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 弹窗卡片(alertCard / closeAlertCard)运行时,对齐 FCard.alertCard 语义:
|
|
3
|
+
* alertCard({cardCode,data,gravity,width,height,offsetX,offsetY,clickOutsideCanHide},cb) 弹另一张卡,
|
|
4
|
+
* cb 在关闭时触发(res.data.event==='hidden',携带 closeAlertCard 传入的 data)。
|
|
5
|
+
* 设计要点:①弹窗堆栈挂 alertStore.stack(对象 getter 走 __ob__.dep,Vue2 才能侦测 push/索引访问),
|
|
6
|
+
* 消费方务必用 alertStore.stack;②每张弹窗卡用「自身 data.json ∪ 传入 data」,AlertCardInstance
|
|
7
|
+
* 提供独立 cardRuntime,this.data/this.state/setData 指向自身作用域。
|
|
8
|
+
*/
|
|
9
|
+
import { reactive } from 'vue'
|
|
10
|
+
import { buildPreview } from '@/runtime/toPreview.js'
|
|
11
|
+
import { apiFetch } from '@/api.js'
|
|
12
|
+
|
|
13
|
+
// 弹窗卡片堆栈(响应式),从栈顶(数组末尾)渲染 / 关闭
|
|
14
|
+
export const alertStore = reactive({ stack: [] })
|
|
15
|
+
let seq = 0
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 打开一张弹窗卡片。
|
|
19
|
+
* @param {object} params
|
|
20
|
+
* @param {function} [callback] 弹窗关闭回调,res = { success, data:{ event:'hidden', ...closeData } }
|
|
21
|
+
*/
|
|
22
|
+
export async function openAlertCard(params, callback) {
|
|
23
|
+
const p = params || {}
|
|
24
|
+
const cardCode = p.cardCode
|
|
25
|
+
// 实时从磁盘读取该卡片,保证弹窗卡片始终是最新落盘版本。
|
|
26
|
+
let card = null
|
|
27
|
+
try {
|
|
28
|
+
const res = await apiFetch(`/__card/${encodeURIComponent(cardCode)}`)
|
|
29
|
+
if (res.ok) {
|
|
30
|
+
const c = await res.json()
|
|
31
|
+
if (c && c.schema) card = c
|
|
32
|
+
}
|
|
33
|
+
} catch (_) {
|
|
34
|
+
/* 读取失败按未找到处理 */
|
|
35
|
+
}
|
|
36
|
+
if (!card || !card.schema) {
|
|
37
|
+
if (typeof callback === 'function') {
|
|
38
|
+
callback({ success: false, error: 'card_not_found', errorMessage: '未找到卡片 ' + cardCode })
|
|
39
|
+
}
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
// 对齐官方行为:重复打开 alertCard 时先关闭已有弹窗(避免多个叠加),
|
|
43
|
+
// 触发已有弹窗的回调(event: 'hidden'),但不传额外 closeData。
|
|
44
|
+
const stack = alertStore.stack
|
|
45
|
+
if (stack.length) {
|
|
46
|
+
const old = stack.splice(0, stack.length)
|
|
47
|
+
old.forEach((entry) => {
|
|
48
|
+
if (typeof entry.callback === 'function') {
|
|
49
|
+
try { entry.callback({ success: true, data: { event: 'hidden' } }) } catch (_) { /* ignore */ }
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
// 数据绑定:卡片自带 data.json 作基底,再用传入 data 覆盖
|
|
54
|
+
const ownData = card.data && typeof card.data === 'object' ? card.data : {}
|
|
55
|
+
const scope = reactive(Object.assign({}, ownData, p.data || {}))
|
|
56
|
+
const preview = buildPreview(card.schema)
|
|
57
|
+
const id = 'alert-' + ++seq
|
|
58
|
+
stack.push({
|
|
59
|
+
id,
|
|
60
|
+
cardCode,
|
|
61
|
+
preview,
|
|
62
|
+
scope,
|
|
63
|
+
gravity: p.gravity || 'bottom',
|
|
64
|
+
width: typeof p.width === 'number' ? p.width : undefined,
|
|
65
|
+
height: typeof p.height === 'number' ? p.height : undefined,
|
|
66
|
+
offsetX: typeof p.offsetX === 'number' ? p.offsetX : 0,
|
|
67
|
+
offsetY: typeof p.offsetY === 'number' ? p.offsetY : 0,
|
|
68
|
+
clickOutsideCanHide: p.clickOutsideCanHide !== false,
|
|
69
|
+
callback,
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 关闭弹窗卡片,并把回传数据交给对应 alertCard 的回调。
|
|
75
|
+
* @param {object} params { cardCode?, index?, data? }
|
|
76
|
+
* @param {function} [callback] closeAlertCard 自身的回调
|
|
77
|
+
*/
|
|
78
|
+
export function closeAlertCard(params, callback) {
|
|
79
|
+
const p = params || {}
|
|
80
|
+
const stack = alertStore.stack
|
|
81
|
+
let target = null
|
|
82
|
+
if (typeof p.index === 'number') {
|
|
83
|
+
const n = Math.abs(p.index)
|
|
84
|
+
if (stack.length >= n) target = stack[stack.length - n]
|
|
85
|
+
} else if (p.cardCode) {
|
|
86
|
+
// 从栈顶往下找到第一个匹配 cardCode 的弹窗
|
|
87
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
88
|
+
if (stack[i].cardCode === p.cardCode) {
|
|
89
|
+
target = stack[i]
|
|
90
|
+
break
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
target = stack[stack.length - 1] // 默认关闭栈顶
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!target) {
|
|
98
|
+
if (typeof callback === 'function') {
|
|
99
|
+
callback({ success: false, error: 'no_alert', errorMessage: '没有可关闭的弹窗' })
|
|
100
|
+
}
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const idx = stack.indexOf(target)
|
|
105
|
+
stack.splice(idx, 1)
|
|
106
|
+
|
|
107
|
+
// 回传参数:closeAlertCard 的 data 作为 alertCard 回调的参数。
|
|
108
|
+
// 仅当显式传入 data 时,才附加 event:'hidden' 关闭标记;未传 data 时不返回多余的 { event:'hidden' }
|
|
109
|
+
if (typeof target.callback === 'function') {
|
|
110
|
+
if (p.data) {
|
|
111
|
+
target.callback({ success: true, data: Object.assign({ event: 'hidden' }, p.data) })
|
|
112
|
+
} else {
|
|
113
|
+
target.callback({ success: true, data: {} })
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (typeof callback === 'function') {
|
|
117
|
+
callback({ success: true })
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** 关闭全部弹窗(切换主卡片时清理,避免残留弹窗引用旧上下文)。静默清空,不触发回调。 */
|
|
122
|
+
export function clearAllAlerts() {
|
|
123
|
+
const stack = alertStore.stack
|
|
124
|
+
if (stack.length) stack.splice(0, stack.length)
|
|
125
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App 级弱提示(toast)单例。
|
|
3
|
+
*
|
|
4
|
+
* 与「设备框内的 showToast(deviceUI.toast,由卡片 ynet.callJSAPI 触发)」区分:
|
|
5
|
+
* 这里是**顶栏异步操作(远端保存 / 同步)**完成后的反馈,应浮在 App 外壳层级、
|
|
6
|
+
* 不被手机预览框裁切。组件 `AppToast.vue` 读取本响应式对象并自动消失。
|
|
7
|
+
*/
|
|
8
|
+
import { reactive } from 'vue'
|
|
9
|
+
|
|
10
|
+
export const appToast = reactive({
|
|
11
|
+
visible: false,
|
|
12
|
+
message: '',
|
|
13
|
+
type: 'ok', // 'ok' | 'err'
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
let appToastTimer = null
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 弹出一个 App 级消息提示,duration 毫秒后自动消失(重复调用会重置计时)。
|
|
20
|
+
* @param {string} message 提示文案
|
|
21
|
+
* @param {'ok'|'err'} [type='ok'] 类型:成功 / 失败(决定图标与配色)
|
|
22
|
+
* @param {number} [duration=2400] 自动消失时长(ms)
|
|
23
|
+
*/
|
|
24
|
+
export function showAppToast(message, type = 'ok', duration = 2400) {
|
|
25
|
+
appToast.message = message
|
|
26
|
+
appToast.type = type
|
|
27
|
+
appToast.visible = true
|
|
28
|
+
if (appToastTimer) clearTimeout(appToastTimer)
|
|
29
|
+
appToastTimer = setTimeout(() => {
|
|
30
|
+
appToast.visible = false
|
|
31
|
+
appToastTimer = null
|
|
32
|
+
}, duration)
|
|
33
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 官方静态资产在预览工程里的落点。
|
|
3
|
+
*
|
|
4
|
+
* 通过 import 引入,交由 Vite 统一处理(hash 命名 + 自动套 base),
|
|
5
|
+
* 这样无论部署在根路径('/')还是子路径(如 '/cardBuddy/'),
|
|
6
|
+
* import.meta.env.BASE_URL 都会让 URL 解析正确,无需手写绝对路径。
|
|
7
|
+
*
|
|
8
|
+
* 对应关系(构建后):
|
|
9
|
+
* svg-symbols.svg -> <base>assets/svg-symbols.<hash>.svg (Icon 的 sprite)
|
|
10
|
+
* default-image.png -> <base>assets/default-image.<hash>.png(Image 的兜底图)
|
|
11
|
+
*/
|
|
12
|
+
import svgSymbolsUrl from '../assets/svg-symbols.svg'
|
|
13
|
+
import defaultImageUrl from '../assets/default-image.png'
|
|
14
|
+
|
|
15
|
+
export const SVG_SYMBOLS_URL = svgSymbolsUrl
|
|
16
|
+
export const DEFAULT_IMAGE_URL = defaultImageUrl
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 移植自官方 inner_card chunk 4 的模块 1987 / 1988 / 1989。
|
|
3
|
+
*
|
|
4
|
+
* Canvas 组件不会把原生 <canvas> 直接交给业务代码,而是包一层:
|
|
5
|
+
* - `canvas.width/height` 换算成「设计稿 px」(除以 scale)
|
|
6
|
+
* - `canvas.getContext("2d")` 返回一个代理 ctx,所有坐标 / 尺寸乘以 scale,
|
|
7
|
+
* 字号再额外乘 fontRatio,fillText 的文本过一遍 doI18n
|
|
8
|
+
*
|
|
9
|
+
* 卡片里的 onMeasured 自定义脚本拿到的就是这个包装对象,因此必须复刻,
|
|
10
|
+
* 否则 `event.canvas.width` 之类的读数会差一个 devicePixelRatio。
|
|
11
|
+
*
|
|
12
|
+
* 我们的 adapter 默认 availableWidth=0,算出来 scale=1、fontRatio=1,
|
|
13
|
+
* 此时代理等价于透传;保留完整实现是为了将来注入真实机型宽度时仍然对齐。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** 直通属性(get/set 原样转发) */
|
|
17
|
+
function passthrough(key) {
|
|
18
|
+
return (ctx) => ({
|
|
19
|
+
get: () => ctx[key],
|
|
20
|
+
set: (v) => {
|
|
21
|
+
ctx[key] = v
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 直通方法(bind 后返回) */
|
|
27
|
+
function passthroughFn(key) {
|
|
28
|
+
return (ctx) => ({
|
|
29
|
+
get: () => ctx[key].bind(ctx),
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 按下标缩放入参的方法 */
|
|
34
|
+
function scaledFn(key, indexes = []) {
|
|
35
|
+
return (ctx, scale) => ({
|
|
36
|
+
get:
|
|
37
|
+
() =>
|
|
38
|
+
(...args) => {
|
|
39
|
+
indexes.forEach((i) => {
|
|
40
|
+
args[i] = Number(args[i]) * scale
|
|
41
|
+
})
|
|
42
|
+
return ctx[key](...args)
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const DESCRIPTORS = {
|
|
48
|
+
textAlign: passthrough('textAlign'),
|
|
49
|
+
textBaseline: passthrough('textBaseline'),
|
|
50
|
+
fillStyle: passthrough('fillStyle'),
|
|
51
|
+
strokeStyle: passthrough('strokeStyle'),
|
|
52
|
+
lineCap: passthrough('lineCap'),
|
|
53
|
+
imageSmoothingEnabled: passthrough('imageSmoothingEnabled'),
|
|
54
|
+
imageSmoothingQuality: passthrough('imageSmoothingQuality'),
|
|
55
|
+
|
|
56
|
+
lineWidth: (ctx, scale) => ({
|
|
57
|
+
get: () => ctx.lineWidth,
|
|
58
|
+
set: (v) => {
|
|
59
|
+
ctx.lineWidth = v * scale
|
|
60
|
+
},
|
|
61
|
+
}),
|
|
62
|
+
|
|
63
|
+
font: (ctx, scale, fontRatio) => ({
|
|
64
|
+
get: () => ctx.font,
|
|
65
|
+
set: (v) => {
|
|
66
|
+
ctx.font = String(v).replace(
|
|
67
|
+
/([0-9.]+)px/,
|
|
68
|
+
(_, num) => Number(num) * scale * Number(fontRatio || 1) + 'px'
|
|
69
|
+
)
|
|
70
|
+
},
|
|
71
|
+
}),
|
|
72
|
+
|
|
73
|
+
fillText: (ctx, scale, fontRatio, vm) => ({
|
|
74
|
+
get:
|
|
75
|
+
() =>
|
|
76
|
+
(...args) => {
|
|
77
|
+
args[0] = args[0] ? vm.adapter.doI18n(args[0]) : args[0]
|
|
78
|
+
;[1, 2].forEach((i) => {
|
|
79
|
+
args[i] = Number(args[i]) * scale
|
|
80
|
+
})
|
|
81
|
+
return ctx.fillText(...args)
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
|
|
85
|
+
fillRect: scaledFn('fillRect', [0, 1, 2, 3]),
|
|
86
|
+
moveTo: scaledFn('moveTo', [0, 1]),
|
|
87
|
+
lineTo: scaledFn('lineTo', [0, 1]),
|
|
88
|
+
arc: scaledFn('arc', [0, 1, 2]),
|
|
89
|
+
clearRect: scaledFn('clearRect', [0, 1, 2, 3]),
|
|
90
|
+
createLinearGradient: scaledFn('createLinearGradient', [0, 1, 2, 3]),
|
|
91
|
+
createRadialGradient: scaledFn('createRadialGradient', [0, 1, 2, 3, 4, 5]),
|
|
92
|
+
|
|
93
|
+
beginPath: passthroughFn('beginPath'),
|
|
94
|
+
closePath: passthroughFn('closePath'),
|
|
95
|
+
fill: passthroughFn('fill'),
|
|
96
|
+
stroke: passthroughFn('stroke'),
|
|
97
|
+
|
|
98
|
+
/** 量文本时先把字号还原成设计稿尺寸,量完再改回去 */
|
|
99
|
+
measureText: (ctx, scale) => ({
|
|
100
|
+
get:
|
|
101
|
+
() =>
|
|
102
|
+
(...args) => {
|
|
103
|
+
const font = ctx.font
|
|
104
|
+
ctx.font = font.replace(/([0-9.]+)px/, (_, num) => Number(num) / scale + 'px')
|
|
105
|
+
const metrics = ctx.measureText(...args)
|
|
106
|
+
ctx.font = font
|
|
107
|
+
return metrics
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
|
|
111
|
+
setLineDash: (ctx) => ({
|
|
112
|
+
get: () => ctx.setLineDash.bind(ctx),
|
|
113
|
+
set: (v) => ctx.setLineDash(v),
|
|
114
|
+
}),
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** 官方 module 1988:给原生 ctx 造一个「按 scale 换算」的代理类 */
|
|
118
|
+
export function makeContextProxyClass(nativeCtx, vm) {
|
|
119
|
+
function Proxied(scale, fontRatio) {
|
|
120
|
+
Object.keys(DESCRIPTORS).forEach((key) => {
|
|
121
|
+
Object.defineProperty(this, key, DESCRIPTORS[key](nativeCtx, scale, fontRatio, vm))
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
// 原型指向原生 ctx,未代理的 API 直接落到原生实现上
|
|
125
|
+
Proxied.prototype = nativeCtx
|
|
126
|
+
return Proxied
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** 官方 module 1987:给 <canvas> 元素造一个「设计稿坐标系」的包装类 */
|
|
130
|
+
export function makeCanvasWrapperClass(canvasEl, vm) {
|
|
131
|
+
return function CanvasWrapper(rect, scale, fontRatio) {
|
|
132
|
+
this.width = rect.width / scale
|
|
133
|
+
this.height = rect.height / scale
|
|
134
|
+
this.style = canvasEl.style
|
|
135
|
+
this.getContext = function (...args) {
|
|
136
|
+
const nativeCtx = canvasEl.getContext(...args)
|
|
137
|
+
const Proxied = makeContextProxyClass(nativeCtx, vm)
|
|
138
|
+
return new Proxied(scale, fontRatio)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|