diff --git a/src/views/system/notice/NoticeModel.vue b/src/views/system/notice/NoticeModel.vue
new file mode 100644
index 0000000..907a861
--- /dev/null
+++ b/src/views/system/notice/NoticeModel.vue
@@ -0,0 +1,58 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit">
+    <BasicForm @register="registerForm" />
+  </BasicModal>
+</template>
+<script lang="ts" setup name="NoticeModal">
+import { ref, computed, unref } from 'vue'
+import { BasicModal, useModalInner } from '@/components/Modal'
+import { BasicForm, useForm } from '@/components/Form'
+import { formSchema } from './notice.data'
+import { createNoticeApi, getNoticeApi, updateNoticeApi } from '@/api/system/notice'
+
+const emit = defineEmits(['success', 'register'])
+const isUpdate = ref(true)
+const rowId = ref()
+
+const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
+  labelWidth: 100,
+  baseColProps: { span: 24 },
+  schemas: formSchema,
+  showActionButtonGroup: false,
+  actionColOptions: {
+    span: 23
+  }
+})
+
+const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
+  resetFields()
+  setModalProps({ confirmLoading: false })
+  isUpdate.value = !!data?.isUpdate
+
+  if (unref(isUpdate)) {
+    const res = await getNoticeApi(data.record.id)
+    rowId.value = res.id
+    setFieldsValue({
+      ...res
+    })
+  }
+})
+
+const getTitle = computed(() => (!unref(isUpdate) ? '新增岗位' : '编辑岗位'))
+
+async function handleSubmit() {
+  try {
+    const values = await validate()
+    setModalProps({ confirmLoading: true })
+    if (unref(isUpdate)) {
+      await updateNoticeApi(values)
+    } else {
+      await createNoticeApi(values)
+    }
+    closeModal()
+    emit('success')
+  } finally {
+    setModalProps({ confirmLoading: false })
+  }
+}
+</script>
diff --git a/src/views/system/notice/index.vue b/src/views/system/notice/index.vue
index 83cefa1..2e2dd69 100644
--- a/src/views/system/notice/index.vue
+++ b/src/views/system/notice/index.vue
@@ -1,3 +1,82 @@
 <template>
-  <div>index</div>
+  <div>
+    <BasicTable @register="registerTable">
+      <template #toolbar>
+        <a-button type="primary" @click="handleCreate"> 新增 </a-button>
+      </template>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                icon: 'clarity:note-edit-line',
+                onClick: handleEdit.bind(null, record)
+              },
+              {
+                icon: 'ant-design:delete-outlined',
+                color: 'error',
+                popConfirm: {
+                  title: '是否确认删除',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record)
+                }
+              }
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <NoticeModal @register="registerModal" @success="reload()" />
+  </div>
 </template>
+<script lang="ts" setup name="Post">
+import { BasicTable, useTable, TableAction } from '@/components/Table'
+import { deleteNoticeApi, getNoticePageApi } from '@/api/system/notice'
+import { useModal } from '@/components/Modal'
+import NoticeModal from './NoticeModel.vue'
+import { columns, searchFormSchema } from './notice.data'
+import { useMessage } from '@/hooks/web/useMessage'
+
+const { createMessage } = useMessage()
+const [registerModal, { openModal }] = useModal()
+const [registerTable, { reload }] = useTable({
+  title: '公告列表',
+  api: getNoticePageApi,
+  columns,
+  formConfig: {
+    labelWidth: 120,
+    schemas: searchFormSchema
+  },
+  useSearchForm: true,
+  showTableSetting: true,
+  showIndexColumn: false,
+  actionColumn: {
+    width: 120,
+    title: '操作',
+    dataIndex: 'action',
+    fixed: 'right'
+  }
+})
+
+function handleCreate() {
+  openModal(true, {
+    isUpdate: false
+  })
+}
+
+function handleEdit(record: Recordable) {
+  openModal(true, {
+    record,
+    isUpdate: true
+  })
+}
+
+async function handleDelete(record: Recordable) {
+  console.log(record)
+  const res = await deleteNoticeApi(record.id)
+  if (res) {
+    createMessage.success('删除成功')
+    reload()
+  }
+}
+</script>
diff --git a/src/views/system/notice/notice.data.ts b/src/views/system/notice/notice.data.ts
new file mode 100644
index 0000000..1e42cd6
--- /dev/null
+++ b/src/views/system/notice/notice.data.ts
@@ -0,0 +1,95 @@
+import { BasicColumn, FormSchema, useRender } from '@/components/Table'
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+
+export const columns: BasicColumn[] = [
+  {
+    title: '公告编号',
+    dataIndex: 'id',
+    width: 100
+  },
+  {
+    title: '公告标题',
+    dataIndex: 'title',
+    width: 180
+  },
+  {
+    title: '公告类型',
+    dataIndex: 'type',
+    width: 180,
+    customRender: ({ text }) => {
+      return useRender.renderDict(text, DICT_TYPE.SYSTEM_NOTICE_TYPE)
+    }
+  },
+  {
+    title: '状态',
+    dataIndex: 'status',
+    width: 180,
+    customRender: ({ text }) => {
+      return useRender.renderDict(text, DICT_TYPE.COMMON_STATUS)
+    }
+  },
+  {
+    title: '创建时间',
+    dataIndex: 'createTime',
+    customRender: ({ text }) => {
+      return useRender.renderDate(text)
+    }
+  }
+]
+
+export const searchFormSchema: FormSchema[] = [
+  {
+    label: '公告标题',
+    field: 'title',
+    component: 'Input',
+    colProps: { span: 8 }
+  },
+  {
+    label: '公告状态',
+    field: 'status',
+    component: 'Select',
+    componentProps: {
+      options: getIntDictOptions(DICT_TYPE.COMMON_STATUS)
+    },
+    colProps: { span: 8 }
+  }
+]
+
+export const formSchema: FormSchema[] = [
+  {
+    label: '编号',
+    field: 'id',
+
+    show: false,
+    component: 'Input'
+  },
+  {
+    label: '公告标题',
+    field: 'title',
+    required: true,
+    component: 'Input'
+  },
+  {
+    label: '公告类型',
+    field: 'type',
+    component: 'Select',
+    defaultValue: 0,
+    componentProps: {
+      options: getIntDictOptions(DICT_TYPE.SYSTEM_NOTICE_TYPE)
+    }
+  },
+  {
+    label: '状态',
+    field: 'status',
+    component: 'Select',
+    defaultValue: 0,
+    componentProps: {
+      options: getIntDictOptions(DICT_TYPE.COMMON_STATUS)
+    }
+  },
+  {
+    label: '内容',
+    field: 'content',
+    component: 'InputTextArea'
+  }
+]