adtec-core-package 0.2.8 → 0.3.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.
Files changed (54) hide show
  1. package/README.en.md +36 -36
  2. package/package.json +2 -2
  3. package/src/api/BasicApi.ts +17 -0
  4. package/src/api/DocumentApi.ts +18 -18
  5. package/src/api/SysDictCacheApi.ts +26 -28
  6. package/src/api/SysUserApi.ts +35 -0
  7. package/src/api/framework.ts +12 -12
  8. package/src/assets/style/ant.scss +19 -19
  9. package/src/assets/style/index.less +180 -180
  10. package/src/components/ElFlex/ElFlex.vue +297 -297
  11. package/src/components/Search/ElSearch.vue +149 -132
  12. package/src/components/Table/ElTableColumnEdit.vue +218 -218
  13. package/src/components/Title/ElTitle.vue +49 -49
  14. package/src/components/autoToolTip/{index.vue → ElAutoToolTip.vue} +61 -61
  15. package/src/components/business/userSelect.vue +412 -0
  16. package/src/components/upload/FileView.vue +158 -158
  17. package/src/components/upload/FileViewComponents.vue +57 -57
  18. package/src/config/ElementPlusConfig.ts +95 -95
  19. package/src/css/elementUI/autocomplete.scss +89 -89
  20. package/src/css/elementUI/common/var.scss +1549 -1549
  21. package/src/css/elementUI/date-picker/picker.scss +219 -219
  22. package/src/css/elementUI/drawer.scss +164 -164
  23. package/src/css/elementUI/table.scss +694 -694
  24. package/src/css/elementUI/tabs.scss +659 -659
  25. package/src/directives/vKeydown.ts +93 -93
  26. package/src/hooks/useDictHooks.ts +77 -78
  27. package/src/hooks/useFileView.ts +34 -34
  28. package/src/hooks/useMessageHooks.ts +132 -132
  29. package/src/hooks/useResetRefHooks.ts +19 -19
  30. package/src/interface/BaseEntity.ts +28 -28
  31. package/src/interface/IMdmDept.ts +82 -0
  32. package/src/interface/IOrgDeptInfo.ts +12 -0
  33. package/src/interface/ISysDictDataCacheVo.ts +46 -46
  34. package/src/interface/ISysDictType.ts +37 -37
  35. package/src/interface/ISysMenuDataVo.ts +22 -22
  36. package/src/interface/ISysMenuInfoVo.ts +83 -83
  37. package/src/interface/ISysMenuOperationVo.ts +21 -21
  38. package/src/interface/ISysUploadFiles.ts +16 -16
  39. package/src/interface/ISysUserInfo.ts +70 -0
  40. package/src/interface/IUserPermissionVo.ts +34 -34
  41. package/src/interface/Message.ts +69 -69
  42. package/src/interface/PageData.ts +17 -17
  43. package/src/interface/ResponseData.ts +16 -16
  44. package/src/interface/dictMapType.ts +11 -0
  45. package/src/interface/enum/MessageEnum.ts +41 -41
  46. package/src/mixin/globalMixin.ts +7 -4
  47. package/src/packages/index.ts +18 -18
  48. package/src/packages/text.vue +13 -13
  49. package/src/plugins/plugins.ts +12 -12
  50. package/src/stores/dictStore.ts +27 -27
  51. package/src/stores/messageStore.ts +49 -49
  52. package/src/stores/storeConfig.ts +23 -23
  53. package/src/stores/userInfoStore.ts +31 -31
  54. package/src/utils/AxiosConfig.ts +216 -216
@@ -1,49 +1,49 @@
1
- import { defineStore } from 'pinia'
2
- import type { ISysMessageVo } from '../interface/Message.ts'
3
-
4
- /**
5
- * Create by丁盼
6
- * 说明: 用户消息Store
7
- * 创建时间: 2024/9/4 下午4:25
8
- * 修改时间: 2024/9/4 下午4:25
9
- */
10
- export const messageStore = defineStore({
11
- id: 'messageStore',
12
- state: () => ({
13
- //@ts-ignore
14
- messageList: [] as ISysMessageVo[],
15
- }),
16
- getters: {
17
- /**
18
- * 获取用户消息
19
- */
20
- getMessageList(): ISysMessageVo[] {
21
- return this.messageList
22
- },
23
- },
24
- actions: {
25
- /**
26
- * 设置系统消息
27
- * @param sysMessage
28
- */
29
- setSysMessage(sysMessage: ISysMessageVo) {
30
- this.messageList.unshift(sysMessage)
31
- },
32
- /**
33
- * 清空消息
34
- */
35
- clearSysMessage() {
36
- this.messageList = []
37
- },
38
- /**
39
- * 设置消息状态
40
- * @param sysMessage
41
- */
42
- setSysMessageStatus(sysMessage: ISysMessageVo) {
43
- const index = this.messageList.findIndex((item) => item.id === sysMessage.id)
44
- if (index !== -1) {
45
- this.messageList.splice(index, 1)
46
- }
47
- },
48
- },
49
- })
1
+ import { defineStore } from 'pinia'
2
+ import type { ISysMessageVo } from '../interface/Message.ts'
3
+
4
+ /**
5
+ * Create by丁盼
6
+ * 说明: 用户消息Store
7
+ * 创建时间: 2024/9/4 下午4:25
8
+ * 修改时间: 2024/9/4 下午4:25
9
+ */
10
+ export const messageStore = defineStore({
11
+ id: 'messageStore',
12
+ state: () => ({
13
+ //@ts-ignore
14
+ messageList: [] as ISysMessageVo[],
15
+ }),
16
+ getters: {
17
+ /**
18
+ * 获取用户消息
19
+ */
20
+ getMessageList(): ISysMessageVo[] {
21
+ return this.messageList
22
+ },
23
+ },
24
+ actions: {
25
+ /**
26
+ * 设置系统消息
27
+ * @param sysMessage
28
+ */
29
+ setSysMessage(sysMessage: ISysMessageVo) {
30
+ this.messageList.unshift(sysMessage)
31
+ },
32
+ /**
33
+ * 清空消息
34
+ */
35
+ clearSysMessage() {
36
+ this.messageList = []
37
+ },
38
+ /**
39
+ * 设置消息状态
40
+ * @param sysMessage
41
+ */
42
+ setSysMessageStatus(sysMessage: ISysMessageVo) {
43
+ const index = this.messageList.findIndex((item) => item.id === sysMessage.id)
44
+ if (index !== -1) {
45
+ this.messageList.splice(index, 1)
46
+ }
47
+ },
48
+ },
49
+ })
@@ -1,23 +1,23 @@
1
- import { createPinia } from 'pinia'
2
- import { storePlugin } from 'pinia-plugin-store'
3
- import Utf8 from 'crypto-js/enc-utf8'
4
- import Base64 from 'crypto-js/enc-base64'
5
-
6
- const store = createPinia()
7
- function encrypt(value: string): string {
8
- return Base64.stringify(Utf8.parse(value))
9
- }
10
- function decrypt(value: string): string {
11
- return Base64.parse(value).toString(Utf8)
12
- }
13
-
14
- const plugin = storePlugin({
15
- stores: ['userInfoStore', 'messageStore', 'dictStore'],
16
- // use alone storage
17
- // stores: [{name:'theme_store',storage: localStorage}]
18
- storage: sessionStorage, //default storage
19
- encrypt,
20
- decrypt,
21
- })
22
- store.use(plugin)
23
- export default store
1
+ import { createPinia } from 'pinia'
2
+ import { storePlugin } from 'pinia-plugin-store'
3
+ import Utf8 from 'crypto-js/enc-utf8'
4
+ import Base64 from 'crypto-js/enc-base64'
5
+
6
+ const store = createPinia()
7
+ function encrypt(value: string): string {
8
+ return Base64.stringify(Utf8.parse(value))
9
+ }
10
+ function decrypt(value: string): string {
11
+ return Base64.parse(value).toString(Utf8)
12
+ }
13
+
14
+ const plugin = storePlugin({
15
+ stores: ['userInfoStore', 'messageStore', 'dictStore'],
16
+ // use alone storage
17
+ // stores: [{name:'theme_store',storage: localStorage}]
18
+ storage: sessionStorage, //default storage
19
+ encrypt,
20
+ decrypt,
21
+ })
22
+ store.use(plugin)
23
+ export default store
@@ -1,31 +1,31 @@
1
- /**
2
- * Create by丁盼
3
- * 说明: userInfoStore
4
- * 创建时间: 2025/1/15 11:40
5
- * 修改时间: 2025/1/15 11:40
6
- */
7
- import { defineStore } from 'pinia'
8
- import type { IUserPermissionVo } from '../interface/IUserPermissionVo'
9
-
10
- export const userInfoStore = defineStore({
11
- id: 'userInfoStore',
12
- state: () => ({
13
- //@ts-ignore
14
- user: null as IUserPermissionVo
15
- }),
16
- getters: {
17
- getUserInfo(): IUserPermissionVo {
18
- return this.user as IUserPermissionVo
19
- }
20
- },
21
- actions: {
22
- setUserInfo(userInfo: IUserPermissionVo) {
23
- //@ts-ignore
24
- this.user = userInfo
25
- },
26
- clear() {
27
- //@ts-ignore
28
- this.user = null
29
- }
30
- }
31
- })
1
+ /**
2
+ * Create by丁盼
3
+ * 说明: userInfoStore
4
+ * 创建时间: 2025/1/15 11:40
5
+ * 修改时间: 2025/1/15 11:40
6
+ */
7
+ import { defineStore } from 'pinia'
8
+ import type { IUserPermissionVo } from '../interface/IUserPermissionVo'
9
+
10
+ export const userInfoStore = defineStore({
11
+ id: 'userInfoStore',
12
+ state: () => ({
13
+ //@ts-ignore
14
+ user: null as IUserPermissionVo
15
+ }),
16
+ getters: {
17
+ getUserInfo(): IUserPermissionVo {
18
+ return this.user as IUserPermissionVo
19
+ }
20
+ },
21
+ actions: {
22
+ setUserInfo(userInfo: IUserPermissionVo) {
23
+ //@ts-ignore
24
+ this.user = userInfo
25
+ },
26
+ clear() {
27
+ //@ts-ignore
28
+ this.user = null
29
+ }
30
+ }
31
+ })
@@ -1,216 +1,216 @@
1
- /**
2
- * Create by丁盼
3
- * 说明: AxiosConfig
4
- * 创建时间: 2025/1/14 16:58
5
- * 修改时间: 2025/1/14 16:58
6
- */
7
- import axios, { type AxiosResponse } from 'axios'
8
- import type { ResponseData } from '../interface/ResponseData'
9
- import { v4 as uuidv4 } from 'uuid'
10
- import encrypt from './encrypt'
11
- // @ts-ignore
12
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
13
-
14
- import { Md5 } from 'ts-md5'
15
- import { ElMessage } from 'element-plus'
16
- import { useEventBus } from '@vueuse/core'
17
- /** 是否正在刷新的标志 */
18
- let isRefreshing = false
19
- const loginAgainBus = useEventBus<string>('loginAgainBus')
20
- /** 存储请求的数组 */
21
- let requests: any[] = []
22
- const request = axios.create({
23
- timeout: 60000,
24
- validateStatus(status) {
25
- return status >= 200 && status < 500
26
- },
27
- })
28
- function setHeaders(config: any) {
29
- //数据防止篡改
30
- const Authorization = sessionStorage.getItem('Authorization')
31
- const signa = 'F2E49299-D0D2-4AA1-87A3-270272EA3D6A'
32
- let dataJson: string = ''
33
- if (config.method === 'get') {
34
- if (config.params) {
35
- if (typeof config.params === 'string') {
36
- dataJson = JSON.stringify(JSON.parse(config.params))
37
- } else {
38
- dataJson = JSON.stringify(config.params)
39
- }
40
- }
41
- dataJson += config.url
42
- } else {
43
- if (config.data) {
44
- if (typeof config.data === 'string') {
45
- dataJson = JSON.stringify(JSON.parse(config.data))
46
- } else {
47
- dataJson = JSON.stringify(config.data)
48
- }
49
- }
50
- }
51
-
52
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
53
- // @ts-ignore
54
- dataJson = dataJson
55
- .replaceAll('"', '')
56
- .replaceAll(',', '')
57
- .replaceAll(':', '')
58
- .replaceAll('.', '')
59
- .replaceAll('/', '')
60
- dataJson = dataJson.split('').sort().join('')
61
- config.headers.sessionId = sessionStorage.getItem('sessionId')
62
- config.headers.Authorization = Authorization
63
- config.headers.signa = Md5.hashStr(Authorization + signa + dataJson)
64
- config.headers.antiShakeKey = Md5.hashStr(dataJson)
65
- //防止重放 GUID+当前时间+TOKEN 加密
66
- const timeDifference = sessionStorage.getItem('timeDifference')
67
- const id = uuidv4()
68
- const localTime = new Date().getTime()
69
- const obj = { timeDifference: timeDifference, guid: id, localTime }
70
- config.headers.replayToken = encrypt.encrypt(JSON.stringify(obj))
71
- }
72
- //用于前端axios 请求防抖
73
- const debounceTokenCancel = new Map()
74
- request.interceptors.request.use(
75
- (config) => {
76
- setHeaders(config)
77
- // @ts-ignore
78
- if (config.isAntiShake) {
79
- // @ts-ignore
80
- const antiShakeTime = config.antiShakeTime
81
- //@ts-ignore
82
- const antiShakeKey = `${config.method}-${config.url}-${config.antiShakeKey}`
83
- const cancel = debounceTokenCancel.get(antiShakeKey)
84
- if (cancel) {
85
- cancel()
86
- }
87
- return new Promise((resolve) => {
88
- const timer = setTimeout(() => {
89
- clearTimeout(timer)
90
- resolve(config)
91
- }, antiShakeTime)
92
- debounceTokenCancel.set(antiShakeKey, () => {
93
- clearTimeout(timer)
94
- })
95
- })
96
- } else {
97
- return config
98
- }
99
- },
100
- (error: any) => {
101
- return Promise.resolve(error)
102
- },
103
- )
104
-
105
- // response 拦截
106
- request.interceptors.response.use(
107
- async (response: AxiosResponse<ResponseData>) => {
108
- const { status, data, config } = response
109
- if (status === 200) {
110
- if (data.code === '0') {
111
- return Promise.resolve(response)
112
- } else if (data.code == '10508') {
113
- isRefreshing = true
114
- } else if (data.code == '10401') {
115
- //TOKen过期
116
- if (!isRefreshing) {
117
- isRefreshing = true
118
- const rToken = sessionStorage.getItem('refreshToken')
119
- const data = await request.post('/api/system/login/refreshToken', {
120
- rToken: rToken,
121
- })
122
- if (data.data.code === '0') {
123
- sessionStorage.setItem('Authorization', data.data.data.token)
124
- sessionStorage.setItem('refreshToken', data.data.data.refreshToken)
125
- const r = request(config)
126
- requests.forEach((cb) => {
127
- cb.fn()
128
- })
129
- isRefreshing = false
130
- requests = []
131
- return r
132
- } else {
133
- isRefreshing = false
134
- requests = []
135
- ElMessage.warning({
136
- showClose: true,
137
- message: '用户信息已过期,请重新登录',
138
- grouping: true,
139
- })
140
- loginAgainBus.emit('loginAgain')
141
- throw {
142
- code: '10508',
143
- msg: '用户信息已过期,请重新登录',
144
- url: config.url,
145
- }
146
- }
147
- } else {
148
- return new Promise((resolve) => {
149
- requests.push({
150
- fn: () => {
151
- //需要重新设置signa
152
- // setHeaders(config)
153
- resolve(request(config))
154
- },
155
- url: config.url,
156
- })
157
- })
158
- }
159
- } else {
160
- return Promise.resolve(response)
161
- }
162
- }
163
- return Promise.resolve(response)
164
- },
165
- (error: any) => {
166
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
167
- // @ts-ignore
168
- return Promise.reject(error)
169
- },
170
- )
171
- export default <T = any>(config: any) => {
172
- return request(config)
173
- .then((res: any) => {
174
- if (res.status !== 200) {
175
- throw {
176
- code: res.status + '',
177
- msg: res.status + res.statusText,
178
- url: config.url,
179
- }
180
- } else {
181
- const data = res.data as ResponseData<T>
182
- if (data.code === '0') {
183
- return data.data
184
- } else if (data.code == '10508') {
185
- ElMessage.warning({
186
- showClose: true,
187
- message: '用户信息已过期,请重新登录',
188
- grouping: true,
189
- })
190
- loginAgainBus.emit('loginAgain')
191
- throw {
192
- code: data.code,
193
- msg: data.msg,
194
- url: config.url,
195
- }
196
- } else {
197
- throw {
198
- code: data.code,
199
- msg: data.msg,
200
- url: config.url,
201
- }
202
- }
203
- }
204
- })
205
- .catch((error) => {
206
- if (error.msg) {
207
- throw error
208
- } else {
209
- throw {
210
- code: error.response.status + '',
211
- msg: error.message,
212
- url: config.url,
213
- }
214
- }
215
- })
216
- }
1
+ /**
2
+ * Create by丁盼
3
+ * 说明: AxiosConfig
4
+ * 创建时间: 2025/1/14 16:58
5
+ * 修改时间: 2025/1/14 16:58
6
+ */
7
+ import axios, { type AxiosResponse } from 'axios'
8
+ import type { ResponseData } from '../interface/ResponseData'
9
+ import { v4 as uuidv4 } from 'uuid'
10
+ import encrypt from './encrypt'
11
+ // @ts-ignore
12
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
13
+
14
+ import { Md5 } from 'ts-md5'
15
+ import { ElMessage } from 'element-plus'
16
+ import { useEventBus } from '@vueuse/core'
17
+ /** 是否正在刷新的标志 */
18
+ let isRefreshing = false
19
+ const loginAgainBus = useEventBus<string>('loginAgainBus')
20
+ /** 存储请求的数组 */
21
+ let requests: any[] = []
22
+ const request = axios.create({
23
+ timeout: 60000,
24
+ validateStatus(status) {
25
+ return status >= 200 && status < 500
26
+ },
27
+ })
28
+ function setHeaders(config: any) {
29
+ //数据防止篡改
30
+ const Authorization = sessionStorage.getItem('Authorization')
31
+ const signa = 'F2E49299-D0D2-4AA1-87A3-270272EA3D6A'
32
+ let dataJson: string = ''
33
+ if (config.method === 'get') {
34
+ if (config.params) {
35
+ if (typeof config.params === 'string') {
36
+ dataJson = JSON.stringify(JSON.parse(config.params))
37
+ } else {
38
+ dataJson = JSON.stringify(config.params)
39
+ }
40
+ }
41
+ dataJson += config.url
42
+ } else {
43
+ if (config.data) {
44
+ if (typeof config.data === 'string') {
45
+ dataJson = JSON.stringify(JSON.parse(config.data))
46
+ } else {
47
+ dataJson = JSON.stringify(config.data)
48
+ }
49
+ }
50
+ }
51
+
52
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
53
+ // @ts-ignore
54
+ dataJson = dataJson
55
+ .replaceAll('"', '')
56
+ .replaceAll(',', '')
57
+ .replaceAll(':', '')
58
+ .replaceAll('.', '')
59
+ .replaceAll('/', '')
60
+ dataJson = dataJson.split('').sort().join('')
61
+ config.headers.sessionId = sessionStorage.getItem('sessionId')
62
+ config.headers.Authorization = Authorization
63
+ config.headers.signa = Md5.hashStr(Authorization + signa + dataJson)
64
+ config.headers.antiShakeKey = Md5.hashStr(dataJson)
65
+ //防止重放 GUID+当前时间+TOKEN 加密
66
+ const timeDifference = sessionStorage.getItem('timeDifference')
67
+ const id = uuidv4()
68
+ const localTime = new Date().getTime()
69
+ const obj = { timeDifference: timeDifference, guid: id, localTime }
70
+ config.headers.replayToken = encrypt.encrypt(JSON.stringify(obj))
71
+ }
72
+ //用于前端axios 请求防抖
73
+ const debounceTokenCancel = new Map()
74
+ request.interceptors.request.use(
75
+ (config) => {
76
+ setHeaders(config)
77
+ // @ts-ignore
78
+ if (config.isAntiShake) {
79
+ // @ts-ignore
80
+ const antiShakeTime = config.antiShakeTime
81
+ //@ts-ignore
82
+ const antiShakeKey = `${config.method}-${config.url}-${config.antiShakeKey}`
83
+ const cancel = debounceTokenCancel.get(antiShakeKey)
84
+ if (cancel) {
85
+ cancel()
86
+ }
87
+ return new Promise((resolve) => {
88
+ const timer = setTimeout(() => {
89
+ clearTimeout(timer)
90
+ resolve(config)
91
+ }, antiShakeTime)
92
+ debounceTokenCancel.set(antiShakeKey, () => {
93
+ clearTimeout(timer)
94
+ })
95
+ })
96
+ } else {
97
+ return config
98
+ }
99
+ },
100
+ (error: any) => {
101
+ return Promise.resolve(error)
102
+ },
103
+ )
104
+
105
+ // response 拦截
106
+ request.interceptors.response.use(
107
+ async (response: AxiosResponse<ResponseData>) => {
108
+ const { status, data, config } = response
109
+ if (status === 200) {
110
+ if (data.code === '0') {
111
+ return Promise.resolve(response)
112
+ } else if (data.code == '10508') {
113
+ isRefreshing = true
114
+ } else if (data.code == '10401') {
115
+ //TOKen过期
116
+ if (!isRefreshing) {
117
+ isRefreshing = true
118
+ const rToken = sessionStorage.getItem('refreshToken')
119
+ const data = await request.post('/api/system/login/refreshToken', {
120
+ rToken: rToken,
121
+ })
122
+ if (data.data.code === '0') {
123
+ sessionStorage.setItem('Authorization', data.data.data.token)
124
+ sessionStorage.setItem('refreshToken', data.data.data.refreshToken)
125
+ const r = request(config)
126
+ requests.forEach((cb) => {
127
+ cb.fn()
128
+ })
129
+ isRefreshing = false
130
+ requests = []
131
+ return r
132
+ } else {
133
+ isRefreshing = false
134
+ requests = []
135
+ ElMessage.warning({
136
+ showClose: true,
137
+ message: '用户信息已过期,请重新登录',
138
+ grouping: true,
139
+ })
140
+ loginAgainBus.emit('loginAgain')
141
+ throw {
142
+ code: '10508',
143
+ msg: '用户信息已过期,请重新登录',
144
+ url: config.url,
145
+ }
146
+ }
147
+ } else {
148
+ return new Promise((resolve) => {
149
+ requests.push({
150
+ fn: () => {
151
+ //需要重新设置signa
152
+ // setHeaders(config)
153
+ resolve(request(config))
154
+ },
155
+ url: config.url,
156
+ })
157
+ })
158
+ }
159
+ } else {
160
+ return Promise.resolve(response)
161
+ }
162
+ }
163
+ return Promise.resolve(response)
164
+ },
165
+ (error: any) => {
166
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
167
+ // @ts-ignore
168
+ return Promise.reject(error)
169
+ },
170
+ )
171
+ export default <T = any>(config: any) => {
172
+ return request(config)
173
+ .then((res: any) => {
174
+ if (res.status !== 200) {
175
+ throw {
176
+ code: res.status + '',
177
+ msg: res.status + res.statusText,
178
+ url: config.url,
179
+ }
180
+ } else {
181
+ const data = res.data as ResponseData<T>
182
+ if (data.code === '0') {
183
+ return data.data
184
+ } else if (data.code == '10508') {
185
+ ElMessage.warning({
186
+ showClose: true,
187
+ message: '用户信息已过期,请重新登录',
188
+ grouping: true,
189
+ })
190
+ loginAgainBus.emit('loginAgain')
191
+ throw {
192
+ code: data.code,
193
+ msg: data.msg,
194
+ url: config.url,
195
+ }
196
+ } else {
197
+ throw {
198
+ code: data.code,
199
+ msg: data.msg,
200
+ url: config.url,
201
+ }
202
+ }
203
+ }
204
+ })
205
+ .catch((error) => {
206
+ if (error.msg) {
207
+ throw error
208
+ } else {
209
+ throw {
210
+ code: error.response.status + '',
211
+ msg: error.message,
212
+ url: config.url,
213
+ }
214
+ }
215
+ })
216
+ }