Commit 03bc1613 authored by Tình Trương's avatar Tình Trương

update UI banner

parent f8acd8d7
......@@ -28,3 +28,8 @@ export enum PostStatus {
active = 1,
inactive = 0,
}
export enum BannerStatus {
active = 1,
inactive = 0,
}
<template>
<q-dialog
persistent
:model-value="isOpened"
@update:model-value="$emit('update:isOpened', $event)"
>
<q-card class="full-width" style="max-width: 50rem" bordered>
<q-form
greedy
@submit.prevent="
$emit('saveBannerInfo', {});
$emit('update:isOpened', false);
"
>
<q-card-section class="q-pa-none">
<q-item>
<q-item-section>
<q-item-label class="text-h6 text-weight-regular">{{
isUpdate
? $t('banner.dialogLabel.title.addBanner')
: $t('field.dialogLabel.title.updateBanner')
}}</q-item-label>
</q-item-section>
</q-item>
</q-card-section>
<q-separator />
<q-card-section
class="overflow-auto"
style="max-height: calc(100vh - 10rem)"
>
<div class="row q-col-gutter-sm">
<div class="col-6">
<div class="row flex-center q-pt-sm">
<div>
<q-card style="margin-bottom: 8px" v-if="image !== null">
<q-img
:src="image"
style="height: 268px; width: 350px"
@click="uploadAvatar"
>
</q-img>
<q-icon
name="mdi-close-circle"
color="red"
style="position: absolute; right: 0; font-size: 18px"
@click="deleteAvatar"
></q-icon>
</q-card>
<q-card v-else style="margin-bottom: 8px">
<q-img
src="~/assets/noavatar.png"
style="height: 265px; width: 350px"
></q-img>
</q-card>
<q-card @click="uploadAvatar">
<div align="center" class="flex flex-center q-py-xs">
<q-icon
name="mdi-plus-circle-outline"
:size="'xs'"
></q-icon>
<div class="q-mt-xs">
{{ $t('banner.uploadImg') }}
</div>
</div>
<input
ref="upload"
hidden
type="file"
@change="selectedFile($event.target.files)"
accept="image/png, image/jpeg"
/>
</q-card>
</div>
</div>
</div>
<div class="col-6">
<q-input
:model-value="name"
@update:model-value="$emit('update:name', $event)"
:label="$t('banner.dialogLabel.bannerLabels.name')"
type="text"
class="q-my-sm"
outlined
:rules="nameRules"
clearable
></q-input>
<q-select
:model-value="type"
@update:model-value="$emit('update:type', $event)"
:label="$t('banner.dialogLabel.bannerLabels.type')"
:rules="typeRules"
:options="typeOptions"
map-options
option-value="id"
option-label="name"
type="text"
class="q-my-sm"
outlined
></q-select>
<q-select
:model-value="location"
@update:model-value="$emit('update:location', $event)"
:label="$t('banner.dialogLabel.bannerLabels.location')"
:rules="locationRules"
:options="locationOptions"
map-options
option-value="id"
option-label="name"
type="text"
class="q-my-sm"
outlined
></q-select>
<q-input
:model-value="urlTarget"
@update:model-value="$emit('update:urlTarget', $event)"
:label="$t('banner.dialogLabel.bannerLabels.urlTarget')"
:rules="urlTargetRules"
type="text"
class="q-my-sm"
outlined
clearable
></q-input>
<div class="q-pt-sm">
<span class="text-body1">{{
$t('banner.dialogLabel.bannerLabels.status')
}}</span
><q-toggle
:model-value="status"
:true-value="1"
:false-value="2"
@update:model-value="$emit('update:status', $event)"
/>
</div>
</div>
</div>
</q-card-section>
<q-card-actions align="right">
<div>
<q-btn
color="grey"
no-caps
style="width: 90px"
class="q-mr-sm"
:label="$t('banner.crudActions.cancel')"
@click="$emit('update:isOpened', false)"
/>
<q-btn
type="submit"
color="primary"
no-caps
style="width: 90px"
:label="$t('banner.crudActions.save')"
/>
</div>
</q-card-actions>
</q-form>
</q-card>
</q-dialog>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { i18n } from 'src/boot/i18n';
export default defineComponent({
props: {
isOpened: {
type: Boolean,
required: true,
},
isUpdate: { type: Boolean, default: false },
name: { type: String, required: true },
image: { type: String, required: true },
urlTarget: { type: String, required: true },
status: { type: Number, required: true },
type: { type: Number, required: true },
typeOptions: { type: Array, required: true },
location: { type: Number, required: true },
locationOptions: { type: Array, required: true },
},
setup(_, context) {
const selectedFile = (value: FileList) => {
context.emit('SetAvatar', {
file: value[0],
url: URL.createObjectURL(value[0]),
});
};
const deleteAvatar = () => {
context.emit('deleteAvatar');
};
const upload = ref(null);
const uploadAvatar = () => {
// eslint-disable-next-line
// @ts-ignore
// eslint-disable-next-line
upload.value?.click();
};
const urlTargetRules = [
(val?: string) =>
(val && val.trim().length) ||
i18n.global.t('banner.validateMessages.requireUrlTarget'),
];
const nameRules = [
(val?: string) =>
(val && val.trim().length) ||
i18n.global.t('banner.validateMessages.requireName'),
];
const typeRules = [
(val?: number) =>
val !== undefined ||
i18n.global.t('banner.validateMessages.requireType'),
];
const locationRules = [
(val?: number) =>
val !== undefined ||
i18n.global.t('banner.validateMessages.requireLocation'),
];
return {
urlTargetRules,
nameRules,
typeRules,
locationRules,
selectedFile,
deleteAvatar,
uploadAvatar,
};
},
emits: [
'update:isOpened',
'update:name',
'update:urlTarget',
'update:type',
'update:location',
'update:status',
'update:image',
'SetAvatar',
'deleteAvatar',
'saveBannerInfo',
],
});
</script>
......@@ -721,4 +721,62 @@ export default {
uploadImg: 'Tải lên ảnh bìa',
},
},
//banner
banner: {
title: 'Danh sách banner',
uploadImg: 'Tải lên ảnh banner',
tableColumnsBanner: {
stt: 'STT',
name: 'Tên banner',
type: 'Loại banner',
location: 'Vị trí',
image: 'Ảnh',
urlTarget: 'Url targer',
status: 'Trạng thái',
action: 'Chức năng',
},
statusLabel: {
active: 'Đang hoạt động',
inactive: 'Ngừng hoạt động',
},
dialogLabel: {
title: {
addBanner: 'Thêm banner',
updateBanner: 'Cập nhật banner',
},
bannerLabels: {
name: 'Tên banner *',
type: 'Loại banner *',
location: 'Vị trí *',
image: 'Ảnh',
urlTarget: 'Url target',
status: 'Trạng thái',
},
},
toolTipMessage: {
updateBanner: 'Cập nhật',
deleteBanner: 'Xoá',
},
crudActions: {
save: 'Lưu',
cancel: 'Đóng',
},
validateMessages: {
requireName: 'Vui lòng nhập Tên banner',
requireType: 'Vui lòng chọn Loại banner',
requireLocation: 'Vui lòng chọn Vị trí',
requireUrlTarget: 'Vui lòng nhập Url target',
},
confirmActionsTitle: {
confirmDeleteBannerTitle: 'Xác nhận',
confirmDeleteBannerCancelBtnLabel: 'Huỷ',
confirmDeleteBannerContent: 'Bạn có chắc chắn muốn xoá Banner này không?',
},
actionMessages: {
addNewBannerAccess: 'Thêm banner thành công',
deleteBannerAccess: 'Xoá banner thành công',
updateBannerAccess: 'Cập nhật banner thành công',
},
},
};
<template>
<div class="row q-col-gutter-sm flex-center q-mt-sm">
<div class="col-auto text-h6 text-weight-regular flex flex-center q-mr-md">
{{ $t('banner.title') }}
<q-separator vertical spaced />
</div>
<q-space></q-space>
<div class="col-2">
<q-input
v-model="nameBanner"
type="text"
dense
outlined
label="Tên banner"
clearable
></q-input>
</div>
<div class="col-auto">
<q-btn
color="primary"
no-caps
:label="$t('crudActions.search')"
@click="getListBanner"
style="width: 7.14rem"
></q-btn>
</div>
<div class="col-auto">
<q-btn
color="primary"
no-caps
:label="$t('crudActions.add')"
style="width: 7.14rem"
@click="openAddBannerDialog"
>
</q-btn>
</div>
<div class="col-12 q-mt-sm">
<q-table
:rows="bannerTableRows"
:columns="bannerTableColumns"
row-key="bannerName"
separator="cell"
:no-data-label="$t('emptyData')"
hide-pagination
class="sticky-header-table"
>
<template v-slot:body-cell-action="">
<q-td style="padding: 0" class="flex flex-center">
<q-btn flat round color="primary" icon="mdi-account-edit-outline">
<!-- @click="openUpdateFieldDialog(item.row.id)" -->
<q-tooltip :offset="[20, 10]">{{
$t('banner.toolTipMessage.updateBanner')
}}</q-tooltip>
</q-btn>
<q-btn flat round color="primary" icon="mdi-delete-outline">
<!-- @click="confirmDeleteField(item.row.id)" -->
<q-tooltip :offset="[20, 10]">{{
$t('banner.toolTipMessage.deleteBanner')
}}</q-tooltip>
</q-btn>
</q-td>
</template>
<template v-slot:body-cell-status="rowData">
<q-td>
<div align="center">
<q-chip
:color="
rowData.value === BannerStatus.active ? 'positive' : 'orange'
"
text-color="white"
size="sm"
>
{{
rowData.value === BannerStatus.active
? $t('banner.statusLabel.active')
: $t('banner.statusLabel.inactive')
}}
</q-chip>
</div>
</q-td>
</template>
<template v-slot:body-cell-image="image">
<q-td style="padding: auto; height: 100%" class="flex flex-center">
<q-img
style="width: 9rem"
fit="contain"
:ratio="16 / 9"
:src="configImg.API_IMAGE_ENDPOINT + image.row.image"
></q-img>
</q-td>
</template>
</q-table>
</div>
<AddUpdateBannerDialog
v-model:isOpened="addBannerDialogIsOpened"
v-model:name="name"
v-model:image="image"
v-model:urlTarget="urlTarget"
v-model:location="location"
v-model:type="type"
v-model:status="status"
:typeOptions="typeOptions"
:locationOptions="locationOptions"
@SetAvatar="setAvatar($event)"
@deleteAvatar="deleteAvatar"
isUpdate
@saveBannerInfo="addBanner"
/>
<AddUpdateBannerDialog
v-model:isOpened="updateBannerDialogIsOpened"
v-model:name="name"
v-model:image="image"
v-model:urlTarget="urlTarget"
v-model:location="location"
v-model:type="type"
v-model:status="status"
:typeOptions="typeOptions"
:locationOptions="locationOptions"
@SetAvatar="setAvatar($event)"
@deleteAvatar="deleteAvatar"
@saveUpdateInfo="updateBanner"
/>
</div>
</template>
<script lang="ts">
import { i18n } from 'src/boot/i18n';
import { defineComponent, onMounted, ref, Ref } from 'vue';
import { API_PATHS } from 'src/assets/configurations';
import { AxiosResponse } from 'axios';
import { api, BaseResponseBody } from 'src/boot/axios';
import { BannerType, FileUploadType } from 'src/assets/type';
import { config } from 'src/assets/configurations';
import { BannerStatus } from 'src/assets/enums';
import AddUpdateBannerDialog from 'components/add-update-banner/index.vue';
import { Dialog, Notify } from 'quasar';
export type AvatarType = {
file?: File;
url?: string | null;
};
export default defineComponent({
components: {
AddUpdateBannerDialog,
},
setup() {
const bannerTableColumns = [
// {
// name: 'index',
// field: 'index',
// required: true,
// label: 'STT',
// align: 'center',
// sortable: false,
// },
{
name: 'name',
field: 'name',
required: true,
label: i18n.global.t('banner.tableColumnsBanner.name'),
headerStyle: 'text-align: center !important;',
align: 'left',
sortable: false,
},
{
name: 'type',
field: 'type',
required: true,
label: i18n.global.t('banner.tableColumnsBanner.type'),
headerStyle: 'text-align: center !important;',
align: 'left',
sortable: false,
},
{
name: 'location',
field: 'location',
required: true,
label: i18n.global.t('banner.tableColumnsBanner.location'),
headerStyle: 'text-align: center !important;',
align: 'left',
sortable: false,
},
{
name: 'image',
field: 'image',
required: true,
label: i18n.global.t('banner.tableColumnsBanner.image'),
headerStyle: 'text-align: center !important;',
align: 'center',
sortable: false,
},
{
name: 'urlTarget',
field: 'urlTarget',
required: true,
label: i18n.global.t('banner.tableColumnsBanner.urlTarget'),
headerStyle: 'text-align: center !important;',
align: 'left',
sortable: false,
},
{
name: 'status',
field: 'status',
required: true,
label: i18n.global.t('banner.tableColumnsBanner.status'),
headerStyle: 'text-align: center !important;',
align: 'center',
sortable: false,
},
{
name: 'action',
field: 'action',
required: true,
label: i18n.global.t('banner.tableColumnsBanner.action'),
headerStyle: 'text-align: center !important;',
align: 'center',
sortable: false,
},
];
const bannerTableRows: Ref<unknown[]> = ref([]);
const addBannerDialogIsOpened = ref(false);
const updateBannerDialogIsOpened = ref(false);
const name = ref('');
const urlTarget = ref('');
const image: Ref<string | null> = ref(null);
const type: Ref<number | undefined> = ref(undefined);
const location: Ref<number | undefined> = ref(undefined);
const typeOptions: Ref<unknown[]> = ref([]);
const locationOptions: Ref<unknown[]> = ref([]);
const status: Ref<number> = ref(BannerStatus.active);
const bannerId: Ref<number | undefined> = ref(undefined);
const nameBanner = ref('');
const avatarFile: Ref<File | null> = ref(null);
const avatarUploaded: Ref<string> = ref('');
//gọi api ds
const getListBanner = async () => {
// const response = (await api({
// url: API_PATHS.getListBanner,
// method: 'GET',
// params: {
// name: nameBanner.value,
// },
// })) as AxiosResponse<BaseResponseBody<BannerType[]>>;
// if (response.data.error.code === config.API_RES_CODE.OK.code) {
// bannerTableRows.value = response.data.data;
// }
};
const confirmDeleteBanner = (id: number) => {
Dialog.create({
title: i18n.global.t(
'banner.confirmActionsTitle.confirmDeleteBannerTitle'
),
message: i18n.global.t(
'banner.confirmActionsTitle.confirmDeleteBannerContent'
),
cancel: i18n.global.t(
'banner.confirmActionsTitle.confirmDeleteBannerCancelBtnLabel'
),
color: 'negative',
}).onOk(() => {
void deleteBanner(id);
});
};
//gói api xóa
const deleteBanner = async (id: number) => {
// try {
// const deleteResult = (await api({
// url: API_PATHS.deleteBanner,
// method: 'GET',
// params: {
// id: id,
// },
// })) as AxiosResponse<BaseResponseBody<unknown>>;
// if (deleteResult.data.error.code === config.API_RES_CODE.OK.code) {
// Notify.create({
// type: 'positive',
// message: i18n.global.t('banner.actionMessages.deleteBannerAccess'),
// });
// void getListBanner();
// }
// } catch (error) {}
};
const openAddBannerDialog = () => {
name.value = '';
status.value = BannerStatus.active;
addBannerDialogIsOpened.value = true;
};
//gọi api add
const addBanner = async () => {
// const data = {
// name: name.value,
// status: status.value,
// };
// const response = (await api({
// url: API_PATHS.addBanner,
// method: 'POST',
// data,
// })) as AxiosResponse<BaseResponseBody<AddBannerType[]>>;
// if (response.data.error.code === config.API_RES_CODE.OK.code) {
// addBannerDialogIsOpened.value = false;
// Notify.create({
// type: 'positive',
// message: i18n.global.t('banner.actionMessages.addNewBannerAccess'),
// actions: [{ icon: 'close', color: 'white' }],
// });
// void getListBanner();
// }
};
const openUpdateBannerDialog = (id: number) => {
void getDetailBanner(id);
updateBannerDialogIsOpened.value = true;
};
//gọi api detail
const getDetailBanner = async (id: number) => {
// try {
// const response = (await api({
// url: API_PATHS.getDetailBanner,
// method: 'GET',
// params: {
// id: id,
// },
// })) as AxiosResponse<BaseResponseBody<DetailBannerType>>;
// if (response.data.error.code === config.API_RES_CODE.OK.code) {
// bannerId.value = response.data.data.id;
// name.value = response.data.data.name;
// status.value = response.data.data.status;
// }
// } catch (error) {}
};
//gọi api update
const updateBanner = async () => {
// const data = {
// id: bannerId.value,
// name: name.value,
// status: status.value,
// };
// const response = (await api({
// url: API_PATHS.updateBanner,
// method: 'POST',
// data,
// })) as AxiosResponse<BaseResponseBody<UpdateBannerType[]>>;
// if (response.data.error.code === config.API_RES_CODE.OK.code) {
// updateBannerDialogIsOpened.value = false;
// Notify.create({
// type: 'positive',
// message: i18n.global.t('banner.actionMessages.updateBannerAccess'),
// actions: [{ icon: 'close', color: 'white' }],
// });
// void getListBanner();
// }
};
const setAvatar = (value: { file?: File; url?: string }) => {
avatarFile.value = value.file as File;
image.value = value.url as string;
};
const callApiUploadAvatar = async (file: File) => {
try {
const bodyFormData = new FormData();
bodyFormData.append('file', file);
const response = (await api({
headers: { 'Content-Type': 'multipart/form-data' },
url: 'http://cms.vab.xteldev.com/file/upload/',
method: 'POST',
data: bodyFormData,
})) as AxiosResponse<BaseResponseBody<FileUploadType>>;
if (response.data.error.code === config.API_RES_CODE.OK.code) {
return response.data.data.fileName;
} else {
return '';
}
} catch (error) {
return '';
}
};
const deleteAvatar = () => {
image.value = null;
};
const configImg = config;
onMounted(() => {
void getListBanner();
});
return {
addBannerDialogIsOpened,
updateBannerDialogIsOpened,
bannerTableColumns,
bannerTableRows,
getListBanner,
BannerStatus,
openAddBannerDialog,
name,
status,
addBanner,
confirmDeleteBanner,
deleteBanner,
openUpdateBannerDialog,
getDetailBanner,
updateBanner,
nameBanner,
urlTarget,
image,
type,
location,
typeOptions,
locationOptions,
setAvatar,
callApiUploadAvatar,
deleteAvatar,
configImg,
avatarUploaded,
};
},
});
</script>
......@@ -14,6 +14,7 @@ export enum Pages {
field = 'linh-vuc-hoat-dong',
post = 'bai-viet',
postCategory = 'danh-muc-bai-viet',
banner = 'banner',
}
const routes: RouteRecordRaw[] = [
......@@ -86,6 +87,11 @@ const routes: RouteRecordRaw[] = [
component: () => import('pages/danh-muc-bai-viet/index.vue'),
name: Pages.postCategory,
},
{
path: 'banner',
component: () => import('pages/banner/index.vue'),
name: Pages.banner,
},
],
},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment