Browse Source

增加Vuex AbpConfiguration模块;本地化增加后端本地化文档的集成

pull/1/head
cKey 6 years ago
parent
commit
7a28ae4a7e
  1. 13
      aspnet-core/modules/common/LINGYUN.Abp.Sms.Aliyun/LINYUN/Abp/Sms/Aliyun/AliyunSmsSender.cs
  2. 10
      aspnet-core/modules/permissions/LINGYUN.Abp.PermissionManagement.Application/LINGYUN/Abp/PermissionManagement/PermissionAppService.cs
  3. 2
      aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/LINGYUN.Platform.HttpApi.Host.csproj
  4. 3
      aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs
  5. 2
      aspnet-core/services/start-all-service.bat
  6. 99
      vueJs/src/api/abpconfiguration.ts
  7. 65
      vueJs/src/api/users.ts
  8. BIN
      vueJs/src/assets/login-images/login.jpg
  9. 48
      vueJs/src/components/LangSelect/index.vue
  10. 175
      vueJs/src/lang/es.ts
  11. 18
      vueJs/src/lang/index.ts
  12. 175
      vueJs/src/lang/ja.ts
  13. 175
      vueJs/src/lang/ko.ts
  14. 15
      vueJs/src/lang/zh.ts
  15. 3
      vueJs/src/permission.ts
  16. 2
      vueJs/src/store/index.ts
  17. 27
      vueJs/src/store/modules/abp.ts
  18. 83
      vueJs/src/store/modules/user.ts
  19. 12
      vueJs/src/utils/cookies.ts
  20. 26
      vueJs/src/utils/localStorage.ts
  21. 116
      vueJs/src/utils/request.ts
  22. 2
      vueJs/src/views/admin/users/components/UserCreateForm.vue
  23. 2
      vueJs/src/views/admin/users/components/UserEditForm.vue
  24. 8
      vueJs/src/views/dashboard/admin/components/BoxCard.vue
  25. 397
      vueJs/src/views/login/index.vue
  26. 7
      vueJs/src/views/profile/index.vue

13
aspnet-core/modules/common/LINGYUN.Abp.Sms.Aliyun/LINYUN/Abp/Sms/Aliyun/AliyunSmsSender.cs

@ -49,9 +49,7 @@ namespace LINYUN.Abp.Sms.Aliyun
TryAddTemplateCode(request, smsMessage); TryAddTemplateCode(request, smsMessage);
TryAddSignName(request, smsMessage); TryAddSignName(request, smsMessage);
TryAddSendPhone(request, smsMessage); TryAddSendPhone(request, smsMessage);
TryAddTemplateParam(request, smsMessage);
var queryParamJson = JsonSerializer.Serialize(smsMessage.Properties);
request.AddQueryParameters("TemplateParam", queryParamJson);
try try
{ {
@ -125,5 +123,14 @@ namespace LINYUN.Abp.Sms.Aliyun
request.AddQueryParameters("PhoneNumbers", smsMessage.PhoneNumber); request.AddQueryParameters("PhoneNumbers", smsMessage.PhoneNumber);
} }
} }
private void TryAddTemplateParam(CommonRequest request, SmsMessage smsMessage)
{
if (smsMessage.Properties.Count > 0)
{
var queryParamJson = JsonSerializer.Serialize(smsMessage.Properties);
request.AddQueryParameters("TemplateParam", queryParamJson);
}
}
} }
} }

10
aspnet-core/modules/permissions/LINGYUN.Abp.PermissionManagement.Application/LINGYUN/Abp/PermissionManagement/PermissionAppService.cs

@ -9,6 +9,7 @@ using Volo.Abp;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.Authorization.Permissions; using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Caching; using Volo.Abp.Caching;
using Volo.Abp.Clients;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.PermissionManagement; using Volo.Abp.PermissionManagement;
@ -22,10 +23,12 @@ namespace LINGYUN.Abp.PermissionManagement
public class PermissionAppService : ApplicationService, IPermissionAppService public class PermissionAppService : ApplicationService, IPermissionAppService
{ {
protected PermissionManagementOptions Options { get; } protected PermissionManagementOptions Options { get; }
protected ICurrentClient CurrentClient { get; }
protected IDistributedCache<PermissionGrantCacheItem> Cache { get; } protected IDistributedCache<PermissionGrantCacheItem> Cache { get; }
protected IPermissionGrantRepository PermissionGrantRepository { get; } protected IPermissionGrantRepository PermissionGrantRepository { get; }
protected IPermissionDefinitionManager PermissionDefinitionManager { get; } protected IPermissionDefinitionManager PermissionDefinitionManager { get; }
public PermissionAppService( public PermissionAppService(
ICurrentClient currentClient,
IDistributedCache<PermissionGrantCacheItem> cache, IDistributedCache<PermissionGrantCacheItem> cache,
IPermissionGrantRepository permissionGrantRepository, IPermissionGrantRepository permissionGrantRepository,
IPermissionDefinitionManager permissionDefinitionManager, IPermissionDefinitionManager permissionDefinitionManager,
@ -33,6 +36,7 @@ namespace LINGYUN.Abp.PermissionManagement
{ {
Cache = cache; Cache = cache;
Options = options.Value; Options = options.Value;
CurrentClient = currentClient;
PermissionGrantRepository = permissionGrantRepository; PermissionGrantRepository = permissionGrantRepository;
PermissionDefinitionManager = permissionDefinitionManager; PermissionDefinitionManager = permissionDefinitionManager;
} }
@ -62,6 +66,12 @@ namespace LINGYUN.Abp.PermissionManagement
} }
} }
} }
if (!CurrentClient.Id.IsNullOrWhiteSpace())
{
var clientPermissions = await PermissionGrantRepository
.GetListAsync(ClientPermissionValueProvider.ProviderName, CurrentClient.Id);
permissions = permissions.Union(clientPermissions);
}
foreach (var permissionGroup in permissionGroups) foreach (var permissionGroup in permissionGroups)
{ {
var groupDto = new PermissionGroupDto var groupDto = new PermissionGroupDto

2
aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/LINGYUN.Platform.HttpApi.Host.csproj

@ -22,7 +22,7 @@
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" /> <PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" /> <PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.4.1" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="5.4.1" />
<PackageReference Include="Volo.Abp.AspNetCore.MultiTenancy" Version="2.8.0" /> <PackageReference Include="Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy" Version="2.8.0" />
<PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="2.8.0" /> <PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="2.8.0" />
<PackageReference Include="Volo.Abp.Autofac" Version="2.8.0" /> <PackageReference Include="Volo.Abp.Autofac" Version="2.8.0" />
<PackageReference Include="Volo.Abp.EntityFrameworkCore.MySQL" Version="2.8.0" /> <PackageReference Include="Volo.Abp.EntityFrameworkCore.MySQL" Version="2.8.0" />

3
aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs

@ -23,6 +23,7 @@ using Volo.Abp;
using Volo.Abp.Account; using Volo.Abp.Account;
using Volo.Abp.AspNetCore.Authentication.JwtBearer; using Volo.Abp.AspNetCore.Authentication.JwtBearer;
using Volo.Abp.AspNetCore.MultiTenancy; using Volo.Abp.AspNetCore.MultiTenancy;
using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy;
using Volo.Abp.Autofac; using Volo.Abp.Autofac;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.MySQL; using Volo.Abp.EntityFrameworkCore.MySQL;
@ -44,7 +45,7 @@ using AbpPermissionManagementApplicationModule = LINGYUN.Abp.PermissionManagemen
namespace LINGYUN.Platform namespace LINGYUN.Platform
{ {
[DependsOn( [DependsOn(
typeof(AbpAspNetCoreMultiTenancyModule), typeof(AbpAspNetCoreMvcUiMultiTenancyModule),
typeof(AbpPermissionManagementDomainIdentityModule), typeof(AbpPermissionManagementDomainIdentityModule),
typeof(AbpPermissionManagementDomainIdentityServerModule), typeof(AbpPermissionManagementDomainIdentityServerModule),
typeof(ApiGatewayApplicationContractsModule), typeof(ApiGatewayApplicationContractsModule),

2
aspnet-core/services/start-all-service.bat

@ -4,3 +4,5 @@ cls
start .\start-identity-server.bat --run start .\start-identity-server.bat --run
start .\start-apigateway-admin.bat --run start .\start-apigateway-admin.bat --run
start .\start-platform.bat --run start .\start-platform.bat --run
ping -n 10 127.1 >nul
start .\start-apigateway-host.bat --run

99
vueJs/src/api/abpconfiguration.ts

@ -0,0 +1,99 @@
import ApiService from './serviceBase'
const serviceUrl = process.env.VUE_APP_BASE_API
export default class AbpConfigurationService {
public static getAbpConfiguration() {
const _url = '/api/abp/application-configuration'
return ApiService.Get<AbpConfiguration>(_url, serviceUrl)
}
}
export class Auth {
policies?: { [key: string]: boolean}
grantedPolicies?: { [key: string]: boolean}
}
export class CurrentTenant {
id?: string
name?: string
isAvailable!: boolean
}
export class CurrentUser {
id?: string
email?: string
userName?: string
tenantId?: string
isAuthenticated!: boolean
}
export class Feature {
values?: { [key: string]: string}
}
export class DateTimeFormat {
calendarAlgorithmType!: string
dateSeparator!: string
dateTimeFormatLong!: string
fullDateTimePattern!: string
longTimePattern!: string
shortDatePattern!: string
shortTimePattern!: string
}
export class CurrentCulture {
cultureName!: string
displayName!: string
englishName!: string
isRightToLeft!: boolean
name!: string
nativeName!: string
threeLetterIsoLanguageName!: string
twoLetterIsoLanguageName!: string
dateTimeFormat!: DateTimeFormat
}
export class Language {
cultureName!: string
displayName!: string
flagIcon?: string
uiCultureName!: string
}
export class Localization {
currentCulture!: CurrentCulture
defaultResourceName?: string
languages!: Language[]
values!: {[key:string]: {[key:string]: string}}
}
export class MultiTenancy {
isEnabled!: boolean
}
export class Setting {
values?: {[key:string]: string}
}
export interface IAbpConfiguration {
auth: Auth
currentTenant: CurrentTenant
currentUser: CurrentUser
features: Feature
localization: Localization
multiTenancy: MultiTenancy
objectExtensions: any
setting: Setting
}
export class AbpConfiguration implements IAbpConfiguration {
auth!: Auth
currentTenant!: CurrentTenant
currentUser!: CurrentUser
features!: Feature
localization!: Localization
multiTenancy!: MultiTenancy
objectExtensions!: any
setting!: Setting
}

65
vueJs/src/api/users.ts

@ -118,8 +118,53 @@ export default class UserApiService {
}) })
} }
public static refreshToken() { public static sendPhoneVerifyCode(phoneVerify: PhoneVerify) {
const _url = '/api/account/phone/verify'
return ApiService.HttpRequest<any>({
baseURL: IdentityServiceUrl,
url: _url,
method: 'POST',
data: phoneVerify
})
}
public static userLoginWithPhone(loginData: UserLoginPhoneData) {
const _url = '/connect/token'
const login = {
grant_type: 'phone_verify',
phone_number: loginData.phoneNumber,
phone_verify_code: loginData.verifyCode,
client_id: process.env.VUE_APP_CLIENT_ID,
client_secret: process.env.VUE_APP_CLIENT_SECRET
}
return ApiService.HttpRequest<UserLoginResult>({
baseURL: IdentityServerUrl,
url: _url,
method: 'POST',
data: qs.stringify(login),
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
}
public static refreshToken(token: string) {
const _url = '/connect/token'
const refresh = {
grant_type: 'refresh_token',
refresh_token: token,
client_id: process.env.VUE_APP_CLIENT_ID,
client_secret: process.env.VUE_APP_CLIENT_SECRET
}
return ApiService.HttpRequest<UserLoginResult>({
baseURL: IdentityServerUrl,
url: _url,
method: 'POST',
data: qs.stringify(refresh),
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
} }
public static userLogout(token: string | undefined) { public static userLogout(token: string | undefined) {
@ -174,6 +219,24 @@ export class UserLoginData {
password!: string password!: string
} }
export enum VerifyType {
register = 0,
signin = 10
}
export class PhoneVerify {
phoneNumber!: string
verifyType!:VerifyType
}
/** 用户手机登录对象 */
export class UserLoginPhoneData {
/** 手机号码 */
phoneNumber!: string
/** 手机验证码 */
verifyCode!: string
}
/** 用户信息对象 由IdentityServer提供 */ /** 用户信息对象 由IdentityServer提供 */
export class UserInfo { export class UserInfo {
/** 标识 */ /** 标识 */

BIN
vueJs/src/assets/login-images/login.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

48
vueJs/src/components/LangSelect/index.vue

@ -23,30 +23,12 @@
> >
English English
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item
:disabled="language==='es'"
command="es"
>
Español
</el-dropdown-item>
<el-dropdown-item
:disabled="language==='ja'"
command="ja"
>
日本語
</el-dropdown-item>
<el-dropdown-item
:disabled="language==='ko'"
command="ko"
>
한국어
</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-property-decorator' import { Component, Vue, Watch } from 'vue-property-decorator'
import { AppModule } from '@/store/modules/app' import { AppModule } from '@/store/modules/app'
@Component({ @Component({
@ -57,9 +39,35 @@ export default class extends Vue {
return AppModule.language return AppModule.language
} }
/**
* 监听abp配置状态,增强本地化
*/
@Watch('$store.state.abpconfiguration.configuration')
private onAbpConfigurationChanged() {
const abpConfig = this.$store.state.abpconfiguration.configuration
if (abpConfig) {
const { twoLetterIsoLanguageName } = abpConfig.localization.currentCulture
const resources: { [key: string]: any} = {}
Object.keys(abpConfig.localization.values).forEach(key => {
const resource = abpConfig.localization.values[key]
if (typeof resource !== 'object') return
Object.keys(resource).forEach(key2 => {
if (/'{|{/g.test(resource[key2])) {
resource[key2] = resource[key2].replace(/'{|{/g, '{{').replace(/}'|}/g, '}}')
}
})
resources[key] = resource
})
this.$i18n.mergeLocaleMessage(twoLetterIsoLanguageName as string, resources)
console.log(this.$i18n)
}
}
private handleSetLanguage(lang: string) { private handleSetLanguage(lang: string) {
this.$i18n.locale = lang
AppModule.SetLanguage(lang) AppModule.SetLanguage(lang)
this.$i18n.locale = lang
this.$message({ this.$message({
message: 'Switch Language Success', message: 'Switch Language Success',
type: 'success' type: 'success'

175
vueJs/src/lang/es.ts

@ -1,175 +0,0 @@
export default {
route: {
dashboard: 'Panel de control',
documentation: 'Documentación',
guide: 'Guía',
permission: 'Permisos',
rolePermission: 'Permisos de rol',
pagePermission: 'Permisos de la página',
directivePermission: 'Permisos de la directiva',
icons: 'Iconos',
components: 'Componentes',
tinymce: 'Tinymce',
markdown: 'Markdown',
jsonEditor: 'Editor JSON',
splitPane: 'Panel dividido',
avatarUpload: 'Subir avatar',
dropzone: 'Subir ficheros',
sticky: 'Sticky',
countTo: 'Count To',
componentMixin: 'Mixin',
backToTop: 'Ir arriba',
draggableDialog: 'Draggable Dialog',
draggableKanban: 'Draggable Kanban',
draggableList: 'Draggable List',
draggableSelect: 'Draggable Select',
charts: 'Gráficos',
barChart: 'Bar Chart',
lineChart: 'Gráfico de líneas',
mixedChart: 'Mixed Chart',
example: 'Ejemplo',
nested: 'Rutas anidadass',
menu1: 'Menu 1',
'menu1-1': 'Menu 1-1',
'menu1-2': 'Menu 1-2',
'menu1-2-1': 'Menu 1-2-1',
'menu1-2-2': 'Menu 1-2-2',
'menu1-3': 'Menu 1-3',
menu2: 'Menu 2',
table: 'Tabla',
dynamicTable: 'Tabla dinámica',
draggableTable: 'Arrastrar tabla',
inlineEditTable: 'Editor',
complexTable: 'Complex Table',
tab: 'Pestaña',
form: 'Formulario',
createArticle: 'Crear artículo',
editArticle: 'Editar artículo',
articleList: 'Listado de artículos',
errorPages: 'Páginas de error',
page401: '401',
page404: '404',
errorLog: 'Registro de errores',
excel: 'Excel',
exportExcel: 'Exportar a Excel',
selectExcel: 'Export seleccionado',
mergeHeader: 'Merge Header',
uploadExcel: 'Subir Excel',
zip: 'Zip',
pdf: 'PDF',
exportZip: 'Exportar a Zip',
theme: 'Tema',
clipboard: 'Clipboard',
i18n: 'I18n',
externalLink: 'Enlace externo',
profile: 'Profile'
},
navbar: {
logOut: 'Salir',
dashboard: 'Panel de control',
github: 'Github',
theme: 'Tema',
size: 'Tamaño global',
profile: 'Profile'
},
login: {
title: 'Formulario de acceso',
logIn: 'Acceso',
username: 'Usuario',
password: 'Contraseña',
any: 'nada',
thirdparty: 'Conectar con',
thirdpartyTips: 'No se puede simular en local, así que combine su propia simulación de negocios. ! !'
},
documentation: {
documentation: 'Documentación',
github: 'Repositorio Github'
},
permission: {
createRole: 'Nuevo rol',
editPermission: 'Permiso de edición',
roles: 'Tus permisos',
switchRoles: 'Cambiar permisos',
tips: 'In some cases it is not suitable to use v-permission, such as element Tab component or el-table-column and other asynchronous rendering dom cases which can only be achieved by manually setting the v-if.',
delete: 'Borrar',
confirm: 'Confirmar',
cancel: 'Cancelar'
},
guide: {
description: 'The guide page is useful for some people who entered the project for the first time. You can briefly introduce the features of the project. Demo is based on ',
button: 'Ver guía'
},
components: {
documentation: 'Documentación',
tinymceTips: 'Rich text editor is a core part of management system, but at the same time is a place with lots of problems. In the process of selecting rich texts, I also walked a lot of detours. The common rich text editors in the market are basically used, and the finally chose Tinymce. See documentation for more detailed rich text editor comparisons and introductions.',
stickyTips: 'when the page is scrolled to the preset position will be sticky on the top.',
backToTopTips1: 'When the page is scrolled to the specified position, the Back to Top button appears in the lower right corner',
backToTopTips2: 'You can customize the style of the button, show / hide, height of appearance, height of the return. If you need a text prompt, you can use element-ui el-tooltip elements externally',
imageUploadTips: 'Since I was using only the vue@1 version, and it is not compatible with mockjs at the moment, I modified it myself, and if you are going to use it, it is better to use official version.'
},
table: {
dynamicTips1: 'Fixed header, sorted by header order',
dynamicTips2: 'Not fixed header, sorted by click order',
dragTips1: 'Orden por defecto',
dragTips2: 'The after dragging order',
title: 'Título',
importance: 'Importancia',
type: 'Tipo',
remark: 'Remark',
search: 'Buscar',
add: 'Añadir',
export: 'Exportar',
reviewer: 'Reviewer',
id: 'ID',
date: 'Fecha',
author: 'Autor',
readings: 'Lector',
status: 'Estado',
actions: 'Acciones',
edit: 'Editar',
publish: 'Publicar',
draft: 'Draft',
delete: 'Eliminar',
cancel: 'Cancelar',
confirm: 'Confirmar'
},
example: {
warning: 'Creating and editing pages cannot be cached by keep-alive because keep-alive include does not currently support caching based on routes, so it is currently cached based on component name. If you want to achieve a similar caching effect, you can use a browser caching scheme such as localStorage. Or do not use keep-alive include to cache all pages directly. See details'
},
errorLog: {
tips: 'Please click the bug icon in the upper right corner',
description: 'Now the management system are basically the form of the spa, it enhances the user experience, but it also increases the possibility of page problems, a small negligence may lead to the entire page deadlock. Fortunately Vue provides a way to catch handling exceptions, where you can handle errors or report exceptions.',
documentation: 'Documento de introducción'
},
excel: {
export: 'Exportar',
selectedExport: 'Exportar seleccionados',
placeholder: 'Por favor escribe un nombre de fichero'
},
zip: {
export: 'Exportar',
placeholder: 'Por favor escribe un nombre de fichero'
},
pdf: {
tips: 'Here we use window.print() to implement the feature of downloading PDF.'
},
theme: {
change: 'Cambiar tema',
documentation: 'Documentación del tema',
tips: 'Tips: It is different from the theme-pick on the navbar is two different skinning methods, each with different application scenarios. Refer to the documentation for details.'
},
tagsView: {
refresh: 'Actualizar',
close: 'Cerrar',
closeOthers: 'Cerrar otros',
closeAll: 'Cerrar todos'
},
settings: {
title: 'Page style setting',
theme: 'Theme Color',
showTagsView: 'Show Tags-View',
showSidebarLogo: 'Show Sidebar Logo',
fixedHeader: 'Fixed Header',
sidebarTextTheme: 'Sidebar Text Theme'
}
}

18
vueJs/src/lang/index.ts

@ -6,16 +6,10 @@ import { getLanguage } from '@/utils/cookies'
// element-ui built-in lang // element-ui built-in lang
import elementEnLocale from 'element-ui/lib/locale/lang/en' import elementEnLocale from 'element-ui/lib/locale/lang/en'
import elementZhLocale from 'element-ui/lib/locale/lang/zh-CN' import elementZhLocale from 'element-ui/lib/locale/lang/zh-CN'
import elementEsLocale from 'element-ui/lib/locale/lang/es'
import elementJaLocale from 'element-ui/lib/locale/lang/ja'
import elementKoLocale from 'element-ui/lib/locale/lang/ko'
// User defined lang // User defined lang
import enLocale from './en' import enLocale from './en'
import zhLocale from './zh' import zhLocale from './zh'
import esLocale from './es'
import jaLocale from './ja'
import koLocale from './ko'
Vue.use(VueI18n) Vue.use(VueI18n)
@ -27,18 +21,6 @@ const messages = {
zh: { zh: {
...zhLocale, ...zhLocale,
...elementZhLocale ...elementZhLocale
},
es: {
...esLocale,
...elementEsLocale
},
ja: {
...jaLocale,
...elementJaLocale
},
ko: {
...koLocale,
...elementKoLocale
} }
} }

175
vueJs/src/lang/ja.ts

@ -1,175 +0,0 @@
export default {
route: {
dashboard: 'トップ',
documentation: 'ドキュメント',
guide: 'ガイド',
permission: '権限',
rolePermission: '権限ロール',
pagePermission: 'ページ権限',
directivePermission: 'ディレクティブ権限',
icons: 'アイコン',
components: 'コンポーネント',
tinymce: 'TinyMCE',
markdown: 'Markdown',
jsonEditor: 'JSON Editor',
splitPane: 'パネル',
avatarUpload: 'アバターアップロード',
dropzone: 'Dropzone',
sticky: 'Sticky',
countTo: 'Count To',
componentMixin: 'コンポーネントMixin',
backToTop: 'Back To Top',
draggableDialog: 'Draggable Dialog',
draggableKanban: 'Draggable 看板',
draggableList: 'Draggable List',
draggableSelect: 'Draggable Select',
charts: 'チャート',
barChart: 'Barチャート',
lineChart: 'Lineチャート',
mixedChart: 'Mixedチャート',
example: 'Example',
nested: 'Nested Routes',
menu1: 'メニュー1',
'menu1-1': 'メニュー 1-1',
'menu1-2': 'メニュー 1-2',
'menu1-2-1': 'メニュー 1-2-1',
'menu1-2-2': 'メニュー 1-2-2',
'menu1-3': 'メニュー 1-3',
menu2: 'メニュー 2',
table: 'Table',
dynamicTable: '可変 Table',
draggableTable: 'Draggable Table',
inlineEditTable: 'Inline Edit Table',
complexTable: 'Complex Table',
tab: 'Tab',
form: 'フォーム',
createArticle: '投稿作成',
editArticle: '投稿編集',
articleList: '投稿リスト',
errorPages: 'エラーページ',
page401: '401',
page404: '404',
errorLog: 'エラーログ',
excel: 'Excel',
exportExcel: '一括エクスポート',
selectExcel: '複数選択エクスポート',
mergeHeader: 'ヘッダーマージ',
uploadExcel: 'アップロード',
zip: 'Zip',
pdf: 'PDF',
exportZip: 'Export Zip',
theme: 'テーマ変更',
clipboard: 'Clipboard',
i18n: '多言語',
externalLink: '外部リンク',
profile: 'プロフィール'
},
navbar: {
dashboard: 'トップ',
github: 'GitHub',
logOut: 'ログアウト',
profile: 'プロフィール',
theme: 'テーマ変更',
size: '画面サイズ'
},
login: {
title: 'ユーザログイン',
logIn: 'ログイン',
username: 'ユーザ名',
password: 'パスワード',
any: 'any',
thirdparty: '外部IDでログイン',
thirdpartyTips: 'ローカル環境ではログインできません。実装が必要です。'
},
documentation: {
documentation: 'ドキュメント',
github: 'Github Link'
},
permission: {
createRole: 'ロール追加',
editPermission: 'ロール変更',
roles: 'ロール',
switchRoles: 'ロール切替',
tips: 'v-permissionは使えない時があります。例えば: Element-UI の el-tab、 el-table-column 及び他の dom。v-ifを使う必要があります。',
delete: '削除',
confirm: '確認',
cancel: 'キャンセル'
},
guide: {
description: 'ガイドは各機能の説明です。',
button: 'ガイドを見る'
},
components: {
documentation: 'ドキュメント',
tinymceTips: 'tinymceは管理画面に重要な機能ですが、その同時に落とし穴がありあす。tinymceを使う道のりが大変でした。Tinymceを使う時に各自のプロジェクト状況で判断が必要です。ドキュメントはこちら',
stickyTips: 'ページの指定位置へスクロールした場合、表示されます。',
backToTopTips1: 'トップへスクロールが表示されます。',
backToTopTips2: 'ボタンのスタイルはカスタマイズできます。例えば、show/hide、height、position。 またはElementのel-tooltipを使って、ツールチップを実装できます。',
imageUploadTips: 'mockjsは使えないため、カスタマイズしています。公式の最新バージョンを使ってください。'
},
table: {
dynamicTips1: '先頭は固定、最後に追加',
dynamicTips2: '戦後に追加せず、指定列に追加',
dragTips1: 'デフォルト順番',
dragTips2: 'Drag後の順番',
title: 'タイトル',
importance: '重要',
type: 'タイプ',
remark: '評価',
search: '検索',
add: '追加',
export: 'エクスポート',
reviewer: 'レビュアー',
id: '番号',
date: '日時',
author: '作成者',
readings: '閲覧数',
status: 'ステータス',
actions: '操作',
edit: '編集',
publish: '公開',
draft: '下書き',
delete: 'キャンセル',
cancel: 'キャンセル',
confirm: '確認'
},
example: {
warning: '新規作成と編集画面は keep-alive を使えないです。keep-alive の include はrouteのキャッシュは使えないです。そのため、component name を使ってキャッシュさせるようにします。このようなキャッシュ機能を作りたい場合,localStorageを使う手があります。もしくは keep-alive の includeを使って、全ページキャッシュする方法はあります。'
},
errorLog: {
tips: '右上のbugアイコンをクリックしてください。',
description: '管理画面はspaを使う場合が多い、ユーザ体現向上はできますが、想定外エラーが発生する場合があります。Vueはそのエラーハンドリング機能を提供し、エラーレポートができます。',
documentation: 'ドキュメント'
},
excel: {
export: 'エクスポート',
selectedExport: 'エクスポート対象を選択してください。',
placeholder: 'ファイル名を入力してください。'
},
zip: {
export: 'エクスポート',
placeholder: 'ファイル名を入力してください。'
},
pdf: {
tips: 'window.print() を使ってPDFダウンロードしています。'
},
theme: {
change: 'テーマ切替',
documentation: 'ドキュメント',
tips: 'Tips: テーマの切り替え方法はnavbarのtheme-pickと異なります、使い方はドキュメントを確認してください。'
},
tagsView: {
refresh: '更新',
close: '閉じる',
closeOthers: 'その他閉じる',
closeAll: 'すべて閉じる'
},
settings: {
title: 'システムテーマ',
theme: 'テーマ色',
showTagsView: 'Tags-View 開く',
showSidebarLogo: 'Show Sidebar Logo',
fixedHeader: 'Fixed Header',
sidebarTextTheme: 'Sidebar Text Theme'
}
}

175
vueJs/src/lang/ko.ts

@ -1,175 +0,0 @@
export default {
route: {
dashboard: '대시보드',
documentation: '문서',
guide: '가이드',
permission: '권한',
rolePermission: '역할 권한',
pagePermission: '페이지 권한',
directivePermission: '지시 권한',
icons: '아이콘',
components: '구성 요소',
tinymce: 'TinyMCE',
markdown: 'Markdown',
jsonEditor: 'JSON Editor',
splitPane: '패널',
avatarUpload: '아바타업로드',
dropzone: 'Dropzone',
sticky: 'Sticky',
countTo: 'Count To',
componentMixin: '구성 요소 Mixin',
backToTop: 'Back To Top',
draggableDialog: '드래그 상자',
draggableKanban: '드래그 간판',
draggableList: '드래그 리스트',
draggableSelect: '드래그 선택',
charts: '차트',
barChart: '막대그래프',
lineChart: '꺽은선그래프',
mixedChart: '종합차트',
example: '예시',
nested: 'Nested Routes',
menu1: '메뉴1',
'menu1-1': '메뉴 1-1',
'menu1-2': '메뉴 1-2',
'menu1-2-1': '메뉴 1-2-1',
'menu1-2-2': '메뉴 1-2-2',
'menu1-3': '메뉴 1-3',
menu2: '메뉴 2',
table: '표',
dynamicTable: 'Dynamic 표',
draggableTable: 'Draggable 표',
inlineEditTable: 'Inline Edit 표',
complexTable: 'Complex 표',
tab: 'Tab',
form: '형태',
createArticle: '게시물 작성',
editArticle: '게시물 편집',
articleList: '게시물 리스트',
errorPages: '에러 페이지',
page401: '401',
page404: '404',
errorLog: '에러 로그',
excel: '엑셀',
exportExcel: '엑셀 내보내기',
selectExcel: '엑셀 선택',
mergeHeader: '헤더 병합',
uploadExcel: '엑셀 올리기',
zip: 'Zip',
pdf: 'PDF',
exportZip: 'Export Zip',
theme: '테마',
clipboard: 'Clipboard',
i18n: '언어',
externalLink: '외부 링크',
profile: '프로필'
},
navbar: {
dashboard: '대시보드',
github: '깃허브',
logOut: '로그아웃',
profile: '프로필',
theme: '테마',
size: '크기'
},
login: {
title: '타이틀',
logIn: '로그인',
username: '이름',
password: '비밀번호',
any: 'any',
thirdparty: '외부 ID로 로그인',
thirdpartyTips: '로컬 환경에서 로그인 할 수 없습니다.'
},
documentation: {
documentation: '문서',
github: '깃허브 링크'
},
permission: {
createRole: '역할 추가',
editPermission: '권한 수정',
roles: '역할',
switchRoles: '역할 바꾸기',
tips: 'v-permission 사용할 때가 있습니다. 예 : Element-UI의 el-tab, el-table-column 및 다른 dom. v-if를 사용해야합니다.',
delete: '삭제',
confirm: '확인',
cancel: '취소'
},
guide: {
description: '각 기능에 대한 설명입니다.',
button: '버튼'
},
components: {
documentation: '문서',
tinymceTips: '서식있는 텍스트는 관리 백엔드의 핵심 기능이지만 동시에 문제가 많은 곳입니다. 리치 텍스트를 선택하는 과정에서도 많은 우회가 필요했습니다. 시중에 나와있는 일반적인 서식있는 텍스트는 기본적으로 사용되었으며 마침내 Tinymce를 선택했습니다. 보다 자세한 리치 텍스트 비교 및 소개를 참조하십시오.',
stickyTips: '페이지의 지정된 위치에 스크롤하면 나타납니다.',
backToTopTips1: '페이지가 지정된 위치로 스크롤되면 맨 아래 오른쪽 모서리에 맨 위로 이동 단추가 나타납니다.',
backToTopTips2: '버튼 스타일, 표시 / 숨기기, 모양 높이, 반환 높이를 사용자 정의 할 수 있습니다. 텍스트 프롬프트가 필요한 경우 element-ui el-tooltip 요소를 외부에서 사용할 수 있습니다',
imageUploadTips: 'vue @ 1 버전 만 사용하고 있었고 현재 mockjs와 호환되지 않기 때문에 직접 수정했으며 사용하려는 경우 공식 버전을 사용하는 것이 좋습니다.'
},
table: {
dynamicTips1: '정렬된 헤더',
dynamicTips2: '정렬되지 않은 헤더',
dragTips1: '기본 순서',
dragTips2: '드래그한 순서',
title: '타이틀',
importance: '중요성',
type: 'Type',
remark: 'Remark',
search: '검색',
add: '추가',
export: '내보내기',
reviewer: 'Reviewer',
id: 'ID',
date: '날짜',
author: '글쓴이',
readings: 'Readings',
status: '상태',
actions: 'Actions',
edit: '수정',
publish: '게시',
draft: 'Draft',
delete: '삭제',
cancel: '취소',
confirm: '확인'
},
example: {
warning: 'keep-alive의 \'include \'는 (는) 현재 경로 기반 캐싱을 지원하지 않으므로 구성 요소 이름을 기반으로 캐시되기 때문에 keep-alive로 페이지를 만들고 편집 할 수 없습니다. 비슷한 캐싱 효과를 얻으려면 localStorage와 같은 브라우저 캐싱 구성표를 사용할 수 있습니다. 또는 keep-alive \' include \'를 사용하여 모든 페이지를 직접 캐시하지 마십시오. 자세히보다'
},
errorLog: {
tips: '오른쪽 상단에있는 버그 아이콘을 클릭하십시오',
description: '이제 관리 시스템은 기본적으로 스파의 형태이며 사용자 경험을 향상 시키지만 페이지 문제의 가능성을 증가시킵니다. 작은 태만으로 인해 전체 페이지 교착 상태가 발생할 수 있습니다. 다행히 Vue는 오류를 처리하거나 예외를보고 할 수있는 예외 처리를 포착하는 방법을 제공합니다.',
documentation: '문서 소개'
},
excel: {
export: '내보내기',
selectedExport: '선택 항목 내보내기',
placeholder: '파일 이름을 입력하세요.(기본 엑셀 목록)'
},
zip: {
export: '내보내기',
placeholder: '파일 이름을 입력하세요.(기본 파일)'
},
pdf: {
tips: '여기서는 window.print ()를 사용하여 PDF 다운로드 기능을 구현합니다.'
},
theme: {
change: '테마 바꾸기',
documentation: '테마 문서',
tips: 'Tips: 탐색 모음의 테마 선택과는 다른 응용 프로그램 시나리오가있는 두 가지 다른 스키닝 방법입니다. 자세한 내용은 설명서를 참조하십시오.'
},
tagsView: {
refresh: '새로고침',
close: '닫기',
closeOthers: '기타 닫기',
closeAll: '모두 닫기'
},
settings: {
title: '페이지 스타일 설정',
theme: '테마 색상',
showTagsView: 'Tags-View 열기',
showSidebarLogo: '사이드 메뉴 로고',
fixedHeader: '헤더 고정',
sidebarTextTheme: '사이드 메뉴 글꼴 테마'
}
}

15
vueJs/src/lang/zh.ts

@ -90,12 +90,22 @@ export default {
login: { login: {
title: '系统登录', title: '系统登录',
logIn: '登录', logIn: '登录',
userLogin: '用户密码登录',
phoneLogin: '手机免密登录',
tenantName: '租户', tenantName: '租户',
username: '账号', username: '账号',
password: '密码', password: '密码',
phoneNumber: '手机号码',
phoneVerifyCode: '手机验证码',
sendVerifyCode: '发送验证码',
afterSendVerifyCode: ' s后重新发送',
any: '随便填', any: '随便填',
thirdparty: '第三方登录', thirdparty: '第三方登录',
thirdpartyTips: '本地不能模拟,请结合自己业务进行模拟!!!' thirdpartyTips: '本地不能模拟,请结合自己业务进行模拟!!!',
tokenExprition: '身份令牌已过期,请重新登录!',
confirmLogout: '确认登出',
relogin: '重新登录',
cancel: '取消'
}, },
documentation: { documentation: {
documentation: '文档', documentation: '文档',
@ -586,6 +596,7 @@ export default {
cancel: '取 消', cancel: '取 消',
confirm: '确 定', confirm: '确 定',
correctEmailAddress: '正确的邮件地址', correctEmailAddress: '正确的邮件地址',
correctPhoneNumber: '正确的手机号码' correctPhoneNumber: '正确的手机号码',
operatingFast: '您的操作过快,请稍后再试!'
} }
} }

3
vueJs/src/permission.ts

@ -24,7 +24,6 @@ const getPageTitle = (key: string) => {
router.beforeEach(async(to: Route, _: Route, next: any) => { router.beforeEach(async(to: Route, _: Route, next: any) => {
// Start progress bar // Start progress bar
NProgress.start() NProgress.start()
// Determine whether the user has logged in // Determine whether the user has logged in
if (UserModule.token) { if (UserModule.token) {
if (to.path === '/login') { if (to.path === '/login') {
@ -35,9 +34,7 @@ router.beforeEach(async(to: Route, _: Route, next: any) => {
// Check whether the user has obtained his permission roles // Check whether the user has obtained his permission roles
if (PermissionModule.authorizedPermissions.length === 0) { if (PermissionModule.authorizedPermissions.length === 0) {
try { try {
// Note: roles must be a object array! such as: ['admin'] or ['developer', 'editor']
const { sub } = await UserModule.GetUserInfo() const { sub } = await UserModule.GetUserInfo()
// const roles = UserModule.roles
// Generate accessible routes map based on role // Generate accessible routes map based on role
await PermissionModule.GenerateRoutes(sub) await PermissionModule.GenerateRoutes(sub)
// Dynamically add accessible routes // Dynamically add accessible routes

2
vueJs/src/store/index.ts

@ -7,10 +7,12 @@ import { IErrorLogState } from './modules/error-log'
import { IPermissionState } from './modules/permission' import { IPermissionState } from './modules/permission'
import { ISettingsState } from './modules/settings' import { ISettingsState } from './modules/settings'
import { IRoleState } from './modules/role' import { IRoleState } from './modules/role'
import { IAbpConfigurationState } from './modules/abp'
Vue.use(Vuex) Vue.use(Vuex)
export interface IRootState { export interface IRootState {
abp: IAbpConfigurationState
app: IAppState app: IAppState
user: IUserState user: IUserState
tagsView: ITagsViewState tagsView: ITagsViewState

27
vueJs/src/store/modules/abp.ts

@ -0,0 +1,27 @@
import store from '@/store'
import { getAbpConfig, setAbpConfig } from '@/utils/localStorage'
import AbpConfigurationService, { IAbpConfiguration } from '@/api/abpconfiguration'
import { VuexModule, Module, Mutation, Action, getModule } from 'vuex-module-decorators'
export interface IAbpConfigurationState {
configuration: IAbpConfiguration
}
@Module({ dynamic: true, store, name: 'abpconfiguration' })
class AbpConfiguration extends VuexModule implements IAbpConfigurationState {
configuration = getAbpConfig()
@Mutation
private SET_ABPCONFIGURATION(configuration: IAbpConfiguration) {
this.configuration = configuration
setAbpConfig(configuration)
}
@Action({ rawError: true })
public async GetAbpConfiguration() {
const config = await AbpConfigurationService.getAbpConfiguration()
this.SET_ABPCONFIGURATION(config)
}
}
export const AbpConfigurationModule = getModule(AbpConfiguration)

83
vueJs/src/store/modules/user.ts

@ -1,18 +1,18 @@
import { VuexModule, Module, Action, Mutation, getModule } from 'vuex-module-decorators' import { VuexModule, Module, Action, Mutation, getModule } from 'vuex-module-decorators'
import UserApiService, { UserLoginData } from '@/api/users' import UserApiService, { UserLoginData, UserLoginPhoneData } from '@/api/users'
import TenantService from '@/api/tenant' import TenantService from '@/api/tenant'
import { getToken, setToken, removeToken, getRefreshToken, setRefreshToken } from '@/utils/cookies' import { getToken, setToken, removeToken, getRefreshToken, setRefreshToken } from '@/utils/localStorage'
import { resetRouter } from '@/router' import { resetRouter } from '@/router'
import { TagsViewModule } from './tags-view' import { TagsViewModule } from './tags-view'
import { removeTenant, setTenant } from '@/utils/sessions' import { removeTenant, setTenant } from '@/utils/sessions'
import { PermissionModule } from '@/store/modules/permission' import { PermissionModule } from '@/store/modules/permission'
import { AbpConfigurationModule } from '@/store/modules/abp'
import store from '@/store' import store from '@/store'
export interface IUserState { export interface IUserState {
token: string token: string
id: string
name: string name: string
avatar: string
introduction: string
roles: string[] roles: string[]
email: string email: string
} }
@ -20,26 +20,25 @@ export interface IUserState {
@Module({ dynamic: true, store, name: 'user' }) @Module({ dynamic: true, store, name: 'user' })
class User extends VuexModule implements IUserState { class User extends VuexModule implements IUserState {
public token = getToken() || '' public token = getToken() || ''
public refreshToken = getRefreshToken() || '' public id = ''
public name = '' public name = ''
public avatar = ''
public introduction = ''
public roles: string[] = [] public roles: string[] = []
public email = '' public email = ''
@Mutation @Mutation
private SET_TOKEN(token: string) { private SET_TOKEN(token: string) {
this.token = token this.token = token
setToken(token)
} }
@Mutation @Mutation
private SET_NAME(name: string) { private SET_ID(id: string) {
this.name = name this.id = id
} }
@Mutation @Mutation
private SET_REFRESH_TOKEN(token: string) { private SET_NAME(name: string) {
this.refreshToken = token this.name = name
} }
@Mutation @Mutation
@ -54,23 +53,32 @@ class User extends VuexModule implements IUserState {
@Action({ rawError: true }) @Action({ rawError: true })
public async Login(userInfo: { tenantName: string | undefined, username: string, password: string}) { public async Login(userInfo: { tenantName: string | undefined, username: string, password: string}) {
if (userInfo.tenantName) {
await this.PreLogin(userInfo.tenantName)
}
const userLoginData = new UserLoginData() const userLoginData = new UserLoginData()
userLoginData.userName = userInfo.username userLoginData.userName = userInfo.username
userLoginData.password = userInfo.password userLoginData.password = userInfo.password
if (userInfo.tenantName) { const loginResult = await UserApiService.userLogin(userLoginData)
const tenantResult = await TenantService.getTenantByName(userInfo.tenantName) const token = loginResult.token_type + ' ' + loginResult.access_token
if (tenantResult.success) { this.SET_TOKEN(token)
setTenant(tenantResult.tenantId) setRefreshToken(loginResult.refresh_token)
} else { await this.PostLogin()
throw new Error('给定的租户不可用: ' + userInfo.tenantName)
} }
@Action({ rawError: true })
public async PhoneLogin(userInfo: { tenantName: string | undefined, phoneNumber: string, verifyCode: string}) {
if (userInfo.tenantName) {
await this.PreLogin(userInfo.tenantName)
} }
const loginResult = await UserApiService.userLogin(userLoginData) const userLoginData = new UserLoginPhoneData()
userLoginData.phoneNumber = userInfo.phoneNumber
userLoginData.verifyCode = userInfo.verifyCode
const loginResult = await UserApiService.userLoginWithPhone(userLoginData)
const token = loginResult.token_type + ' ' + loginResult.access_token const token = loginResult.token_type + ' ' + loginResult.access_token
setToken(token)
this.SET_TOKEN(token) this.SET_TOKEN(token)
setRefreshToken(loginResult.refresh_token) setRefreshToken(loginResult.refresh_token)
this.SET_REFRESH_TOKEN(loginResult.refresh_token) await this.PostLogin()
} }
@Action @Action
@ -87,6 +95,7 @@ class User extends VuexModule implements IUserState {
throw Error('GetUserInfo: token is undefined!') throw Error('GetUserInfo: token is undefined!')
} }
const userInfo = await UserApiService.getUserInfo() const userInfo = await UserApiService.getUserInfo()
this.SET_ID(userInfo.sub)
this.SET_NAME(userInfo.name) this.SET_NAME(userInfo.name)
this.SET_EMAIL(userInfo.email) this.SET_EMAIL(userInfo.email)
return userInfo return userInfo
@ -97,7 +106,12 @@ class User extends VuexModule implements IUserState {
if (this.token === '') { if (this.token === '') {
throw Error('LogOut: token is undefined!') throw Error('LogOut: token is undefined!')
} }
await UserApiService.userLogout(getRefreshToken()) const token = getRefreshToken()
if (token) {
await UserApiService.userLogout(token)
}
this.SET_TOKEN('')
this.SET_ROLES([])
removeToken() removeToken()
removeTenant() removeTenant()
resetRouter() resetRouter()
@ -105,13 +119,36 @@ class User extends VuexModule implements IUserState {
TagsViewModule.delAllViews() TagsViewModule.delAllViews()
PermissionModule.ResetPermissions() PermissionModule.ResetPermissions()
PermissionModule.ResetRoutes() PermissionModule.ResetRoutes()
this.SET_TOKEN('')
this.SET_ROLES([])
} }
@Action @Action
public RefreshSession() { public RefreshSession() {
return new Promise((resolve, reject) => {
const token = getToken()
if (token) {
UserApiService.refreshToken(token).then(result => {
const token = result.token_type + ' ' + result.access_token
setRefreshToken(result.refresh_token)
this.SET_TOKEN(token)
return resolve(result)
}).catch(error => {
return reject(error)
})
} else {
return resolve('')
}
})
}
@Action
private async PreLogin(tenantName: string) {
const tenantResult = await TenantService.getTenantByName(tenantName)
setTenant(tenantResult.tenantId)
}
@Action
private async PostLogin() {
await AbpConfigurationModule.GetAbpConfiguration()
} }
} }

12
vueJs/src/utils/cookies.ts

@ -12,15 +12,3 @@ export const setLanguage = (language: string) => Cookies.set(languageKey, langua
const sizeKey = 'size' const sizeKey = 'size'
export const getSize = () => Cookies.get(sizeKey) export const getSize = () => Cookies.get(sizeKey)
export const setSize = (size: string) => Cookies.set(sizeKey, size) export const setSize = (size: string) => Cookies.set(sizeKey, size)
// User
const tokenKey = 'vue_typescript_admin_access_token'
const refreshTokenKey = 'vue_typescript_admin_refresh_token'
export const getToken = () => Cookies.get(tokenKey)
export const setToken = (token: string) => Cookies.set(tokenKey, token)
export const getRefreshToken = () => Cookies.get(refreshTokenKey)
export const setRefreshToken = (token: string) => Cookies.set(refreshTokenKey, token)
export const removeToken = () => {
Cookies.remove(tokenKey)
return Cookies.remove(refreshTokenKey)
}

26
vueJs/src/utils/localStorage.ts

@ -0,0 +1,26 @@
import { AbpConfiguration } from '@/api/abpconfiguration'
const abpConfigKey = 'vue_admin_abp_configuration'
export const getAbpConfig = () => {
const abpConfigItem = localStorage.getItem(abpConfigKey)
if (abpConfigItem) {
return JSON.parse(abpConfigItem) as AbpConfiguration
}
return new AbpConfiguration()
}
export const setAbpConfig = (abpConfig: AbpConfiguration) => {
const abpConfigItem = JSON.stringify(abpConfig)
localStorage.setItem(abpConfigKey, abpConfigItem)
}
export const removeAbpConfig = () => localStorage.removeItem(abpConfigKey)
// User
const tokenKey = 'vue_typescript_admin_token'
const refreshTokenKey = 'vue_typescript_admin_refresh_token'
export const getToken = () => localStorage.getItem(tokenKey)
export const setToken = (token: string) => localStorage.setItem(tokenKey, token)
export const getRefreshToken = () => localStorage.getItem(refreshTokenKey)
export const setRefreshToken = (token: string) => localStorage.setItem(refreshTokenKey, token)
export const removeToken = () => {
localStorage.removeItem(tokenKey)
localStorage.removeItem(refreshTokenKey)
}

116
vueJs/src/utils/request.ts

@ -1,8 +1,10 @@
import axios from 'axios' import axios from 'axios'
import { MessageBox, Message } from 'element-ui' import i18n from 'vue-i18n'
import { MessageBox, Notification } from 'element-ui'
import { UserModule } from '@/store/modules/user' import { UserModule } from '@/store/modules/user'
import { getTenant } from '@/utils/sessions' import { getTenant } from '@/utils/sessions'
import { getLanguage } from '@/utils/cookies' import { getLanguage } from '@/utils/cookies'
import { getToken, getRefreshToken } from '@/utils/localStorage'
const service = axios.create({ const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
@ -13,17 +15,24 @@ const service = axios.create({
// Request interceptors // Request interceptors
service.interceptors.request.use( service.interceptors.request.use(
(config) => { (config) => {
if (config.url === '/connect/token') {
return config
}
const token = getToken()
// Add X-Access-Token header to every request, you can add other custom headers here // Add X-Access-Token header to every request, you can add other custom headers here
if (UserModule.token) { if (token) {
config.headers.Authorization = UserModule.token config.headers.Authorization = token
} }
const tenantId = getTenant() const tenantId = getTenant()
if (tenantId) { if (tenantId) {
config.headers.__tenant = tenantId config.headers.__tenant = tenantId
} }
// abp官方类库用的 zh-Hans 的简体中文包 这里直接粗暴一点 // abp官方类库用的 zh-Hans 的简体中文包 这里直接粗暴一点
if (getLanguage()?.indexOf('zh') !== -1) { const language = getLanguage()
if (language?.indexOf('zh') !== -1) {
config.headers['Accept-Language'] = 'zh-Hans' config.headers['Accept-Language'] = 'zh-Hans'
} else {
config.headers['Accept-Language'] = language
} }
return config return config
}, },
@ -32,53 +41,98 @@ service.interceptors.request.use(
} }
) )
function l(name: string) {
return i18n.prototype.t(name).toString()
}
function showError(error: any, status: number) {
let message = ''
let title = ''
if (typeof error === 'string') {
message = error
} else if (error.error.details) {
message = error.error.details
title = error.error.message
} else if (error.error.message) {
message = error.error.message
} else {
switch (status) {
case 400:
title = error.error
message = error.error_description
break
case 401:
title = l('AbpAccount.DefaultErrorMessage401')
message = l('AbpAccount:DefaultErrorMessage401Detail')
break
case 403:
title = l('AbpAccount:DefaultErrorMessage403')
message = l('AbpAccount.DefaultErrorMessage403Detail')
break
case 404:
title = l('AbpAccount.DefaultErrorMessage404')
message = l('AbpAccount.DefaultErrorMessage404Detail')
break
case 429:
message = l('global.operatingFast')
break
case 500:
title = l('AbpAccount.500Message')
message = l('AbpAccount.InternalServerErrorMessage')
break
default:
break
}
}
Notification({
title: title,
message: message,
type: 'error',
duration: 5 * 1000
})
}
// Response interceptors // Response interceptors
service.interceptors.response.use( service.interceptors.response.use(
(response) => { (response) => {
return response return response
}, },
(error) => { (error) => {
console.log(error.response) showError(error.response.data, error.response.status)
if (error.response.status === 401) { if (error.response.status === 401) {
const token = getRefreshToken()
if (token) {
UserModule.RefreshSession().then(() => {
return service.request(error.config)
}).catch(() => {
MessageBox.confirm( MessageBox.confirm(
'身份令牌已过期,请重新登录!', l('login.tokenExprition'),
'确定登出', l('login.confirmLogout'),
{ {
confirmButtonText: '重新登录', confirmButtonText: l('login.relogin'),
cancelButtonText: '取消', cancelButtonText: l('global.cancel'),
type: 'error' type: 'error'
}).then(() => { }).then(() => {
UserModule.ResetToken() UserModule.ResetToken()
location.reload() // To prevent bugs from vue-router location.reload() // To prevent bugs from vue-router
return Promise.reject(error) return Promise.reject(error)
}) })
}
if (error.response.status === 429) {
Message({
message: '您的操作过快,请稍后再试!',
type: 'warning',
duration: 5 * 1000
}) })
} else {
MessageBox.confirm(
l('login.tokenExprition'),
l('login.confirmLogout'),
{
confirmButtonText: l('login.relogin'),
cancelButtonText: l('global.cancel'),
type: 'error'
}).then(() => {
UserModule.ResetToken()
location.reload() // To prevent bugs from vue-router
return Promise.reject(error) return Promise.reject(error)
}
if (error.response.status === 400 && error.response.data.error_description) {
Message({
message: error.response.data.error_description,
type: 'error',
duration: 5 * 1000
}) })
return Promise.reject(error)
} }
let message = error.message
if (error.response.headers._abperrorformat) {
message = error.response.data.error.message + error.response.data.error.details
} }
Message({
message: message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error) return Promise.reject(error)
} }
) )

2
vueJs/src/views/admin/users/components/UserCreateForm.vue

@ -166,9 +166,9 @@ export default class extends Vue {
if (valid) { if (valid) {
UserApiService.createUser(this.createUser).then(user => { UserApiService.createUser(this.createUser).then(user => {
this.$message.success(this.l('users.createUserSuccess', { name: user.name })) this.$message.success(this.l('users.createUserSuccess', { name: user.name }))
this.$emit('onUserProfileChanged', user.id)
this.resetForm() this.resetForm()
this.onCancel() this.onCancel()
this.$emit('onUserProfileChanged', user.id)
}) })
} }
}) })

2
vueJs/src/views/admin/users/components/UserEditForm.vue

@ -290,8 +290,8 @@ export default class extends Vue {
await PermissionService.setPermissionsByKey('U', this.userProfile.id, setUserPermissions) await PermissionService.setPermissionsByKey('U', this.userProfile.id, setUserPermissions)
} }
frmEditUser.resetFields() frmEditUser.resetFields()
this.$emit('onUserProfileChanged', this.userProfile.id)
this.onCancel() this.onCancel()
this.$emit('onUserProfileChanged', this.userProfile.id)
} else { } else {
return false return false
} }

8
vueJs/src/views/dashboard/admin/components/BoxCard.vue

@ -10,10 +10,6 @@
<img src="https://wpimg.wallstcn.com/e7d23d71-cf19-4b90-a1cc-f56af8c0903d.png"> <img src="https://wpimg.wallstcn.com/e7d23d71-cf19-4b90-a1cc-f56af8c0903d.png">
</div> </div>
<div style="position:relative;"> <div style="position:relative;">
<pan-thumb
:image="avatar"
class="panThumb"
/>
<mallki <mallki
class="mallki-text" class="mallki-text"
text="vue-typescript-admin" text="vue-typescript-admin"
@ -62,10 +58,6 @@ export default class extends Vue {
return UserModule.name return UserModule.name
} }
get avatar() {
return UserModule.avatar
}
get roles() { get roles() {
return UserModule.roles return UserModule.roles
} }

397
vueJs/src/views/login/index.vue

@ -1,12 +1,12 @@
<template> <template>
<div class="login-container"> <div class="login-container">
<el-form <el-form
ref="loginForm" ref="formLogin"
:model="loginForm" :model="loginForm"
:rules="loginRules" :rules="loginFormRules"
class="login-form"
autocomplete="on"
label-position="left" label-position="left"
label-width="0px"
class="demo-ruleForm login-page"
> >
<div class="title-container"> <div class="title-container">
<h3 class="title"> <h3 class="title">
@ -14,59 +14,37 @@
</h3> </h3>
<lang-select class="set-language" /> <lang-select class="set-language" />
</div> </div>
<el-tabs
<el-form-item prop="tenantName"> stretch
<span class="svg-container"> @tab-click="handleLoginTabChanged"
<svg-icon name="tree" /> >
</span> <el-tab-pane :label="$t('login.userLogin')">
<el-input <div v-if="loginType === 'password'">
ref="tenantName" <el-form-item
v-model="loginForm.tenantName" prop="username"
:placeholder="$t('login.tenantName')" >
name="tenantName"
type="text"
tabindex="1"
autocomplete="on"
/>
</el-form-item>
<el-form-item prop="username">
<span class="svg-container">
<svg-icon name="user" />
</span>
<el-input <el-input
ref="username"
v-model="loginForm.username" v-model="loginForm.username"
:placeholder="$t('login.username')" prefix-icon="el-icon-user"
name="username"
type="text" type="text"
auto-complete="off"
tabindex="1" tabindex="1"
autocomplete="on" :placeholder="$t('global.pleaseInputBy', {key: $t('login.username')})"
/> />
</el-form-item> </el-form-item>
<el-form-item
<el-tooltip prop="password"
v-model="capsTooltip"
content="Caps lock is On"
placement="right"
manual
> >
<el-form-item prop="password">
<span class="svg-container">
<svg-icon name="password" />
</span>
<el-input <el-input
:key="passwordType" :key="passwordType"
ref="password" ref="password"
v-model="loginForm.password" v-model="loginForm.password"
prefix-icon="el-icon-lock"
:type="passwordType" :type="passwordType"
:placeholder="$t('login.password')" :placeholder="$t('global.pleaseInputBy', {key: $t('login.password')})"
name="password" name="password"
tabindex="2" tabindex="2"
autocomplete="on" @keyup.enter.native="handleUserLogin"
@keyup.native="checkCapslock"
@blur="capsTooltip = false"
@keyup.enter.native="handleLogin"
/> />
<span <span
class="show-pwd" class="show-pwd"
@ -75,82 +53,134 @@
<svg-icon :name="passwordType === 'password' ? 'eye-off' : 'eye-on'" /> <svg-icon :name="passwordType === 'password' ? 'eye-off' : 'eye-on'" />
</span> </span>
</el-form-item> </el-form-item>
</el-tooltip> </div>
</el-tab-pane>
<el-tab-pane :label="$t('login.phoneLogin')">
<div v-if="loginType === 'phone'">
<el-form-item
prop="phoneNumber"
>
<el-input
ref="loginItemPhone"
v-model="loginForm.phoneNumber"
prefix-icon="el-icon-mobile-phone"
type="text"
maxlength="11"
auto-complete="off"
:placeholder="$t('global.pleaseInputBy', {key: $t('login.phoneNumber')})"
/>
</el-form-item>
<el-form-item
prop="verifyCode"
>
<el-row>
<el-col :span="16">
<el-input
v-model="loginForm.verifyCode"
auto-complete="off"
:placeholder="$t('global.pleaseInputBy', {key: $t('login.phoneVerifyCode')})"
prefix-icon="el-icon-key"
style="margin:-right: 10px;"
/>
</el-col>
<el-col :span="8">
<el-button
ref="sendButton"
style="margin-left: 10px;width: 132px;"
:disabled="sending"
@click="handleSendPhoneVerifyCode"
>
{{ sendButtonName }}
</el-button>
</el-col>
</el-row>
</el-form-item>
</div>
</el-tab-pane>
</el-tabs>
<el-form-item style="width:100%;">
<el-button <el-button
:loading="loading"
type="primary" type="primary"
style="width:100%; margin-bottom:30px;" style="width:100%;"
@click.native.prevent="handleLogin" :loading="logining"
@click="handleUserLogin"
> >
{{ $t('login.logIn') }} {{ $t('login.logIn') }}
</el-button> </el-button>
</el-form-item>
</el-form> </el-form>
<el-dialog
:title="$t('login.thirdparty')"
:visible.sync="showDialog"
>
{{ $t('login.thirdpartyTips') }}
<br>
<br>
<br>
<social-sign />
</el-dialog>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator' import { Input } from 'element-ui'
import { Route } from 'vue-router' import { Route } from 'vue-router'
import { Dictionary } from 'vue-router/types/router'
import { Form as ElForm, Input } from 'element-ui'
import { UserModule } from '@/store/modules/user' import { UserModule } from '@/store/modules/user'
import { Dictionary } from 'vue-router/types/router'
import LangSelect from '@/components/LangSelect/index.vue' import LangSelect from '@/components/LangSelect/index.vue'
import SocialSign from './components/SocialSignin.vue' import { Component, Vue, Watch } from 'vue-property-decorator'
import UserService, { PhoneVerify, VerifyType } from '@/api/users'
@Component({ @Component({
name: 'Login', name: 'Login',
components: { components: {
LangSelect, LangSelect
SocialSign
} }
}) })
export default class extends Vue { export default class extends Vue {
private validateUsername = (rule: any, value: string, callback: Function) => { private loginType = 'password'
// if (!isValidUsername(value)) { private passwordType = 'password'
// callback(new Error('Please enter the correct user name')) private redirect?: string
// } else {
// callback() private sendTimer: any
// } private sending = false
callback() private sendButtonName = this.l('login.sendVerifyCode')
private logining = false
private loginForm = {
tenantName: '',
username: '',
password: '',
phoneNumber: '',
verifyCode: ''
} }
private validatePassword = (rule: any, value: string, callback: Function) => { private validatePhoneNumberValue = (rule: any, value: string, callback: any) => {
// if (value.length < 6) { const phoneReg = /^1[34578]\d{9}$/
// callback(new Error('The password can not be less than 6 digits')) if (!value || !phoneReg.test(value)) {
// } else { callback(new Error(this.l('global.pleaseInputBy', { key: this.l('global.correctPhoneNumber') })))
// callback() } else {
// }
callback() callback()
} }
private loginForm = {
tenantName: 'test-vue',
username: 'admin',
password: '1q2w3E*'
} }
private loginRules = { private loginFormRules = {
username: [{ validator: this.validateUsername, trigger: 'blur' }], username: [
password: [{ validator: this.validatePassword, trigger: 'blur' }] {
required: true, message: this.l('global.pleaseInputBy', { key: this.l('login.username') }), trigger: 'blur'
}
],
password: [
{
required: true, message: this.l('global.pleaseInputBy', { key: this.l('login.password') }), trigger: 'blur'
}
],
phoneNumber: [
{
required: true, validator: this.validatePhoneNumberValue, trigger: 'blur'
}
],
verifyCode: [
{
required: true, message: this.l('global.pleaseInputBy', { key: this.l('login.phoneVerifyCode') }), trigger: 'blur'
}
]
} }
private passwordType = 'password' destroyed() {
private loading = false if (this.sendTimer) {
private showDialog = false clearInterval(this.sendTimer)
private capsTooltip = false }
private redirect?: string }
@Watch('$route', { immediate: true }) @Watch('$route', { immediate: true })
private onRouteChange(route: Route) { private onRouteChange(route: Route) {
@ -162,19 +192,6 @@ export default class extends Vue {
} }
} }
mounted() {
if (this.loginForm.username === '') {
(this.$refs.username as Input).focus()
} else if (this.loginForm.password === '') {
(this.$refs.password as Input).focus()
}
}
private checkCapslock(e: KeyboardEvent) {
const { key } = e
this.capsTooltip = key !== null && key.length === 1 && (key >= 'A' && key <= 'Z')
}
private showPwd() { private showPwd() {
if (this.passwordType === 'password') { if (this.passwordType === 'password') {
this.passwordType = '' this.passwordType = ''
@ -186,98 +203,91 @@ export default class extends Vue {
}) })
} }
private handleLogin() { private handleUserLogin() {
(this.$refs.loginForm as ElForm).validate(async(valid: boolean) => { const frmLogin = this.$refs.formLogin as any
frmLogin.validate(async(valid: boolean) => {
if (valid) { if (valid) {
this.loading = true this.logining = true
// await UserModule.Login(this.loginForm) try {
UserModule.Login(this.loginForm).then(() => { if (this.loginType === 'password') {
// await UserModule.Login(this.loginForm) const userLogin = {
tenantName: this.loginForm.tenantName,
username: this.loginForm.username,
password: this.loginForm.password
}
await UserModule.Login(userLogin)
this.$router.push({ this.$router.push({
path: this.redirect || '/' path: this.redirect || '/'
}) })
}).catch(() => {
setTimeout(() => {
this.loading = false
}, 0.5 * 1000)
})
} else { } else {
return false const phoneLogin = {
tenantName: this.loginForm.tenantName,
phoneNumber: this.loginForm.phoneNumber,
verifyCode: this.loginForm.verifyCode
} }
await UserModule.PhoneLogin(phoneLogin)
this.$router.push({
path: this.redirect || '/'
}) })
} }
} } catch {
</script> this.resetLoginButton()
}
<style lang="scss"> }
// References: https://www.zhangxinxu.com/wordpress/2018/01/css-caret-color-first-line/ })
@supports (-webkit-mask: none) and (not (cater-color: $loginCursorColor)) {
.login-container .el-input {
input { color: $loginCursorColor; }
input::first-line { color: $lightGray; }
} }
}
.login-container {
.el-input {
display: inline-block;
height: 47px;
width: 85%;
input {
height: 47px;
background: transparent;
border: 0px;
border-radius: 0px;
padding: 12px 5px 12px 15px;
color: $lightGray;
caret-color: $loginCursorColor;
-webkit-appearance: none;
&:-webkit-autofill { private handleSendPhoneVerifyCode() {
box-shadow: 0 0 0px 1000px $loginBg inset !important; const frmLogin = this.$refs.formLogin as any
-webkit-text-fill-color: #fff !important; frmLogin.validateField('phoneNumber', (errorMsg: string) => {
if (!errorMsg) {
this.sending = true
const phoneVerify = new PhoneVerify()
phoneVerify.phoneNumber = this.loginForm.phoneNumber
phoneVerify.verifyType = VerifyType.signin
UserService.sendPhoneVerifyCode(phoneVerify).then(() => {
let interValTime = 60
const sendingName = this.l('login.afterSendVerifyCode')
const sendedName = this.l('login.sendVerifyCode')
this.sendTimer = setInterval(() => {
this.sendButtonName = interValTime + sendingName
--interValTime
if (interValTime < 0) {
this.sendButtonName = sendedName
this.sending = false
clearInterval(this.sendTimer)
} }
}, 1000)
}).catch(() => {
this.sending = false
})
} }
})
} }
.el-form-item { private handleLoginTabChanged(tab: any) {
border: 1px solid rgba(255, 255, 255, 0.1); this.loginType = tab.paneName === '1' ? 'phone' : 'password'
background: rgba(0, 0, 0, 0.1); }
border-radius: 5px;
color: #454545; private l(name: string, values?: any[] | { [key: string]: any }) {
return this.$t(name, values).toString()
}
private resetLoginButton() {
setTimeout(() => {
this.logining = false
}, 0.5 * 1000)
} }
} }
</style> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.login-container { .login-container {
height: 100%;
width: 100%; width: 100%;
height: 100%;
overflow: hidden; overflow: hidden;
background-color: $loginBg; background-color: $loginBg;
.login-form {
position: relative;
width: 520px;
max-width: 100%;
padding: 160px 35px 0;
margin: 0 auto;
overflow: hidden;
}
.tips {
font-size: 14px;
color: #fff;
margin-bottom: 10px;
span {
&:first-of-type {
margin-right: 16px;
}
}
}
.svg-container { .svg-container {
padding: 6px 5px 6px 15px; padding: 6px 5px 6px 15px;
color: $darkGray; color: $darkGray;
@ -292,11 +302,23 @@ export default class extends Vue {
.title { .title {
font-size: 26px; font-size: 26px;
color: $lightGray; color: $lightGray;
margin: 0px auto 40px auto; margin: 0px auto 20px auto;
text-align: center; text-align: center;
font-weight: bold; font-weight: bold;
} }
.tips {
font-size: 14px;
color: #fff;
margin-bottom: 10px;
span {
&:first-of-type {
margin-right: 16px;
}
}
}
.set-language { .set-language {
color: #fff; color: #fff;
position: absolute; position: absolute;
@ -310,23 +332,30 @@ export default class extends Vue {
.show-pwd { .show-pwd {
position: absolute; position: absolute;
right: 10px; right: 10px;
top: 7px;
font-size: 16px; font-size: 16px;
color: $darkGray; color: $darkGray;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
} }
}
.thirdparty-button { .login-page {
position: absolute; -webkit-border-radius: 5px;
right: 0; border-radius: 5px;
bottom: 6px; margin: 130px auto;
width: 500px;
padding: 35px 35px 15px;
border: 1px solid #8c9494;
box-shadow: 0 0 25px #454646;
background-color:rgb(110, 115, 116);
.loginTab.el-tabs__item {
width: 180px;
} }
}
@media only screen and (max-width: 470px) { label.el-checkbox.rememberme {
.thirdparty-button { margin: 0px 0px 15px;
display: none; text-align: left;
}
}
} }
</style> </style>

7
vueJs/src/views/profile/index.vue

@ -51,14 +51,12 @@ import UserCard from './components/UserCard.vue'
export interface IProfile { export interface IProfile {
name: string name: string
email: string email: string
avatar: string
roles: string roles: string
} }
const defaultProfile: IProfile = { const defaultProfile: IProfile = {
name: 'Loading...', name: 'Loading...',
email: 'Loading...', email: 'Loading...',
avatar: 'Loading...',
roles: 'Loading...' roles: 'Loading...'
} }
@ -83,10 +81,6 @@ export default class extends Vue {
return UserModule.email return UserModule.email
} }
get avatar() {
return UserModule.avatar
}
get roles() { get roles() {
return UserModule.roles return UserModule.roles
} }
@ -99,7 +93,6 @@ export default class extends Vue {
this.user = { this.user = {
name: this.name, name: this.name,
email: this.email, email: this.email,
avatar: this.avatar,
roles: this.roles.join(' | ') roles: this.roles.join(' | ')
} }
} }

Loading…
Cancel
Save