1. 新增接入高德地图API

2. 新增按钮级别权限
3. vue-element-plus-admin版本更新
This commit is contained in:
ktianc 2022-11-18 20:59:10 +08:00
parent cc3ca58dd6
commit 06d118cad7
29 changed files with 517 additions and 154 deletions

View File

@ -70,7 +70,7 @@ github地址https://github.com/vvandk/kinit 👩‍👦‍👦
- [x] 📚字典管理:对系统中经常使用的一些较为固定的数据进行维护。 - [x] 📚字典管理:对系统中经常使用的一些较为固定的数据进行维护。
- [ ] 📁附件管理对平台上所有文件、图片等进行统一管理对接阿里云OSS - [x] 📁文件上传对接阿里云OSS与本地存储
- [x] 🔒登录认证:目前支持用户使用手机号+密码方式登录。 - [x] 🔒登录认证:目前支持用户使用手机号+密码方式登录。
@ -82,7 +82,7 @@ github地址https://github.com/vvandk/kinit 👩‍👦‍👦
网站标题LOGO描述ICO备案号底部内容百度统计代码等等 网站标题LOGO描述ICO备案号底部内容百度统计代码等等
- [ ] 数据分析:根据用户的登录用户地址分析出哪个地区的人最多 - [x] 用户分布:接入高德地图显示各地区用户分布情况
- [x] 🗓️登录日志:用户登录日志记录和查询。 - [x] 🗓️登录日志:用户登录日志记录和查询。
@ -94,7 +94,7 @@ github地址https://github.com/vvandk/kinit 👩‍👦‍👦
- [x] 导入导出:灵活支持数据导入导出功能 - [x] 导入导出:灵活支持数据导入导出功能
- [x] 手机验证码登录功能 - [x] 手机验证码登录功能
## TODO ## TODO
@ -123,6 +123,7 @@ github地址https://github.com/vvandk/kinit 👩‍👦‍👦
- [vue3-json-viewer](https://gitee.com/isfive/vue3-json-viewer)简单易用的json内容展示组件,适配vue3和vite。 - [vue3-json-viewer](https://gitee.com/isfive/vue3-json-viewer)简单易用的json内容展示组件,适配vue3和vite。
- [vue3-slide-verify](https://github.com/monoplasty/vue3-slide-verify):滑块验证码插件 vue3 + typescript - [vue3-slide-verify](https://github.com/monoplasty/vue3-slide-verify):滑块验证码插件 vue3 + typescript
- [SortableJS/vue.draggable.next](https://github.com/SortableJS/vue.draggable.next)Vue 组件 Vue.js 3.0 允许拖放和与视图模型数组同步。 - [SortableJS/vue.draggable.next](https://github.com/SortableJS/vue.draggable.next)Vue 组件 Vue.js 3.0 允许拖放和与视图模型数组同步。
- [高德地图API (amap.com)](https://lbs.amap.com/api/jsapi-v2/guide/webcli/map-vue1):地图 JSAPI 2.0 是高德开放平台免费提供的第四代 Web 地图渲染引擎, 以 WebGL 为主要绘图手段,本着“更轻、更快、更易用”的服务原则,广泛采用了各种前沿技术,交互体验、视觉体验大幅提升,同时提供了众多新增能力和特性。
#### 后端 #### 后端

View File

@ -1,6 +1,6 @@
{ {
"name": "vue-element-plus-admin", "name": "vue-element-plus-admin",
"version": "1.8.4", "version": "1.8.5",
"description": "一套基于vue3、element-plus、typesScript、vite3的后台集成方案。", "description": "一套基于vue3、element-plus、typesScript、vite3的后台集成方案。",
"author": "Archer <502431556@qq.com>", "author": "Archer <502431556@qq.com>",
"private": false, "private": false,
@ -24,9 +24,10 @@
"analysis": "windicss-analysis" "analysis": "windicss-analysis"
}, },
"dependencies": { "dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@iconify/iconify": "^3.0.0", "@iconify/iconify": "^3.0.0",
"@vueuse/core": "^9.4.0", "@vueuse/core": "^9.5.0",
"@wangeditor/editor": "^5.1.22", "@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.10", "@wangeditor/editor-for-vue": "^5.1.10",
"@zxcvbn-ts/core": "^2.1.0", "@zxcvbn-ts/core": "^2.1.0",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
@ -45,7 +46,7 @@
"qrcode": "^1.5.1", "qrcode": "^1.5.1",
"qs": "^6.11.0", "qs": "^6.11.0",
"url": "^0.11.0", "url": "^0.11.0",
"vue": "3.2.41", "vue": "3.2.45",
"vue-i18n": "9.2.2", "vue-i18n": "9.2.2",
"vue-router": "^4.1.6", "vue-router": "^4.1.6",
"vue-types": "^4.2.1", "vue-types": "^4.2.1",
@ -56,7 +57,7 @@
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^17.2.0", "@commitlint/cli": "^17.2.0",
"@commitlint/config-conventional": "^17.2.0", "@commitlint/config-conventional": "^17.2.0",
"@iconify/json": "^2.1.134", "@iconify/json": "^2.1.139",
"@intlify/vite-plugin-vue-i18n": "^6.0.3", "@intlify/vite-plugin-vue-i18n": "^6.0.3",
"@purge-icons/generated": "^0.9.0", "@purge-icons/generated": "^0.9.0",
"@types/intro.js": "^5.1.0", "@types/intro.js": "^5.1.0",
@ -65,35 +66,35 @@
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.5.0", "@types/qrcode": "^1.5.0",
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
"@typescript-eslint/eslint-plugin": "^5.42.0", "@typescript-eslint/eslint-plugin": "^5.43.0",
"@typescript-eslint/parser": "^5.42.0", "@typescript-eslint/parser": "^5.43.0",
"@vitejs/plugin-vue": "^3.2.0", "@vitejs/plugin-vue": "^3.2.0",
"@vitejs/plugin-vue-jsx": "^2.1.0", "@vitejs/plugin-vue-jsx": "^2.1.1",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"eslint": "^8.27.0", "eslint": "^8.27.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-define-config": "^1.11.0", "eslint-define-config": "^1.12.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.7.0", "eslint-plugin-vue": "^9.7.0",
"husky": "^8.0.1", "husky": "^8.0.2",
"less": "^4.1.3", "less": "^4.1.3",
"lint-staged": "^13.0.3", "lint-staged": "^13.0.3",
"plop": "^3.1.1", "plop": "^3.1.1",
"postcss": "^8.4.18", "postcss": "^8.4.19",
"postcss-html": "^1.5.0", "postcss-html": "^1.5.0",
"postcss-less": "^6.0.0", "postcss-less": "^6.0.0",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rollup": "^3.2.5", "rollup": "^3.3.0",
"stylelint": "^14.14.1", "stylelint": "^14.15.0",
"stylelint-config-html": "^1.1.0", "stylelint-config-html": "^1.1.0",
"stylelint-config-prettier": "^9.0.3", "stylelint-config-prettier": "^9.0.4",
"stylelint-config-recommended": "^9.0.0", "stylelint-config-recommended": "^9.0.0",
"stylelint-config-standard": "^29.0.0", "stylelint-config-standard": "^29.0.0",
"stylelint-order": "^5.0.0", "stylelint-order": "^5.0.0",
"typescript": "4.8.4", "typescript": "4.9.3",
"unplugin-vue-macros": "^0.16.0", "unplugin-vue-macros": "^0.16.3",
"vite": "3.2.2", "vite": "3.2.4",
"vite-plugin-eslint": "^1.8.1", "vite-plugin-eslint": "^1.8.1",
"vite-plugin-html": "^3.2.0", "vite-plugin-html": "^3.2.0",
"vite-plugin-mock": "^2.9.6", "vite-plugin-mock": "^2.9.6",

View File

@ -0,0 +1,5 @@
import request from '@/config/axios'
export const getUserLoginDistributeApi = (): Promise<IResponse> => {
return request.get({ url: '/vadmin/record/analysis/user/login/distribute/' })
}

View File

@ -245,11 +245,7 @@ export default defineComponent({
vModel={formModel.value[item.field]} vModel={formModel.value[item.field]}
{...(autoSetPlaceholder && setTextPlaceholder(item))} {...(autoSetPlaceholder && setTextPlaceholder(item))}
{...setComponentProps(item)} {...setComponentProps(item)}
style={ style={item.componentProps?.style}
item?.component === 'Input'
? { width: '100%', ...item.componentProps?.style }
: { ...item.componentProps?.style }
}
{...(notRenderOptions.includes(item?.component as string) && {...(notRenderOptions.includes(item?.component as string) &&
item?.componentProps?.options item?.componentProps?.options
? { options: item?.componentProps?.options || [] } ? { options: item?.componentProps?.options || [] }
@ -276,8 +272,8 @@ export default defineComponent({
return renderRadioOptions(item) return renderRadioOptions(item)
case 'Checkbox': case 'Checkbox':
case 'CheckboxButton': case 'CheckboxButton':
const { renderChcekboxOptions } = useRenderCheckbox() const { renderCheckboxOptions } = useRenderCheckbox()
return renderChcekboxOptions(item) return renderCheckboxOptions(item)
default: default:
break break
} }

View File

@ -3,7 +3,7 @@ import { ElCheckbox, ElCheckboxButton } from 'element-plus'
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
export const useRenderCheckbox = () => { export const useRenderCheckbox = () => {
const renderChcekboxOptions = (item: FormSchema) => { const renderCheckboxOptions = (item: FormSchema) => {
// 如果有别名,就取别名 // 如果有别名,就取别名
const labelAlias = item?.componentProps?.optionsAlias?.labelField const labelAlias = item?.componentProps?.optionsAlias?.labelField
const valueAlias = item?.componentProps?.optionsAlias?.valueField const valueAlias = item?.componentProps?.optionsAlias?.valueField
@ -11,17 +11,16 @@ export const useRenderCheckbox = () => {
typeof defineComponent typeof defineComponent
> >
return item?.componentProps?.options?.map((option) => { return item?.componentProps?.options?.map((option) => {
return <Com label={option[labelAlias || 'value']}>{option[valueAlias || 'label']}</Com> const { value, ...other } = option
// const { value, ...other } = option return (
// return ( <Com {...other} label={option[valueAlias || 'value']}>
// <Com label={option[labelAlias || 'value']} {...other}> {option[labelAlias || 'label']}
// {option[valueAlias || 'label']} </Com>
// </Com> )
// )
}) })
} }
return { return {
renderChcekboxOptions renderCheckboxOptions
} }
} }

View File

@ -11,13 +11,12 @@ export const useRenderRadio = () => {
typeof defineComponent typeof defineComponent
> >
return item?.componentProps?.options?.map((option) => { return item?.componentProps?.options?.map((option) => {
return <Com label={option[labelAlias || 'value']}>{option[valueAlias || 'label']}</Com> const { value, ...other } = option
// const { value, ...other } = option return (
// return ( <Com {...other} label={option[valueAlias || 'value']}>
// <Com label={option[labelAlias || 'value']} {...other}> {option[labelAlias || 'label']}
// {option[valueAlias || 'label']} </Com>
// </Com> )
// )
}) })
} }

View File

@ -36,9 +36,9 @@ export const useRenderSelect = (slots: Slots) => {
return ( return (
<ElOption <ElOption
{...other}
label={labelAlias ? option[labelAlias] : label} label={labelAlias ? option[labelAlias] : label}
value={valueAlias ? option[valueAlias] : value} value={valueAlias ? option[valueAlias] : value}
{...other}
> >
{{ {{
default: () => default: () =>

View File

@ -52,7 +52,6 @@ export default defineComponent({
}, },
emits: ['update:limit', 'update:page', 'register'], emits: ['update:limit', 'update:page', 'register'],
setup(props, { attrs, slots, emit, expose }) { setup(props, { attrs, slots, emit, expose }) {
console.log('attrs', attrs)
const elTableRef = ref<ComponentRef<typeof ElTable>>() const elTableRef = ref<ComponentRef<typeof ElTable>>()
// //

View File

@ -32,6 +32,14 @@ const toHome = () => {
push('/system/home') push('/system/home')
} }
const toGitee = () => {
window.open('https://gitee.com/ktianc/kinit')
}
const toGithub = () => {
window.open('https://github.com/vvandk/kinit')
}
const user = authStore.getUser const user = authStore.getUser
</script> </script>
@ -52,6 +60,12 @@ const user = authStore.getUser
<ElDropdownItem> <ElDropdownItem>
<ElButton @click="toHome" link>个人主页</ElButton> <ElButton @click="toHome" link>个人主页</ElButton>
</ElDropdownItem> </ElDropdownItem>
<ElDropdownItem>
<ElButton @click="toGitee" link>Gitee</ElButton>
</ElDropdownItem>
<ElDropdownItem>
<ElButton @click="toGithub" link>Github</ElButton>
</ElDropdownItem>
<ElDropdownItem divided> <ElDropdownItem divided>
<ElButton @click="loginOut" link>退出系统</ElButton> <ElButton @click="loginOut" link>退出系统</ElButton>
</ElDropdownItem> </ElDropdownItem>

View File

@ -53,9 +53,8 @@ export const useAuthStore = defineStore('auth', {
if (res) { if (res) {
wsCache.set(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`) wsCache.set(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`)
// 存储用户信息 // 存储用户信息
wsCache.set(appStore.getUserInfo, res.data.user) const auth = useAuthStore()
this.user = res.data.user await auth.getUserInfo()
this.isUser = true
} }
return res return res
}, },

View File

@ -0,0 +1,183 @@
<script setup lang="ts">
import AMapLoader from '@amap/amap-jsapi-loader'
import { shallowRef, ref } from 'vue'
import { getSystemSettingsApi } from '@/api/vadmin/system/settings'
import { getUserLoginDistributeApi } from '@/api/dashboard/map/index'
let map = shallowRef()
let AMap = shallowRef()
// AMap.Map https://lbs.amap.com/api/javascript-api/reference/map
// InfoWindow https://lbs.amap.com/api/javascript-api/reference/infowindow#InfoWindow
// https://blog.csdn.net/qq_39417037/article/details/124040318
const initMap = async () => {
const res = await getSystemSettingsApi({ tab_id: 8 })
if (res) {
AMapLoader.load({
key: res.data.map_key, // WebKey load
version: '2.0', // JSAPI 1.4.15
plugins: [''] // 使'AMap.Scale'
})
.then(async (A) => {
AMap.value = A
map.value = new A.Map('map-container', {
// id
pitch: res.data.map_pitch, // 0 - 83
terrain: true, //
viewMode: res.data.map_view_mode, // 3D
zoom: res.data.map_zoom, //
resizeEnable: true,
mapStyle: res.data.map_style, //
center: JSON.parse(res.data.map_center) //
})
await setValues()
})
.catch((e) => {
console.log(e)
})
}
}
const setValues = async () => {
const infoWindow = new AMap.value.InfoWindow({
offset: new AMap.value.Pixel(2, 15),
closeWhenClickMap: true,
isCustom: true,
anchor: 'top-left'
})
const res = await getUserLoginDistributeApi()
if (res) {
const markers = res.data.map((item) => {
const center = item.center
let circleMarker = ref()
if (item.total > 40) {
circleMarker.value = new AMap.value.Marker({
position: center,
offset: new AMap.value.Pixel(0, 15) //
})
// div
var markerDiv = document.createElement('div')
// className,
markerDiv.className = 'alarmDevice'
// div
circleMarker.value.setContent(markerDiv)
} else {
circleMarker.value = new AMap.value.CircleMarker({
center: center,
radius: item.total > 30 ? 20 : item.total / 2, // 3DCircleMarker64px
strokeColor: '#f05b72',
strokeWeight: 2,
strokeOpacity: 0.5,
fillColor: '#f05b72',
fillOpacity: 0.5,
zIndex: 10,
bubble: true,
cursor: 'pointer',
clickable: true
})
}
//
circleMarker.value.on('mouseover', function (e) {
infoWindow.setContent(
`<div class="description">
<div class="name-box">
<span class="point"></span>
<span class="name">${item.name}</span>
</div>
<span>${item.total}</span>
</div>`
)
infoWindow.open(map.value, center)
})
//
circleMarker.value.on('mouseout', function (e) {
infoWindow.close(map.value, center)
})
return circleMarker.value
})
map.value.add(markers)
}
}
initMap()
</script>
<template>
<div id="map-container"></div>
</template>
<style scoped lang="less">
#map-container {
padding: 0px;
margin: 0px;
width: 100%;
height: 800px;
}
#map-container :deep(.description) {
background-color: #fff;
height: 50px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
box-sizing: border-box;
border: 2px solid #f05b72;
border-radius: 5px;
font-size: 14px;
}
#map-container :deep(.point) {
display: inline-block;
width: 9px;
height: 9px;
border-radius: 50%;
background-color: #f05b72;
margin-bottom: 1px;
margin-right: 2px;
}
#map-container :deep(.name-box) {
display: inline;
margin-right: 8px;
}
#map-container :deep(.alarmDevice) {
text-align: center;
margin: 0 auto;
width: 30px;
height: 30px;
background-color: #f13737;
box-shadow: 0px 0px 15px #f61212;
border-radius: 50%;
-webkit-animation-name: 'alarmDeviceBreath'; /*动画属性名也就是我们前面keyframes定义的动画名*/
-webkit-animation-duration: 1s; /*动画持续时间*/
-webkit-animation-timing-function: ease; /*动画频率和transition-timing-function是一样的*/
-webkit-animation-delay: 0s; /*动画延迟时间*/
-webkit-animation-iteration-count: infinite; /*定义循环资料infinite为无限次*/
-webkit-animation-direction: alternate; /*定义动画方式*/
}
</style>
<style>
@keyframes alarmDeviceBreath {
0% {
margin-left: 0;
margin-top: 0;
width: 30px;
height: 30px;
box-shadow: 0px 0px 15px #f61212;
opacity: 1.2;
}
100% {
margin-left: 5px;
margin-top: 5px;
width: 20px;
height: 20px;
box-shadow: 0px 0px 10px #f61212;
opacity: 0.6;
}
}
</style>

View File

@ -129,8 +129,8 @@ const signIn = async () => {
loading.value = true loading.value = true
const { getFormData } = methods const { getFormData } = methods
const formData = await getFormData<UserLoginType>() const formData = await getFormData<UserLoginType>()
const authStore = useAuthStoreWithOut()
try { try {
const authStore = useAuthStoreWithOut()
const res = await authStore.login(formData) const res = await authStore.login(formData)
if (res) { if (res) {
if (!res.data.is_reset_password) { if (!res.data.is_reset_password) {
@ -140,8 +140,10 @@ const signIn = async () => {
// 使 // 使
getMenu() getMenu()
} }
} else {
loading.value = false
} }
} finally { } catch (e: any) {
loading.value = false loading.value = false
} }
} }

View File

@ -97,8 +97,8 @@ const telephoneCodeLogin = async () => {
loading.value = true loading.value = true
const { getFormData } = methods const { getFormData } = methods
const formData = await getFormData<UserLoginType>() const formData = await getFormData<UserLoginType>()
const authStore = useAuthStoreWithOut()
try { try {
const authStore = useAuthStoreWithOut()
const res = await authStore.login(formData) const res = await authStore.login(formData)
if (res) { if (res) {
if (!res.data.is_reset_password) { if (!res.data.is_reset_password) {
@ -108,8 +108,10 @@ const telephoneCodeLogin = async () => {
// 使 // 使
getMenu() getMenu()
} }
} else {
loading.value = false
} }
} finally { } catch (e: any) {
loading.value = false loading.value = false
} }
} }
@ -127,17 +129,21 @@ const getSMSCode = async () => {
SMSCodeNumber.value = 60 SMSCodeNumber.value = 60
const { getFormData } = methods const { getFormData } = methods
const formData = await getFormData<UserLoginType>() const formData = await getFormData<UserLoginType>()
const res = await postSMSCodeApi({ telephone: formData.telephone }) try {
if (res?.data) { const res = await postSMSCodeApi({ telephone: formData.telephone })
let timer = setInterval(() => { if (res?.data) {
SMSCodeNumber.value-- let timer = setInterval(() => {
if (SMSCodeNumber.value < 1) { SMSCodeNumber.value--
SMSCodeStatus.value = true if (SMSCodeNumber.value < 1) {
clearInterval(timer) SMSCodeStatus.value = true
} clearInterval(timer)
}, 1000) }
} else { }, 1000)
ElMessage.error('发送失败,请联系管理员') } else {
ElMessage.error('发送失败,请联系管理员')
SMSCodeStatus.value = true
}
} catch (e: any) {
SMSCodeStatus.value = true SMSCodeStatus.value = true
} }
} }

View File

@ -107,8 +107,10 @@ const save = async () => {
if (res) { if (res) {
// 使 // 使
getMenu() getMenu()
} else {
loading.value = false
} }
} finally { } catch (e: any) {
loading.value = false loading.value = false
} }
} }

View File

@ -6,6 +6,7 @@ import { useValidator } from '@/hooks/web/useValidator'
import { getMenuTreeOptionsApi } from '@/api/vadmin/auth/menu' import { getMenuTreeOptionsApi } from '@/api/vadmin/auth/menu'
import { ElButton, ElInput } from 'element-plus' import { ElButton, ElInput } from 'element-plus'
import { schema } from './menu.data' import { schema } from './menu.data'
import { propTypes } from '@/utils/propTypes'
const { required } = useValidator() const { required } = useValidator()
@ -13,7 +14,8 @@ const props = defineProps({
currentRow: { currentRow: {
type: Object as PropType<Nullable<any>>, type: Object as PropType<Nullable<any>>,
default: () => null default: () => null
} },
parentId: propTypes.number.def(undefined)
}) })
const rules = reactive({ const rules = reactive({
@ -53,6 +55,10 @@ const getMenuTreeOptions = async () => {
value: res.data value: res.data
} }
]) ])
if (props.parentId) {
const { setValue } = methods
setValue('parent_id', props.parentId)
}
} }
} }

View File

@ -58,7 +58,7 @@ export const columns = reactive<TableColumn[]>([
}, },
{ {
field: 'action', field: 'action',
width: '150px', width: '200px',
label: '操作', label: '操作',
show: true show: true
} }
@ -77,7 +77,9 @@ export const schema = reactive<FormSchema[]>([
width: '100%' width: '100%'
}, },
checkStrictly: true, checkStrictly: true,
placeholder: '请选择上级菜单' placeholder: '请选择上级菜单',
nodeKey: 'value',
defaultExpandAll: true
} }
}, },
{ {
@ -130,6 +132,11 @@ export const schema = reactive<FormSchema[]>([
component: 'InputNumber', component: 'InputNumber',
colProps: { colProps: {
span: 12 span: 12
},
componentProps: {
style: {
width: '100%'
}
} }
}, },
{ {
@ -216,6 +223,6 @@ export const schema = reactive<FormSchema[]>([
colProps: { colProps: {
span: 12 span: 12
}, },
ifshow: (values) => values.menu_type !== '0' ifshow: (values) => values.menu_type === '2'
} }
]) ])

View File

@ -45,9 +45,11 @@ const dialogVisible = ref(false)
const dialogTitle = ref('') const dialogTitle = ref('')
const delLoading = ref(false) const delLoading = ref(false)
const actionType = ref('') const actionType = ref('')
const parentId = ref()
// //
const AddAction = () => { const AddAction = () => {
parentId.value = null
dialogTitle.value = t('exampleDemo.add') dialogTitle.value = t('exampleDemo.add')
tableObject.currentRow = null tableObject.currentRow = null
dialogVisible.value = true dialogVisible.value = true
@ -56,6 +58,7 @@ const AddAction = () => {
// //
const updateAction = (row: any) => { const updateAction = (row: any) => {
parentId.value = null
dialogTitle.value = '编辑' dialogTitle.value = '编辑'
tableObject.currentRow = row tableObject.currentRow = row
dialogVisible.value = true dialogVisible.value = true
@ -64,6 +67,7 @@ const updateAction = (row: any) => {
// //
const delData = async (row: any) => { const delData = async (row: any) => {
parentId.value = null
tableObject.currentRow = row tableObject.currentRow = row
const { delListApi } = methods const { delListApi } = methods
delLoading.value = true delLoading.value = true
@ -72,6 +76,15 @@ const delData = async (row: any) => {
}) })
} }
//
const addSonMenu = async (row: any) => {
parentId.value = row.id
dialogTitle.value = t('exampleDemo.add')
tableObject.currentRow = null
dialogVisible.value = true
actionType.value = 'add'
}
const loading = ref(false) const loading = ref(false)
const writeRef = ref<ComponentRef<typeof Write>>() const writeRef = ref<ComponentRef<typeof Write>>()
@ -124,7 +137,9 @@ watch(
<div class="mb-8px flex justify-between"> <div class="mb-8px flex justify-between">
<ElRow :gutter="10"> <ElRow :gutter="10">
<ElCol :span="1.5"> <ElCol :span="1.5">
<ElButton type="primary" @click="AddAction">新增菜单</ElButton> <ElButton type="primary" v-hasPermi="['auth.menu.create']" @click="AddAction"
>新增菜单</ElButton
>
</ElCol> </ElCol>
</ElRow> </ElRow>
<RightToolbar @get-list="getList" v-model:table-size="tableSize" v-model:columns="columns" /> <RightToolbar @get-list="getList" v-model:table-size="tableSize" v-model:columns="columns" />
@ -149,10 +164,31 @@ watch(
</div> </div>
</template> </template>
<template #action="{ row }"> <template #action="{ row }">
<ElButton type="primary" link size="small" @click="updateAction(row)"> <ElButton
type="primary"
v-hasPermi="['auth.menu.update']"
link
size="small"
@click="updateAction(row)"
>
{{ t('exampleDemo.edit') }} {{ t('exampleDemo.edit') }}
</ElButton> </ElButton>
<ElButton type="danger" link size="small" @click="delData(row)"> <ElButton
type="primary"
v-hasPermi="['auth.menu.create']"
link
size="small"
@click="addSonMenu(row)"
>
添加子菜单
</ElButton>
<ElButton
type="danger"
v-hasPermi="['auth.menu.delete']"
link
size="small"
@click="delData(row)"
>
{{ t('exampleDemo.del') }} {{ t('exampleDemo.del') }}
</ElButton> </ElButton>
</template> </template>
@ -173,7 +209,7 @@ watch(
</Table> </Table>
<Dialog v-model="dialogVisible" :title="dialogTitle" width="700px"> <Dialog v-model="dialogVisible" :title="dialogTitle" width="700px">
<Write ref="writeRef" :current-row="tableObject.currentRow" /> <Write ref="writeRef" :current-row="tableObject.currentRow" :parent-id="parentId" />
<template #footer> <template #footer>
<ElButton type="primary" :loading="loading" @click="save"> <ElButton type="primary" :loading="loading" @click="save">

View File

@ -5,7 +5,7 @@ import { PropType, reactive, watch, ref } from 'vue'
import { useValidator } from '@/hooks/web/useValidator' import { useValidator } from '@/hooks/web/useValidator'
import { getMenuRoleTreeOptionsApi } from '@/api/vadmin/auth/menu' import { getMenuRoleTreeOptionsApi } from '@/api/vadmin/auth/menu'
import { schema } from './role.data' import { schema } from './role.data'
import { ElTree } from 'element-plus' import { ElTree, ElCheckbox } from 'element-plus'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
const { required } = useValidator() const { required } = useValidator()
@ -50,7 +50,7 @@ const defaultProps = {
children: 'children', children: 'children',
label: 'label' label: 'label'
} }
let data = ref([]) let data = ref([] as Recordable[])
const getMenuRoleTreeOptions = async () => { const getMenuRoleTreeOptions = async () => {
const res = await getMenuRoleTreeOptionsApi() const res = await getMenuRoleTreeOptionsApi()
@ -72,23 +72,72 @@ defineExpose({
getFormData: methods.getFormData, getFormData: methods.getFormData,
getTreeCheckedKeys: getTreeCheckedKeys getTreeCheckedKeys: getTreeCheckedKeys
}) })
let selectAll = ref(false)
let defaultExpandAll = ref(true)
let checkStrictly = ref(false)
// key
const getTreeNodeKeys = (nodes: Recordable[]): number[] => {
let keys = [] as number[]
for (let i = 0; i < nodes.length; i++) {
keys.push(nodes[i].value)
if (nodes[i].children && nodes[i].children.length > 0) {
keys = keys.concat(getTreeNodeKeys(nodes[i].children))
}
}
return keys
}
// /
const handleCheckedTreeExpand = () => {
for (let i = 0; i < data.value.length; i++) {
treeRef.value!.store.nodesMap[data.value[i].value].expanded = defaultExpandAll.value
}
}
///
function handleCheckedTreeNodeAll() {
treeRef.value!.setCheckedKeys(selectAll.value ? getTreeNodeKeys(data.value) : [])
}
</script> </script>
<template> <template>
<Form :rules="rules" @register="register"> <Form :rules="rules" @register="register">
<template #menu_ids> <template #menu_ids>
<ElTree <div>
ref="treeRef" <div>
:data="data" <ElCheckbox
show-checkbox v-model="defaultExpandAll"
node-key="value" @change="handleCheckedTreeExpand"
:props="defaultProps" label="展开/折叠"
:default-checked-keys="defaultCheckedKeys" size="large"
> />
<template #default="{ node }"> <ElCheckbox
<span>{{ t(node.label) }}</span> v-model="selectAll"
</template> @change="handleCheckedTreeNodeAll"
</ElTree> label="全选/全不选"
size="large"
/>
<ElCheckbox v-model="checkStrictly" label="父子联动" size="large" />
</div>
<div class="max-h-390px border p-10px overflow-auto">
<ElTree
ref="treeRef"
:data="data"
show-checkbox
node-key="value"
:props="defaultProps"
:default-expand-all="defaultExpandAll"
:check-strictly="!checkStrictly"
:default-checked-keys="defaultCheckedKeys"
>
<template #default="{ node }">
<span>{{ t(node.label) }}</span>
</template>
</ElTree>
</div>
</div>
</template> </template>
</Form> </Form>
</template> </template>

View File

@ -53,6 +53,7 @@ const updateAction = async (row: any) => {
dialogTitle.value = '编辑' dialogTitle.value = '编辑'
tableObject.currentRow = res.data tableObject.currentRow = res.data
defaultCheckedKeys.value = res.data.menus.map((item: any) => item.id) defaultCheckedKeys.value = res.data.menus.map((item: any) => item.id)
console.log(defaultCheckedKeys.value)
dialogVisible.value = true dialogVisible.value = true
actionType.value = 'edit' actionType.value = 'edit'
} }
@ -123,7 +124,7 @@ watch(
<div class="mb-8px flex justify-between"> <div class="mb-8px flex justify-between">
<ElRow :gutter="10"> <ElRow :gutter="10">
<ElCol :span="1.5"> <ElCol :span="1.5" v-hasPermi="['auth.role.create']">
<ElButton type="primary" @click="AddAction">新增角色</ElButton> <ElButton type="primary" @click="AddAction">新增角色</ElButton>
</ElCol> </ElCol>
</ElRow> </ElRow>
@ -144,10 +145,24 @@ watch(
@register="register" @register="register"
> >
<template #action="{ row }"> <template #action="{ row }">
<ElButton type="primary" link size="small" @click="updateAction(row)" v-if="row.id !== 1"> <ElButton
type="primary"
v-hasPermi="['auth.role.update']"
link
size="small"
@click="updateAction(row)"
v-if="row.id !== 1"
>
{{ t('exampleDemo.edit') }} {{ t('exampleDemo.edit') }}
</ElButton> </ElButton>
<ElButton type="danger" link size="small" @click="delData(row)" v-if="row.id !== 1"> <ElButton
type="danger"
v-hasPermi="['auth.role.delete']"
link
size="small"
@click="delData(row)"
v-if="row.id !== 1"
>
{{ t('exampleDemo.del') }} {{ t('exampleDemo.del') }}
</ElButton> </ElButton>
</template> </template>
@ -161,7 +176,7 @@ watch(
</template> </template>
</Table> </Table>
<Dialog v-model="dialogVisible" :title="dialogTitle" width="700px"> <Dialog v-model="dialogVisible" :title="dialogTitle" width="700px" maxHeight="600px">
<Write <Write
ref="writeRef" ref="writeRef"
:current-row="tableObject.currentRow" :current-row="tableObject.currentRow"

View File

@ -179,19 +179,19 @@ const sendPasswordToSMS = async () => {
<div class="mb-8px flex justify-between"> <div class="mb-8px flex justify-between">
<ElRow :gutter="10"> <ElRow :gutter="10">
<ElCol :span="1.5"> <ElCol :span="1.5" v-hasPermi="['auth.user.create']">
<ElButton type="primary" @click="AddAction">新增用户</ElButton> <ElButton type="primary" @click="AddAction">新增用户</ElButton>
</ElCol> </ElCol>
<ElCol :span="1.5"> <ElCol :span="1.5" v-hasPermi="['auth.user.import']">
<ElButton @click="importList">批量导入用户</ElButton> <ElButton @click="importList">批量导入用户</ElButton>
</ElCol> </ElCol>
<ElCol :span="1.5"> <ElCol :span="1.5" v-hasPermi="['auth.user.export']">
<ElButton @click="exportQueryList">导出筛选用户</ElButton> <ElButton @click="exportQueryList">导出筛选用户</ElButton>
</ElCol> </ElCol>
<ElCol :span="1.5"> <ElCol :span="1.5" v-hasPermi="['auth.user.reset']">
<ElButton @click="sendPasswordToSMS">重置密码通知短信</ElButton> <ElButton @click="sendPasswordToSMS">重置密码通知短信</ElButton>
</ElCol> </ElCol>
<ElCol :span="1.5"> <ElCol :span="1.5" v-hasPermi="['auth.user.delete']">
<ElButton type="danger" @click="delDatas(null, true)">批量删除</ElButton> <ElButton type="danger" @click="delDatas(null, true)">批量删除</ElButton>
</ElCol> </ElCol>
</ElRow> </ElRow>
@ -212,11 +212,18 @@ const sendPasswordToSMS = async () => {
@register="register" @register="register"
> >
<template #action="{ row }"> <template #action="{ row }">
<ElButton type="primary" link size="small" @click="updateAction(row)"> <ElButton
type="primary"
v-hasPermi="['auth.user.update']"
link
size="small"
@click="updateAction(row)"
>
{{ t('exampleDemo.edit') }} {{ t('exampleDemo.edit') }}
</ElButton> </ElButton>
<ElButton <ElButton
type="danger" type="danger"
v-hasPermi="['auth.user.delete']"
link link
size="small" size="small"
@click="delDatas(row, false)" @click="delDatas(row, false)"

View File

@ -273,7 +273,7 @@ class MenuDal(DalBase):
3获取菜单树列表角色添加菜单权限时使用 3获取菜单树列表角色添加菜单权限时使用
""" """
if mode == 3: if mode == 3:
sql = select(self.model).where(self.model.disabled == 0, self.model.menu_type != "2") sql = select(self.model).where(self.model.disabled == 0)
else: else:
sql = select(self.model) sql = select(self.model)
queryset = await self.db.execute(sql) queryset = await self.db.execute(sql)

View File

@ -10,20 +10,16 @@ from apps.vadmin.auth import crud, models
from .validation import AuthValidation, Auth from .validation import AuthValidation, Auth
async def get_user_permissions(user: models.VadminUser, db: AsyncSession): async def get_user_permissions(user):
""" """
获取跟进系统用户所有权限列表 获取跟进系统用户所有权限列表
""" """
roles = [] if any([role.is_admin for role in user.roles]):
for i in user.roles: return ['*.*.*']
if i.is_admin:
return ["*:*:*"]
roles.append(i.id)
permissions = set() permissions = set()
for data_id in roles: for role_obj in user.roles:
role_obj = await crud.RoleDal(db).get_data(data_id, options=[models.VadminUser])
for menu in role_obj.menus: for menu in role_obj.menus:
if menu.perms and menu.status: if menu.perms and not menu.disabled:
permissions.add(menu.perms) permissions.add(menu.perms)
return list(permissions) return list(permissions)

View File

@ -5,7 +5,6 @@
# @IDE : PyCharm # @IDE : PyCharm
# @desc : 安全认证视图 # @desc : 安全认证视图
""" """
JWT 表示 JSON Web Tokenshttps://jwt.io/ JWT 表示 JSON Web Tokenshttps://jwt.io/
@ -16,13 +15,13 @@ JWT 表示 「JSON Web Tokens」。https://jwt.io/
我们需要安装 python-jose 以在 Python 中生成和校验 JWT 令牌pip install python-jose[cryptography] 我们需要安装 python-jose 以在 Python 中生成和校验 JWT 令牌pip install python-jose[cryptography]
PassLib 是一个用于处理哈希密码的很棒的 Python 它支持许多安全哈希算法以及配合算法使用的实用程序推荐的算法是 Bcryptpip install passlib[bcrypt] PassLib 是一个用于处理哈希密码的很棒的 Python 它支持许多安全哈希算法以及配合算法使用的实用程序
推荐的算法是 Bcryptpip install passlib[bcrypt]
""" """
import json
from datetime import timedelta from datetime import timedelta
from fastapi import APIRouter, Depends, Request from fastapi import APIRouter, Depends, Request
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from core.database import db_getter from core.database import db_getter
from utils.response import SuccessResponse, ErrorResponse from utils.response import SuccessResponse, ErrorResponse
from application import settings from application import settings
@ -45,33 +44,22 @@ async def login_for_access_token(request: Request, data: LoginForm, manage: Logi
else: else:
return ErrorResponse(msg="请使用正确的登录方式") return ErrorResponse(msg="请使用正确的登录方式")
if not result.status: if not result.status:
res = {"message": result.msg} resp = {"message": result.msg}
telephone = data.telephone telephone = data.telephone
await VadminLoginRecord.\ await VadminLoginRecord.\
create_login_record(telephone=telephone, status=result.status, request=request, response=res, db=db) create_login_record(db, telephone, result.status, request, resp)
return ErrorResponse(msg=result.msg) return ErrorResponse(msg=result.msg)
user = result.user user = result.user
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = LoginManage.create_access_token(data={"sub": user.telephone}, expires_delta=access_token_expires) access_token = LoginManage.create_access_token(data={"sub": user.telephone}, expires_delta=access_token_expires)
res = { resp = {
"access_token": access_token, "access_token": access_token,
"token_type": "bearer", "token_type": "bearer",
"is_reset_password": user.is_reset_password, "is_reset_password": user.is_reset_password
"user": {
"id": user.id,
"telephone": user.telephone,
"name": user.name,
"nickname": user.nickname,
"avatar": user.avatar,
"gender": user.gender,
"create_datetime": user.create_datetime,
"roles": [{"name": i.name, "value": i.role_key} for i in user.roles]
}
} }
await VadminLoginRecord.\ await VadminLoginRecord.create_login_record(db, user.telephone, result.status, request, resp)
create_login_record(telephone=user.telephone, status=result.status, request=request, response=res, db=db) return SuccessResponse(resp)
return SuccessResponse(res)
@app.get("/getMenuList/", summary="获取当前用户菜单树") @app.get("/getMenuList/", summary="获取当前用户菜单树")

View File

@ -44,7 +44,7 @@ class LoginValidation:
async def __call__(self, data: LoginForm, db: AsyncSession, request: Request) -> LoginResult: async def __call__(self, data: LoginForm, db: AsyncSession, request: Request) -> LoginResult:
self.result = LoginResult() self.result = LoginResult()
options = [models.VadminUser.roles] options = [models.VadminUser.roles, "roles.menus"]
user = await crud.UserDal(db).get_data(telephone=data.telephone, return_none=True, options=options) user = await crud.UserDal(db).get_data(telephone=data.telephone, return_none=True, options=options)
if not user: if not user:
self.result.msg = "该手机号不存在!" self.result.msg = "该手机号不存在!"

View File

@ -9,8 +9,8 @@
from fastapi import APIRouter, Depends, Body, UploadFile, Request from fastapi import APIRouter, Depends, Body, UploadFile, Request
from utils.response import SuccessResponse, ErrorResponse from utils.response import SuccessResponse, ErrorResponse
from . import schemas, crud, models from . import schemas, crud, models
from core.dependencies import Paging, IdList from core.dependencies import IdList
from apps.vadmin.auth.utils.current import login_auth, Auth from apps.vadmin.auth.utils.current import login_auth, Auth, get_user_permissions, full_admin
from .params import UserParams, RoleParams from .params import UserParams, RoleParams
app = APIRouter() app = APIRouter()
@ -65,8 +65,10 @@ async def post_user_current_update_info(data: schemas.UserUpdate, auth: Auth = D
@app.get("/user/current/info/", summary="获取当前用户基本信息") @app.get("/user/current/info/", summary="获取当前用户基本信息")
async def get_user_current_info(auth: Auth = Depends(login_auth)): async def get_user_current_info(auth: Auth = Depends(full_admin)):
return SuccessResponse(schemas.UserSimpleOut.from_orm(auth.user).dict()) result = schemas.UserSimpleOut.from_orm(auth.user).dict()
result["permissions"] = await get_user_permissions(auth.user)
return SuccessResponse(result)
@app.get("/user/current/info/", summary="获取当前用户基本信息") @app.get("/user/current/info/", summary="获取当前用户基本信息")

View File

@ -5,6 +5,8 @@
# @File : crud.py # @File : crud.py
# @IDE : PyCharm # @IDE : PyCharm
# @desc : 数据库 增删改查操作 # @desc : 数据库 增删改查操作
import random
from typing import List
# sqlalchemy 查询操作https://segmentfault.com/a/1190000016767008 # sqlalchemy 查询操作https://segmentfault.com/a/1190000016767008
# sqlalchemy 关联查询https://www.jianshu.com/p/dfad7c08c57a # sqlalchemy 关联查询https://www.jianshu.com/p/dfad7c08c57a
@ -15,9 +17,56 @@ from core.crud import DalBase
class LoginRecordDal(DalBase): class LoginRecordDal(DalBase):
def __init__(self, db: AsyncSession): def __init__(self, db: AsyncSession):
super(LoginRecordDal, self).__init__(db, models.VadminLoginRecord, schemas.LoginRecordSimpleOut) super(LoginRecordDal, self).__init__(db, models.VadminLoginRecord, schemas.LoginRecordSimpleOut)
async def get_user_distribute(self) -> List[dict]:
"""
获取用户登录分布情况
高德经纬度查询https://lbs.amap.com/tools/picker
{
name: '北京',
center: [116.407394, 39.904211],
total: 20
}
@return: List[dict]
"""
result = [{
"name": '北京',
"center": [116.407394, 39.904211],
},
{
"name": '重庆',
"center": [106.551643, 29.562849],
},
{
"name": '郑州',
"center": [113.778584, 34.759197],
},
{
"name": '南京',
"center": [118.796624, 32.059344],
},
{
"name": '武汉',
"center": [114.304569, 30.593354],
},
{
"name": '乌鲁木齐',
"center": [87.616824, 43.825377],
},
{
"name": '新乡',
"center": [113.92679, 35.303589],
}]
for data in result:
assert isinstance(data, dict)
data["total"] = random.randint(2, 80)
return result
class SMSSendRecordDal(DalBase): class SMSSendRecordDal(DalBase):
def __init__(self, db: AsyncSession): def __init__(self, db: AsyncSession):

View File

@ -35,24 +35,22 @@ class VadminLoginRecord(BaseModel):
request = Column(TEXT, comment="请求信息") request = Column(TEXT, comment="请求信息")
@classmethod @classmethod
async def create_login_record(cls, telephone: str, status: bool, request: Request, response: dict, async def create_login_record(cls, db: AsyncSession, telephone: str, status: bool, req: Request, resp: dict):
db: AsyncSession):
""" """
创建登录记录 创建登录记录
@return: @return:
""" """
header = {} header = {}
for k, v in request.headers.items(): for k, v in req.headers.items():
header[k] = v header[k] = v
body = json.loads((await request.body()).decode()) body = json.loads((await req.body()).decode())
user_agent = parse(request.headers.get("user-agent")) user_agent = parse(req.headers.get("user-agent"))
system = f"{user_agent.os.family} {user_agent.os.version_string}" system = f"{user_agent.os.family} {user_agent.os.version_string}"
browser = f"{user_agent.browser.family} {user_agent.browser.version_string}" browser = f"{user_agent.browser.family} {user_agent.browser.version_string}"
ip = IPManage(request.client.host) ip = IPManage(req.client.host)
location = await ip.parse() location = await ip.parse()
resp = json.dumps(response) params = json.dumps({"body": body, "headers": header})
resq = json.dumps({"body": body, "headers": header})
obj = VadminLoginRecord(**location.dict(), telephone=telephone, status=status, browser=browser, obj = VadminLoginRecord(**location.dict(), telephone=telephone, status=status, browser=browser,
system=system, response=resp, request=resq) system=system, response=json.dumps(resp), request=params)
db.add(obj) db.add(obj)
await db.flush() await db.flush()

View File

@ -5,9 +5,7 @@
# @IDE : PyCharm # @IDE : PyCharm
# @desc : 主要接口文件 # @desc : 主要接口文件
from typing import Optional from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, Query
from core.dependencies import Paging, IdList
from utils.response import SuccessResponse from utils.response import SuccessResponse
from . import crud, schemas from . import crud, schemas
from apps.vadmin.auth.utils.current import login_auth, Auth from apps.vadmin.auth.utils.current import login_auth, Auth
@ -40,3 +38,11 @@ async def get_sms_send_list(params: SMSParams = Depends(), auth: Auth = Depends(
datas = await crud.SMSSendRecordDal(auth.db).get_datas(**params.dict()) datas = await crud.SMSSendRecordDal(auth.db).get_datas(**params.dict())
count = await crud.SMSSendRecordDal(auth.db).get_count(**params.to_count()) count = await crud.SMSSendRecordDal(auth.db).get_count(**params.to_count())
return SuccessResponse(datas, count=count) return SuccessResponse(datas, count=count)
###########################################################
# 日志分析
###########################################################
@app.get("/analysis/user/login/distribute/", summary="获取用户登录分布情况列表")
async def get_user_login_distribute(auth: Auth = Depends(login_auth)):
return SuccessResponse(await crud.LoginRecordDal(auth.db).get_user_distribute())

View File

@ -22,14 +22,12 @@ pip install alibabacloud_dysmsapi20170525
import json import json
import random import random
import re import re
import uuid
from enum import Enum, unique from enum import Enum, unique
from core.exception import CustomException from core.exception import CustomException
from alibabacloud_dysmsapi20170525.client import Client as Dysmsapi20170525Client from alibabacloud_dysmsapi20170525.client import Client as Dysmsapi20170525Client
from alibabacloud_tea_openapi import models as open_api_models from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_dysmsapi20170525 import models as dysmsapi_20170525_models from alibabacloud_dysmsapi20170525 import models as dysmsapi_20170525_models
from alibabacloud_tea_util import models as util_models from alibabacloud_tea_util import models as util_models
from alibabacloud_tea_util.client import Client as UtilClient
from core.logger import logger from core.logger import logger
import datetime import datetime
from aioredis.client import Redis from aioredis.client import Redis
@ -44,8 +42,8 @@ class AliyunSMS:
@unique @unique
class Scene(Enum): class Scene(Enum):
login = "template_code_1" login = "sms_template_code_1"
reset_password = "template_code_2" reset_password = "sms_template_code_2"
def __init__(self, rd: Redis, telephone: str): def __init__(self, rd: Redis, telephone: str):
self.check_telephone_format(telephone) self.check_telephone_format(telephone)
@ -67,14 +65,14 @@ class AliyunSMS:
raise CustomException("获取短信配置信息失败,请联系管理员!", code=status.HTTP_ERROR) raise CustomException("获取短信配置信息失败,请联系管理员!", code=status.HTTP_ERROR)
else: else:
aliyun_sms = json.loads(aliyun_sms) aliyun_sms = json.loads(aliyun_sms)
self.access_key = aliyun_sms.get("access_key") self.access_key = aliyun_sms.get("sms_access_key")
self.access_key_secret = aliyun_sms.get("access_key_secret") self.access_key_secret = aliyun_sms.get("sms_access_key_secret")
self.send_interval = int(aliyun_sms.get("send_interval")) self.send_interval = int(aliyun_sms.get("sms_send_interval"))
self.valid_time = int(aliyun_sms.get("valid_time")) self.valid_time = int(aliyun_sms.get("sms_valid_time"))
if self.scene == self.Scene.login: if self.scene == self.Scene.login:
self.sign_name = aliyun_sms.get("sign_name_1") self.sign_name = aliyun_sms.get("sms_sign_name_1")
else: else:
self.sign_name = aliyun_sms.get("sign_name_2") self.sign_name = aliyun_sms.get("sms_sign_name_2")
self.template_code = aliyun_sms.get(self.scene.value) self.template_code = aliyun_sms.get(self.scene.value)
async def main_async(self, scene: Scene, **kwargs) -> bool: async def main_async(self, scene: Scene, **kwargs) -> bool: