mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-09-30 23:26:41 +08:00
feat(components): 增加影音管理
This commit is contained in:
parent
7f748f2a61
commit
9505d5b4c1
2
.env
2
.env
@ -7,7 +7,7 @@ VITE_APP_TITLE=Soybean管理系统
|
||||
VITE_APP_DESC=SoybeanAdmin是一个中后台管理系统模版
|
||||
|
||||
# 权限路由模式: static | dynamic
|
||||
VITE_AUTH_ROUTE_MODE=static
|
||||
VITE_AUTH_ROUTE_MODE=dynamic
|
||||
|
||||
# 路由首页(根路由重定向), 用于static模式的权限路由,dynamic模式取决于后端返回的路由首页
|
||||
VITE_ROUTE_HOME_PATH=/dashboard/analysis
|
||||
|
@ -1121,6 +1121,28 @@ export const routeModel: Record<Auth.RoleType, AuthRoute.Route[]> = {
|
||||
icon: 'fluent:book-information-24-regular',
|
||||
order: 8
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'movie',
|
||||
path: '/movie',
|
||||
component: 'basic',
|
||||
children: [
|
||||
{
|
||||
name: 'movie_list',
|
||||
path: '/movie/list',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '影片列表',
|
||||
requiresAuth: false,
|
||||
icon: 'icon-park-outline:analysis'
|
||||
}
|
||||
}
|
||||
],
|
||||
meta: {
|
||||
title: '影音管理',
|
||||
icon: 'mdi:monitor-dashboard',
|
||||
order: 9
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -27,7 +27,6 @@ export async function createDynamicRouteGuard(
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
await route.initAuthRoute();
|
||||
|
||||
if (to.name === routeName('not-found')) {
|
||||
|
3
src/typings/page-route.d.ts
vendored
3
src/typings/page-route.d.ts
vendored
@ -52,6 +52,8 @@ declare namespace PageRoute {
|
||||
| 'management_role'
|
||||
| 'management_route'
|
||||
| 'management_user'
|
||||
| 'movie'
|
||||
| 'movie_list'
|
||||
| 'multi-menu'
|
||||
| 'multi-menu_first'
|
||||
| 'multi-menu_first_second-new'
|
||||
@ -106,6 +108,7 @@ declare namespace PageRoute {
|
||||
| 'management_role'
|
||||
| 'management_route'
|
||||
| 'management_user'
|
||||
| 'movie_list'
|
||||
| 'multi-menu_first_second-new_third'
|
||||
| 'multi-menu_first_second'
|
||||
| 'plugin_charts_antv'
|
||||
|
@ -31,23 +31,23 @@ const { loading, startLoading, endLoading, empty, setEmpty } = useLoadingEmpty()
|
||||
|
||||
const columns: DataTableColumn[] = [
|
||||
{
|
||||
title: 'Name',
|
||||
title: '姓名',
|
||||
key: 'name',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: 'Age',
|
||||
title: '年龄',
|
||||
key: 'age',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: 'Address',
|
||||
title: '地址',
|
||||
key: 'address',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
key: 'action',
|
||||
title: 'Action',
|
||||
title: '操作',
|
||||
align: 'center',
|
||||
render: () => {
|
||||
return (
|
||||
|
@ -33,6 +33,7 @@ export const views: Record<
|
||||
management_role: () => import('./management/role/index.vue'),
|
||||
management_route: () => import('./management/route/index.vue'),
|
||||
management_user: () => import('./management/user/index.vue'),
|
||||
movie_list: () => import('./movie/list/index.vue'),
|
||||
'multi-menu_first_second-new_third': () => import('./multi-menu/first/second-new/third/index.vue'),
|
||||
'multi-menu_first_second': () => import('./multi-menu/first/second/index.vue'),
|
||||
plugin_charts_antv: () => import('./plugin/charts/antv/index.vue'),
|
||||
|
69
src/views/movie/list/components/column-setting.vue
Normal file
69
src/views/movie/list/components/column-setting.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<n-popover placement="bottom" trigger="click">
|
||||
<template #trigger>
|
||||
<n-button size="small" type="primary">
|
||||
<icon-ant-design-setting-outlined class="mr-4px text-16px" />
|
||||
表格列设置
|
||||
</n-button>
|
||||
</template>
|
||||
<div class="w-180px">
|
||||
<vue-draggable v-model="list" item-key="key">
|
||||
<template #item="{ element }">
|
||||
<div v-if="element.key" class="flex-y-center h-36px px-12px hover:bg-primary_active">
|
||||
<icon-mdi-drag class="mr-8px text-20px cursor-move" />
|
||||
<n-checkbox v-model:checked="element.checked">
|
||||
{{ element.title }}
|
||||
</n-checkbox>
|
||||
</div>
|
||||
</template>
|
||||
</vue-draggable>
|
||||
</div>
|
||||
</n-popover>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import type { DataTableColumn } from 'naive-ui';
|
||||
import VueDraggable from 'vuedraggable';
|
||||
|
||||
type Column = DataTableColumn<UserManagement.User>;
|
||||
|
||||
interface Props {
|
||||
columns: Column[];
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:columns', columns: Column[]): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
type List = Column & { checked?: boolean };
|
||||
|
||||
const list = ref(initList());
|
||||
|
||||
function initList(): List[] {
|
||||
return props.columns.map(item => ({ ...item, checked: true }));
|
||||
}
|
||||
|
||||
watch(
|
||||
list,
|
||||
newValue => {
|
||||
const newColumns = newValue.filter(item => item.checked);
|
||||
|
||||
const columns: Column[] = newColumns.map(item => {
|
||||
const column = { ...item };
|
||||
delete column.checked;
|
||||
|
||||
return column;
|
||||
}) as Column[];
|
||||
|
||||
emit('update:columns', columns);
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
150
src/views/movie/list/components/table-action-modal.vue
Normal file
150
src/views/movie/list/components/table-action-modal.vue
Normal file
@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<n-modal v-model:show="modalVisible" preset="card" :title="title" class="w-700px">
|
||||
<n-form ref="formRef" label-placement="left" :label-width="80" :model="formModel" :rules="rules">
|
||||
<n-grid :cols="24" :x-gap="18">
|
||||
<n-form-item-grid-item :span="12" label="用户名" path="userName">
|
||||
<n-input v-model:value="formModel.userName" />
|
||||
</n-form-item-grid-item>
|
||||
<n-form-item-grid-item :span="12" label="年龄" path="age">
|
||||
<n-input-number v-model:value="formModel.age" clearable />
|
||||
</n-form-item-grid-item>
|
||||
<n-form-item-grid-item :span="12" label="性别" path="gender">
|
||||
<n-radio-group v-model:value="formModel.gender">
|
||||
<n-radio v-for="item in genderOptions" :key="item.value" :value="item.value">{{ item.label }}</n-radio>
|
||||
</n-radio-group>
|
||||
</n-form-item-grid-item>
|
||||
<n-form-item-grid-item :span="12" label="手机号" path="phone">
|
||||
<n-input v-model:value="formModel.phone" />
|
||||
</n-form-item-grid-item>
|
||||
<n-form-item-grid-item :span="12" label="邮箱" path="email">
|
||||
<n-input v-model:value="formModel.email" />
|
||||
</n-form-item-grid-item>
|
||||
<n-form-item-grid-item :span="12" label="状态" path="userStatus">
|
||||
<n-select v-model:value="formModel.userStatus" :options="userStatusOptions" />
|
||||
</n-form-item-grid-item>
|
||||
</n-grid>
|
||||
<n-space class="w-full pt-16px" :size="24" justify="end">
|
||||
<n-button class="w-72px" @click="closeModal">取消</n-button>
|
||||
<n-button class="w-72px" type="primary" @click="handleSubmit">确定</n-button>
|
||||
</n-space>
|
||||
</n-form>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, reactive, watch } from 'vue';
|
||||
import type { FormInst, FormItemRule } from 'naive-ui';
|
||||
import { genderOptions, userStatusOptions } from '@/constants';
|
||||
import { formRules, createRequiredFormRule } from '@/utils';
|
||||
|
||||
export interface Props {
|
||||
/** 弹窗可见性 */
|
||||
visible: boolean;
|
||||
/**
|
||||
* 弹窗类型
|
||||
* add: 新增
|
||||
* edit: 编辑
|
||||
*/
|
||||
type?: 'add' | 'edit';
|
||||
/** 编辑的表格行数据 */
|
||||
editData?: UserManagement.User | null;
|
||||
}
|
||||
|
||||
export type ModalType = NonNullable<Props['type']>;
|
||||
|
||||
defineOptions({ name: 'TableActionModal' });
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: 'add',
|
||||
editData: null
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const modalVisible = computed({
|
||||
get() {
|
||||
return props.visible;
|
||||
},
|
||||
set(visible) {
|
||||
emit('update:visible', visible);
|
||||
}
|
||||
});
|
||||
const closeModal = () => {
|
||||
modalVisible.value = false;
|
||||
};
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<ModalType, string> = {
|
||||
add: '添加用户',
|
||||
edit: '编辑用户'
|
||||
};
|
||||
return titles[props.type];
|
||||
});
|
||||
|
||||
const formRef = ref<HTMLElement & FormInst>();
|
||||
|
||||
type FormModel = Pick<UserManagement.User, 'userName' | 'age' | 'gender' | 'phone' | 'email' | 'userStatus'>;
|
||||
|
||||
const formModel = reactive<FormModel>(createDefaultFormModel());
|
||||
|
||||
const rules: Record<keyof FormModel, FormItemRule | FormItemRule[]> = {
|
||||
userName: createRequiredFormRule('请输入用户名'),
|
||||
age: createRequiredFormRule('请输入年龄'),
|
||||
gender: createRequiredFormRule('请选择性别'),
|
||||
phone: formRules.phone,
|
||||
email: formRules.email,
|
||||
userStatus: createRequiredFormRule('请选择用户状态')
|
||||
};
|
||||
|
||||
function createDefaultFormModel(): FormModel {
|
||||
return {
|
||||
userName: '',
|
||||
age: null,
|
||||
gender: null,
|
||||
phone: '',
|
||||
email: null,
|
||||
userStatus: null
|
||||
};
|
||||
}
|
||||
|
||||
function handleUpdateFormModel(model: Partial<FormModel>) {
|
||||
Object.assign(formModel, model);
|
||||
}
|
||||
|
||||
function handleUpdateFormModelByModalType() {
|
||||
const handlers: Record<ModalType, () => void> = {
|
||||
add: () => {
|
||||
const defaultFormModel = createDefaultFormModel();
|
||||
handleUpdateFormModel(defaultFormModel);
|
||||
},
|
||||
edit: () => {
|
||||
if (props.editData) {
|
||||
handleUpdateFormModel(props.editData);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handlers[props.type]();
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await formRef.value?.validate();
|
||||
window.$message?.success('新增成功!');
|
||||
closeModal();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
newValue => {
|
||||
if (newValue) {
|
||||
handleUpdateFormModelByModalType();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
201
src/views/movie/list/index.vue
Normal file
201
src/views/movie/list/index.vue
Normal file
@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<n-card title="用户管理" :bordered="false" class="rounded-16px shadow-sm">
|
||||
<n-space class="pb-12px" justify="space-between">
|
||||
<n-space>
|
||||
<n-button type="primary" @click="handleAddTable">
|
||||
<icon-ic-round-plus class="mr-4px text-20px" />
|
||||
新增
|
||||
</n-button>
|
||||
<n-button type="error">
|
||||
<icon-ic-round-delete class="mr-4px text-20px" />
|
||||
删除
|
||||
</n-button>
|
||||
<n-button type="success">
|
||||
<icon-uil:export class="mr-4px text-20px" />
|
||||
导出Excel
|
||||
</n-button>
|
||||
</n-space>
|
||||
<n-space align="center" :size="18">
|
||||
<n-button size="small" type="primary" @click="getTableData">
|
||||
<icon-mdi-refresh class="mr-4px text-16px" :class="{ 'animate-spin': loading }" />
|
||||
刷新表格
|
||||
</n-button>
|
||||
<column-setting v-model:columns="columns" />
|
||||
</n-space>
|
||||
</n-space>
|
||||
<n-data-table :columns="columns" :data="tableData" :loading="loading" :pagination="pagination" />
|
||||
<table-action-modal v-model:visible="visible" :type="modalType" :edit-data="editData" />
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { reactive, ref } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import { NButton, NPopconfirm, NSpace, NTag } from 'naive-ui';
|
||||
import type { DataTableColumns, PaginationProps } from 'naive-ui';
|
||||
import { genderLabels, userStatusLabels } from '@/constants';
|
||||
import { fetchUserList } from '@/service';
|
||||
import { useBoolean, useLoading } from '@/hooks';
|
||||
import TableActionModal from './components/table-action-modal.vue';
|
||||
import type { ModalType } from './components/table-action-modal.vue';
|
||||
import ColumnSetting from './components/column-setting.vue';
|
||||
|
||||
const { loading, startLoading, endLoading } = useLoading(false);
|
||||
const { bool: visible, setTrue: openModal } = useBoolean();
|
||||
|
||||
const tableData = ref<UserManagement.User[]>([]);
|
||||
function setTableData(data: UserManagement.User[]) {
|
||||
tableData.value = data;
|
||||
}
|
||||
|
||||
async function getTableData() {
|
||||
startLoading();
|
||||
const { data } = await fetchUserList();
|
||||
if (data) {
|
||||
setTimeout(() => {
|
||||
setTableData(data);
|
||||
endLoading();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
const columns: Ref<DataTableColumns<UserManagement.User>> = ref([
|
||||
{
|
||||
type: 'selection',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
title: '序号',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
key: 'userName',
|
||||
title: '用户名',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
key: 'age',
|
||||
title: '用户年龄',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
key: 'gender',
|
||||
title: '性别',
|
||||
align: 'center',
|
||||
render: row => {
|
||||
if (row.gender) {
|
||||
const tagTypes: Record<UserManagement.GenderKey, NaiveUI.ThemeColor> = {
|
||||
'0': 'success',
|
||||
'1': 'warning'
|
||||
};
|
||||
|
||||
return <NTag type={tagTypes[row.gender]}>{genderLabels[row.gender]}</NTag>;
|
||||
}
|
||||
|
||||
return <span></span>;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'phone',
|
||||
title: '手机号码',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
key: 'email',
|
||||
title: '邮箱',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
key: 'userStatus',
|
||||
title: '状态',
|
||||
align: 'center',
|
||||
render: row => {
|
||||
if (row.userStatus) {
|
||||
const tagTypes: Record<UserManagement.UserStatusKey, NaiveUI.ThemeColor> = {
|
||||
'1': 'success',
|
||||
'2': 'error',
|
||||
'3': 'warning',
|
||||
'4': 'default'
|
||||
};
|
||||
|
||||
return <NTag type={tagTypes[row.userStatus]}>{userStatusLabels[row.userStatus]}</NTag>;
|
||||
}
|
||||
return <span></span>;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
title: '操作',
|
||||
align: 'center',
|
||||
render: row => {
|
||||
return (
|
||||
<NSpace justify={'center'}>
|
||||
<NButton size={'small'} onClick={() => handleEditTable(row.id)}>
|
||||
编辑
|
||||
</NButton>
|
||||
<NPopconfirm onPositiveClick={() => handleDeleteTable(row.id)}>
|
||||
{{
|
||||
default: () => '确认删除',
|
||||
trigger: () => <NButton size={'small'}>删除</NButton>
|
||||
}}
|
||||
</NPopconfirm>
|
||||
</NSpace>
|
||||
);
|
||||
}
|
||||
}
|
||||
]) as Ref<DataTableColumns<UserManagement.User>>;
|
||||
|
||||
const modalType = ref<ModalType>('add');
|
||||
|
||||
function setModalType(type: ModalType) {
|
||||
modalType.value = type;
|
||||
}
|
||||
|
||||
const editData = ref<UserManagement.User | null>(null);
|
||||
|
||||
function setEditData(data: UserManagement.User | null) {
|
||||
editData.value = data;
|
||||
}
|
||||
|
||||
function handleAddTable() {
|
||||
openModal();
|
||||
setModalType('add');
|
||||
}
|
||||
|
||||
function handleEditTable(rowId: string) {
|
||||
const findItem = tableData.value.find(item => item.id === rowId);
|
||||
if (findItem) {
|
||||
setEditData(findItem);
|
||||
}
|
||||
setModalType('edit');
|
||||
openModal();
|
||||
}
|
||||
|
||||
function handleDeleteTable(rowId: string) {
|
||||
window.$message?.info(`点击了删除,rowId为${rowId}`);
|
||||
}
|
||||
|
||||
const pagination: PaginationProps = reactive({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
showSizePicker: true,
|
||||
pageSizes: [10, 15, 20, 25, 30],
|
||||
onChange: (page: number) => {
|
||||
pagination.page = page;
|
||||
},
|
||||
onUpdatePageSize: (pageSize: number) => {
|
||||
pagination.pageSize = pageSize;
|
||||
pagination.page = 1;
|
||||
}
|
||||
});
|
||||
|
||||
function init() {
|
||||
getTableData();
|
||||
}
|
||||
|
||||
// 初始化
|
||||
init();
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
Loading…
Reference in New Issue
Block a user