AI代码生成工具

This commit is contained in:
李琦
2026-08-11 09:39:41 +08:00
parent cab69b4193
commit 21f39eed85
8 changed files with 431 additions and 4 deletions

View File

@@ -0,0 +1,35 @@
# 模板名称 — uniapp 粘贴说明C 端三页)
## 落点
`nl-admin-uniapp/src/pages-sub/my-gen/dashCase/`
## pages.json 片段
`subPackages``root: pages-sub/my-gen``pages` 追加:
```json
{ "path": "dashCase/pages/list", "style": { "navigationBarTitleText": "模板名称" } },
{ "path": "dashCase/pages/detail", "style": { "navigationBarTitleText": "模板名称详情" } },
{ "path": "dashCase/pages/form", "style": { "navigationBarTitleText": "编辑模板名称" } }
```
## features.ts 片段
```ts
{
key: 'dashCase',
title: '模板名称',
desc: '代码生成模块',
icon: '码',
category: 'gen',
path: '/pages-sub/my-gen/dashCase/pages/list',
keywords: ['模板名称'],
}
```
## 强制约定
- 兼容微信小程序 + App + H5
- 交互:列表 → 详情 → 全屏表单;**禁止**底部抽屉做主 CRUD
- 复用 `AppCardList` / `AppDetailHero` / `AppInfoGroup` / `AppBottomBar` / `AppPageForm`

View File

@@ -0,0 +1,33 @@
/**
* 模板名称 APIuniapp
* 粘贴后路径pages-sub/my-gen/dashCase/api/index.ts
* 必须兼容微信小程序 + App + H5
*/
import { get, post } from '@/utils/request'
const prefix = 'dashCase/'
/** 分页列表 */
export function getupperCamelCaseList(params: Record<string, any> = {}) {
return get<any>(`${prefix}list`, params)
}
/** 详情 */
export function getupperCamelCaseInfo(id: number) {
return get<any>(`${prefix}detail`, { id })
}
/** 新增 */
export function createupperCamelCase(data: Record<string, any>) {
return post<any>(`${prefix}create`, data)
}
/** 更新 */
export function updateupperCamelCase(data: Record<string, any>) {
return post<any>(`${prefix}update`, data)
}
/** 删除 */
export function deleteupperCamelCase(data: Record<string, any>) {
return post<any>(`${prefix}delete`, data)
}

View File

@@ -0,0 +1,58 @@
<script setup lang="ts">
/**
* 模板名称详情:信息展示 + 底栏编辑/删除(禁止抽屉主路径)
*/
import { computed, ref } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
import { deleteupperCamelCase, getupperCamelCaseInfo } from '../api'
import AppBottomBar from '@/components/AppBottomBar.vue'
import AppDetailHero from '@/components/AppDetailHero.vue'
import AppInfoGroup, { type AppInfoRow } from '@/components/AppInfoGroup.vue'
const id = ref(0)
const info = ref<Record<string, any>>({})
onLoad((q) => { id.value = Number(q?.id || 0) })
onShow(async () => {
if (id.value) info.value = (await getupperCamelCaseInfo(id.value)) || {}
})
const title = computed(() => String(info.value.UNI_TITLE_FIELD || `模板名称#${id.value}`))
const rows = computed<AppInfoRow[]>(() => [
// UNI_DETAIL_ROWS
{ label: '状态', value: Number(info.value.status) === 0 ? '正常' : '禁用' },
{ label: '创建时间', value: info.value.created_at },
])
function goEdit() {
uni.navigateTo({ url: `/pages-sub/my-gen/dashCase/pages/form?id=${id.value}` })
}
function onDelete() {
uni.showModal({
title: '删除确认',
content: '确定删除该记录吗?',
confirmColor: '#ef4444',
success: async (res) => {
if (!res.confirm) return
await deleteupperCamelCase({ ids: [id.value] })
uni.showToast({ title: '已删除', icon: 'success' })
setTimeout(() => uni.navigateBack(), 400)
},
})
}
</script>
<template>
<view class="nl-page">
<AppDetailHero
:title="title"
:subtitle="String(info.UNI_SUBTITLE_FIELD || '')"
:status-text="Number(info.status) === 0 ? '正常' : '禁用'"
:status-type="Number(info.status) === 0 ? 'success' : 'danger'"
/>
<AppInfoGroup title="详细信息" :items="rows" />
<AppBottomBar primary-text="编辑" danger-text="删除" @primary="goEdit" @danger="onDelete" />
</view>
</template>

View File

@@ -0,0 +1,57 @@
<script setup lang="ts">
/**
* 模板名称新建/编辑全屏表单(替代底部抽屉)
*/
import { computed, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { createupperCamelCase, getupperCamelCaseInfo, updateupperCamelCase } from '../api'
import AppPageForm from '@/components/AppPageForm.vue'
const id = ref(0)
const submitting = ref(false)
const form = ref<Record<string, any>>({
// UNI_FORM_DEFAULTS
})
const isEdit = computed(() => !!id.value)
onLoad(async (q) => {
id.value = Number(q?.id || 0)
if (id.value) {
const info = await getupperCamelCaseInfo(id.value)
form.value = {
// UNI_FORM_ASSIGN
}
// 回填兜底
Object.assign(form.value, info || {})
}
})
async function onSubmit() {
submitting.value = true
try {
if (isEdit.value) await updateupperCamelCase({ id: id.value, ...form.value })
else await createupperCamelCase({ ...form.value })
uni.showToast({ title: '保存成功', icon: 'success' })
setTimeout(() => uni.navigateBack(), 400)
} finally {
submitting.value = false
}
}
</script>
<template>
<AppPageForm tip="请填写模板名称信息后保存。" :loading="submitting" @submit="onSubmit">
<!-- UNI_FORM_FIELDS -->
</AppPageForm>
</template>
<style scoped lang="scss">
.form-item {
display: flex; align-items: center; justify-content: space-between;
min-height: 100rpx; border-bottom: 1rpx solid $nl-color-border;
}
.label { width: 160rpx; color: $nl-color-text-secondary; font-size: 28rpx; }
.input { flex: 1; text-align: right; font-size: 28rpx; }
</style>

View File

@@ -0,0 +1,120 @@
<script setup lang="ts">
/**
* 模板名称列表C 端):点击进详情,新增进全屏表单
* 兼容微信小程序 / App / H5
*/
import { computed, ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { getupperCamelCaseList } from '../api'
import AppCardList, { type AppCardItem } from '@/components/AppCardList.vue'
import AppEmpty from '@/components/AppEmpty.vue'
import AppFilterBar from '@/components/AppFilterBar.vue'
import AppSearchBar from '@/components/AppSearchBar.vue'
import { usePullRefresh } from '@/composables/usePullRefresh'
const list = ref<any[]>([])
const keyword = ref('')
const draftKeyword = ref('')
const loading = ref(false)
const page = ref(1)
const finished = ref(false)
const selectedCount = computed(() => (keyword.value ? 1 : 0))
const cards = computed<AppCardItem[]>(() =>
list.value.map((item) => ({
id: item.id,
title: String(item.UNI_TITLE_FIELD || `模板名称#${item.id}`),
subtitle: String(item.UNI_SUBTITLE_FIELD || ''),
iconText: String(item.UNI_TITLE_FIELD || '模').slice(0, 1),
statusText: Number(item.status) === 0 ? '正常' : '禁用',
statusType: Number(item.status) === 0 ? 'success' : 'danger',
})),
)
onShow(() => reload())
// pages.json 该页需 enablePullDownRefresh: true
usePullRefresh(reload)
async function reload() {
page.value = 1
finished.value = false
list.value = []
await loadMore()
}
async function loadMore() {
if (loading.value || finished.value) return
loading.value = true
try {
const res = await getupperCamelCaseList({
page: page.value,
pageSize: 20,
keyword: keyword.value || undefined,
})
const items = res?.items || res?.data || []
list.value = page.value === 1 ? items : list.value.concat(items)
const total = Number(res?.total || 0)
if (!items.length || list.value.length >= total) finished.value = true
else page.value += 1
} finally {
loading.value = false
}
}
function onFilterReset() {
draftKeyword.value = ''
}
function onFilterConfirm() {
keyword.value = draftKeyword.value
reload()
}
function goDetail(item: AppCardItem) {
uni.navigateTo({ url: `/pages-sub/my-gen/dashCase/pages/detail?id=${item.id}` })
}
function goCreate() {
uni.navigateTo({ url: '/pages-sub/my-gen/dashCase/pages/form' })
}
</script>
<template>
<view class="nl-page page">
<AppSearchBar
v-model="keyword"
placeholder="搜索模板名称"
@search="(v) => { keyword = v; reload() }"
@clear="() => { keyword = ''; reload() }"
/>
<AppFilterBar :selected-count="selectedCount" @reset="onFilterReset" @confirm="onFilterConfirm">
<view class="filter-row">
<text>关键词</text>
<input v-model="draftKeyword" class="filter-input" placeholder="关键词" />
</view>
</AppFilterBar>
<AppCardList v-if="cards.length" :list="cards" @select="goDetail" />
<AppEmpty v-else text="暂无数据" />
<view v-if="cards.length" class="more" @tap="loadMore">
{{ finished ? '没有更多了' : loading ? '加载中' : '加载更多' }}
</view>
<view class="fab" @tap="goCreate">新增</view>
</view>
</template>
<style scoped lang="scss">
.page { padding-bottom: 160rpx; }
.filter-row {
display: flex; align-items: center; justify-content: space-between; min-height: 72rpx; font-size: 26rpx;
}
.filter-input { flex: 1; text-align: right; margin-left: 24rpx; }
.more { text-align: center; color: $nl-color-text-muted; padding: 16rpx; font-size: 24rpx; }
.fab {
position: fixed; right: 32rpx; bottom: calc(48rpx + env(safe-area-inset-bottom));
min-width: 120rpx; height: 88rpx; padding: 0 36rpx; border-radius: 999rpx;
background: $nl-color-primary; color: #fff; display: flex; align-items: center; justify-content: center;
font-weight: 700; box-shadow: 0 16rpx 40rpx rgba(255, 107, 0, 0.35);
}
</style>