diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue
index ef1cd0db..3dbe4758 100644
--- a/src/views/system/dept/index.vue
+++ b/src/views/system/dept/index.vue
@@ -66,7 +66,7 @@ const [register, { expandAll, collapseAll, getForm, reload }] = useTable({
   showIndexColumn: false,
   canResize: false,
   actionColumn: {
-    width: 120,
+    width: 140,
     title: t('common.action'),
     dataIndex: 'action',
     fixed: 'right'
diff --git a/src/views/system/dict/DictData.vue b/src/views/system/dict/DictData.vue
index cbe7bf1c..1e45f731 100644
--- a/src/views/system/dict/DictData.vue
+++ b/src/views/system/dict/DictData.vue
@@ -61,7 +61,7 @@ const [registerTable, { reload }] = useTable({
   showTableSetting: true,
   showIndexColumn: false,
   actionColumn: {
-    width: 120,
+    width: 140,
     title: t('common.action'),
     dataIndex: 'action',
     fixed: 'right'
diff --git a/src/views/system/dict/index.vue b/src/views/system/dict/index.vue
index 03880549..3d1a9f7e 100644
--- a/src/views/system/dict/index.vue
+++ b/src/views/system/dict/index.vue
@@ -56,7 +56,7 @@ const [registerTable, { reload }] = useTable({
   showTableSetting: true,
   showIndexColumn: false,
   actionColumn: {
-    width: 120,
+    width: 140,
     title: t('common.action'),
     dataIndex: 'action',
     fixed: 'right'
diff --git a/src/views/system/errorCode/index.vue b/src/views/system/errorCode/index.vue
index 075a12bf..28a42941 100644
--- a/src/views/system/errorCode/index.vue
+++ b/src/views/system/errorCode/index.vue
@@ -52,7 +52,7 @@ const [registerTable, { getForm, reload }] = useTable({
   showTableSetting: true,
   showIndexColumn: false,
   actionColumn: {
-    width: 120,
+    width: 140,
     title: t('common.action'),
     dataIndex: 'action',
     fixed: 'right'
diff --git a/src/views/system/mail/account/index.vue b/src/views/system/mail/account/index.vue
index 968c5a0f..96f02888 100644
--- a/src/views/system/mail/account/index.vue
+++ b/src/views/system/mail/account/index.vue
@@ -51,7 +51,7 @@ const [registerTable, { reload }] = useTable({
   showTableSetting: true,
   showIndexColumn: false,
   actionColumn: {
-    width: 120,
+    width: 140,
     title: t('common.action'),
     dataIndex: 'action',
     fixed: 'right'
diff --git a/src/views/system/mail/template/index.vue b/src/views/system/mail/template/index.vue
index f1d1a091..7dd015c3 100644
--- a/src/views/system/mail/template/index.vue
+++ b/src/views/system/mail/template/index.vue
@@ -51,7 +51,7 @@ const [registerTable, { reload }] = useTable({
   showTableSetting: true,
   showIndexColumn: false,
   actionColumn: {
-    width: 120,
+    width: 140,
     title: t('common.action'),
     dataIndex: 'action',
     fixed: 'right'
diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue
index 1e0f4d18..442d18bb 100644
--- a/src/views/system/menu/index.vue
+++ b/src/views/system/menu/index.vue
@@ -61,7 +61,7 @@ const [register, { expandAll, collapseAll, getForm, reload }] = useTable({
   showIndexColumn: false,
   canResize: false,
   actionColumn: {
-    width: 120,
+    width: 140,
     title: t('common.action'),
     dataIndex: 'action',
     fixed: 'right'
diff --git a/src/views/system/notice/index.vue b/src/views/system/notice/index.vue
index 23d014dd..ab850227 100644
--- a/src/views/system/notice/index.vue
+++ b/src/views/system/notice/index.vue
@@ -51,7 +51,7 @@ const [registerTable, { reload }] = useTable({
   showTableSetting: true,
   showIndexColumn: false,
   actionColumn: {
-    width: 120,
+    width: 140,
     title: t('common.action'),
     dataIndex: 'action',
     fixed: 'right'
diff --git a/src/views/system/oauth2/client/index.vue b/src/views/system/oauth2/client/index.vue
index 38a145e6..e4ce5945 100644
--- a/src/views/system/oauth2/client/index.vue
+++ b/src/views/system/oauth2/client/index.vue
@@ -51,7 +51,7 @@ const [registerTable, { reload }] = useTable({
   showTableSetting: true,
   showIndexColumn: false,
   actionColumn: {
-    width: 120,
+    width: 140,
     title: t('common.action'),
     dataIndex: 'action',
     fixed: 'right'
diff --git a/src/views/system/post/index.vue b/src/views/system/post/index.vue
index 2e53244b..b0ac2e20 100644
--- a/src/views/system/post/index.vue
+++ b/src/views/system/post/index.vue
@@ -53,7 +53,7 @@ const [registerTable, { getForm, reload }] = useTable({
   showTableSetting: true,
   showIndexColumn: false,
   actionColumn: {
-    width: 120,
+    width: 140,
     title: t('common.action'),
     dataIndex: 'action',
     fixed: 'right'
diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue
index e396b9f7..980752e1 100644
--- a/src/views/system/role/index.vue
+++ b/src/views/system/role/index.vue
@@ -54,7 +54,7 @@ const [registerTable, { getForm, reload }] = useTable({
   showTableSetting: true,
   showIndexColumn: false,
   actionColumn: {
-    width: 120,
+    width: 140,
     title: t('common.action'),
     dataIndex: 'action',
     fixed: 'right'
diff --git a/src/views/system/sensitiveWord/index.vue b/src/views/system/sensitiveWord/index.vue
index 252ecb66..b3bf2f43 100644
--- a/src/views/system/sensitiveWord/index.vue
+++ b/src/views/system/sensitiveWord/index.vue
@@ -57,7 +57,7 @@ const [registerTable, { getForm, reload }] = useTable({
   showTableSetting: true,
   showIndexColumn: false,
   actionColumn: {
-    width: 120,
+    width: 140,
     title: t('common.action'),
     dataIndex: 'action',
     fixed: 'right'
diff --git a/src/views/system/sms/SmsChannelModal.vue b/src/views/system/sms/SmsChannelModal.vue
new file mode 100644
index 00000000..ac32a8ea
--- /dev/null
+++ b/src/views/system/sms/SmsChannelModal.vue
@@ -0,0 +1,54 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit">
+    <BasicForm @register="registerForm" />
+  </BasicModal>
+</template>
+<script lang="ts" setup name="SmsChannelModal">
+import { ref, computed, unref } from 'vue'
+import { BasicModal, useModalInner } from '@/components/Modal'
+import { BasicForm, useForm } from '@/components/Form'
+import { formSchema } from './smsChannel.data'
+import { createSmsChannelApi, getSmsChannelApi, updateSmsChannelApi } from '@/api/system/sms/smsChannel'
+
+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 getSmsChannelApi(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 updateSmsChannelApi(values)
+    } else {
+      await createSmsChannelApi(values)
+    }
+    closeModal()
+    emit('success')
+  } finally {
+    setModalProps({ confirmLoading: false })
+  }
+}
+</script>
diff --git a/src/views/system/sms/smsChannel.data.ts b/src/views/system/sms/smsChannel.data.ts
new file mode 100644
index 00000000..83b58f23
--- /dev/null
+++ b/src/views/system/sms/smsChannel.data.ts
@@ -0,0 +1,137 @@
+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: 'signature',
+    width: 180
+  },
+  {
+    title: '渠道编码',
+    dataIndex: 'code',
+    width: 180,
+    customRender: ({ text }) => {
+      return useRender.renderDict(text, DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE)
+    }
+  },
+  {
+    title: '启用状态',
+    dataIndex: 'status',
+    width: 180,
+    customRender: ({ text }) => {
+      return useRender.renderDict(text, DICT_TYPE.COMMON_STATUS)
+    }
+  },
+  {
+    title: '备注',
+    dataIndex: 'remark',
+    width: 180
+  },
+  {
+    title: '短信 API 的账号',
+    dataIndex: 'apiKey',
+    width: 180
+  },
+  {
+    title: '短信 API 的密钥',
+    dataIndex: 'apiSecret',
+    width: 180
+  },
+  {
+    title: '短信发送回调 URL',
+    dataIndex: 'callbackUrl',
+    width: 180
+  },
+  {
+    title: '创建时间',
+    dataIndex: 'createTime',
+    width: 180,
+    customRender: ({ text }) => {
+      return useRender.renderDate(text)
+    }
+  }
+]
+
+export const searchFormSchema: FormSchema[] = [
+  {
+    label: '短信签名',
+    field: 'signature',
+    component: 'Input',
+    colProps: { span: 8 }
+  },
+  {
+    label: '启用状态',
+    field: 'status',
+    component: 'Select',
+    componentProps: {
+      options: getIntDictOptions(DICT_TYPE.COMMON_STATUS)
+    },
+    colProps: { span: 8 }
+  },
+  {
+    label: '创建时间',
+    field: 'createTime',
+    component: 'RangePicker',
+    colProps: { span: 8 }
+  }
+]
+
+export const formSchema: FormSchema[] = [
+  {
+    label: '编号',
+    field: 'id',
+    show: false,
+    component: 'Input'
+  },
+  {
+    label: '短信签名',
+    field: 'signature',
+    required: true,
+    component: 'Input'
+  },
+  {
+    label: '渠道编码',
+    field: 'code',
+    component: 'Select',
+    required: true,
+    componentProps: {
+      options: getIntDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE)
+    }
+  },
+  {
+    label: '启用状态',
+    field: 'status',
+    component: 'Select',
+    defaultValue: 0,
+    componentProps: {
+      options: getIntDictOptions(DICT_TYPE.COMMON_STATUS)
+    }
+  },
+  {
+    label: '备注',
+    field: 'remark',
+    component: 'InputTextArea'
+  },
+  {
+    label: '短信 API 的账号',
+    field: 'apiKey',
+    required: true,
+    component: 'Input'
+  },
+  {
+    label: '短信 API 的密钥',
+    field: 'apiSecret',
+    component: 'Input'
+  },
+  {
+    label: '短信发送回调 URL',
+    field: 'callbackUrl',
+    component: 'Input'
+  }
+]
diff --git a/src/views/system/sms/smsChannel.vue b/src/views/system/sms/smsChannel.vue
index 3b64cfc4..302dce1f 100644
--- a/src/views/system/sms/smsChannel.vue
+++ b/src/views/system/sms/smsChannel.vue
@@ -1,3 +1,80 @@
 <template>
-  <div>开发中</div>
+  <div>
+    <BasicTable @register="registerTable">
+      <template #toolbar>
+        <a-button type="primary" @click="handleCreate"> {{ t('action.create') }} </a-button>
+      </template>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              { icon: 'clarity:note-edit-line', label: t('action.edit'), onClick: handleEdit.bind(null, record) },
+              {
+                icon: 'ant-design:delete-outlined',
+                color: 'error',
+                label: t('action.delete'),
+                popConfirm: {
+                  title: t('common.delMessage'),
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record)
+                }
+              }
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <SmsChannelModal @register="registerModal" @success="reload()" />
+  </div>
 </template>
+<script lang="ts" setup name="SmsChannel">
+import { useI18n } from '@/hooks/web/useI18n'
+import { useMessage } from '@/hooks/web/useMessage'
+import { useModal } from '@/components/Modal'
+import SmsChannelModal from './SmsChannelModal.vue'
+import { BasicTable, useTable, TableAction } from '@/components/Table'
+import { deleteSmsChannelApi, getSmsChannelPageApi } from '@/api/system/sms/smsChannel'
+import { columns, searchFormSchema } from './smsChannel.data'
+
+const { t } = useI18n()
+const { createMessage } = useMessage()
+const [registerModal, { openModal }] = useModal()
+
+const [registerTable, { reload }] = useTable({
+  title: '短信渠道列表',
+  api: getSmsChannelPageApi,
+  columns,
+  formConfig: {
+    labelWidth: 120,
+    schemas: searchFormSchema
+  },
+  useSearchForm: true,
+  showTableSetting: true,
+  showIndexColumn: false,
+  actionColumn: {
+    width: 140,
+    title: t('common.action'),
+    dataIndex: 'action',
+    fixed: 'right'
+  }
+})
+
+function handleCreate() {
+  openModal(true, {
+    isUpdate: false
+  })
+}
+
+function handleEdit(record: Recordable) {
+  openModal(true, {
+    record,
+    isUpdate: true
+  })
+}
+
+async function handleDelete(record: Recordable) {
+  await deleteSmsChannelApi(record.id)
+  createMessage.success(t('common.delSuccessText'))
+  reload()
+}
+</script>
diff --git a/src/views/system/tenant/index.vue b/src/views/system/tenant/index.vue
index 8b4742aa..4e4dc0c3 100644
--- a/src/views/system/tenant/index.vue
+++ b/src/views/system/tenant/index.vue
@@ -52,7 +52,7 @@ const [registerTable, { getForm, reload }] = useTable({
   showTableSetting: true,
   showIndexColumn: false,
   actionColumn: {
-    width: 120,
+    width: 140,
     title: t('common.action'),
     dataIndex: 'action',
     fixed: 'right'
diff --git a/src/views/system/tenantPackage/index.vue b/src/views/system/tenantPackage/index.vue
index 5916d87e..d92fbe4d 100644
--- a/src/views/system/tenantPackage/index.vue
+++ b/src/views/system/tenantPackage/index.vue
@@ -51,7 +51,7 @@ const [registerTable, { reload }] = useTable({
   showTableSetting: true,
   showIndexColumn: false,
   actionColumn: {
-    width: 120,
+    width: 140,
     title: t('common.action'),
     dataIndex: 'action',
     fixed: 'right'
diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue
index 3c166f2e..6b4027ae 100644
--- a/src/views/system/user/index.vue
+++ b/src/views/system/user/index.vue
@@ -58,7 +58,7 @@ const [registerTable, { getForm, reload }] = useTable({
   showTableSetting: true,
   showIndexColumn: false,
   actionColumn: {
-    width: 120,
+    width: 140,
     title: t('common.action'),
     dataIndex: 'action',
     fixed: 'right'