بنقرة واحدة
add-page
Add a new frontend page with proper layout, table, and API integration following OMS UI standards
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Add a new frontend page with proper layout, table, and API integration following OMS UI standards
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Add a new API endpoint with controller, service, and DTOs following OMS patterns
Add a new database entity with migrations for all three databases (MySQL, MSSQL, PostgreSQL)
Add a new mobile page to frontend.h5 using Vant 4 components
Audit backend code to ensure all queries properly filter by OrgId for multi-tenant security
Debug and troubleshoot issues in the OMS system with systematic approach
Create database migration files for all three databases (MySQL, MSSQL, PostgreSQL) when schema changes are needed
| name | add-page |
| description | Add a new frontend page with proper layout, table, and API integration following OMS UI standards |
| disable-model-invocation | false |
| allowed-tools | Read, Write, Edit, Glob, Grep |
Add a new frontend page to the OMS PC frontend following UI standards and best practices.
When the user requests to add a new page, follow these steps:
Location: frontend/src/api/{module}.ts
import request from '@/utils/request'
export interface {EntityName} {
id: number
orgId: number
propertyName: string
createdAt: string
updatedAt: string
}
export interface Create{EntityName}Dto {
propertyName: string
}
export interface Update{EntityName}Dto {
propertyName: string
}
export const {entityName}Api = {
getList: () => request.get<{EntityName}[]>('/api/{EntityName}'),
getById: (id: number) => request.get<{EntityName}>(`/api/{EntityName}/${id}`),
create: (data: Create{EntityName}Dto) => request.post<{EntityName}>('/api/{EntityName}', data),
update: (id: number, data: Update{EntityName}Dto) =>
request.put<{EntityName}>(`/api/{EntityName}/${id}`, data),
delete: (id: number) => request.delete(`/api/{EntityName}/${id}`)
}
Location: frontend/src/views/{module}/{PageName}.vue
CRITICAL UI Standards:
SplitLayout for resizable panels (NOT manual Flex/Grid)MoreOutlined dropdownExample List Page:
<template>
<div class="page-container">
<a-card title="{Entity} Management" :bordered="false">
<template #extra>
<a-space>
<a-button type="primary" @click="handleAdd">
<template #icon><PlusOutlined /></template>
Add {Entity}
</a-button>
</a-space>
</template>
<a-table
:columns="columns"
:data-source="dataSource"
:loading="loading"
:pagination="pagination"
@change="handleTableChange"
:scroll="{ y: 'calc(100vh - 300px)' }"
>
<!-- Row number column -->
<template #bodyCell="{ column, index }">
<template v-if="column.key === 'rowNumber'">
{{ (pagination.current - 1) * pagination.pageSize + index + 1 }}
</template>
<template v-else-if="column.key === 'action'">
<a-space>
<a-tooltip title="Edit">
<a-button type="link" size="small" @click="handleEdit(record)">
<template #icon><EditOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip title="Delete">
<a-button type="link" danger size="small" @click="handleDelete(record)">
<template #icon><DeleteOutlined /></template>
</a-button>
</a-tooltip>
</a-space>
</template>
</template>
</a-table>
</a-card>
<!-- Edit Modal -->
<a-modal
v-model:open="modalVisible"
:title="modalTitle"
:confirm-loading="modalLoading"
@ok="handleModalOk"
width="600px"
>
<a-form
ref="formRef"
:model="formData"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 16 }"
>
<a-form-item label="Property Name" name="propertyName" :rules="[{ required: true }]">
<a-input v-model:value="formData.propertyName" />
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { {entityName}Api, type {EntityName}, type Create{EntityName}Dto } from '@/api/{module}'
const loading = ref(false)
const dataSource = ref<{EntityName}[]>([])
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0
})
const columns = [
{
title: 'No.',
key: 'rowNumber',
width: 60,
align: 'center'
},
{
title: 'Property Name',
dataIndex: 'propertyName',
key: 'propertyName'
},
{
title: 'Created At',
dataIndex: 'createdAt',
key: 'createdAt',
width: 180
},
{
title: 'Action',
key: 'action',
width: 120,
align: 'center'
}
]
const modalVisible = ref(false)
const modalLoading = ref(false)
const modalTitle = ref('')
const editingId = ref<number | null>(null)
const formRef = ref()
const formData = reactive<Create{EntityName}Dto>({
propertyName: ''
})
const fetchData = async () => {
loading.value = true
try {
const res = await {entityName}Api.getList()
dataSource.value = res.data || []
pagination.total = res.data?.length || 0
} catch (error) {
message.error('Failed to load data')
} finally {
loading.value = false
}
}
const handleAdd = () => {
modalTitle.value = 'Add {Entity}'
editingId.value = null
Object.assign(formData, { propertyName: '' })
modalVisible.value = true
}
const handleEdit = (record: {EntityName}) => {
modalTitle.value = 'Edit {Entity}'
editingId.value = record.id
Object.assign(formData, {
propertyName: record.propertyName
})
modalVisible.value = true
}
const handleModalOk = async () => {
try {
await formRef.value.validate()
modalLoading.value = true
if (editingId.value) {
await {entityName}Api.update(editingId.value, formData)
message.success('Updated successfully')
} else {
await {entityName}Api.create(formData)
message.success('Created successfully')
}
modalVisible.value = false
fetchData()
} catch (error) {
message.error('Operation failed')
} finally {
modalLoading.value = false
}
}
const handleDelete = (record: {EntityName}) => {
Modal.confirm({
title: 'Confirm Delete',
content: 'Are you sure you want to delete this item?',
onOk: async () => {
try {
await {entityName}Api.delete(record.id)
message.success('Deleted successfully')
fetchData()
} catch (error) {
message.error('Delete failed')
}
}
})
}
const handleTableChange = (pag: any) => {
pagination.current = pag.current
pagination.pageSize = pag.pageSize
fetchData()
}
onMounted(() => {
fetchData()
})
</script>
<style scoped lang="less">
.page-container {
height: 100%;
padding: 16px;
.ant-card {
height: 100%;
}
}
</style>
Location: frontend/src/router/index.ts or dynamic routes
{
path: '/{module}/{page-name}',
name: '{PageName}',
component: () => import('@/views/{module}/{PageName}.vue'),
meta: {
title: '{Page Title}',
requiresAuth: true
}
}
Update menu configuration to include the new page.
SplitLayout for resizable panels (not manual Flex/Grid)title tooltips (not text)MoreOutlined dropdownuseDraggableModal)loading statemessage.error()message.success()<SplitLayout
:default-size="300"
:min-size="200"
:max-size="500"
storage-key="page-split"
>
<template #left>
<!-- Left panel content -->
</template>
<template #right>
<!-- Right panel content -->
</template>
</SplitLayout>
<script setup>
import { useDraggableModal } from '@/composables/useDraggableModal'
const { modalRef, handleMouseDown } = useDraggableModal()
</script>
<template>
<a-modal
ref="modalRef"
v-model:open="visible"
:title="title"
@mousedown="handleMouseDown"
>
<!-- Modal content -->
</a-modal>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-space>
<a-tooltip title="Edit">
<a-button type="link" size="small" @click="handleEdit(record)">
<template #icon><EditOutlined /></template>
</a-button>
</a-tooltip>
<a-dropdown>
<a-button type="link" size="small">
<template #icon><MoreOutlined /></template>
</a-button>
<template #overlay>
<a-menu>
<a-menu-item @click="handleView(record)">
<EyeOutlined /> View
</a-menu-item>
<a-menu-item @click="handleCopy(record)">
<CopyOutlined /> Copy
</a-menu-item>
<a-menu-item danger @click="handleDelete(record)">
<DeleteOutlined /> Delete
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-space>
</template>
</template>