diff --git a/src/api/system/notify/template.ts b/src/api/system/notify/template.ts
index ebf8a9e..f595415 100644
--- a/src/api/system/notify/template.ts
+++ b/src/api/system/notify/template.ts
@@ -34,3 +34,30 @@ export function listSimplePosts() {
 export function exportNotifyTemplateExcel(params) {
   return defHttp.download({ url: '/system/notify-template/export-excel', params }, '导出站内信模板.xls')
 }
+
+export type SendNotifyParam = {
+  userId: number
+  templateCode: string
+  templateParams: {
+    [key: string]: string
+  }
+}
+
+export type NotifyTemplate = {
+  name: string
+  code: string
+  type: number
+  nickname: string
+  content: string
+  status: number
+  remark?: any
+  id: number
+  params: string[]
+  createTime: number
+  key: string
+}
+
+// 发送
+export function sendNotify(data: SendNotifyParam) {
+  return defHttp.post({ url: '/system/notify-template/send-notify', data })
+}
diff --git a/src/locales/lang/zh-CN/action.ts b/src/locales/lang/zh-CN/action.ts
index eaa422e..270cf12 100644
--- a/src/locales/lang/zh-CN/action.ts
+++ b/src/locales/lang/zh-CN/action.ts
@@ -9,5 +9,6 @@ export default {
   export: '导出',
   import: '导入',
   sync: '同步',
-  cancel: '取消'
+  cancel: '取消',
+  send: '发送'
 }
diff --git a/src/views/infra/codegen/components/ImportTableModal.vue b/src/views/infra/codegen/components/ImportTableModal.vue
index 0452071..92ffcd2 100644
--- a/src/views/infra/codegen/components/ImportTableModal.vue
+++ b/src/views/infra/codegen/components/ImportTableModal.vue
@@ -24,7 +24,8 @@ const [registerTable, { getSelectRowKeys, getForm }] = useTable({
   useSearchForm: true,
   pagination: false,
   showTableSetting: false,
-  showIndexColumn: false
+  showIndexColumn: false,
+  immediate: false
 })
 
 const [registerModal, { setModalProps, closeModal }] = useModalInner(async () => {
diff --git a/src/views/system/notify/template/SendNotifyModal.vue b/src/views/system/notify/template/SendNotifyModal.vue
new file mode 100644
index 0000000..fc86a15
--- /dev/null
+++ b/src/views/system/notify/template/SendNotifyModal.vue
@@ -0,0 +1,89 @@
+<template>
+  <BasicModal v-bind="$attrs" title="发送站内信" @register="innerRegister" @ok="submit">
+    <BasicForm @register="register" :schemas="reactiveSchemas" />
+  </BasicModal>
+</template>
+
+<script setup lang="ts">
+import { BasicModal, useModalInner } from '@/components/Modal'
+import { BasicForm, FormSchema, useForm } from '@/components/Form'
+import { reactive, ref } from 'vue'
+import { useMessage } from '@/hooks/web/useMessage'
+import { sendNotify, SendNotifyParam, NotifyTemplate } from '@/api/system/notify/template'
+import { baseSendSchemas } from './template.data'
+
+defineOptions({ name: 'SendNotifyModal' })
+
+const { createMessage } = useMessage()
+let reactiveSchemas: FormSchema[] = reactive([])
+const templateCode = ref<string>('')
+
+const [register, { setFieldsValue, getFieldsValue, validateFields, resetFields, clearValidate, setProps }] = useForm({
+  labelWidth: 100,
+  // schemas: reactiveSchemas, 这里用动态绑定会有问题
+  baseColProps: {
+    span: 24
+  },
+  showSubmitButton: false,
+  showResetButton: false
+})
+
+const [innerRegister, { changeLoading, closeModal }] = useModalInner((data: NotifyTemplate) => {
+  resetForm()
+  data.params.forEach((item) => {
+    const dySchema: FormSchema = {
+      // 这里加上前缀 防止和content/userId字段重名
+      field: `key-${item}`,
+      label: `参数{${item}} `,
+      component: 'Input',
+      componentProps: {
+        placeholder: `输入{${item}}`
+      },
+      required: true
+    }
+    reactiveSchemas.push(dySchema)
+  })
+  const { content, code } = data
+  setFieldsValue({ content })
+  templateCode.value = code
+})
+
+const submit = async () => {
+  try {
+    setProps({ disabled: true })
+    changeLoading(true)
+    await validateFields()
+    const fields = getFieldsValue()
+    const data: SendNotifyParam = {
+      userId: fields.userId,
+      templateCode: templateCode.value,
+      templateParams: {}
+    }
+    Object.keys(fields).forEach((key) => {
+      if (key === 'content' || key === 'userId') {
+        return
+      }
+      // 去掉 - 后的key
+      const realKey = key.split('-')[1]
+      data.templateParams[realKey] = fields[key]
+    })
+    await sendNotify(data)
+    createMessage.success(`发送站内信成功`)
+    closeModal()
+  } finally {
+    setProps({ disabled: false })
+    changeLoading(false)
+  }
+}
+
+const resetForm = () => {
+  // 这里需要每次清空动态表单
+  reactiveSchemas.splice(0, reactiveSchemas.length)
+  reactiveSchemas.push(...baseSendSchemas)
+  // 清除上一次的表单校验和参数
+  resetFields()
+  clearValidate()
+}
+</script>
+
+<style scoped></style>
diff --git a/src/views/system/notify/template/index.vue b/src/views/system/notify/template/index.vue
index c4b76e1..d9a24da 100644
--- a/src/views/system/notify/template/index.vue
+++ b/src/views/system/notify/template/index.vue
@@ -10,6 +10,12 @@
         <template v-if="column.key === 'action'">
           <TableAction
             :actions="[
+              {
+                icon: IconEnum.UPLOAD,
+                label: t('action.send'),
+                auth: 'system:notify-template:update',
+                onClick: handleSend.bind(null, record)
+              },
               {
                 icon: IconEnum.EDIT,
                 label: t('action.edit'),
@@ -33,6 +39,7 @@
       </template>
     </BasicTable>
     <TemplateModal @register="registerModal" @success="reload()" />
+    <SendNotifyModal @register="registerSendModal" />
   </div>
 </template>
 <script lang="ts" setup>
@@ -44,12 +51,14 @@ import { IconEnum } from '@/enums/appEnum'
 import { BasicTable, useTable, TableAction } from '@/components/Table'
 import { deleteNotifyTemplate, getNotifyTemplatePage } from '@/api/system/notify/template'
 import { columns, searchFormSchema } from './template.data'
+import SendNotifyModal from './SendNotifyModal.vue'
 
 defineOptions({ name: 'SystemMessageTemplate' })
 
 const { t } = useI18n()
 const { createMessage } = useMessage()
 const [registerModal, { openModal }] = useModal()
+const [registerSendModal, { openModal: openSendModal }] = useModal()
 
 const [registerTable, { reload }] = useTable({
   title: '站内信模板列表',
@@ -60,7 +69,7 @@ const [registerTable, { reload }] = useTable({
   showTableSetting: true,
   showIndexColumn: false,
   actionColumn: {
-    width: 140,
+    width: 200,
     title: t('common.action'),
     dataIndex: 'action',
     fixed: 'right'
@@ -75,6 +84,11 @@ function handleEdit(record: Recordable) {
   openModal(true, { record, isUpdate: true })
 }
 
+function handleSend(record: Recordable) {
+  console.log(JSON.stringify(record, [...Object.keys(record)], 2))
+  openSendModal(true, record)
+}
+
 async function handleDelete(record: Recordable) {
   await deleteNotifyTemplate(record.id)
   createMessage.success(t('common.delSuccessText'))
diff --git a/src/views/system/notify/template/template.data.ts b/src/views/system/notify/template/template.data.ts
index 75a1e23..8ebb776 100644
--- a/src/views/system/notify/template/template.data.ts
+++ b/src/views/system/notify/template/template.data.ts
@@ -1,5 +1,6 @@
 import { BasicColumn, FormSchema, useRender } from '@/components/Table'
 import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import { getListSimpleUsers } from '@/api/system/user/index'
 
 export const columns: BasicColumn[] = [
   {
@@ -125,6 +126,7 @@ export const formSchema: FormSchema[] = [
   {
     label: '开启状态',
     field: 'status',
+    required: true,
     component: 'RadioGroup',
     componentProps: {
       options: getDictOptions(DICT_TYPE.COMMON_STATUS)
@@ -136,3 +138,27 @@ export const formSchema: FormSchema[] = [
     component: 'InputTextArea'
   }
 ]
+
+// 发送站内信
+export const baseSendSchemas: FormSchema[] = [
+  {
+    field: 'content',
+    component: 'InputTextArea',
+    label: '模板内容 ',
+    required: false,
+    componentProps: {
+      disabled: true
+    }
+  },
+  {
+    field: 'userId',
+    component: 'ApiSelect',
+    label: '接收人 ',
+    required: true,
+    componentProps: {
+      api: getListSimpleUsers,
+      labelField: 'nickname',
+      valueField: 'id'
+    }
+  }
+]