refactor: 确立独立子工程架构 (backend, admin, mpuniapp) 并同步更新 AGENTS.md 规范

这个提交包含在:
wikiboss
2026-09-20 17:05:22 +08:00
父节点 de3cf8b870
当前提交 9fa1d1fbd0
共有 1679 个文件被更改,包括 204798 次插入 和 16817 次删除

查看文件

@@ -0,0 +1,242 @@
const platformInfo = process.env.uniTestPlatformInfo.toLocaleLowerCase()
const isWeb = platformInfo.startsWith('web')
const isMP = platformInfo.startsWith('mp')
const isDom2 = process.env.UNI_APP_X_DOM2 === "true"
const isAndroid = platformInfo.startsWith('android')
const isIos = platformInfo.startsWith('ios')
const isHarmony = platformInfo.startsWith('harmony')
const isApp = isAndroid || isIos || isHarmony
const PAGE_PATH = '/pages/component/button/button'
describe('Button.uvue', () => {
if (isMP) {
it('skip', () => {
expect(1).toBe(1)
})
return
}
let page
let screenShotOptions = { fullPage: true }
beforeAll(async () => {
page = await program.reLaunch(PAGE_PATH)
await page.waitFor('view')
const windowInfo = await program.callUniMethod('getWindowInfo')
screenShotOptions = {
fullPage: true,
area: {
x: windowInfo.safeArea.left,
width: windowInfo.safeArea.right - windowInfo.safeArea.left - 10
}
}
})
async function setPageData(newData) {
return await page.setData({ data: newData });
}
it('click', async () => {
// TODO 待测试框架支持text的dispatchEvent
const btn = await page.$('.btn')
expect(await page.data('data.count')).toEqual(0)
await btn.tap()
expect(await page.data('data.count')).toEqual(1)
if (isAndroid) { // android 平台 element.tap 不受 disabled 影响
return;
}
await setPageData({
disabled_boolean: true
})
await btn.tap()
expect(await page.data('data.count')).toEqual(1)
await setPageData({
disabled_boolean: false
})
await btn.tap()
expect(await page.data('data.count')).toEqual(2)
})
it('length', async () => {
const elements = await page.$$('.btn')
expect(elements.length).toBe(1)
})
it('text', async () => {
const textBtn = await page.$('.btn')
expect(await textBtn.text()).toEqual('uni-app-x')
await setPageData({text: 'uni-app-x button'})
expect(await textBtn.text()).toEqual('uni-app-x button')
})
it('type', async () => {
const btn = await page.$('.btn')
expect(await btn.property('type')).toBe('default')
await setPageData({type_enum_current: 1})
expect(await btn.property('type')).toBe('primary')
await setPageData({type_enum_current: 2})
expect(await btn.property('type')).toBe('warn')
})
it('size', async () => {
const btn = await page.$('.btn')
expect(await btn.property('size')).toBe('default')
await setPageData({size_enum_current: 1})
expect(await btn.property('size')).toBe('mini')
})
it('plain', async () => {
const btn = await page.$('.btn')
expect(await btn.property('plain')).toBe(false)
await setPageData({plain_boolean: true})
expect(await btn.property('plain')).toBe(true)
})
it('disabled', async () => {
const btn = await page.$('.btn')
await setPageData({disabled_boolean: false})
expect(await btn.property('disabled')).toBe(false)
await setPageData({disabled_boolean: true})
expect(await btn.property('disabled')).toBe(true)
})
it("checkUniButtonElement", async () => {
if (isMP) {
expect(1).toBe(1)
return
}
const value = await page.callMethod('checkUniButtonElement')
expect(value).toBe(true)
})
it("setbuttonEmpty", async () => {
const textBtn = await page.$('.btn')
await setPageData({text: ''})
expect(await textBtn.text()).toEqual('')
})
// 自定义button和默认button来回切换截图对比
it("button-screenshot-plain+primary+default", async () => {
if (isWeb || isMP) {
expect(1).toBe(1)
return
}
const btn = await page.$('.btn')
await setPageData({
text: 'uni-app-x',
plain_boolean: true,
type_enum_current: 1,
size_enum_current: 0,
disabled_boolean: false,
default_style: false
})
await page.waitFor(100);
expect(await btn.property('size')).toBe('default')
expect(await btn.property('plain')).toBe(true)
expect(await btn.property('type')).toBe('primary')
const image1 = await program.screenshot(screenShotOptions);
expect(image1).toSaveImageSnapshot({customSnapshotIdentifier() {
return 'button-screenshot-plain+primary+default'
}});
await setPageData({
text: 'uni-app-x',
plain_boolean: true,
type_enum_current: 1,
size_enum_current: 0,
disabled_boolean: false,
default_style: true
})
await page.waitFor(100);
const image2 = await program.screenshot(screenShotOptions);
expect(image2).toSaveImageSnapshot({customSnapshotIdentifier() {
return 'custom-button-screenshot-plain+primary+default'
}});
await setPageData({
text: 'uni-app-x',
plain_boolean: true,
type_enum_current: 1,
size_enum_current: 0,
disabled_boolean: false,
default_style: false
})
await page.waitFor(100);
const image3 = await program.screenshot(screenShotOptions);
expect(image3).toSaveImageSnapshot({customSnapshotIdentifier() {
return 'custom-button-screenshot-plain+primary+default-changeToDefault'
}});
})
})
describe('Buttonstatus.uvue', () => {
let page
let screenShotOptions = { fullPage: true }
beforeAll(async () => {
page = await program.reLaunch('/pages/component/button/buttonstatus')
await page.waitFor('button')
const windowInfo = await program.callUniMethod('getWindowInfo')
screenShotOptions = {
fullPage: true,
area: {
x: windowInfo.safeArea.left,
width: windowInfo.safeArea.right - windowInfo.safeArea.left - 10
}
}
})
test('newline', async () => {
const image = await program.screenshot(screenShotOptions);
expect(image).toSaveImageSnapshot({customSnapshotIdentifier() {
return 'buttonstatus-newline'
}});
})
test('loading-class', async () => {
if (!isDom2 || isMP) {
expect(1).toBe(1)
return
}
const btn = await page.$('.loading-class')
expect(await btn.attribute('loading-class')).toContain('custom-loading')
})
test('change-disabled-screenshot', async () => {
const changeDisabledButton = await page.$('#changeDisabledButton')
await changeDisabledButton.tap()
await page.waitFor(100);
const image = await program.screenshot(screenShotOptions);
expect(image).toSaveImageSnapshot({customSnapshotIdentifier() {
return 'buttonstatus-disabled'
}});
})
test('button-hover', async () => {
await page.callMethod('set_disabled_false')
await page.waitFor(100)
const btn = await page.$('#test-button-hover-class')
if (isApp) {
const rect = await page.callMethod('getHoverButtonRect')
const tapPoint = {
x: Math.round(rect.left + rect.width / 2.0),
y: Math.round(rect.y + rect.height - (isDom2 ? 20 : 10))
}
console.log('button rect', rect)
console.log('button tap point', tapPoint)
await program.tap({
x: tapPoint.x,
y: tapPoint.y,
duration: 300
})
} else {
await btn.longpress()
}
await page.waitFor(400)
const image = await program.screenshot(screenShotOptions);
expect(image).toSaveImageSnapshot({customSnapshotIdentifier() {
return 'buttonstatus-button-hover-class-default-value'
}});
})
})

查看文件

@@ -0,0 +1,199 @@
<script setup lang="uts">
import { ItemType } from '@/components/enum-data/enum-data-types'
type DataType = {
plain_boolean: boolean;
disabled_boolean: boolean;
default_style: boolean;
size_enum: ItemType[];
size_enum_current: number;
type_enum: ItemType[];
type_enum_current: number;
count: number;
text: string;
loading: boolean;
}
// 使用reactive避免ref数据在自动化测试中无法访问
const data = reactive({
plain_boolean: false,
disabled_boolean: false,
default_style: false,
size_enum: [{ "value": 0, "name": "default" }, { "value": 1, "name": "mini" }],
size_enum_current: 0,
type_enum: [{ "value": 0, "name": "default" }, { "value": 1, "name": "primary" }, { "value": 2, "name": "warn" }],
type_enum_current: 0,
count: 0,
text: '',
loading: false,
} as DataType)
onReady(() => {
data.text = 'uni-app-x'
})
const button_click = () => {
console.log("组件被点击时触发")
data.count++
}
const button_touchstart = () => {
console.log("手指触摸动作开始")
}
const button_touchmove = () => {
console.log("手指触摸后移动")
}
const button_touchcancel = () => {
console.log("手指触摸动作被打断,如来电提醒,弹窗")
}
const button_touchend = () => {
console.log("手指触摸动作结束")
}
const button_tap = () => {
console.log("手指触摸后马上离开")
}
const button_longpress = () => {
console.log("如果一个组件被绑定了 longpress 事件,那么当用户长按这个组件时,该事件将会被触发。")
}
const change_plain_boolean = (checked: boolean) => {
data.plain_boolean = checked
}
const change_loading = (checked: boolean) => {
data.loading = checked
}
const change_disabled_boolean = (checked: boolean) => {
data.disabled_boolean = checked
}
const change_default_style = (checked: boolean) => {
data.default_style = checked
}
const radio_change_size_enum = (checked: number) => {
data.size_enum_current = checked
}
const radio_change_type_enum = (checked: number) => {
data.type_enum_current = checked
}
const confirm_text_input = (value: string) => {
data.text = value
}
const navigateToChild = () => {
uni.navigateTo({
url: 'buttonstatus',
})
}
const openPrivacyDialog = () => {
uni.openDialogPage({
url: '/pages/component/button/privacy',
})
}
//用于自动化测试
const checkUniButtonElement = (): boolean => {
const button = uni.getElementById("testButton")
if (button != null && button instanceof UniButtonElement) {
return true
}
return false
}
defineExpose({
data,
checkUniButtonElement
})
</script>
<template>
<view class="main">
<button id="testButton" :loading="data.loading" :disabled="data.disabled_boolean"
:size="data.size_enum[data.size_enum_current].name" :type="data.type_enum[data.type_enum_current].name"
:plain="data.plain_boolean" @touchstart="button_touchstart" @touchmove="button_touchmove"
@touchcancel="button_touchcancel" @touchend="button_touchend" @tap="button_tap" @click="button_click"
@longpress="button_longpress" class="btn"
:loading-text-class="data.loading ? (data.type_enum[data.type_enum_current].name == 'default' ? 'black-loading-text' : 'white-loading-text') : ''"
:class="data.default_style ? (data.disabled_boolean ? 'custom-btn-disable' : 'custom-btn') : ''"
:hover-class="data.default_style ? 'is-hover' : 'button-hover'">
{{ data.text }}
</button>
</view>
<scroll-view style="flex: 1">
<view class="content">
<boolean-data :defaultValue="false" title="按钮是否镂空,背景色透明" @change="change_plain_boolean"></boolean-data>
<boolean-data :defaultValue="false" title="是否禁用" @change="change_disabled_boolean"></boolean-data>
<boolean-data :defaultValue="false" title="修改默认样式和点击效果(高优先)" @change="change_default_style"></boolean-data>
<boolean-data :defaultValue="false" title="显示loading(限小程序和蒸汽模式的App)" @change="change_loading"></boolean-data>
<enum-data :items="data.size_enum" title="按钮的大小" @change="radio_change_size_enum"></enum-data>
<enum-data :items="data.type_enum" title="按钮的类型" @change="radio_change_type_enum"></enum-data>
<input-data :defaultValue="data.text" title="按钮的文案" type="text" @confirm="confirm_text_input"></input-data>
<view style="height: 10px;"></view>
<button @click="navigateToChild">更多button示例</button>
<view style="height: 10px;"></view>
<!-- #ifdef APP -->
<button @click="openPrivacyDialog">open-type实现App隐私政策弹框</button>
<view style="height: 10px;"></view>
<!-- #endif -->
</view>
<navigator url="/pages/template/button-100/button-100">
<button>组件性能测试</button>
</navigator>
</scroll-view>
</template>
<style>
.main {
padding: 5px 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
flex-direction: row;
justify-content: center;
}
.custom-btn {
color: #ffffff;
background-color: #1AAD19;
border-color: #1AAD19;
}
.custom-btn-disable {
color: rgba(255, 255, 255, 0.7);
background-color: rgba(26, 173, 25, 0.7);
border-color: rgba(26, 173, 25, 0.7);
}
.is-hover {
color: rgba(255, 255, 255, 0.6);
background-color: #179b16;
border-color: #179b16;
}
.button-hover {
color: rgba(0, 0, 0, 0.6);
background-color: #dedede;
}
.default-button {
color: #000000;
background-color: #f8f8f8;
border-color: rgba(0, 0, 0, 0.2);
}
.black-loading-text {
color: #000000
}
.white-loading-text {
color: #ffffff
}
</style>

查看文件

@@ -0,0 +1,179 @@
<template>
<!-- #ifdef APP && !VUE3-VAPOR -->
<scroll-view style="flex:1;">
<!-- #endif -->
<view style="padding: 8px;">
<!--type-->
<button class="margin-10 btn" :disabled="disabled_boolean">
type-default
</button>
<button class="margin-10 btn" :disabled="disabled_boolean" type="primary">
type-primary
</button>
<button class="margin-10 btn" :disabled="disabled_boolean" type="warn">
type-warn
</button>
<!--size-->
<button class="margin-10 btn" :disabled="disabled_boolean">
size-default
</button>
<button class="margin-10 btn" :disabled="disabled_boolean" size="mini">
size-mini
</button>
<!--plain-->
<button class="margin-10 btn" :disabled="disabled_boolean" :plain="true">
plain=true
</button>
<button class="margin-10 btn" :disabled="disabled_boolean" type="primary" :plain="true">
plain=true type=primary
</button>
<button class="margin-10 btn" :disabled="disabled_boolean" type="warn" :plain="true">
plain=true type=warn
</button>
<button class="margin-10 btn" :disabled="disabled_boolean">
plain-false
</button>
<button id="test-button-hover-class" class="margin-10" :disabled="disabled_boolean" hover-class="button-hover" :hover-stay-time="5000">hover-class button-hover</button>
<button class="margin-10 btn custom-btn" :disabled="disabled_boolean" hover-class="is-hover">
custom-btn
</button>
<button class="margin-10 btn custom-btn" :disabled="disabled_boolean" plain hover-class="is-hover">
custom-btn
</button>
<button class="margin-10 btn" style="font-size: 15px;color: red;" type="warn" :plain="true" :disabled="disabled_boolean">
custom-btn-plain-warn
</button>
<button class="margin-10" type="primary" :disabled="disabled_boolean" hover-class="none">hover-class none</button>
<button class="margin-10" type="primary" :disabled="disabled_boolean">uni-app x</button>
<button id="changeDisabledButton" class="margin-10" type="primary" @click="change_disabled_boolean">修改disabled为{{!disabled_boolean}}</button>
<!--disable-->
<button class="margin-10 btn">
disable-false
</button>
<button class="margin-10 btn" :disabled="true">
disable-true
</button>
<button class="margin-10 btn" :disabled="disabled_boolean">
uni-app x,是下一代 uni-app,是一个跨平台应用开发引擎。
</button>
<!-- #ifdef VUE3-VAPOR && !MP -->
<button class="margin-10 loading-class" :disabled="disabled_boolean" loading loading-class="custom-loading">
自定义 loading 状态
</button>
<!-- #endif -->
<!-- #ifdef VUE3-VAPOR && APP-IOS -->
<button class="margin-10" :disabled="disabled_boolean" loading ios-loading-snow>
iOS 雪花加载
</button>
<!-- #endif -->
</view>
<!-- #ifdef APP && !VUE3-VAPOR -->
</scroll-view>
<!-- #endif -->
</template>
<script setup lang="uts">
const disabled_boolean = ref(false)
function change_disabled_boolean() {
disabled_boolean.value = !disabled_boolean.value
}
function set_disabled_false() {
disabled_boolean.value = false
}
// #ifndef APP
defineExpose({
set_disabled_false,
})
// #endif
// #ifdef APP
type DomRectType = {
x : number,
y : number,
left : number,
top : number,
right : number,
bottom : number,
width : number,
height : number,
}
type Data = {
rect: DomRectType | null,
}
const data = reactive<Data>({
rect: null,
})
function getHoverButtonRect() : DomRectType | null {
const el = uni.getElementById('test-button-hover-class')
const rect = el?.getBoundingClientRect() ?? null
if (rect == null) {
return null
}
const offsetY = uni.getSystemInfoSync().safeArea.top + 44
return {
x: rect.x,
y: rect.y + offsetY,
left: rect.left,
top: rect.top + offsetY,
right: rect.right,
bottom: rect.bottom + offsetY,
width: rect.width,
height: rect.height,
}
}
onReady(() => {
data.rect = getHoverButtonRect()
})
defineExpose({
data,
getHoverButtonRect,
set_disabled_false,
})
// #endif
</script>
<style>
.custom-btn {
color: #ffffff;
background-color: #1AAD19;
border-color: #1AAD19;
border-radius: 5px;
border-width: 0.5px;
height: 53px;
}
.is-hover {
color: rgba(255, 255, 255, 0.6);
background-color: rgba(26, 173, 25, 0.7);
border-color: rgba(26, 173, 25, 0.7);
}
.button-hover {
color: rgba(26, 173, 25, 0.7);
}
/* #ifdef VUE3-VAPOR && !MP */
.loading-class {
color: #28cc1c;
font-size: 20px;
}
.custom-loading {
border-color: #28cc1c;
width: 20px;
height: 20px;
}
/* #endif */
</style>

查看文件

@@ -0,0 +1,29 @@
<template>
<web-view :src="src" :update-title="updateTitle"></web-view>
</template>
<script setup lang="uts">
const src = ref('')
const updateTitle = ref(true)
onLoad((options : OnLoadOptions) => {
if (typeof options['url'] === 'string' && options['url']!.length > 0) {
const srcValue = decodeURIComponent(options['url'] as string)
if (srcValue != null) {
src.value = srcValue
}
}
if (typeof options['title'] === 'string' && options['title']!.length > 0) {
updateTitle.value = false;
uni.setNavigationBarTitle({
title: options['title']!
});
}
})
</script>
<style>
</style>

查看文件

@@ -0,0 +1,131 @@
<template>
<view class="dialog-container">
<view class="dialog-content">
<view style="width: 100%;justify-content: center;align-items: center;background-color: #fff;">
<image src="/static/test-image/logo.png" style="margin-top: 20px;width: 60px;height: 60px;"></image>
</view>
<text
style="text-align: center;padding-top: 20px;font-size: 20px;color: black;background-color: #fff;">个人信息保护指引</text>
<scroll-view
style="flex: 1;align-content: center;padding-top: 10px;padding-left: 25px;padding-right: 25px;background-color: #fff;"
show-scrollbar="false">
<text>
<!-- #ifdef APP-HARMONY -->
<text class="privacy-text">欢迎使用DCloud开发者中心系统,我们将通过</text>
<!-- #endif -->
<!-- #ifndef APP-HARMONY -->
<text class="privacy-text">欢迎使用 Hello uni-app x 应用,我们将通过</text>
<!-- #endif -->
<text class="privacy-href" @click="hrefClick(hrefLicense)">《用户服务协议》</text>
<text class="privacy-text">及</text>
<text
class="privacy-text">相关个人信息处理规则帮助你了解我们如何收集、处理个人信息。根据《常见类型移动互联网应用程序必要个人信息范围规定》,应用在演示 uni-app x 能力时仅收集、处理相关必要个人信息及数据。我们通过</text>
<text class="privacy-href" @click="hrefClick(hrefPrivacy)">《隐私政策》</text>
<text class="privacy-text">帮助你全面了解我们的服务及收集、处理个人信息的详细情况。</text>
</text>
<text class="privacy-text">如你同意请点击“同意”按钮接受我们的服务。</text>
</scroll-view>
<button class="button" @click="reject">不同意</button>
<button class="button" type="primary" open-type="agreePrivacyAuthorization" @click="agree">同意</button>
</view>
</view>
</template>
<script setup lang="uts">
// #ifdef APP-ANDROID || APP-IOS || APP-HARMONY
import { openSchema } from '@/uni_modules/uts-openSchema'
// #endif
// Get page instance at component initialization
const currentPage = getCurrentInstance()?.proxy?.$page
let number = 0
const hrefLicense = 'https://dcloud.io/license/DCloud.html'
const hrefPrivacy = 'https://dcloud.io/license/hello-uni-app-x.html'
function agree() {
if (currentPage != null) {
uni.closeDialogPage({
dialogPage: currentPage
})
}
}
function reject() {
uni.resetPrivacyAuthorization()
if (currentPage != null) {
uni.closeDialogPage({
dialogPage: currentPage
})
}
}
function hrefClick(href : string) {
// #ifdef APP-HARMONY
uni.navigateTo({
url: '/pages/component/button/privacy-web-view?url=/hybrid/html/privacy.html&title=隐私政策'
})
// #endif
// #ifdef APP-ANDROID || APP-IOS
openSchema(href)
// #endif
}
onLoad((_) => {
number = uni.onPrivacyAuthorizationChange((callback) => {
uni.showToast({
title: 'isPrivacyAgree:' + !callback.needAuthorization
})
})
})
onUnmounted(() => {
uni.offPrivacyAuthorizationChange(number)
})
</script>
<style>
.dialog-container {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
}
.dialog-content {
height: 60%;
width: 80%;
/* background-color: #fff; */
border-radius: 12px;
}
.button {
border-radius: 0px;
border: 0px none;
font-size: 15px;
color: lightgrey;
text-align: center;
}
.button-hover1 {
color: black;
background-color: #fff;
}
.button-hover2 {
color: black;
background-color: royalblue;
}
.privacy-text {
color: grey;
}
.privacy-href {
color: cadetblue;
font-weight: bold;
text-decoration-line: underline;
}
</style>