@hostlink/nuxt-light 1.37.1 → 1.39.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.
- package/dist/module.json +1 -1
- package/dist/module.mjs +5 -0
- package/dist/runtime/components/l-app-main.vue +1 -0
- package/dist/runtime/components/l-editor.vue +4 -4
- package/dist/runtime/components/l-input.vue +1 -0
- package/dist/runtime/components/l-login.vue +1 -1
- package/dist/runtime/composables/useLight.d.ts +9 -5
- package/dist/runtime/composables/useLight.js +4 -0
- package/dist/runtime/composables/useRoles.d.ts +7 -0
- package/dist/runtime/composables/useRoles.js +39 -0
- package/dist/runtime/composables/useWebAuthn.d.ts +5 -0
- package/dist/runtime/composables/useWebAuthn.js +5 -0
- package/dist/runtime/pages/Permission/all.vue +175 -48
- package/dist/runtime/pages/System/database/backup.vue +113 -5
- package/dist/runtime/pages/System/database/restore.vue +207 -0
- package/dist/runtime/pages/System/database/restore.vue.d.ts +2 -0
- package/dist/runtime/pages/System/database/table.vue +2 -0
- package/dist/runtime/pages/User/setting/open_id.vue +160 -70
- package/dist/runtime/pages/User/setting/password.vue +3 -2
- package/dist/runtime/pages/User/setting/two-factor-auth.vue +213 -47
- package/package.json +9 -9
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { q, m } from "#imports";
|
|
3
|
+
import { Loading, Notify } from "quasar";
|
|
4
|
+
import { ref, onMounted } from "vue";
|
|
5
|
+
const isUploading = ref(false);
|
|
6
|
+
const selectedFile = ref(null);
|
|
7
|
+
const fileInput = ref(null);
|
|
8
|
+
const databaseInfo = ref(null);
|
|
9
|
+
const fetchDatabaseInfo = async () => {
|
|
10
|
+
try {
|
|
11
|
+
const info = await q({
|
|
12
|
+
system: {
|
|
13
|
+
database: {
|
|
14
|
+
type: true,
|
|
15
|
+
sizeBytes: true,
|
|
16
|
+
version: true
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
databaseInfo.value = info.system.database;
|
|
21
|
+
} catch (e) {
|
|
22
|
+
console.error("Failed to fetch database info:", e);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const onFileChange = (event) => {
|
|
26
|
+
const file = event.target.files[0];
|
|
27
|
+
if (file) {
|
|
28
|
+
if (file.type === "application/sql" || file.name.endsWith(".sql")) {
|
|
29
|
+
selectedFile.value = file;
|
|
30
|
+
} else {
|
|
31
|
+
Notify.create({
|
|
32
|
+
type: "negative",
|
|
33
|
+
message: "\u8ACB\u9078\u64C7 SQL \u6A94\u6848 (.sql)",
|
|
34
|
+
position: "top"
|
|
35
|
+
});
|
|
36
|
+
event.target.value = "";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const clearFile = () => {
|
|
41
|
+
selectedFile.value = null;
|
|
42
|
+
if (fileInput.value) {
|
|
43
|
+
fileInput.value.value = "";
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const onClickRestore = async () => {
|
|
47
|
+
if (!selectedFile.value) {
|
|
48
|
+
Notify.create({
|
|
49
|
+
type: "negative",
|
|
50
|
+
message: "\u8ACB\u5148\u9078\u64C7\u8981\u9084\u539F\u7684 SQL \u6A94\u6848",
|
|
51
|
+
position: "top"
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
isUploading.value = true;
|
|
56
|
+
Loading.show({ message: "\u6B63\u5728\u9084\u539F\u8CC7\u6599\u5EAB\uFF0C\u8ACB\u7A0D\u5019..." });
|
|
57
|
+
try {
|
|
58
|
+
const result = await m("restoreDatabase", {
|
|
59
|
+
file: selectedFile.value
|
|
60
|
+
});
|
|
61
|
+
Notify.create({
|
|
62
|
+
type: "positive",
|
|
63
|
+
message: "\u8CC7\u6599\u5EAB\u9084\u539F\u6210\u529F\uFF01",
|
|
64
|
+
position: "top"
|
|
65
|
+
});
|
|
66
|
+
clearFile();
|
|
67
|
+
await fetchDatabaseInfo();
|
|
68
|
+
} catch (e) {
|
|
69
|
+
console.error("Restore failed:", e);
|
|
70
|
+
Notify.create({
|
|
71
|
+
type: "negative",
|
|
72
|
+
message: "\u9084\u539F\u5931\u6557\uFF0C\u8ACB\u6AA2\u67E5\u6A94\u6848\u683C\u5F0F\u6216\u7A0D\u5F8C\u518D\u8A66\u3002",
|
|
73
|
+
position: "top"
|
|
74
|
+
});
|
|
75
|
+
} finally {
|
|
76
|
+
Loading.hide();
|
|
77
|
+
isUploading.value = false;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const formatFileSize = (bytes) => {
|
|
81
|
+
if (bytes === 0) return "0 B";
|
|
82
|
+
const k = 1024;
|
|
83
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
84
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
85
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
86
|
+
};
|
|
87
|
+
onMounted(() => {
|
|
88
|
+
fetchDatabaseInfo();
|
|
89
|
+
});
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
<template>
|
|
93
|
+
<l-page title="資料庫還原">
|
|
94
|
+
<div class="import-container">
|
|
95
|
+
<!-- 頁面說明 -->
|
|
96
|
+
<l-card class="info-card">
|
|
97
|
+
<div class="card-header">
|
|
98
|
+
<h5 class="card-title">
|
|
99
|
+
<q-icon name="sym_o_info" class="text-primary" />
|
|
100
|
+
還原說明
|
|
101
|
+
</h5>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="card-content">
|
|
104
|
+
<p class="description">
|
|
105
|
+
資料庫還原功能可以從 SQL 備份檔案恢復資料庫內容。
|
|
106
|
+
請確保上傳的 SQL 檔案來源可信且格式正確。
|
|
107
|
+
</p>
|
|
108
|
+
</div>
|
|
109
|
+
</l-card>
|
|
110
|
+
|
|
111
|
+
<!-- 當前資料庫資訊 -->
|
|
112
|
+
<l-card class="status-card">
|
|
113
|
+
<div class="card-header">
|
|
114
|
+
<h5 class="card-title">
|
|
115
|
+
<q-icon name="sym_o_database" class="text-secondary" />
|
|
116
|
+
當前資料庫資訊
|
|
117
|
+
</h5>
|
|
118
|
+
</div>
|
|
119
|
+
<div class="card-content">
|
|
120
|
+
<div class="info-grid">
|
|
121
|
+
<div class="info-item" v-if="databaseInfo">
|
|
122
|
+
<q-icon name="sym_o_storage" class="info-icon" />
|
|
123
|
+
<div class="info-content">
|
|
124
|
+
<span class="info-label">資料庫類型</span>
|
|
125
|
+
<span class="info-value">{{ databaseInfo.type || '未知' }}</span>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="info-item" v-if="databaseInfo">
|
|
129
|
+
<q-icon name="sym_o_storage" class="info-icon" />
|
|
130
|
+
<div class="info-content">
|
|
131
|
+
<span class="info-label">當前大小</span>
|
|
132
|
+
<span class="info-value">{{ databaseInfo.sizeBytes ? (databaseInfo.sizeBytes / 1024 /
|
|
133
|
+
1024).toFixed(2) + ' MB' : '計算中...' }}</span>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
<div class="info-item" v-if="databaseInfo && databaseInfo.version">
|
|
137
|
+
<q-icon name="sym_o_info" class="info-icon" />
|
|
138
|
+
<div class="info-content">
|
|
139
|
+
<span class="info-label">資料庫版本</span>
|
|
140
|
+
<span class="info-value">{{ databaseInfo.version }}</span>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</l-card>
|
|
146
|
+
|
|
147
|
+
<!-- 安全警告 -->
|
|
148
|
+
<l-alert type="negative" class="security-alert">
|
|
149
|
+
<template #prefix>
|
|
150
|
+
<q-icon name="sym_o_warning" />
|
|
151
|
+
</template>
|
|
152
|
+
<strong>重要警告:</strong>還原資料庫將會完全覆蓋現有資料,此操作無法復原。建議在還原前先進行當前資料庫的備份。
|
|
153
|
+
</l-alert>
|
|
154
|
+
|
|
155
|
+
<!-- 檔案上傳區域 -->
|
|
156
|
+
<l-card class="upload-card">
|
|
157
|
+
<div class="card-header">
|
|
158
|
+
<h5 class="card-title">
|
|
159
|
+
<q-icon name="sym_o_upload" class="text-primary" />
|
|
160
|
+
選擇備份檔案
|
|
161
|
+
</h5>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="card-content">
|
|
164
|
+
<div class="upload-area" :class="{ 'has-file': selectedFile }">
|
|
165
|
+
<input ref="fileInput" type="file" accept=".sql,application/sql" @change="onFileChange"
|
|
166
|
+
class="file-input" id="sql-file-input" />
|
|
167
|
+
|
|
168
|
+
<div v-if="!selectedFile" class="upload-placeholder">
|
|
169
|
+
<q-icon name="sym_o_cloud_upload" class="upload-icon" />
|
|
170
|
+
<p class="upload-text">點擊選擇 SQL 檔案</p>
|
|
171
|
+
<p class="upload-hint">支援 .sql 格式的檔案</p>
|
|
172
|
+
<label for="sql-file-input" class="upload-button">
|
|
173
|
+
<l-btn label="選擇檔案" icon="sym_o_folder_open" color="primary" />
|
|
174
|
+
</label>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<div v-else class="file-info">
|
|
178
|
+
<div class="file-details">
|
|
179
|
+
<q-icon name="sym_o_description" class="file-icon" />
|
|
180
|
+
<div class="file-content">
|
|
181
|
+
<span class="file-name">{{ selectedFile.name }}</span>
|
|
182
|
+
<span class="file-size">{{ formatFileSize(selectedFile.size) }}</span>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
<l-btn icon="sym_o_close" color="negative" flat round @click="clearFile"
|
|
186
|
+
class="remove-btn" />
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</l-card>
|
|
191
|
+
|
|
192
|
+
<!-- 還原按鈕區域 -->
|
|
193
|
+
<div class="restore-section">
|
|
194
|
+
<l-btn :label="isUploading ? '正在還原中...' : '開始還原資料庫'" icon="sym_o_restore" color="primary" size="lg"
|
|
195
|
+
:loading="isUploading" :disabled="isUploading || !selectedFile" @click="onClickRestore"
|
|
196
|
+
class="restore-btn" />
|
|
197
|
+
<p class="restore-hint">
|
|
198
|
+
請確認已備份當前資料庫,還原操作將無法復原
|
|
199
|
+
</p>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
</l-page>
|
|
203
|
+
</template>
|
|
204
|
+
|
|
205
|
+
<style scoped>
|
|
206
|
+
.import-container{display:flex;flex-direction:column;gap:24px;margin:0 auto;max-width:800px;padding:20px}.info-card,.status-card,.upload-card{border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,.1)}.card-header{padding:20px 24px 0}.card-title{align-items:center;color:#2c3e50;display:flex;font-weight:600;gap:8px;margin:0}.card-content{padding:16px 24px 24px}.description{color:#5a6c7d;line-height:1.6;margin:0}.info-grid{display:grid;gap:16px}.info-item{align-items:center;background:#f8f9fa;border-radius:8px;display:flex;gap:12px;padding:12px}.info-icon{color:#6c757d;font-size:24px}.info-content{display:flex;flex-direction:column;gap:4px}.info-label{color:#6c757d;font-size:14px;font-weight:500}.info-value{color:#2c3e50;font-size:16px;font-weight:600}.security-alert{border-radius:8px}.upload-area{background:#fafbfc;border:2px dashed #dee2e6;border-radius:12px;min-height:200px;position:relative;transition:all .3s ease}.upload-area:hover{background:#f8f9ff;border-color:#007bff}.upload-area.has-file{background:#f8fff9;border-color:#28a745}.file-input{cursor:pointer;height:100%;opacity:0;position:absolute;width:100%;z-index:1}.upload-placeholder{align-items:center;display:flex;flex-direction:column;height:200px;justify-content:center;text-align:center}.upload-icon{color:#007bff;font-size:64px;margin-bottom:16px}.upload-text{color:#2c3e50;font-size:18px;font-weight:600;margin:0 0 8px}.upload-hint{color:#6c757d;font-size:14px;margin:0 0 20px}.upload-button{cursor:pointer;position:relative;z-index:2}.file-info{height:200px;justify-content:space-between;padding:20px}.file-details,.file-info{align-items:center;display:flex}.file-details{gap:16px}.file-icon{color:#28a745;font-size:48px}.file-content{display:flex;flex-direction:column;gap:4px}.file-name{color:#2c3e50;font-size:16px;font-weight:600}.file-size{color:#6c757d;font-size:14px}.remove-btn{flex-shrink:0}.restore-section{background:linear-gradient(135deg,#fff3cd,#ffeeba);border:2px solid #ffc107;border-radius:16px;padding:32px 24px;text-align:center}.restore-btn{border-radius:8px;box-shadow:0 4px 12px rgba(255,193,7,.3);font-size:16px;font-weight:600;height:48px;margin-bottom:12px;min-width:240px;transition:all .3s ease}.restore-btn:hover:not(:disabled){box-shadow:0 6px 20px rgba(255,193,7,.4);transform:translateY(-2px)}.restore-btn:disabled{opacity:.6}.restore-hint{color:#856404;font-size:14px;font-weight:500;margin:0 auto;max-width:400px}@media (max-width:768px){.import-container{gap:20px;padding:16px}.card-content{padding:12px 20px 20px}.upload-area{min-height:160px}.file-info,.upload-placeholder{height:160px}.file-info{padding:16px}.restore-section{padding:24px 20px}.restore-btn{min-width:100%}}
|
|
207
|
+
</style>
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
export default _default;
|
|
@@ -7,6 +7,7 @@ const { data, refresh } = await useAsyncData("database", async () => {
|
|
|
7
7
|
system: {
|
|
8
8
|
database: {
|
|
9
9
|
table: true,
|
|
10
|
+
type: true,
|
|
10
11
|
version: true,
|
|
11
12
|
tableStatus: true
|
|
12
13
|
}
|
|
@@ -171,6 +172,7 @@ const truncatTable = async () => {
|
|
|
171
172
|
|
|
172
173
|
<l-card>
|
|
173
174
|
<l-list>
|
|
175
|
+
<l-item label="Type">{{ data.type }}</l-item>
|
|
174
176
|
<l-item label="Version">{{ data.version }}</l-item>
|
|
175
177
|
</l-list>
|
|
176
178
|
</l-card>
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { reactive, onMounted, nextTick } from "vue";
|
|
3
3
|
import { useQuasar } from "quasar";
|
|
4
|
-
import { q,
|
|
5
|
-
import {
|
|
4
|
+
import { q, useLight, api } from "#imports";
|
|
5
|
+
import { fabGoogle, fabFacebook, fabMicrosoft } from "@quasar/extras/fontawesome-v6";
|
|
6
6
|
const light = useLight();
|
|
7
|
-
const { t } = useI18n();
|
|
8
7
|
const quasar = useQuasar();
|
|
9
8
|
let { app, my } = await q({
|
|
10
9
|
app: {
|
|
@@ -139,78 +138,169 @@ const onUnlinkFacebook = async () => {
|
|
|
139
138
|
</script>
|
|
140
139
|
|
|
141
140
|
<template>
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
<
|
|
146
|
-
|
|
141
|
+
<div class="q-pa-md">
|
|
142
|
+
<!-- 頁面標題 -->
|
|
143
|
+
<div class="q-mb-lg">
|
|
144
|
+
<h1 class="text-h4 q-mb-sm">{{ $t('Social Account Linking') }}</h1>
|
|
145
|
+
<p class="text-grey-6">{{ $t('Link your social accounts to enable quick sign-in and enhanced features.') }}
|
|
146
|
+
</p>
|
|
147
|
+
</div>
|
|
147
148
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
<!-- 社交平台連結區域 -->
|
|
150
|
+
<div class="row q-col-gutter-md">
|
|
151
|
+
<!-- Google -->
|
|
152
|
+
<div class="col-12 col-md-4">
|
|
153
|
+
<q-card class="full-height" :class="my.google ? 'bg-green-1 border-green' : 'bg-grey-1'">
|
|
154
|
+
<q-card-section class="text-center q-pb-none">
|
|
155
|
+
<q-avatar size="64px" class="q-mb-md" :color="my.google ? 'green' : 'grey-5'"
|
|
156
|
+
text-color="white">
|
|
157
|
+
<q-icon :name="fabGoogle" size="32px" />
|
|
158
|
+
</q-avatar>
|
|
159
|
+
<div class="text-h6 q-mb-xs">Google</div>
|
|
160
|
+
<q-badge v-if="my.google" color="green" class="q-mb-md">
|
|
161
|
+
{{ $t('Connected') }}
|
|
162
|
+
</q-badge>
|
|
163
|
+
<q-badge v-else color="grey-5" class="q-mb-md">
|
|
164
|
+
{{ $t('Not Connected') }}
|
|
165
|
+
</q-badge>
|
|
166
|
+
</q-card-section>
|
|
151
167
|
|
|
152
|
-
|
|
168
|
+
<q-card-section v-if="app.googleClientId">
|
|
169
|
+
<template v-if="my.google">
|
|
170
|
+
<div class="text-center q-mb-md">
|
|
171
|
+
<div class="text-caption text-grey-6">{{ $t('Connected Account') }}</div>
|
|
172
|
+
<div class="text-body2 text-weight-medium q-mt-xs">{{ my.google }}</div>
|
|
173
|
+
</div>
|
|
174
|
+
<q-btn class="full-width" color="negative" outline @click="onUnlink" icon="sym_o_link_off"
|
|
175
|
+
:label="$t('Unlink Account')" />
|
|
176
|
+
</template>
|
|
177
|
+
<template v-else>
|
|
178
|
+
<div class="text-center q-mb-md text-grey-6">
|
|
179
|
+
{{ $t('Click to link your Google account for quick sign-in') }}
|
|
180
|
+
</div>
|
|
181
|
+
<div id="g_id_signin" class="flex justify-center"></div>
|
|
182
|
+
</template>
|
|
183
|
+
</q-card-section>
|
|
184
|
+
<q-card-section v-else>
|
|
185
|
+
<q-banner class="bg-orange-2 text-orange-9">
|
|
186
|
+
<template v-slot:avatar>
|
|
187
|
+
<q-icon name="sym_o_warning" />
|
|
188
|
+
</template>
|
|
189
|
+
{{ $t('Service not configured. Please contact administrator.') }}
|
|
190
|
+
</q-banner>
|
|
191
|
+
</q-card-section>
|
|
192
|
+
</q-card>
|
|
193
|
+
</div>
|
|
153
194
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
195
|
+
<!-- Microsoft -->
|
|
196
|
+
<div class="col-12 col-md-4">
|
|
197
|
+
<q-card class="full-height" :class="my.microsoft ? 'bg-blue-1 border-blue' : 'bg-grey-1'">
|
|
198
|
+
<q-card-section class="text-center q-pb-none">
|
|
199
|
+
<q-avatar size="64px" class="q-mb-md" :color="my.microsoft ? 'blue' : 'grey-5'"
|
|
200
|
+
text-color="white">
|
|
201
|
+
<q-icon :name="fabMicrosoft" size="32px" />
|
|
202
|
+
</q-avatar>
|
|
203
|
+
<div class="text-h6 q-mb-xs">Microsoft</div>
|
|
204
|
+
<q-badge v-if="my.microsoft" color="blue" class="q-mb-md">
|
|
205
|
+
{{ $t('Connected') }}
|
|
206
|
+
</q-badge>
|
|
207
|
+
<q-badge v-else color="grey-5" class="q-mb-md">
|
|
208
|
+
{{ $t('Not Connected') }}
|
|
209
|
+
</q-badge>
|
|
210
|
+
</q-card-section>
|
|
159
211
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
212
|
+
<q-card-section v-if="app.microsoftClientId">
|
|
213
|
+
<template v-if="my.microsoft">
|
|
214
|
+
<div class="text-center q-mb-md">
|
|
215
|
+
<div class="text-caption text-grey-6">{{ $t('Connected Account') }}</div>
|
|
216
|
+
<div class="text-body2 text-weight-medium q-mt-xs">{{ my.microsoft }}</div>
|
|
217
|
+
</div>
|
|
218
|
+
<q-btn class="full-width" color="negative" outline @click="onUnlinkMicrosoft"
|
|
219
|
+
icon="sym_o_link_off" :label="$t('Unlink Account')" />
|
|
220
|
+
</template>
|
|
221
|
+
<template v-else>
|
|
222
|
+
<div class="text-center q-mb-md text-grey-6">
|
|
223
|
+
{{ $t('Click to link your Microsoft account for quick sign-in') }}
|
|
224
|
+
</div>
|
|
225
|
+
<div class="flex justify-center">
|
|
226
|
+
<l-microsoft-button :client-id="app.microsoftClientId"
|
|
227
|
+
:tenant-id="app.microsoftTenantId" @login="onLinkMicrosoft" />
|
|
228
|
+
</div>
|
|
229
|
+
</template>
|
|
230
|
+
</q-card-section>
|
|
231
|
+
<q-card-section v-else>
|
|
232
|
+
<q-banner class="bg-orange-2 text-orange-9">
|
|
233
|
+
<template v-slot:avatar>
|
|
234
|
+
<q-icon name="sym_o_warning" />
|
|
235
|
+
</template>
|
|
236
|
+
{{ $t('Service not configured. Please contact administrator.') }}
|
|
237
|
+
</q-banner>
|
|
238
|
+
</q-card-section>
|
|
239
|
+
</q-card>
|
|
240
|
+
</div>
|
|
164
241
|
|
|
242
|
+
<!-- Facebook -->
|
|
243
|
+
<div class="col-12 col-md-4">
|
|
244
|
+
<q-card class="full-height" :class="my.facebook ? 'bg-indigo-1 border-indigo' : 'bg-grey-1'">
|
|
245
|
+
<q-card-section class="text-center q-pb-none">
|
|
246
|
+
<q-avatar size="64px" class="q-mb-md" :color="my.facebook ? 'indigo' : 'grey-5'"
|
|
247
|
+
text-color="white">
|
|
248
|
+
<q-icon :name="fabFacebook" size="32px" />
|
|
249
|
+
</q-avatar>
|
|
250
|
+
<div class="text-h6 q-mb-xs">Facebook</div>
|
|
251
|
+
<q-badge v-if="my.facebook" color="indigo" class="q-mb-md">
|
|
252
|
+
{{ $t('Connected') }}
|
|
253
|
+
</q-badge>
|
|
254
|
+
<q-badge v-else color="grey-5" class="q-mb-md">
|
|
255
|
+
{{ $t('Not Connected') }}
|
|
256
|
+
</q-badge>
|
|
257
|
+
</q-card-section>
|
|
165
258
|
|
|
166
|
-
|
|
259
|
+
<q-card-section v-if="app.facebookAppId">
|
|
260
|
+
<template v-if="my.facebook">
|
|
261
|
+
<div class="text-center q-mb-md">
|
|
262
|
+
<div class="text-caption text-grey-6">{{ $t('Connected Account') }}</div>
|
|
263
|
+
<div class="text-body2 text-weight-medium q-mt-xs">{{ my.facebook }}</div>
|
|
264
|
+
</div>
|
|
265
|
+
<q-btn class="full-width" color="negative" outline @click="onUnlinkFacebook"
|
|
266
|
+
icon="sym_o_link_off" :label="$t('Unlink Account')" />
|
|
267
|
+
</template>
|
|
268
|
+
<template v-else>
|
|
269
|
+
<div class="text-center q-mb-md text-grey-6">
|
|
270
|
+
{{ $t('Click to link your Facebook account for quick sign-in') }}
|
|
271
|
+
</div>
|
|
272
|
+
<div class="flex justify-center">
|
|
273
|
+
<l-facebook-button @login="onLinkFacebook" />
|
|
274
|
+
</div>
|
|
275
|
+
</template>
|
|
276
|
+
</q-card-section>
|
|
277
|
+
<q-card-section v-else>
|
|
278
|
+
<q-banner class="bg-orange-2 text-orange-9">
|
|
279
|
+
<template v-slot:avatar>
|
|
280
|
+
<q-icon name="sym_o_warning" />
|
|
281
|
+
</template>
|
|
282
|
+
{{ $t('Service not configured. Please contact administrator.') }}
|
|
283
|
+
</q-banner>
|
|
284
|
+
</q-card-section>
|
|
285
|
+
</q-card>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
167
288
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
<
|
|
171
|
-
<
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
@login="onLinkMicrosoft" />
|
|
182
|
-
</div>
|
|
183
|
-
</template>
|
|
184
|
-
</template>
|
|
185
|
-
<template v-else>
|
|
186
|
-
Micorsoft login is not available. Please set authentication microsft client id in server settings.
|
|
187
|
-
</template>
|
|
188
|
-
</q-card-section>
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
<q-separator />
|
|
192
|
-
<q-card-section>
|
|
193
|
-
<h2 class="text-h6">Facebook</h2>
|
|
194
|
-
<template v-if="app.facebookAppId">
|
|
195
|
-
<template v-if="my.facebook">
|
|
196
|
-
{{ $t('You have already linked your Facebook account.') }}<br />
|
|
197
|
-
Your account id is {{ my.facebook }}
|
|
198
|
-
<l-btn label="Unlink" @click="onUnlinkFacebook" icon="sym_o_link_off"></l-btn>
|
|
199
|
-
</template>
|
|
200
|
-
<template v-else>
|
|
201
|
-
<div>{{ $t('Click the button below to link your Facebook account.') }}</div>
|
|
202
|
-
<l-facebook-button @login="onLinkFacebook" />
|
|
203
|
-
</template>
|
|
204
|
-
</template>
|
|
205
|
-
<template v-else>
|
|
206
|
-
Facebook login is not available. Please set authentication facebook app id in server settings.
|
|
207
|
-
</template>
|
|
208
|
-
</q-card-section>
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
</q-card>
|
|
289
|
+
<!-- 安全提示 -->
|
|
290
|
+
<q-card class="q-mt-lg bg-blue-1">
|
|
291
|
+
<q-card-section>
|
|
292
|
+
<div class="row items-center">
|
|
293
|
+
<q-icon name="sym_o_security" color="blue" size="24px" class="q-mr-sm" />
|
|
294
|
+
<div class="text-h6 text-blue-8">{{ $t('Security Notice') }}</div>
|
|
295
|
+
</div>
|
|
296
|
+
<p class="text-blue-7 q-mb-none q-mt-sm">
|
|
297
|
+
{{ $t('Your social accounts are securely linked using industry-standard OAuth protocols. You can unlink them at any time.') }}
|
|
298
|
+
</p>
|
|
299
|
+
</q-card-section>
|
|
300
|
+
</q-card>
|
|
301
|
+
</div>
|
|
216
302
|
</template>
|
|
303
|
+
|
|
304
|
+
<style scoped>
|
|
305
|
+
.border-green{border:2px solid #4caf50!important}.border-blue{border:2px solid #2196f3!important}.border-indigo{border:2px solid #3f51b5!important}.q-card{border-radius:12px;transition:all .3s ease}.q-card:hover{box-shadow:0 8px 24px rgba(0,0,0,.12);transform:translateY(-2px)}.q-avatar{transition:all .3s ease}.q-card:hover .q-avatar{transform:scale(1.05)}.full-height{height:100%;min-height:300px}@media (max-width:768px){.full-height{min-height:auto}}
|
|
306
|
+
</style>
|
|
@@ -43,13 +43,14 @@ const policies = computed(() => {
|
|
|
43
43
|
validation="required|confirm" />
|
|
44
44
|
</FormKit>
|
|
45
45
|
|
|
46
|
-
<q-
|
|
46
|
+
<q-separator spaced />
|
|
47
|
+
|
|
48
|
+
<q-card flat :bordered="false">
|
|
47
49
|
<q-card-section>
|
|
48
50
|
<div>{{ $t('Password policy') }}</div>
|
|
49
51
|
<ul>
|
|
50
52
|
<li v-for="policy in policies" :key="policy">{{ policy }}</li>
|
|
51
53
|
</ul>
|
|
52
|
-
|
|
53
54
|
</q-card-section>
|
|
54
55
|
</q-card>
|
|
55
56
|
</template>
|