create-cundi-app 1.0.15 → 1.0.17

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/index.js CHANGED
@@ -44,6 +44,14 @@ async function main() {
44
44
  projectName = response.projectName;
45
45
  }
46
46
 
47
+ // Ask if samples should be included
48
+ const { includeSamples } = await prompts({
49
+ type: "confirm",
50
+ name: "includeSamples",
51
+ message: "Include sample pages? (Article, Product, BasicTypeTest, TiptapTest, DrawioTest)",
52
+ initial: false,
53
+ });
54
+
47
55
  const targetDir = path.resolve(process.cwd(), projectName);
48
56
 
49
57
  // Check if directory already exists
@@ -65,6 +73,9 @@ async function main() {
65
73
 
66
74
  console.log();
67
75
  console.log(kleur.blue(`Creating project in ${kleur.bold(targetDir)}...`));
76
+ if (includeSamples) {
77
+ console.log(kleur.gray("Including sample pages..."));
78
+ }
68
79
  console.log();
69
80
 
70
81
  // Copy template files
@@ -73,12 +84,39 @@ async function main() {
73
84
  try {
74
85
  await fs.copy(templateDir, targetDir, {
75
86
  filter: (src) => {
76
- // Skip node_modules and dist if they exist
77
87
  const relativePath = path.relative(templateDir, src);
78
- return !relativePath.includes("node_modules") && !relativePath.includes("dist");
88
+ // Skip node_modules and dist if they exist
89
+ if (relativePath.includes("node_modules") || relativePath.includes("dist")) {
90
+ return false;
91
+ }
92
+ // Skip samples if not requested
93
+ if (!includeSamples && relativePath.includes("pages/samples")) {
94
+ return false;
95
+ }
96
+ // Skip the alternative version files (they will be handled separately)
97
+ if (relativePath.endsWith(".no-samples.tsx") || relativePath.endsWith(".no-samples.ts")) {
98
+ return false;
99
+ }
100
+ return true;
79
101
  },
80
102
  });
81
103
 
104
+ // Handle App.tsx and i18n.ts based on sample choice
105
+ if (!includeSamples) {
106
+ // If not including samples, use the no-samples versions
107
+ const appNoSamplesPath = path.join(templateDir, "src/App.no-samples.tsx");
108
+ const appPath = path.join(targetDir, "src/App.tsx");
109
+ if (fs.existsSync(appNoSamplesPath)) {
110
+ await fs.copy(appNoSamplesPath, appPath, { overwrite: true });
111
+ }
112
+
113
+ const i18nNoSamplesPath = path.join(templateDir, "src/i18n.no-samples.ts");
114
+ const i18nPath = path.join(targetDir, "src/i18n.ts");
115
+ if (fs.existsSync(i18nNoSamplesPath)) {
116
+ await fs.copy(i18nNoSamplesPath, i18nPath, { overwrite: true });
117
+ }
118
+ }
119
+
82
120
  // Generate package.json with project name
83
121
  const pkgTemplatePath = path.join(targetDir, "package.json.template");
84
122
  const pkgTargetPath = path.join(targetDir, "package.json");
@@ -129,6 +167,10 @@ VITE_REDIRECT_URI=http://localhost:5173/auth/callback
129
167
  // console.log(` ${kleur.gray("$")} cp .env.example .env ${kleur.gray("# Configure your environment")}`);
130
168
  console.log(` ${kleur.gray("$")} npm run dev`);
131
169
  console.log();
170
+ if (includeSamples) {
171
+ console.log(kleur.yellow("Note: Sample pages are included. Make sure your backend has the corresponding Business Objects."));
172
+ console.log();
173
+ }
132
174
  console.log(kleur.gray("────────────────────────────────────"));
133
175
  console.log(kleur.blue("Happy coding! 🎉"));
134
176
  console.log();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-cundi-app",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "description": "Create a new Cundi app with React + Refine + Ant Design",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,7 +24,7 @@
24
24
  "dependencies": {
25
25
  "@ant-design/icons": "^5.5.1",
26
26
  "@ant-design/v5-patch-for-react-19": "^1.0.3",
27
- "@cundi/refine-xaf": "^1.0.12",
27
+ "@cundi/refine-xaf": "^1.0.14",
28
28
  "@refinedev/antd": "^6.0.3",
29
29
  "@refinedev/cli": "^2.16.50",
30
30
  "@refinedev/core": "^5.0.6",
@@ -0,0 +1,249 @@
1
+ import {
2
+ Refine,
3
+ Authenticated,
4
+ } from "@refinedev/core";
5
+ import {
6
+ useNotificationProvider,
7
+ ThemedLayout,
8
+ ErrorComponent,
9
+ } from "@refinedev/antd";
10
+ import {
11
+ DashboardOutlined,
12
+ UserOutlined,
13
+ SettingOutlined,
14
+ TeamOutlined,
15
+ FieldTimeOutlined,
16
+ ThunderboltOutlined,
17
+ CopyOutlined,
18
+ } from "@ant-design/icons";
19
+ import routerProvider, {
20
+ NavigateToResource,
21
+ CatchAllNavigate,
22
+ UnsavedChangesNotifier,
23
+ DocumentTitleHandler,
24
+ } from "@refinedev/react-router";
25
+ import { BrowserRouter, Routes, Route, Outlet } from "react-router";
26
+ import { App as AntdApp } from "antd";
27
+ import { useTranslation } from "react-i18next";
28
+ import "./i18n";
29
+
30
+ import "@ant-design/v5-patch-for-react-19";
31
+ import "@refinedev/antd/dist/reset.css";
32
+
33
+ import { DashboardPage } from "./pages/dashboard";
34
+ import {
35
+ authProvider,
36
+ dataProvider,
37
+ Header,
38
+ LoginPage,
39
+ KeycloakLoginPage,
40
+ AuthCallback,
41
+ ApplicationUserList,
42
+ ApplicationUserCreate,
43
+ ApplicationUserEdit,
44
+ RoleList,
45
+ RoleCreate,
46
+ RoleEdit,
47
+ ColorModeContextProvider,
48
+ BackgroundJobList,
49
+ TriggerRuleList,
50
+ TriggerRuleCreate,
51
+ TriggerRuleEdit,
52
+ TriggerLogList,
53
+ MirrorTypeMappingConfigList,
54
+ MirrorTypeMappingConfigCreate,
55
+ MirrorTypeMappingConfigEdit,
56
+ } from "@cundi/refine-xaf";
57
+
58
+ import { accessControlProvider } from "./accessControlProvider";
59
+
60
+ const API_URL = import.meta.env.VITE_API_URL + "/api/odata";
61
+
62
+ const InnerApp: React.FC = () => {
63
+ const { t, i18n } = useTranslation();
64
+
65
+ const i18nProvider = {
66
+ translate: (key: string, params: any) => t(key, params) as string,
67
+ changeLocale: (lang: string) => i18n.changeLanguage(lang),
68
+ getLocale: () => i18n.language,
69
+ };
70
+
71
+ return (
72
+ <BrowserRouter>
73
+ <AntdApp>
74
+ <Refine
75
+ authProvider={authProvider}
76
+ accessControlProvider={accessControlProvider}
77
+ dataProvider={{ default: dataProvider(API_URL) }}
78
+ i18nProvider={i18nProvider}
79
+ routerProvider={routerProvider}
80
+ resources={[
81
+ {
82
+ name: "dashboard",
83
+ list: "/",
84
+ meta: {
85
+ label: t("sider.dashboard"),
86
+ icon: <DashboardOutlined />,
87
+ },
88
+ },
89
+ {
90
+ name: "ApplicationUser",
91
+ list: "/ApplicationUsers",
92
+ create: "/ApplicationUsers/create",
93
+ edit: "/ApplicationUsers/edit/:id",
94
+ meta: {
95
+ label: t("sider.users"),
96
+ icon: <UserOutlined />,
97
+ parent: "Settings",
98
+ },
99
+ },
100
+ {
101
+ name: "PermissionPolicyRole",
102
+ list: "/PermissionPolicyRoles",
103
+ create: "/PermissionPolicyRoles/create",
104
+ edit: "/PermissionPolicyRoles/edit/:id",
105
+ meta: {
106
+ label: t("sider.roles"),
107
+ parent: "Settings",
108
+ icon: <TeamOutlined />,
109
+ },
110
+ },
111
+ {
112
+ name: "BackgroundJobs",
113
+ list: "/background-jobs",
114
+ meta: {
115
+ label: t("sider.backgroundJobs"),
116
+ icon: <FieldTimeOutlined />,
117
+ parent: "Settings",
118
+ },
119
+ },
120
+ {
121
+ name: "TriggerRule",
122
+ list: "/TriggerRules",
123
+ create: "/TriggerRules/create",
124
+ edit: "/TriggerRules/edit/:id",
125
+ meta: {
126
+ label: t("sider.triggerRules"),
127
+ parent: "Settings",
128
+ icon: <ThunderboltOutlined />,
129
+ },
130
+ },
131
+ {
132
+ name: "TriggerLog",
133
+ list: "/TriggerLogs",
134
+ meta: {
135
+ label: t("sider.triggerLogs"),
136
+ parent: "Settings",
137
+ icon: <ThunderboltOutlined />,
138
+ },
139
+ },
140
+ {
141
+ name: "MirrorTypeMappingConfig",
142
+ list: "/MirrorTypeMappingConfigs",
143
+ create: "/MirrorTypeMappingConfigs/create",
144
+ edit: "/MirrorTypeMappingConfigs/edit/:id",
145
+ meta: {
146
+ label: t("sider.mirrorConfigs"),
147
+ parent: "Settings",
148
+ icon: <CopyOutlined />,
149
+ },
150
+ },
151
+ {
152
+ name: "Settings",
153
+ meta: {
154
+ label: t("sider.settings"),
155
+ icon: <SettingOutlined />,
156
+ },
157
+ },
158
+ ]}
159
+ notificationProvider={useNotificationProvider}
160
+ options={{
161
+ syncWithLocation: true,
162
+ warnWhenUnsavedChanges: true,
163
+ }}
164
+ >
165
+ <Routes>
166
+ <Route
167
+ element={
168
+ <Authenticated
169
+ key="authenticated-routes"
170
+ fallback={<CatchAllNavigate to="/login" />}
171
+ >
172
+ <ThemedLayout Header={Header}>
173
+ <Outlet />
174
+ </ThemedLayout>
175
+ </Authenticated>
176
+ }
177
+ >
178
+ <Route index element={<DashboardPage />} />
179
+
180
+ <Route path="/ApplicationUsers">
181
+ <Route index element={<ApplicationUserList />} />
182
+ <Route path="create" element={<ApplicationUserCreate />} />
183
+ <Route path="edit/:id" element={<ApplicationUserEdit />} />
184
+ </Route>
185
+
186
+ <Route path="/PermissionPolicyRoles">
187
+ <Route index element={<RoleList />} />
188
+ <Route path="create" element={<RoleCreate />} />
189
+ <Route path="edit/:id" element={<RoleEdit />} />
190
+ </Route>
191
+
192
+ <Route path="/background-jobs" element={<BackgroundJobList title={t("sider.backgroundJobs")} />} />
193
+
194
+ <Route path="/TriggerRules">
195
+ <Route index element={<TriggerRuleList />} />
196
+ <Route path="create" element={<TriggerRuleCreate />} />
197
+ <Route path="edit/:id" element={<TriggerRuleEdit />} />
198
+ </Route>
199
+ <Route path="/TriggerLogs">
200
+ <Route index element={<TriggerLogList />} />
201
+ </Route>
202
+ <Route path="/MirrorTypeMappingConfigs">
203
+ <Route index element={<MirrorTypeMappingConfigList />} />
204
+ <Route path="create" element={<MirrorTypeMappingConfigCreate />} />
205
+ <Route path="edit/:id" element={<MirrorTypeMappingConfigEdit />} />
206
+ </Route>
207
+ </Route>
208
+
209
+ <Route
210
+ element={
211
+ <Authenticated key="auth-pages" fallback={<Outlet />}>
212
+ <NavigateToResource resource="dashboard" />
213
+ </Authenticated>
214
+ }
215
+ >
216
+ <Route path="/login" element={<KeycloakLoginPage />} />
217
+ <Route path="/login/api" element={<LoginPage />} />
218
+ <Route path="/auth/callback" element={<AuthCallback />} />
219
+ </Route>
220
+
221
+ <Route
222
+ element={
223
+ <Authenticated key="catch-all">
224
+ <ThemedLayout Header={Header}>
225
+ <Outlet />
226
+ </ThemedLayout>
227
+ </Authenticated>
228
+ }
229
+ >
230
+ <Route path="*" element={<ErrorComponent />} />
231
+ </Route>
232
+ </Routes>
233
+ <UnsavedChangesNotifier />
234
+ <DocumentTitleHandler />
235
+ </Refine>
236
+ </AntdApp>
237
+ </BrowserRouter>
238
+ );
239
+ };
240
+
241
+ const App: React.FC = () => {
242
+ return (
243
+ <ColorModeContextProvider>
244
+ <InnerApp />
245
+ </ColorModeContextProvider>
246
+ );
247
+ };
248
+
249
+ export default App;
@@ -15,6 +15,7 @@ import {
15
15
  FieldTimeOutlined,
16
16
  ThunderboltOutlined,
17
17
  CopyOutlined,
18
+ AppstoreOutlined,
18
19
  } from "@ant-design/icons";
19
20
  import routerProvider, {
20
21
  NavigateToResource,
@@ -57,6 +58,25 @@ import {
57
58
 
58
59
  import { accessControlProvider } from "./accessControlProvider";
59
60
 
61
+ // Sample pages
62
+ import { ArticleList } from "./pages/samples/article/list";
63
+ import { ArticleCreate } from "./pages/samples/article/create";
64
+ import { ArticleEdit } from "./pages/samples/article/edit";
65
+ import { ProductList } from "./pages/samples/product/list";
66
+ import { ProductCreate } from "./pages/samples/product/create";
67
+ import { ProductEdit } from "./pages/samples/product/edit";
68
+ import { BasicTypeTestList } from "./pages/samples/basic-type-test/list";
69
+ import { BasicTypeTestCreate } from "./pages/samples/basic-type-test/create";
70
+ import { BasicTypeTestEdit } from "./pages/samples/basic-type-test/edit";
71
+ import { TiptapTestList } from "./pages/samples/tiptap-test/list";
72
+ import { TiptapTestCreate } from "./pages/samples/tiptap-test/create";
73
+ import { TiptapTestEdit } from "./pages/samples/tiptap-test/edit";
74
+ import { TiptapTestShow } from "./pages/samples/tiptap-test/show";
75
+ import { DrawioTestList } from "./pages/samples/drawio-test/list";
76
+ import { DrawioTestCreate } from "./pages/samples/drawio-test/create";
77
+ import { DrawioTestEdit } from "./pages/samples/drawio-test/edit";
78
+ import { DrawioTestShow } from "./pages/samples/drawio-test/show";
79
+
60
80
  const API_URL = import.meta.env.VITE_API_URL + "/api/odata";
61
81
 
62
82
  const InnerApp: React.FC = () => {
@@ -86,6 +106,81 @@ const InnerApp: React.FC = () => {
86
106
  icon: <DashboardOutlined />,
87
107
  },
88
108
  },
109
+ // Sample resources
110
+ {
111
+ name: "Samples",
112
+ meta: {
113
+ label: t("sider.samples"),
114
+ icon: <AppstoreOutlined />,
115
+ },
116
+ },
117
+ {
118
+ name: "Model",
119
+ meta: {
120
+ label: t("sider.model"),
121
+ parent: "Samples",
122
+ },
123
+ },
124
+ {
125
+ name: "BasicTypeTest",
126
+ list: "/BasicTypeTests",
127
+ create: "/BasicTypeTests/create",
128
+ edit: "/BasicTypeTests/edit/:id",
129
+ meta: {
130
+ label: t("sider.basicTypeTest"),
131
+ parent: "Model",
132
+ },
133
+ },
134
+ {
135
+ name: "TiptapTest",
136
+ list: "/TiptapTests",
137
+ create: "/TiptapTests/create",
138
+ edit: "/TiptapTests/edit/:id",
139
+ show: "/TiptapTests/show/:id",
140
+ meta: {
141
+ label: t("sider.tiptapTest"),
142
+ parent: "Model",
143
+ },
144
+ },
145
+ {
146
+ name: "DrawioTest",
147
+ list: "/DrawioTests",
148
+ create: "/DrawioTests/create",
149
+ edit: "/DrawioTests/edit/:id",
150
+ show: "/DrawioTests/show/:id",
151
+ meta: {
152
+ label: t("sider.drawioTest"),
153
+ parent: "Model",
154
+ },
155
+ },
156
+ {
157
+ name: "FullTextSearch",
158
+ meta: {
159
+ label: t("sider.fullTextSearch"),
160
+ parent: "Samples",
161
+ },
162
+ },
163
+ {
164
+ name: "Article",
165
+ list: "/Articles",
166
+ create: "/Articles/create",
167
+ edit: "/Articles/edit/:id",
168
+ meta: {
169
+ label: t("types.Article"),
170
+ parent: "FullTextSearch",
171
+ },
172
+ },
173
+ {
174
+ name: "Product",
175
+ list: "/Products",
176
+ create: "/Products/create",
177
+ edit: "/Products/edit/:id",
178
+ meta: {
179
+ label: t("types.Product"),
180
+ parent: "FullTextSearch",
181
+ },
182
+ },
183
+ // Core resources
89
184
  {
90
185
  name: "ApplicationUser",
91
186
  list: "/ApplicationUsers",
@@ -177,6 +272,40 @@ const InnerApp: React.FC = () => {
177
272
  >
178
273
  <Route index element={<DashboardPage />} />
179
274
 
275
+ {/* Sample routes */}
276
+ <Route path="/Articles">
277
+ <Route index element={<ArticleList />} />
278
+ <Route path="create" element={<ArticleCreate />} />
279
+ <Route path="edit/:id" element={<ArticleEdit />} />
280
+ </Route>
281
+
282
+ <Route path="/Products">
283
+ <Route index element={<ProductList />} />
284
+ <Route path="create" element={<ProductCreate />} />
285
+ <Route path="edit/:id" element={<ProductEdit />} />
286
+ </Route>
287
+
288
+ <Route path="/BasicTypeTests">
289
+ <Route index element={<BasicTypeTestList />} />
290
+ <Route path="create" element={<BasicTypeTestCreate />} />
291
+ <Route path="edit/:id" element={<BasicTypeTestEdit />} />
292
+ </Route>
293
+
294
+ <Route path="/TiptapTests">
295
+ <Route index element={<TiptapTestList />} />
296
+ <Route path="create" element={<TiptapTestCreate />} />
297
+ <Route path="edit/:id" element={<TiptapTestEdit />} />
298
+ <Route path="show/:id" element={<TiptapTestShow />} />
299
+ </Route>
300
+
301
+ <Route path="/DrawioTests">
302
+ <Route index element={<DrawioTestList />} />
303
+ <Route path="create" element={<DrawioTestCreate />} />
304
+ <Route path="edit/:id" element={<DrawioTestEdit />} />
305
+ <Route path="show/:id" element={<DrawioTestShow />} />
306
+ </Route>
307
+
308
+ {/* Core routes */}
180
309
  <Route path="/ApplicationUsers">
181
310
  <Route index element={<ApplicationUserList />} />
182
311
  <Route path="create" element={<ApplicationUserCreate />} />
@@ -11,8 +11,8 @@ const ADMIN_ONLY_RESOURCES = ["ApplicationUser", "PermissionPolicyRole", "Settin
11
11
  */
12
12
  export const accessControlProvider: AccessControlProvider = {
13
13
  can: async ({ resource, action, params }) => {
14
- const isAdmin = localStorage.getItem("user_is_admin") === "true";
15
- const roles = JSON.parse(localStorage.getItem("user_roles") || "[]") as string[];
14
+ const isAdmin = (localStorage.getItem("user_is_admin") || sessionStorage.getItem("user_is_admin")) === "true";
15
+ const roles = JSON.parse(localStorage.getItem("user_roles") || sessionStorage.getItem("user_roles") || "[]") as string[];
16
16
 
17
17
  // Admin-only resources check
18
18
  if (resource && ADMIN_ONLY_RESOURCES.includes(resource) && !isAdmin) {
@@ -0,0 +1,49 @@
1
+ import i18n from "i18next";
2
+ import { initReactI18next } from "react-i18next";
3
+ import LanguageDetector from "i18next-browser-languagedetector";
4
+ import { refineXafTranslations } from "@cundi/refine-xaf";
5
+
6
+ // Use translations from the SDK
7
+ const { en, "zh-TW": zhTW } = refineXafTranslations;
8
+
9
+ const enExtended = {
10
+ ...en,
11
+ sider: {
12
+ ...((en as any).sider || {}),
13
+ triggerRules: "Trigger Rules",
14
+ triggerLogs: "Trigger Logs",
15
+ mirrorConfigs: "Mirror Configs",
16
+ },
17
+ };
18
+
19
+ const zhTWExtended = {
20
+ ...zhTW,
21
+ sider: {
22
+ ...((zhTW as any).sider || {}),
23
+ triggerRules: "觸發規則",
24
+ triggerLogs: "觸發紀錄",
25
+ mirrorConfigs: "鏡射設定",
26
+ },
27
+ };
28
+
29
+ i18n
30
+ .use(LanguageDetector)
31
+ .use(initReactI18next)
32
+ .init({
33
+ resources: {
34
+ en: {
35
+ translation: enExtended,
36
+ },
37
+ "zh-TW": {
38
+ translation: zhTWExtended,
39
+ },
40
+ },
41
+ lng: "zh-TW", // Default language
42
+ fallbackLng: "en",
43
+ debug: false,
44
+ interpolation: {
45
+ escapeValue: false,
46
+ },
47
+ });
48
+
49
+ export default i18n;
@@ -13,6 +13,44 @@ const enExtended = {
13
13
  triggerRules: "Trigger Rules",
14
14
  triggerLogs: "Trigger Logs",
15
15
  mirrorConfigs: "Mirror Configs",
16
+ // Sample translations
17
+ samples: "Samples",
18
+ model: "Model",
19
+ basicTypeTest: "Basic Type",
20
+ tiptapTest: "Tiptap Editor",
21
+ drawioTest: "Drawio Editor",
22
+ fullTextSearch: "Full Text Search",
23
+ },
24
+ // Sample field translations
25
+ article: {
26
+ title: "Title",
27
+ content: "Content",
28
+ },
29
+ product: {
30
+ name: "Name",
31
+ description: "Description",
32
+ },
33
+ tiptapTest: {
34
+ name: "Name",
35
+ content: "Content",
36
+ },
37
+ drawioTest: {
38
+ name: "Name",
39
+ xmlContent: "Diagram Content",
40
+ },
41
+ basicTypeTest: {
42
+ stringValue: "String Value",
43
+ memoValue: "Memo Value",
44
+ intValue: "Int Value",
45
+ doubleValue: "Double Value",
46
+ dateTimeValue: "DateTime Value",
47
+ boolValue: "Bool Value",
48
+ imageValue: "Image Value",
49
+ },
50
+ types: {
51
+ Article: "Article",
52
+ Product: "Product",
53
+ BasicTypeTest: "Basic Type",
16
54
  },
17
55
  };
18
56
 
@@ -23,6 +61,44 @@ const zhTWExtended = {
23
61
  triggerRules: "觸發規則",
24
62
  triggerLogs: "觸發紀錄",
25
63
  mirrorConfigs: "鏡射設定",
64
+ // Sample translations
65
+ samples: "範例",
66
+ model: "資料模型",
67
+ basicTypeTest: "基本型別",
68
+ tiptapTest: "Tiptap 編輯器",
69
+ drawioTest: "Drawio 編輯器",
70
+ fullTextSearch: "全文檢索",
71
+ },
72
+ // Sample field translations
73
+ article: {
74
+ title: "標題",
75
+ content: "內容",
76
+ },
77
+ product: {
78
+ name: "名稱",
79
+ description: "描述",
80
+ },
81
+ tiptapTest: {
82
+ name: "名稱",
83
+ content: "內容",
84
+ },
85
+ drawioTest: {
86
+ name: "名稱",
87
+ xmlContent: "圖表內容",
88
+ },
89
+ basicTypeTest: {
90
+ stringValue: "字串",
91
+ memoValue: "長文字",
92
+ intValue: "整數",
93
+ doubleValue: "浮點數",
94
+ dateTimeValue: "日期時間",
95
+ boolValue: "布林值",
96
+ imageValue: "圖片",
97
+ },
98
+ types: {
99
+ Article: "文章",
100
+ Product: "產品",
101
+ BasicTypeTest: "基本型別",
26
102
  },
27
103
  };
28
104