@liujitcn/kratos-uni-app-core 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.
- package/README.md +46 -0
- package/dist/module.mjs +84 -0
- package/dist/vite.mjs +405 -0
- package/package.json +58 -0
- package/src/api/base/config.ts +20 -0
- package/src/api/base/file.ts +79 -0
- package/src/api/base/login.ts +87 -0
- package/src/api/base/menu.ts +17 -0
- package/src/api/base/oauth.ts +149 -0
- package/src/api/system/auth.ts +54 -0
- package/src/api/system/base_area.ts +34 -0
- package/src/api/system/base_dict.ts +24 -0
- package/src/bootstrap.ts +23 -0
- package/src/components/BootstrapStatus.vue +39 -0
- package/src/components/KratosTabBar.vue +73 -0
- package/src/index.ts +6 -0
- package/src/module.ts +85 -0
- package/src/navigation-pattern.d.mts +2 -0
- package/src/navigation-pattern.mjs +29 -0
- package/src/navigation-tree.mjs +45 -0
- package/src/navigation.ts +330 -0
- package/src/pages.ts +26 -0
- package/src/rpc/base/v1/config.ts +34 -0
- package/src/rpc/base/v1/file.ts +66 -0
- package/src/rpc/base/v1/login.ts +128 -0
- package/src/rpc/base/v1/mcp.ts +20 -0
- package/src/rpc/base/v1/oauth.ts +199 -0
- package/src/rpc/base/v1/sse.ts +22 -0
- package/src/rpc/common/v1/common.ts +71 -0
- package/src/rpc/common/v1/enum.ts +71 -0
- package/src/rpc/common/v1/types.ts +77 -0
- package/src/rpc/google/protobuf/empty.ts +19 -0
- package/src/rpc/google/protobuf/wrappers.ts +124 -0
- package/src/rpc/system/app/v1/auth.ts +54 -0
- package/src/rpc/system/app/v1/base_area.ts +24 -0
- package/src/rpc/system/app/v1/base_dict.ts +37 -0
- package/src/rpc/system/app/v1/base_menu.ts +61 -0
- package/src/rpc/system/common/v1/enum.ts +80 -0
- package/src/static/images/avatar.png +0 -0
- package/src/static/images/center_bg.png +0 -0
- package/src/static/images/logo_icon.png +0 -0
- package/src/static/images/navigator_bg.png +0 -0
- package/src/static/tabs/home_default.png +0 -0
- package/src/static/tabs/home_selected.png +0 -0
- package/src/static/tabs/user_default.png +0 -0
- package/src/static/tabs/user_selected.png +0 -0
- package/src/stores/index.ts +14 -0
- package/src/stores/modules/setting.ts +56 -0
- package/src/stores/modules/user.ts +266 -0
- package/src/styles/base.scss +42 -0
- package/src/types/env.d.ts +24 -0
- package/src/types/go-captcha-uni.d.ts +7 -0
- package/src/types/uni-helper.d.ts +54 -0
- package/src/uni.scss +76 -0
- package/src/utils/auth.ts +67 -0
- package/src/utils/file.ts +95 -0
- package/src/utils/http.ts +430 -0
- package/src/utils/index.ts +79 -0
- package/src/utils/navigation.ts +81 -0
- package/src/utils/passwordCrypto.ts +275 -0
- package/src/views/pages/index/index.vue +227 -0
- package/src/views/pages/login/login.vue +1133 -0
- package/src/views/pages/login/protocal.vue +176 -0
- package/src/views/pages/status/index.vue +36 -0
- package/src/views/pages/webview/webview.vue +136 -0
- package/src/vite.ts +474 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onLoad } from '@dcloudio/uni-app'
|
|
3
|
+
import { useSettingStore } from '../../../stores'
|
|
4
|
+
import { ref } from 'vue'
|
|
5
|
+
|
|
6
|
+
const settingStore = useSettingStore()
|
|
7
|
+
const content = ref('')
|
|
8
|
+
|
|
9
|
+
const protocolClassMap: Record<string, string> = {
|
|
10
|
+
h2: 'protocol-title',
|
|
11
|
+
h3: 'protocol-section-title',
|
|
12
|
+
h4: 'protocol-section-title',
|
|
13
|
+
p: 'protocol-paragraph',
|
|
14
|
+
ul: 'protocol-list',
|
|
15
|
+
ol: 'protocol-list',
|
|
16
|
+
li: 'protocol-list-item',
|
|
17
|
+
strong: 'protocol-emphasis',
|
|
18
|
+
b: 'protocol-emphasis',
|
|
19
|
+
a: 'protocol-link',
|
|
20
|
+
img: 'protocol-image',
|
|
21
|
+
blockquote: 'protocol-quote',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** 为协议富文本节点补充统一类名,确保小程序端也能应用页面样式。 */
|
|
25
|
+
const decorateProtocolContent = (html: string) => {
|
|
26
|
+
return html.replace(
|
|
27
|
+
/<(h2|h3|h4|p|ul|ol|li|strong|b|a|img|blockquote)(\s[^>]*)?>/gi,
|
|
28
|
+
(match, tagName: string, attrs = '') => {
|
|
29
|
+
const className = protocolClassMap[tagName.toLowerCase()]
|
|
30
|
+
if (!className) return match
|
|
31
|
+
|
|
32
|
+
const classAttribute = attrs.match(/\bclass\s*=\s*(["'])(.*?)\1/i)
|
|
33
|
+
if (classAttribute) {
|
|
34
|
+
const nextAttrs = attrs.replace(
|
|
35
|
+
classAttribute[0],
|
|
36
|
+
`class="${classAttribute[2]} ${className}"`,
|
|
37
|
+
)
|
|
38
|
+
return `<${tagName}${nextAttrs}>`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return `<${tagName} class="${className}"${attrs}>`
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 加载协议内容
|
|
47
|
+
const loadProtocol = async (type?: string) => {
|
|
48
|
+
const isPrivacy = type === 'privacy'
|
|
49
|
+
const title = isPrivacy ? '隐私协议' : '服务条款'
|
|
50
|
+
const key = isPrivacy ? 'privacyProtocol' : 'serviceProtocol'
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
await settingStore.loadData()
|
|
54
|
+
const protocol = settingStore.getData(key)
|
|
55
|
+
if (!protocol) {
|
|
56
|
+
throw new Error(`${title}未配置,暂时无法查看`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await uni.setNavigationBarTitle({ title })
|
|
60
|
+
content.value = decorateProtocolContent(protocol)
|
|
61
|
+
} catch (error) {
|
|
62
|
+
await uni.showToast({
|
|
63
|
+
icon: 'none',
|
|
64
|
+
title: error instanceof Error ? error.message : '移动端配置加载失败',
|
|
65
|
+
})
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
uni.navigateBack()
|
|
68
|
+
}, 300)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
onLoad((query) => {
|
|
73
|
+
void loadProtocol(query?.type)
|
|
74
|
+
})
|
|
75
|
+
</script>
|
|
76
|
+
<template>
|
|
77
|
+
<view class="protocol-page">
|
|
78
|
+
<view class="protocol-card">
|
|
79
|
+
<rich-text class="protocol-content" :nodes="content" />
|
|
80
|
+
</view>
|
|
81
|
+
</view>
|
|
82
|
+
</template>
|
|
83
|
+
|
|
84
|
+
<style lang="scss">
|
|
85
|
+
page {
|
|
86
|
+
background-color: #f5f7f6;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.protocol-page {
|
|
90
|
+
min-height: 100vh;
|
|
91
|
+
padding: 24rpx;
|
|
92
|
+
box-sizing: border-box;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.protocol-card {
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
padding: 40rpx 32rpx 56rpx;
|
|
98
|
+
border: 1rpx solid #edf1ef;
|
|
99
|
+
border-radius: 16rpx;
|
|
100
|
+
background-color: #fff;
|
|
101
|
+
box-shadow: 0 8rpx 24rpx rgba(33, 54, 48, 0.05);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.protocol-content {
|
|
105
|
+
display: block;
|
|
106
|
+
color: #53635d;
|
|
107
|
+
font-size: 28rpx;
|
|
108
|
+
line-height: 1.85;
|
|
109
|
+
word-break: break-word;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.protocol-content .protocol-title {
|
|
113
|
+
margin: 0 0 28rpx;
|
|
114
|
+
color: #1f2d29;
|
|
115
|
+
font-size: 42rpx;
|
|
116
|
+
font-weight: 700;
|
|
117
|
+
line-height: 1.35;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.protocol-content .protocol-section-title {
|
|
121
|
+
margin: 38rpx 0 14rpx;
|
|
122
|
+
padding-left: 16rpx;
|
|
123
|
+
border-left: 6rpx solid #27ba9b;
|
|
124
|
+
color: #263a34;
|
|
125
|
+
font-size: 31rpx;
|
|
126
|
+
font-weight: 600;
|
|
127
|
+
line-height: 1.45;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.protocol-content .protocol-paragraph {
|
|
131
|
+
margin: 0 0 22rpx;
|
|
132
|
+
color: #53635d;
|
|
133
|
+
font-size: 28rpx;
|
|
134
|
+
line-height: 1.85;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.protocol-content .protocol-paragraph:last-child {
|
|
138
|
+
margin-bottom: 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.protocol-content .protocol-list {
|
|
142
|
+
margin: 0 0 22rpx;
|
|
143
|
+
padding-left: 42rpx;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.protocol-content .protocol-list-item {
|
|
147
|
+
margin-bottom: 10rpx;
|
|
148
|
+
padding-left: 6rpx;
|
|
149
|
+
line-height: 1.8;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.protocol-content .protocol-emphasis {
|
|
153
|
+
color: #263a34;
|
|
154
|
+
font-weight: 600;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.protocol-content .protocol-link {
|
|
158
|
+
color: #16806d;
|
|
159
|
+
text-decoration: underline;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.protocol-content .protocol-image {
|
|
163
|
+
display: block;
|
|
164
|
+
max-width: 100%;
|
|
165
|
+
height: auto;
|
|
166
|
+
margin: 24rpx auto;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.protocol-content .protocol-quote {
|
|
170
|
+
margin: 24rpx 0;
|
|
171
|
+
padding: 18rpx 22rpx;
|
|
172
|
+
border-left: 6rpx solid #b8e8dc;
|
|
173
|
+
color: #6c7b75;
|
|
174
|
+
background-color: #f3fbf8;
|
|
175
|
+
}
|
|
176
|
+
</style>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onLoad } from '@dcloudio/uni-app'
|
|
3
|
+
import { ref } from 'vue'
|
|
4
|
+
import BootstrapStatus from '../../../components/BootstrapStatus.vue'
|
|
5
|
+
import { initializeAppNavigation, launchAppStatus, navigateAppRoute } from '../../../navigation'
|
|
6
|
+
import type { BootstrapViewKey } from '../../../module'
|
|
7
|
+
|
|
8
|
+
const state = ref<BootstrapViewKey>('BOOTSTRAP_LOADING')
|
|
9
|
+
const detail = ref('')
|
|
10
|
+
const stateTitles: Record<BootstrapViewKey, string> = {
|
|
11
|
+
BOOTSTRAP_LOADING: '正在加载',
|
|
12
|
+
FORBIDDEN: '暂无访问权限',
|
|
13
|
+
NOT_FOUND: '页面不存在',
|
|
14
|
+
OFFLINE: '网络不可用',
|
|
15
|
+
CONFIG_ERROR: '导航配置无效',
|
|
16
|
+
PAGE_UNAVAILABLE: '页面暂不可用',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
onLoad(async (options) => {
|
|
20
|
+
state.value = (options?.state as BootstrapViewKey | undefined) ?? 'BOOTSTRAP_LOADING'
|
|
21
|
+
detail.value = options?.detail ? decodeURIComponent(options.detail) : ''
|
|
22
|
+
if (options?.bootstrap !== '1') return
|
|
23
|
+
try {
|
|
24
|
+
await initializeAppNavigation()
|
|
25
|
+
navigateAppRoute(options?.route ? decodeURIComponent(options.route) : 'app/home', {
|
|
26
|
+
replace: true,
|
|
27
|
+
})
|
|
28
|
+
} catch (error) {
|
|
29
|
+
launchAppStatus('CONFIG_ERROR', error instanceof Error ? error.message : String(error))
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<BootstrapStatus :title="stateTitles[state]" :detail="detail" />
|
|
36
|
+
</template>
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<view class="webview-container">
|
|
3
|
+
<!-- 小程序/APP 使用 web-view 组件 -->
|
|
4
|
+
<!-- #ifdef MP-WEIXIN || MP-ALIPAY || APP-PLUS -->
|
|
5
|
+
<web-view :src="url" @message="handleMessage"></web-view>
|
|
6
|
+
<!-- #endif -->
|
|
7
|
+
|
|
8
|
+
<!-- H5 直接跳转 -->
|
|
9
|
+
<!-- #ifdef H5 -->
|
|
10
|
+
<iframe
|
|
11
|
+
v-if="isH5 && url"
|
|
12
|
+
:src="url"
|
|
13
|
+
frameborder="0"
|
|
14
|
+
class="h5-iframe"
|
|
15
|
+
@load="handleIframeLoad"
|
|
16
|
+
></iframe>
|
|
17
|
+
<!-- #endif -->
|
|
18
|
+
|
|
19
|
+
<view v-if="showFallback" class="webview-empty">
|
|
20
|
+
<view class="webview-empty__title">链接无法打开</view>
|
|
21
|
+
<view class="webview-empty__desc">{{ emptyDesc }}</view>
|
|
22
|
+
</view>
|
|
23
|
+
|
|
24
|
+
<!-- #ifdef H5 -->
|
|
25
|
+
<!-- X-Frame-Options 拒绝嵌入时浏览器仍可能触发 iframe load,始终保留外部打开入口。 -->
|
|
26
|
+
<button v-if="isH5 && url && isIframeTimedOut" class="webview-open-button" @tap="openInBrowser">
|
|
27
|
+
新窗口打开
|
|
28
|
+
</button>
|
|
29
|
+
<!-- #endif -->
|
|
30
|
+
</view>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script setup lang="ts">
|
|
34
|
+
import { computed, ref } from 'vue'
|
|
35
|
+
import { onLoad } from '@dcloudio/uni-app'
|
|
36
|
+
|
|
37
|
+
const url = ref('')
|
|
38
|
+
const isH5 = ref(false)
|
|
39
|
+
const isIframeLoaded = ref(false)
|
|
40
|
+
const isIframeTimedOut = ref(false)
|
|
41
|
+
|
|
42
|
+
const showFallback = computed(
|
|
43
|
+
() => !url.value || (isH5.value && isIframeTimedOut.value && !isIframeLoaded.value),
|
|
44
|
+
)
|
|
45
|
+
const emptyDesc = computed(() => {
|
|
46
|
+
if (!url.value) {
|
|
47
|
+
return '缺少有效链接地址'
|
|
48
|
+
}
|
|
49
|
+
return '当前 H5 页面可能被目标站点限制嵌入'
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
onLoad((query) => {
|
|
53
|
+
url.value = decodeURIComponent(query?.url || '')
|
|
54
|
+
const title = decodeURIComponent(query?.title || '')
|
|
55
|
+
// 调用方传入标题时同步更新原生导航栏,避免继续显示页面注册时的空标题。
|
|
56
|
+
if (title) {
|
|
57
|
+
void uni.setNavigationBarTitle({ title })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// #ifdef H5
|
|
61
|
+
isH5.value = true
|
|
62
|
+
// H5 iframe 可能被 X-Frame-Options 拦截,超时后展示明确兜底。
|
|
63
|
+
window.setTimeout(() => {
|
|
64
|
+
isIframeTimedOut.value = true
|
|
65
|
+
}, 800)
|
|
66
|
+
// #endif
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
/** 标记 H5 iframe 已完成加载,避免正常外链被兜底层遮挡。 */
|
|
70
|
+
const handleIframeLoad = () => {
|
|
71
|
+
isIframeLoaded.value = true
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** H5 下外链被禁止嵌入时,允许用户用新窗口继续访问。 */
|
|
75
|
+
const openInBrowser = () => {
|
|
76
|
+
if (!url.value) return
|
|
77
|
+
// #ifdef H5
|
|
78
|
+
window.open(url.value, '_blank', 'noopener,noreferrer')
|
|
79
|
+
// #endif
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 小程序接收消息
|
|
83
|
+
const handleMessage = (e: any) => {
|
|
84
|
+
console.log('收到H5消息:', e.detail)
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<style scoped>
|
|
89
|
+
.webview-container {
|
|
90
|
+
position: relative;
|
|
91
|
+
flex: 1;
|
|
92
|
+
height: 100vh;
|
|
93
|
+
}
|
|
94
|
+
.h5-iframe {
|
|
95
|
+
width: 100%;
|
|
96
|
+
height: 100vh;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.webview-empty {
|
|
100
|
+
position: absolute;
|
|
101
|
+
inset: 0;
|
|
102
|
+
display: flex;
|
|
103
|
+
flex-direction: column;
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
padding: 48rpx;
|
|
107
|
+
text-align: center;
|
|
108
|
+
background-color: #fff;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.webview-empty__title {
|
|
112
|
+
font-size: 32rpx;
|
|
113
|
+
font-weight: 600;
|
|
114
|
+
color: #333;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.webview-empty__desc {
|
|
118
|
+
margin-top: 16rpx;
|
|
119
|
+
font-size: 26rpx;
|
|
120
|
+
color: #888;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.webview-open-button {
|
|
124
|
+
position: fixed;
|
|
125
|
+
right: 24rpx;
|
|
126
|
+
bottom: 96rpx;
|
|
127
|
+
z-index: 2;
|
|
128
|
+
width: 240rpx;
|
|
129
|
+
height: 72rpx;
|
|
130
|
+
line-height: 72rpx;
|
|
131
|
+
border-radius: 72rpx;
|
|
132
|
+
background-color: #27ba9b;
|
|
133
|
+
color: #fff;
|
|
134
|
+
font-size: 26rpx;
|
|
135
|
+
}
|
|
136
|
+
</style>
|