From fb27a501928d4d251e5baf8b8d0470c8de01face Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=B0=8F=E8=AF=BA?= <1253070437@qq.com>
Date: Sat, 28 Jan 2023 23:40:12 +0800
Subject: [PATCH] =?UTF-8?q?=E3=80=90=E6=96=B0=E5=A2=9E=E3=80=91=E6=96=B0?=
 =?UTF-8?q?=E5=A2=9E=E7=A7=BB=E5=8A=A8=E7=AB=AF=E8=8F=9C=E5=8D=95=E7=AE=A1?=
 =?UTF-8?q?=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=88=E5=BE=85=E5=AE=8C=E5=96=84?=
 =?UTF-8?q?=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../src/api/mobile/mobileMenuApi.js           |  32 ++++
 .../src/views/mobile/menu/form.vue            | 130 +++++++++++++
 .../src/views/mobile/menu/index.vue           | 175 ++++++++++++++++++
 snowy-plugin-api/pom.xml                      |   3 +
 .../snowy-plugin-mobile-api/README.md         |   1 +
 .../snowy-plugin-mobile-api/pom.xml           |  25 +++
 .../java/vip/xiaonuo/mobile/package-info.java |  13 ++
 snowy-plugin/pom.xml                          |   3 +
 snowy-plugin/snowy-plugin-mobile/README.md    |   1 +
 snowy-plugin/snowy-plugin-mobile/pom.xml      |  46 +++++
 .../mobile/core/config/MobileConfigure.java   |  67 +++++++
 .../menu/controller/MobileMenuController.java | 123 ++++++++++++
 .../modular/menu/entity/MobileMenu.java       | 108 +++++++++++
 .../modular/menu/enums/MobileMenuEnum.java    |  34 ++++
 .../modular/menu/mapper/MobileMenuMapper.java |  25 +++
 .../menu/mapper/mapping/MobileMenuMapper.xml  |   5 +
 .../menu/param/MobileMenuAddParam.java        |  78 ++++++++
 .../menu/param/MobileMenuEditParam.java       |  83 +++++++++
 .../modular/menu/param/MobileMenuIdParam.java |  35 ++++
 .../menu/param/MobileMenuTreeParam.java       |  41 ++++
 .../menu/service/MobileMenuService.java       |  77 ++++++++
 .../service/impl/MobileMenuServiceImpl.java   |  98 ++++++++++
 snowy-web-app/pom.xml                         |   7 +
 .../main/resources/_sql/update20230128.sql    |  27 +++
 24 files changed, 1237 insertions(+)
 create mode 100644 snowy-admin-web/src/api/mobile/mobileMenuApi.js
 create mode 100644 snowy-admin-web/src/views/mobile/menu/form.vue
 create mode 100644 snowy-admin-web/src/views/mobile/menu/index.vue
 create mode 100644 snowy-plugin-api/snowy-plugin-mobile-api/README.md
 create mode 100644 snowy-plugin-api/snowy-plugin-mobile-api/pom.xml
 create mode 100644 snowy-plugin-api/snowy-plugin-mobile-api/src/main/java/vip/xiaonuo/mobile/package-info.java
 create mode 100644 snowy-plugin/snowy-plugin-mobile/README.md
 create mode 100644 snowy-plugin/snowy-plugin-mobile/pom.xml
 create mode 100644 snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/core/config/MobileConfigure.java
 create mode 100644 snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/controller/MobileMenuController.java
 create mode 100644 snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/entity/MobileMenu.java
 create mode 100644 snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/enums/MobileMenuEnum.java
 create mode 100644 snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/mapper/MobileMenuMapper.java
 create mode 100644 snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/mapper/mapping/MobileMenuMapper.xml
 create mode 100644 snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/param/MobileMenuAddParam.java
 create mode 100644 snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/param/MobileMenuEditParam.java
 create mode 100644 snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/param/MobileMenuIdParam.java
 create mode 100644 snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/param/MobileMenuTreeParam.java
 create mode 100644 snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/service/MobileMenuService.java
 create mode 100644 snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/service/impl/MobileMenuServiceImpl.java
 create mode 100644 snowy-web-app/src/main/resources/_sql/update20230128.sql

diff --git a/snowy-admin-web/src/api/mobile/mobileMenuApi.js b/snowy-admin-web/src/api/mobile/mobileMenuApi.js
new file mode 100644
index 00000000..d873eabb
--- /dev/null
+++ b/snowy-admin-web/src/api/mobile/mobileMenuApi.js
@@ -0,0 +1,32 @@
+import { baseRequest } from '@/utils/request'
+
+const request = (url, ...arg) => baseRequest(`/mobile/menu/` + url, ...arg)
+
+/**
+ * 移动端菜单Api接口管理器
+ *
+ * @author yubaoshan
+ * @date  2023/01/28 22:42
+ **/
+export default {
+	// 获取移动端菜单tree
+	mobileMenuTree(data) {
+		return request('tree', data, 'get')
+	},
+	// 获取移动端菜单列表
+	mobileMenuList(data) {
+		return request('list', data, 'get')
+	},
+	// 提交移动端菜单表单 edit为true时为编辑,默认为新增
+	mobileMenuSubmitForm(data, edit = false) {
+		return request(edit ? 'add' : 'edit', data)
+	},
+	// 删除移动端菜单
+	mobileMenuDelete(data) {
+		return request('delete', data)
+	},
+	// 获取移动端菜单详情
+	mobileMenuDetail(data) {
+		return request('detail', data, 'get')
+	}
+}
diff --git a/snowy-admin-web/src/views/mobile/menu/form.vue b/snowy-admin-web/src/views/mobile/menu/form.vue
new file mode 100644
index 00000000..347920cb
--- /dev/null
+++ b/snowy-admin-web/src/views/mobile/menu/form.vue
@@ -0,0 +1,130 @@
+<template>
+    <a-drawer
+        :title="formData.id ? '编辑移动端菜单' : '增加移动端菜单'"
+        :width="600"
+        :visible="visible"
+        :destroy-on-close="true"
+        :footer-style="{ textAlign: 'right' }"
+        @close="onClose"
+    >
+        <a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
+            <a-row :gutter="16">
+                <a-col :span="12">
+                    <a-form-item label="父ID:" name="parentId">
+                        <a-input v-model:value="formData.parentId" placeholder="请输入父ID" allow-clear />
+                    </a-form-item>
+                </a-col>
+                <a-col :span="12">
+                    <a-form-item label="名称:" name="title">
+                        <a-input v-model:value="formData.title" placeholder="请输入名称" allow-clear />
+                    </a-form-item>
+                </a-col>
+                <a-col :span="12">
+                    <a-form-item label="界面路径:" name="pages">
+                        <a-input v-model:value="formData.pages" placeholder="请输入界面路径" allow-clear />
+                    </a-form-item>
+                </a-col>
+                <a-col :span="12">
+                    <a-form-item label="分类:" name="category">
+                        <a-select v-model:value="formData.category" placeholder="请选择分类" :options="categoryOptions" />
+                    </a-form-item>
+                </a-col>
+                <a-col :span="12">
+                    <a-form-item label="图标:" name="icon">
+                        <a-input v-model:value="formData.icon" placeholder="请输入图标" allow-clear />
+                    </a-form-item>
+                </a-col>
+                <a-col :span="12">
+                    <a-form-item label="颜色:" name="color">
+                        <a-input v-model:value="formData.color" placeholder="请输入颜色" allow-clear />
+                    </a-form-item>
+                </a-col>
+                <a-col :span="12">
+                    <a-form-item label="是否正规则:" name="isRegExp">
+                        <a-radio-group v-model:value="formData.isRegExp" placeholder="请选择正规则" :options="isRegExpOptions" />
+                    </a-form-item>
+                </a-col>
+                <a-col :span="12">
+                    <a-form-item label="可用状态:" name="status">
+                        <a-radio-group v-model:value="formData.status" placeholder="请选择可用状态" :options="statusOptions" />
+                    </a-form-item>
+                </a-col>
+            </a-row>
+			<a-form-item label="排序码:" name="sortCode">
+				<a-slider v-model:value="formData.sortCode" :max="1000" style="width: 100%" />
+			</a-form-item>
+        </a-form>
+        <template #footer>
+            <a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
+            <a-button type="primary" @click="onSubmit" :loading="submitLoading">保存</a-button>
+        </template>
+    </a-drawer>
+</template>
+
+<script setup name="mobileMenuForm">
+    import tool from '@/utils/tool'
+    import { cloneDeep } from 'lodash-es'
+    import { required } from '@/utils/formRules'
+    import mobileMenuApi from '@/api/mobile/mobileMenuApi'
+    // 抽屉状态
+    const visible = ref(false)
+    const emit = defineEmits({ successful: null })
+    const formRef = ref()
+    // 表单数据
+    const formData = ref({})
+    const submitLoading = ref(false)
+    const categoryOptions = ref([])
+    const isRegExpOptions = ref([])
+    const statusOptions = ref([])
+
+    // 打开抽屉
+    const onOpen = (record) => {
+        visible.value = true
+        if (record) {
+            let recordData = cloneDeep(record)
+            formData.value = Object.assign({}, recordData)
+        }
+        categoryOptions.value = tool.dictList('MOBILE_CATEGORY')
+        isRegExpOptions.value = tool.dictList('MOBILE_IS_REG_EXP')
+        statusOptions.value = tool.dictList('MOBILE_STATUS')
+    }
+    // 关闭抽屉
+    const onClose = () => {
+        formRef.value.resetFields()
+        formData.value = {}
+        visible.value = false
+    }
+    // 默认要校验的
+    const formRules = {
+        parentId: [required('请输入父ID')],
+        title: [required('请输入名称')],
+        pages: [required('请输入界面路径')],
+        category: [required('请输入分类')],
+        icon: [required('请输入图标')],
+        color: [required('请输入颜色')],
+        isRegExp: [required('请输入正规则')],
+        status: [required('请输入可用状态')],
+    }
+    // 验证并提交数据
+    const onSubmit = () => {
+        formRef.value
+            .validate()
+            .then(() => {
+                submitLoading.value = true
+                const formDataParam = cloneDeep(formData.value)
+                mobileMenuApi
+                    .mobileMenuSubmitForm(formDataParam, !formDataParam.id)
+                    .then(() => {
+                        onClose()
+                        emit('successful')
+                    })
+                    .finally(() => {
+                        submitLoading.value = false
+                    })
+            })
+    }
+    // 抛出函数
+    defineExpose({
+        onOpen
+    })
+</script>
diff --git a/snowy-admin-web/src/views/mobile/menu/index.vue b/snowy-admin-web/src/views/mobile/menu/index.vue
new file mode 100644
index 00000000..6a815b88
--- /dev/null
+++ b/snowy-admin-web/src/views/mobile/menu/index.vue
@@ -0,0 +1,175 @@
+<template>
+    <a-card :bordered="false">
+        <a-form ref="searchFormRef" name="advanced_search" :model="searchFormState" class="ant-advanced-search-form mb-4">
+            <a-row :gutter="24">
+                <a-col :span="6">
+                    <a-form-item label="关键词" name="searchKey">
+                        <a-input v-model:value="searchFormState.searchKey" placeholder="请输入关键词" />
+                    </a-form-item>
+                </a-col>
+                <a-col :span="6">
+                    <a-form-item label="分类" name="category">
+                        <a-select v-model:value="searchFormState.category" placeholder="请选择分类" :options="categoryOptions" />
+                    </a-form-item>
+                </a-col>
+                <a-col :span="6">
+                    <a-form-item label="可用状态" name="status">
+                        <a-select v-model:value="searchFormState.status" placeholder="请选择可用状态" :options="statusOptions" />
+                    </a-form-item>
+                </a-col>
+                <a-col :span="6">
+                    <a-button type="primary" @click="table.refresh(true)">查询</a-button>
+                    <a-button style="margin: 0 8px" @click="() => searchFormRef.resetFields()">重置</a-button>
+                </a-col>
+            </a-row>
+        </a-form>
+        <s-table
+            ref="table"
+            :columns="columns"
+            :data="loadData"
+            :alert="options.alert.show"
+            bordered
+            :row-key="(record) => record.id"
+            :tool-config="toolConfig"
+			:show-pagination="false"
+            :row-selection="options.rowSelection"
+        >
+            <template #operator class="table-operator">
+                <a-space>
+                    <a-button type="primary" @click="formRef.onOpen()">
+                        <template #icon><plus-outlined /></template>
+                        新增
+                    </a-button>
+                    <a-button danger @click="deleteBatchMobileMenu()">删除</a-button>
+                </a-space>
+            </template>
+            <template #bodyCell="{ column, record }">
+                <template v-if="column.dataIndex === 'category'">
+                    {{ $TOOL.dictTypeData('MOBILE_CATEGORY', record.category) }}
+                </template>
+                <template v-if="column.dataIndex === 'isRegExp'">
+                    {{ $TOOL.dictTypeData('MOBILE_IS_REG_EXP', record.isRegExp) }}
+                </template>
+                <template v-if="column.dataIndex === 'status'">
+                    {{ $TOOL.dictTypeData('MOBILE_STATUS', record.status) }}
+                </template>
+                <template v-if="column.dataIndex === 'action'">
+                    <a-space>
+                        <a @click="formRef.onOpen(record)">编辑</a>
+                        <a-divider type="vertical" />
+                        <a-popconfirm title="确定要删除吗?" @confirm="deleteMobileMenu(record)">
+                            <a-button type="link" danger size="small">删除</a-button>
+                        </a-popconfirm>
+                    </a-space>
+                </template>
+            </template>
+        </s-table>
+    </a-card>
+    <Form ref="formRef" @successful="table.refresh(true)" />
+</template>
+
+<script setup name="mobileMenuIndex">
+    import { message } from 'ant-design-vue'
+    import tool from '@/utils/tool'
+    import Form from './form.vue'
+    import mobileMenuApi from '@/api/mobile/mobileMenuApi'
+    let searchFormState = reactive({})
+    const searchFormRef = ref()
+    const table = ref()
+    const formRef = ref()
+    const toolConfig = { refresh: true, height: true, columnSetting: true, striped: false }
+    const columns = [
+        {
+            title: '名称',
+            dataIndex: 'title'
+        },
+        {
+            title: '界面路径',
+            dataIndex: 'pages',
+            ellipsis: true
+        },
+        {
+            title: '分类',
+            dataIndex: 'category'
+        },
+        {
+            title: '图标',
+            dataIndex: 'icon'
+        },
+        {
+            title: '正规则',
+            dataIndex: 'isRegExp'
+        },
+        {
+            title: '可用状态',
+            dataIndex: 'status'
+        },
+        {
+            title: '排序码',
+            dataIndex: 'sortCode'
+        },
+        {
+            title: '创建时间',
+            dataIndex: 'createTime',
+            ellipsis: true
+        },
+		{
+			title: '操作',
+			dataIndex: 'action',
+			align: 'center',
+			width: '150px'
+		}
+    ]
+    let selectedRowKeys = ref([])
+    // 列表选择配置
+    const options = {
+        alert: {
+            show: false,
+            clear: () => {
+                selectedRowKeys = ref([])
+            }
+        },
+        rowSelection: {
+            onChange: (selectedRowKey, selectedRows) => {
+                selectedRowKeys.value = selectedRowKey
+            }
+        }
+    }
+    const loadData = (parameter) => {
+        const searchFormParam = JSON.parse(JSON.stringify(searchFormState))
+        return mobileMenuApi.mobileMenuTree(Object.assign(parameter, searchFormParam)).then((data) => {
+			if (data) {
+				return data
+			}
+			return []
+        })
+    }
+    // 删除
+    const deleteMobileMenu = (record) => {
+        let params = [
+            {
+                id: record.id
+            }
+        ]
+        mobileMenuApi.mobileMenuDelete(params).then(() => {
+            table.value.refresh(true)
+        })
+    }
+    // 批量删除
+    const deleteBatchMobileMenu = () => {
+        if (selectedRowKeys.value.length < 1) {
+            message.warning('请选择一条或多条数据')
+            return false
+        }
+        const params = selectedRowKeys.value.map((m) => {
+            return {
+                id: m
+            }
+        })
+        mobileMenuApi.mobileMenuDelete(params).then(() => {
+            table.value.clearRefreshSelected()
+        })
+    }
+    const categoryOptions = tool.dictList('MOBILE_CATEGORY')
+    const statusOptions = tool.dictList('MOBILE_STATUS')
+</script>
diff --git a/snowy-plugin-api/pom.xml b/snowy-plugin-api/pom.xml
index 5a3e4aef..e0523441 100644
--- a/snowy-plugin-api/pom.xml
+++ b/snowy-plugin-api/pom.xml
@@ -30,6 +30,9 @@
         <!-- 代码生成插件api接口 -->
         <module>snowy-plugin-gen-api</module>
 
+        <!-- 移动端管理插件api接口 -->
+        <module>snowy-plugin-mobile-api</module>
+
         <!-- 系统功能插件api接口 -->
         <module>snowy-plugin-sys-api</module>
     </modules>
diff --git a/snowy-plugin-api/snowy-plugin-mobile-api/README.md b/snowy-plugin-api/snowy-plugin-mobile-api/README.md
new file mode 100644
index 00000000..02ad0efb
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-mobile-api/README.md
@@ -0,0 +1 @@
+# 移动端管理插件api接口
diff --git a/snowy-plugin-api/snowy-plugin-mobile-api/pom.xml b/snowy-plugin-api/snowy-plugin-mobile-api/pom.xml
new file mode 100644
index 00000000..c5747db1
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-mobile-api/pom.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>vip.xiaonuo</groupId>
+        <artifactId>snowy-plugin-api</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>snowy-plugin-mobile-api</artifactId>
+    <packaging>jar</packaging>
+    <description>移动端管理插件api接口</description>
+
+    <dependencies>
+        <!-- 每个插件接口都要引入common -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-common</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/snowy-plugin-api/snowy-plugin-mobile-api/src/main/java/vip/xiaonuo/mobile/package-info.java b/snowy-plugin-api/snowy-plugin-mobile-api/src/main/java/vip/xiaonuo/mobile/package-info.java
new file mode 100644
index 00000000..ba79db40
--- /dev/null
+++ b/snowy-plugin-api/snowy-plugin-mobile-api/src/main/java/vip/xiaonuo/mobile/package-info.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.biz;
\ No newline at end of file
diff --git a/snowy-plugin/pom.xml b/snowy-plugin/pom.xml
index 1c90c934..71f77a38 100644
--- a/snowy-plugin/pom.xml
+++ b/snowy-plugin/pom.xml
@@ -30,6 +30,9 @@
         <!-- 代码生成插件 -->
         <module>snowy-plugin-gen</module>
 
+        <!-- 移动端管理插件 -->
+        <module>snowy-plugin-mobile</module>
+
         <!-- 系统功能插件 -->
         <module>snowy-plugin-sys</module>
     </modules>
diff --git a/snowy-plugin/snowy-plugin-mobile/README.md b/snowy-plugin/snowy-plugin-mobile/README.md
new file mode 100644
index 00000000..608ac34d
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-mobile/README.md
@@ -0,0 +1 @@
+# 移动端管理插件
diff --git a/snowy-plugin/snowy-plugin-mobile/pom.xml b/snowy-plugin/snowy-plugin-mobile/pom.xml
new file mode 100644
index 00000000..61a10cf8
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-mobile/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>vip.xiaonuo</groupId>
+        <artifactId>snowy-plugin</artifactId>
+        <version>2.0.0</version>
+    </parent>
+
+    <artifactId>snowy-plugin-mobile</artifactId>
+    <packaging>jar</packaging>
+    <description>移动端管理插件</description>
+
+    <dependencies>
+        <!-- 每个插件都要引入自己的对外接口 -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-plugin-mobile-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+
+        <!-- 引入登录鉴权接口,用于获取登录用户 -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-plugin-auth-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+
+        <!-- 引入系统接口,用于授权角色等功能 -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-plugin-sys-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+
+        <!-- 引入开发工具接口,用于配置信息 -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-plugin-dev-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/core/config/MobileConfigure.java b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/core/config/MobileConfigure.java
new file mode 100644
index 00000000..0c8c3900
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/core/config/MobileConfigure.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.mobile.core.config;
+
+import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.bind.annotation.RequestMethod;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import vip.xiaonuo.common.pojo.CommonResult;
+
+import javax.annotation.Resource;
+
+/**
+ * 业务相关配置
+ *
+ * @author xuyuxiang yubaoshan
+ * @date 2022/7/7 16:18
+ **/
+@Configuration
+public class MobileConfigure {
+
+    @Resource
+    private OpenApiExtensionResolver openApiExtensionResolver;
+
+    /**
+     * API文档分组配置
+     *
+     * @author xuyuxiang yubaoshan
+     * @date 2022/7/7 16:18
+     **/
+    @Bean(value = "mobileDocApi")
+    public Docket mobileDocApi() {
+        return new Docket(DocumentationType.SWAGGER_2)
+                .apiInfo(new ApiInfoBuilder()
+                        .title("移动端功能MOBILE")
+                        .description("移动端功能MOBILE")
+                        .termsOfServiceUrl("https://www.xiaonuo.vip")
+                        .contact(new Contact("SNOWY_TEAM","https://www.xiaonuo.vip", "xiaonuobase@qq.com"))
+                        .version("2.0.0")
+                        .build())
+                .globalResponseMessage(RequestMethod.GET, CommonResult.responseList())
+                .globalResponseMessage(RequestMethod.POST, CommonResult.responseList())
+                .groupName("移动端功能MOBILE")
+                .select()
+                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+                .apis(RequestHandlerSelectors.basePackage("vip.xiaonuo.mobile"))
+                .paths(PathSelectors.any())
+                .build().extensions(openApiExtensionResolver.buildExtensions("移动端功能MOBILE"));
+    }
+}
diff --git a/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/controller/MobileMenuController.java b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/controller/MobileMenuController.java
new file mode 100644
index 00000000..1a0e4a5a
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/controller/MobileMenuController.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.mobile.modular.menu.controller;
+
+import cn.hutool.core.lang.tree.Tree;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import com.github.xiaoymin.knife4j.annotations.ApiSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import vip.xiaonuo.common.annotation.CommonLog;
+import vip.xiaonuo.common.pojo.CommonResult;
+import vip.xiaonuo.common.pojo.CommonValidList;
+import vip.xiaonuo.mobile.modular.menu.entity.MobileMenu;
+import vip.xiaonuo.mobile.modular.menu.param.*;
+import vip.xiaonuo.mobile.modular.menu.service.MobileMenuService;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import javax.validation.constraints.NotEmpty;
+import java.util.List;
+
+/**
+ * 移动端菜单控制器
+ *
+ * @author yubaoshan
+ * @date  2023/01/28 22:42
+ */
+@Api(tags = "移动端菜单控制器")
+@ApiSupport(author = "SNOWY_TEAM", order = 1)
+@RestController
+@Validated
+public class MobileMenuController {
+
+    @Resource
+    private MobileMenuService mobileMenuService;
+
+    /**
+     * 获取移动端菜单tree
+     *
+     * @author yubaoshan
+     * @date  2023/01/28 22:42
+     */
+    @ApiOperationSupport(order = 1)
+    @ApiOperation("获取移动端菜单tree")
+    @GetMapping("/mobile/menu/tree")
+    public CommonResult<List<Tree<String>>> tree(MobileMenuTreeParam mobileMenuTreeParam) {
+        return CommonResult.data(mobileMenuService.tree(mobileMenuTreeParam));
+    }
+
+    /**
+     * 添加移动端菜单
+     *
+     * @author yubaoshan
+     * @date  2023/01/28 22:42
+     */
+    @ApiOperationSupport(order = 2)
+    @ApiOperation("添加移动端菜单")
+    @CommonLog("添加移动端菜单")
+    @PostMapping("/mobile/menu/add")
+    public CommonResult<String> add(@RequestBody @Valid MobileMenuAddParam mobileMenuAddParam) {
+        mobileMenuService.add(mobileMenuAddParam);
+        return CommonResult.ok();
+    }
+
+    /**
+     * 编辑移动端菜单
+     *
+     * @author yubaoshan
+     * @date  2023/01/28 22:42
+     */
+    @ApiOperationSupport(order = 3)
+    @ApiOperation("编辑移动端菜单")
+    @CommonLog("编辑移动端菜单")
+    @PostMapping("/mobile/menu/edit")
+    public CommonResult<String> edit(@RequestBody @Valid MobileMenuEditParam mobileMenuEditParam) {
+        mobileMenuService.edit(mobileMenuEditParam);
+        return CommonResult.ok();
+    }
+
+    /**
+     * 删除移动端菜单
+     *
+     * @author yubaoshan
+     * @date  2023/01/28 22:42
+     */
+    @ApiOperationSupport(order = 4)
+    @ApiOperation("删除移动端菜单")
+    @CommonLog("删除移动端菜单")
+    @PostMapping("/mobile/menu/delete")
+    public CommonResult<String> delete(@RequestBody @Valid @NotEmpty(message = "集合不能为空")
+                                                   CommonValidList<MobileMenuIdParam> mobileMenuIdParamList) {
+        mobileMenuService.delete(mobileMenuIdParamList);
+        return CommonResult.ok();
+    }
+
+    /**
+     * 获取移动端菜单详情
+     *
+     * @author yubaoshan
+     * @date  2023/01/28 22:42
+     */
+    @ApiOperationSupport(order = 5)
+    @ApiOperation("获取移动端菜单详情")
+    @GetMapping("/mobile/menu/detail")
+    public CommonResult<MobileMenu> detail(@Valid MobileMenuIdParam mobileMenuIdParam) {
+        return CommonResult.data(mobileMenuService.detail(mobileMenuIdParam));
+    }
+}
diff --git a/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/entity/MobileMenu.java b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/entity/MobileMenu.java
new file mode 100644
index 00000000..fc8e69c7
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/entity/MobileMenu.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.mobile.modular.menu.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.TableField;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 移动端菜单实体
+ *
+ * @author yubaoshan
+ * @date  2023/01/28 22:42
+ **/
+@Getter
+@Setter
+@TableName("MOBILE_MENU")
+public class MobileMenu {
+
+    /** 主键 */
+    @TableId
+    @ApiModelProperty(value = "主键", position = 1)
+    private String id;
+
+    /** 父ID */
+    @ApiModelProperty(value = "父ID", position = 2)
+    private String parentId;
+
+    /** 名称 */
+    @ApiModelProperty(value = "名称", position = 3)
+    private String title;
+
+    /** 编码 */
+    @ApiModelProperty(value = "编码", position = 4)
+    private String code;
+
+    /** 界面路径 */
+    @ApiModelProperty(value = "界面路径", position = 5)
+    private String pages;
+
+    /** 分类 */
+    @ApiModelProperty(value = "分类", position = 6)
+    private String category;
+
+    /** 图标 */
+    @ApiModelProperty(value = "图标", position = 7)
+    private String icon;
+
+    /** 颜色 */
+    @ApiModelProperty(value = "颜色", position = 8)
+    private String color;
+
+    /** 正规则 */
+    @ApiModelProperty(value = "正规则", position = 9)
+    private String isRegExp;
+
+    /** 可用状态 */
+    @ApiModelProperty(value = "可用状态", position = 10)
+    private String status;
+
+    /** 排序码 */
+    @ApiModelProperty(value = "排序码", position = 11)
+    private Integer sortCode;
+
+    /** 扩展信息 */
+    @ApiModelProperty(value = "扩展信息", position = 12)
+    private String extJson;
+
+    /** 删除标志 */
+    @ApiModelProperty(value = "删除标志", position = 13)
+    private String deleteFlag;
+
+    /** 创建时间 */
+    @ApiModelProperty(value = "创建时间", position = 14)
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    /** 创建用户 */
+    @ApiModelProperty(value = "创建用户", position = 15)
+    @TableField(fill = FieldFill.INSERT)
+    private String createUser;
+
+    /** 修改时间 */
+    @ApiModelProperty(value = "修改时间", position = 16)
+    @TableField(fill = FieldFill.UPDATE)
+    private Date updateTime;
+
+    /** 修改用户 */
+    @ApiModelProperty(value = "修改用户", position = 17)
+    @TableField(fill = FieldFill.UPDATE)
+    private String updateUser;
+}
diff --git a/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/enums/MobileMenuEnum.java b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/enums/MobileMenuEnum.java
new file mode 100644
index 00000000..647d14ee
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/enums/MobileMenuEnum.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.mobile.modular.menu.enums;
+
+import lombok.Getter;
+
+/**
+ * 移动端菜单枚举
+ *
+ * @author yubaoshan
+ * @date  2023/01/28 22:42
+ **/
+@Getter
+public enum MobileMenuEnum {
+
+    /** 测试 */
+    TEST("TEST");
+
+    private final String value;
+
+    MobileMenuEnum(String value) {
+        this.value = value;
+    }
+}
diff --git a/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/mapper/MobileMenuMapper.java b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/mapper/MobileMenuMapper.java
new file mode 100644
index 00000000..a5a41a5e
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/mapper/MobileMenuMapper.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.mobile.modular.menu.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import vip.xiaonuo.mobile.modular.menu.entity.MobileMenu;
+
+/**
+ * 移动端菜单Mapper接口
+ *
+ * @author yubaoshan
+ * @date  2023/01/28 22:42
+ **/
+public interface MobileMenuMapper extends BaseMapper<MobileMenu> {
+}
diff --git a/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/mapper/mapping/MobileMenuMapper.xml b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/mapper/mapping/MobileMenuMapper.xml
new file mode 100644
index 00000000..f9a66fb1
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/mapper/mapping/MobileMenuMapper.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="vip.xiaonuo.mobile.modular.menu.mapper.MobileMenuMapper">
+
+</mapper>
\ No newline at end of file
diff --git a/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/param/MobileMenuAddParam.java b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/param/MobileMenuAddParam.java
new file mode 100644
index 00000000..657eb48f
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/param/MobileMenuAddParam.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.mobile.modular.menu.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 移动端菜单添加参数
+ *
+ * @author yubaoshan
+ * @date  2023/01/28 22:42
+ **/
+@Getter
+@Setter
+public class MobileMenuAddParam {
+
+    /** 父ID */
+    @ApiModelProperty(value = "父ID", required = true, position = 2)
+    @NotBlank(message = "parentId不能为空")
+    private String parentId;
+
+    /** 名称 */
+    @ApiModelProperty(value = "名称", required = true, position = 3)
+    @NotBlank(message = "title不能为空")
+    private String title;
+
+    /** 界面路径 */
+    @ApiModelProperty(value = "界面路径", required = true, position = 5)
+    @NotBlank(message = "pages不能为空")
+    private String pages;
+
+    /** 分类 */
+    @ApiModelProperty(value = "分类", required = true, position = 6)
+    @NotBlank(message = "category不能为空")
+    private String category;
+
+    /** 图标 */
+    @ApiModelProperty(value = "图标", required = true, position = 7)
+    @NotBlank(message = "icon不能为空")
+    private String icon;
+
+    /** 颜色 */
+    @ApiModelProperty(value = "颜色", required = true, position = 8)
+    @NotBlank(message = "color不能为空")
+    private String color;
+
+    /** 正规则 */
+    @ApiModelProperty(value = "正规则", required = true, position = 9)
+    @NotBlank(message = "isRegExp不能为空")
+    private String isRegExp;
+
+    /** 可用状态 */
+    @ApiModelProperty(value = "可用状态", required = true, position = 10)
+    @NotBlank(message = "status不能为空")
+    private String status;
+
+    /** 排序码 */
+    @ApiModelProperty(value = "排序码", position = 11)
+    private Integer sortCode;
+
+}
diff --git a/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/param/MobileMenuEditParam.java b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/param/MobileMenuEditParam.java
new file mode 100644
index 00000000..3c46bd4f
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/param/MobileMenuEditParam.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.mobile.modular.menu.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 移动端菜单编辑参数
+ *
+ * @author yubaoshan
+ * @date  2023/01/28 22:42
+ **/
+@Getter
+@Setter
+public class MobileMenuEditParam {
+
+    /** 主键 */
+    @ApiModelProperty(value = "主键", required = true, position = 1)
+    @NotBlank(message = "id不能为空")
+    private String id;
+
+    /** 父ID */
+    @ApiModelProperty(value = "父ID", required = true, position = 2)
+    @NotBlank(message = "parentId不能为空")
+    private String parentId;
+
+    /** 名称 */
+    @ApiModelProperty(value = "名称", required = true, position = 3)
+    @NotBlank(message = "title不能为空")
+    private String title;
+
+    /** 界面路径 */
+    @ApiModelProperty(value = "界面路径", required = true, position = 5)
+    @NotBlank(message = "pages不能为空")
+    private String pages;
+
+    /** 分类 */
+    @ApiModelProperty(value = "分类", required = true, position = 6)
+    @NotBlank(message = "category不能为空")
+    private String category;
+
+    /** 图标 */
+    @ApiModelProperty(value = "图标", required = true, position = 7)
+    @NotBlank(message = "icon不能为空")
+    private String icon;
+
+    /** 颜色 */
+    @ApiModelProperty(value = "颜色", required = true, position = 8)
+    @NotBlank(message = "color不能为空")
+    private String color;
+
+    /** 正规则 */
+    @ApiModelProperty(value = "正规则", required = true, position = 9)
+    @NotBlank(message = "isRegExp不能为空")
+    private String isRegExp;
+
+    /** 可用状态 */
+    @ApiModelProperty(value = "可用状态", required = true, position = 10)
+    @NotBlank(message = "status不能为空")
+    private String status;
+
+    /** 排序码 */
+    @ApiModelProperty(value = "排序码", position = 11)
+    private Integer sortCode;
+
+}
diff --git a/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/param/MobileMenuIdParam.java b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/param/MobileMenuIdParam.java
new file mode 100644
index 00000000..a170a835
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/param/MobileMenuIdParam.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.mobile.modular.menu.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 移动端菜单Id参数
+ *
+ * @author yubaoshan
+ * @date  2023/01/28 22:42
+ **/
+@Getter
+@Setter
+public class MobileMenuIdParam {
+
+    /** 主键 */
+    @ApiModelProperty(value = "主键", required = true)
+    @NotBlank(message = "id不能为空")
+    private String id;
+}
diff --git a/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/param/MobileMenuTreeParam.java b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/param/MobileMenuTreeParam.java
new file mode 100644
index 00000000..67791c33
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/param/MobileMenuTreeParam.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.mobile.modular.menu.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 移动端菜单tree查询参数
+ *
+ * @author yubaoshan
+ * @date  2023/01/28 22:42
+ **/
+@Getter
+@Setter
+public class MobileMenuTreeParam {
+
+    /** 关键词 */
+    @ApiModelProperty(value = "关键词")
+    private String searchKey;
+
+    /** 分类 */
+    @ApiModelProperty(value = "分类")
+    private String category;
+
+    /** 可用状态 */
+    @ApiModelProperty(value = "可用状态")
+    private String status;
+
+}
diff --git a/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/service/MobileMenuService.java b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/service/MobileMenuService.java
new file mode 100644
index 00000000..f904c2ac
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/service/MobileMenuService.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.mobile.modular.menu.service;
+
+import cn.hutool.core.lang.tree.Tree;
+import com.baomidou.mybatisplus.extension.service.IService;
+import vip.xiaonuo.mobile.modular.menu.entity.MobileMenu;
+import vip.xiaonuo.mobile.modular.menu.param.*;
+
+import java.util.List;
+
+/**
+ * 移动端菜单Service接口
+ *
+ * @author yubaoshan
+ * @date  2023/01/28 22:42
+ **/
+public interface MobileMenuService extends IService<MobileMenu> {
+
+    /**
+     * 获取移动端菜单tree
+     *
+     * @author yubaoshan
+     * @date  2023/01/28 22:42
+     */
+    List<Tree<String>> tree(MobileMenuTreeParam mobileMenuTreeParam);
+
+    /**
+     * 添加移动端菜单
+     *
+     * @author yubaoshan
+     * @date  2023/01/28 22:42
+     */
+    void add(MobileMenuAddParam mobileMenuAddParam);
+
+    /**
+     * 编辑移动端菜单
+     *
+     * @author yubaoshan
+     * @date  2023/01/28 22:42
+     */
+    void edit(MobileMenuEditParam mobileMenuEditParam);
+
+    /**
+     * 删除移动端菜单
+     *
+     * @author yubaoshan
+     * @date  2023/01/28 22:42
+     */
+    void delete(List<MobileMenuIdParam> mobileMenuIdParamList);
+
+    /**
+     * 获取移动端菜单详情
+     *
+     * @author yubaoshan
+     * @date  2023/01/28 22:42
+     */
+    MobileMenu detail(MobileMenuIdParam mobileMenuIdParam);
+
+    /**
+     * 获取移动端菜单详情
+     *
+     * @author yubaoshan
+     * @date  2023/01/28 22:42
+     **/
+    MobileMenu queryEntity(String id);
+}
diff --git a/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/service/impl/MobileMenuServiceImpl.java b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/service/impl/MobileMenuServiceImpl.java
new file mode 100644
index 00000000..0a5c9450
--- /dev/null
+++ b/snowy-plugin/snowy-plugin-mobile/src/main/java/vip/xiaonuo/mobile/modular/menu/service/impl/MobileMenuServiceImpl.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright [2022] [https://www.xiaonuo.vip]
+ *
+ * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
+ *
+ * 1.请不要删除和修改根目录下的LICENSE文件。
+ * 2.请不要删除和修改Snowy源码头部的版权声明。
+ * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
+ * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
+ * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。
+ * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip
+ */
+package vip.xiaonuo.mobile.modular.menu.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollStreamUtil;
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.lang.tree.TreeNode;
+import cn.hutool.core.lang.tree.TreeUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import vip.xiaonuo.common.exception.CommonException;
+import vip.xiaonuo.mobile.modular.menu.entity.MobileMenu;
+import vip.xiaonuo.mobile.modular.menu.mapper.MobileMenuMapper;
+import vip.xiaonuo.mobile.modular.menu.param.*;
+import vip.xiaonuo.mobile.modular.menu.service.MobileMenuService;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 移动端菜单Service接口实现类
+ *
+ * @author yubaoshan
+ * @date  2023/01/28 22:42
+ **/
+@Service
+public class MobileMenuServiceImpl extends ServiceImpl<MobileMenuMapper, MobileMenu> implements MobileMenuService {
+
+    @Override
+    public List<Tree<String>> tree(MobileMenuTreeParam mobileMenuTreeParam) {
+        LambdaQueryWrapper<MobileMenu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
+        if(ObjectUtil.isNotEmpty(mobileMenuTreeParam.getCategory())) {
+            lambdaQueryWrapper.eq(MobileMenu::getCategory, mobileMenuTreeParam.getCategory());
+        }
+        if(ObjectUtil.isNotEmpty(mobileMenuTreeParam.getSearchKey())) {
+            lambdaQueryWrapper.like(MobileMenu::getTitle, mobileMenuTreeParam.getSearchKey());
+        }
+        if(ObjectUtil.isNotEmpty(mobileMenuTreeParam.getStatus())) {
+            lambdaQueryWrapper.like(MobileMenu::getStatus, mobileMenuTreeParam.getStatus());
+        }
+        lambdaQueryWrapper.orderByDesc(MobileMenu::getSortCode);
+        List<MobileMenu> mobileMenuList = this.list(lambdaQueryWrapper);
+        List<TreeNode<String>> treeNodeList = mobileMenuList.stream().map(mobileMenu ->
+                        new TreeNode<>(mobileMenu.getId(), mobileMenu.getParentId(),
+                                mobileMenu.getTitle(), mobileMenu.getSortCode()).setExtra(JSONUtil.parseObj(mobileMenu)))
+                .collect(Collectors.toList());
+        return TreeUtil.build(treeNodeList, "0");
+    }
+
+    @Override
+    public void add(MobileMenuAddParam mobileMenuAddParam) {
+        MobileMenu mobileMenu = BeanUtil.toBean(mobileMenuAddParam, MobileMenu.class);
+        this.save(mobileMenu);
+    }
+
+    @Override
+    public void edit(MobileMenuEditParam mobileMenuEditParam) {
+        MobileMenu mobileMenu = this.queryEntity(mobileMenuEditParam.getId());
+        BeanUtil.copyProperties(mobileMenuEditParam, mobileMenu);
+        this.updateById(mobileMenu);
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void delete(List<MobileMenuIdParam> mobileMenuIdParamList) {
+        // 执行删除
+        this.removeBatchByIds(CollStreamUtil.toList(mobileMenuIdParamList, MobileMenuIdParam::getId));
+    }
+
+    @Override
+    public MobileMenu detail(MobileMenuIdParam mobileMenuIdParam) {
+        return this.queryEntity(mobileMenuIdParam.getId());
+    }
+
+    @Override
+    public MobileMenu queryEntity(String id) {
+        MobileMenu mobileMenu = this.getById(id);
+        if(ObjectUtil.isEmpty(mobileMenu)) {
+            throw new CommonException("移动端菜单不存在,id值为:{}", id);
+        }
+        return mobileMenu;
+    }
+}
diff --git a/snowy-web-app/pom.xml b/snowy-web-app/pom.xml
index 21c3be20..72cdcbbf 100644
--- a/snowy-web-app/pom.xml
+++ b/snowy-web-app/pom.xml
@@ -135,6 +135,13 @@
             <version>${project.parent.version}</version>
         </dependency>
 
+        <!-- 移动端管理插件 -->
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-plugin-mobile</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+
         <!-- 系统功能插件 -->
         <dependency>
             <groupId>vip.xiaonuo</groupId>
diff --git a/snowy-web-app/src/main/resources/_sql/update20230128.sql b/snowy-web-app/src/main/resources/_sql/update20230128.sql
new file mode 100644
index 00000000..cd55e660
--- /dev/null
+++ b/snowy-web-app/src/main/resources/_sql/update20230128.sql
@@ -0,0 +1,27 @@
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ----------------------------
+-- Records of dev_dict
+-- ----------------------------
+INSERT INTO `dev_dict` VALUES ('1619342903569928193', '0', '移动菜单分类', 'MOBILE_CATEGORY', 'FRM', 90, NULL, 'NOT_DELETE', '2023-01-28 22:33:29', '1543837863788879871', '2023-01-28 22:33:45', '1543837863788879871');
+INSERT INTO `dev_dict` VALUES ('1619343064064970754', '1619342903569928193', '系统', 'SYS', 'FRM', 91, NULL, 'NOT_DELETE', '2023-01-28 22:34:07', '1543837863788879871', '2023-01-28 22:34:22', '1543837863788879871');
+INSERT INTO `dev_dict` VALUES ('1619343182029770754', '1619342903569928193', '业务', 'BIZ', 'FRM', 92, NULL, 'NOT_DELETE', '2023-01-28 22:34:35', '1543837863788879871', NULL, NULL);
+INSERT INTO `dev_dict` VALUES ('1619343323218432002', '0', '移动菜单状态', 'MOBILE_STATUS', 'FRM', 93, NULL, 'NOT_DELETE', '2023-01-28 22:35:09', '1543837863788879871', '2023-01-28 22:35:31', '1543837863788879871');
+INSERT INTO `dev_dict` VALUES ('1619343680636047362', '1619343323218432002', '可用', 'ENABLE', 'FRM', 94, NULL, 'NOT_DELETE', '2023-01-28 22:36:34', '1543837863788879871', '2023-01-28 22:37:29', '1543837863788879871');
+INSERT INTO `dev_dict` VALUES ('1619343846382358529', '1619343323218432002', '不可用', 'DISABLED', 'FRM', 96, NULL, 'NOT_DELETE', '2023-01-28 22:37:14', '1543837863788879871', NULL, NULL);
+INSERT INTO `dev_dict` VALUES ('1619344256295882753', '0', '移动菜单规则', 'MOBILE_IS_REG_EXP', 'FRM', 97, NULL, 'NOT_DELETE', '2023-01-28 22:38:51', '1543837863788879871', NULL, NULL);
+INSERT INTO `dev_dict` VALUES ('1619344428111351809', '1619344256295882753', '正规则', 'YES', 'FRM', 98, NULL, 'NOT_DELETE', '2023-01-28 22:39:32', '1543837863788879871', NULL, NULL);
+INSERT INTO `dev_dict` VALUES ('1619344504456073218', '1619344256295882753', '反规则', 'NO', 'FRM', 98, NULL, 'NOT_DELETE', '2023-01-28 22:39:50', '1543837863788879871', NULL, NULL);
+
+-- ----------------------------
+-- Records of sys_relation
+-- ----------------------------
+INSERT INTO `sys_relation` VALUES ('1619345262266142721', '1570687866138206208', '1619345262001901569', 'SYS_ROLE_HAS_RESOURCE', '{\"menuId\":\"1619345262001901569\",\"buttonInfo\":[\"1619345262085787650\",\"1619345262131924994\",\"1619345262131924995\",\"1619345262131924996\"]}');
+
+-- ----------------------------
+-- Records of sys_resource
+-- ----------------------------
+INSERT INTO `sys_resource` VALUES ('1619345262001901569', '0', '移动端菜单', 'menu', '4a84jeju7l', 'MENU', '1548901111999770525', 'MENU', '/mobile/menu', 'mobile/menu/index', 'appstore-outlined', NULL, 99, NULL, 'NOT_DELETE', '2023-01-28 22:42:51', '1543837863788879871', NULL, NULL);
+
+SET FOREIGN_KEY_CHECKS = 1;