1. 新增接入高德地图API
2. 新增按钮级别权限 3. vue-element-plus-admin版本更新
This commit is contained in:
parent
cc3ca58dd6
commit
06d118cad7
@ -70,7 +70,7 @@ github地址:https://github.com/vvandk/kinit 👩👦👦
|
||||
|
||||
- [x] 📚字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
||||
|
||||
- [ ] 📁附件管理:对平台上所有文件、图片等进行统一管理,对接阿里云OSS。
|
||||
- [x] 📁文件上传:对接阿里云OSS与本地存储。
|
||||
|
||||
- [x] 🔒登录认证:目前支持用户使用手机号+密码方式登录。
|
||||
|
||||
@ -82,7 +82,7 @@ github地址:https://github.com/vvandk/kinit 👩👦👦
|
||||
|
||||
网站标题,LOGO,描述,ICO,备案号,底部内容,百度统计代码,等等
|
||||
|
||||
- [ ] 数据分析:根据用户的登录用户地址分析出哪个地区的人最多
|
||||
- [x] 用户分布:接入高德地图显示各地区用户分布情况
|
||||
|
||||
- [x] 🗓️登录日志:用户登录日志记录和查询。
|
||||
|
||||
@ -94,7 +94,7 @@ github地址:https://github.com/vvandk/kinit 👩👦👦
|
||||
|
||||
- [x] 导入导出:灵活支持数据导入导出功能
|
||||
|
||||
- [x] 手机验证码登录功能’
|
||||
- [x] 手机验证码登录功能
|
||||
|
||||
## 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-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) 允许拖放和与视图模型数组同步。
|
||||
- [高德地图API (amap.com)](https://lbs.amap.com/api/jsapi-v2/guide/webcli/map-vue1):地图 JSAPI 2.0 是高德开放平台免费提供的第四代 Web 地图渲染引擎, 以 WebGL 为主要绘图手段,本着“更轻、更快、更易用”的服务原则,广泛采用了各种前沿技术,交互体验、视觉体验大幅提升,同时提供了众多新增能力和特性。
|
||||
|
||||
#### 后端
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vue-element-plus-admin",
|
||||
"version": "1.8.4",
|
||||
"version": "1.8.5",
|
||||
"description": "一套基于vue3、element-plus、typesScript、vite3的后台集成方案。",
|
||||
"author": "Archer <502431556@qq.com>",
|
||||
"private": false,
|
||||
@ -24,9 +24,10 @@
|
||||
"analysis": "windicss-analysis"
|
||||
},
|
||||
"dependencies": {
|
||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||
"@iconify/iconify": "^3.0.0",
|
||||
"@vueuse/core": "^9.4.0",
|
||||
"@wangeditor/editor": "^5.1.22",
|
||||
"@vueuse/core": "^9.5.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||
"@zxcvbn-ts/core": "^2.1.0",
|
||||
"animate.css": "^4.1.1",
|
||||
@ -45,7 +46,7 @@
|
||||
"qrcode": "^1.5.1",
|
||||
"qs": "^6.11.0",
|
||||
"url": "^0.11.0",
|
||||
"vue": "3.2.41",
|
||||
"vue": "3.2.45",
|
||||
"vue-i18n": "9.2.2",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue-types": "^4.2.1",
|
||||
@ -56,7 +57,7 @@
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^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",
|
||||
"@purge-icons/generated": "^0.9.0",
|
||||
"@types/intro.js": "^5.1.0",
|
||||
@ -65,35 +66,35 @@
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/qrcode": "^1.5.0",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.42.0",
|
||||
"@typescript-eslint/parser": "^5.42.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.43.0",
|
||||
"@typescript-eslint/parser": "^5.43.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",
|
||||
"eslint": "^8.27.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-vue": "^9.7.0",
|
||||
"husky": "^8.0.1",
|
||||
"husky": "^8.0.2",
|
||||
"less": "^4.1.3",
|
||||
"lint-staged": "^13.0.3",
|
||||
"plop": "^3.1.1",
|
||||
"postcss": "^8.4.18",
|
||||
"postcss": "^8.4.19",
|
||||
"postcss-html": "^1.5.0",
|
||||
"postcss-less": "^6.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^3.2.5",
|
||||
"stylelint": "^14.14.1",
|
||||
"rollup": "^3.3.0",
|
||||
"stylelint": "^14.15.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-standard": "^29.0.0",
|
||||
"stylelint-order": "^5.0.0",
|
||||
"typescript": "4.8.4",
|
||||
"unplugin-vue-macros": "^0.16.0",
|
||||
"vite": "3.2.2",
|
||||
"typescript": "4.9.3",
|
||||
"unplugin-vue-macros": "^0.16.3",
|
||||
"vite": "3.2.4",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
"vite-plugin-mock": "^2.9.6",
|
||||
|
5
kinit-admin/src/api/dashboard/map/index.ts
Normal file
5
kinit-admin/src/api/dashboard/map/index.ts
Normal 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/' })
|
||||
}
|
@ -245,11 +245,7 @@ export default defineComponent({
|
||||
vModel={formModel.value[item.field]}
|
||||
{...(autoSetPlaceholder && setTextPlaceholder(item))}
|
||||
{...setComponentProps(item)}
|
||||
style={
|
||||
item?.component === 'Input'
|
||||
? { width: '100%', ...item.componentProps?.style }
|
||||
: { ...item.componentProps?.style }
|
||||
}
|
||||
style={item.componentProps?.style}
|
||||
{...(notRenderOptions.includes(item?.component as string) &&
|
||||
item?.componentProps?.options
|
||||
? { options: item?.componentProps?.options || [] }
|
||||
@ -276,8 +272,8 @@ export default defineComponent({
|
||||
return renderRadioOptions(item)
|
||||
case 'Checkbox':
|
||||
case 'CheckboxButton':
|
||||
const { renderChcekboxOptions } = useRenderCheckbox()
|
||||
return renderChcekboxOptions(item)
|
||||
const { renderCheckboxOptions } = useRenderCheckbox()
|
||||
return renderCheckboxOptions(item)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { ElCheckbox, ElCheckboxButton } from 'element-plus'
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export const useRenderCheckbox = () => {
|
||||
const renderChcekboxOptions = (item: FormSchema) => {
|
||||
const renderCheckboxOptions = (item: FormSchema) => {
|
||||
// 如果有别名,就取别名
|
||||
const labelAlias = item?.componentProps?.optionsAlias?.labelField
|
||||
const valueAlias = item?.componentProps?.optionsAlias?.valueField
|
||||
@ -11,17 +11,16 @@ export const useRenderCheckbox = () => {
|
||||
typeof defineComponent
|
||||
>
|
||||
return item?.componentProps?.options?.map((option) => {
|
||||
return <Com label={option[labelAlias || 'value']}>{option[valueAlias || 'label']}</Com>
|
||||
// const { value, ...other } = option
|
||||
// return (
|
||||
// <Com label={option[labelAlias || 'value']} {...other}>
|
||||
// {option[valueAlias || 'label']}
|
||||
// </Com>
|
||||
// )
|
||||
const { value, ...other } = option
|
||||
return (
|
||||
<Com {...other} label={option[valueAlias || 'value']}>
|
||||
{option[labelAlias || 'label']}
|
||||
</Com>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
renderChcekboxOptions
|
||||
renderCheckboxOptions
|
||||
}
|
||||
}
|
||||
|
@ -11,13 +11,12 @@ export const useRenderRadio = () => {
|
||||
typeof defineComponent
|
||||
>
|
||||
return item?.componentProps?.options?.map((option) => {
|
||||
return <Com label={option[labelAlias || 'value']}>{option[valueAlias || 'label']}</Com>
|
||||
// const { value, ...other } = option
|
||||
// return (
|
||||
// <Com label={option[labelAlias || 'value']} {...other}>
|
||||
// {option[valueAlias || 'label']}
|
||||
// </Com>
|
||||
// )
|
||||
const { value, ...other } = option
|
||||
return (
|
||||
<Com {...other} label={option[valueAlias || 'value']}>
|
||||
{option[labelAlias || 'label']}
|
||||
</Com>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -36,9 +36,9 @@ export const useRenderSelect = (slots: Slots) => {
|
||||
|
||||
return (
|
||||
<ElOption
|
||||
{...other}
|
||||
label={labelAlias ? option[labelAlias] : label}
|
||||
value={valueAlias ? option[valueAlias] : value}
|
||||
{...other}
|
||||
>
|
||||
{{
|
||||
default: () =>
|
||||
|
@ -52,7 +52,6 @@ export default defineComponent({
|
||||
},
|
||||
emits: ['update:limit', 'update:page', 'register'],
|
||||
setup(props, { attrs, slots, emit, expose }) {
|
||||
console.log('attrs', attrs)
|
||||
const elTableRef = ref<ComponentRef<typeof ElTable>>()
|
||||
|
||||
// 注册
|
||||
|
@ -32,6 +32,14 @@ const toHome = () => {
|
||||
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
|
||||
</script>
|
||||
|
||||
@ -52,6 +60,12 @@ const user = authStore.getUser
|
||||
<ElDropdownItem>
|
||||
<ElButton @click="toHome" link>个人主页</ElButton>
|
||||
</ElDropdownItem>
|
||||
<ElDropdownItem>
|
||||
<ElButton @click="toGitee" link>Gitee</ElButton>
|
||||
</ElDropdownItem>
|
||||
<ElDropdownItem>
|
||||
<ElButton @click="toGithub" link>Github</ElButton>
|
||||
</ElDropdownItem>
|
||||
<ElDropdownItem divided>
|
||||
<ElButton @click="loginOut" link>退出系统</ElButton>
|
||||
</ElDropdownItem>
|
||||
|
@ -53,9 +53,8 @@ export const useAuthStore = defineStore('auth', {
|
||||
if (res) {
|
||||
wsCache.set(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`)
|
||||
// 存储用户信息
|
||||
wsCache.set(appStore.getUserInfo, res.data.user)
|
||||
this.user = res.data.user
|
||||
this.isUser = true
|
||||
const auth = useAuthStore()
|
||||
await auth.getUserInfo()
|
||||
}
|
||||
return res
|
||||
},
|
||||
|
183
kinit-admin/src/views/Dashboard/Map.vue
Normal file
183
kinit-admin/src/views/Dashboard/Map.vue
Normal 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, // 申请好的Web端开发者Key,首次调用 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, // 3D视图下,CircleMarker半径不要超过64px
|
||||
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>
|
@ -129,8 +129,8 @@ const signIn = async () => {
|
||||
loading.value = true
|
||||
const { getFormData } = methods
|
||||
const formData = await getFormData<UserLoginType>()
|
||||
try {
|
||||
const authStore = useAuthStoreWithOut()
|
||||
try {
|
||||
const res = await authStore.login(formData)
|
||||
if (res) {
|
||||
if (!res.data.is_reset_password) {
|
||||
@ -140,8 +140,10 @@ const signIn = async () => {
|
||||
// 是否使用动态路由
|
||||
getMenu()
|
||||
}
|
||||
} else {
|
||||
loading.value = false
|
||||
}
|
||||
} finally {
|
||||
} catch (e: any) {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
@ -97,8 +97,8 @@ const telephoneCodeLogin = async () => {
|
||||
loading.value = true
|
||||
const { getFormData } = methods
|
||||
const formData = await getFormData<UserLoginType>()
|
||||
try {
|
||||
const authStore = useAuthStoreWithOut()
|
||||
try {
|
||||
const res = await authStore.login(formData)
|
||||
if (res) {
|
||||
if (!res.data.is_reset_password) {
|
||||
@ -108,8 +108,10 @@ const telephoneCodeLogin = async () => {
|
||||
// 是否使用动态路由
|
||||
getMenu()
|
||||
}
|
||||
} else {
|
||||
loading.value = false
|
||||
}
|
||||
} finally {
|
||||
} catch (e: any) {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
@ -127,6 +129,7 @@ const getSMSCode = async () => {
|
||||
SMSCodeNumber.value = 60
|
||||
const { getFormData } = methods
|
||||
const formData = await getFormData<UserLoginType>()
|
||||
try {
|
||||
const res = await postSMSCodeApi({ telephone: formData.telephone })
|
||||
if (res?.data) {
|
||||
let timer = setInterval(() => {
|
||||
@ -140,6 +143,9 @@ const getSMSCode = async () => {
|
||||
ElMessage.error('发送失败,请联系管理员')
|
||||
SMSCodeStatus.value = true
|
||||
}
|
||||
} catch (e: any) {
|
||||
SMSCodeStatus.value = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -107,8 +107,10 @@ const save = async () => {
|
||||
if (res) {
|
||||
// 是否使用动态路由
|
||||
getMenu()
|
||||
} else {
|
||||
loading.value = false
|
||||
}
|
||||
} finally {
|
||||
} catch (e: any) {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { getMenuTreeOptionsApi } from '@/api/vadmin/auth/menu'
|
||||
import { ElButton, ElInput } from 'element-plus'
|
||||
import { schema } from './menu.data'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
|
||||
const { required } = useValidator()
|
||||
|
||||
@ -13,7 +14,8 @@ const props = defineProps({
|
||||
currentRow: {
|
||||
type: Object as PropType<Nullable<any>>,
|
||||
default: () => null
|
||||
}
|
||||
},
|
||||
parentId: propTypes.number.def(undefined)
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
@ -53,6 +55,10 @@ const getMenuTreeOptions = async () => {
|
||||
value: res.data
|
||||
}
|
||||
])
|
||||
if (props.parentId) {
|
||||
const { setValue } = methods
|
||||
setValue('parent_id', props.parentId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ export const columns = reactive<TableColumn[]>([
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
width: '150px',
|
||||
width: '200px',
|
||||
label: '操作',
|
||||
show: true
|
||||
}
|
||||
@ -77,7 +77,9 @@ export const schema = reactive<FormSchema[]>([
|
||||
width: '100%'
|
||||
},
|
||||
checkStrictly: true,
|
||||
placeholder: '请选择上级菜单'
|
||||
placeholder: '请选择上级菜单',
|
||||
nodeKey: 'value',
|
||||
defaultExpandAll: true
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -130,6 +132,11 @@ export const schema = reactive<FormSchema[]>([
|
||||
component: 'InputNumber',
|
||||
colProps: {
|
||||
span: 12
|
||||
},
|
||||
componentProps: {
|
||||
style: {
|
||||
width: '100%'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -216,6 +223,6 @@ export const schema = reactive<FormSchema[]>([
|
||||
colProps: {
|
||||
span: 12
|
||||
},
|
||||
ifshow: (values) => values.menu_type !== '0'
|
||||
ifshow: (values) => values.menu_type === '2'
|
||||
}
|
||||
])
|
||||
|
@ -45,9 +45,11 @@ const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
const delLoading = ref(false)
|
||||
const actionType = ref('')
|
||||
const parentId = ref()
|
||||
|
||||
// 添加事件
|
||||
const AddAction = () => {
|
||||
parentId.value = null
|
||||
dialogTitle.value = t('exampleDemo.add')
|
||||
tableObject.currentRow = null
|
||||
dialogVisible.value = true
|
||||
@ -56,6 +58,7 @@ const AddAction = () => {
|
||||
|
||||
// 编辑事件
|
||||
const updateAction = (row: any) => {
|
||||
parentId.value = null
|
||||
dialogTitle.value = '编辑'
|
||||
tableObject.currentRow = row
|
||||
dialogVisible.value = true
|
||||
@ -64,6 +67,7 @@ const updateAction = (row: any) => {
|
||||
|
||||
// 删除事件
|
||||
const delData = async (row: any) => {
|
||||
parentId.value = null
|
||||
tableObject.currentRow = row
|
||||
const { delListApi } = methods
|
||||
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 writeRef = ref<ComponentRef<typeof Write>>()
|
||||
@ -124,7 +137,9 @@ watch(
|
||||
<div class="mb-8px flex justify-between">
|
||||
<ElRow :gutter="10">
|
||||
<ElCol :span="1.5">
|
||||
<ElButton type="primary" @click="AddAction">新增菜单</ElButton>
|
||||
<ElButton type="primary" v-hasPermi="['auth.menu.create']" @click="AddAction"
|
||||
>新增菜单</ElButton
|
||||
>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<RightToolbar @get-list="getList" v-model:table-size="tableSize" v-model:columns="columns" />
|
||||
@ -149,10 +164,31 @@ watch(
|
||||
</div>
|
||||
</template>
|
||||
<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') }}
|
||||
</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') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
@ -173,7 +209,7 @@ watch(
|
||||
</Table>
|
||||
|
||||
<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>
|
||||
<ElButton type="primary" :loading="loading" @click="save">
|
||||
|
@ -5,7 +5,7 @@ import { PropType, reactive, watch, ref } from 'vue'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { getMenuRoleTreeOptionsApi } from '@/api/vadmin/auth/menu'
|
||||
import { schema } from './role.data'
|
||||
import { ElTree } from 'element-plus'
|
||||
import { ElTree, ElCheckbox } from 'element-plus'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const { required } = useValidator()
|
||||
@ -50,7 +50,7 @@ const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'label'
|
||||
}
|
||||
let data = ref([])
|
||||
let data = ref([] as Recordable[])
|
||||
|
||||
const getMenuRoleTreeOptions = async () => {
|
||||
const res = await getMenuRoleTreeOptionsApi()
|
||||
@ -72,23 +72,72 @@ defineExpose({
|
||||
getFormData: methods.getFormData,
|
||||
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>
|
||||
|
||||
<template>
|
||||
<Form :rules="rules" @register="register">
|
||||
<template #menu_ids>
|
||||
<div>
|
||||
<div>
|
||||
<ElCheckbox
|
||||
v-model="defaultExpandAll"
|
||||
@change="handleCheckedTreeExpand"
|
||||
label="展开/折叠"
|
||||
size="large"
|
||||
/>
|
||||
<ElCheckbox
|
||||
v-model="selectAll"
|
||||
@change="handleCheckedTreeNodeAll"
|
||||
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>
|
||||
</Form>
|
||||
</template>
|
||||
|
@ -53,6 +53,7 @@ const updateAction = async (row: any) => {
|
||||
dialogTitle.value = '编辑'
|
||||
tableObject.currentRow = res.data
|
||||
defaultCheckedKeys.value = res.data.menus.map((item: any) => item.id)
|
||||
console.log(defaultCheckedKeys.value)
|
||||
dialogVisible.value = true
|
||||
actionType.value = 'edit'
|
||||
}
|
||||
@ -123,7 +124,7 @@ watch(
|
||||
|
||||
<div class="mb-8px flex justify-between">
|
||||
<ElRow :gutter="10">
|
||||
<ElCol :span="1.5">
|
||||
<ElCol :span="1.5" v-hasPermi="['auth.role.create']">
|
||||
<ElButton type="primary" @click="AddAction">新增角色</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
@ -144,10 +145,24 @@ watch(
|
||||
@register="register"
|
||||
>
|
||||
<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') }}
|
||||
</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') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
@ -161,7 +176,7 @@ watch(
|
||||
</template>
|
||||
</Table>
|
||||
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="700px">
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="700px" maxHeight="600px">
|
||||
<Write
|
||||
ref="writeRef"
|
||||
:current-row="tableObject.currentRow"
|
||||
|
@ -179,19 +179,19 @@ const sendPasswordToSMS = async () => {
|
||||
|
||||
<div class="mb-8px flex justify-between">
|
||||
<ElRow :gutter="10">
|
||||
<ElCol :span="1.5">
|
||||
<ElCol :span="1.5" v-hasPermi="['auth.user.create']">
|
||||
<ElButton type="primary" @click="AddAction">新增用户</ElButton>
|
||||
</ElCol>
|
||||
<ElCol :span="1.5">
|
||||
<ElCol :span="1.5" v-hasPermi="['auth.user.import']">
|
||||
<ElButton @click="importList">批量导入用户</ElButton>
|
||||
</ElCol>
|
||||
<ElCol :span="1.5">
|
||||
<ElCol :span="1.5" v-hasPermi="['auth.user.export']">
|
||||
<ElButton @click="exportQueryList">导出筛选用户</ElButton>
|
||||
</ElCol>
|
||||
<ElCol :span="1.5">
|
||||
<ElCol :span="1.5" v-hasPermi="['auth.user.reset']">
|
||||
<ElButton @click="sendPasswordToSMS">重置密码通知短信</ElButton>
|
||||
</ElCol>
|
||||
<ElCol :span="1.5">
|
||||
<ElCol :span="1.5" v-hasPermi="['auth.user.delete']">
|
||||
<ElButton type="danger" @click="delDatas(null, true)">批量删除</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
@ -212,11 +212,18 @@ const sendPasswordToSMS = async () => {
|
||||
@register="register"
|
||||
>
|
||||
<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') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="danger"
|
||||
v-hasPermi="['auth.user.delete']"
|
||||
link
|
||||
size="small"
|
||||
@click="delDatas(row, false)"
|
||||
|
@ -273,7 +273,7 @@ class MenuDal(DalBase):
|
||||
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:
|
||||
sql = select(self.model)
|
||||
queryset = await self.db.execute(sql)
|
||||
|
@ -10,20 +10,16 @@ from apps.vadmin.auth import crud, models
|
||||
from .validation import AuthValidation, Auth
|
||||
|
||||
|
||||
async def get_user_permissions(user: models.VadminUser, db: AsyncSession):
|
||||
async def get_user_permissions(user):
|
||||
"""
|
||||
获取跟进系统用户所有权限列表
|
||||
"""
|
||||
roles = []
|
||||
for i in user.roles:
|
||||
if i.is_admin:
|
||||
return ["*:*:*"]
|
||||
roles.append(i.id)
|
||||
if any([role.is_admin for role in user.roles]):
|
||||
return ['*.*.*']
|
||||
permissions = set()
|
||||
for data_id in roles:
|
||||
role_obj = await crud.RoleDal(db).get_data(data_id, options=[models.VadminUser])
|
||||
for role_obj in user.roles:
|
||||
for menu in role_obj.menus:
|
||||
if menu.perms and menu.status:
|
||||
if menu.perms and not menu.disabled:
|
||||
permissions.add(menu.perms)
|
||||
return list(permissions)
|
||||
|
||||
|
@ -5,7 +5,6 @@
|
||||
# @IDE : PyCharm
|
||||
# @desc : 安全认证视图
|
||||
|
||||
|
||||
"""
|
||||
JWT 表示 「JSON Web Tokens」。https://jwt.io/
|
||||
|
||||
@ -16,13 +15,13 @@ JWT 表示 「JSON Web Tokens」。https://jwt.io/
|
||||
|
||||
我们需要安装 python-jose 以在 Python 中生成和校验 JWT 令牌:pip install python-jose[cryptography]
|
||||
|
||||
PassLib 是一个用于处理哈希密码的很棒的 Python 包。它支持许多安全哈希算法以及配合算法使用的实用程序。推荐的算法是 「Bcrypt」:pip install passlib[bcrypt]
|
||||
PassLib 是一个用于处理哈希密码的很棒的 Python 包。它支持许多安全哈希算法以及配合算法使用的实用程序。
|
||||
推荐的算法是 「Bcrypt」:pip install passlib[bcrypt]
|
||||
"""
|
||||
import json
|
||||
|
||||
from datetime import timedelta
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from core.database import db_getter
|
||||
from utils.response import SuccessResponse, ErrorResponse
|
||||
from application import settings
|
||||
@ -45,33 +44,22 @@ async def login_for_access_token(request: Request, data: LoginForm, manage: Logi
|
||||
else:
|
||||
return ErrorResponse(msg="请使用正确的登录方式")
|
||||
if not result.status:
|
||||
res = {"message": result.msg}
|
||||
resp = {"message": result.msg}
|
||||
telephone = data.telephone
|
||||
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)
|
||||
|
||||
user = result.user
|
||||
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)
|
||||
res = {
|
||||
resp = {
|
||||
"access_token": access_token,
|
||||
"token_type": "bearer",
|
||||
"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]
|
||||
"is_reset_password": user.is_reset_password
|
||||
}
|
||||
}
|
||||
await VadminLoginRecord.\
|
||||
create_login_record(telephone=user.telephone, status=result.status, request=request, response=res, db=db)
|
||||
return SuccessResponse(res)
|
||||
await VadminLoginRecord.create_login_record(db, user.telephone, result.status, request, resp)
|
||||
return SuccessResponse(resp)
|
||||
|
||||
|
||||
@app.get("/getMenuList/", summary="获取当前用户菜单树")
|
||||
|
@ -44,7 +44,7 @@ class LoginValidation:
|
||||
|
||||
async def __call__(self, data: LoginForm, db: AsyncSession, request: Request) -> 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)
|
||||
if not user:
|
||||
self.result.msg = "该手机号不存在!"
|
||||
|
@ -9,8 +9,8 @@
|
||||
from fastapi import APIRouter, Depends, Body, UploadFile, Request
|
||||
from utils.response import SuccessResponse, ErrorResponse
|
||||
from . import schemas, crud, models
|
||||
from core.dependencies import Paging, IdList
|
||||
from apps.vadmin.auth.utils.current import login_auth, Auth
|
||||
from core.dependencies import IdList
|
||||
from apps.vadmin.auth.utils.current import login_auth, Auth, get_user_permissions, full_admin
|
||||
from .params import UserParams, RoleParams
|
||||
|
||||
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="获取当前用户基本信息")
|
||||
async def get_user_current_info(auth: Auth = Depends(login_auth)):
|
||||
return SuccessResponse(schemas.UserSimpleOut.from_orm(auth.user).dict())
|
||||
async def get_user_current_info(auth: Auth = Depends(full_admin)):
|
||||
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="获取当前用户基本信息")
|
||||
|
@ -5,6 +5,8 @@
|
||||
# @File : crud.py
|
||||
# @IDE : PyCharm
|
||||
# @desc : 数据库 增删改查操作
|
||||
import random
|
||||
from typing import List
|
||||
|
||||
# sqlalchemy 查询操作:https://segmentfault.com/a/1190000016767008
|
||||
# sqlalchemy 关联查询:https://www.jianshu.com/p/dfad7c08c57a
|
||||
@ -15,9 +17,56 @@ from core.crud import DalBase
|
||||
|
||||
|
||||
class LoginRecordDal(DalBase):
|
||||
|
||||
def __init__(self, db: AsyncSession):
|
||||
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):
|
||||
def __init__(self, db: AsyncSession):
|
||||
|
@ -35,24 +35,22 @@ class VadminLoginRecord(BaseModel):
|
||||
request = Column(TEXT, comment="请求信息")
|
||||
|
||||
@classmethod
|
||||
async def create_login_record(cls, telephone: str, status: bool, request: Request, response: dict,
|
||||
db: AsyncSession):
|
||||
async def create_login_record(cls, db: AsyncSession, telephone: str, status: bool, req: Request, resp: dict):
|
||||
"""
|
||||
创建登录记录
|
||||
@return:
|
||||
"""
|
||||
header = {}
|
||||
for k, v in request.headers.items():
|
||||
for k, v in req.headers.items():
|
||||
header[k] = v
|
||||
body = json.loads((await request.body()).decode())
|
||||
user_agent = parse(request.headers.get("user-agent"))
|
||||
body = json.loads((await req.body()).decode())
|
||||
user_agent = parse(req.headers.get("user-agent"))
|
||||
system = f"{user_agent.os.family} {user_agent.os.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()
|
||||
resp = json.dumps(response)
|
||||
resq = json.dumps({"body": body, "headers": header})
|
||||
params = json.dumps({"body": body, "headers": header})
|
||||
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)
|
||||
await db.flush()
|
||||
|
@ -5,9 +5,7 @@
|
||||
# @IDE : PyCharm
|
||||
# @desc : 主要接口文件
|
||||
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from core.dependencies import Paging, IdList
|
||||
from fastapi import APIRouter, Depends
|
||||
from utils.response import SuccessResponse
|
||||
from . import crud, schemas
|
||||
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())
|
||||
count = await crud.SMSSendRecordDal(auth.db).get_count(**params.to_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())
|
||||
|
@ -22,14 +22,12 @@ pip install alibabacloud_dysmsapi20170525
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
import uuid
|
||||
from enum import Enum, unique
|
||||
from core.exception import CustomException
|
||||
from alibabacloud_dysmsapi20170525.client import Client as Dysmsapi20170525Client
|
||||
from alibabacloud_tea_openapi import models as open_api_models
|
||||
from alibabacloud_dysmsapi20170525 import models as dysmsapi_20170525_models
|
||||
from alibabacloud_tea_util import models as util_models
|
||||
from alibabacloud_tea_util.client import Client as UtilClient
|
||||
from core.logger import logger
|
||||
import datetime
|
||||
from aioredis.client import Redis
|
||||
@ -44,8 +42,8 @@ class AliyunSMS:
|
||||
|
||||
@unique
|
||||
class Scene(Enum):
|
||||
login = "template_code_1"
|
||||
reset_password = "template_code_2"
|
||||
login = "sms_template_code_1"
|
||||
reset_password = "sms_template_code_2"
|
||||
|
||||
def __init__(self, rd: Redis, telephone: str):
|
||||
self.check_telephone_format(telephone)
|
||||
@ -67,14 +65,14 @@ class AliyunSMS:
|
||||
raise CustomException("获取短信配置信息失败,请联系管理员!", code=status.HTTP_ERROR)
|
||||
else:
|
||||
aliyun_sms = json.loads(aliyun_sms)
|
||||
self.access_key = aliyun_sms.get("access_key")
|
||||
self.access_key_secret = aliyun_sms.get("access_key_secret")
|
||||
self.send_interval = int(aliyun_sms.get("send_interval"))
|
||||
self.valid_time = int(aliyun_sms.get("valid_time"))
|
||||
self.access_key = aliyun_sms.get("sms_access_key")
|
||||
self.access_key_secret = aliyun_sms.get("sms_access_key_secret")
|
||||
self.send_interval = int(aliyun_sms.get("sms_send_interval"))
|
||||
self.valid_time = int(aliyun_sms.get("sms_valid_time"))
|
||||
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:
|
||||
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)
|
||||
|
||||
async def main_async(self, scene: Scene, **kwargs) -> bool:
|
||||
|
Loading…
x
Reference in New Issue
Block a user