1. 修复:移动端头像剪切功能

2. 更新:升级PC端vue-element-plus-admin版本到1.9.2
3. 更新:文件上传接口
4. 新增:kinit-api 命令创建app目录
This commit is contained in:
ktianc 2023-01-28 12:00:23 +08:00
parent 36079a5055
commit 5abaef08cc
50 changed files with 1223 additions and 680 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
# vue-element-plus-admin # vue-element-plus-admin
vue-element-plus-admin 是一个基于 `element-plus` 免费开源的中后台模版。使用了最新的`vue3``vite3``TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,可以用来作为项目的启动模版,也可用于学习参考。并且时刻关注着最新技术动向,尽可能的第一时间更新。 vue-element-plus-admin 是一个基于 `element-plus` 免费开源的中后台模版。使用了最新的`vue3``vite4``TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,可以用来作为项目的启动模版,也可用于学习参考。并且时刻关注着最新技术动向,尽可能的第一时间更新。
## 特性 ## 特性
- **最新技术栈**:使用 Vue3/vite3 等前端前沿技术开发 - **最新技术栈**:使用 Vue3/vite4 等前端前沿技术开发
- **TypeScript**: 应用程序级 JavaScript 的语言 - **TypeScript**: 应用程序级 JavaScript 的语言
- **主题**: 可配置的主题 - **主题**: 可配置的主题
- **国际化**:内置完善的国际化方案 - **国际化**:内置完善的国际化方案

View File

@ -1,7 +1,7 @@
{ {
"name": "vue-element-plus-admin", "name": "vue-element-plus-admin",
"version": "1.8.7", "version": "1.9.2",
"description": "一套基于vue3、element-plus、typesScript、vite3的后台集成方案。", "description": "一套基于vue3、element-plus、typesScript、vite4的后台集成方案。",
"author": "Archer <502431556@qq.com>", "author": "Archer <502431556@qq.com>",
"private": false, "private": false,
"scripts": { "scripts": {
@ -25,88 +25,88 @@
}, },
"dependencies": { "dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1", "@amap/amap-jsapi-loader": "^1.0.1",
"@iconify/iconify": "^3.0.0", "@iconify/iconify": "^3.0.1",
"@kjgl77/datav-vue3": "^1.3.3", "@kjgl77/datav-vue3": "^1.4.2",
"@vueuse/core": "^9.5.0", "@vueuse/core": "^9.10.0",
"@wangeditor/editor": "^5.1.23", "@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",
"axios": "^1.1.3", "axios": "^1.2.2",
"clipboard": "^2.0.11", "echarts": "^5.4.1",
"consola": "^2.15.3", "echarts-wordcloud": "^2.1.0",
"echarts": "^5.4.0", "element-plus": "2.2.28",
"echarts-wordcloud": "^2.0.0",
"element-plus": "2.2.21",
"intro.js": "^6.0.0", "intro.js": "^6.0.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mitt": "^3.0.0", "mitt": "^3.0.0",
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
"moment": "^2.29.4", "moment": "^2.29.4",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.0.23", "pinia": "^2.0.29",
"pinia-plugin-persist": "^1.0.0",
"qrcode": "^1.5.1", "qrcode": "^1.5.1",
"qs": "^6.11.0", "qs": "^6.11.0",
"terser": "^5.16.1",
"url": "^0.11.0", "url": "^0.11.0",
"vue": "3.2.45", "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": "^5.0.2",
"vue3-json-viewer": "^2.2.2", "vue3-json-viewer": "^2.2.2",
"vuedraggable": "4.1.0", "vuedraggable": "4.1.0",
"web-storage-cache": "^1.1.1" "web-storage-cache": "^1.1.1"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^17.2.0", "@commitlint/cli": "^17.4.2",
"@commitlint/config-conventional": "^17.2.0", "@commitlint/config-conventional": "^17.4.2",
"@iconify/json": "^2.1.139", "@iconify/json": "^2.2.7",
"@intlify/vite-plugin-vue-i18n": "^6.0.3", "@intlify/unplugin-vue-i18n": "^0.8.1",
"@purge-icons/generated": "^0.9.0", "@purge-icons/generated": "^0.9.0",
"@types/intro.js": "^5.1.0", "@types/intro.js": "^5.1.0",
"@types/lodash-es": "^4.17.6", "@types/lodash-es": "^4.17.6",
"@types/node": "^18.11.9", "@types/node": "^18.11.18",
"@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.43.0", "@typescript-eslint/eslint-plugin": "^5.48.1",
"@typescript-eslint/parser": "^5.43.0", "@typescript-eslint/parser": "^5.48.1",
"@vitejs/plugin-vue": "^3.2.0", "@vitejs/plugin-legacy": "^3.0.1",
"@vitejs/plugin-vue-jsx": "^2.1.1", "@vitejs/plugin-vue": "^4.0.0",
"@vitejs/plugin-vue-jsx": "^3.0.0",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"eslint": "^8.27.0", "consola": "^2.15.3",
"eslint-config-prettier": "^8.5.0", "eslint": "^8.32.0",
"eslint-define-config": "^1.12.0", "eslint-config-prettier": "^8.6.0",
"eslint-define-config": "^1.14.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.7.0", "eslint-plugin-vue": "^9.9.0",
"husky": "^8.0.2", "husky": "^8.0.3",
"less": "^4.1.3", "less": "^4.1.3",
"lint-staged": "^13.0.3", "lint-staged": "^13.1.0",
"plop": "^3.1.1", "plop": "^3.1.1",
"postcss": "^8.4.19", "postcss": "^8.4.21",
"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.8.3",
"rimraf": "^3.0.2", "rimraf": "^4.0.7",
"rollup": "^3.3.0", "rollup": "^3.10.0",
"stylelint": "^14.15.0", "stylelint": "^14.16.1",
"stylelint-config-html": "^1.1.0", "stylelint-config-html": "^1.1.0",
"stylelint-config-prettier": "^9.0.4", "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": "^6.0.1",
"typescript": "4.9.3", "terser": "^5.16.1",
"unplugin-vue-macros": "^0.16.3", "typescript": "4.9.4",
"vite": "3.2.4", "unplugin-vue-define-options": "^1.1.4",
"vite": "4.0.4",
"vite-plugin-ejs": "^1.6.4", "vite-plugin-ejs": "^1.6.4",
"vite-plugin-eslint": "^1.8.1", "vite-plugin-eslint": "^1.8.1",
"vite-plugin-mock": "^2.9.6", "vite-plugin-mock": "^2.9.6",
"vite-plugin-purge-icons": "^0.9.1", "vite-plugin-progress": "^0.0.6",
"vite-plugin-purge-icons": "^0.9.2",
"vite-plugin-style-import": "2.0.0", "vite-plugin-style-import": "2.0.0",
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-windicss": "^1.8.8", "vite-plugin-windicss": "^1.8.10",
"vue-tsc": "^1.0.9", "vue-tsc": "^1.0.24",
"windicss": "^3.5.6", "windicss": "^3.5.6",
"windicss-analysis": "^0.3.5" "windicss-analysis": "^0.3.5"
}, },

View File

@ -47,7 +47,7 @@ const { wsCache } = useCache()
// //
const setDefaultTheme = () => { const setDefaultTheme = () => {
if (wsCache.get('isDark')) { if (wsCache.get('isDark') !== null) {
appStore.setIsDark(wsCache.get('isDark')) appStore.setIsDark(wsCache.get('isDark'))
return return
} }
@ -68,7 +68,7 @@ setDefaultTheme()
@prefix-cls: ~'@{namespace}-app'; @prefix-cls: ~'@{namespace}-app';
.size { .size {
width: 100%; width: 100% !important;
height: 100%; height: 100%;
} }

View File

@ -14,7 +14,7 @@ const { variables } = useDesign()
const appStore = useAppStore() const appStore = useAppStore()
const props = defineProps({ const props = defineProps({
size: propTypes.oneOf<ElementPlusSize[]>(['default', 'small', 'large']).def('default') size: propTypes.oneOf<ElementPlusSize>(['default', 'small', 'large']).def('default')
}) })
provide('configGlobal', props) provide('configGlobal', props)

View File

@ -23,7 +23,7 @@ const props = defineProps({
default: () => [] default: () => []
}, },
data: { data: {
type: Object as PropType<Recordable>, type: Object as PropType<any>,
default: () => ({}) default: () => ({})
} }
}) })

View File

@ -50,6 +50,7 @@ watch(
) )
const dialogStyle = computed(() => { const dialogStyle = computed(() => {
console.log(unref(dialogHeight))
return { return {
height: unref(dialogHeight) height: unref(dialogHeight)
} }
@ -63,6 +64,7 @@ const dialogStyle = computed(() => {
destroy-on-close destroy-on-close
lock-scroll lock-scroll
draggable draggable
align-center
:close-on-click-modal="false" :close-on-click-modal="false"
> >
<template #header> <template #header>

View File

@ -57,31 +57,33 @@ watch(
</script> </script>
<template> <template>
<router-link <div>
:class="[ <router-link
prefixCls,
layout !== 'classic' ? `${prefixCls}__Top` : '',
'flex !h-[var(--logo-height)] items-center cursor-pointer pl-8px relative',
'dark:bg-[var(--el-bg-color)]'
]"
to="/"
>
<img
:src="logoImage"
class="w-[calc(var(--logo-height)-10px)] h-[calc(var(--logo-height)-10px)]"
/>
<div
v-if="show"
:class="[ :class="[
'ml-10px text-16px font-700', prefixCls,
{ layout !== 'classic' ? `${prefixCls}__Top` : '',
'text-[var(--logo-title-text-color)]': layout === 'classic', 'flex !h-[var(--logo-height)] items-center cursor-pointer pl-8px relative',
'text-[var(--top-header-text-color)]': 'dark:bg-[var(--el-bg-color)]'
layout === 'topLeft' || layout === 'top' || layout === 'cutMenu'
}
]" ]"
to="/"
> >
{{ title }} <img
</div> :src="logoImage"
</router-link> class="w-[calc(var(--logo-height)-10px)] h-[calc(var(--logo-height)-10px)]"
/>
<div
v-if="show"
:class="[
'ml-10px text-16px font-700',
{
'text-[var(--logo-title-text-color)]': layout === 'classic',
'text-[var(--top-header-text-color)]':
layout === 'topLeft' || layout === 'top' || layout === 'cutMenu'
}
]"
>
{{ title }}
</div>
</router-link>
</div>
</template> </template>

View File

@ -220,10 +220,10 @@ export default defineComponent({
// //
&__horizontal { &__horizontal {
height: calc(~'var( - -top-tool-height)') !important; height: calc(~'var(--top-tool-height)') !important;
:deep(.@{elNamespace}-menu--horizontal) { :deep(.@{elNamespace}-menu--horizontal) {
height: calc(~'var( - -top-tool-height)'); height: calc(~'var(--top-tool-height)');
border-bottom: none; border-bottom: none;
// //
& > .@{elNamespace}-sub-menu.is-active { & > .@{elNamespace}-sub-menu.is-active {

View File

@ -137,7 +137,7 @@ const moveToCurrentTag = async () => {
const tagLinksRefs = useTemplateRefsList<RouterLinkProps>() const tagLinksRefs = useTemplateRefsList<RouterLinkProps>()
const moveToTarget = (currentTag: RouteLocationNormalizedLoaded) => { const moveToTarget = (currentTag: RouteLocationNormalizedLoaded) => {
const wrap$ = unref(scrollbarRef)?.wrap$ const wrap$ = unref(scrollbarRef)?.wrapRef
let firstTag: Nullable<RouterLinkProps> = null let firstTag: Nullable<RouterLinkProps> = null
let lastTag: Nullable<RouterLinkProps> = null let lastTag: Nullable<RouterLinkProps> = null
@ -233,7 +233,7 @@ const scroll = ({ scrollLeft }) => {
// //
const move = (to: number) => { const move = (to: number) => {
const wrap$ = unref(scrollbarRef)?.wrap$ const wrap$ = unref(scrollbarRef)?.wrapRef
const { start } = useScrollTo({ const { start } = useScrollTo({
el: wrap$!, el: wrap$!,
position: 'scrollLeft', position: 'scrollLeft',

View File

@ -13,7 +13,7 @@ const { t } = useI18n()
const authStore = useAuthStoreWithOut() const authStore = useAuthStoreWithOut()
const { replace, push } = useRouter() const { push } = useRouter()
const loginOut = () => { const loginOut = () => {
ElMessageBox.confirm(t('common.loginOutMessage'), t('common.reminder'), { ElMessageBox.confirm(t('common.loginOutMessage'), t('common.reminder'), {

View File

@ -8,6 +8,7 @@ const TIME_AGO_MESSAGE_MAP: {
} = { } = {
'zh-CN': { 'zh-CN': {
justNow: '刚刚', justNow: '刚刚',
invalid: '无效时间',
past: (n) => (n.match(/\d/) ? `${n}` : n), past: (n) => (n.match(/\d/) ? `${n}` : n),
future: (n) => (n.match(/\d/) ? `${n}` : n), future: (n) => (n.match(/\d/) ? `${n}` : n),
month: (n, past) => (n === 1 ? (past ? '上个月' : '下个月') : `${n} 个月`), month: (n, past) => (n === 1 ? (past ? '上个月' : '下个月') : `${n} 个月`),
@ -20,6 +21,7 @@ const TIME_AGO_MESSAGE_MAP: {
}, },
en: { en: {
justNow: '刚刚', justNow: '刚刚',
invalid: 'Invalid Date',
past: (n) => (n.match(/\d/) ? `${n} ago` : n), past: (n) => (n.match(/\d/) ? `${n} ago` : n),
future: (n) => (n.match(/\d/) ? `in ${n}` : n), future: (n) => (n.match(/\d/) ? `in ${n}` : n),
month: (n, past) => month: (n, past) =>

View File

@ -1,11 +1,8 @@
import type { App } from 'vue' import type { App } from 'vue'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persist' // 持久化存储
const store = createPinia() const store = createPinia()
store.use(piniaPluginPersist)
export const setupStore = (app: App<Element>) => { export const setupStore = (app: App<Element>) => {
app.use(store) app.use(store)
} }

View File

@ -35,10 +35,6 @@ export const useAuthStore = defineStore('auth', {
isUser: false isUser: false
} }
}, },
persist: {
// 开启持久化存储
enabled: true
},
getters: { getters: {
getUser(): UserState { getUser(): UserState {
return this.user return this.user

View File

@ -10,10 +10,6 @@ export const useDictStore = defineStore('dict', {
state: (): DictState => ({ state: (): DictState => ({
dictObj: {} dictObj: {}
}), }),
persist: {
// 开启持久化存储
enabled: true
},
getters: {}, getters: {},
actions: { actions: {
async getDictObj(dictTypes: string[]) { async getDictObj(dictTypes: string[]) {

View File

@ -18,10 +18,6 @@ export const usePermissionStore = defineStore('permission', {
isAddRouters: false, isAddRouters: false,
menuTabRouters: [] menuTabRouters: []
}), }),
persist: {
// 开启持久化存储
enabled: true
},
getters: { getters: {
getRouters(): AppRouteRecordRaw[] { getRouters(): AppRouteRecordRaw[] {
return this.routers return this.routers

View File

@ -1,10 +1,9 @@
import { EChartsOption } from 'echarts' import { EChartsOption } from 'echarts'
import { EChartsOption as EChartsWordOption } from 'echarts-wordcloud'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n() const { t } = useI18n()
export const lineOptions: EChartsOption = { export const lineOptions = {
title: { title: {
text: t('analysis.monthlySales'), text: t('analysis.monthlySales'),
left: 'center' left: 'center'
@ -185,7 +184,7 @@ export const radarOption: EChartsOption = {
] ]
} }
export const wordOptions: EChartsWordOption = { export const wordOptions = {
series: [ series: [
{ {
type: 'wordCloud', type: 'wordCloud',

View File

@ -49,13 +49,21 @@ const { setValues, setValue } = methods
// //
const handleICOUploadSuccess: UploadProps['onSuccess'] = (response) => { const handleICOUploadSuccess: UploadProps['onSuccess'] = (response) => {
setValue('web_ico', response.data.remote_path) if (response.code === 200) {
setValue('web_ico_local_path', response.data.local_path) setValue('web_ico', response.data.remote_path)
setValue('web_ico_local_path', response.data.local_path)
} else {
ElMessage.error(response.message)
}
} }
// //
const handleLogoUploadSuccess: UploadProps['onSuccess'] = (response) => { const handleLogoUploadSuccess: UploadProps['onSuccess'] = (response) => {
setValue('web_logo', response.data.remote_path) if (response.code === 200) {
setValue('web_logo', response.data.remote_path)
} else {
ElMessage.error(response.message)
}
} }
let formData = ref({} as Recordable) let formData = ref({} as Recordable)

View File

@ -24,13 +24,13 @@
"@/*": ["src/*"] "@/*": ["src/*"]
}, },
"types": [ "types": [
"@intlify/vite-plugin-vue-i18n/client", "@intlify/unplugin-vue-i18n/types",
"vite/client", "vite/client",
"element-plus/global", "element-plus/global",
"@types/intro.js", "@types/intro.js",
"@types/qrcode", "@types/qrcode",
"vite-plugin-svg-icons/client", "vite-plugin-svg-icons/client",
"unplugin-vue-macros/macros-global" "unplugin-vue-define-options/macros-global"
], ],
"typeRoots": ["./node_modules/@types/", "./types"] "typeRoots": ["./node_modules/@types/", "./types"]
}, },

View File

@ -3,14 +3,14 @@ import { loadEnv } from 'vite'
import type { UserConfig, ConfigEnv } from 'vite' import type { UserConfig, ConfigEnv } from 'vite'
import Vue from '@vitejs/plugin-vue' import Vue from '@vitejs/plugin-vue'
import WindiCSS from 'vite-plugin-windicss' import WindiCSS from 'vite-plugin-windicss'
import progress from 'vite-plugin-progress'
import VueJsx from '@vitejs/plugin-vue-jsx' import VueJsx from '@vitejs/plugin-vue-jsx'
import EslintPlugin from 'vite-plugin-eslint' import EslintPlugin from 'vite-plugin-eslint'
import VueI18n from '@intlify/vite-plugin-vue-i18n'
import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import PurgeIcons from 'vite-plugin-purge-icons' import PurgeIcons from 'vite-plugin-purge-icons'
import { viteMockServe } from 'vite-plugin-mock' import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite"
import VueMarcos from 'unplugin-vue-macros/vite' import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import DefineOptions from "unplugin-vue-define-options/vite"
import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import'
import { ViteEjsPlugin } from "vite-plugin-ejs" import { ViteEjsPlugin } from "vite-plugin-ejs"
// https://vitejs.dev/config/ // https://vitejs.dev/config/
@ -34,6 +34,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
Vue(), Vue(),
VueJsx(), VueJsx(),
WindiCSS(), WindiCSS(),
progress(),
createStyleImportPlugin({ createStyleImportPlugin({
resolves: [ElementPlusResolve()], resolves: [ElementPlusResolve()],
libs: [{ libs: [{
@ -48,7 +49,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
cache: false, cache: false,
include: ['src/**/*.vue', 'src/**/*.ts', 'src/**/*.tsx'] // 检查的文件 include: ['src/**/*.vue', 'src/**/*.ts', 'src/**/*.tsx'] // 检查的文件
}), }),
VueI18n({ VueI18nPlugin({
runtimeOnly: true, runtimeOnly: true,
compositionOnly: true, compositionOnly: true,
include: [resolve(__dirname, 'src/locales/**')] include: [resolve(__dirname, 'src/locales/**')]
@ -70,7 +71,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
// setupProdMockServer() // setupProdMockServer()
// ` // `
// }), // }),
VueMarcos(), DefineOptions(),
ViteEjsPlugin({ ViteEjsPlugin({
title: env.VITE_APP_TITLE title: env.VITE_APP_TITLE
}) })

View File

@ -11,7 +11,7 @@ from fastapi.security import OAuth2PasswordBearer
""" """
系统版本 系统版本
""" """
VERSION = "1.4.2" VERSION = "1.4.3"
"""安全警告: 不要在生产中打开调试运行!""" """安全警告: 不要在生产中打开调试运行!"""
DEBUG = True DEBUG = True

View File

@ -7,7 +7,6 @@
# @desc : 增删改查 # @desc : 增删改查
from typing import List from typing import List
from aioredis import Redis from aioredis import Redis
from fastapi import UploadFile from fastapi import UploadFile
from core.exception import CustomException from core.exception import CustomException
@ -16,11 +15,11 @@ from sqlalchemy import select
from core.crud import DalBase from core.crud import DalBase
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from core.validator import vali_telephone from core.validator import vali_telephone
from utils.aliyun_oss import AliyunOSS, BucketConf from utils.file.aliyun_oss import AliyunOSS, BucketConf
from utils.file.file_manage import FileManage
from utils.aliyun_sms import AliyunSMS from utils.aliyun_sms import AliyunSMS
from utils.excel.import_manage import ImportManage, FieldType from utils.excel.import_manage import ImportManage, FieldType
from utils.excel.write_xlsx import WriteXlsx from utils.excel.write_xlsx import WriteXlsx
from utils.file_manage import FileManage
from .params import UserParams from .params import UserParams
from utils.tools import test_password from utils.tools import test_password
from . import models, schemas from . import models, schemas
@ -214,8 +213,10 @@ class UserDal(DalBase):
""" """
更新当前用户头像 更新当前用户头像
""" """
manage = FileManage(file, "avatar") test = await FileManage.save_tmp_file(file)
result = await AliyunOSS(BucketConf(**settings.ALIYUN_OSS)).upload_image(manage.path, file) print(test)
await file.seek(0)
result = await AliyunOSS(BucketConf(**settings.ALIYUN_OSS)).upload_image("avatar", file)
if not result: if not result:
raise CustomException(msg="上传失败", code=status.HTTP_ERROR) raise CustomException(msg="上传失败", code=status.HTTP_ERROR)
user.avatar = result user.avatar = result

View File

@ -11,6 +11,7 @@
""" """
from fastapi import Depends from fastapi import Depends
from core.dependencies import Paging, QueryParams from core.dependencies import Paging, QueryParams
from typing import Union
class UserParams(QueryParams): class UserParams(QueryParams):
@ -18,7 +19,8 @@ class UserParams(QueryParams):
列表分页 列表分页
""" """
def __init__(self, name: str = None, telephone: str = None, is_active: bool = None, params: Paging = Depends()): def __init__(self, name: str = None, telephone: str = None, is_active: Union[bool, str] = None,
params: Paging = Depends()):
super().__init__(params) super().__init__(params)
self.name = ("like", name) self.name = ("like", name)
self.telephone = ("like", telephone) self.telephone = ("like", telephone)

View File

@ -13,9 +13,8 @@ import os
from typing import List, Union from typing import List, Union
from sqlalchemy import select, update from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from application.settings import STATIC_ROOT from application.settings import STATIC_ROOT
from utils.file_manage import FileManage from utils.file.file_manage import FileManage
from . import models, schemas from . import models, schemas
from core.crud import DalBase from core.crud import DalBase

View File

@ -6,17 +6,17 @@
# @desc : 主要接口文件 # @desc : 主要接口文件
# UploadFile 库依赖pip install python-multipart # UploadFile 库依赖pip install python-multipart
from typing import Optional, List from typing import List
from fastapi import APIRouter, Depends, Query, Body, UploadFile, Request, Form from fastapi import APIRouter, Depends, Body, UploadFile, Request, Form
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from application.settings import ALIYUN_OSS from application.settings import ALIYUN_OSS
from core.database import db_getter from core.database import db_getter
from utils.aliyun_oss import AliyunOSS, BucketConf from utils.file.aliyun_oss import AliyunOSS, BucketConf
from utils.aliyun_sms import AliyunSMS from utils.aliyun_sms import AliyunSMS
from utils.file_manage import FileManage from utils.file.file_manage import FileManage
from utils.response import SuccessResponse, ErrorResponse from utils.response import SuccessResponse, ErrorResponse
from . import schemas, crud, models from . import schemas, crud
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
from .params import DictTypeParams, DictDetailParams from .params import DictTypeParams, DictDetailParams
@ -107,8 +107,7 @@ async def get_dict_detail(data_id: int, auth: Auth = Depends(login_auth)):
########################################################### ###########################################################
@app.post("/upload/image/to/oss/", summary="上传图片到阿里云OSS") @app.post("/upload/image/to/oss/", summary="上传图片到阿里云OSS")
async def upload_image_to_oss(file: UploadFile, path: str = Form(...)): async def upload_image_to_oss(file: UploadFile, path: str = Form(...)):
manage = FileManage(file, path) result = await AliyunOSS(BucketConf(**ALIYUN_OSS)).upload_image(path, file)
result = await AliyunOSS(BucketConf(**ALIYUN_OSS)).upload_image(manage.path, file)
if not result: if not result:
return ErrorResponse(msg="上传失败") return ErrorResponse(msg="上传失败")
return SuccessResponse(result) return SuccessResponse(result)

View File

@ -23,12 +23,13 @@ from core.exception import register_exception
import typer import typer
from scripts.initialize.initialize import InitializeData, Environment from scripts.initialize.initialize import InitializeData, Environment
import asyncio import asyncio
from scripts.create_app.main import CreateApp
shell_app = typer.Typer() shell_app = typer.Typer()
def create_app(): def init_app():
""" """
启动项目 启动项目
@ -83,7 +84,7 @@ def run():
""" """
启动项目 启动项目
""" """
uvicorn.run(app='main:create_app', host="0.0.0.0", port=9000) uvicorn.run(app='main:init_app', host="0.0.0.0", port=9000)
@shell_app.command() @shell_app.command()
@ -109,5 +110,17 @@ def migrate(env: Environment = Environment.pro):
InitializeData().migrate_model(env) InitializeData().migrate_model(env)
@shell_app.command()
def create_app(path: str):
"""
自动创建初始化 APP 结构
@params path: app 路径根目录为apps填写apps后面路径即可例子vadmin/auth
"""
print(f"开始创建并初始化 {path} APP")
app = CreateApp(path)
app.run()
if __name__ == '__main__': if __name__ == '__main__':
shell_app() shell_app()

View File

@ -0,0 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Creaet Time : 2022/12/9 15:26
# @File : __init__.py
# @IDE : PyCharm
# @desc : 简要说明

View File

@ -0,0 +1,100 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Creaet Time : 2022/12/9 15:27
# @File : main.py
# @IDE : PyCharm
# @desc : 简要说明
import datetime
import os.path
from application.settings import BASE_DIR
class CreateApp:
APPS_ROOT = os.path.join(BASE_DIR, "apps")
SCRIPT_DIR = os.path.join(BASE_DIR, 'scripts', 'create_app')
def __init__(self, path: str):
"""
@params path: app 路径根目录为apps填写apps后面路径即可例子vadmin/auth
"""
self.app_path = os.path.join(self.APPS_ROOT, path)
self.path = path
def run(self):
"""
自动创建初始化 APP 结构如何该路径已经存在则不执行
"""
if self.exist(self.app_path):
print(f"{self.app_path} 已经存在,无法自动创建,请删除后,重新执行。")
return False
print("开始生成 App 目录:", self.path)
path = []
for item in self.path.split("/"):
path.append(item)
self.create_pag(os.path.join(self.APPS_ROOT, *path))
self.create_pag(os.path.join(self.app_path, "models"))
self.create_pag(os.path.join(self.app_path, "params"))
self.create_pag(os.path.join(self.app_path, "schemas"))
self.generate_file("views.py")
self.generate_file("crud.py")
print("App 目录生成结束", self.app_path)
def create_pag(self, path: str) -> None:
"""
创建 python
@params path: 绝对路径
"""
if self.exist(path):
return
os.makedirs(path)
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
params = {
"create_datetime": now,
"filename": "__init__.py",
"desc": "初始化文件"
}
self.create_file(os.path.join(path, "__init__.py"), "init.py", **params)
def generate_file(self, name: str) -> None:
"""
创建文件
"""
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
params = {
"create_datetime": now,
}
self.create_file(os.path.join(self.app_path, name), name, **params)
def create_file(self, filepath: str, name: str, **kwargs):
"""
创建文件
"""
with open(filepath, "w", encoding="utf-8") as f:
content = self.__get_template(name)
f.write(content.format(**kwargs))
@classmethod
def exist(cls, path) -> bool:
"""
判断路径是否已经存在
"""
return os.path.exists(path)
def __get_template(self, name: str) -> str:
"""
获取模板内容
"""
template = open(os.path.join(self.SCRIPT_DIR, "template", name), 'r')
content = template.read()
template.close()
return content
if __name__ == '__main__':
app = CreateApp("test/vv")
app.run()

View File

@ -0,0 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Creaet Time : {create_datetime}
# @File : crud.py
# @IDE : PyCharm
# @desc :

View File

@ -0,0 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Creaet Time : {create_datetime}
# @File : {filename}
# @IDE : PyCharm
# @desc : {desc}

View File

@ -0,0 +1,18 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Creaet Time : {create_datetime}
# @File : views.py
# @IDE : PyCharm
# @desc :
from fastapi import APIRouter, Depends
from utils.response import SuccessResponse
from . import schemas, crud, models
from core.dependencies import IdList
from apps.vadmin.auth.utils.current import login_auth, Auth
app = APIRouter()

View File

@ -0,0 +1,327 @@
/*
Navicat Premium Data Transfer
Source Server : aliyun-mysql
Source Server Type : MySQL
Source Server Version : 80018 (8.0.18)
Source Host : rm-bp181adf0phw2o0r05o.mysql.rds.aliyuncs.com:3306
Source Schema : test_kinit
Target Server Type : MySQL
Target Server Version : 80018 (8.0.18)
File Encoding : 65001
Date: 23/11/2022 22:25:06
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for alembic_version
-- ----------------------------
DROP TABLE IF EXISTS `alembic_version`;
CREATE TABLE `alembic_version` (
`version_num` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
PRIMARY KEY (`version_num`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of alembic_version
-- ----------------------------
INSERT INTO `alembic_version` VALUES ('');
-- ----------------------------
-- Table structure for vadmin_auth_menu
-- ----------------------------
DROP TABLE IF EXISTS `vadmin_auth_menu`;
CREATE TABLE `vadmin_auth_menu` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`create_datetime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_datetime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`icon` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '菜单图标',
`component` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '前端组件地址',
`path` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '前端路由地址',
`hidden` tinyint(1) NULL DEFAULT NULL COMMENT '是否隐藏',
`menu_type` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '菜单类型',
`parent_id` int(11) NULL DEFAULT NULL COMMENT '父菜单',
`perms` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '权限标识',
`order` int(11) NULL DEFAULT NULL COMMENT '排序',
`disabled` tinyint(1) NULL DEFAULT NULL COMMENT '是否禁用',
`redirect` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '重定向地址',
`noCache` tinyint(1) NULL DEFAULT NULL COMMENT '如果设置为true则不会被 <keep-alive> 缓存(默认 false)',
`breadcrumb` tinyint(1) NULL DEFAULT NULL COMMENT '如果设置为false则不会在breadcrumb面包屑中显示(默认 true)',
`affix` tinyint(1) NULL DEFAULT NULL COMMENT '如果设置为true则会一直固定在tag项中(默认 false)',
`noTagsView` tinyint(1) NULL DEFAULT NULL COMMENT '如果设置为true则不会出现在tag中(默认 false)',
`canTo` tinyint(1) NULL DEFAULT NULL COMMENT '设置为true即使hidden为true也依然可以进行路由跳转(默认 false)',
`title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '名称',
`delete_datetime` datetime NULL DEFAULT NULL COMMENT '删除时间',
`alwaysShow` tinyint(1) NULL DEFAULT NULL COMMENT '当你一个路由下面的 children 声明的路由大于1个时自动会变成嵌套的模式\n 只有一个时,会将那个子路由当做根路由显示在侧边栏,若你想不管路由下面的 children 声明的个数都显示你的根路由,\n 你可以设置 alwaysShow: true这样它就会忽略之前定义的规则一直显示根路由(默认 true)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ix_vadmin_auth_menu_id`(`id` ASC) USING BTREE,
INDEX `parent_id`(`parent_id` ASC) USING BTREE,
INDEX `ix_vadmin_auth_menu_perms`(`perms` ASC) USING BTREE,
INDEX `ix_vadmin_auth_menu_title`(`title` ASC) USING BTREE,
CONSTRAINT `vadmin_auth_menu_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `vadmin_auth_menu` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 29 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '菜单表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of vadmin_auth_menu
-- ----------------------------
-- ----------------------------
-- Table structure for vadmin_auth_role
-- ----------------------------
DROP TABLE IF EXISTS `vadmin_auth_role`;
CREATE TABLE `vadmin_auth_role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`create_datetime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_datetime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '名称',
`role_key` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '权限字符',
`desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述',
`is_admin` tinyint(1) NULL DEFAULT NULL COMMENT '是否为超级角色',
`order` int(11) NULL DEFAULT NULL COMMENT '排序',
`disabled` tinyint(1) NULL DEFAULT NULL COMMENT '是否禁用',
`delete_datetime` datetime NULL DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ix_vadmin_auth_role_id`(`id` ASC) USING BTREE,
INDEX `ix_vadmin_auth_role_name`(`name` ASC) USING BTREE,
INDEX `ix_vadmin_auth_role_role_key`(`role_key` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of vadmin_auth_role
-- ----------------------------
-- ----------------------------
-- Table structure for vadmin_auth_role_menus
-- ----------------------------
DROP TABLE IF EXISTS `vadmin_auth_role_menus`;
CREATE TABLE `vadmin_auth_role_menus` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`role_id` int(11) NOT NULL,
`menu_id` int(11) NOT NULL,
PRIMARY KEY (`id`, `role_id`, `menu_id`) USING BTREE,
UNIQUE INDEX `ix_vadmin_auth_role_menus_id`(`id` ASC) USING BTREE,
INDEX `menu_id`(`menu_id` ASC) USING BTREE,
INDEX `role_id`(`role_id` ASC) USING BTREE,
CONSTRAINT `vadmin_auth_role_menus_ibfk_1` FOREIGN KEY (`menu_id`) REFERENCES `vadmin_auth_menu` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
CONSTRAINT `vadmin_auth_role_menus_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `vadmin_auth_role` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 47 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of vadmin_auth_role_menus
-- ----------------------------
-- ----------------------------
-- Table structure for vadmin_auth_user
-- ----------------------------
DROP TABLE IF EXISTS `vadmin_auth_user`;
CREATE TABLE `vadmin_auth_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`create_datetime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_datetime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`telephone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '手机号',
`name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '姓名',
`nickname` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '昵称',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密码',
`avatar` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '头像',
`gender` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '性别',
`is_active` tinyint(1) NULL DEFAULT NULL COMMENT '是否可用',
`is_cancel` tinyint(1) NULL DEFAULT NULL COMMENT '是否注销',
`is_reset_password` tinyint(1) NULL DEFAULT NULL COMMENT '是否已经重置密码,没有重置的,登陆系统后必须重置密码',
`last_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '最后一次登录IP',
`last_login` datetime NULL DEFAULT NULL COMMENT '最近一次登录时间',
`delete_datetime` datetime NULL DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ix_vadmin_auth_user_id`(`id` ASC) USING BTREE,
UNIQUE INDEX `ix_vadmin_auth_user_telephone`(`telephone` ASC) USING BTREE,
INDEX `ix_vadmin_auth_user_name`(`name` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 27 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of vadmin_auth_user
-- ----------------------------
-- ----------------------------
-- Table structure for vadmin_auth_user_roles
-- ----------------------------
DROP TABLE IF EXISTS `vadmin_auth_user_roles`;
CREATE TABLE `vadmin_auth_user_roles` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
PRIMARY KEY (`id`, `user_id`, `role_id`) USING BTREE,
UNIQUE INDEX `ix_vadmin_auth_user_roles_id`(`id` ASC) USING BTREE,
INDEX `role_id`(`role_id` ASC) USING BTREE,
INDEX `user_id`(`user_id` ASC) USING BTREE,
CONSTRAINT `vadmin_auth_user_roles_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `vadmin_auth_role` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
CONSTRAINT `vadmin_auth_user_roles_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `vadmin_auth_user` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 24 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of vadmin_auth_user_roles
-- ----------------------------
-- ----------------------------
-- Table structure for vadmin_record_login
-- ----------------------------
DROP TABLE IF EXISTS `vadmin_record_login`;
CREATE TABLE `vadmin_record_login` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`create_datetime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_datetime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`telephone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '手机号',
`status` tinyint(1) NULL DEFAULT NULL COMMENT '是否登录成功',
`ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '登陆地址',
`address` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '登陆地点',
`browser` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '浏览器',
`system` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作系统',
`response` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '响应信息',
`request` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '请求信息',
`delete_datetime` datetime NULL DEFAULT NULL COMMENT '删除时间',
`country` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '国家',
`province` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '',
`city` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '城市',
`county` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '区/县',
`operator` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '运营商',
`postal_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '邮政编码',
`area_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '地区区号',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ix_vadmin_record_login_id`(`id` ASC) USING BTREE,
INDEX `ix_vadmin_record_login_telephone`(`telephone` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 531 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '登录记录表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of vadmin_record_login
-- ----------------------------
-- ----------------------------
-- Table structure for vadmin_record_sms_send
-- ----------------------------
DROP TABLE IF EXISTS `vadmin_record_sms_send`;
CREATE TABLE `vadmin_record_sms_send` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`create_datetime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_datetime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`user_id` int(11) NULL DEFAULT NULL COMMENT '操作人',
`status` tinyint(1) NULL DEFAULT NULL COMMENT '发送状态',
`content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '发送内容',
`telephone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '目标手机号',
`desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '失败描述',
`scene` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '发送场景',
`delete_datetime` datetime NULL DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ix_vadmin_record_sms_send_id`(`id` ASC) USING BTREE,
INDEX `user_id`(`user_id` ASC) USING BTREE,
CONSTRAINT `vadmin_record_sms_send_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `vadmin_auth_user` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '短信发送记录表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of vadmin_record_sms_send
-- ----------------------------
-- ----------------------------
-- Table structure for vadmin_system_dict_details
-- ----------------------------
DROP TABLE IF EXISTS `vadmin_system_dict_details`;
CREATE TABLE `vadmin_system_dict_details` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`create_datetime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_datetime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`is_default` tinyint(1) NULL DEFAULT NULL COMMENT '是否默认',
`dict_type_id` int(11) NULL DEFAULT NULL COMMENT '关联字典类型',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
`disabled` tinyint(1) NULL DEFAULT NULL COMMENT '字典状态,是否禁用',
`order` int(11) NULL DEFAULT NULL COMMENT '字典排序',
`label` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '字典标签',
`value` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '字典键值',
`delete_datetime` datetime NULL DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ix_vadmin_system_dict_details_id`(`id` ASC) USING BTREE,
INDEX `dict_type_id`(`dict_type_id` ASC) USING BTREE,
INDEX `ix_vadmin_system_dict_details_label`(`label` ASC) USING BTREE,
INDEX `ix_vadmin_system_dict_details_value`(`value` ASC) USING BTREE,
CONSTRAINT `vadmin_system_dict_details_ibfk_1` FOREIGN KEY (`dict_type_id`) REFERENCES `vadmin_system_dict_type` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '字典详情表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of vadmin_system_dict_details
-- ----------------------------
-- ----------------------------
-- Table structure for vadmin_system_dict_type
-- ----------------------------
DROP TABLE IF EXISTS `vadmin_system_dict_type`;
CREATE TABLE `vadmin_system_dict_type` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`create_datetime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_datetime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`dict_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '字典名称',
`dict_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '字典类型',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
`disabled` tinyint(1) NULL DEFAULT NULL COMMENT '字典状态,是否禁用',
`delete_datetime` datetime NULL DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ix_vadmin_system_dict_type_id`(`id` ASC) USING BTREE,
INDEX `ix_vadmin_system_dict_type_dict_name`(`dict_name` ASC) USING BTREE,
INDEX `ix_vadmin_system_dict_type_dict_type`(`dict_type` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '字典类型表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of vadmin_system_dict_type
-- ----------------------------
-- ----------------------------
-- Table structure for vadmin_system_settings
-- ----------------------------
DROP TABLE IF EXISTS `vadmin_system_settings`;
CREATE TABLE `vadmin_system_settings` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`create_datetime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_datetime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`delete_datetime` datetime NULL DEFAULT NULL COMMENT '删除时间',
`config_label` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '配置表标签',
`config_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '配置表键',
`config_value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '配置表内容',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注信息',
`tab_id` int(11) NULL DEFAULT NULL COMMENT '关联tab标签',
`disabled` tinyint(1) NULL DEFAULT NULL COMMENT '是否禁用',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ix_vadmin_system_settings_id`(`id` ASC) USING BTREE,
UNIQUE INDEX `ix_vadmin_system_settings_config_key`(`config_key` ASC) USING BTREE,
INDEX `tab_id`(`tab_id` ASC) USING BTREE,
CONSTRAINT `vadmin_system_settings_ibfk_1` FOREIGN KEY (`tab_id`) REFERENCES `vadmin_system_settings_tab` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 24 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统配置表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of vadmin_system_settings
-- ----------------------------
-- ----------------------------
-- Table structure for vadmin_system_settings_tab
-- ----------------------------
DROP TABLE IF EXISTS `vadmin_system_settings_tab`;
CREATE TABLE `vadmin_system_settings_tab` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`create_datetime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_datetime` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`delete_datetime` datetime NULL DEFAULT NULL COMMENT '删除时间',
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '标题',
`tab_label` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'tab标题',
`hidden` tinyint(1) NULL DEFAULT NULL COMMENT '是否隐藏',
`classify` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '分类键',
`tab_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'tab标识符',
`disabled` tinyint(1) NULL DEFAULT NULL COMMENT '是否禁用',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ix_vadmin_system_settings_tab_id`(`id` ASC) USING BTREE,
UNIQUE INDEX `ix_vadmin_system_settings_tab_tab_name`(`tab_name` ASC) USING BTREE,
INDEX `ix_vadmin_system_settings_tab_classify`(`classify` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统配置分类表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of vadmin_system_settings_tab
-- ----------------------------
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -1,53 +0,0 @@
import base64
from Crypto.Cipher import AES # 安装pip install pycryptodome
# 密钥key, 密斯偏移量iv CBC模式加密
# base64 详解https://cloud.tencent.com/developer/article/1099008
_key = '0CoJUm6Qywm6ts68' # 自己密钥
def aes_encrypt(data: str):
"""
加密
"""
vi = '0102030405060708'
pad = lambda s: s + (16 - len(s) % 16) * chr(16 - len(s) % 16)
data = pad(data)
# 字符串补位
cipher = AES.new(_key.encode('utf8'), AES.MODE_CBC, vi.encode('utf8'))
encrypted_bytes = cipher.encrypt(data.encode('utf8'))
# 加密后得到的是bytes类型的数据
encode_strs = base64.urlsafe_b64encode(encrypted_bytes)
# 使用Base64进行编码,返回byte字符串
# 对byte字符串按utf-8进行解码
return encode_strs.decode('utf8')
def aes_decrypt(data):
"""
解密
"""
vi = '0102030405060708'
data = data.encode('utf8')
encode_bytes = base64.urlsafe_b64decode(data)
# 将加密数据转换位bytes类型数据
cipher = AES.new(_key.encode('utf8'), AES.MODE_CBC, vi.encode('utf8'))
text_decrypted = cipher.decrypt(encode_bytes)
unpad = lambda s: s[0:-s[-1]]
text_decrypted = unpad(text_decrypted)
# 补位
text_decrypted = text_decrypted.decode('utf8')
return text_decrypted
if __name__ == '__main__':
_data = '16658273438153332588-95YEUPJR' # 需要加密的内容
enctext = aes_encrypt(_data)
print(enctext)
# enctext = "Wzll1oiVs9UKAySY1-xSy_CbrZmelVwyqu8P0CZTrrc="
# _text_decrypted = aes_decrypt(_key, enctext)
# print(_text_decrypted)

View File

@ -10,7 +10,7 @@ from fastapi import UploadFile
from core.exception import CustomException from core.exception import CustomException
from utils import status from utils import status
from .excel_manage import ExcelManage from .excel_manage import ExcelManage
from utils.file_manage import FileManage from utils.file.file_manage import FileManage
from .write_xlsx import WriteXlsx from .write_xlsx import WriteXlsx
from ..tools import list_dict_find from ..tools import list_dict_find
from enum import Enum from enum import Enum

View File

@ -0,0 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Creaet Time : 2022/12/12 14:30
# @File : __init__.py.py
# @IDE : PyCharm
# @desc : 简要说明

View File

@ -12,8 +12,9 @@ from pydantic import BaseModel
import oss2 # 安装依赖库pip install oss2 import oss2 # 安装依赖库pip install oss2
from oss2.models import PutObjectResult from oss2.models import PutObjectResult
from core.logger import logger from core.logger import logger
from utils.compress.cpressJPG import compress_jpg_png from utils.file.compress.cpressJPG import compress_jpg_png
from utils.file_manage import FileManage from utils.file.file_manage import FileManage
from utils.file.file_base import FileBase
class BucketConf(BaseModel): class BucketConf(BaseModel):
@ -24,7 +25,7 @@ class BucketConf(BaseModel):
baseUrl: str baseUrl: str
class AliyunOSS: class AliyunOSS(FileBase):
""" """
阿里云对象存储 阿里云对象存储
@ -53,6 +54,7 @@ class AliyunOSS:
@param compress: 是否压缩该文件 @param compress: 是否压缩该文件
@return: 上传后的文件oss链接 @return: 上传后的文件oss链接
""" """
path = self.generate_path(path, file.filename)
if compress: if compress:
# 压缩图片 # 压缩图片
file_path = await FileManage.save_tmp_file(file) file_path = await FileManage.save_tmp_file(file)
@ -70,20 +72,19 @@ class AliyunOSS:
return "" return ""
return self.baseUrl + path return self.baseUrl + path
def upload_local_file(self, path: str, filename: str) -> str: async def upload_file(self, path: str, file: UploadFile) -> str:
""" """
上传本地文件 上传文件
@param path: path由包含文件后缀不包含Bucket名称组成的Object完整路径例如abc/efg/123.jpg @param path: path由包含文件后缀不包含Bucket名称组成的Object完整路径例如abc/efg/123.jpg
@param filename: 本地文件路径 @param file: 文件对象
@return: 上传后的文件oss链接 @return: 上传后的文件oss链接
""" """
with open(filename, "rb") as f: path = self.generate_path(path, file.filename)
result = self.bucket.put_object(path, f.read()) file_data = await file.read()
result = self.bucket.put_object(path, file_data)
assert isinstance(result, PutObjectResult) assert isinstance(result, PutObjectResult)
if result.status != 200: if result.status != 200:
logger.error(f"本地文件上传到OSS失败状态码{result.status}") logger.error(f"文件上传到OSS失败状态码{result.status}")
print("本地文件上传路径", path)
print(f"本地文件上传到OSS失败状态码{result.status}")
return "" return ""
return self.baseUrl + path return self.baseUrl + path

View File

@ -1,5 +1,5 @@
from PIL import Image, ExifTags # 安装依赖包pip3 install pillow from PIL import Image, ExifTags # 安装依赖包pip3 install pillow
from utils.compress import dynamic_quality from utils.file.compress import dynamic_quality
import os import os
import time import time

View File

@ -0,0 +1,67 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Creaet Time : 2022/12/12 14:31
# @File : file_base.py
# @IDE : PyCharm
# @desc : 简要说明
import datetime
import os
import uuid
from fastapi import UploadFile
from core.exception import CustomException
from utils import status
from typing import Union
class FileBase:
IMAGE_ACCEPT = ["image/png", "image/jpeg", "image/gif", "image/x-icon"]
VIDEO_ACCEPT = ["audio/mp4", "video/mp4", "video/mpeg"]
ALL_ACCEPT = [*IMAGE_ACCEPT, *VIDEO_ACCEPT]
@classmethod
def generate_path(cls, path: str, filename):
"""
生成文件路径
"""
if path[0] == "/":
path = path[1:]
if path[-1] == "/":
path = path[:-1]
full_date = datetime.datetime.now().date()
_filename = str(int(datetime.datetime.now().timestamp())) + str(uuid.uuid4())[:8]
return f"{path}/{full_date}/{_filename}{os.path.splitext(filename)[-1]}"
@classmethod
async def validate_file(cls, file: UploadFile, max_size: int = None, mime_types: list = None) -> bool:
"""
验证文件是否符合格式
@params max_size: 文件最大值单位 MB
"""
if max_size:
size = len(await file.read()) / 1024 / 1024
if size > max_size:
raise CustomException(f"上传文件过大,不能超过{max_size}MB", status.HTTP_ERROR)
await file.seek(0)
if mime_types:
if file.content_type not in mime_types:
raise CustomException(f"上传文件格式错误,只支持 {'/'.join(mime_types)} 格式!", status.HTTP_ERROR)
return True
@classmethod
def get_file_type(cls, content_type: str) -> Union[str, None]:
"""
获取文件类型
0: 图片
1视频
"""
if content_type in cls.IMAGE_ACCEPT:
return "0"
elif content_type in cls.VIDEO_ACCEPT:
return "1"
else:
return None

View File

@ -8,29 +8,19 @@
import datetime import datetime
import os import os
import shutil import shutil
from application.settings import TEMP_DIR, STATIC_ROOT, BASE_DIR, STATIC_DIR, STATIC_URL from application.settings import TEMP_DIR, STATIC_ROOT, BASE_DIR, STATIC_URL, STATIC_DIR
from fastapi import UploadFile from fastapi import UploadFile
import sys import sys
from core.exception import CustomException from utils.file.file_base import FileBase
from utils import status
import uuid
class FileManage: class FileManage(FileBase):
""" """
上传文件管理 上传文件管理
""" """
image_accept = ["image/png", "image/jpeg", "image/gif", "image/x-icon"]
def __init__(self, file: UploadFile, path: str): def __init__(self, file: UploadFile, path: str):
if path[0] == "/": self.path = self.generate_path(path, file.filename)
path = path[1:]
if path[-1] == "/":
path = path[:-1]
full_date = datetime.datetime.now().date()
filename = str(int(datetime.datetime.now().timestamp())) + str(uuid.uuid4())[:8]
self.path = f"{path}/{full_date}/{filename}{os.path.splitext(file.filename)[-1]}"
self.file = file self.file = file
async def save_image_local(self, accept: list = None) -> dict: async def save_image_local(self, accept: list = None) -> dict:
@ -38,9 +28,8 @@ class FileManage:
保存图片文件到本地 保存图片文件到本地
""" """
if accept is None: if accept is None:
accept = self.image_accept accept = self.IMAGE_ACCEPT
if self.file.content_type not in accept: await self.validate_file(self.file, max_size=5, mime_types=accept)
raise CustomException(f"上传图片必须是 {'/'.join(accept)} 格式!", status.HTTP_ERROR)
return await self.save_local() return await self.save_local()
async def save_local(self) -> dict: async def save_local(self) -> dict:
@ -61,7 +50,7 @@ class FileManage:
} }
@staticmethod @staticmethod
async def save_tmp_file(file: UploadFile) -> str: async def save_tmp_file(file: UploadFile):
""" """
保存临时文件 保存临时文件
""" """
@ -75,7 +64,7 @@ class FileManage:
return filename return filename
@staticmethod @staticmethod
def copy(src: str, dst: str) -> None: def copy(src: str, dst: str):
""" """
复制文件 复制文件
根目录为项目根目录传过来的文件路径均为相对路径 根目录为项目根目录传过来的文件路径均为相对路径
@ -95,4 +84,7 @@ class FileManage:
if __name__ == '__main__': if __name__ == '__main__':
print() # src = r"D:\ktianc\private\vv-reserve\reserve-api\static\system\2022-12-07\16703958210ab33912.ico"
# dst = r"D:\ktianc\private\vv-reserve\reserve-api\static\system\favicon.ico"
# shutil.copyfile(src, dst)
pass

View File

@ -1,17 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @version : 1.0
# @Creaet Time : 2022/11/16 8:55
# @File : test.py
# @IDE : PyCharm
# @desc : 测试文件
from enum import Enum
class Scene(Enum):
login = "template_code_1"
reset_password = "template_code_2"
if __name__ == '__main__':
print(Scene.login.value)

View File

@ -235,34 +235,42 @@
uni.showLoading({ uni.showLoading({
title: '图片生成中...', title: '图片生成中...',
}) })
// uni.getImageInfo({
const ctx = uni.createCanvasContext('myCanvas') src: _this.imageSrc,
ctx.drawImage(_this.imageSrc, 0, 0, IMG_REAL_W, IMG_REAL_H) success: function (image) {
ctx.draw(true, () => { //
// * canvasT = (_this.cutT / _this.cropperH) * (_this.imageH / pixelRatio) const ctx = uni.createCanvasContext('myCanvas')
var canvasW = ((_this.cropperW - _this.cutL - _this.cutR) / _this.cropperW) * IMG_REAL_W ctx.drawImage(image.path, 0, 0, IMG_REAL_W, IMG_REAL_H)
var canvasH = ((_this.cropperH - _this.cutT - _this.cutB) / _this.cropperH) * IMG_REAL_H ctx.draw(true, () => {
var canvasL = (_this.cutL / _this.cropperW) * IMG_REAL_W // * canvasT = (_this.cutT / _this.cropperH) * (_this.imageH / pixelRatio)
var canvasT = (_this.cutT / _this.cropperH) * IMG_REAL_H var canvasW = ((_this.cropperW - _this.cutL - _this.cutR) / _this.cropperW) * IMG_REAL_W
uni.canvasToTempFilePath({ var canvasH = ((_this.cropperH - _this.cutT - _this.cutB) / _this.cropperH) * IMG_REAL_H
x: canvasL, var canvasL = (_this.cutL / _this.cropperW) * IMG_REAL_W
y: canvasT, var canvasT = (_this.cutT / _this.cropperH) * IMG_REAL_H
width: canvasW, uni.canvasToTempFilePath({
height: canvasH, x: canvasL,
destWidth: canvasW, y: canvasT,
destHeight: canvasH, width: canvasW,
quality: 0.5, height: canvasH,
canvasId: 'myCanvas', destWidth: canvasW,
success: function (res) { destHeight: canvasH,
uni.hideLoading() quality: 0.5,
postCurrentUserUploadAvatar(res.tempFilePath).then(response => { canvasId: 'myCanvas',
store.commit('SET_AVATAR', response.data) success: function (res) {
uni.showToast({ title: "修改成功", icon: 'success' }) uni.hideLoading()
uni.navigateBack() postCurrentUserUploadAvatar(res.tempFilePath).then(response => {
store.commit('SET_AVATAR', response.data)
uni.showToast({ title: "修改成功", icon: 'success' })
uni.navigateBack()
})
}
}) })
} })
}) },
}) fail: function (err) {
uni.hideLoading()
}
});
}, },
// touchStart // touchStart
dragStart(e) { dragStart(e) {