Commit 75b2d744 by Jay

init

parents
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
bin/
Bin/
obj/
Obj/
# Visual Studio 2015 cache/options directory
.vs/
.vs/JWTVueDemo/v16/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Microsoft Azure ApplicationInsights config file
ApplicationInsights.config
# Windows Store app package directory
AppPackages/
BundleArtifacts/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
orleans.codegen.cs
/node_modules
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
# FAKE - F# Make
.fake/

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29613.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JWTVueDemo", "JWTVueDemo\JWTVueDemo.csproj", "{010DBDE6-34FE-4B17-8CF4-A56DD5FE5D73}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{010DBDE6-34FE-4B17-8CF4-A56DD5FE5D73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{010DBDE6-34FE-4B17-8CF4-A56DD5FE5D73}.Debug|Any CPU.Build.0 = Debug|Any CPU
{010DBDE6-34FE-4B17-8CF4-A56DD5FE5D73}.Release|Any CPU.ActiveCfg = Release|Any CPU
{010DBDE6-34FE-4B17-8CF4-A56DD5FE5D73}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {69C8B8A9-8C17-4D5E-9C41-B43775F6D5F9}
EndGlobalSection
EndGlobal
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
bin/
Bin/
obj/
Obj/
# Visual Studio 2015 cache/options directory
.vs/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Microsoft Azure ApplicationInsights config file
ApplicationInsights.config
# Windows Store app package directory
AppPackages/
BundleArtifacts/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
orleans.codegen.cs
/node_modules
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
# FAKE - F# Make
.fake/
VUE_APP_API_BASE_URL=/api/
\ No newline at end of file
VUE_APP_API_BASE_URL=http://localhost:50233/api/
\ No newline at end of file
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
{
"singleQuote": true,
"semi": true,
"printWidth": 140
}
# order
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "demo",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"axios": "^0.19.2",
"core-js": "^3.4.4",
"moment": "^2.24.0",
"vue": "^2.6.10",
"vue-router": "^3.1.3",
"vuetify": "^2.1.0",
"vuetify-datetime-picker": "^2.1.1",
"vuex": "^3.1.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.1.0",
"@vue/cli-plugin-router": "^4.1.0",
"@vue/cli-plugin-vuex": "^4.1.0",
"@vue/cli-service": "^4.1.0",
"material-design-icons-iconfont": "^5.0.1",
"node-sass": "^4.14.1",
"sass": "^1.19.0",
"sass-loader": "^8.0.0",
"vue-cli-plugin-vuetify": "^2.0.3",
"vue-template-compiler": "^2.6.10",
"vuetify-loader": "^1.3.0"
}
}
<!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">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title></title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
<script src="./ckeditor/ckeditor.js"></script>
</head>
<body>
<noscript>
<strong>We're sorry but order doesn't work properly without JavaScript enabled. Please enable it to
continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
\ No newline at end of file
<template>
<v-app v-if="isLogin" style="background-color: #bbdefb;">
<v-navigation-drawer v-if="isLogin" v-model="sideBarOpen" app>
<v-list dense>
<v-list-item v-for="link in getLinks" :key="link.path" link :to="link.path">
<v-list-item-action>
<v-icon>{{ link.icon }}</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{ link.props.title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
<v-app-bar v-if="isLogin" app color="indigo" dark>
<v-app-bar-nav-icon @click="sideBarOpen = !sideBarOpen"></v-app-bar-nav-icon>
<v-toolbar-title>{{ pageTitle }}</v-toolbar-title>
<v-spacer />
<v-btn v-if="isLogin" icon large target="_blank" @click="Logout">
<v-icon large>mdi-account-arrow-right</v-icon>
</v-btn>
</v-app-bar>
<v-content>
<!-- <v-container class="fill-height" fluid> -->
<router-view></router-view>
<!-- </v-container> -->
</v-content>
<v-footer color="indigo" app>
<span class="white--text">&copy; 2020</span>
</v-footer>
<v-snackbar v-model="snackbar.visible" :timeout="0" :top="true" :color="snackbar.color">{{ snackbar.message }}</v-snackbar>
</v-app>
<v-app v-else>
<router-view />
</v-app>
</template>
<script>
import { changePassword } from './api/auth';
export default {
name: 'App',
components: {},
data: () => ({
sideBarOpen: false,
changePasswordDialogVisible: false,
requiredRule: [(v) => !!v || 'Required'],
}),
created() {
const localJWT = localStorage.getItem('authJWT');
if (localJWT) {
this.$store.commit('SetAuthJWT', { authJWT: localJWT });
this.$store.dispatch('basicData');
} else {
this.$router.push('/login');
}
this.changePasswordDialogVisible = false;
},
methods: {
Logout() {
this.$store.dispatch('Logout');
},
},
computed: {
pageTitle() {
return this.$store.state.pagesData.pageTitle;
},
isLogin() {
return !(this.$store.state.authData.authJWT === '');
},
getLinks() {
return this.$router.options.routes.filter(
(link) => link.isMenu //&& (link.admin === undefined || link.admin === this.$store.state.authData.isAdmin)
);
},
isAdmin() {
return this.$store.state.authData.isAdmin;
},
snackbar() {
return this.$store.state.snackbar;
},
},
watch: {},
};
</script>
<style lang="scss">
* {
box-sizing: border-box;
}
.row {
margin: 0 !important;
}
</style>
import { instance } from './instance';
export const login = ({ Account, Password }) => instance.post('/Auth/Login', { Account, Password });
/**
* @typedef {object} BaseResponse
* @property {boolean} Success
* @property {string} Msg
* @property {T} Data
* @template T
*/
/**
* @typedef {import('axios').AxiosResponse<T>} AxiosResponse
* @template T
*/
/**
* @typedef {Promise<AxiosResponse<BaseResponse<T>>>} PromiseWithBaseResponse
* @template T
*/
export {};
import axios from 'axios';
import store from '../store/index';
import router from '@/router';
import { authDataAction } from '../store/authData';
const instance = axios.create({
baseURL: process.env.VUE_APP_API_BASE_URL
});
instance.interceptors.request.use(config => {
const authJWT = store.state.authData.authJWT;
if (authJWT) {
config.headers.Authorization = 'Bearer ' + authJWT;
}
return config;
});
instance.interceptors.response.use(
config => {
// console.log(config);
return config; //.data;
},
error => {
if (error.response && (error.response.status === 401 || error.response.status === 403)) {
store.dispatch(authDataAction.Logout);
router.push('/');
}
return Promise.reject(error);
}
);
export { instance };
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg>
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import vuetify from './plugins/vuetify';
import moment from 'moment';
import DatetimePicker from 'vuetify-datetime-picker';
// (Optional) import 'vuetify-datetime-picker/src/stylus/main.styl'
Vue.use(DatetimePicker);
Vue.config.productionTip = false;
router.beforeEach((to, from, next) => {
// document.title = to.meta.title;
const isLogin = localStorage.getItem('authJWT') ? true : false;
if (isLogin) {
if (to.path === '/login') {
next('/home');
} else if (to.path === '/') {
next('/home');
} else {
// const page = router.options.routes.find(link => link.path === to.path);
// if (page) {
// const isAdmin = JSON.parse(atob(localStorage.getItem('authJWT').split('.')[1])).roles === 'Admin';
// if (page.admin === undefined || page.admin === isAdmin) {
// next();
// } else {
// alert('沒有權限進入該頁面');
// next(from.path); //
// }
// } else {
// next(from.path);
// }
next();
}
} else {
if (to.path === '/login') {
next();
} else {
next('/login');
}
}
});
Vue.filter('formatDateTime', value => {
if (value) {
return moment(String(value)).format('YYYY/MM/DD HH:mm:ss');
}
});
Vue.filter('formatDate', value => {
if (value) {
return moment(String(value)).format('YYYY/MM/DD');
}
});
new Vue({
router,
store,
vuetify,
render: h => h(App)
}).$mount('#app');
import { pagesDataAction } from '../store/pagesData';
/**
* @type {import('vue/types/options').ComponentOptions}
*/
export const setPageTitleMixin = {
props: ['title'],
created() {
document.title = this.title;
this.$store.commit(pagesDataAction.setPageTitle, { pageTitle: this.title });
}
};
import Vue from 'vue';
import 'material-design-icons-iconfont/dist/material-design-icons.css';
import Vuetify from 'vuetify/lib';
import zhHant from 'vuetify/es5/locale/zh-Hant';
Vue.use(Vuetify);
export default new Vuetify({
lang: {
locales: { zhHant },
current: 'zhHant'
},
icons: {
iconfont: 'md'
}
});
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '../views/Home.vue';
Vue.use(VueRouter);
const routes = [
{
path: '/login',
name: 'login',
component: () => import(/* webpackChunkName: "login" */ '../views/Login.vue'),
isMenu: false
// props: { title: '登入' },
},
{
path: '/home',
name: 'home',
component: Home,
isMenu: true,
icon: 'mdi-home',
admin: true,
props: { title: 'Home' }
},
{
path: '*',
component: () => import(/* webpackChunkName: "login" */ '../views/Login.vue'),
isMenu: false,
props: { title: '登入' }
}
];
const router = new VueRouter({
mode: 'history',
routes
});
export default router;
import { login } from '../api/auth';
import router from '@/router';
const SetAuthJWT = 'SetAuthJWT';
const RemoveAuthJWT = 'RemoveAuthJWT';
export const authDataMutation = {
SetAuthJWT,
RemoveAuthJWT
};
const Login = 'Login';
const Logout = 'Logout';
export const authDataAction = {
Login,
Logout
};
/**
* @type {import('vuex/types/index').Module<{authJWT:string}>}
*/
const authData = {
state: {
authJWT: '',
isAdmin: false,
name: ''
},
mutations: {
/**
* @param {{authJWT:string}} payload
*/
[SetAuthJWT](state, payload) {
state.authJWT = payload.authJWT;
const info = JSON.parse(atob(payload.authJWT.split('.')[1]));
state.isAdmin = info.roles === 'Admin';
state.name = decodeURI(info.ShopName);
},
[RemoveAuthJWT](state, payload) {
state.authJWT = '';
state.isAdmin = false;
state.name = '';
}
},
actions: {
async [Login]({ commit, dispatch }, payload) {
const { data } = await login({
Account: payload.Account,
Password: payload.Password
});
if (data.Success) {
commit(SetAuthJWT, { authJWT: data.Data });
localStorage.setItem('authJWT', data.Data);
dispatch('basicData');
}
return data;
},
async [Logout]({ commit, dispatch }, payload) {
commit(RemoveAuthJWT);
localStorage.removeItem('authJWT');
router.push('/login');
return true;
}
}
};
export { authData };
import Vue from 'vue';
import Vuex from 'vuex';
import { pagesData } from './pagesData';
import { authData } from './authData';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
snackbar: {
visible: false,
message: '',
color: 'error',
},
},
mutations: {
showSuccessSnackBar(state, payload) {
state.snackbar.message = payload;
state.snackbar.color = 'success';
state.snackbar.visible = true;
setTimeout(() => {
state.snackbar.visible = false;
}, 3000);
},
showInfoSnackBar(state, payload) {
state.snackbar.message = payload;
state.snackbar.color = 'info';
state.snackbar.visible = true;
setTimeout(() => {
state.snackbar.visible = false;
}, 3000);
},
showErrorSnackBar(state, payload) {
state.snackbar.message = payload;
state.snackbar.color = 'error';
state.snackbar.visible = true;
setTimeout(() => {
state.snackbar.visible = false;
}, 3000);
},
},
actions: {},
modules: {
pagesData,
authData,
},
});
const setPageTitle = 'setPageTitle';
export const pagesDataAction = {
setPageTitle
};
/**
* @type {import('vuex/types/index').Module<{pageTitle:string}>}
*/
const pagesData = {
state: {
pageTitle: ''
},
mutations: {
setPageTitle(state, { pageTitle }) {
state.pageTitle = pageTitle;
}
}
};
export { pagesData };
<template>
<div class="home">
<!-- <v-btn>Home</v-btn> -->
</div>
</template>
<script>
// @ is an alias to /src
import { setPageTitleMixin } from '../mixins/setPageTitleMixin';
export default {
mixins: [setPageTitleMixin],
components: {},
name: 'home'
};
</script>
<template>
<v-row align="center" justify="center">
<v-col cols="12" sm="8" md="6" lg="4" xl="3">
<v-card class="elevation-12">
<v-toolbar color="primary" dark flat>
<v-toolbar-title>登入</v-toolbar-title>
</v-toolbar>
<v-card-text>
<!-- <v-img src="logo.png" class="mb-4" contain></v-img> -->
<v-form>
<v-row no-gutters>
<span class="mr-1">帳號</span>
<v-icon>account_circle</v-icon>
</v-row>
<v-text-field class="mt-n3" v-model="account"></v-text-field>
<v-row no-gutters>
<span class="mr-1">密碼</span>
<v-icon>mdi-lock</v-icon>
</v-row>
<v-text-field class="mt-n3" v-model="password" type="password"></v-text-field>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn color="primary" @click="login">Login</v-btn>
</v-card-actions>
</v-card>
</v-col>
<v-snackbar v-model="snackbarVisible" :timeout="3000" :top="true" color="error">{{ errMsg }}</v-snackbar>
</v-row>
</template>
<script>
import { setPageTitleMixin } from '../mixins/setPageTitleMixin';
import { pagesDataAction } from '../store/pagesData';
import { login } from '../api/auth';
import { authData, authDataAction } from '../store/authData';
// import { getAllBranch } from '../api/branch';
export default {
mixins: [setPageTitleMixin],
data: () => ({
account: '',
password: '',
snackbarVisible: false,
errMsg: ''
}),
async created() {},
methods: {
async login() {
const loginState = await this.$store.dispatch(authDataAction.Login, {
Account: this.account,
Password: this.password
});
if (loginState.Success) {
this.$router.push('/home');
} else {
this.snackbarVisible = true;
this.errMsg = loginState.Msg;
}
}
}
};
</script>
<style lang="scss" scoped></style>
<template>
<div class="home"></div>
</template>
<script>
// @ is an alias to /src
import { setPageTitleMixin } from '../mixins/setPageTitleMixin';
export default {
mixins: [setPageTitleMixin],
components: {},
name: 'template',
props: { id: String },
data: () => ({}),
created() {},
mounted() {},
methods: {}
};
</script>
module.exports = { productionSourceMap: false, transpileDependencies: ['vuetify'] };
using JWTVueDemo.Models;
using JWTVueDemo.Models.RequestModel;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace JWTVueDemo.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthController : BaseController
{
public AuthController(IOptions<ApplicationSettings> appSettings) : base(appSettings)
{
}
[HttpPost("[action]")]
public BaseResponse<string> Login(LoginRequestModel loginInfo)
{
BaseResponse<string> response = new BaseResponse<string>
{
Data = "",
Msg = "",
Success = true
};
if (loginInfo.Account.Equals("admin") && loginInfo.Password.Equals("123"))
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(AppSettings.JWTSecret));
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim("UserId","admin"),
//new Claim("roles",isAdmin?Roles.Admin:Roles.User)
}),
Expires = DateTime.UtcNow.AddDays(30),
SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(securityToken);
response.Data = token;
return response;
}
else
{
response.Success = false;
response.Msg = "帳號密碼錯誤";
return response;
}
}
}
}
using JWTVueDemo.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace JWTVueDemo.Controllers
{
public class BaseController : ControllerBase
{
internal readonly ApplicationSettings AppSettings;
public BaseController(IOptions<ApplicationSettings> appSettings)
{
this.AppSettings = appSettings?.Value;
}
}
}
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable>
<SpaRoot>ClientApp\</SpaRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
<!-- Set this to true if you enable server-side prerendering -->
<BuildServerSideRenderer>false</BuildServerSideRenderer>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.5" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.0" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.6.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.6.0" />
</ItemGroup>
<ItemGroup>
<!-- Don't publish the SPA source files, but do show them in the project files list -->
<Content Remove="$(SpaRoot)**" />
<None Remove="$(SpaRoot)**" />
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
</ItemGroup>
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
</Target>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<!--<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build prod" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build:ssr prod" Condition=" '$(BuildServerSideRenderer)' == 'true' " />-->
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="$(SpaRoot)dist\**; $(SpaRoot)dist-server\**" />
<DistFiles Include="$(SpaRoot)node_modules\**" Condition="'$(BuildServerSideRenderer)' == 'true'" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
</Project>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace JWTVueDemo.Models
{
public class ApplicationSettings
{
public string JWTSecret { get; set; }
public string IdentityConnection { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace JWTVueDemo.Models
{
public class BaseResponse
{
public BaseResponse()
{
Success = false;
Msg = string.Empty;
}
public BaseResponse(bool success)
{
Success = success;
Msg = string.Empty;
}
public BaseResponse(bool success, string msg)
{
Success = success;
Msg = msg;
}
public bool Success { get; set; }
public string Msg { get; set; }
}
public class BaseResponse<T> : BaseResponse
{
public BaseResponse()
{
Success = false;
Msg = string.Empty;
Data = default;
}
public BaseResponse(bool success, string msg, T data) : base(success, msg)
{
Data = data;
}
public T Data { get; set; }
}
}
namespace JWTVueDemo.Models.RequestModel
{
public class LoginRequestModel
{
public string Account { get; set; }
public string Password { get; set; }
}
}
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace JWTVueDemo.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
private readonly ILogger<ErrorModel> _logger;
public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}
@using JWTVueDemo
@namespace JWTVueDemo.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace JWTVueDemo
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:50233",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"JWTVueDemo": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
using JWTVueDemo.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SpaServices.AngularCli;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using JWTVueDemo.Util;
namespace JWTVueDemo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var settingsSection = Configuration.GetSection("ApplicationSettings");
var applicationSettings = settingsSection.Get<ApplicationSettings>();
services.Configure<ApplicationSettings>(settingsSection);
services.AddJWT(applicationSettings.JWTSecret);
services.AddControllersWithViews().AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDeveloperExceptionPage();
app.UseCors(builder => builder
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
//.SetIsOriginAllowed((host) => true)
//.AllowCredentials()
);
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
//spa.UseAngularCliServer(npmScript: "start");
}
});
}
}
}
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Text;
namespace JWTVueDemo.Util
{
public static class JwtServiceProvider
{
public static IServiceCollection AddJWT(this IServiceCollection services, string JWTSecret)
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JWTSecret));
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = false;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
ValidateIssuer = false,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero
};
});
return services;
}
}
}
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ApplicationSettings": {
"JWTSecret": "1234567890123456",
"IdentityConnection": "Server=.;Database=Test;User Id=sa;password=P@ssw0rd;MultipleActiveResultSets=True;"
}
}
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ApplicationSettings": {
"JWTSecret": "1234567890123456",
"IdentityConnection": "Server=.;Database=Test;User Id=sa;password=P@ssw0rd;MultipleActiveResultSets=True;"
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment