@smart100/spu-web-plugin 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,187 @@
1
+ import AMapLoader from './AMapLoader'
2
+ import { cloneDeep } from 'lodash-es'
3
+ import urlquery from './urlquery'
4
+
5
+
6
+ type Location = {
7
+ longitude: string;
8
+ latitude: string;
9
+ address: string;
10
+ [propName: string]: any;
11
+ } | null
12
+
13
+ let AMap: any = null
14
+ let geocoder: any = null
15
+ let geolocation: any = null
16
+ let datetime: number | null = null
17
+ let lastLocation: Location = null
18
+ let runing = false
19
+ let locationPromise: Promise<Location>
20
+
21
+
22
+ const init = async () => {
23
+ if (!AMap) {
24
+ AMap = await AMapLoader.load({
25
+ plugins: ['AMap.Geolocation', 'AMap.Geocoder']
26
+ })
27
+ geocoder = new AMap.Geocoder({
28
+ city: '',
29
+ radius: 500
30
+ })
31
+ geolocation = new AMap.Geolocation({
32
+ enableHighAccuracy: true,
33
+ timeout: 3000
34
+ })
35
+ }
36
+ }
37
+
38
+ const getAmapLocation = async (): Promise<Location> => {
39
+ await init()
40
+ return new Promise((resolve, reject) => {
41
+ geolocation.getCurrentPosition((status: string, result: any) => {
42
+ if (status === 'complete') {
43
+ const { lng, lat } = result.position
44
+ // console.log('getAmapLocation success')
45
+ resolve({
46
+ longitude: lng.toString(),
47
+ latitude: lat.toString(),
48
+ address: ''
49
+ })
50
+ } else {
51
+ // reject(new Error('getAmapLocation fail'))
52
+ console.error('getAmapLocation fail')
53
+ resolve(null)
54
+ }
55
+ })
56
+ })
57
+ }
58
+
59
+ const getSpuLocation = async (): Promise<Location> => {
60
+ return new Promise((resolve, reject) => {
61
+ window.Native.getLocation((result: any, error: any, status: any) => {
62
+ // console.log('getLocation result', result)
63
+ // console.log('getLocation error', error)
64
+ // console.log('getLocation status', status)
65
+ if (result && result?.longitude && result?.latitude) {
66
+ resolve({
67
+ longitude: result.longitude.toString(),
68
+ latitude: result.latitude.toString(),
69
+ address: result.address || ''
70
+ })
71
+ } else {
72
+ console.error('getSpuLocation fail')
73
+ resolve(null)
74
+ }
75
+ })
76
+ })
77
+ }
78
+
79
+ const getAmapCityLocation = async (): Promise<Location> => {
80
+ await init()
81
+ return new Promise((resolve, reject) => {
82
+ geolocation.getCityInfo((status: string, result: any) => {
83
+ if (status === 'complete') {
84
+ const lng = result.position[0].toString()
85
+ const lat = result.position[1].toString()
86
+ // console.log('getAmapCityLocation success')
87
+ // resolve([lng, lat])
88
+ resolve({
89
+ longitude: lng.toString(),
90
+ latitude: lat.toString(),
91
+ address: ''
92
+ })
93
+ } else {
94
+ // reject(new Error('getAmapCityLocation fail'))
95
+ console.error('getAmapCityLocation fail')
96
+ resolve(null)
97
+ }
98
+ })
99
+ })
100
+ }
101
+
102
+ const getAddress = async (position: Location): Promise<string> => {
103
+ await init()
104
+ return new Promise((resolve, reject) => {
105
+ if (position) {
106
+ geocoder.getAddress([position.longitude, position.latitude], (status: string, result: any) => {
107
+ if (status === 'complete' && result.regeocode) {
108
+ resolve(result.regeocode.formattedAddress)
109
+ } else {
110
+ // reject(new Error('getAddress fail'))
111
+ console.error('getAddress fail')
112
+ resolve('')
113
+ }
114
+ })
115
+ }
116
+ })
117
+ }
118
+
119
+ // 定位流程: 缓存 > 判断环境(APP,小程序,企微)基于环境获取定位 > 高德地图高精度定位 > 百度地图定位 > 高德城市定位
120
+ const getLocationPromise = async (): Promise<Location> => {
121
+ let location: Location = null
122
+
123
+ // 在 SPU 容器里使用 Native-API 的定位
124
+ if (window?.Native?.getLocation) {
125
+ location = await getSpuLocation()
126
+ }
127
+
128
+ // 高德定位
129
+ if (!location) {
130
+ location = await getAmapLocation()
131
+ }
132
+
133
+ // 城市定位结果不精确 仅在开发模式下使用
134
+ // if (!location && process.env.NODE_ENV !== 'production') {
135
+ if (!location && urlquery.isvirtuallocation) {
136
+ location = await getAmapCityLocation()
137
+ }
138
+
139
+ // 测试虚拟定位
140
+ if (!location && urlquery.isvirtuallocation) {
141
+ location = {
142
+ longitude: '113.34331353081598',
143
+ latitude: '23.105349663628473',
144
+ address: ''
145
+ }
146
+ }
147
+
148
+ if (location && !location.address) {
149
+ location.address = await getAddress(location) || '经纬度获取成功,但地址获取失败。'
150
+ }
151
+
152
+ return location
153
+ }
154
+
155
+
156
+ const getLocation = async () => {
157
+ await init()
158
+ // debugger
159
+ // 缓存30秒
160
+ if (datetime && Date.now() - datetime <= 30000 && lastLocation && !runing) {
161
+ return cloneDeep(lastLocation)
162
+ }
163
+ // 兼容同时间发起多个
164
+ if (runing && locationPromise) {
165
+ return locationPromise
166
+ }
167
+ // console.log('runing')
168
+ runing = true
169
+ locationPromise = getLocationPromise()
170
+ const locationRes = await locationPromise
171
+ runing = false
172
+ if (locationRes) {
173
+ datetime = Date.now()
174
+ lastLocation = locationRes
175
+ }
176
+ return locationRes
177
+ }
178
+
179
+ const getDistance = async (p1: [number, number], p2: [number, number]) => {
180
+ await init()
181
+ return AMap.GeometryUtil.distance(p1, p2)
182
+ }
183
+
184
+ export {
185
+ getLocation,
186
+ getDistance
187
+ }
package/src/login.ts ADDED
@@ -0,0 +1,248 @@
1
+ import { cloneDeep } from 'lodash-es'
2
+ import jwtDecode from 'jwt-decode'
3
+ import { apaasAxios } from './axios'
4
+ import tenantInfo from './tenantInfo'
5
+ import { lsProxy } from './storageProxy'
6
+
7
+ type JwtResult = {
8
+ LoginUser: IAny
9
+ exp: number
10
+ } | null
11
+
12
+ class Login {
13
+ private cache: IAny = {}
14
+
15
+ private getData (key: string) {
16
+ if (this.cache[key]) {
17
+ return this.cache[key]
18
+ } else {
19
+ const data = lsProxy.getItem(key)
20
+ this.cache[key] = data
21
+ return data
22
+ }
23
+ }
24
+
25
+ private setData (key: string, value: any) {
26
+ this.cache[key] = value
27
+ lsProxy.setItem(key, value)
28
+ }
29
+
30
+ private removeData (key: string) {
31
+ delete this.cache[key]
32
+ lsProxy.removeItem(key)
33
+ }
34
+
35
+ getEnvname () {
36
+ return this.getData('envname')
37
+ }
38
+
39
+ setEnvname (value: string) {
40
+ this.setData('envname', value)
41
+ }
42
+
43
+ removeEnvname () {
44
+ this.removeData('envname')
45
+ }
46
+
47
+ getToken () {
48
+ return this.getData('token')
49
+ }
50
+
51
+ setToken (value: string) {
52
+ this.setData('token', value)
53
+ }
54
+
55
+ removeToken () {
56
+ this.removeData('token')
57
+ }
58
+
59
+ getTokenExpires () {
60
+ return this.getData('tokenexpires')
61
+ }
62
+
63
+ setTokenExpires (value: string) {
64
+ this.setData('tokenexpires', value)
65
+ }
66
+
67
+ removeTokenExpires () {
68
+ this.removeData('tokenexpires')
69
+ }
70
+
71
+ getRefreshToken () {
72
+ return this.getData('refreshtoken')
73
+ }
74
+
75
+ setRefreshToken (value: string) {
76
+ this.setData('refreshtoken', value)
77
+ }
78
+
79
+ removeRefreshToken () {
80
+ this.removeData('refreshtoken')
81
+ }
82
+
83
+ private updateToken () {
84
+ return apaasAxios.get('/api/auth/refreshtoken', {
85
+ params: {
86
+ refreshtoken: this.getRefreshToken()
87
+ },
88
+ isShowLoadding: false,
89
+ isShowErrorMessage: false
90
+ }).then((res: any) => {
91
+ // console.log(res)
92
+ const data = res?.data
93
+ if (data) {
94
+ this.setToken(data.token)
95
+ this.setRefreshToken(data.refreshtoken)
96
+ this.setTokenExpires(data.tokenexpires)
97
+ }
98
+ })
99
+ }
100
+
101
+ private refreshtokenTimer: number | null = null
102
+
103
+ startRefreshtoken () {
104
+ // 如果有登录 则过期前15秒更新token
105
+ // 如果没登录 每隔1分钟走token更新逻辑(如果刚开始没登录 后面才登录【不需要再在登陆后写刷新token逻辑】)
106
+ this.stopRefreshtoken()
107
+ const time = this.checkLogin() ? (Number(this.getTokenExpires()) - Date.now() - 1000 * 15) : (1000 * 60)
108
+ // const time = 5000
109
+ if (time > 0) {
110
+ this.refreshtokenTimer = window.setTimeout(async () => {
111
+ if (this.checkLogin()) {
112
+ await this.updateToken()
113
+ }
114
+ this.startRefreshtoken()
115
+ }, time)
116
+ }
117
+ }
118
+
119
+ private stopRefreshtoken () {
120
+ clearTimeout(this.refreshtokenTimer as number)
121
+ this.refreshtokenTimer = null
122
+ }
123
+
124
+ getUser (key?: string): any {
125
+ const user = this.getData('user')
126
+ const userObj = user ? JSON.parse(user) : null
127
+ if (!key) {
128
+ return userObj
129
+ } else {
130
+ return userObj ? (userObj[key] || '') : ''
131
+ }
132
+ }
133
+
134
+ setUser (value: string | IAny) {
135
+ let res
136
+ if (typeof value === 'string') {
137
+ res = JSON.parse(value)
138
+ } else {
139
+ res = cloneDeep(value)
140
+ }
141
+
142
+ for (const x in res) {
143
+ res[x.toLowerCase()] = res[x]
144
+ }
145
+
146
+ this.setData('user', JSON.stringify(res))
147
+ }
148
+
149
+ setUserByToken (token: string) {
150
+ const jwtInfo = this.jwtDecode(token)
151
+ if (jwtInfo && jwtInfo.LoginUser) {
152
+ this.setUser(jwtInfo.LoginUser)
153
+ } else {
154
+ this.removeUser()
155
+ }
156
+ }
157
+
158
+ removeUser () {
159
+ this.removeData('user')
160
+ }
161
+
162
+ private jwtDecode (token?: string) {
163
+ if (!token) {
164
+ console.error('token为空 jwt解析token出错')
165
+ return null
166
+ }
167
+ try {
168
+ return jwtDecode<JwtResult>(token)
169
+ } catch (e) {
170
+ console.error('jwt解析token出错', token, e)
171
+ return null
172
+ }
173
+ }
174
+
175
+ // 检测用户是否登录状态
176
+ checkLogin () {
177
+ let haslogged = false
178
+ const token = this.getToken()
179
+ const refreshtoken = this.getRefreshToken()
180
+ const tokenexpires = this.getTokenExpires()
181
+ const now = Date.now()
182
+ if (token && refreshtoken && tokenexpires && Number(tokenexpires) > now) {
183
+ // 优化成用 jwt 解析token 获取过期时间 不需要请求接口
184
+ const jwtInfo = this.jwtDecode(token)
185
+ if (jwtInfo?.exp) {
186
+ haslogged = Number(jwtInfo.exp + '000') > now
187
+ } else {
188
+ haslogged = false
189
+ }
190
+ }
191
+ return haslogged
192
+ }
193
+
194
+ // 单点登录
195
+ async singleLogin (query: IAny) {
196
+ // 自动登录
197
+ query = cloneDeep(query)
198
+ const token = query.token
199
+ // 之所以不强制校验 refreshtoken tokenexpires 是因为安装卸载配置页面有可能放在产品运营中心 没有这两字段
200
+ if (token) {
201
+ this.setToken(token)
202
+ this.setUserByToken(token) // 解析token为用户信息存入
203
+
204
+ const refreshtoken = query.refreshtoken
205
+ const tokenexpires = query.tokenexpires
206
+ if (refreshtoken) {
207
+ this.setRefreshToken(refreshtoken)
208
+ }
209
+ if (tokenexpires) {
210
+ this.setTokenExpires(tokenexpires)
211
+ }
212
+ // debugger
213
+
214
+ // context 上下文字段 产品运营中心安装 卸载 配置 和 产品配置中心业务配置 页面需要用到
215
+ let context = query.context
216
+ if (context) {
217
+ context = decodeURIComponent(context)
218
+ lsProxy.setItem('context', context)
219
+ delete query.context
220
+ }
221
+
222
+ // const tenant = await apassRequest.get('/api/auth/tenantlist', {}).then((res) => {
223
+ // return res?.data?.tenants?.[0]
224
+ // })
225
+ // if (tenant) {
226
+ // lsProxy.setItem('tenant', JSON.stringify(tenant))
227
+ // const normalizedTenant = tenantInfoApi.format(tenant as Tenant)
228
+ // normalizedTenant && tenantInfoApi.save(normalizedTenant)
229
+ // }
230
+ // console.log(tenant)
231
+
232
+ await tenantInfo.getAndSave()
233
+ // debugger
234
+ } else {
235
+ console.error('query 中没有 token,无法单点登录。')
236
+ }
237
+ // 单点登录后 无论是否成功 都需要删除 query 中相关参数
238
+ delete query.token
239
+ delete query.refreshtoken
240
+ delete query.tokenexpires
241
+ delete query.context
242
+ return {
243
+ query
244
+ }
245
+ }
246
+ }
247
+
248
+ export default new Login()
@@ -0,0 +1,192 @@
1
+ import CloudServ from '../cloudServ'
2
+ import { initServToken } from './servtoken'
3
+ import OSS from '../package/ali-oss/aliyun-oss-sdk.apaas.min.js'
4
+ // import * as OSS from '../package/ali-oss/aliyun-oss-sdk.apaas.min.js'
5
+ // const OSS = require('../package/ali-oss/aliyun-oss-sdk.apaas.min.js')
6
+ import ObsClient from '../package/huaweicloud-obs/esdk-obs-browserjs.3.22.3.min.js'
7
+ // import * as ObsClient from '../package/huaweicloud-obs/esdk-obs-browserjs.3.22.3.min.js'
8
+ // const ObsClient = require('../package/huaweicloud-obs/esdk-obs-browserjs.3.22.3.min.js')
9
+ import dayjs from 'dayjs'
10
+ import login from '../login'
11
+ // import { get } from 'lodash-es'
12
+ // import qs from 'qs'
13
+
14
+ // function getUrlPaths(url: string): string[] {
15
+ // let path = url.split('?')[0]
16
+ // if (!path) return []
17
+ // path = path.replace('http://', '')
18
+ // path = path.replace('https://', '')
19
+ // return path.split('/')
20
+ // }
21
+
22
+ // console.log(OSS)
23
+ // debugger
24
+ const getContentType = (suffix: string) => {
25
+ const map: IAny = {
26
+ '.jpg': 'image/jpeg',
27
+ '.jpeg': 'image/jpeg',
28
+ '.png': 'image/png',
29
+ '.gif': 'image/gif'
30
+ // '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
31
+ }
32
+ return map[suffix.toLowerCase()] || ''
33
+ }
34
+
35
+ // interface UrlInfo {
36
+ // [propName: string]: {
37
+ // url: string
38
+ // outTime: number
39
+ // }
40
+ // }
41
+
42
+ // /**
43
+ // * @Description: 缓存DownloadUrl
44
+ // * @author: hyl
45
+ // */
46
+ // const cacheUrlInfo: UrlInfo = {}
47
+
48
+ // function setCacheUrl(key: string, url: string, expires: number) {
49
+ // const thisTime = new Date().getTime()
50
+ // let diff = expires * 1000 - thisTime
51
+ // if (diff < 0) return
52
+ // // 最多只缓存20分钟
53
+ // if (diff > 1000 * 60 * 20) diff = 1000 * 60 * 20
54
+ // const outTime = thisTime + diff
55
+ // cacheUrlInfo[key] = {
56
+ // url,
57
+ // outTime
58
+ // }
59
+ // }
60
+
61
+
62
+ type Cope = { width?: number, height?: number } | string | boolean
63
+
64
+ const getNormalizeAliOssCope = (cope?: Cope) => {
65
+ let copeObj = ''
66
+ if (cope) {
67
+ if (cope === true) {
68
+ copeObj = 'image/resize,m_fixed,w_100,h_100'
69
+ } else if (typeof cope === 'string') {
70
+ // 'image/resize,m_fixed,w_100,h_100'
71
+ copeObj = cope
72
+ } else if (cope.width || cope.height) {
73
+ copeObj = 'image/resize,m_fixed'
74
+ if (cope.width) {
75
+ copeObj += `,w_${cope.width}`
76
+ }
77
+ if (cope.height) {
78
+ copeObj += `,h_${cope.height}`
79
+ }
80
+ }
81
+ }
82
+ return copeObj
83
+ }
84
+
85
+
86
+ interface IDownload {
87
+ type?: 'att' | 'img',
88
+ source: string,
89
+ datetime: string | number,
90
+ storagetype?: StorageType,
91
+ cope?: Cope
92
+ }
93
+
94
+ // 根据文件信息最后生成一个云文件服务可以用的链接http://xxxxx.xxx.jpg
95
+ const getUrl = async ({
96
+ type = 'img',
97
+ source = '',
98
+ datetime = '',
99
+ storagetype = 'storage',
100
+ cope = ''
101
+ }: IDownload) => {
102
+ const cloudServStorage = CloudServ.get(storagetype)
103
+ if (!cloudServStorage) throw Error('无可用存储设置')
104
+ const servToken = await initServToken()
105
+ if (!servToken) throw Error('无可用servToken')
106
+
107
+ const isAliYun = CloudServ.isAliyun(storagetype)
108
+ const isHuawei = CloudServ.isHuawei(storagetype)
109
+ const tenantCode = login.getUser('tenantcode')
110
+ const isAbsoluteUrl = !!source.match(/\/att\//) || !!source.match(/\/img\//) || !!source.match(/att\//) || !!source.match(/img\//)
111
+ const suffix = source.slice(source.lastIndexOf('.'))
112
+ const date = dayjs(+datetime).format('YYYYMMDD')
113
+ let objectKey = isAbsoluteUrl ? source : `${source.slice(0, 3)}/${type}/${date}/${tenantCode}/${source}`
114
+ const copeObj = getNormalizeAliOssCope(cope)
115
+ const contentType = getContentType(suffix)
116
+
117
+ if (isAliYun) {
118
+ const ossClient = new OSS({
119
+ // region: cloudServ.cloudserv_storage_storageendpoint,
120
+ endpoint: cloudServStorage.cloudserv_storage_storageendpoint,
121
+ accessKeyId: servToken.accesskeyid,
122
+ accessKeySecret: servToken.accesskeysecret,
123
+ stsToken: servToken.securitytoken,
124
+ bucket: cloudServStorage.cloudserv_storage_storagebucket,
125
+ secure: true
126
+ })
127
+
128
+ // TODO 这两个请求头不能改顺序,不知道什么情况!!改了顺序会报错
129
+ const responseHeader: IAny = {
130
+ // 'content-type': contentType || undefined,
131
+ 'content-disposition': 'attachment; filename=' + (source && encodeURIComponent(source)) // 阿里云提供的下载名字
132
+ }
133
+ if (contentType) {
134
+ responseHeader['content-type'] = contentType
135
+ }
136
+
137
+ const ossUrl = ossClient.signatureUrl(objectKey, {
138
+ response: responseHeader,
139
+ process: copeObj
140
+ })
141
+
142
+ // // 假阿里云会自动拼cloudserv_storage_storagebucket,这里要去掉
143
+ // const paths = getUrlPaths(ossUrl)
144
+ // if (paths.length >= 3 && paths[1] === paths[2]) {
145
+ // ossUrl = ossUrl.replace(paths[1] + '/' + paths[2], paths[1])
146
+ // }
147
+
148
+ return ossUrl
149
+ } else if (isHuawei) {
150
+ const obs = new ObsClient({
151
+ access_key_id: servToken.accesskeyid,
152
+ secret_access_key: servToken.accesskeysecret,
153
+ server: cloudServStorage.cloudserv_storage_storageendpoint,
154
+ security_token: servToken.securitytoken
155
+ })
156
+ // 修复地址里面有//就不能下载了
157
+ if (objectKey && objectKey[0] === '/') {
158
+ objectKey = objectKey.slice(1)
159
+ }
160
+ try {
161
+ const Params: IAny = {
162
+ // Method: 'get',
163
+ Bucket: cloudServStorage.cloudserv_storage_storagebucket,
164
+ Key: objectKey
165
+ // Headers: responseHeader
166
+ // Expires: 3600,
167
+ // Headers: headers
168
+ }
169
+ if (contentType.startsWith('image') && copeObj) {
170
+ Params.QueryParams = {
171
+ 'x-image-process': copeObj
172
+ }
173
+ }
174
+ const res = obs.createSignedUrlSync(Params)
175
+ const signedUrl = res.SignedUrl
176
+
177
+ // const expires = get(qs.parse(signedUrl), 'Expires') as string
178
+ // if (expires) setCacheUrl(cacheKey, signedUrl, +expires)
179
+
180
+ return signedUrl
181
+ } catch (e: any) {
182
+ console.error(e)
183
+ throw Error(e)
184
+ }
185
+ } else {
186
+ throw Error('暂不支持非阿里云OSS/华为云OBS存储类型')
187
+ }
188
+ }
189
+
190
+ export default {
191
+ getUrl
192
+ }
@@ -0,0 +1,8 @@
1
+
2
+ import downloadService from './downloadService'
3
+ import uploadService from './uploadService'
4
+
5
+ export {
6
+ downloadService,
7
+ uploadService
8
+ }