update จัดการ web services

This commit is contained in:
DESKTOP-1R2VSQH\Lenovo ThinkPad E490 2025-08-13 14:28:58 +07:00
parent a5f27c342b
commit daec5d06ec
4 changed files with 392 additions and 111 deletions

View file

@ -6,6 +6,11 @@ import { useCounterMixin } from "@/stores/mixin";
import { useDataStoreManage } from "@/modules/06_webservices/stores/manage";
import type { DataOption } from "@/modules/06_webservices/interface/index/Main";
import type {
DataSystems,
Propertys,
DataAttributes,
} from "@/modules/06_webservices/interface/response/Main";
/** importComponents*/
import DialogHeader from "@/components/DialogHeader.vue";
@ -34,36 +39,56 @@ const props = defineProps({
},
});
// title dialog
const titleName = computed(() => {
return isEdit.value ? "แก้ไข API" : "เพิ่ม API";
});
// "all"
const options = computed<DataOption[]>(() => {
return systemList.value.filter((e) => e.id !== "all") ?? [];
});
// Main
const mainFields = computed(() => {
return availableFields.value.filter((field) => field.isMain === true);
});
const systemOptions = ref<DataOption[]>(options.value); // Default to first option
//
const getSelectedCount = computed(() => {
return (tbName: string) => {
if (!selectedAttributes.value[tbName]) return 0;
const apiPath = ref<string>("");
return Object.values(selectedAttributes.value[tbName]).filter(
(selected) => selected === true
).length;
};
});
const systemMain = ref<string>(""); // Default system
const systemOptions = ref<DataOption[]>(options.value); // Default to first option
const apiPath = ref<string>(""); // Path API
const splitterModel = ref<number>(15); // Initial splitter position
const tabs = ref<string>(""); // Tabs
// Form data
const formData = reactive({
name: "",
methodApi: "GET",
system: "",
isActive: true,
isActive: false,
});
// state fields attributes
const availableFields = ref<any[]>([]);
const selectedAttributes = ref<Record<string, Record<string, boolean>>>({});
const loading = ref(false);
const validationErrors = ref<string[]>([]);
const availableFields = ref<DataSystems[]>([]); // fields
const selectedAttributes = ref<Record<string, Record<string, boolean>>>({}); // attributes
const dataAttributes = ref<DataAttributes[]>([]); // attributes
const loading = ref<boolean>(false); //
const validationErrors = ref<string[]>([]); // error validation
/**
* งขอม fields ของ API ตามชอระบบ
* @param system อระบบทองการดงขอม fields
*/
async function fetchData(system: string) {
availableFields.value = [];
loading.value = true;
@ -80,56 +105,64 @@ async function fetchData(system: string) {
selectedAttributes.value[field.tb] = {};
if (field.propertys && field.propertys.length > 0) {
field.propertys.forEach((prop: any) => {
field.propertys.forEach((prop: Propertys) => {
// properties group
selectedAttributes.value[field.tb][prop.propertyName] = false;
});
}
});
tabs.value =
availableFields.value.length > 0 ? availableFields.value[0].tb : "";
} catch (error) {
console.error("Error fetching fields:", error);
messageError($q, error);
availableFields.value = [];
} finally {
loading.value = false;
}
}
/**
* งขอม API ตาม ID
* @param id ID ของ API องการดงขอม
*/
async function fetchDataId(id: string) {
showLoader();
try {
const res = await http.get(`${config.API.apiManage}${id}`);
const data = res.data.result;
systemMain.value = data.system;
formData.name = data.name;
formData.methodApi = data.methodApi;
formData.system = data.system;
formData.isActive = data.isActive;
apiPath.value = data.pathApi;
dataAttributes.value = data.apiAttributes || [];
// fields attributes
await fetchData(data.system);
console.log(data.apiAttributes);
// selectedAttributes
if (data.apiAttributes?.length > 0) {
data.apiAttributes.forEach(
({ tbName, propertyKey }: { tbName: string; propertyKey: string }) => {
if (selectedAttributes.value[tbName]?.[propertyKey] !== undefined) {
selectedAttributes.value[tbName][propertyKey] = true;
}
}
);
}
updateSelectedAttributes(data.apiAttributes);
} catch (error) {
console.error("Error fetching API by ID:", error);
messageError($q, error);
} finally {
hideLoader();
}
}
/**
* งกนเมอเลอกระบบจาก select
* @param value อระบบทกเลอก
*/
async function onSelectSystem(value: string) {
await fetchData(value);
console.log("value", value);
if (systemMain.value === value && dataAttributes.value?.length > 0) {
updateSelectedAttributes(dataAttributes.value);
}
}
// validation
/** ฟังก์ชันตรวจสอบ validation */
function validateSelection(): boolean {
validationErrors.value = [];
@ -151,6 +184,7 @@ function validateSelection(): boolean {
return validationErrors.value.length === 0;
}
/** ฟังก์ชันบันทึกข้อมูล */
function onSubmit() {
if (!validateSelection()) {
validationErrors.value.forEach((error) => {
@ -211,6 +245,21 @@ function onSubmit() {
});
}
/**
* งกนอพเดต selectedAttributes ตามขอมลทงมา
* @param data อมลทองการอพเดต selectedAttributes
*/
function updateSelectedAttributes(
data: Array<{ tbName: string; propertyKey: string }>
) {
if (!data?.length) return;
data.forEach(({ tbName, propertyKey }) => {
if (selectedAttributes.value[tbName]?.[propertyKey] !== undefined) {
selectedAttributes.value[tbName][propertyKey] = true;
}
});
}
/**
* function นหาคำใน select
* @param val คำคนหา
@ -225,6 +274,7 @@ function filterSelector(val: string, update: Function) {
});
}
/** ฟังก์ชันปิด dialog และรีเซ็ตข้อมูล */
function onCloseDialog() {
modal.value = false;
formData.name = "";
@ -235,6 +285,8 @@ function onCloseDialog() {
availableFields.value = [];
selectedAttributes.value = {};
validationErrors.value = [];
dataAttributes.value = [];
systemMain.value = "";
}
watch(modal, (newVal) => {
@ -257,11 +309,10 @@ watch(modal, (newVal) => {
<DialogHeader :tittle="titleName" :close="onCloseDialog" />
<q-separator />
<q-card-section class="col q-pt-none scroll">
<div class="q-pa-md">
<div class="row q-col-gutter-md q-mb-lg">
<div class="col-12 col-md-4">
<div class="col-12 col-md-6">
<q-input
v-model="formData.name"
label="API Name"
@ -272,7 +323,14 @@ watch(modal, (newVal) => {
:rules="[(val:string) => !!val || 'กรุณากรอก API Name']"
/>
</div>
<div class="col-12 col-md-4">
<div class="col-12 col-md-6">
<q-checkbox
v-model="formData.isActive"
color="primary"
:label="formData.isActive ? 'เปิดใช้งาน' : 'ปิดใช้งาน'"
/>
</div>
<div class="col-12 col-md-1">
<q-select
v-model="formData.methodApi"
label="Method"
@ -283,7 +341,7 @@ watch(modal, (newVal) => {
disable
/>
</div>
<div class="col-12 col-md-4">
<div class="col-12 col-md-5">
<q-input
v-model="apiPath"
outlined
@ -295,8 +353,187 @@ watch(modal, (newVal) => {
</div>
</div>
<q-card bordered class="row col-12 text-dark">
<div class="bg-grey-1 q-pa-sm col-12 row items-center">
<div class="q-pl-sm text-weight-bold text-subtitle2">
เลอกระบบ
</div>
</div>
<div class="col-12"><q-separator /></div>
<div class="row col-12 q-pa-md">
<div class="col-12 row q-col-gutter-md">
<div class="col-12 col-md-4">
<q-select
dense
hide-bottom-space
outlined
option-label="name"
option-value="id"
emit-value
map-options
v-model="formData.system"
:options="systemOptions"
use-input
hide-selected
fill-input
lazy-rules
:rules="[(val:string) => !!val || 'กรุณาเลือกระบบข้อมูล']"
@update:model-value="onSelectSystem"
@filter="(inputValue: string,doneFn: Function) => filterSelector(inputValue, doneFn )"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
ไมอม
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<div v-if="loading" class="col-12 text-center">
<q-spinner-dots
color="primary"
size="30px"
class="q-mt-md"
/>
</div>
<q-splitter
v-model="splitterModel"
style="height: 55vh"
v-else-if="
!loading &&
formData.system !== '' &&
availableFields.length > 0
"
>
<template v-slot:before>
<div>
<q-tabs
v-model="tabs"
vertical
active-color="primary"
indicator-color="transparent"
dense
>
<q-tab
v-for="items in availableFields"
:key="items.tb"
:name="items.tb"
:label="
items.description +
' (' +
getSelectedCount(items.tb) +
')'
"
style="
justify-content: flex-start;
text-align: left;
"
/>
</q-tabs>
</div>
</template>
<template v-slot:after>
<div class="q-pa-none">
<q-tab-panels
v-model="tabs"
animated
swipeable
vertical
transition-prev="jump-up"
transition-next="jump-up"
>
<q-tab-panel
v-for="value in availableFields"
:key="value.tb"
:name="value.tb"
class="q-pt-none"
>
<div
v-if="
formData.system !== '' &&
value.propertys &&
value.propertys.length > 0
"
>
<div class="row q-col-gutter-sm">
<!-- Loop through properties of this field -->
<div
v-for="property in value.propertys"
:key="property.propertyName"
class="col-xs-12 col-sm-6 col-md-4 col-lg-3"
>
<!-- Property Card -->
<q-card flat bordered>
<q-card-section>
<!-- Property Name Header -->
<div class="text-subtitle2 text-primary">
{{ property.propertyName }}
</div>
<!-- Checkbox with details -->
<q-item
tag="label"
v-ripple
class="q-pa-none"
>
<q-item-section avatar>
<q-checkbox
v-model="
selectedAttributes[value.tb][
property.propertyName
]
"
color="primary"
/>
</q-item-section>
<q-item-section>
<q-item-label class="text-body2">
{{
property.comment ||
property.propertyName
}}
</q-item-label>
<q-item-label
caption
class="text-grey-6"
>
Type: {{ property.type }}
</q-item-label>
</q-item-section>
</q-item>
</q-card-section>
</q-card>
</div>
</div>
</div>
<!-- Empty state for this tab -->
<div
v-else-if="
!value.propertys || value.propertys.length === 0
"
class="text-center q-pa-lg text-grey-6"
>
<q-icon name="info_outline" size="48px" />
<div class="q-mt-md">
ไม properties สำหรบหมวดน
</div>
</div>
</q-tab-panel>
</q-tab-panels>
</div>
</template>
</q-splitter>
</div>
</div>
</q-card>
<!-- System Selection -->
<div class="row q-mb-lg">
<!-- <div class="row q-mb-lg">
<div class="col-12 col-md-4">
<div class="text-subtitle2 q-mb-sm">เลอกระบบ</div>
<q-select
@ -326,77 +563,7 @@ watch(modal, (newVal) => {
</template>
</q-select>
</div>
</div>
<!-- Attributes Section -->
<div
class="q-mb-lg"
v-if="formData.system !== '' && availableFields.length > 0"
>
<div class="row q-col-gutter-md">
<!-- Loop through available fields -->
<div
v-for="field in availableFields"
:key="field.tb"
class="col-xs-6 col-md-3"
>
<!-- วขอหมวดหม - ไมสามารถเลอกได -->
<div class="text-subtitle1 border-bottom">
{{ field.description }}
</div>
<!-- Properties สามารถเลอกได -->
<div
v-if="field.propertys && field.propertys.length > 0"
v-for="property in field.propertys"
:key="property.propertyName"
>
<q-item tag="label" v-ripple>
<q-item-section avatar>
<q-checkbox
v-model="
selectedAttributes[field.tb][property.propertyName]
"
/>
</q-item-section>
<q-item-section>
<q-item-label>{{ property.comment }}</q-item-label>
<q-item-label caption>
{{ property.propertyName }} ({{ property.type }})
</q-item-label>
</q-item-section>
</q-item>
</div>
<!-- าไม properties แสดงขอความวาง -->
<div v-else class="q-ml-md text-grey-6 text-caption q-mb-sm">
ไม attributes อยสำหรบหมวดน
</div>
</div>
</div>
</div>
<!-- Loading state -->
<div v-else-if="loading" class="text-center q-pa-md">
<q-spinner-dots size="40px" color="primary" />
<div class="q-mt-sm">กำลงโหลดขอม...</div>
</div>
<!-- Empty state -->
<div
v-else-if="
formData.system !== '' &&
availableFields.length === 0 &&
!loading
"
class="text-center q-pa-md"
>
<q-icon name="info" size="40px" color="grey" />
<div class="q-mt-sm text-grey">
ไมอม Attributes สำหรบระบบน
</div>
</div>
</div> -->
</div>
</q-card-section>

View file

@ -31,4 +31,9 @@ interface DataOption {
name: string;
}
export type { ListWebServices, ListApi, DataOption, ItemsDropdown };
interface Pagination {
page: number;
rowsPerPage: number;
}
export type { ListWebServices, ListApi, DataOption, ItemsDropdown, Pagination };

View file

@ -1,3 +1,5 @@
import type { D } from "@fullcalendar/core/internal-common";
interface ResListWebServices {
id: string;
topic: string;
@ -26,4 +28,49 @@ interface ResApHistory {
ipApi: string;
}
export type { ResListWebServices, ResApiName, ResApHistory };
interface ListSystems {
code: string;
name: string;
}
interface ListApiManages {
code: string;
createdAt: Date;
id: string;
isActive: boolean;
lastUpdatedAt: Date;
methodApi: string;
name: string;
pathApi: string;
system: string;
}
interface DataSystems {
description: string;
isMain: boolean;
tb: string;
propertys: Propertys[];
}
interface Propertys {
comment: string;
key: string;
propertyName: string;
type: string;
}
interface DataAttributes {
propertyKey: string;
tbName: string;
}
export type {
ResListWebServices,
ResApiName,
ResApHistory,
ListSystems,
ListApiManages,
DataSystems,
Propertys,
DataAttributes,
};

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import { onMounted, ref, toRefs } from "vue";
import { onMounted, ref, toRefs, watch } from "vue";
import { useQuasar } from "quasar";
import { useCounterMixin } from "@/stores/mixin";
@ -9,9 +9,14 @@ import config from "@/app.config";
/** importType*/
import type { QTableProps } from "quasar";
import type { DataOption } from "@/modules/06_webservices/interface/index/Main";
import type { ListWebServices } from "@/modules/06_webservices/interface/index/Main";
import type { ResApiName } from "@/modules/06_webservices/interface/response/Main";
import type {
DataOption,
Pagination,
} from "@/modules/06_webservices/interface/index/Main";
import type {
ListSystems,
ListApiManages,
} from "@/modules/06_webservices/interface/response/Main";
/** importComponents*/
import DialogApi from "@/modules/06_webservices/components/DialogApi.vue";
@ -33,9 +38,12 @@ const systemOptions = ref<DataOption[]>(systemList.value);
/** Table*/
const page = ref<number>(1); //
const pageSize = ref<number>(10); //
const rows = ref<any[]>([]); // webservices
const rows = ref<ListApiManages[]>([]); // webservices
const total = ref<number>(0); //
const maxPage = ref<number>(1); //
const keyword = ref<string>(""); // webservices
const systemfilter = ref<string>("all"); //
const isActive = ref<boolean>(true); //
const visibleColumns = ref<string[]>([
"system",
"name",
@ -99,12 +107,13 @@ const modalDialog = ref<boolean>(false); //ตัวแปรเปิดปิ
const isEditData = ref<boolean>(false); //
const apiId = ref<string>(""); // id api
/** ฟังก์ชันดึงข้อมูลรายการระบบ */
async function fetchSystemData() {
if (systemList.value.length > 1) return; //
try {
const res = await http.get(`${config.API.apiManage}systems`);
const data = res.data.result;
const systemData = data.map((item: any) => ({
const systemData = data.map((item: ListSystems) => ({
id: item.code,
name: item.name,
}));
@ -114,6 +123,7 @@ async function fetchSystemData() {
}
}
/** ฟังก์ชันดึงข้อมูลรายการ API */
async function fetchData() {
try {
const queryParams = {
@ -121,18 +131,25 @@ async function fetchData() {
pageSize: pageSize.value,
keyword: keyword.value,
systems: systemfilter.value === "all" ? "" : systemfilter.value,
isActive: isActive.value,
};
const res = await http.get(`${config.API.apiManage}lists`, {
params: queryParams,
});
const result = res.data.result;
total.value = result.total;
maxPage.value = Math.ceil(total.value / pageSize.value);
rows.value = result.data;
} catch (error) {
messageError(error);
}
}
/**
* งกนลบขอมลรายการ API
* @param id ID ของ API องการลบ
*/
function onDeleteData(id: string) {
dialogRemove($q, async () => {
try {
@ -148,19 +165,24 @@ function onDeleteData(id: string) {
});
}
/**
* งกนแกไขขอมลรายการ API
* @param id ID ของ API องการแกไข
*/
function onEditData(id: string) {
isEditData.value = true;
modalDialog.value = true;
apiId.value = id; // id api
}
/** ฟังก์ชันค้นหาข้อมูลในตารางใหม่ */
function onSearchDataTable() {
page.value = 1; // 1
fetchData();
}
/**
* function นหาคำใน select
* งกนหาคำใน select
* @param val คำคนหา
* @param update function
*/
@ -173,6 +195,23 @@ function filterSelector(val: string, update: Function) {
});
}
/**
* งกนอปเดท paging
* @param initialPagination อม pagination
*/
async function updatePagination(initialPagination: Pagination) {
pageSize.value = initialPagination.rowsPerPage;
}
/** function callback เมื่อมีการเปลี่ยนหน้า*/
watch(
() => pageSize.value,
() => {
onSearchDataTable();
}
);
/** ทำงานเมื่อ component ถูก mount */
onMounted(async () => {
try {
showLoader();
@ -232,13 +271,20 @@ onMounted(async () => {
<q-space />
<div class="row q-col-gutter-sm col-xs-12 col-sm-auto">
<div class="col-xs-12 col-sm-auto">
<q-toggle
v-model="isActive"
label="แสดงเฉพาะที่ใช้งาน"
@update:model-value="onSearchDataTable"
/>
</div>
<div class="col-xs-12 col-sm-auto">
<q-input
borderless
dense
outlined
v-model="keyword"
placeholder="ค้นหา"
placeholder="ค้นหาชื่อ API"
@keydown.enter.prevent="onSearchDataTable"
>
<template v-slot:append>
@ -277,6 +323,7 @@ onMounted(async () => {
dense
:rows-per-page-options="[10, 25, 50, 100]"
:visible-columns="visibleColumns"
@update:pagination="updatePagination"
>
<template v-slot:header="props">
<q-tr :props="props">
@ -317,6 +364,21 @@ onMounted(async () => {
</q-td>
</q-tr>
</template>
<template v-slot:pagination="scope">
งหมด {{ total }} รายการ
<q-pagination
v-model="page"
active-color="primary"
color="dark"
:max="Number(maxPage)"
size="sm"
boundary-links
direction-links
:max-pages="5"
@update:model-value="fetchData"
></q-pagination>
</template>
</d-table>
</div>