新加入图片资源管理功能,已更新到线上地址,并开通了批量上传图片权限
This commit is contained in:
parent
34248f3c95
commit
ee4f4612fe
@ -0,0 +1,185 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Form } from '@/components/Form'
|
||||||
|
import { useForm } from '@/hooks/web/useForm'
|
||||||
|
import { PropType, reactive, watch, ref } from 'vue'
|
||||||
|
import { useValidator } from '@/hooks/web/useValidator'
|
||||||
|
import { schema } from './images.data'
|
||||||
|
import { ElUpload } from 'element-plus'
|
||||||
|
import { ElMessage, ElImageViewer } from 'element-plus'
|
||||||
|
import type {
|
||||||
|
UploadProps,
|
||||||
|
UploadUserFile,
|
||||||
|
UploadRequestOptions,
|
||||||
|
UploadFile,
|
||||||
|
UploadFiles
|
||||||
|
} from 'element-plus'
|
||||||
|
|
||||||
|
const { required } = useValidator()
|
||||||
|
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const imgIndex = ref(0)
|
||||||
|
const maxLimit = ref(50)
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
currentRow: {
|
||||||
|
type: Object as PropType<Nullable<any>>,
|
||||||
|
default: () => null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = reactive({
|
||||||
|
name: [required()]
|
||||||
|
})
|
||||||
|
|
||||||
|
const { register, methods, elFormRef } = useForm({
|
||||||
|
schema: schema
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.currentRow,
|
||||||
|
(currentRow) => {
|
||||||
|
if (!currentRow) return
|
||||||
|
const { setValues } = methods
|
||||||
|
setValues(currentRow)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const { setValue } = methods
|
||||||
|
|
||||||
|
const fileList = ref<UploadUserFile[]>([])
|
||||||
|
|
||||||
|
const beforeImageUpload: UploadProps['beforeUpload'] = (rawFile) => {
|
||||||
|
const isIMAGE = ['image/jpeg', 'image/gif', 'image/png'].includes(rawFile.type)
|
||||||
|
const isLtSize = rawFile.size / 1024 / 1024 < 3
|
||||||
|
|
||||||
|
if (!isIMAGE) {
|
||||||
|
ElMessage.error('上传图片素材必须是 JPG/PNG/ 格式!')
|
||||||
|
}
|
||||||
|
if (!isLtSize) {
|
||||||
|
ElMessage.error('上传图片素材大小不能超过 3MB!')
|
||||||
|
}
|
||||||
|
|
||||||
|
return isIMAGE && isLtSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传成功的钩子函数
|
||||||
|
const handleUploadSuccess: UploadProps['onSuccess'] = (
|
||||||
|
response: any,
|
||||||
|
uploadFile: UploadFile,
|
||||||
|
uploadFiles: UploadFiles
|
||||||
|
) => {
|
||||||
|
fileList.value = uploadFiles
|
||||||
|
setValue('images', uploadFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义上传
|
||||||
|
const handleHttpRequest: UploadProps['httpRequest'] = (options: UploadRequestOptions) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
resolve(options)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile: UploadFile) => {
|
||||||
|
imgIndex.value = fileList.value.findIndex((item) => item.uid === uploadFile.uid)
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCloseViewer = () => {
|
||||||
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleExceedLimit = () => {
|
||||||
|
ElMessage.error('上传失败,超出图片最大数量限制!')
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
elFormRef,
|
||||||
|
getFormData: methods.getFormData
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Form :rules="rules" label-position="top" @register="register">
|
||||||
|
<template #images>
|
||||||
|
<div class="flex justify-between w-[100%]">
|
||||||
|
<span>图片资源</span>
|
||||||
|
<span>最大数量限制:{{ fileList.length }}/{{ maxLimit }}</span>
|
||||||
|
</div>
|
||||||
|
<ElUpload
|
||||||
|
class="resource-image-uploader"
|
||||||
|
action="#"
|
||||||
|
:http-request="handleHttpRequest"
|
||||||
|
v-model:file-list="fileList"
|
||||||
|
:show-file-list="true"
|
||||||
|
:multiple="true"
|
||||||
|
:before-upload="beforeImageUpload"
|
||||||
|
:on-success="handleUploadSuccess"
|
||||||
|
:on-preview="handlePictureCardPreview"
|
||||||
|
:on-exceed="handleExceedLimit"
|
||||||
|
accept="image/jpeg,image/png"
|
||||||
|
name="file"
|
||||||
|
list-type="picture-card"
|
||||||
|
:limit="maxLimit"
|
||||||
|
:drag="true"
|
||||||
|
:disabled="maxLimit <= fileList.length"
|
||||||
|
>
|
||||||
|
<div v-if="fileList.length < maxLimit">
|
||||||
|
<div class="resource-image-uploader-icon">
|
||||||
|
<Icon icon="akar-icons:plus" :size="23" />
|
||||||
|
<span class="text-[12px] mt-4">点击或拖拽到这里</span>
|
||||||
|
<span class="text-[12px] mt-4">{{ fileList.length }}/{{ maxLimit }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="resource-image-uploader-icon">
|
||||||
|
<span class="text-[12px]">已到最大限制</span>
|
||||||
|
<span class="text-[12px] mt-4">{{ fileList.length }}/{{ maxLimit }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ElUpload>
|
||||||
|
</template>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<ElImageViewer
|
||||||
|
v-if="dialogVisible"
|
||||||
|
:z-index="9999"
|
||||||
|
@close="handleCloseViewer"
|
||||||
|
:url-list="fileList.map((item) => item.url as string)"
|
||||||
|
:initial-index="imgIndex"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.resource-image-uploader .el-upload {
|
||||||
|
border: 1px dashed var(--el-border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: var(--el-transition-duration-fast);
|
||||||
|
|
||||||
|
.el-upload-dragger {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-image-uploader-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
color: #8c939d;
|
||||||
|
width: 148px;
|
||||||
|
height: 148px;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-image-uploader .el-upload:hover {
|
||||||
|
border-color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,103 @@
|
|||||||
|
import { FormSchema } from '@/types/form'
|
||||||
|
import { TableColumn } from '@/types/table'
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
|
||||||
|
export const columns = reactive<TableColumn[]>([
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
label: '编号',
|
||||||
|
show: true,
|
||||||
|
disabled: false,
|
||||||
|
width: '120px',
|
||||||
|
span: 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'image_url',
|
||||||
|
label: '图片',
|
||||||
|
show: true,
|
||||||
|
disabled: true,
|
||||||
|
minWidth: '90px',
|
||||||
|
span: 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remark',
|
||||||
|
label: '备注',
|
||||||
|
show: false,
|
||||||
|
disabled: false,
|
||||||
|
span: 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'update_datetime',
|
||||||
|
label: '更新时间',
|
||||||
|
show: false,
|
||||||
|
width: '180px',
|
||||||
|
span: 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'create_datetime',
|
||||||
|
label: '创建时间',
|
||||||
|
width: '180px',
|
||||||
|
show: true,
|
||||||
|
span: 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'create_user.name',
|
||||||
|
label: '创建人',
|
||||||
|
show: false,
|
||||||
|
span: 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'action',
|
||||||
|
label: '操作',
|
||||||
|
show: true,
|
||||||
|
disabled: false,
|
||||||
|
width: '260px',
|
||||||
|
fixed: 'right',
|
||||||
|
span: 24
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
export const searchSchema = reactive<FormSchema[]>([
|
||||||
|
{
|
||||||
|
field: 'filename',
|
||||||
|
label: '文件名称',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
clearable: true,
|
||||||
|
style: {
|
||||||
|
width: '214px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
export const schema = reactive<FormSchema[]>([
|
||||||
|
{
|
||||||
|
field: 'upload_method',
|
||||||
|
label: '上传方式',
|
||||||
|
colProps: {
|
||||||
|
span: 24
|
||||||
|
},
|
||||||
|
component: 'Radio',
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: '同时上传',
|
||||||
|
value: '1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '按顺序上传',
|
||||||
|
value: '2'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
value: '1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'images',
|
||||||
|
label: '',
|
||||||
|
colProps: {
|
||||||
|
span: 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
218
kinit-admin/src/views/vadmin/resource/images/index.vue
Normal file
218
kinit-admin/src/views/vadmin/resource/images/index.vue
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'ResourceImages'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ContentWrap } from '@/components/ContentWrap'
|
||||||
|
import { Table } from '@/components/Table'
|
||||||
|
import { addImagesApi, getImagesListApi, delImagesListApi } from '@/api/vadmin/resource/images'
|
||||||
|
import { useTable } from '@/hooks/web/useTable'
|
||||||
|
import { columns, searchSchema } from './components/images.data'
|
||||||
|
import { ref, watch, nextTick, unref } from 'vue'
|
||||||
|
import { ElRow, ElButton, ElCol, ElImage, ElMessage } from 'element-plus'
|
||||||
|
import { RightToolbar } from '@/components/RightToolbar'
|
||||||
|
import { useCache } from '@/hooks/web/useCache'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import Write from './components/Write.vue'
|
||||||
|
import { Dialog } from '@/components/Dialog'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import { Search } from '@/components/Search'
|
||||||
|
import { useClipboard } from '@vueuse/core'
|
||||||
|
|
||||||
|
const { wsCache } = useCache()
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const { register, elTableRef, tableObject, methods } = useTable({
|
||||||
|
getListApi: getImagesListApi,
|
||||||
|
delListApi: delImagesListApi,
|
||||||
|
response: {
|
||||||
|
data: 'data',
|
||||||
|
count: 'count'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { getList, setSearchParams } = methods
|
||||||
|
|
||||||
|
const tableSize = ref('default')
|
||||||
|
|
||||||
|
watch(tableSize, (val) => {
|
||||||
|
tableSize.value = val
|
||||||
|
})
|
||||||
|
|
||||||
|
const route = useRouter()
|
||||||
|
const cacheTableHeadersKey = route.currentRoute.value.fullPath
|
||||||
|
|
||||||
|
watch(
|
||||||
|
columns,
|
||||||
|
async (val) => {
|
||||||
|
wsCache.set(cacheTableHeadersKey, JSON.stringify(val))
|
||||||
|
await nextTick()
|
||||||
|
elTableRef.value?.doLayout()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const dialogTitle = ref('')
|
||||||
|
const loading = ref(false)
|
||||||
|
const actionType = ref('')
|
||||||
|
|
||||||
|
// 新增事件
|
||||||
|
const addAction = async () => {
|
||||||
|
dialogTitle.value = '新增图片素材'
|
||||||
|
tableObject.currentRow = null
|
||||||
|
dialogVisible.value = true
|
||||||
|
actionType.value = 'add'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除事件
|
||||||
|
const delAction = async (row: any) => {
|
||||||
|
const { delListApi } = methods
|
||||||
|
if (row) {
|
||||||
|
await delListApi(true, [row.id])
|
||||||
|
} else {
|
||||||
|
await delListApi(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeRef = ref<ComponentRef<typeof Write>>()
|
||||||
|
|
||||||
|
// 提交事件
|
||||||
|
const save = async () => {
|
||||||
|
const write = unref(writeRef)
|
||||||
|
await write?.elFormRef?.validate(async (isValid) => {
|
||||||
|
if (isValid) {
|
||||||
|
loading.value = true
|
||||||
|
let data = await write?.getFormData()
|
||||||
|
data?.images.forEach((item) => (item.status = 'uploading'))
|
||||||
|
if (data?.upload_method === '2') {
|
||||||
|
// 按顺序上传文件
|
||||||
|
for (const item of data?.images) {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', item.raw)
|
||||||
|
await addImagesApi(formData)
|
||||||
|
item.status = 'success'
|
||||||
|
}
|
||||||
|
} else if (data?.upload_method === '1') {
|
||||||
|
// 同时上传文件
|
||||||
|
const uploadPromises = data?.images.map(async (item) => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', item.raw)
|
||||||
|
await addImagesApi(formData)
|
||||||
|
item.status = 'success'
|
||||||
|
})
|
||||||
|
await Promise.all(uploadPromises)
|
||||||
|
}
|
||||||
|
// 全部上传完成后执行的代码
|
||||||
|
getList()
|
||||||
|
dialogVisible.value = false
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制数据到剪切板
|
||||||
|
const toCopy = async (value: string) => {
|
||||||
|
// 复制功能打包部署到线上后,需要线上地址使用 https 才可使用
|
||||||
|
const { copy } = useClipboard()
|
||||||
|
await copy(value)
|
||||||
|
return ElMessage.success('复制成功')
|
||||||
|
}
|
||||||
|
|
||||||
|
getList()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ContentWrap>
|
||||||
|
<Search :schema="searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||||
|
|
||||||
|
<div class="mb-8px flex justify-between">
|
||||||
|
<ElRow :gutter="10">
|
||||||
|
<ElCol :span="1.5" v-shop>
|
||||||
|
<ElButton type="primary" @click="addAction">新增图片素材</ElButton>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="1.5" v-shop>
|
||||||
|
<ElButton type="danger" @click="delAction(null)">批量删除选中的图片素材</ElButton>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
<RightToolbar
|
||||||
|
@get-list="getList"
|
||||||
|
v-model:table-size="tableSize"
|
||||||
|
v-model:columns="columns"
|
||||||
|
:cache-table-headers-key="cacheTableHeadersKey"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
v-model:limit="tableObject.limit"
|
||||||
|
v-model:page="tableObject.page"
|
||||||
|
:columns="columns"
|
||||||
|
:data="tableObject.tableData"
|
||||||
|
:loading="tableObject.loading"
|
||||||
|
:selection="true"
|
||||||
|
:size="tableSize"
|
||||||
|
:border="true"
|
||||||
|
:pagination="{
|
||||||
|
total: tableObject.count
|
||||||
|
}"
|
||||||
|
@register="register"
|
||||||
|
>
|
||||||
|
<template #image_url="{ row, $index }">
|
||||||
|
<div class="resource-image-name flex items-center">
|
||||||
|
<div>
|
||||||
|
<ElImage
|
||||||
|
:src="`${row.image_url}?x-oss-process=image/resize,m_fixed,h_100`"
|
||||||
|
:zoom-rate="1.2"
|
||||||
|
:preview-src-list="tableObject.tableData.map((item) => item.image_url)"
|
||||||
|
:preview-teleported="true"
|
||||||
|
:initial-index="$index"
|
||||||
|
style="height: 60px; display: block"
|
||||||
|
fit="cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="leading-[35px] ml-2 truncate">
|
||||||
|
<span>{{ row.filename }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #action="{ row }">
|
||||||
|
<ElButton type="primary" link size="small" @click="toCopy(row.id)"> 复制图片编号 </ElButton>
|
||||||
|
<ElButton type="primary" link size="small" @click="toCopy(row.image_url)">
|
||||||
|
复制图片链接
|
||||||
|
</ElButton>
|
||||||
|
<ElButton v-shop type="danger" link size="small" @click="delAction(row)">
|
||||||
|
{{ t('exampleDemo.del') }}
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
<Dialog v-model="dialogVisible" :title="dialogTitle" width="996px" height="600px" top="3vh">
|
||||||
|
<Write ref="writeRef" :current-row="tableObject.currentRow" />
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<ElButton type="primary" :loading="loading" @click="save">
|
||||||
|
{{ t('exampleDemo.save') }}
|
||||||
|
</ElButton>
|
||||||
|
<ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.resource-image .image-slot {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 60px;
|
||||||
|
height: 35px;
|
||||||
|
background: var(--el-fill-color-light);
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -253,7 +253,7 @@ def __dict_filter(self, **kwargs) -> list[BinaryExpression]:
|
|||||||
查询所有用户id为1或2或 4或6,并且email不为空,并且名称包括李:
|
查询所有用户id为1或2或 4或6,并且email不为空,并且名称包括李:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
users = UserDal(db).get_datas(limit=0, id=("in", [1,2,4,6]), email=("not None"), name=("like", "李"))
|
users = UserDal(db).get_datas(limit=0, id=("in", [1,2,4,6]), email=("not None", ), name=("like", "李"))
|
||||||
|
|
||||||
# limit=0:表示返回所有结果数据
|
# limit=0:表示返回所有结果数据
|
||||||
# 这里的 get_datas 默认返回的是 pydantic 模型数据
|
# 这里的 get_datas 默认返回的是 pydantic 模型数据
|
||||||
@ -261,7 +261,7 @@ users = UserDal(db).get_datas(limit=0, id=("in", [1,2,4,6]), email=("not None"),
|
|||||||
users = UserDal(db).get_datas(
|
users = UserDal(db).get_datas(
|
||||||
limit=0,
|
limit=0,
|
||||||
id=("in", [1,2,4,6]),
|
id=("in", [1,2,4,6]),
|
||||||
email=("not None"),
|
email=("not None", ),
|
||||||
name=("like", "李"),
|
name=("like", "李"),
|
||||||
v_return_objs=True
|
v_return_objs=True
|
||||||
)
|
)
|
||||||
|
@ -11,10 +11,10 @@ from fastapi.security import OAuth2PasswordBearer
|
|||||||
"""
|
"""
|
||||||
系统版本
|
系统版本
|
||||||
"""
|
"""
|
||||||
VERSION = "2.0.0"
|
VERSION = "2.1.0"
|
||||||
|
|
||||||
"""安全警告: 不要在生产中打开调试运行!"""
|
"""安全警告: 不要在生产中打开调试运行!"""
|
||||||
DEBUG = True
|
DEBUG = False
|
||||||
|
|
||||||
"""是否开启演示功能:取消所有POST,DELETE,PUT操作权限"""
|
"""是否开启演示功能:取消所有POST,DELETE,PUT操作权限"""
|
||||||
DEMO = not DEBUG
|
DEMO = not DEBUG
|
||||||
@ -24,6 +24,7 @@ DEMO_WHITE_LIST_PATH = [
|
|||||||
"/auth/token/refresh",
|
"/auth/token/refresh",
|
||||||
"/auth/wx/login",
|
"/auth/wx/login",
|
||||||
"/vadmin/system/dict/types/details",
|
"/vadmin/system/dict/types/details",
|
||||||
|
"/vadmin/resource/images",
|
||||||
"/vadmin/auth/user/export/query/list/to/excel"
|
"/vadmin/auth/user/export/query/list/to/excel"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ from apps.vadmin.record.views import app as vadmin_record_app
|
|||||||
from apps.vadmin.workplace.views import app as vadmin_workplace_app
|
from apps.vadmin.workplace.views import app as vadmin_workplace_app
|
||||||
from apps.vadmin.analysis.views import app as vadmin_analysis_app
|
from apps.vadmin.analysis.views import app as vadmin_analysis_app
|
||||||
from apps.vadmin.help.views import app as vadmin_help_app
|
from apps.vadmin.help.views import app as vadmin_help_app
|
||||||
|
from apps.vadmin.resource.views import app as vadmin_resource_app
|
||||||
|
|
||||||
|
|
||||||
# 引入应用中的路由
|
# 引入应用中的路由
|
||||||
@ -23,4 +24,5 @@ urlpatterns = [
|
|||||||
{"ApiRouter": vadmin_workplace_app, "prefix": "/vadmin/workplace", "tags": ["工作区管理"]},
|
{"ApiRouter": vadmin_workplace_app, "prefix": "/vadmin/workplace", "tags": ["工作区管理"]},
|
||||||
{"ApiRouter": vadmin_analysis_app, "prefix": "/vadmin/analysis", "tags": ["数据分析管理"]},
|
{"ApiRouter": vadmin_analysis_app, "prefix": "/vadmin/analysis", "tags": ["数据分析管理"]},
|
||||||
{"ApiRouter": vadmin_help_app, "prefix": "/vadmin/help", "tags": ["帮助中心管理"]},
|
{"ApiRouter": vadmin_help_app, "prefix": "/vadmin/help", "tags": ["帮助中心管理"]},
|
||||||
|
{"ApiRouter": vadmin_resource_app, "prefix": "/vadmin/resource", "tags": ["资源管理"]},
|
||||||
]
|
]
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||||
from apps.vadmin.auth.models import VadminUser
|
from apps.vadmin.auth.models import VadminUser
|
||||||
from db.db_base import BaseModel
|
from db.db_base import BaseModel
|
||||||
from sqlalchemy import String, Boolean, Integer, ForeignKey
|
from sqlalchemy import String, Boolean, Integer, ForeignKey, Text
|
||||||
|
|
||||||
|
|
||||||
class VadminIssueCategory(BaseModel):
|
class VadminIssueCategory(BaseModel):
|
||||||
@ -42,7 +42,7 @@ class VadminIssue(BaseModel):
|
|||||||
category: Mapped[list["VadminIssueCategory"]] = relationship(foreign_keys=category_id, back_populates='issues')
|
category: Mapped[list["VadminIssueCategory"]] = relationship(foreign_keys=category_id, back_populates='issues')
|
||||||
|
|
||||||
title: Mapped[str] = mapped_column(String(255), index=True, nullable=False, comment="标题")
|
title: Mapped[str] = mapped_column(String(255), index=True, nullable=False, comment="标题")
|
||||||
content: Mapped[str] = mapped_column(String(5000), comment="内容")
|
content: Mapped[str] = mapped_column(Text, comment="内容")
|
||||||
view_number: Mapped[int] = mapped_column(Integer, default=0, comment="查看次数")
|
view_number: Mapped[int] = mapped_column(Integer, default=0, comment="查看次数")
|
||||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否可见")
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否可见")
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ from apps.vadmin.auth.utils.validation import LoginForm, WXLoginForm
|
|||||||
from utils.ip_manage import IPManage
|
from utils.ip_manage import IPManage
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from db.db_base import BaseModel
|
from db.db_base import BaseModel
|
||||||
from sqlalchemy import String, Boolean
|
from sqlalchemy import String, Boolean, Text
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from starlette.requests import Request as StarletteRequest
|
from starlette.requests import Request as StarletteRequest
|
||||||
from user_agents import parse
|
from user_agents import parse
|
||||||
@ -39,8 +39,8 @@ class VadminLoginRecord(BaseModel):
|
|||||||
area_code: Mapped[str | None] = mapped_column(String(255), comment="地区区号")
|
area_code: Mapped[str | None] = mapped_column(String(255), comment="地区区号")
|
||||||
browser: Mapped[str | None] = mapped_column(String(50), comment="浏览器")
|
browser: Mapped[str | None] = mapped_column(String(50), comment="浏览器")
|
||||||
system: Mapped[str | None] = mapped_column(String(50), comment="操作系统")
|
system: Mapped[str | None] = mapped_column(String(50), comment="操作系统")
|
||||||
response: Mapped[str | None] = mapped_column(String(5000), comment="响应信息")
|
response: Mapped[str | None] = mapped_column(Text, comment="响应信息")
|
||||||
request: Mapped[str | None] = mapped_column(String(5000), comment="请求信息")
|
request: Mapped[str | None] = mapped_column(Text, comment="请求信息")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def create_login_record(
|
async def create_login_record(
|
||||||
|
0
kinit-api/apps/vadmin/resource/__init__.py
Normal file
0
kinit-api/apps/vadmin/resource/__init__.py
Normal file
17
kinit-api/apps/vadmin/resource/crud.py
Normal file
17
kinit-api/apps/vadmin/resource/crud.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @version : 1.0
|
||||||
|
# @Create Time : 2023/8/25 13:15
|
||||||
|
# @File : crud.py
|
||||||
|
# @IDE : PyCharm
|
||||||
|
# @desc : 简要说明
|
||||||
|
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from core.crud import DalBase
|
||||||
|
from . import models, schemas
|
||||||
|
|
||||||
|
|
||||||
|
class ImagesDal(DalBase):
|
||||||
|
|
||||||
|
def __init__(self, db: AsyncSession):
|
||||||
|
super(ImagesDal, self).__init__(db, models.VadminImages, schemas.ImagesSimpleOut)
|
1
kinit-api/apps/vadmin/resource/models/__init__.py
Normal file
1
kinit-api/apps/vadmin/resource/models/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .images import VadminImages
|
27
kinit-api/apps/vadmin/resource/models/images.py
Normal file
27
kinit-api/apps/vadmin/resource/models/images.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @version : 1.0
|
||||||
|
# @Create Time : 2023/8/25 13:41
|
||||||
|
# @File : images.py
|
||||||
|
# @IDE : PyCharm
|
||||||
|
# @desc : 图片素材表
|
||||||
|
|
||||||
|
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||||
|
from apps.vadmin.auth.models import VadminUser
|
||||||
|
from db.db_base import BaseModel
|
||||||
|
from sqlalchemy import String, ForeignKey, Integer
|
||||||
|
|
||||||
|
|
||||||
|
class VadminImages(BaseModel):
|
||||||
|
__tablename__ = "vadmin_resource_images"
|
||||||
|
__table_args__ = ({'comment': '图片素材表'})
|
||||||
|
|
||||||
|
filename: Mapped[str] = mapped_column(String(255), nullable=False, comment="原图片名称")
|
||||||
|
image_url: Mapped[str] = mapped_column(String(500), nullable=False, comment="图片链接")
|
||||||
|
|
||||||
|
create_user_id: Mapped[int] = mapped_column(
|
||||||
|
Integer,
|
||||||
|
ForeignKey("vadmin_auth_user.id", ondelete='RESTRICT'),
|
||||||
|
comment="创建人"
|
||||||
|
)
|
||||||
|
create_user: Mapped[VadminUser] = relationship(foreign_keys=create_user_id)
|
1
kinit-api/apps/vadmin/resource/params/__init__.py
Normal file
1
kinit-api/apps/vadmin/resource/params/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .images import ImagesParams
|
27
kinit-api/apps/vadmin/resource/params/images.py
Normal file
27
kinit-api/apps/vadmin/resource/params/images.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @version : 1.0
|
||||||
|
# @Create Time : 2023/8/25 14:59
|
||||||
|
# @File : images.py
|
||||||
|
# @IDE : PyCharm
|
||||||
|
# @desc : 简要说明
|
||||||
|
|
||||||
|
|
||||||
|
from fastapi import Depends
|
||||||
|
from core.dependencies import Paging, QueryParams
|
||||||
|
|
||||||
|
|
||||||
|
class ImagesParams(QueryParams):
|
||||||
|
"""
|
||||||
|
列表分页
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
filename: str = None,
|
||||||
|
params: Paging = Depends()
|
||||||
|
):
|
||||||
|
super().__init__(params)
|
||||||
|
self.filename = ('like', filename)
|
||||||
|
self.v_order = "desc"
|
||||||
|
self.v_order_field = "create_datetime"
|
1
kinit-api/apps/vadmin/resource/schemas/__init__.py
Normal file
1
kinit-api/apps/vadmin/resource/schemas/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .images import Images, ImagesOut, ImagesSimpleOut
|
33
kinit-api/apps/vadmin/resource/schemas/images.py
Normal file
33
kinit-api/apps/vadmin/resource/schemas/images.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @version : 1.0
|
||||||
|
# @Create Time : 2023/8/25 14:49
|
||||||
|
# @File : images.py
|
||||||
|
# @IDE : PyCharm
|
||||||
|
# @desc : 简要说明
|
||||||
|
|
||||||
|
|
||||||
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
from core.data_types import DatetimeStr
|
||||||
|
from apps.vadmin.auth.schemas import UserSimpleOut
|
||||||
|
|
||||||
|
|
||||||
|
class Images(BaseModel):
|
||||||
|
filename: str
|
||||||
|
image_url: str
|
||||||
|
|
||||||
|
create_user_id: int
|
||||||
|
|
||||||
|
|
||||||
|
class ImagesSimpleOut(Images):
|
||||||
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
id: int
|
||||||
|
create_datetime: DatetimeStr
|
||||||
|
update_datetime: DatetimeStr
|
||||||
|
|
||||||
|
|
||||||
|
class ImagesOut(ImagesSimpleOut):
|
||||||
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
create_user: UserSimpleOut
|
60
kinit-api/apps/vadmin/resource/views.py
Normal file
60
kinit-api/apps/vadmin/resource/views.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @version : 1.0
|
||||||
|
# @Create Time : 2023/8/25 9:29
|
||||||
|
# @File : views.py
|
||||||
|
# @IDE : PyCharm
|
||||||
|
# @desc : 简要说明
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, UploadFile
|
||||||
|
from sqlalchemy.orm import joinedload
|
||||||
|
from core.dependencies import IdList
|
||||||
|
from utils.file.aliyun_oss import AliyunOSS, BucketConf
|
||||||
|
from utils.response import SuccessResponse
|
||||||
|
from . import schemas, crud, params, models
|
||||||
|
from apps.vadmin.auth.utils.current import FullAdminAuth
|
||||||
|
from apps.vadmin.auth.utils.validation.auth import Auth
|
||||||
|
from application.settings import ALIYUN_OSS
|
||||||
|
|
||||||
|
app = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
###########################################################
|
||||||
|
# 图片资源管理
|
||||||
|
###########################################################
|
||||||
|
@app.get("/images", summary="获取图片列表")
|
||||||
|
async def get_images_list(p: params.ImagesParams = Depends(), auth: Auth = Depends(FullAdminAuth())):
|
||||||
|
model = models.VadminImages
|
||||||
|
v_options = [joinedload(model.create_user)]
|
||||||
|
v_schema = schemas.ImagesOut
|
||||||
|
datas, count = await crud.ImagesDal(auth.db).get_datas(
|
||||||
|
**p.dict(),
|
||||||
|
v_options=v_options,
|
||||||
|
v_schema=v_schema,
|
||||||
|
v_return_count=True
|
||||||
|
)
|
||||||
|
return SuccessResponse(datas, count=count)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/images", summary="创建图片")
|
||||||
|
async def create_images(file: UploadFile, auth: Auth = Depends(FullAdminAuth())):
|
||||||
|
filepath = f"/resource/images/"
|
||||||
|
result = await AliyunOSS(BucketConf(**ALIYUN_OSS)).upload_image(filepath, file)
|
||||||
|
data = schemas.Images(
|
||||||
|
filename=file.filename,
|
||||||
|
image_url=result,
|
||||||
|
create_user_id=auth.user.id
|
||||||
|
)
|
||||||
|
|
||||||
|
return SuccessResponse(await crud.ImagesDal(auth.db).create_data(data=data))
|
||||||
|
|
||||||
|
|
||||||
|
@app.delete("/images", summary="删除图片", description="硬删除")
|
||||||
|
async def delete_images(ids: IdList = Depends(), auth: Auth = Depends(FullAdminAuth())):
|
||||||
|
await crud.ImagesDal(auth.db).delete_datas(ids.ids, v_soft=False)
|
||||||
|
return SuccessResponse("删除成功")
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/images/{data_id}", summary="获取图片信息")
|
||||||
|
async def get_images(data_id: int, auth: Auth = Depends(FullAdminAuth())):
|
||||||
|
return SuccessResponse(await crud.ImagesDal(auth.db).get_data(data_id, v_schema=schemas.ImagesSimpleOut))
|
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user