Commit c8d09a08 authored by 罗超's avatar 罗超

初始化

parent 2b9c0db1
Pipeline #143 failed with stages
......@@ -3,5 +3,6 @@
"vetur.format.enable": false,
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
"typescript.tsdk": "node_modules/typescript/lib",
"vetur.experimental.templateInterpolationService": true
"vetur.experimental.templateInterpolationService": true,
"vue-i18n.i18nPaths": "src/i18n"
}
<!DOCTYPE html>
<html>
<head>
<title><%= productName %></title>
<meta charset="utf-8">
<meta name="description" content="<%= productDescription %>">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>">
<link rel="icon" type="image/png" sizes="128x128" href="icons/favicon-128x128.png">
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
<link rel="icon" type="image/ico" href="favicon.ico">
</head>
<body>
<!-- quasar:entry-point -->
</body>
</html>
{
"name": "ocss",
"name": "bigwood",
"version": "0.0.1",
"description": "niuu bee",
"productName": "Ocss App",
"description": "JVS 同業訂房網",
"productName": "JVS 同業訂房網",
"author": "alex9012 <alex9012@vip.qq.com>",
"private": true,
"scripts": {
"lint": "eslint --ext .js,.ts,.vue ./",
"format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test": "echo \"No test specified\" && exit 0"
},
"dependencies": {
"@quasar/extras": "^1.0.0",
"@quasar/extras": "^1.15.5",
"@types/lockr": "^0.8.7",
"@types/lodash": "^4.14.175",
"@types/webpack-env": "^1.16.2",
"axios": "^0.21.1",
"core-js": "^3.6.5",
"js-md5": "^0.7.3",
"katex": "^0.13.18",
"lockr": "^0.9.0-beta.0",
"lodash": "^4.17.21",
"md5-ts": "^0.1.6",
"mermaid": "^8.12.1",
"quasar": "^2.0.0",
"quasar": "^2.10.0",
"quasar-tiptap-branch": "^1.8.1",
"vue": "^3.0.0",
"vue-i18n": "^9.0.0",
"vue-inline-svg": "^2.1.1",
"vue-router": "^4.0.0",
"vuex": "^4.0.1",
"vuex-persistedstate": "^4.1.0"
},
"devDependencies": {
"@babel/eslint-parser": "^7.13.14",
"@quasar/app": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
"eslint": "^8.10.0",
"eslint-plugin-vue": "^9.0.0",
"eslint-config-prettier": "^8.1.0",
"prettier": "^2.5.1",
"@types/node": "^12.20.21",
"@types/webpack-env": "^1.16.2",
"@typescript-eslint/eslint-plugin": "^4.31.1",
"@typescript-eslint/parser": "^4.31.1",
"@vue/eslint-config-typescript": "^7.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^7.0.0"
"@intlify/vite-plugin-vue-i18n": "^3.3.1",
"@quasar/app-vite": "^1.0.0",
"autoprefixer": "^10.4.2",
"typescript": "^4.5.4"
},
"browserslist": [
"last 10 Chrome versions",
......@@ -49,7 +57,7 @@
"last 5 Opera versions"
],
"engines": {
"node": ">= 12.22.1",
"node": "^18 || ^16 || ^14.19",
"npm": ">= 6.13.4",
"yarn": ">= 1.21.1"
}
......
......@@ -51,7 +51,10 @@ module.exports = configure(function (ctx) {
// Full list of options: https://v2.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build
build: {
vueRouterMode: 'history', // available values: 'hash', 'history'
target: {
browser: [ 'es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1' ],
node: 'node16'
},
// transpile: false,
// Add dependencies for transpiling with Babel (Array of string/regex)
......@@ -61,19 +64,33 @@ module.exports = configure(function (ctx) {
env: ctx.dev
? {
BASE_APP_API: 'http://192.168.20.51:8088/api'
BASE_APP_API: 'http://192.168.10.206:8015/api/common/post'
}
: {
BASE_APP_API: 'https://eduapi.oytour.com/api'
BASE_APP_API: 'https://reborn.oytour.com/api/common/post'
},
extendWebpack(cfg, { isServer, isClient }) {
cfg.resolve.alias = {
...cfg.resolve.alias,
'@': path.resolve(__dirname, './src')
}
},
// extendWebpack(cfg, { isServer, isClient }) {
// cfg.resolve.alias = {
// ...cfg.resolve.alias,
// '@': path.resolve(__dirname, './src')
// }
// },
vitePlugins: [
[
'@intlify/vite-plugin-vue-i18n',
{
// if you want to use Vue I18n Legacy API, you need to set `compositionOnly: false`
// compositionOnly: false,
// you need to set i18n resource including paths !
include: path.resolve(__dirname, './src/i18n/**'),
},
],
],
// alias:[{
// '@': path.resolve(__dirname, './src')
// }],
// rtl: true, // https://v2.quasar.dev/options/rtl-support
// preloadChunks: true,
// showProgress: false,
......@@ -85,9 +102,9 @@ module.exports = configure(function (ctx) {
// https://v2.quasar.dev/quasar-cli/handling-webpack
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
chainWebpack(/* chain */) {
//
}
// chainWebpack(/* chain */) {
// //
// }
},
// Full list of options: https://v2.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-devServer
......@@ -144,74 +161,91 @@ module.exports = configure(function (ctx) {
// https://v2.quasar.dev/quasar-cli/developing-pwa/configuring-pwa
pwa: {
workboxPluginMode: 'GenerateSW', // 'GenerateSW' or 'InjectManifest'
workboxOptions: {}, // only for GenerateSW
// for the custom service worker ONLY (/src-pwa/custom-service-worker.[js|ts])
// if using workbox in InjectManifest mode
chainWebpackCustomSW(/* chain */) {
//
},
manifest: {
name: 'Ocss App',
short_name: 'Ocss App',
description: 'niuu bee',
display: 'standalone',
orientation: 'portrait',
background_color: '#ffffff',
theme_color: '#027be3',
icons: [
{
src: 'icons/icon-128x128.png',
sizes: '128x128',
type: 'image/png'
},
{
src: 'icons/icon-192x192.png',
sizes: '192x192',
type: 'image/png'
},
{
src: 'icons/icon-256x256.png',
sizes: '256x256',
type: 'image/png'
},
{
src: 'icons/icon-384x384.png',
sizes: '384x384',
type: 'image/png'
},
{
src: 'icons/icon-512x512.png',
sizes: '512x512',
type: 'image/png'
}
]
}
// workboxPluginMode: 'generateSW', // 'GenerateSW' or 'InjectManifest'
// workboxOptions: {}, // only for GenerateSW
// // for the custom service worker ONLY (/src-pwa/custom-service-worker.[js|ts])
// // if using workbox in InjectManifest mode
// chainWebpackCustomSW(/* chain */) {
// //
// },
// manifest: {
// name: 'Ocss App',
// short_name: 'Ocss App',
// description: 'niuu bee',
// display: 'standalone',
// orientation: 'portrait',
// background_color: '#ffffff',
// theme_color: '#027be3',
// icons: [
// {
// src: 'icons/icon-128x128.png',
// sizes: '128x128',
// type: 'image/png'
// },
// {
// src: 'icons/icon-192x192.png',
// sizes: '192x192',
// type: 'image/png'
// },
// {
// src: 'icons/icon-256x256.png',
// sizes: '256x256',
// type: 'image/png'
// },
// {
// src: 'icons/icon-384x384.png',
// sizes: '384x384',
// type: 'image/png'
// },
// {
// src: 'icons/icon-512x512.png',
// sizes: '512x512',
// type: 'image/png'
// }
// ]
// }
workboxMode: 'generateSW', // or 'injectManifest'
injectPwaMetaTags: true,
swFilename: 'sw.js',
manifestFilename: 'manifest.json',
useCredentialsForManifestTag: false,
// useFilenameHashes: true,
// extendGenerateSWOptions (cfg) {}
// extendInjectManifestOptions (cfg) {},
// extendManifestJson (json) {}
// extendPWACustomSWConf (esbuildConf) {}
},
// Full list of options: https://v2.quasar.dev/quasar-cli/developing-cordova-apps/configuring-cordova
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-cordova-apps/configuring-cordova
cordova: {
// noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing
},
// Full list of options: https://v2.quasar.dev/quasar-cli/developing-capacitor-apps/configuring-capacitor
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-capacitor-apps/configuring-capacitor
capacitor: {
hideSplashscreen: true
},
// Full list of options: https://v2.quasar.dev/quasar-cli/developing-electron-apps/configuring-electron
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-electron-apps/configuring-electron
electron: {
// extendElectronMainConf (esbuildConf)
// extendElectronPreloadConf (esbuildConf)
inspectPort: 5858,
bundler: 'packager', // 'packager' or 'builder'
packager: {
// https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options
// OS X / Mac App Store
// appBundleId: '',
// appCategoryType: '',
// osxSign: '',
// protocol: 'myapp://path',
// Windows only
// win32metadata: { ... }
},
......@@ -219,20 +253,18 @@ module.exports = configure(function (ctx) {
builder: {
// https://www.electron.build/configuration/configuration
appId: 'ocss'
},
appId: 'bigwood'
}
},
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
chainWebpack(/* chain */) {
// do something with the Electron main process Webpack cfg
// extendWebpackMain also available besides this chainWebpackMain
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex
bex: {
contentScripts: [
'JVS'
],
// "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain
chainWebpackPreload(/* chain */) {
// do something with the Electron main process Webpack cfg
// extendWebpackPreload also available besides this chainWebpackPreload
}
// extendBexScriptsConf (esbuildConf) {}
// extendBexManifestJson (json) {}
}
}
})
......@@ -9,6 +9,10 @@ export enum RoleType {
'项目成员',
'项目访客'
}
export enum ApiResult{
'SUCCESS' = 1,
'FAILED' = 0
}
/**
* @description 常用异常结果常量定义
*/
......
import { AppStateType } from '@/store/modules/app/state'
import { UserStateType } from '@/store/modules/user/state'
import { TeamStateType } from '@/store/modules/user/modules/team/state'
import { AppStateType } from '../store/modules/app/state'
import { UserStateType } from '../store/modules/user/state'
import { TeamStateType } from '../store/modules/user/modules/team/state'
import { RoleType } from './enumHelper'
// vuex state 的模块的类型
......@@ -75,8 +76,9 @@ export interface HttpResponse {
status: number
statusText: string
data: {
code: number
desc: string
resultCode: number
message: string
data: any
[key: string]: any
}
}
......@@ -115,6 +117,12 @@ export interface TeamMemberType {
cloudRole?: string
}
export interface SitLang{
langName?:string,
langLocale?:string,
langIcon?:string
}
// 权限列表类型
export interface RoleItemType {
......
<template>
<router-view />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { defineComponent } from 'vue';
export default defineComponent({
name: 'App'
})
});
</script>
<style lang="sass">
@import url('./css/font.sass')
@import url('//at.alicdn.com/t/c/font_3734871_9agx0gqqibi.css')
</style>
import Axios, { AxiosResponse, AxiosRequestConfig, AxiosError } from 'axios'
import router from '@/router/index'
import router from '../router/index'
// import { message } from 'ant-design-vue'
import Store from '@/store'
import Store from '../store'
import { Notify } from 'quasar'
/**
......@@ -68,30 +68,33 @@ const service = Axios.create({
}
})
/**
* @description 请求发起前的拦截器
* @returns {AxiosRequestConfig} config
*/
service.interceptors.request.use(
async (config: AxiosRequestConfig) => {
// 如果是获取token接口:
if (config.url === '/auth/oauth/token') {
//TODO:用户登录的特殊处理,
} else {
// 如果保存有token,则取,否则不添加Authorization
if (localStorage.vuex && JSON.parse(localStorage.vuex).user?.token) {
//TODO:Access_TOKEN 的获取,需要根据实际业务情况进行调整
const token = Store.state.user?.token
const tokenType = token.token_type
const accessToken = token.access_token
config.headers.Authorization = `${tokenType} ${accessToken}`
}
// if (config.url === '/auth/oauth/token') {
// //TODO:用户登录的特殊处理,
// } else {
// // 如果保存有token,则取,否则不添加Authorization
// if (localStorage.vuex && JSON.parse(localStorage.vuex).user?.token) {
// //TODO:Access_TOKEN 的获取,需要根据实际业务情况进行调整
// const token = Store.state.user?.token
// const tokenType = token.token_type
// const accessToken = token.access_token
// config.headers.Authorization = `${tokenType} ${accessToken}`
// }
//TODO:包装一层MSG,但是微服务应该去掉此步骤
config.data = {
Msg: config.data
}
}
// //TODO:包装一层MSG,但是微服务应该去掉此步骤
// config.data = {
// Msg: config.data
// }
// }
return config
},
......
import Axios from './axios'
import { HttpResponse } from '@/@types/index'
import { HttpResponse } from '../@types/index'
/**
* @description 公共模块的的网络请求,所有通用 api 放在此处
......
import service from "./axios";
import Store from '../store'
import md5 from "md5-ts";
import { HttpResponse } from "@/@types";
const request = (cmd:string,msg:any): Promise<HttpResponse>=>{
msg = msg??{}
let token = "";
let key = "";
let timestamp = (new Date()).valueOf();
if (localStorage.vuex && JSON.parse(localStorage.vuex).user?.token) {
token = Store.state.user?.token?.access_token
key = Store.state.user?.secretKey
}
var encodeMsg = encodeURIComponent(JSON.stringify(msg)).toLowerCase();
var md5Str = md5(`cmd=${cmd}&msg=${encodeMsg}&timestamp=${timestamp}&token=${token}&key=${key}`);
var postData = {
"msg": msg,
"cmd": cmd,
"timestamp": timestamp,
"token": token,
"sign": md5Str,
"languageId": 0
}
return service.post('', postData)
}
export default request;
\ No newline at end of file
......@@ -2,8 +2,9 @@
* 所有跟用户相关的接口(TODO:DEMO USER)
*/
import { HttpResponse } from '@/@types'
import { HttpResponse } from '../@types'
import Axios from './axios'
import request from './request'
interface HttpParams {
coinName: string
......@@ -12,14 +13,14 @@ interface HttpParams {
/**
* @interface loginParams -登录参数
* @property {string} grant_type -授权类型
* @property {string} email -邮箱
* @property {string} password -用户密码
* @property {string} account -账号
* @property {string} password -密码
* @property {string} platform -登陆平台(默认为0)
*/
interface LoginParams {
grant_type: string
username: string
account: string
password: string
platform: number
}
/**
......@@ -88,14 +89,15 @@ export interface UserApi {
*/
class UserService {
//#region 测试方法
// 登录
static async login(params: LoginParams): Promise<HttpResponse> {
return Axios('/auth/oauth/token', {
method: 'post',
responseType: 'json',
params: params
})
}
// static async login(params: LoginParams): Promise<HttpResponse> {
// return Axios('/auth/oauth/token', {
// method: 'post',
// responseType: 'json',
// params: params
// })
// }
// 刷新令牌
static async refreshToken(params: RefreshTokenParams): Promise<HttpResponse> {
......@@ -169,6 +171,11 @@ class UserService {
data: params
})
}
//#endregion
static async login(params:any): Promise<HttpResponse> {
console.log(params)
return request("b2b_post_Login",params);
}
}
export default UserService
import { userDictionmary } from '@/config/dictionary'
import { userDictionmary } from '../config/dictionary'
import { boot } from 'quasar/wrappers'
export default boot(({ app }) => {
......
import { registeGlobalComponent } from '@/components'
import { registeGlobalComponent } from '../components'
import { boot } from 'quasar/wrappers'
export default boot(({ app }) => {
......
import { boot } from 'quasar/wrappers'
import { createI18n } from 'vue-i18n'
// import { boot } from 'quasar/wrappers'
// import { createI18n } from 'vue-i18n'
import messages from 'src/i18n'
// import messages from 'src/i18n'
const i18n = createI18n({
locale: 'en-US',
messages
})
// const i18n = createI18n({
// locale: localStorage.getItem("lanuage")??'zh-TW',
// messages
// })
// export default boot(({ app }) => {
// // Set i18n instance on app
// app.use(i18n)
// })
// export { i18n }
import { boot } from 'quasar/wrappers';
import { createI18n } from 'vue-i18n';
import messages from 'src/i18n';
export type MessageLanguages = keyof typeof messages;
// Type-define 'en-US' as the master schema for the resource
export type MessageSchema = typeof messages['en-US'];
// See https://vue-i18n.intlify.dev/guide/advanced/typescript.html#global-resource-schema-type-definition
/* eslint-disable @typescript-eslint/no-empty-interface */
declare module 'vue-i18n' {
// define the locale messages schema
export interface DefineLocaleMessage extends MessageSchema {}
// define the datetime format schema
export interface DefineDateTimeFormat {}
// define the number format schema
export interface DefineNumberFormat {}
}
/* eslint-enable @typescript-eslint/no-empty-interface */
const i18n = createI18n({
locale: localStorage.getItem("lanuage")??'zh-TW',
legacy: false,
globalInjection:true,
messages,
});
export default boot(({ app }) => {
// Set i18n instance on app
app.use(i18n)
})
export { i18n }
// Set i18n instance on app
app.use(i18n);
});
export { i18n }
\ No newline at end of file
import { AuthMenuType } from '@/@types'
import { ResultType } from '@/@types/enumHelper'
import router from '@/router/index'
import { AuthMenuType } from '../@types'
import { ResultType } from '../@types/enumHelper'
import router from '../router/index'
import { getAuth, getUserAllMenu } from '@/utils/auth'
import { getAuth, getUserAllMenu } from '../utils/auth'
import { LoadingBar } from 'quasar'
LoadingBar.setDefaults({
color: 'primary',
......@@ -12,7 +12,7 @@ LoadingBar.setDefaults({
let loadAsyncRouter = false
const whiteList = ['/auth/login','/auth/regist']
const whiteList = ['/auth/login','/auth/regist','/auth/forget']
router.beforeEach((to, from, next) => {
localStorage.setItem('routerBefore', from.path)
......@@ -72,6 +72,7 @@ router.beforeEach((to, from, next) => {
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
console.log(whiteList,to.path)
next()
} else {
next(`/auth/login?redirect=${decodeURIComponent(to.path)}`) // 否则全部重定向到登录页
......
<template>
<span>
<inline-svg :class="[`${color}`]" :src="require(`@/assets/svg/${icon}`)" :width="svgSize" :height="svgSize" :fill="svgColor" :stroke="svgColor" :title="tips" :aria-label="tips"></inline-svg>
<inline-svg :class="[`${color}`]" :src="svgIcon" :width="svgSize" :height="svgSize" :fill="svgColor" :stroke="svgColor" :title="tips" :aria-label="tips"></inline-svg>
</span>
</template>
......@@ -42,7 +42,11 @@ export default {
return tempColor.indexOf('#') != 0 ? `var(--q-${tempColor})` : tempColor
})
return { svgSize, svgColor }
const svgIcon = computed(() => {
return new URL(`../../assets/svg/${props.icon}`,import.meta.url).href
})
return { svgSize, svgColor,svgIcon }
}
}
</script>
......
......@@ -7,9 +7,9 @@ import { kebabCase } from 'lodash'
* @returns {void} void
*/
export function registeGlobalComponent(app: ReturnType<typeof createApp>): void {
const files = require.context('./global', true, /\.(vue|ts)$/)
files.keys().forEach(key => {
const config = files(key)
const files = import.meta.globEager("./global/*.(vue|ts)")//require.context('./global', true, /\.(vue|ts)$/)
Object.keys(files).forEach(key => {
const config = files[key]
const name = kebabCase(key.replace(/^\.\//, '').replace(/\.\w+$/, ''))
app.component(name, config.default || config)
})
......
/** 跟应用全局相关的静态配置放在这里 */
import { Notify } from 'quasar'
import { i18n } from '../boot/i18n'
const AppConfig = {
$message: Notify
}
const StaticConfig = {
MaxPageSize: 1000,
appsuffix: '大水豚渠道管理系统'
appsuffix: i18n.global.t("appsuffix")
}
export { AppConfig, StaticConfig }
// app global css in SCSS form
.q-px-xxl{
padding-left:5rem;
padding-right:5rem;
}
.q-py-xxl{
padding-top:5rem;
padding-bottom:5rem;
}
.q-pa-xxl{
padding:5rem;
}
.q-pl-xxl{
padding-left:5rem;
}
.q-pr-xxl{
padding-right:5rem;
}
.q-pt-xxl{
padding-top:5rem;
}
.q-pb-xxl{
padding-bottom:5rem;
}
#q-app{
height: 100vh;
}
\ No newline at end of file
......@@ -39,7 +39,7 @@
.Poppins
font-family: 'Poppins'
body
font-family: 'Poppins','Helvetica','sans-serif',pf
font-family: 'Roboto', 'Arial','微軟正黑體修正', '微軟正黑體', "Microsoft JhengHei", 'sans-serif','pf'
color: var(--q-dark)
.no-underline
text-decoration: none !important
......
......@@ -25,9 +25,9 @@
// src/css/quasar.variables.scss
$primary: #04c8c8;
$primary: #009ef7;
$secondary: #e4e6ef;
$accent: #009ef7;
$accent: #04c8c8;
$dark: #181c32;
......
import enUS from './en-US'
import zhTW from './zh-TW'
export default {
'en-US': enUS
'en-US': enUS,
'zh-TW': zhTW
}
// This is just an example,
// so you can safely delete all default props below
export default {
failed: '執行失敗',
success: '執行成功',
appsuffix: "JVS同業預定系統",
lanuage:"系統語言",
login:{
notaccess:"沒有同業會員帳戶?",
registlink:"申請註冊",
title:"登入",
subTitle:"歡迎您的到來,開啟急速採購",
account:"帳戶(EMail)",
password:"密碼",
forgot:"忘記密碼?",
signin:"登入",
pageTitle:"登入",
ruleTipsAccount:"請填寫正確的郵箱帳號",
ruleTipsPwd:"請填寫密碼",
formTips:"請填寫登陸信息"
},
forget:{
pageTitle:"忘记密码",
title:"忘記密碼",
subtitle:"輸入您的郵箱,以重置密碼",
notaccess:"已經是會員了?",
registlink:"登錄",
account:"帳戶(EMail)",
submit:"提交"
}
}
......@@ -16,7 +16,7 @@
<link rel="icon" type="image/ico" href="favicon.ico">
</head>
<body>
<!-- DO NOT touch the following DIV -->
<div id="q-app"></div>
<!-- quasar:entry-point -->
</body>
</html>
......@@ -97,13 +97,13 @@ const linksList = [
]
import { defineComponent, ref, reactive, provide, watch } from 'vue'
import Navs from '@/components/layout/navs.vue'
import Navs from '../components/layout/navs.vue'
import { useRouter } from 'vue-router'
import useScrollModule from '@/module/scrollbar/scrollModule'
import { dispatchAction, getStoreGetter } from '@/store/utils'
import { UserGetter } from '@/store/modules/user/getters'
import { UserActionsType } from '@/store/modules/user/actions'
import router from '@/router'
import useScrollModule from '../module/scrollbar/scrollModule'
import { dispatchAction, getStoreGetter } from '../store/utils'
import { UserGetter } from '../store/modules/user/getters'
import { UserActionsType } from '../store/modules/user/actions'
import router from '../router'
export default defineComponent({
name: 'MainLayout',
......
import { StaticConfig } from '@/config/app'
import { StaticConfig } from '../../config/app'
import { useMeta } from 'quasar'
import { ref } from 'vue'
const userMetaTitleModule = () => {
const title = ref(StaticConfig.appsuffix)
useMeta(() => {
......
import { ResultType } from '@/@types/enumHelper'
import router from '@/router'
import { UserActionsType } from '@/store/modules/user/actions'
import { UserGetter } from '@/store/modules/user/getters'
import { dispatchAction, getStoreGetter, setStoreState } from '@/store/utils'
import message from '@/utils/message'
import router from '../../router'
import { UserActionsType } from '../../store/modules/user/actions'
import { dispatchAction, getStoreGetter, setStoreState } from '../../store/utils'
import message from '../../utils/message'
import { reactive, ref } from 'vue'
import { isEmail } from '../../utils/validate'
import { useI18n } from 'vue-i18n'
interface LoginParams {
username: string
password: string
......@@ -17,10 +17,10 @@ const userUserLoginModule = () => {
password: '',
remeber: false
})
const {t} = useI18n()
const userValidateRule = reactive({
usernameRule: [(val: any) => !!val || '请填写账户信息'],
userpasswordRule: [(val: any) => !!val || '请填写密码信息']
usernameRule: [(val:any, rules:any) => rules.email(val) || t("login.ruleTipsAccount")],
userpasswordRule: [(val: any) => !!val || t("login.ruleTipsPwd")]
})
const stateManager = reactive({
subLogin: ref(false)
......@@ -28,56 +28,34 @@ const userUserLoginModule = () => {
const usernameRef = ref(null)
const passwordRef = ref(null)
const loginSubmit = () => {
const loginSubmit = async () => {
if (!stateManager.subLogin) {
stateManager.subLogin = true
const ur = usernameRef as any //断言任意类型
const pr = passwordRef as any
ur.value.validate()
pr.value.validate()
if (ur.value.hasError || pr.value.hasError) {
message.warnMsg('请完善登录信息')
message.warnMsg(t("login.formTips"))
stateManager.subLogin = false
} else {
setTimeout(() => {
//#region 测试使用
const param = {
username: userModel.username,
password: userModel.password
}
dispatchAction<UserActionsType>('user', 'userLogin', param)
const menu:any = {
menuId: 1,
menuName: '首页',
menuUrl: '/index'
}
const menu2:any = {
menuId: 2,
menuName: '首页',
menuUrl: '/'
}
const menus: Array<any> = []
const auths = getStoreGetter<UserGetter>('user', 'getUserAllAuth')
if (auths != ResultType.EmptyArray) {
Object.assign(menus, ...auths)
}
menus.push(menu)
menus.push(menu2)
setStoreState('user', 'menuList', menus)
message.successMsg('登录成功')
stateManager.subLogin = false
router.push({
path: '/index'
})
//#endregion
}, 2000)
const param = {
account: userModel.username,
password: userModel.password,
platform: 0
}
let r = await dispatchAction<UserActionsType>('user', 'userLogin', param) as [boolean,string]
if(!r[0]){
message.errorMsg(r[1])
stateManager.subLogin=false
}
}
}
}
return { userModel, usernameRef, passwordRef, userValidateRule, loginSubmit, stateManager }
}
......
import { AuthMenuType } from '@/@types'
import { IndustryType, ResultType, ScaleType } from '@/@types/enumHelper'
import { userDictionmary } from '@/config/dictionary'
import router from '@/router'
import { UserGetter } from '@/store/modules/user/getters'
import { getStoreGetter, setStoreState } from '@/store/utils'
import message from '@/utils/message'
import { formatEnum } from '@/utils/tools'
import { AuthMenuType } from '../../@types'
import { IndustryType, ResultType, ScaleType } from '../../@types/enumHelper'
import { userDictionmary } from '../../config/dictionary'
import router from '../../router'
import { UserGetter } from '../../store/modules/user/getters'
import { getStoreGetter, setStoreState } from '../../store/utils'
import message from '../../utils/message'
import { formatEnum } from '../../utils/tools'
import { SessionStorage } from 'quasar'
// import message from '@/utils/message'
import { nextTick, reactive, ref } from 'vue'
......
......@@ -5,7 +5,7 @@
</template>
<script lang="ts">
import { Todo, Meta } from '@/components/models'
import { Todo, Meta } from '../components/models'
import ExampleComponent from 'components/CompositionComponent.vue'
import { defineComponent, ref } from 'vue'
......
<template>
<div class="row full-height">
<div class="col q-pa-xl fit row justify-center height">
<div style="width:450px;" class="column justify-between content-between">
<div :class="{'row':$q.platform.is.desktop,'column':$q.platform.is.mobile}">
<div class="col" :class="{'text-center q-mb-md':$q.platform.is.mobile}">
<q-btn round flat>
<svg-icon icon="Navigation/Arrow-left.svg" :size="24"></svg-icon>
</q-btn>
</div>
<div class="text-grey-6 text-weight-bold col" :class="{'text-right':$q.platform.is.desktop,'text-center':$q.platform.is.mobile}">
<span>{{$t("forget.notaccess")}}</span>
<router-link :to="{ path: '/auth/regist' }" class="text-primary pfb text-subtitle">{{$t("forget.registlink")}}</router-link>
</div>
</div>
<div class="q-py-xxl full-width">
<div class="text-h3 text-weight-bold">{{$t("forget.title")}}</div>
<div class="text-grey-6 q-mt-sm q-mb-lg">{{$t("forget.subtitle")}}</div>
<div class="form-box q-my-lg">
<div class="q-mb-lg">
<q-input :label="$t('forget.account')" standout v-model="account" dense ref="accountRef" :rules="[(val:any, rules:any) => rules.email(val) || $t('login.ruleTipsAccount')]" />
</div>
</div>
<div class="row items-center" :class="{'row':$q.platform.is.desktop,'column':$q.platform.is.mobile}">
<div class="col">
<q-btn size="md" class="q-px-lg" color="primary" :loading="loading" @click="sendVerifyCode" unelevated :label="$t('forget.submit')" />
</div>
</div>
</div>
<div>
<q-select
borderless
v-model="currentLang"
:options="langs"
map-options
option-value="langLocale"
option-label="langName"
style="width:180px"
>
<template v-slot:selected>
{{$t("lanuage")}}
<q-chip
dense
square
color="white"
text-color="primary"
class="q-my-none q-ml-xs q-mr-none"
>
{{ currentLang.langName }}
</q-chip>
</template>
</q-select>
</div>
</div>
</div>
<div class="col right-bg full-height mobile-hide"></div>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive } from 'vue'
import useMetaModule from '../../module/meta/metaModule'
import { useI18n } from 'vue-i18n'
import { getLangs } from "../../utils/tools";
import { SitLang } from '@/@types';
import svgIcon from '../../components/global/svg-icon.vue';
export default defineComponent({
components: { svgIcon },
setup() {
let { setTitle } = useMetaModule()
const dtNow = new Date().getFullYear()
const {locale,t } = useI18n();
setTitle(t("login.pageTitle"))
const data=reactive({
currentLang: {} as SitLang,
langs:[] as SitLang[],
account:'',
loading:false
})
data.langs=getLangs()
if(data.langs && data.langs.length>0){
data.currentLang = data.langs.find(x=> x.langLocale==locale.value) ?? {};
}
let methods = {
sendVerifyCode(){
}
}
return { ...data, ...methods }
}
})
</script>
<style>
.login-bg {
background-image: url(../../assets/images/8.png);
background-position-x: center;
background-position-y: bottom;
background-repeat: no-repeat;
background-size: contain;
}
.w-450 {
width: 450px;
box-shadow: 0 0.5rem 1.5rem 0.5rem rgba(0, 0, 0, 0.075) !important;
border-radius: 0.475rem !important;
}
.w-450-only {
width: 450px;
}
.right-bg{
background-image: url(../../assets/images/login-right-bg.png);
background-position-x: left;
background-position-y: center;
background-repeat: no-repeat;
background-size: cover;
}
.top-bg{
background-image: url(https://preview.keenthemes.com/metronic8/demo1/assets/media/auth/bg11.png);
background-position-x: -100px;
background-position-y: -85vh;
background-repeat: no-repeat;
background-size: cover;
}
</style>
<template>
<div class="row">
<div class="col window-height column">
<div class="row full-height">
<!-- <div class="col window-height column">
<div class="col q-pa-lg flex justify-center items-center">
<div class="relative-position">
<img src="../../assets/images/big-logo.png" style="height: 60px" class="q-mb-lg" alt="" />
......@@ -50,23 +50,101 @@
<router-link :to="{ path: '/auth/forget' }" class="f12 no-underline change-a-primary q-mr-md">技术支持</router-link>
</div>
</div>
</div> -->
<div class="col q-pa-xl fit row justify-center height">
<div style="width:450px;" class="column justify-between content-between">
<div :class="{'row':$q.platform.is.desktop,'column':$q.platform.is.mobile}">
<div class="col" :class="{'text-center q-mb-md':$q.platform.is.mobile}">
<img src="../../assets/images/lg-logo.png" style="width:80%;" />
</div>
<div class="text-grey-6 text-weight-bold col" :class="{'text-right':$q.platform.is.desktop,'text-center':$q.platform.is.mobile}">
<span>{{$t("login.notaccess")}}</span>
<router-link :to="{ path: '/auth/regist' }" class="text-primary pfb text-subtitle">{{$t("login.registlink")}}</router-link>
</div>
</div>
<div class="q-py-xxl full-width">
<div class="text-h3 text-weight-bold">{{$t("login.title")}}</div>
<div class="text-grey-6 q-mt-sm q-mb-lg">{{$t("login.subTitle")}}</div>
<div class="form-box q-my-lg">
<div class="q-mb-lg">
<q-input :label="$t('login.account')" standout v-model="userModel.username" dense ref="usernameRef" :rules="userValidateRule.usernameRule" />
</div>
<div>
<q-input :label="$t('login.password')" standout v-model="userModel.password" type="password" dense ref="passwordRef" :rules="userValidateRule.userpasswordRule" />
</div>
<div class="text-right">
<router-link :to="{ path: '/auth/forget' }" class="text-primary pfb text-body no-underline">{{$t("login.forgot")}}</router-link>
</div>
</div>
<div class="row items-center" :class="{'row':$q.platform.is.desktop,'column':$q.platform.is.mobile}">
<div class="col">
<q-btn size="md" class="q-px-lg" color="primary" :loading="stateManager.subLogin" @click="loginSubmit" unelevated :label="$t('login.signin')" />
</div>
<div class="col text-right" :class="{'q-mt-md':$q.platform.is.mobile}">
<span class="text-grey-6 q-mr-xs text-weight-bold">OR</span>
<q-btn size="md" color="primary" flat round icon="iconfont icon-google" />
<q-btn size="md" class="q-mx-xs" color="primary" flat round icon="iconfont icon-facebook" />
<q-btn size="md" color="primary" flat round icon="iconfont icon-apple" />
</div>
</div>
</div>
<div>
<q-select
borderless
v-model="currentLang"
:options="langs"
map-options
option-value="langLocale"
option-label="langName"
style="width:180px"
>
<template v-slot:selected>
{{$t("lanuage")}}
<q-chip
dense
square
color="white"
text-color="primary"
class="q-my-none q-ml-xs q-mr-none"
>
{{ currentLang.langName }}
</q-chip>
</template>
</q-select>
</div>
</div>
</div>
<div class="col right-bg full-height mobile-hide"></div>
</div>
</template>
<script>
import { defineComponent } from 'vue'
import useLgoinModule from '@/module/user/loginModule'
import useMetaModule from '@/module/meta/metaModule'
<script lang="ts">
import { defineComponent, reactive } from 'vue'
import useLgoinModule from '../../module/user/loginModule'
import useMetaModule from '../../module/meta/metaModule'
import { useI18n } from 'vue-i18n'
import { getLangs } from "../../utils/tools";
import { SitLang } from '@/@types';
import {ref } from 'vue';
export default defineComponent({
setup() {
//TODO: 缺陷,验证与提交应该使用Form表单来完成,不应该进行单个验证
let { userModel, usernameRef, passwordRef, userValidateRule, loginSubmit, stateManager } = useLgoinModule()
let { setTitle } = useMetaModule()
setTitle('登录')
const dtNow = new Date().getFullYear()
return { userModel, usernameRef, passwordRef, userValidateRule, loginSubmit, dtNow, stateManager }
const {locale,t } = useI18n();
setTitle(t("login.pageTitle"))
const data=reactive({
currentLang: {} as SitLang,
langs:[] as SitLang[]
})
data.langs=getLangs()
if(data.langs && data.langs.length>0){
data.currentLang = data.langs.find(x=> x.langLocale==locale.value) ?? {};
}
return { userModel, usernameRef, passwordRef, userValidateRule, loginSubmit, dtNow, stateManager,...data}
}
})
</script>
......@@ -87,4 +165,18 @@ export default defineComponent({
.w-450-only {
width: 450px;
}
.right-bg{
background-image: url(../../assets/images/login-right-bg.png);
background-position-x: left;
background-position-y: center;
background-repeat: no-repeat;
background-size: cover;
}
.top-bg{
background-image: url(https://preview.keenthemes.com/metronic8/demo1/assets/media/auth/bg11.png);
background-position-x: -100px;
background-position-y: -85vh;
background-repeat: no-repeat;
background-size: cover;
}
</style>
......@@ -4,7 +4,7 @@
<div class="w-600 col q-py-lg flex items-center">
<div class="form-box-diy col">
<div class="text-center">
<img src="@/assets/images/big-logo.png" height="60" alt="" />
<img src="../../assets/images/big-logo.png" height="60" alt="" />
</div>
<q-card class="mycard q-mt-lg q-pa-xl">
<q-tab-panels v-model="stateManager.currentSetup" animated>
......@@ -85,10 +85,10 @@
<script>
import { defineComponent, onMounted } from 'vue'
import useMetaModule from '@/module/meta/metaModule'
import useUserRegistModule from '@/module/user/registModule'
import verifyCodeBox from '@/components/common/verifyCode.vue'
import useScrollModule from '@/module/scrollbar/scrollModule'
import useMetaModule from '../../module/meta/metaModule'
import useUserRegistModule from '../../module/user/registModule'
import verifyCodeBox from '../../components/common/verifyCode.vue'
import useScrollModule from '../../module/scrollbar/scrollModule'
export default defineComponent({
components: {
verifyCodeBox
......
......@@ -3,22 +3,26 @@ import { RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
{
path: '/index',
component: () => import('@/layouts/MainLayout.vue'),
children: [{ path: '', component: () => import('@/pages/Index.vue') }]
component: () => import('../layouts/MainLayout.vue'),
children: [{ path: '', component: () => import('../pages/index.vue') }]
},
{
path:'/auth/login',
component: () => import('@/pages/auth/login.vue')
component: () => import('../pages/auth/login.vue')
},
{
path:'/auth/regist',
component: () => import('@/pages/auth/regist.vue')
component: () => import('../pages/auth/regist.vue')
},
{
path:'/auth/forget',
component: () => import('../pages/auth/forget.vue')
},
// Always leave this as last one,
// but you can also remove it
{
path: '/:catchAll(.*)*',
component: () => import('pages/Error404.vue')
component: () => import('../pages/Error404.vue')
}
]
......
/* eslint-disable */
/// <reference types="vite/client" />
// Mocks all files ending in `.vue` showing them as plain Vue instances
declare module '*.vue' {
import { ComponentOptions } from 'vue'
const component: ComponentOptions
export default component
}
import type { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}
\ No newline at end of file
......@@ -2,11 +2,11 @@ import { createStore, createLogger, Store } from 'vuex'
import createPersistedState from 'vuex-persistedstate'
import mutations from './mutations'
import modules from './modules'
import { StateType } from '@/@types'
import { StateType } from '../@types'
import { InjectionKey } from 'vue'
export const key: InjectionKey<Store<StateType>> = Symbol()
console.log(modules)
const store: Store<StateType> = createStore({
strict: !!process.env.DEBUGGING,
mutations,
......
import { StateType } from '@/@types'
import { StateType } from '../../../@types'
import { Module } from 'vuex'
const state = {
......
// https://vuex.vuejs.org/en/modules.html
const files = require.context('.', true, /\.ts$/)
const files = import.meta.globEager("./**/*.ts") //require.context('.', true, /\.ts$/)
const modules: any = {}
files.keys().forEach((key: string) => {
console.log(files)
Object.keys(files).forEach((key: string) => {
console.log(key)
if (key === './index.ts') return
const path = key.replace(/(\.\/|\.ts)/g, '')
const [namespace, imported] = path.split('/')
if (!modules[namespace]) {
......@@ -12,7 +14,7 @@ files.keys().forEach((key: string) => {
namespaced: true
}
}
modules[namespace][imported] = files(key).default
modules[namespace][imported] = files[key].default
})
export default modules
import UserService from '@/api/user'
//import { message } from 'ant-design-vue';
import { ApiResult } from './../../../@types/enumHelper';
import UserService from '../../../api/user'
import { setStoreState } from '../../utils'
import Store from '@/store'
import Store from '../../../store'
import message from '../../../utils/message'
//import router from '../../../router';
/**
* @description 所有跟用户相关的内容
* @return status 返回状态 err_code:1,逻辑正确,err_code:0,发生错误。
......@@ -39,37 +43,43 @@ const userActions = {
},
userLogin(params: { username: string; password: string }) {
//TODO: 实现用户登录
// UserService.login(params).then(res=>{
// })
//测试伪造数据
const loginUser = {
email: 'alex9012@vip.qq.com',
type: 0, // 用户账号本身类型 0:主账号,1:子账号
userId: 1,
username: params.username,
description: '',
nickName: '罗超',
phone: '17308037817',
tenantId: 1,
roleId: 1,
userAvatar: 'https://preview.keenthemes.com/metronic8/demo7/assets/media/avatars/150-26.jpg'
}
setStoreState('user', 'userDetail', loginUser)
const token = {
token_type: 'login_auth',
access_token: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
async userLogin({},params: { account: string; password: string, platform:number }):Promise<[boolean, string]> {
let flag:[boolean,string] = [false,'']
try {
let param={
'account':params.account,
'password':params.password,
'platform':params.platform
}
console.log(params)
let loginResult = await UserService.login(param)
if(loginResult.data.resultCode==ApiResult.SUCCESS){
message.successMsg('登录成功')
setStoreState('user', 'loginUserInfo', loginResult.data.data)
setStoreState('user', 'menuList', loginResult.data.data.MenuList)
const token = {
token_type: 'login_auth',
access_token: loginResult.data.data.token
}
const expireTime = 72 * 60 * 60 * 1000 + new Date().getTime()
setStoreState('user', 'token', { ...token, expireTime })
flag[0]=true;
}else {
flag[1]=loginResult.data.message
flag[0] = false
}
} catch (error) {
flag[1]=error as string
flag[0] = false
}
const expireTime = 60 * 72 * 1000 + new Date().getTime()
setStoreState('user', 'token', { ...token, expireTime })
return flag;
}
}
type UserActionsType = typeof userActions
export { UserActionsType }
export { type UserActionsType }
export default userActions
import { ResultType } from '@/@types/enumHelper'
import store from '@/store'
import { ResultType } from '../../../@types/enumHelper'
import store from '../../../store'
const userGetter = {
getUserToken() {
......@@ -18,5 +18,5 @@ const userGetter = {
}
type UserGetter = typeof userGetter
export { UserGetter }
export { type UserGetter }
export default userGetter
import { Module } from 'vuex'
import { StateType } from '@/@types/index'
import { StateType } from '../../../../../@types/index'
const state = {
teamName: '汉'
}
......@@ -10,5 +10,5 @@ const ModuleTeam: Module<TeamStateType, StateType> = {
...state
}
export { TeamStateType, state }
export { type TeamStateType, state }
export default ModuleTeam
import { StateType } from '@/@types'
import { StateType } from '../../../@types'
import { Module } from 'vuex'
import ModuleTeam from './modules/team/state'
interface Token {
......@@ -6,17 +6,18 @@ interface Token {
}
const state = {
token: {} as Token,
secretKey: '',
userDetail: {
email: '',
type: -1, // 用户账号本身类型 0:主账号,1:子账号
userId: -1,
username: '',
description: '',
nickName: '',
phone: '',
tenantId: 0,
roleId: 0,
userAvatar:''
// email: '',
// type: -1, // 用户账号本身类型 0:主账号,1:子账号
// userId: -1,
// username: '',
// description: '',
// nickName: '',
// phone: '',
// tenantId: 0,
// roleId: 0,
// userAvatar:''
},
currentTeamRoleId: 0, // 当前所选择的团队用户所具有的权限
currentProjectRoleId: 0, // 当前所选择的项目用户所具有的权限
......@@ -32,5 +33,5 @@ const user: Module<UserStateType, StateType> = {
}
}
export { UserStateType, state }
export { type UserStateType, state }
export default user
import store from '@/store'
import store from '../store'
// 定义 state 下的 module 值
type ModuleNameType = 'app' | 'console' | 'user'
......@@ -36,7 +36,7 @@ export function setStoreState<T>(module: ModuleNameType, key: keyof T, value: an
* @example 使用方法如下 const result = await dispatchActions<UserActionsType>('console','refreshToken',1)
*/
export function dispatchAction<T>(module: ModuleNameType, key: keyof T, value?: any) {
store.dispatch(`${module}/${key}`, value)
return store.dispatch(`${module}/${String(key)}`, value)
}
/**
......@@ -45,5 +45,5 @@ export function dispatchAction<T>(module: ModuleNameType, key: keyof T, value?:
* @example 使用方法如下 const result = getStoreGetter<ConsoleGetterType>('console','list')
*/
export function getStoreGetter<T>(module: ModuleNameType, key: keyof T) {
return store.getters[`${module}/${key}`]
return store.getters[`${module}/${String(key)}`]
}
import { getStoreGetter } from '@/store/utils'
import { UserGetter } from '@/store/modules/user/getters'
import { ResultType } from '@/@types/enumHelper'
import { getStoreGetter } from '../store/utils'
import { UserGetter } from '../store/modules/user/getters'
import { ResultType } from '../@types/enumHelper'
/**
* @description 获取授权信息
*/
export function getAuth() {
const token = getStoreGetter<UserGetter>('user', 'getUserToken')
const token = getStoreGetter<UserGetter>('user', 'getUserToken') ?? ResultType.Empty
console.log(token)
return token != ResultType.Empty
}
......@@ -15,7 +16,7 @@ export function getAuth() {
* @returns 菜单数组
*/
export function getUserAllMenu() {
const auths = getStoreGetter<UserGetter>('user', 'getUserAllAuth')
const auths = getStoreGetter<UserGetter>('user', 'getUserAllAuth')??[]
console.log(auths)
return auths.length > 0 ? auths : ResultType.EmptyArray
}
import { SitLang } from './../@types/index';
/**
* @description 按照需要写入 必要可以注入全局
*/
......@@ -29,3 +30,13 @@ export function isNaNModified(inputStr: string) {
const numericRepr = parseFloat(inputStr)
return isNaN(numericRepr) || numericRepr.toString().length != inputStr.length
}
export function getLangs() {
const zhTw:SitLang={
langLocale:"zh-TW",
langName:"中文繁体"
}
let langs:SitLang[]=[]
langs.push(zhTw);
return langs
}
......@@ -27,6 +27,13 @@ export function validatAlphabets(str: string) {
return reg.test(str)
}
export function isEmail(account:string):boolean {
let serchfind:boolean;
let regexp = new RegExp('/^(([^<>()\[\]\\.,;:\[email protected]"]+(\.[^<>()\[\]\\.,;:\[email protected]"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/');
serchfind = regexp.test(account);
return serchfind
}
/** 比对数组是否相同 */
export function compareArray(arrA: any[], arrB: any[]) {
let isSame = true
......
<div id="#kt_app_body_content" style="background-color:#D5D9E2; font-family:Arial,Helvetica,sans-serif; line-height: 1.5; min-height: 100%; font-weight: normal; font-size: 15px; color: #2F3044; margin:0; padding:0; width:100%;">
<div style="background-color:#ffffff; padding: 45px 0 34px 0; border-radius: 24px; margin:40px auto; max-width: 600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" height="auto" style="border-collapse:collapse">
<tbody>
<tr>
<td align="center" valign="center" style="text-align:center; padding-bottom: 10px">
<!--begin:Email content-->
<div style="text-align:center; margin:0 60px 34px 60px">
<!--begin:Logo-->
<div style="margin-bottom: 10px">
<a href="http://b2b.jvs.oytour.com" rel="noopener" target="_blank">
<img alt="Logo" src="https://vt-im-bucket.oss-cn-chengdu.aliyuncs.com/mochat/202210261124201.png" style="height: 35px">
</a>
</div>
<!--end:Logo-->
<!--begin:Media-->
<div style="margin-bottom: 15px">
<img alt="Logo" src="https://vt-im-bucket.oss-cn-chengdu.aliyuncs.com/mochat/icon-positive-vote-2.svg">
</div>
<!--end:Media-->
<!--begin:Text-->
<div style="font-size: 14px; font-weight: 500; margin-bottom: 27px; font-family:Arial,Helvetica,sans-serif;">
<p style="margin-bottom:9px; color:#181C32; font-size: 22px; font-weight:700">It’s almost set!</p>
<p style="margin-bottom:2px; color:#7E8299">Please click the "Change Pass" button below</p>
<p style="margin-bottom:2px; color:#7E8299">Please do not send the link to other people</p>
<p style="margin-bottom:2px; color:#7E8299">and do not forward the email to other people</p>
</div>
<!--end:Text-->
<!--begin:Action-->
<a href="http://b2b.jvs.oytour.com/auth/newpassword" target="_blank" style="background-color:#50cd89; border-radius:6px;display:inline-block; padding:11px 19px; color: #FFFFFF; font-size: 14px; font-weight:500; font-family:Arial,Helvetica,sans-serif;">Change Password</a>
<!--end:Action-->
</div>
<!--end:Email content-->
</td>
</tr>
<tr>
<td align="center" valign="center" style="font-size: 13px; text-align:center; padding: 0 10px 10px 10px; font-weight: 500; color: #A1A5B7; font-family:Arial,Helvetica,sans-serif">
<p style="color:#181C32; font-size: 16px; font-weight: 600; margin-bottom:9px">It’s all about customers!</p>
<p style="margin-bottom:2px">Call our customer care number: +31 6 3344 55 56</p>
<p style="margin-bottom:4px">You may reach us at
<a rel="noopener" target="_blank" style="font-weight: 600">b2b.jvs.oytour.com</a>.</p>
<p>We serve Mon-Fri, 9AM-18AM</p>
</td>
</tr>
<tr>
<td align="center" valign="center" style="font-size: 13px; padding:0 15px; text-align:center; font-weight: 500; color: #A1A5B7;font-family:Arial,Helvetica,sans-serif">
<p>© Copyright JVS.
<a href="http://b2b.jvs.oytour.com" rel="noopener" target="_blank" style="font-weight: 600;font-family:Arial,Helvetica,sans-serif">Unsubscribe</a>&nbsp; from newsletter.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
\ No newline at end of file
{
"extends": "@quasar/app/tsconfig-preset",
"extends": "@quasar/app-vite/tsconfig-preset",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"types": [
"node",
"webpack-env" // here
"node"
]
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
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