AI代码生成工具
This commit is contained in:
31
.cursor/rules/Code-Generation.mdc
Normal file
31
.cursor/rules/Code-Generation.mdc
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
description: 代码生成双端产出与 uniapp C 端三页模板约定
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# 代码生成(双端前端)
|
||||
|
||||
`CodeGenerationService::generate` 一次生成必须包含:
|
||||
|
||||
1. **后端**:Controller / Service / Model / 路由 / 建表 / PC 菜单
|
||||
2. **PC 前端 zip**:`view/<dash-case>/` → `nl-admin-view/.../views/my-gen/`
|
||||
3. **uniapp 前端 zip**:`uniapp/<dash-case>/` → `nl-admin-uniapp/src/pages-sub/my-gen/<dash-case>/`
|
||||
|
||||
模板目录:
|
||||
|
||||
- `app/template/view/` — Vben Admin
|
||||
- `app/template/uniapp/` — 移动超管(C 端)
|
||||
|
||||
# uniapp 生成物强制
|
||||
|
||||
- 兼容 **微信小程序 + App + H5**
|
||||
- **交互必须是三页**:`pages/list.vue` → `pages/detail.vue` → `pages/form.vue`
|
||||
- 列表点击进详情;新增/编辑用全屏表单;**禁止**底部抽屉做主 CRUD
|
||||
- **C 端 UI**:大留白卡片、DetailHero、InfoGroup、BottomBar、PageForm;禁止 PC 宽表
|
||||
- 复用:`AppCardList` / `AppFilterBar` / `AppSearchBar` / `AppDetailHero` / `AppInfoGroup` / `AppBottomBar` / `AppPageForm`
|
||||
- `AppFormDrawer` 不得作为生成主路径
|
||||
- zip 内附带 `PAGES_JSON_SNIPPET.md`(注册 list/detail/form 三页 + features.ts)
|
||||
|
||||
# 与移动端规则对齐
|
||||
|
||||
以 `nl-admin-uniapp` 的 `Code-Standards.mdc` 为准;本仓库生成模板不得与之冲突。
|
||||
@@ -99,5 +99,11 @@ alwaysApply: true
|
||||
|
||||
## 代码生成
|
||||
|
||||
- 模板在 `app/template/`,服务在 `app/Service/core/CodeGenerationService`
|
||||
- 生成物需仍遵守本文件的分层、基类、路由注册与命名约定;生成后记得在 `routes/api.php` 的 `autoRouteRegister` 中挂上控制器
|
||||
- 模板在 `app/template/`:`view/`(PC Vben)与 `uniapp/`(移动端)两套并存;服务在 `app/Service/core/CodeGenerationService`
|
||||
- **`generate` 必须同时产出** zip 内的 `view/<dash-case>/` 与 `uniapp/<dash-case>/`,缺一不可
|
||||
- 生成物需仍遵守本文件的分层、基类、路由注册与命名约定;生成后记得在 `routes/api.php` 的 `autoRouteRegister` 中挂上控制器(当前 `genRoute` 会自动写入)
|
||||
- **uniapp 模板强制约定**(与移动端规则同口径):
|
||||
- 必须兼容 **微信小程序 + App + H5**
|
||||
- 交互必须是 **list → detail → form 三页**;禁止底部抽屉做主 CRUD;禁止 PC 宽表
|
||||
- 复用:`AppCardList` / `AppFilterBar` / `AppDetailHero` / `AppInfoGroup` / `AppBottomBar` / `AppPageForm`
|
||||
- 粘贴落点:`nl-admin-uniapp/src/pages-sub/my-gen/<dash-case>/`,并同步 `pages.json`(三页)与 `features.ts`
|
||||
|
||||
@@ -67,6 +67,19 @@ class CodeGenerationService
|
||||
private string $viewSearch = '';
|
||||
private string $searchField = '';
|
||||
private string $viewData = '';
|
||||
/** uniapp C 端表单字段 HTML 片段 */
|
||||
private string $uniFormFields = '';
|
||||
/** uniapp 表单默认值片段 */
|
||||
private string $uniFormDefaults = '';
|
||||
/** uniapp 表单编辑回填片段 */
|
||||
private string $uniFormAssign = '';
|
||||
/** uniapp 详情信息行片段 */
|
||||
private string $uniDetailRows = '';
|
||||
/** uniapp 卡片标题字段名 */
|
||||
private string $uniTitleField = 'id';
|
||||
/** uniapp 卡片副标题字段名 */
|
||||
private string $uniSubtitleField = '';
|
||||
private bool $uniTitlePicked = false;
|
||||
private array $tmpFile = [];
|
||||
|
||||
public function __construct()
|
||||
@@ -228,6 +241,23 @@ class CodeGenerationService
|
||||
// 生成唯一的临时目录名
|
||||
$this->tempDir = 'temp/templates/' . $this->upperCamelCase . '-' . $this->uniqid;
|
||||
Storage::makeDirectory($this->tempDir);
|
||||
// 重置动态拼接字段,避免批量生成串数据
|
||||
$this->selectField = '"id"';
|
||||
$this->insertField = '';
|
||||
$this->updateField = '';
|
||||
$this->viewForm = '';
|
||||
$this->viewSearch = '';
|
||||
$this->searchField = '';
|
||||
$this->viewData = '';
|
||||
$this->uniFormFields = '';
|
||||
$this->uniFormDefaults = '';
|
||||
$this->uniFormAssign = '';
|
||||
$this->uniDetailRows = '';
|
||||
$this->uniTitleField = 'id';
|
||||
$this->uniSubtitleField = '';
|
||||
$this->uniTitlePicked = false;
|
||||
$this->sql = '';
|
||||
$this->tmpFile = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,8 +286,10 @@ class CodeGenerationService
|
||||
$this->genRoute();
|
||||
// 执行sql
|
||||
$this->querySql();
|
||||
// 生成视图
|
||||
// 生成 PC 视图(Vben)
|
||||
$this->genViews();
|
||||
// 生成 uniapp 视图(微信小程序 + App + H5,C 端风格)
|
||||
$this->genUniappViews();
|
||||
// 生成菜单
|
||||
$this->genMenu();
|
||||
DB::commit();
|
||||
@@ -389,7 +421,7 @@ class CodeGenerationService
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成前端部分文件
|
||||
* 生成 PC 前端文件(Vben),写入 zip 的 view/ 目录
|
||||
* @return void
|
||||
*/
|
||||
private function genViews(): void
|
||||
@@ -413,6 +445,31 @@ class CodeGenerationService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 uniapp 前端文件(C 端 list/detail/form 三页),写入 zip 的 uniapp/ 目录
|
||||
* 禁止抽屉主路径;必须兼容微信小程序+App+H5
|
||||
* @return void
|
||||
*/
|
||||
private function genUniappViews(): void
|
||||
{
|
||||
$appPath = app_path();
|
||||
$uniList = [
|
||||
'uni-api' => 'api/index.ts',
|
||||
'uni-list' => 'pages/list.vue',
|
||||
'uni-detail' => 'pages/detail.vue',
|
||||
'uni-form' => 'pages/form.vue',
|
||||
'uni-readme' => 'PAGES_JSON_SNIPPET.md',
|
||||
];
|
||||
foreach ($uniList as $k => $v) {
|
||||
$this->strReplaceTmp(
|
||||
$appPath . '/template/uniapp/' . $v,
|
||||
$this->tempDir . "/uniapp/{$this->dashCase}/" . $v,
|
||||
'uniapp',
|
||||
$k
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成菜单/授权
|
||||
*
|
||||
@@ -477,6 +534,20 @@ class CodeGenerationService
|
||||
case 'view-table':
|
||||
$newFile = str_replace("// 生成的列表字段", "// 生成的列表字段 \r ". $this->viewData, $newFile);
|
||||
break;
|
||||
case 'uni-list':
|
||||
$newFile = str_replace('UNI_TITLE_FIELD', $this->uniTitleField, $newFile);
|
||||
$newFile = str_replace('UNI_SUBTITLE_FIELD', $this->uniSubtitleField ?: $this->uniTitleField, $newFile);
|
||||
break;
|
||||
case 'uni-detail':
|
||||
$newFile = str_replace('// UNI_DETAIL_ROWS', $this->uniDetailRows ?: '', $newFile);
|
||||
$newFile = str_replace('UNI_TITLE_FIELD', $this->uniTitleField, $newFile);
|
||||
$newFile = str_replace('UNI_SUBTITLE_FIELD', $this->uniSubtitleField ?: $this->uniTitleField, $newFile);
|
||||
break;
|
||||
case 'uni-form':
|
||||
$newFile = str_replace('<!-- UNI_FORM_FIELDS -->', $this->uniFormFields ?: '<!-- 无表单字段 -->', $newFile);
|
||||
$newFile = str_replace('// UNI_FORM_DEFAULTS', $this->uniFormDefaults ?: '', $newFile);
|
||||
$newFile = str_replace('// UNI_FORM_ASSIGN', $this->uniFormAssign ?: '', $newFile);
|
||||
break;
|
||||
}
|
||||
|
||||
$this->tmpFile[$type][$fileKey] = $newFile;
|
||||
@@ -510,6 +581,22 @@ class CodeGenerationService
|
||||
$this->insertField .= !empty($this->insertField) ? '","' . $item['name'] : $item['name'];
|
||||
$this->updateField .= !empty($this->updateField) ? '","' . $item['name'] : 'id","' . $item['name'];
|
||||
$this->viewForm .= "{\n fieldName: '" . $item['name'] . "',\n label: '" . $item['comment'] . "',\n component: '" . $item['formType'] . "',\n rules: " . $isNull . ",\n },\n";
|
||||
// uniapp C 端表单:用 input 承载,避免生成 Vben 组件名
|
||||
$name = $item['name'];
|
||||
$label = $item['comment'] ?: $name;
|
||||
$this->uniFormDefaults .= " {$name}: '',\n";
|
||||
$this->uniFormAssign .= " {$name}: info?.{$name} ?? '',\n";
|
||||
$this->uniDetailRows .= " { label: '{$label}', value: info.value.{$name} },\n";
|
||||
$this->uniFormFields .= "<view class=\"form-item\">\n"
|
||||
. " <text class=\"label\">{$label}</text>\n"
|
||||
. " <input v-model=\"form.{$name}\" class=\"input\" placeholder=\"{$label}\" />\n"
|
||||
. " </view>\n";
|
||||
if (!$this->uniTitlePicked) {
|
||||
$this->uniTitleField = $name;
|
||||
$this->uniTitlePicked = true;
|
||||
} elseif ($this->uniSubtitleField === '') {
|
||||
$this->uniSubtitleField = $name;
|
||||
}
|
||||
}
|
||||
// 设置搜索字段
|
||||
if ($item['search']) {
|
||||
|
||||
35
app/template/uniapp/PAGES_JSON_SNIPPET.md
Normal file
35
app/template/uniapp/PAGES_JSON_SNIPPET.md
Normal 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`
|
||||
33
app/template/uniapp/api/index.ts
Normal file
33
app/template/uniapp/api/index.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 模板名称 API(uniapp)
|
||||
* 粘贴后路径: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)
|
||||
}
|
||||
58
app/template/uniapp/pages/detail.vue
Normal file
58
app/template/uniapp/pages/detail.vue
Normal 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>
|
||||
57
app/template/uniapp/pages/form.vue
Normal file
57
app/template/uniapp/pages/form.vue
Normal 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>
|
||||
120
app/template/uniapp/pages/list.vue
Normal file
120
app/template/uniapp/pages/list.vue
Normal 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>
|
||||
Reference in New Issue
Block a user