@kevisual/api 0.0.1

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.
@@ -0,0 +1,434 @@
1
+ import { Query, BaseQuery } from '@kevisual/query';
2
+ import type { Result, DataOpts } from '@kevisual/query/query';
3
+ import { setBaseResponse } from '@kevisual/query/query';
4
+ import { LoginCacheStore, CacheStore } from './login-cache.ts';
5
+ import { Cache } from './login-cache.ts';
6
+
7
+ export type QueryLoginOpts = {
8
+ query?: Query;
9
+ isBrowser?: boolean;
10
+ onLoad?: () => void;
11
+ storage?: Storage;
12
+ cache: Cache;
13
+ };
14
+ export type QueryLoginData = {
15
+ username?: string;
16
+ password: string;
17
+ email?: string;
18
+ };
19
+ export type QueryLoginResult = {
20
+ accessToken: string;
21
+ refreshToken: string;
22
+ };
23
+
24
+ export class QueryLogin extends BaseQuery {
25
+ /**
26
+ * query login cache, 非实际操作, 一个cache的包裹模块
27
+ */
28
+ cacheStore: CacheStore;
29
+ isBrowser: boolean;
30
+ load?: boolean;
31
+ storage: Storage;
32
+ onLoad?: () => void;
33
+
34
+ constructor(opts?: QueryLoginOpts) {
35
+ super({
36
+ query: opts?.query || new Query(),
37
+ });
38
+ this.cacheStore = new LoginCacheStore({ name: 'login', cache: opts?.cache! });
39
+ this.isBrowser = opts?.isBrowser ?? true;
40
+ this.init();
41
+ this.onLoad = opts?.onLoad;
42
+ this.storage = opts?.storage || localStorage;
43
+ }
44
+ setQuery(query: Query) {
45
+ this.query = query;
46
+ }
47
+ private async init() {
48
+ await this.cacheStore.init();
49
+ this.load = true;
50
+ this.onLoad?.();
51
+ }
52
+ async post<T = any>(data: any, opts?: DataOpts) {
53
+ try {
54
+ return this.query.post<T>({ path: 'user', ...data }, opts);
55
+ } catch (error) {
56
+ console.log('error', error);
57
+ return {
58
+ code: 400,
59
+ } as any;
60
+ }
61
+ }
62
+ /**
63
+ * 登录,
64
+ * @param data
65
+ * @returns
66
+ */
67
+ async login(data: QueryLoginData) {
68
+ const res = await this.post<QueryLoginResult>({ key: 'login', ...data });
69
+ if (res.code === 200) {
70
+ const { accessToken, refreshToken } = res?.data || {};
71
+ this.storage.setItem('token', accessToken || '');
72
+ await this.beforeSetLoginUser({ accessToken, refreshToken });
73
+ }
74
+ return res;
75
+ }
76
+ /**
77
+ * 手机号登录
78
+ * @param data
79
+ * @returns
80
+ */
81
+ async loginByCode(data: { phone: string; code: string }) {
82
+ const res = await this.post<QueryLoginResult>({ path: 'sms', key: 'login', data });
83
+ if (res.code === 200) {
84
+ const { accessToken, refreshToken } = res?.data || {};
85
+ this.storage.setItem('token', accessToken || '');
86
+ await this.beforeSetLoginUser({ accessToken, refreshToken });
87
+ }
88
+ return res;
89
+ }
90
+ /**
91
+ * 设置token
92
+ * @param token
93
+ */
94
+ async setLoginToken(token: { accessToken: string; refreshToken: string }) {
95
+ const { accessToken, refreshToken } = token;
96
+ this.storage.setItem('token', accessToken || '');
97
+ await this.beforeSetLoginUser({ accessToken, refreshToken });
98
+ }
99
+ async loginByWechat(data: { code: string }) {
100
+ const res = await this.post<QueryLoginResult>({ path: 'wx', key: 'open-login', code: data.code });
101
+ if (res.code === 200) {
102
+ const { accessToken, refreshToken } = res?.data || {};
103
+ this.storage.setItem('token', accessToken || '');
104
+ await this.beforeSetLoginUser({ accessToken, refreshToken });
105
+ }
106
+ return res;
107
+ }
108
+ /**
109
+ * 检测微信登录,登陆成功后,调用onSuccess,否则调用onError
110
+ * @param param0
111
+ */
112
+ async checkWechat({ onSuccess, onError }: { onSuccess?: (res: QueryLoginResult) => void; onError?: (res: any) => void }) {
113
+ const url = new URL(window.location.href);
114
+ const code = url.searchParams.get('code');
115
+ const state = url.searchParams.get('state');
116
+ if (code && state) {
117
+ const res = await this.loginByWechat({ code });
118
+ if (res.code === 200) {
119
+ onSuccess?.(res.data);
120
+ } else {
121
+ onError?.(res);
122
+ }
123
+ }
124
+ }
125
+ /**
126
+ * 登陆成功,需要获取用户信息进行缓存
127
+ * @param param0
128
+ */
129
+ async beforeSetLoginUser({ accessToken, refreshToken, check401 }: { accessToken?: string; refreshToken?: string; check401?: boolean }) {
130
+ if (accessToken && refreshToken) {
131
+ const resUser = await this.getMe(accessToken, check401);
132
+ if (resUser.code === 200) {
133
+ const user = resUser.data;
134
+ if (user) {
135
+ this.cacheStore.setLoginUser({
136
+ user,
137
+ id: user.id,
138
+ accessToken,
139
+ refreshToken,
140
+ });
141
+ } else {
142
+ console.error('登录失败');
143
+ }
144
+ }
145
+ }
146
+ }
147
+ /**
148
+ * 刷新token
149
+ * @param refreshToken
150
+ * @returns
151
+ */
152
+ async queryRefreshToken(refreshToken?: string) {
153
+ const _refreshToken = refreshToken || this.cacheStore.getRefreshToken();
154
+ let data = { refreshToken: _refreshToken };
155
+ if (!_refreshToken) {
156
+ await this.cacheStore.clearCurrentUser();
157
+ return {
158
+ code: 401,
159
+ message: '请先登录',
160
+ data: {} as any,
161
+ };
162
+ }
163
+ return this.post(
164
+ { key: 'refreshToken', data },
165
+ {
166
+ afterResponse: async (response, ctx) => {
167
+ setBaseResponse(response);
168
+ return response as any;
169
+ },
170
+ },
171
+ );
172
+ }
173
+ /**
174
+ * 检查401错误,并刷新token, 如果refreshToken存在,则刷新token, 否则返回401
175
+ * 拦截请求,请使用run401Action, 不要直接使用 afterCheck401ToRefreshToken
176
+ * @param response
177
+ * @param ctx
178
+ * @param refetch
179
+ * @returns
180
+ */
181
+ async afterCheck401ToRefreshToken(response: Result, ctx?: { req?: any; res?: any; fetch?: any }, refetch?: boolean) {
182
+ const that = this;
183
+ if (response?.code === 401) {
184
+ const hasRefreshToken = await that.cacheStore.getRefreshToken();
185
+ if (hasRefreshToken) {
186
+ const res = await that.queryRefreshToken(hasRefreshToken);
187
+ if (res.code === 200) {
188
+ const { accessToken, refreshToken } = res?.data || {};
189
+ that.storage.setItem('token', accessToken || '');
190
+ await that.beforeSetLoginUser({ accessToken, refreshToken, check401: false });
191
+ if (refetch && ctx && ctx.req && ctx.req.url && ctx.fetch) {
192
+ await new Promise((resolve) => setTimeout(resolve, 1500));
193
+ const url = ctx.req?.url;
194
+ const body = ctx.req?.body;
195
+ const headers = ctx.req?.headers;
196
+ const res = await ctx.fetch(url, {
197
+ method: 'POST',
198
+ body: body,
199
+ headers: { ...headers, Authorization: `Bearer ${accessToken}` },
200
+ });
201
+ setBaseResponse(res);
202
+ return res;
203
+ }
204
+ } else {
205
+ that.storage.removeItem('token');
206
+ await that.cacheStore.clearCurrentUser();
207
+ }
208
+ return res;
209
+ }
210
+ }
211
+ return response as any;
212
+ }
213
+ /**
214
+ * 一个简单的401处理, 如果401,则刷新token, 如果refreshToken不存在,则返回401
215
+ * refetch 是否重新请求, 会有bug,无限循环,按需要使用
216
+ * TODO:
217
+ * @param response
218
+ * @param ctx
219
+ * @param opts
220
+ * @returns
221
+ */
222
+ async run401Action(
223
+ response: Result,
224
+ ctx?: { req?: any; res?: any; fetch?: any },
225
+ opts?: {
226
+ /**
227
+ * 是否重新请求, 会有bug,无限循环,按需要使用
228
+ */
229
+ refetch?: boolean;
230
+ /**
231
+ * check之后的回调
232
+ */
233
+ afterCheck?: (res: Result) => any;
234
+ /**
235
+ * 401处理后, 还是401, 则回调
236
+ */
237
+ afterAlso401?: (res: Result) => any;
238
+ },
239
+ ) {
240
+ const that = this;
241
+ const refetch = opts?.refetch ?? false;
242
+ if (response?.code === 401) {
243
+ if (that.query.stop === true) {
244
+ return { code: 500, success: false, message: 'refresh token loading...' };
245
+ }
246
+ that.query.stop = true;
247
+ const res = await that.afterCheck401ToRefreshToken(response, ctx, refetch);
248
+ that.query.stop = false;
249
+ opts?.afterCheck?.(res);
250
+ if (res.code === 401) {
251
+ opts?.afterAlso401?.(res);
252
+ }
253
+ return res;
254
+ } else {
255
+ return response as any;
256
+ }
257
+ }
258
+ /**
259
+ * 获取用户信息
260
+ * @param token
261
+ * @returns
262
+ */
263
+ async getMe(token?: string, check401: boolean = true) {
264
+ const _token = token || this.storage.getItem('token');
265
+ const that = this;
266
+ return that.post(
267
+ { key: 'me' },
268
+ {
269
+ beforeRequest: async (config) => {
270
+ if (config.headers) {
271
+ config.headers['Authorization'] = `Bearer ${_token}`;
272
+ }
273
+ if (!_token) {
274
+ return false;
275
+ }
276
+ return config;
277
+ },
278
+ afterResponse: async (response, ctx) => {
279
+ if (response?.code === 401 && check401 && !token) {
280
+ return await that.afterCheck401ToRefreshToken(response, ctx);
281
+ }
282
+ return response as any;
283
+ },
284
+ },
285
+ );
286
+ }
287
+ /**
288
+ * 检查本地用户,如果本地用户存在,则返回本地用户,否则返回null
289
+ * @returns
290
+ */
291
+ async checkLocalUser() {
292
+ const user = await this.cacheStore.getCurrentUser();
293
+ if (user) {
294
+ return user;
295
+ }
296
+ return null;
297
+ }
298
+ /**
299
+ * 检查本地token是否存在,简单的判断是否已经属于登陆状态
300
+ * @returns
301
+ */
302
+ async checkLocalToken() {
303
+ const token = this.storage.getItem('token');
304
+ return !!token;
305
+ }
306
+ /**
307
+ * 检查本地用户列表
308
+ * @returns
309
+ */
310
+ async getToken() {
311
+ const token = this.storage.getItem('token');
312
+ return token || '';
313
+ }
314
+ async beforeRequest(opts: any = {}) {
315
+ const token = this.storage.getItem('token');
316
+ if (token) {
317
+ opts.headers = { ...opts.headers, Authorization: `Bearer ${token}` };
318
+ }
319
+ return opts;
320
+ }
321
+ /**
322
+ * 请求更新,切换用户, 使用switchUser
323
+ * @param username
324
+ * @returns
325
+ */
326
+ private async postSwitchUser(username: string) {
327
+ return this.post({ key: 'switchCheck', data: { username } });
328
+ }
329
+ /**
330
+ * 切换用户
331
+ * @param username
332
+ * @returns
333
+ */
334
+ async switchUser(username: string) {
335
+ const localUserList = await this.cacheStore.getCurrentUserList();
336
+ const user = localUserList.find((userItem) => userItem.user!.username === username);
337
+ if (user) {
338
+ this.storage.setItem('token', user.accessToken || '');
339
+ await this.beforeSetLoginUser({ accessToken: user.accessToken, refreshToken: user.refreshToken });
340
+ return {
341
+ code: 200,
342
+ data: {
343
+ accessToken: user.accessToken,
344
+ refreshToken: user.refreshToken,
345
+ },
346
+ success: true,
347
+ message: '切换用户成功',
348
+ };
349
+ }
350
+ const res = await this.postSwitchUser(username);
351
+
352
+ if (res.code === 200) {
353
+ const { accessToken, refreshToken } = res?.data || {};
354
+ this.storage.setItem('token', accessToken || '');
355
+ await this.beforeSetLoginUser({ accessToken, refreshToken });
356
+ }
357
+ return res;
358
+ }
359
+ /**
360
+ * 退出登陆,去掉token, 并删除缓存
361
+ * @returns
362
+ */
363
+ async logout() {
364
+ this.storage.removeItem('token');
365
+ const users = await this.cacheStore.getCurrentUserList();
366
+ const tokens = users
367
+ .map((user) => {
368
+ return user?.accessToken;
369
+ })
370
+ .filter(Boolean);
371
+ this.cacheStore.delValue();
372
+ return this.post<Result>({ key: 'logout', data: { tokens } });
373
+ }
374
+ /**
375
+ * 检查用户名的组,这个用户是否存在
376
+ * @param username
377
+ * @returns
378
+ */
379
+ async hasUser(username: string) {
380
+ const that = this;
381
+ return this.post<Result>(
382
+ {
383
+ path: 'org',
384
+ key: 'hasUser',
385
+ data: {
386
+ username,
387
+ },
388
+ },
389
+ {
390
+ afterResponse: async (response, ctx) => {
391
+ if (response?.code === 401) {
392
+ const res = await that.afterCheck401ToRefreshToken(response, ctx, true);
393
+ return res;
394
+ }
395
+ return response as any;
396
+ },
397
+ },
398
+ );
399
+ }
400
+ /**
401
+ * 检查登录状态
402
+ * @param token
403
+ * @returns
404
+ */
405
+ async checkLoginStatus(token: string) {
406
+ const res = await this.post({
407
+ path: 'user',
408
+ key: 'checkLoginStatus',
409
+ loginToken: token,
410
+ });
411
+ if (res.code === 200) {
412
+ const accessToken = res.data?.accessToken;
413
+ this.storage.setItem('token', accessToken || '');
414
+ await this.beforeSetLoginUser({ accessToken, refreshToken: res.data?.refreshToken });
415
+ return res;
416
+ }
417
+ return false;
418
+ }
419
+ /**
420
+ * 使用web登录,创建url地址, 需要MD5和jsonwebtoken
421
+ */
422
+ loginWithWeb(baseURL: string, { MD5, jsonwebtoken }: { MD5: any; jsonwebtoken: any }) {
423
+ const randomId = Math.random().toString(36).substring(2, 15);
424
+ const timestamp = Date.now();
425
+ const tokenSecret = 'xiao' + randomId;
426
+ const sign = MD5(`${tokenSecret}${timestamp}`).toString();
427
+ const token = jsonwebtoken.sign({ randomId, timestamp, sign }, tokenSecret, {
428
+ // 10分钟过期
429
+ expiresIn: 60 * 10, // 10分钟
430
+ });
431
+ const url = `${baseURL}/api/router?path=user&key=webLogin&p&loginToken=${token}&sign=${sign}&randomId=${randomId}`;
432
+ return { url, token, tokenSecret };
433
+ }
434
+ }
@@ -0,0 +1,154 @@
1
+ import { Query } from '@kevisual/query';
2
+ import type { Result, DataOpts } from '@kevisual/query/query';
3
+
4
+ export type SimpleObject = Record<string, any>;
5
+ export const markType = ['simple', 'md', 'mdx', 'wallnote', 'excalidraw', 'chat'] as const;
6
+ export type MarkType = (typeof markType)[number];
7
+ export type MarkData = {
8
+ nodes?: any[];
9
+ edges?: any[];
10
+ elements?: any[];
11
+ permission?: any;
12
+
13
+ [key: string]: any;
14
+ };
15
+ export type Mark = {
16
+ id: string;
17
+ title: string;
18
+ description: string;
19
+ markType: MarkType;
20
+ link: string;
21
+ data?: MarkData;
22
+ uid: string;
23
+ puid: string;
24
+ summary: string;
25
+ thumbnail?: string;
26
+ tags: string[];
27
+ createdAt: string;
28
+ updatedAt: string;
29
+ version: number;
30
+ };
31
+ export type ShowMarkPick = Pick<Mark, 'id' | 'title' | 'description' | 'summary' | 'link' | 'tags' | 'thumbnail' | 'updatedAt'>;
32
+
33
+ export type SearchOpts = {
34
+ page?: number;
35
+ pageSize?: number;
36
+ search?: string;
37
+ sort?: string; // DESC, ASC
38
+ markType?: MarkType; // 类型
39
+ [key: string]: any;
40
+ };
41
+
42
+ export type QueryMarkOpts<T extends SimpleObject = SimpleObject> = {
43
+ query?: Query;
44
+ isBrowser?: boolean;
45
+ onLoad?: () => void;
46
+ } & T;
47
+
48
+ export type ResultMarkList = {
49
+ list: Mark[];
50
+ pagination: {
51
+ pageSize: number;
52
+ current: number;
53
+ total: number;
54
+ };
55
+ };
56
+ export type QueryMarkData = {
57
+ id?: string;
58
+ title?: string;
59
+ description?: string;
60
+ [key: string]: any;
61
+ };
62
+ export type QueryMarkResult = {
63
+ accessToken: string;
64
+ refreshToken: string;
65
+ };
66
+
67
+ export class QueryMarkBase<T extends SimpleObject = SimpleObject> {
68
+ query: Query;
69
+ isBrowser: boolean;
70
+ load?: boolean;
71
+ storage?: Storage;
72
+ onLoad?: () => void;
73
+
74
+ constructor(opts?: QueryMarkOpts<T>) {
75
+ this.query = opts?.query || new Query();
76
+ this.isBrowser = opts?.isBrowser ?? true;
77
+ this.init();
78
+ this.onLoad = opts?.onLoad;
79
+ }
80
+ setQuery(query: Query) {
81
+ this.query = query;
82
+ }
83
+ private async init() {
84
+ this.load = true;
85
+ this.onLoad?.();
86
+ }
87
+
88
+ async post<T = Result<any>>(data: any, opts?: DataOpts): Promise<T> {
89
+ try {
90
+ return this.query.post({ path: 'mark', ...data }, opts) as Promise<T>;
91
+ } catch (error) {
92
+ console.log('error', error);
93
+ return {
94
+ code: 400,
95
+ } as any;
96
+ }
97
+ }
98
+
99
+ async getMarkList(search: SearchOpts, opts?: DataOpts) {
100
+ return this.post<Result<ResultMarkList>>({ key: 'list', ...search }, opts);
101
+ }
102
+
103
+ async getMark(id: string, opts?: DataOpts) {
104
+ return this.post<Result<Mark>>({ key: 'get', id }, opts);
105
+ }
106
+ async getVersion(id: string, opts?: DataOpts) {
107
+ return this.post<Result<{ version: number; id: string }>>({ key: 'getVersion', id }, opts);
108
+ }
109
+ /**
110
+ * 检查版本
111
+ * 当需要更新时,返回true
112
+ * @param id
113
+ * @param version
114
+ * @param opts
115
+ * @returns
116
+ */
117
+ async checkVersion(id: string, version?: number, opts?: DataOpts) {
118
+ if (!version) {
119
+ return true;
120
+ }
121
+ const res = await this.getVersion(id, opts);
122
+ if (res.code === 200) {
123
+ if (res.data!.version > version) {
124
+ return true;
125
+ }
126
+ return false;
127
+ }
128
+ return true;
129
+ }
130
+
131
+ async updateMark(data: any, opts?: DataOpts) {
132
+ return this.post<Result<Mark>>({ key: 'update', data }, opts);
133
+ }
134
+
135
+ async deleteMark(id: string, opts?: DataOpts) {
136
+ return this.post<Result<Mark>>({ key: 'delete', id }, opts);
137
+ }
138
+ }
139
+ export class QueryMark extends QueryMarkBase<SimpleObject> {
140
+ markType: string;
141
+ constructor(opts?: QueryMarkOpts & { markType?: MarkType }) {
142
+ super(opts);
143
+ this.markType = opts?.markType || 'simple';
144
+ }
145
+ async getMarkList(search?: SearchOpts, opts?: DataOpts) {
146
+ return this.post<Result<ResultMarkList>>({ key: 'list', ...search, markType: this.markType }, opts);
147
+ }
148
+ async updateMark(data: any, opts?: DataOpts) {
149
+ if (!data.id) {
150
+ data.markType = this.markType || 'simple';
151
+ }
152
+ return super.updateMark(data, opts);
153
+ }
154
+ }
@@ -0,0 +1,71 @@
1
+ import { adapter, DataOpts, Result } from '@kevisual/query';
2
+
3
+ type QueryResourcesOptions = {
4
+ prefix?: string;
5
+ storage?: Storage;
6
+ username?: string;
7
+ [key: string]: any;
8
+ };
9
+ export class QueryResources {
10
+ prefix: string; // root/resources
11
+ storage: Storage;
12
+ constructor(opts: QueryResourcesOptions) {
13
+ if (opts.username) {
14
+ this.prefix = `/${opts.username}/resources/`;
15
+ } else {
16
+ this.prefix = opts.prefix || '';
17
+ }
18
+ this.storage = opts.storage || localStorage;
19
+ }
20
+ setUsername(username: string) {
21
+ this.prefix = `/${username}/resources/`;
22
+ }
23
+ header(headers?: Record<string, string>, json = true): Record<string, string> {
24
+ const token = this.storage.getItem('token');
25
+ const _headers: Record<string, string> = {
26
+ 'Content-Type': 'application/json',
27
+ ...headers,
28
+ };
29
+ if (!json) {
30
+ delete _headers['Content-Type'];
31
+ }
32
+ if (!token) {
33
+ return _headers;
34
+ }
35
+ return {
36
+ ..._headers,
37
+ Authorization: `Bearer ${token}`,
38
+ };
39
+ }
40
+ async get(data: any, opts: DataOpts): Promise<any> {
41
+ return adapter({
42
+ url: opts.url!,
43
+ method: 'GET',
44
+ body: data,
45
+ ...opts,
46
+ headers: this.header(opts?.headers),
47
+ });
48
+ }
49
+ async getList(prefix: string, data?: { recursive?: boolean }, opts?: DataOpts): Promise<Result<any[]>> {
50
+ return this.get(data, {
51
+ url: `${this.prefix}${prefix}`,
52
+ body: data,
53
+ ...opts,
54
+ });
55
+ }
56
+ async fetchFile(filepath: string, opts?: DataOpts): Promise<Result<any>> {
57
+ return fetch(`${this.prefix}${filepath}`, {
58
+ method: 'GET',
59
+ headers: this.header(opts?.headers, false),
60
+ }).then(async (res) => {
61
+ if (!res.ok) {
62
+ return {
63
+ code: 500,
64
+ success: false,
65
+ message: `Failed to fetch file: ${res.status} ${res.statusText}`,
66
+ } as Result<any>;
67
+ }
68
+ return { code: 200, data: await res.text(), success: true } as Result<any>;
69
+ });
70
+ }
71
+ }
@@ -0,0 +1,27 @@
1
+ import { QueryUtil } from '@/query/index.ts';
2
+
3
+ export const shopDefine = QueryUtil.create({
4
+ getRegistry: {
5
+ path: 'shop',
6
+ key: 'get-registry',
7
+ description: '获取应用商店注册表信息',
8
+ },
9
+
10
+ listInstalled: {
11
+ path: 'shop',
12
+ key: 'list-installed',
13
+ description: '列出当前已安装的所有应用',
14
+ },
15
+
16
+ install: {
17
+ path: 'shop',
18
+ key: 'install',
19
+ description: '安装指定的应用,可以指定 id、type、force 和 yes 参数',
20
+ },
21
+
22
+ uninstall: {
23
+ path: 'shop',
24
+ key: 'uninstall',
25
+ description: '卸载指定的应用,可以指定 id 和 type 参数',
26
+ },
27
+ });
@@ -0,0 +1,17 @@
1
+ import { shopDefine } from './defines/query-shop-define.ts';
2
+
3
+ import { BaseQuery, DataOpts, Query } from '@kevisual/query/query';
4
+
5
+ export { shopDefine };
6
+
7
+ export class QueryShop<T extends Query = Query> extends BaseQuery<T, typeof shopDefine> {
8
+ constructor(opts?: { query: T }) {
9
+ super({
10
+ query: opts?.query!,
11
+ queryDefine: shopDefine,
12
+ });
13
+ }
14
+ getInstall(data: any, opts?: DataOpts) {
15
+ return this.queryDefine.queryChain('install').post(data, opts);
16
+ }
17
+ }