使用vue+vuetify为密钥斗篷创建一个登录主题,但不起作用



我想使用vue+vuetify为keycapture创建一个登录页面。

我遵循了一些指南和项目,试图简化这一点,但根本没有成功。事实上,我可以让它显示一些日志,但没有呈现应用程序。

概括一下,主要想法是为密钥斗篷的每个区域都有一个项目,比如:[登录帐户注册]。

所以每个项目都会有:

应用.vue

在这种情况下是登录页面的代码。

main.js

import Vue from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify'
const environment = document.querySelector('#environment');
if (environment) {
console.log('environment has value');
const environmentData = JSON.parse(environment.textContent)
console.log(environmentData);
Vue.config.productionTip = false
Vue.prototype.$environment = environmentData;
console.log('injected environment');
new Vue({
vuetify,
render: h => h(App)
}).$mount('#app')
console.log('started vue app');
}

index.ftl

<#import "template.ftl" as layout>
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','password') displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled??; section>
<#if section = "scripts">
<script typo="module" src="${url.resourcesPath}/js/login.js"></script>
</#if>
</@layout.registrationLayout>

静态(文件夹(

主题.属性

locales=ca,cs,da,de,en,es,fr,hu,it,ja,lt,nl,no,pl,pt-BR,ru,sk,sv,tr,zh-CN

template.ftl

<#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true displayRequiredFields=false showAnotherWayIfPresent=true>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" class="${properties.kcHtmlClass!}">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="robots" content="noindex, nofollow">
<title>${msg("loginTitle",(realm.displayName!''))}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<#nested "head">
</head>
<body>
<div id="app"></div>
<script id="environment" type="application/json">
{
"urls": {
"loginResetCredentials": "${url.loginResetCredentialsUrl}",
"login": "${url.loginUrl}",
"registration": "${url.registrationUrl}",
"loginAction": "${url.loginAction}",
"registrationAction": "${url.registrationAction}"
},
"titles": {
"loginProfileTitle": "${msg("loginProfileTitle")}",
"loginAccountTitle": "${msg("loginAccountTitle")}",
"registerTitle": "${msg("registerTitle")}",
"emailForgotTitle": "${msg("emailForgotTitle")}",
"confirmLinkIdpTitle": "${msg("confirmLinkIdpTitle")}",
"emailLinkIdpTitle": "${msg("emailLinkIdpTitle", idpDisplayName)}"
},
"permissions": {
"usernameEditDisabled": <#if usernameEditDisabled??>true<#else>false</#if>,
"loginWithEmailAllowed": <#if realm.loginWithEmailAllowed>true<#else>false</#if>,
"registrationEmailAsUsername": <#if realm.registrationEmailAsUsername>true<#else>false</#if>,
"rememberMe": <#if realm.rememberMe>true<#else>false</#if>,
"resetPasswordAllowed": <#if realm.resetPasswordAllowed>true<#else>false</#if>,
"password": <#if realm.password>true<#else>false</#if>,
"registrationAllowed": <#if realm.registrationAllowed>true<#else>false</#if>,
"registrationDisabled": <#if registrationDisabled??>true<#else>false</#if>,
"passwordRequired": <#if passwordRequired??>true<#else>false</#if>
},
"labels": {
"firstName": "${msg("firstName")}",
"lastName": "${msg("lastName")}",
"username": "${msg("username")}",
"usernameOrEmail": "${msg("usernameOrEmail")}",
"email": "${msg("email")}",
"password": "${msg("password")}",
"passwordConfirm": "${msg("passwordConfirm")}",
"rememberMe": "${msg("rememberMe")}",
"doForgotPassword": "${msg("doForgotPassword")}",
"doLogIn": "${msg("doLogIn")}",
"doSubmit": "${msg("doSubmit")}",
"noAccount": "${msg("noAccount")}",
"doRegister": "${msg("doRegister")}",
"backToLogin": "${kcSanitize(msg("backToLogin"))?no_esc}",
"confirmLinkIdpContinue": "${msg("confirmLinkIdpContinue")}",
"doClickHere": "${msg("doClickHere")}"
},
"forms": {
"loginUsername": "${(login.username!'')}",
"loginRememberMe": <#if login.rememberMe??>true<#else>false</#if>,
"selectedCredential": "${(auth.selectedCredential!'')}",
"registerFirstName": <#if register??>"${(register.formData.firstName!'')}"<#else>""</#if>,
"registerLastName": <#if register??>"${(register.formData.lastName!'')}"<#else>""</#if>,
"registerEmail": <#if register??>"${(register.formData.email!'')}"<#else>""</#if>,
"registerUsername": <#if register??>"${(register.formData.username!'')}"<#else>""</#if>
},
"user": {
"username": <#if user??>"${(user.username!'')}"<#else>""</#if>,
"email": <#if user??>"${(user.email!'')}"<#else>""</#if>,
"firstName": <#if user??>"${(user.firstName!'')}"<#else>""</#if>,
"lastName": <#if user??>"${(user.lastName!'')}"<#else>""</#if>
},
"validations": {
"firstName": <#if messagesPerField.existsError('firstName')>"${kcSanitize(messagesPerField.get('firstName'))?no_esc}"<#else>""</#if>,
"lastName":  <#if messagesPerField.existsError('lastName')>"${kcSanitize(messagesPerField.get('lastName'))?no_esc}"<#else>""</#if>,
"email": <#if messagesPerField.existsError('email')>"${kcSanitize(messagesPerField.get('email'))?no_esc}"<#else>""</#if>,
"usernameOrPassword": <#if messagesPerField.existsError('username','password')>"${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc}"<#else>""</#if>,
"username": <#if messagesPerField.existsError('username')>"${kcSanitize(messagesPerField.get('username'))?no_esc}"<#else>""</#if>,
"password": <#if messagesPerField.existsError('password')>"${kcSanitize(messagesPerField.get('password'))?no_esc}"<#else>""</#if>,
"passwordConfirm": <#if messagesPerField.existsError('password-confirm')>"${kcSanitize(messagesPerField.get('password-confirm'))?no_esc}"<#else>""</#if>
},
"message": {
"type": <#if displayMessage && message?has_content && (message.type != 'warning' || !isAppInitiatedAction??)>"${message.type}"<#else>""</#if>,
"sumary": <#if displayMessage && message?has_content && (message.type != 'warning' || !isAppInitiatedAction??)>"${kcSanitize(message.summary)?no_esc}"<#else>""</#if>
},
"instruction": {
"emailLinkIdp1": <#if emailLinkIdp1??>"${msg("emailLinkIdp1", idpDisplayName, brokerContext.username, realm.displayName)}"<#else>""</#if>,
"emailLinkIdp2": <#if emailLinkIdp2??>"${msg("emailLinkIdp2")}"<#else>""</#if>,
"emailLinkIdp3": <#if emailLinkIdp3??>"${msg("emailLinkIdp3")}"<#else>""</#if>,
"emailLinkIdp4": <#if emailLinkIdp4??>"${msg("emailLinkIdp4")}"<#else>""</#if>,
"emailLinkIdp5": <#if emailLinkIdp5??>"${msg("emailLinkIdp5")}"<#else>""</#if>
},
"social": [
<#if realm.password && social.providers??>
<#list social.providers as p>
{ 
"alias": "${p.alias}",
"displayName": "${p.displayName!}",
"loginUrl": "${p.loginUrl}"
}<#sep>, </#sep>
</#list>
</#if>
]
}
</script>
<#nested "scripts">
</body>
</html>
</#macro>

package.json

{
"name": "login",
"theme":{
"name":"customTheme"
},
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.8.3",
"vue": "^2.6.14",
"vuetify": "^2.6.0"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"html-webpack-plugin": "^5.5.0",
"sass": "~1.32",
"sass-loader": "^10.3.1",
"vue-template-compiler": "^2.6.14",
"vuetify-loader": "^1.9.1"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {
"no-unused-vars": "off",
"vue/multi-word-component-names": 0
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

vue.config.js

const { defineConfig } = require('@vue/cli-service');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const packageJson = require('./package.json');
module.exports = defineConfig({
transpileDependencies: [
'vuetify'
],
configureWebpack:  {
entry: () => {
let entry = {}
entry[packageJson.name] = path.resolve(
__dirname,
'src',
'main.js'
);
return entry;
},
output: {
filename: `keycloak/themes/${packageJson.theme.name}/${packageJson.name}/resources/js/[name].js`,
publicPath: '/'
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.vue', '.json', '.scss'],
alias: {
'@': path.resolve(__dirname, 'src'),     
'@hooks': path.resolve(__dirname, 'src','hooks'),
'@plugins': path.resolve(__dirname, 'src','plugins'),
'@components': path.resolve(__dirname, 'src','components'),         
}
},
module: {
rules: [
{
test: /.vue$/,
loader: 'vue-loader',
options: {
extract: false
}
},
{
test: /.(scss|css)$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: { autoprefixer: {} }
}
}
},
'sass-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, 'src', 'static'),
to: path.resolve(__dirname, 'dist', 'keycloak', 'themes', packageJson.theme.name, packageJson.name)
},
{
from: path.resolve(__dirname, 'src', 'index.ftl'),
to: path.resolve(__dirname, 'dist', 'keycloak', 'themes', packageJson.theme.name, packageJson.name,`${packageJson.name}.ftl`)
},
]
})
],
... {
optimization: {
removeAvailableModules: false,
removeEmptyChunks: false,
splitChunks: false
}
}
},
})

如果你需要更多的细节,或者如果你不想创建一个新的应用程序,而是想克隆我的回购,这里是链接

参考项目:

Vue TS项目

vuetify 概念的证明

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@3.2.5/dist/vuetify.min.css"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@3.2.5/dist/vuetify.min.js"></script>




</head>
<body>
<div id="app">
<v-card class="mx-auto" max-width="344">
<v-img src="https://cdn.vuetifyjs.com/images/cards/sunshine.jpg" height="200px" cover></v-img>

<v-card-title>
Top western road trips
</v-card-title>

<v-card-subtitle>
1,000 miles of wonder
</v-card-subtitle>

<v-card-actions>
<v-btn color="orange-lighten-2" variant="text">
Explore
</v-btn>

<v-spacer></v-spacer>

<v-btn :icon="show ? 'mdi-chevron-up' : 'mdi-chevron-down'" @click="show = !show"></v-btn>
</v-card-actions>

<v-expand-transition>
<div v-show="show">
<v-divider></v-divider>

<v-card-text>
I'm a thing. But, like most politicians, he promised more than he could deliver. You won't have time
for sleeping, soldier, not with all the bed making you'll be doing. Then we'll go with that data
file! Hey, you add a one and two zeros to that or we walk! You're going to do his laundry? I've got
to find a way to escape.
</v-card-text>
</div>
</v-expand-transition>
</v-card>
</div>

<script>
const { createApp } = Vue
const { createVuetify } = Vuetify
const vuetify = createVuetify()
const app = createApp()
app.use(vuetify).mount('#app')
</script>
</body>
</html>

您可以使用package.json 的CDN实例

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@3.2.5/dist/vuetify.min.css"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@3.2.5/dist/vuetify.min.js"></script>

最新更新