@mixd-id/web-scaffold 0.2.250801013 → 0.2.260801002
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 +25 -1
- package/docs/components/AIDashboard.md +557 -0
- package/docs/components/Alert.md +3 -1
- package/docs/components/Card.md +5 -0
- package/docs/components/PivotList.md +207 -0
- package/docs/components/Textbox.md +20 -1
- package/docs/components/Tooltip.md +148 -0
- package/docs/components/WebPageBuilder.md +125 -0
- package/package.json +22 -89
- package/src/components/AIChat.vue +488 -0
- package/src/components/AIChatItem.vue +199 -0
- package/src/components/AIChatTextbox.vue +56 -0
- package/src/components/AIDashboard.vue +1159 -0
- package/src/components/Alert.vue +58 -35
- package/src/components/Card.vue +58 -7
- package/src/components/Carousel.vue +1 -1
- package/src/{widgets → components}/CartSetting.vue +1 -0
- package/src/components/Chart.vue +4 -3
- package/src/components/Checkbox.vue +22 -24
- package/src/components/CodeEditor.vue +123 -13
- package/src/{widgets → components}/CollapsiblePanelSetting.vue +1 -0
- package/src/components/ColorPicker.vue +8 -4
- package/src/components/ColumnSelector.vue +93 -0
- package/src/{widgets → components}/ComponentSetting2.vue +1 -1
- package/src/components/Confirm.vue +2 -0
- package/src/{widgets → components}/ContactForm.vue +1 -1
- package/src/components/CopyToClipboard.vue +0 -1
- package/src/components/Dashboard.vue +493 -212
- package/src/components/Datepicker.vue +2 -0
- package/src/components/Datetimepicker.vue +1 -1
- package/src/components/Dropdown.vue +1 -2
- package/src/{widgets → components}/FeatureList.vue +1 -1
- package/src/components/Gmaps.vue +1 -1
- package/src/components/HTMLEditor.vue +9 -1
- package/src/{widgets → components}/IconList.vue +1 -1
- package/src/components/Image.vue +1 -1
- package/src/components/ImagePreview.vue +32 -51
- package/src/components/LLMChat.vue +327 -0
- package/src/components/List.vue +267 -62
- package/src/components/MarkdownEdit.vue +124 -5
- package/src/components/MarkdownPreview.vue +4 -1
- package/src/{widgets → components}/MenuItem1Setting.vue +1 -0
- package/src/components/Metric.vue +30 -7
- package/src/components/MultiDropdown.vue +3 -1
- package/src/{widgets → components}/MultiValueSetting.vue +2 -2
- package/src/{widgets → components}/MultiValueSetting2.vue +2 -2
- package/src/components/OTPField.vue +3 -1
- package/src/components/PivotList.vue +1239 -0
- package/src/{widgets → components}/PresetBar.vue +32 -9
- package/src/{widgets → components}/PresetBarPivot.vue +147 -15
- package/src/{widgets → components}/PresetBarPivotColumnEdit.vue +8 -19
- package/src/components/PresetBarPivotValueEdit.vue +172 -0
- package/src/{widgets → components}/PresetSelector.vue +8 -4
- package/src/components/PresetSelectorFilterItem.vue +2 -1
- package/src/components/Radio.vue +1 -1
- package/src/{widgets → components}/Share.vue +1 -1
- package/src/components/SvgEditor.vue +1 -1
- package/src/components/Switch.vue +1 -1
- package/src/components/TextWithTag.vue +5 -5
- package/src/components/Textbox.vue +63 -5
- package/src/components/Toast.vue +2 -1
- package/src/components/Tooltip.vue +579 -96
- package/src/components/TreeView.vue +1 -1
- package/src/components/VirtualGrid.vue +2 -2
- package/src/components/VirtualTable.vue +170 -78
- package/src/{widgets → components}/WebDatasourceSelector.vue +2 -0
- package/src/{widgets → components}/WebLayoutSelector.vue +1 -1
- package/src/{widgets → components}/WebPageBuilder.vue +153 -352
- package/src/directives/tooltip.js +131 -0
- package/src/index.js +11 -287
- package/src/instructions/dashboard.js +203 -0
- package/src/themes/default/index.js +16 -0
- package/src/utils/ai-chat-answer.mjs +240 -0
- package/src/utils/ai-chat.js +167 -0
- package/src/utils/ai-dashboard-chat.mjs +371 -0
- package/src/utils/ai-dashboard.js +446 -0
- package/src/utils/ai-list-chat.mjs +407 -0
- package/src/utils/dashboard.js +13 -0
- package/src/utils/helpers.mjs +52 -5
- package/src/utils/list.mjs +150 -92
- package/src/utils/pivot-formula.mjs +358 -0
- package/src/utils/pivot-list.mjs +557 -0
- package/src/utils/preset-selector.mjs +183 -27
- package/src/utils/tooltip.mjs +243 -0
- package/src/utils/wss.js +26 -8
- package/src/utils/wss.mjs +29 -2
- package/docs/components/ArrayList.md +0 -39
- package/docs/components/Box.md +0 -15
- package/docs/components/ChartBar.md +0 -34
- package/docs/components/DayTimeRange.md +0 -20
- package/docs/components/ErrorText.md +0 -15
- package/docs/components/FBLogin.md +0 -21
- package/docs/components/GHeatMaps.md +0 -16
- package/docs/components/GSignIn.md +0 -21
- package/docs/components/GmapsDirection.md +0 -20
- package/docs/components/IconMenu.md +0 -20
- package/src/components/ArrayList.vue +0 -49
- package/src/components/Box.vue +0 -34
- package/src/components/ChartBar.vue +0 -192
- package/src/components/DayTimeRange.vue +0 -126
- package/src/components/ErrorText.vue +0 -57
- package/src/components/FBLogin.vue +0 -194
- package/src/components/GHeatMaps.vue +0 -317
- package/src/components/GSignIn.vue +0 -166
- package/src/components/GmapsDirection.vue +0 -191
- package/src/components/IconMenu.vue +0 -95
- package/src/components/IconPlus.vue +0 -89
- package/src/components/Image360.vue +0 -140
- package/src/components/ImageFullScreen.vue +0 -101
- package/src/components/ImportModal.vue +0 -260
- package/src/components/ListContextMenu.vue +0 -88
- package/src/components/LogViewerItem.vue +0 -116
- package/src/components/MultilineText.vue +0 -174
- package/src/components/PageBuilder.vue +0 -449
- package/src/components/SplitPane.vue +0 -129
- package/src/components/TabView.vue +0 -105
- package/src/components/TableView.vue +0 -200
- package/src/components/TableViewHead.vue +0 -159
- package/src/components/Test.vue +0 -23
- package/src/components/TextEditor.vue +0 -85
- package/src/components/TreeViewItem1.vue +0 -282
- package/src/components/VirtualScroll.vue +0 -282
- package/src/configs/dashboard/bar.js +0 -10
- package/src/configs/dashboard/collection-1.js +0 -5
- package/src/configs/dashboard/data-table.js +0 -9
- package/src/configs/dashboard/doughnut.js +0 -7
- package/src/configs/dashboard/gheatmaps.js +0 -9
- package/src/configs/dashboard/grid-2.js +0 -34
- package/src/configs/dashboard/grid-3.js +0 -34
- package/src/configs/dashboard/grid-4.js +0 -34
- package/src/configs/dashboard/grid.js +0 -15
- package/src/configs/dashboard/metric.js +0 -10
- package/src/configs/dashboard/pie.js +0 -7
- package/src/configs/dashboard/polar-area.js +0 -7
- package/src/configs/dashboard/virtual-table.js +0 -9
- package/src/configs/icons-fa.json +0 -12
- package/src/middleware/http/trim-string.js +0 -20
- package/src/mixin/ready-state.js +0 -37
- package/src/stores/components.js +0 -36
- package/src/stores/datasource.js +0 -11
- package/src/utils/event-bus.js +0 -8
- package/src/utils/helpers.cjs +0 -635
- package/src/utils/importer.js +0 -209
- package/src/utils/listview.js +0 -1287
- package/src/utils/preset-selector.cjs +0 -1455
- package/src/utils/preset-selector.js +0 -1452
- package/src/utils/queue.js +0 -63
- package/src/utils/web.js +0 -50
- package/src/utils/web.mjs +0 -120
- package/src/widgets/BackgroundSetting.vue +0 -132
- package/src/widgets/BorderSetting.vue +0 -201
- package/src/widgets/BotEditor/BotEditorActions.vue +0 -163
- package/src/widgets/BotEditor/BotEditorSettings.vue +0 -21
- package/src/widgets/BotEditor.vue +0 -248
- package/src/widgets/BoxSetting.vue +0 -53
- package/src/widgets/CheckoutSetting.vue +0 -46
- package/src/widgets/ColumnSelector.vue +0 -104
- package/src/widgets/ComponentPropertyLabel.vue +0 -35
- package/src/widgets/ComponentPropertySelector.vue +0 -115
- package/src/widgets/ComponentSetting3.vue +0 -231
- package/src/widgets/EffectSetting.vue +0 -94
- package/src/widgets/FiltersSetting.vue +0 -88
- package/src/widgets/Header.vue +0 -15
- package/src/widgets/Header0.vue +0 -41
- package/src/widgets/Header0Setting.vue +0 -75
- package/src/widgets/HeaderSetting.vue +0 -102
- package/src/widgets/LayoutSetting.vue +0 -251
- package/src/widgets/LogViewer.vue +0 -85
- package/src/widgets/MarginPaddingSetting.vue +0 -58
- package/src/widgets/MarginSetting.vue +0 -142
- package/src/widgets/MenuEditor.vue +0 -251
- package/src/widgets/PaddingSetting.vue +0 -142
- package/src/widgets/SizingSetting.vue +0 -306
- package/src/widgets/SpacingSetting.vue +0 -77
- package/src/widgets/StyleSetting.vue +0 -543
- package/src/widgets/StyleSetting2.vue +0 -90
- package/src/widgets/TransitionSetting.vue +0 -64
- package/src/widgets/TypographySetting.vue +0 -334
- package/src/widgets/UserActionBuilder/UserActionCondition.vue +0 -99
- package/src/widgets/UserActionBuilder/UserActionConsole.vue +0 -97
- package/src/widgets/UserActionBuilder/UserActionItem.vue +0 -273
- package/src/widgets/UserActionBuilder/UserActionOutput.vue +0 -150
- package/src/widgets/UserActionBuilder/UserActionOutputDelay.vue +0 -27
- package/src/widgets/UserActionBuilder/UserActionOutputLog.vue +0 -28
- package/src/widgets/UserActionBuilder/UserActionOutputReply.vue +0 -112
- package/src/widgets/UserActionBuilder/UserActionProps.vue +0 -213
- package/src/widgets/UserActionBuilder.vue +0 -314
- package/src/widgets/WebPageBuilder4/BackgroundColorSetting.vue +0 -54
- package/src/widgets/WebPageBuilder4/BackgroundPositionSetting.vue +0 -75
- package/src/widgets/WebPageBuilder4/BackgroundRepeatSetting.vue +0 -72
- package/src/widgets/WebPageBuilder4/BackgroundSizeSetting.vue +0 -72
- package/src/widgets/WebPageBuilder4/BlurSetting.vue +0 -72
- package/src/widgets/WebPageBuilder4/BorderColorSetting.vue +0 -54
- package/src/widgets/WebPageBuilder4/BorderRadiusSetting.vue +0 -75
- package/src/widgets/WebPageBuilder4/BorderSizeSetting.vue +0 -69
- package/src/widgets/WebPageBuilder4/BorderStyleSetting.vue +0 -72
- package/src/widgets/WebPageBuilder4/BoxShadowSetting.vue +0 -73
- package/src/widgets/WebPageBuilder4/ButtonSetting.vue +0 -65
- package/src/widgets/WebPageBuilder4/CarouselSetting.vue +0 -178
- package/src/widgets/WebPageBuilder4/DisplaySetting.vue +0 -76
- package/src/widgets/WebPageBuilder4/FlexAlignSetting.vue +0 -71
- package/src/widgets/WebPageBuilder4/FlexDirectionSetting.vue +0 -70
- package/src/widgets/WebPageBuilder4/FlexJustifySetting.vue +0 -74
- package/src/widgets/WebPageBuilder4/FlexPropertySetting.vue +0 -71
- package/src/widgets/WebPageBuilder4/FlexSetting.vue +0 -96
- package/src/widgets/WebPageBuilder4/FlexWrapSetting.vue +0 -69
- package/src/widgets/WebPageBuilder4/GapSetting.vue +0 -75
- package/src/widgets/WebPageBuilder4/GrayscaleSetting.vue +0 -68
- package/src/widgets/WebPageBuilder4/GridSetting.vue +0 -216
- package/src/widgets/WebPageBuilder4/HeightSetting.vue +0 -98
- package/src/widgets/WebPageBuilder4/ImageSetting.vue +0 -102
- package/src/widgets/WebPageBuilder4/MarginSetting.vue +0 -73
- package/src/widgets/WebPageBuilder4/MaxHeightSetting.vue +0 -82
- package/src/widgets/WebPageBuilder4/MaxWidthSetting.vue +0 -90
- package/src/widgets/WebPageBuilder4/MinHeightSetting.vue +0 -81
- package/src/widgets/WebPageBuilder4/MinWidthSetting.vue +0 -101
- package/src/widgets/WebPageBuilder4/MultiValueSetting.vue +0 -163
- package/src/widgets/WebPageBuilder4/OpacitySetting.vue +0 -77
- package/src/widgets/WebPageBuilder4/OverflowSetting.vue +0 -81
- package/src/widgets/WebPageBuilder4/PaddingSetting.vue +0 -57
- package/src/widgets/WebPageBuilder4/PositionSetting.vue +0 -191
- package/src/widgets/WebPageBuilder4/PropertySetting.vue +0 -176
- package/src/widgets/WebPageBuilder4/TextSetting.vue +0 -72
- package/src/widgets/WebPageBuilder4/TreeView.vue +0 -46
- package/src/widgets/WebPageBuilder4/TreeViewItem.vue +0 -303
- package/src/widgets/WebPageBuilder4/WebPageComponentSelector.vue +0 -141
- package/src/widgets/WebPageBuilder4/WebPagePropertySelector.vue +0 -168
- package/src/widgets/WebPageBuilder4/WidthSetting.vue +0 -105
- package/src/widgets/WebPageBuilder4/ZIndexSetting.vue +0 -72
- package/src/widgets/WebPagePreview.vue +0 -295
- package/src/widgets/WebPageSelector.vue +0 -173
- package/src/widgets/WebTemplateCreator.vue +0 -156
- /package/src/{widgets → components}/AhrefSetting.vue +0 -0
- /package/src/{widgets → components}/ArticleSetting.vue +0 -0
- /package/src/{widgets → components}/BackgroundColorSetting.vue +0 -0
- /package/src/{widgets → components}/BlockSetting.vue +0 -0
- /package/src/{widgets → components}/BorderColorSetting.vue +0 -0
- /package/src/{widgets → components}/ButtonSetting.vue +0 -0
- /package/src/{widgets → components}/CarouselSetting.vue +0 -0
- /package/src/{widgets → components}/ComponentSetting.vue +0 -0
- /package/src/{widgets → components}/ContactFormSetting.vue +0 -0
- /package/src/{widgets → components}/CountdownSetting.vue +0 -0
- /package/src/{widgets → components}/DataListSetting.vue +0 -0
- /package/src/{widgets → components}/EmbeddedVideo.vue +0 -0
- /package/src/{widgets → components}/EmbeddedVideoSetting.vue +0 -0
- /package/src/{widgets → components}/FAQ.vue +0 -0
- /package/src/{widgets → components}/FAQSetting.vue +0 -0
- /package/src/{widgets → components}/FeatureListSetting.vue +0 -0
- /package/src/{widgets → components}/FlexSetting.vue +0 -0
- /package/src/{widgets → components}/GridSetting.vue +0 -0
- /package/src/{widgets → components}/Header2.vue +0 -0
- /package/src/{widgets → components}/Header2Setting.vue +0 -0
- /package/src/{widgets → components}/IconListSetting.vue +0 -0
- /package/src/{widgets → components}/ImageSetting.vue +0 -0
- /package/src/{widgets → components}/LinkSetting.vue +0 -0
- /package/src/{widgets → components}/LinkSettingModal.vue +0 -0
- /package/src/{widgets → components}/ModalSetting.vue +0 -0
- /package/src/{widgets → components}/OGSettingModal.vue +0 -0
- /package/src/{widgets → components}/ParagraphSetting.vue +0 -0
- /package/src/{widgets → components}/PositionSetting.vue +0 -0
- /package/src/{widgets → components}/Review.vue +0 -0
- /package/src/{widgets → components}/ReviewSetting.vue +0 -0
- /package/src/{widgets → components}/SearchModalSetting.vue +0 -0
- /package/src/{widgets → components}/ShareSetting.vue +0 -0
- /package/src/{widgets → components}/SvgSetting.vue +0 -0
- /package/src/{widgets → components}/TableSetting.vue +0 -0
- /package/src/{widgets → components}/TestimonialSetting.vue +0 -0
- /package/src/{widgets → components}/TextBlockSetting.vue +0 -0
- /package/src/{widgets → components}/ThumbnailSetting.vue +0 -0
- /package/src/{widgets → components}/WebComponentSelector.vue +0 -0
- /package/src/{widgets → components}/YoutubeVideoSetting.vue +0 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# PivotList
|
|
2
|
+
|
|
3
|
+
A pivot-first data list. Same structure and preset system as `List`, but the
|
|
4
|
+
preset is **always** in pivot mode and the result renders in a **single
|
|
5
|
+
`VirtualTable`**. Built for aggregating a Sequelize model — including columns
|
|
6
|
+
that live across **relationships** (`customer.name`, `order.customer.city`) —
|
|
7
|
+
with client-side **virtual columns** edited in a spreadsheet-style formula bar.
|
|
8
|
+
|
|
9
|
+
## TL;DR — how to use
|
|
10
|
+
|
|
11
|
+
**1. Client** (any CMS front end):
|
|
12
|
+
|
|
13
|
+
```vue
|
|
14
|
+
<PivotList title="Sales"
|
|
15
|
+
controller="order"
|
|
16
|
+
preset-key="order-pivot"
|
|
17
|
+
subscribe-key="order"
|
|
18
|
+
:default-config="defaultConfig"
|
|
19
|
+
@pivot-item-click="drillDown" />
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
import PivotList from '@mixd-id/web-scaffold/components/PivotList.vue'
|
|
24
|
+
|
|
25
|
+
const defaultConfig = () => ({
|
|
26
|
+
name: 'Orders',
|
|
27
|
+
columns: [
|
|
28
|
+
{ key: 'status', label: 'Status', filterable: true, search: true },
|
|
29
|
+
{ key: 'amount', label: 'Amount', type: 'currency', filterable: true },
|
|
30
|
+
{ key: 'orderedAt', label: 'Ordered', type: 'date', filterable: true },
|
|
31
|
+
// relationship column — dotted key resolves through model.associations
|
|
32
|
+
{ key: 'customer.city', label: 'City', filterable: true },
|
|
33
|
+
],
|
|
34
|
+
presets: [{
|
|
35
|
+
name: 'By city / month',
|
|
36
|
+
pivot: {
|
|
37
|
+
rows: [
|
|
38
|
+
{ key: 'customer.city' },
|
|
39
|
+
{ key: 'orderedAt', aggregate: 'month' },
|
|
40
|
+
],
|
|
41
|
+
values: [
|
|
42
|
+
{ key: 'amount', aggregate: 'sum' },
|
|
43
|
+
{ key: 'id', aggregate: 'count' },
|
|
44
|
+
],
|
|
45
|
+
sorts: [ { key: '_amount-sum', type: 'desc' } ],
|
|
46
|
+
useTotal: true,
|
|
47
|
+
},
|
|
48
|
+
}],
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const drillDown = ({ item, column, filters }) => {
|
|
52
|
+
// filters are ready-made List filters equivalent to the clicked cell
|
|
53
|
+
// — e.g. navigate to the orders List with them applied
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**2. Server** — apply the mixin once on the model base class
|
|
58
|
+
(`server/src/classes/model.js`):
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
import { PivotListMixin } from '@mixd-id/web-scaffold/pivot-list'
|
|
62
|
+
|
|
63
|
+
class CustomModel extends PivotListMixin(Model) { ... }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
then add the socket handler the component calls (`${controller}.pivot-load`):
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
// server/src/controllers/sockets/order.js
|
|
70
|
+
async 'pivot-load'(preset){
|
|
71
|
+
return Order.findAllFromPivotPreset(preset, {
|
|
72
|
+
config: { columns: [ ...same column defs... ] },
|
|
73
|
+
Sequelize
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
(Or skip the mixin and call the standalone helper:
|
|
79
|
+
`findAllFromPivotPreset(Order, preset, { config })`.)
|
|
80
|
+
|
|
81
|
+
**3. Presets** persist exactly like `List`: pass `preset-key` and the config
|
|
82
|
+
(presets, pivot setup, virtual columns, sidebar state) auto-saves to
|
|
83
|
+
`${controller}.preset` (falling back to `user.preset`) a moment after any
|
|
84
|
+
change, and reloads on mount. No extra wiring if your controller already
|
|
85
|
+
supports `List` presets.
|
|
86
|
+
|
|
87
|
+
That's it — the sidebar (`Alt+\``) gives users groups, aggregates, crosstab
|
|
88
|
+
spreads, ordering, top-N limits, filters and sharing; the formula bar adds
|
|
89
|
+
calculated columns without touching the server.
|
|
90
|
+
|
|
91
|
+
## How it differs from List
|
|
92
|
+
|
|
93
|
+
| | List | PivotList |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| Pivot | optional per preset | always on (`pivot.enabled` is forced) |
|
|
96
|
+
| Result view | table or grid, pivot drill-down opens a second table | one `VirtualTable`, always |
|
|
97
|
+
| Pagination | infinite scroll (`afterItem`) | none — one grouped result set, cap with `pivot.limit` |
|
|
98
|
+
| Drill-down | opens the built-in ext panel | emits `pivot-item-click` with ready-made filters; the host decides |
|
|
99
|
+
| Relationships | filter-only (`filtersToSequelizeInclude`) | groups, aggregates, spreads, filters and sorts on dotted keys |
|
|
100
|
+
| Virtual columns | — | client-side formula columns (formula bar) |
|
|
101
|
+
| Server endpoint | `${controller}.load` | `${controller}.pivot-load` |
|
|
102
|
+
|
|
103
|
+
## Data-type behavior
|
|
104
|
+
|
|
105
|
+
Column types come from your config or, when omitted, from the model
|
|
106
|
+
(`columnTypes` in the response — dotted relationship keys included). The type
|
|
107
|
+
changes what the pivot does with a column:
|
|
108
|
+
|
|
109
|
+
| Type | As a group (row) | As a value (aggregate) | Default aggregate |
|
|
110
|
+
|---|---|---|---|
|
|
111
|
+
| `number` / `currency` | groups raw values | `sum`, `avg`, `min`, `max`, `stddev`, `count`, `countDistinct`, `first`, `last` | `sum` |
|
|
112
|
+
| `date` | buckets: `hour`, `date`, `week`, `month`, `quarter`, `year` | `min` (earliest), `max` (latest), `count`, `countDistinct` | `max` |
|
|
113
|
+
| `boolean` | groups true/false | `countTrue`, `countFalse`, `count` | `countTrue` |
|
|
114
|
+
| `string` / `enum` | groups raw values | `count`, `countDistinct`, `groupConcat` (distinct values in one cell), `min`, `max` | `count` |
|
|
115
|
+
|
|
116
|
+
An aggregate that doesn't fit the column's type falls back to the type's
|
|
117
|
+
default (e.g. `sum` on a string becomes `count`). Value-returning aggregates
|
|
118
|
+
(`min`/`max`/`first`/`last`) keep the source column's type and format in the
|
|
119
|
+
result — a date's `max` renders as a date, an enum's `first` resolves its
|
|
120
|
+
enum label.
|
|
121
|
+
|
|
122
|
+
Drill-down filters are also type-aware: boolean cells compare as booleans and
|
|
123
|
+
date buckets become `fn: 'format_month'`-style filters, on both own and
|
|
124
|
+
relationship columns.
|
|
125
|
+
|
|
126
|
+
## Relationships (Sequelize)
|
|
127
|
+
|
|
128
|
+
Any dotted key resolves through `model.associations`, joining as needed:
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
rows: [ { key: 'customer.city' } ] // GROUP BY `customer`.`city`
|
|
132
|
+
values: [ { key: 'customer.name', aggregate: 'countDistinct' } ]
|
|
133
|
+
filters:[ { key: 'customer.city', value: [ { operator: 'contains', value: 'Jak' } ] } ]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Nested paths work too (`order.customer.company.name` →
|
|
137
|
+
`` `order->customer->company`.`name` ``). Joins are `LEFT OUTER` with
|
|
138
|
+
`attributes: []` — they exist only for grouping/aggregating.
|
|
139
|
+
|
|
140
|
+
Caveats:
|
|
141
|
+
- **hasMany joins multiply rows.** Aggregating a parent measure while joining
|
|
142
|
+
a hasMany relation inflates `sum`/`count` — prefer pivoting from the child
|
|
143
|
+
model, or use `countDistinct`.
|
|
144
|
+
- `first`/`last` are only supported on own columns (they resolve via the root
|
|
145
|
+
table's id); on dotted keys they fall back to `count`.
|
|
146
|
+
|
|
147
|
+
## Virtual columns (formula bar)
|
|
148
|
+
|
|
149
|
+
The `fx` bar above the table adds columns **calculated client-side** per pivot
|
|
150
|
+
row — including the total row — like a spreadsheet formula:
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
=([_amount-sum] - [_cost-sum]) / [_amount-sum] * 100
|
|
154
|
+
=IF([_id-count] > 100, "High", "Low")
|
|
155
|
+
=ROUND([_amount-sum] / [_qty-sum], 2)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
- Reference pivot result columns as `[key]` — focus the input and click a
|
|
159
|
+
chip to insert one. Aggregate aliases follow the `List` contract:
|
|
160
|
+
`_<key>-<aggregate>`, spreads `_<key>-<aggregate>-<group>`, plus
|
|
161
|
+
`_rowTotal`.
|
|
162
|
+
- Operators: `+ - * / % ^`, `&` (concat), comparisons `= <> > >= < <=`.
|
|
163
|
+
- Functions: `IF`, `AND`, `OR`, `NOT`, `ABS`, `ROUND`, `FLOOR`, `CEIL`,
|
|
164
|
+
`MIN`, `MAX`, `SUM`, `AVG`, `COUNT`, `CONCAT`, `LEN`, `UPPER`, `LOWER`.
|
|
165
|
+
- Blank cells count as `0`; per-cell failures show Excel-style codes
|
|
166
|
+
(`#DIV/0!`, `#VALUE!`, `#NAME?`) without breaking the column.
|
|
167
|
+
- Stored on the preset (`pivot.virtualColumns`), so they persist and share
|
|
168
|
+
with the preset; recomputed locally after every load — the server never
|
|
169
|
+
sees them.
|
|
170
|
+
|
|
171
|
+
## Props
|
|
172
|
+
|
|
173
|
+
| Property | Type | Default | Description |
|
|
174
|
+
|----------|------|---------|-------------|
|
|
175
|
+
| config | Object | `undefined` | Preset config (columns/presets); omit to use `defaultConfig` + `presetKey` |
|
|
176
|
+
| defaultConfig | Function | `undefined` | Returns the default config when no saved preset exists |
|
|
177
|
+
| presetKey | String | `undefined` | Persistence key — enables preset load/save |
|
|
178
|
+
| controller | String | `undefined` | Socket controller prefix (`<controller>.pivot-load`, `<controller>.preset`) |
|
|
179
|
+
| src | String | `` `${controller}.pivot-load` `` | Override the load endpoint |
|
|
180
|
+
| subscribeKey | String | `undefined` | Realtime channel; signals trigger a debounced pivot reload |
|
|
181
|
+
| title | String | `'Untitled'` | Small title above the preset name |
|
|
182
|
+
| updateInterval | Number | `1200` | Debounce (ms) for realtime reloads |
|
|
183
|
+
| presetMode | String | `'sidebar'` | `'sidebar'`, `'popup'` or `'external'` (emits `open-preset`) |
|
|
184
|
+
| searchable | Boolean/String | `true` | Show the search box (columns with `search: true` are matched) |
|
|
185
|
+
| toolbar | Boolean/String | `true` | Show the right toolbar + formula bar |
|
|
186
|
+
| headerClass / containerClass / itemClass | String | — | Styling pass-throughs (same as `List`) |
|
|
187
|
+
| itemsFn | Function | `undefined` | Transform the loaded rows before render |
|
|
188
|
+
|
|
189
|
+
## Events
|
|
190
|
+
|
|
191
|
+
| Event | Payload | Description |
|
|
192
|
+
|-------|---------|-------------|
|
|
193
|
+
| pivot-item-click | `{ item, column, filters }` | Aggregate cell clicked — `filters` reproduce the cell's row slice |
|
|
194
|
+
| item-click | `(item, column)` | Group-column or virtual-column cell clicked |
|
|
195
|
+
| after-load | — | Pivot data loaded |
|
|
196
|
+
| signal | `(event, items)` | Realtime signal received (reload already scheduled) |
|
|
197
|
+
| open-preset | — | `presetMode="external"` asked to open the preset editor |
|
|
198
|
+
|
|
199
|
+
## Slots
|
|
200
|
+
|
|
201
|
+
`head`, `left`, `left-toolbar`, `toolbar`, `right-toolbar`, `mobile-toolbar`,
|
|
202
|
+
and per-column `col-<key>` header/content slots — same contract as `List`.
|
|
203
|
+
|
|
204
|
+
## Keyboard
|
|
205
|
+
|
|
206
|
+
- `Alt+\`` — toggle the preset sidebar
|
|
207
|
+
- `Alt+1..9` — switch preset
|
|
@@ -6,7 +6,7 @@ A versatile text input component with support for various types, formatting, and
|
|
|
6
6
|
|----------|------|---------|-------------|
|
|
7
7
|
| modelValue | Any | `undefined` | The input value. Supports `v-model` |
|
|
8
8
|
| value | Any | `undefined` | Alternative value prop |
|
|
9
|
-
| type | String | `undefined` | Input type: `'text'`, `'tel'`, `'password'`, `'number'` |
|
|
9
|
+
| type | String | `undefined` | Input type: `'text'`, `'tel'`, `'password'`, `'number'`. `tel` accepts digits and one decimal separator — see below |
|
|
10
10
|
| placeholder | String | `undefined` | Placeholder text |
|
|
11
11
|
| readonly | Any | `undefined` | Makes the input read-only |
|
|
12
12
|
| disabled | Any | `undefined` | Disables the input |
|
|
@@ -63,6 +63,25 @@ A versatile text input component with support for various types, formatting, and
|
|
|
63
63
|
<Textbox v-model="phone" type="tel" placeholder="+62..." />
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
### `type="tel"` and decimals
|
|
67
|
+
|
|
68
|
+
`tel` takes digits and **one** decimal separator. Either key produces it — a dot and a comma
|
|
69
|
+
mean the same thing here, because which one a keyboard offers depends on the layout, and
|
|
70
|
+
being told the key you have is the wrong one is not a useful thing for a number field to say.
|
|
71
|
+
|
|
72
|
+
What is shown follows the locale (`Intl.NumberFormat` — a dot in `en`, a comma in `id`, `de`,
|
|
73
|
+
`fr`), and **what is emitted is always written with a dot**, so `Number()` and `v-model.number`
|
|
74
|
+
read it the same wherever it was typed:
|
|
75
|
+
|
|
76
|
+
| typed | shown (en) | shown (id) | emitted |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| `0.7` | `0.7` | `0,7` | `0.7` |
|
|
79
|
+
| `0,7` | `0.7` | `0,7` | `0.7` |
|
|
80
|
+
| `1,2,3` | `1.23` | `1,23` | `1.23` |
|
|
81
|
+
|
|
82
|
+
Only the first separator is one; anything after it belongs to the fraction. `maxlength` counts
|
|
83
|
+
the separator, so a field expecting `0.75` needs at least 4.
|
|
84
|
+
|
|
66
85
|
### With clear button
|
|
67
86
|
```vue
|
|
68
87
|
<Textbox v-model="search" clearable @clear="search = ''" />
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Tooltip
|
|
2
|
+
Floating label shown on hover, keyboard focus, or long-press. A single tooltip layer is mounted lazily on first use and reused for every tooltip in the app, so only one tooltip element ever exists in the DOM.
|
|
3
|
+
|
|
4
|
+
Three ways to use it:
|
|
5
|
+
|
|
6
|
+
- `v-tooltip` directive — the common case, works on any element.
|
|
7
|
+
- `<Tooltip>` component — wraps a trigger, useful when you want props instead of a binding object.
|
|
8
|
+
- `showTooltip()` / `hideTooltip()` — programmatic, for custom triggers.
|
|
9
|
+
|
|
10
|
+
## Props
|
|
11
|
+
| Property | Type | Default | Description |
|
|
12
|
+
|----------|------|---------|-------------|
|
|
13
|
+
| text | String | `undefined` | Tooltip body. Supports `\n` for multiple lines |
|
|
14
|
+
| title | String | `undefined` | Optional bold first line, for a label + description tooltip |
|
|
15
|
+
| shortcut | String | `undefined` | Keyboard hint rendered as a chip, e.g. `⌘S` |
|
|
16
|
+
| placement | String | `'top'` | `'top'`, `'bottom'`, `'left'`, `'right'`. Flips automatically when it doesn't fit |
|
|
17
|
+
| variant | String | `'base'` | `'base'`, `'primary'`, `'secondary'`, `'accent'`, `'danger'`, `'success'` |
|
|
18
|
+
| delay | Number | `null` | Hover intent in ms before showing (default `400`). `0` shows immediately |
|
|
19
|
+
| offset | Number | `8` | Gap in px between the anchor and the tooltip |
|
|
20
|
+
| maxWidth | Number/String | `260` | Max width of the tooltip surface |
|
|
21
|
+
| arrow | Boolean | `true` | Show the arrow pointing at the anchor |
|
|
22
|
+
| disabled | Boolean | `false` | Never show this tooltip |
|
|
23
|
+
| open | Boolean | `null` | Controlled mode — when set, hover/focus is ignored |
|
|
24
|
+
| tag | String | `'span'` | Element used for the trigger wrapper |
|
|
25
|
+
| singleton | Boolean | `false` | Renders the shared layer instead of a trigger. Set by the controller — you never need this |
|
|
26
|
+
|
|
27
|
+
## Events
|
|
28
|
+
| Event | Payload | Description |
|
|
29
|
+
|-------|---------|-------------|
|
|
30
|
+
| show | - | Emitted when the tooltip actually becomes visible for this trigger (after the hover-intent delay) |
|
|
31
|
+
| hide | - | Emitted when it stops being visible — including self-hides such as `Escape` or scrolling the anchor away |
|
|
32
|
+
| update:open | Boolean | Emitted alongside `show`/`hide`, enables `v-model:open` |
|
|
33
|
+
|
|
34
|
+
Events come from the wrapper component only; the `v-tooltip` directive doesn't emit.
|
|
35
|
+
|
|
36
|
+
## Methods
|
|
37
|
+
| Method | Parameters | Description |
|
|
38
|
+
|--------|------------|-------------|
|
|
39
|
+
| show | - | Shows the tooltip |
|
|
40
|
+
| hide | `immediate` | Hides it, skipping the hide delay when `immediate` is true |
|
|
41
|
+
|
|
42
|
+
## Slots
|
|
43
|
+
| Slot | Description |
|
|
44
|
+
|------|-------------|
|
|
45
|
+
| default | The trigger element the tooltip is anchored to |
|
|
46
|
+
|
|
47
|
+
## Directive
|
|
48
|
+
|
|
49
|
+
```vue
|
|
50
|
+
<button v-tooltip="'Save changes'">Save</button>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Placement via argument or modifier, variant via modifier, `.now` to skip the delay:
|
|
54
|
+
|
|
55
|
+
```vue
|
|
56
|
+
<button v-tooltip:bottom="'Filters'">…</button>
|
|
57
|
+
<button v-tooltip.right.primary="'Filters'">…</button>
|
|
58
|
+
<button v-tooltip.now="'Copied'">…</button>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Object form, for anything richer:
|
|
62
|
+
|
|
63
|
+
```vue
|
|
64
|
+
<button v-tooltip="{ title: 'Save', text: 'Writes to the server', shortcut: '⌘S', placement: 'bottom' }">
|
|
65
|
+
Save
|
|
66
|
+
</button>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The binding is read at show time, so a value that starts empty and fills in later works — as does `v-tooltip` on a **disabled** button, which the directive hit-tests separately because disabled controls never fire mouse events.
|
|
70
|
+
|
|
71
|
+
## Samples
|
|
72
|
+
|
|
73
|
+
### Basic
|
|
74
|
+
```vue
|
|
75
|
+
<Tooltip text="Delete this row">
|
|
76
|
+
<button>Delete</button>
|
|
77
|
+
</Tooltip>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Title, description and shortcut
|
|
81
|
+
```vue
|
|
82
|
+
<Tooltip title="Publish" text="Makes the page visible to everyone" shortcut="⌘↵" placement="bottom">
|
|
83
|
+
<Button text="Publish" />
|
|
84
|
+
</Tooltip>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Variants
|
|
88
|
+
```vue
|
|
89
|
+
<Tooltip text="Looks good" variant="success"><span>✓</span></Tooltip>
|
|
90
|
+
<Tooltip text="Quota exceeded" variant="danger"><span>!</span></Tooltip>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Controlled
|
|
94
|
+
```vue
|
|
95
|
+
<Tooltip text="Copied to clipboard" :open="justCopied" placement="right">
|
|
96
|
+
<button @click="copy">Copy</button>
|
|
97
|
+
</Tooltip>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Reacting to show/hide
|
|
101
|
+
```vue
|
|
102
|
+
<Tooltip text="Writes to the server" @show="track('tooltip-seen')" @hide="onHidden">
|
|
103
|
+
<button>Save</button>
|
|
104
|
+
</Tooltip>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`v-model:open` keeps a controlled tooltip in sync when it closes itself (Escape, scrolling away):
|
|
108
|
+
|
|
109
|
+
```vue
|
|
110
|
+
<Tooltip text="Copied to clipboard" v-model:open="justCopied">
|
|
111
|
+
<button @click="copy">Copy</button>
|
|
112
|
+
</Tooltip>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Programmatic
|
|
116
|
+
```js
|
|
117
|
+
import { showTooltip, hideTooltip } from '@mixd-id/web-scaffold/tooltip'
|
|
118
|
+
|
|
119
|
+
showTooltip(el, { text: 'Drop files here', placement: 'bottom', delay: 0 })
|
|
120
|
+
hideTooltip(el)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Behaviour
|
|
124
|
+
- Shows after `400ms` of hover intent; while a tooltip is already open (or one closed within `320ms`) the next one appears instantly, so scanning a toolbar doesn't stutter.
|
|
125
|
+
- Hides on mouse leave, click, blur, `Escape`, and when the anchor scrolls out of view. It repositions itself while the page scrolls or resizes.
|
|
126
|
+
- Keyboard focus shows it only for `:focus-visible`, so clicking a button doesn't leave a tooltip behind.
|
|
127
|
+
- Touch devices: long-press (`450ms`) shows it, and it auto-hides after `2.5s`.
|
|
128
|
+
- Text is rendered as text, never HTML.
|
|
129
|
+
|
|
130
|
+
## Styling
|
|
131
|
+
The layer is global CSS (class `mx-tooltip`) and paints itself entirely from the app's theme tokens — no colours of its own — so it follows whatever theme is active, light or dark:
|
|
132
|
+
|
|
133
|
+
| Part | Token |
|
|
134
|
+
|------|-------|
|
|
135
|
+
| surface, arrow | `bg-base-500`, `border-border-100` |
|
|
136
|
+
| text | `text-text` |
|
|
137
|
+
| description under a title | `text-text-400` |
|
|
138
|
+
| shortcut chip | `bg-base-300`, `text-text-400` |
|
|
139
|
+
| `primary` / `secondary` / `accent` | that tone's `-500` surface, `-400` border, `-50` / `-100` text |
|
|
140
|
+
|
|
141
|
+
Only layout knobs are exposed as custom properties:
|
|
142
|
+
|
|
143
|
+
```css
|
|
144
|
+
.mx-tooltip{
|
|
145
|
+
--mx-tooltip-max-width: 320px;
|
|
146
|
+
--mx-tooltip-z: 12000;
|
|
147
|
+
}
|
|
148
|
+
```
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# WebPageBuilder
|
|
2
|
+
A full-screen visual page builder widget. It renders a three-pane editor — settings panel (page info / layout / components / chat), a live preview iframe, and a per-component setting panel — and talks to a socket controller for loading, saving and previewing pages.
|
|
3
|
+
|
|
4
|
+
Registered globally by the scaffold plugin as an async component, so it can be used directly without importing.
|
|
5
|
+
|
|
6
|
+
## Requirements
|
|
7
|
+
- `vue-router` must be installed; the page uid is read from `$route.params.uid`, and a navigation guard warns about unsaved changes.
|
|
8
|
+
- The host app must provide the scaffold injections: `alert`, `appStyle`, `confirm`, `useSocket`, `toast` (all provided by the standard scaffold app setup).
|
|
9
|
+
- A preview route (returned by the server as `previewHost`, e.g. `/web-preview`) that renders the page inside the iframe and implements the iframe message protocol below.
|
|
10
|
+
|
|
11
|
+
## Props
|
|
12
|
+
| Property | Type | Default | Description |
|
|
13
|
+
|----------|------|---------|-------------|
|
|
14
|
+
| controller | String | `undefined` | Socket controller namespace. Every server call is prefixed with it, e.g. `web-page.open` |
|
|
15
|
+
| presetKey | String | `undefined` | Key used to persist UI state (tab index, panel widths, zoom level…) via `${controller}.preset` |
|
|
16
|
+
| canManageLayout | Boolean | `false` | Whether the user may manage layouts |
|
|
17
|
+
| excludeComponents | Array | `[]` | Component `type` strings to hide from the component selector |
|
|
18
|
+
| moreComponents | Array | `[]` | Extra component definitions added to the selector (`{ type, name, group, props }`) |
|
|
19
|
+
| uploadConfig | Object | `undefined` | Axios-style config (`{ method, url, data }`) used to upload images when `uploadImageFn` is not given |
|
|
20
|
+
| uploadImageFn | Function | `undefined` | `(image, extra?, opt?) => Promise` — custom image upload handler; takes precedence over `uploadConfig` |
|
|
21
|
+
| useChat | Boolean | `false` | Shows the AI chat tab; requires a `${controller}.chat` handler on the server |
|
|
22
|
+
| useDatasource | Array | `undefined` | Datasource support tuple; the second element is the socket path used to load datasource data (the Datasource tab is currently disabled) |
|
|
23
|
+
|
|
24
|
+
## Events
|
|
25
|
+
| Event | Payload | Description |
|
|
26
|
+
|-------|---------|-------------|
|
|
27
|
+
| close | - | Emitted when the close (✕) button is clicked. The parent decides where to navigate |
|
|
28
|
+
| unmount | `{ uid }` | Emitted when the builder unmounts, with the page uid |
|
|
29
|
+
|
|
30
|
+
## Methods
|
|
31
|
+
| Method | Parameters | Description |
|
|
32
|
+
|--------|------------|-------------|
|
|
33
|
+
| getPage | - | Returns the current (reactive) page object |
|
|
34
|
+
|
|
35
|
+
## Server contract
|
|
36
|
+
The widget expects these socket paths on `controller` (base controller paths included):
|
|
37
|
+
|
|
38
|
+
| Path | Payload | Returns |
|
|
39
|
+
|------|---------|---------|
|
|
40
|
+
| `${controller}.open` | `{ uid }` | `{ page, previewHost, debugMode? }` — `page.app.hosts[0].name` is used to build the public URL |
|
|
41
|
+
| `${controller}.save` | `{ ...page, layout }` | - |
|
|
42
|
+
| `${controller}.load-layouts` | `{}` | Array of layout objects |
|
|
43
|
+
| `${controller}.create-layout` | layout | The created layout |
|
|
44
|
+
| `${controller}.load-templates` | - | `{ items }` — component templates for the selector |
|
|
45
|
+
| `${controller}.subscribe` / `.unsubscribe` | `{ name:'page' }` | - |
|
|
46
|
+
| `${controller}.preset` | `{ key }` / `{ key, config }` | Persisted UI config |
|
|
47
|
+
| `${controller}.chat` | `{ key, prompt, attachments, pageId, input }` | `{ messages }` — only needed when `useChat` is set |
|
|
48
|
+
|
|
49
|
+
## Preview iframe protocol
|
|
50
|
+
Messages are exchanged with the preview iframe via `postMessage`.
|
|
51
|
+
|
|
52
|
+
Sent to the iframe:
|
|
53
|
+
| Message | Description |
|
|
54
|
+
|---------|-------------|
|
|
55
|
+
| `{ action:'update', page }` | Full page JSON (headers, footers, components, stylesheet, data, editMode). Throttled; sent on every page/layout change |
|
|
56
|
+
| `{ action:'select', uid }` | Scrolls/highlights the component with `uid` |
|
|
57
|
+
| `{ action:'reload' }` | Reloads the preview |
|
|
58
|
+
|
|
59
|
+
Received from the iframe:
|
|
60
|
+
| Message | Description |
|
|
61
|
+
|---------|-------------|
|
|
62
|
+
| `{ type:'mounted' }` | Preview is ready; triggers the first update |
|
|
63
|
+
| `{ type:'component-click', uid }` | Selects the clicked component in the editor |
|
|
64
|
+
| `{ type:'setProp', uid, value }` | Merges `value` into the component's props (inline editing) |
|
|
65
|
+
| `{ type:'setSubAction', value }` | Forwarded to the active setting component |
|
|
66
|
+
|
|
67
|
+
## Provided injections
|
|
68
|
+
`*Setting` components rendered in the right panel can inject: `imageUrl`, `imageSrc`, `store`, `postIframe`, `getPage`, `uploadImage`, `openComponentSelector`, `setUid`, `openRightPane2`, `getRightPane`, `pageHistory`, `duplicate`.
|
|
69
|
+
|
|
70
|
+
Each component type `Foo` resolves its setting panel as a globally registered `FooSetting` component.
|
|
71
|
+
|
|
72
|
+
## Keyboard shortcuts
|
|
73
|
+
| Shortcut | Action |
|
|
74
|
+
|----------|--------|
|
|
75
|
+
| Alt+1 | Mobile view |
|
|
76
|
+
| Alt+2 | Tablet view (`md:`) |
|
|
77
|
+
| Alt+3 | Desktop view (`xl:`) |
|
|
78
|
+
| Alt+4 | Wide view (`2xl:`) |
|
|
79
|
+
|
|
80
|
+
## Samples
|
|
81
|
+
|
|
82
|
+
### Page builder page (see `admin/src/pages/WebPageEdit.vue`)
|
|
83
|
+
```vue
|
|
84
|
+
<template>
|
|
85
|
+
<div class="flex-1 flex flex-col">
|
|
86
|
+
<WebPageBuilder class="flex-1"
|
|
87
|
+
controller="web-page"
|
|
88
|
+
preset-key="web-page"
|
|
89
|
+
:more-components="components"
|
|
90
|
+
:upload-image-fn="uploadImage"
|
|
91
|
+
:use-chat="true"
|
|
92
|
+
@close="$router.push({ name:'WebPage' })" />
|
|
93
|
+
</div>
|
|
94
|
+
</template>
|
|
95
|
+
|
|
96
|
+
<script setup>
|
|
97
|
+
import {inject, ref} from 'vue'
|
|
98
|
+
|
|
99
|
+
const socket = inject('useSocket')()
|
|
100
|
+
|
|
101
|
+
const components = ref([
|
|
102
|
+
{ type:'SaasRegister', name:'SAAS Register', group:'SAAS', props:{ items:[] }},
|
|
103
|
+
])
|
|
104
|
+
|
|
105
|
+
async function uploadImage(image){
|
|
106
|
+
return socket.send('web-page.upload-image', { image })
|
|
107
|
+
}
|
|
108
|
+
</script>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The route must carry the page uid, e.g.:
|
|
112
|
+
```js
|
|
113
|
+
{
|
|
114
|
+
name: 'WebPageEdit',
|
|
115
|
+
path: 'web-page/:uid',
|
|
116
|
+
component: () => import('./pages/WebPageEdit.vue')
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Upload via config instead of a function
|
|
121
|
+
```vue
|
|
122
|
+
<WebPageBuilder controller="web-page"
|
|
123
|
+
:upload-config="{ method:'post', url:'/api/upload' }"
|
|
124
|
+
@close="$router.back()" />
|
|
125
|
+
```
|