feat(P4): precompute移植+app.js sources双向回填+回归1003/1003+验收体积/文件/data.json
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Aurora Admin · Popconfirm (H5)</title>
|
||||
<link rel="stylesheet" href="../.design_library/aurora-admin/colors_and_type.css">
|
||||
<link rel="stylesheet" href="./Popconfirm.css">
|
||||
<style>
|
||||
.demo { padding: 24px; background: var(--au-color-page-bg); font-family: var(--font-family); }
|
||||
h1 { font-size: 20px; margin: 0 0 4px; color: var(--au-color-text-title); }
|
||||
.sub { color: var(--au-color-text-secondary); margin: 0 0 24px; font-size: 12px; }
|
||||
.demo-block { background: var(--au-color-card-bg); border-radius: 8px; padding: 32px; margin-bottom: 20px; }
|
||||
.demo-block > .hint { color: var(--au-color-text-secondary); font-size: 12px; margin: 0 0 16px; }
|
||||
.aa-btn { height: 32px; padding: 0 16px; border-radius: 4px; font-size: 14px; cursor: pointer; border: 1px solid var(--au-color-border); background: var(--au-color-card-bg); color: var(--au-color-text-body); }
|
||||
.aa-btn.danger { border-color: var(--au-color-error); color: var(--au-color-error); }
|
||||
.aa-btn.danger:hover { background: var(--au-color-error); color: var(--au-color-text-inverse); }
|
||||
.log { margin-top: 16px; font-size: 13px; color: var(--au-color-text-secondary); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="demo">
|
||||
<h1>Aurora Admin · Popconfirm 组件(纯 H5)</h1>
|
||||
<p class="sub">对齐 Popconfirm 规范:气泡确认、图标+描述+确认/取消,点击外部关闭</p>
|
||||
|
||||
<div class="demo-block">
|
||||
<p class="hint">删除确认(底部弹出)</p>
|
||||
<span class="aa-popconfirm" data-popconfirm>
|
||||
<button class="aa-btn danger">删除</button>
|
||||
<div class="aa-popconfirm-pop placement-bottom" hidden>
|
||||
<div class="aa-popconfirm-inner">
|
||||
<span class="aa-popconfirm-icon">!</span>
|
||||
<span class="aa-popconfirm-title">确定要删除该条记录吗?此操作不可撤销。</span>
|
||||
</div>
|
||||
<div class="aa-popconfirm-actions">
|
||||
<button class="aa-popconfirm-btn" data-act="cancel">取消</button>
|
||||
<button class="aa-popconfirm-btn primary" data-act="ok">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
<div class="log" id="log">结果:—</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-block">
|
||||
<p class="hint">顶部弹出</p>
|
||||
<span class="aa-popconfirm" data-popconfirm>
|
||||
<button class="aa-btn">提交</button>
|
||||
<div class="aa-popconfirm-pop placement-top" hidden>
|
||||
<div class="aa-popconfirm-inner">
|
||||
<span class="aa-popconfirm-icon">!</span>
|
||||
<span class="aa-popconfirm-title">确定要提交审批吗?</span>
|
||||
</div>
|
||||
<div class="aa-popconfirm-actions">
|
||||
<button class="aa-popconfirm-btn" data-act="cancel">取消</button>
|
||||
<button class="aa-popconfirm-btn primary" data-act="ok">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll('[data-popconfirm]').forEach(root => {
|
||||
const trigger = root.querySelector('button');
|
||||
const pop = root.querySelector('.aa-popconfirm-pop');
|
||||
|
||||
trigger.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const open = pop.hidden;
|
||||
closeAll();
|
||||
pop.hidden = !open;
|
||||
});
|
||||
|
||||
pop.querySelector('[data-act="cancel"]').addEventListener('click', (e) => {
|
||||
e.stopPropagation(); pop.hidden = true;
|
||||
});
|
||||
pop.querySelector('[data-act="ok"]').addEventListener('click', (e) => {
|
||||
e.stopPropagation(); pop.hidden = true;
|
||||
const log = document.getElementById('log');
|
||||
if (log) log.textContent = '结果:已确认(ok)';
|
||||
});
|
||||
|
||||
// 点击气泡本身不关闭
|
||||
pop.addEventListener('click', (e) => e.stopPropagation());
|
||||
});
|
||||
|
||||
function closeAll() {
|
||||
document.querySelectorAll('.aa-popconfirm-pop').forEach(p => p.hidden = true);
|
||||
}
|
||||
document.addEventListener('click', closeAll);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,82 @@
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import './Popconfirm.css';
|
||||
|
||||
/**
|
||||
* Aurora Admin Popconfirm(React)
|
||||
* 对齐 组件2.txt Popconfirm 规范:气泡确认、图标+描述+确认/取消,点击外部关闭
|
||||
*
|
||||
* props:
|
||||
* - title : 确认描述文本
|
||||
* - okText / cancelText
|
||||
* - placement : 'bottom' | 'top'
|
||||
* - visible / defaultVisible : 受控打开
|
||||
* - onConfirm() / onCancel()
|
||||
* - children : 触发元素(如按钮)
|
||||
*/
|
||||
export default function Popconfirm({
|
||||
title,
|
||||
okText = '确定',
|
||||
cancelText = '取消',
|
||||
placement = 'bottom',
|
||||
visible,
|
||||
defaultVisible = false,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
children
|
||||
}) {
|
||||
const rootRef = useRef(null);
|
||||
const [innerOpen, setInnerOpen] = useState(defaultVisible);
|
||||
const open = visible !== undefined ? visible : innerOpen;
|
||||
const [style, setStyle] = useState({});
|
||||
|
||||
const syncPos = () => {
|
||||
if (!rootRef.current) return;
|
||||
const r = rootRef.current.getBoundingClientRect();
|
||||
setStyle(placement === 'top'
|
||||
? { left: r.left, bottom: window.innerHeight - r.top }
|
||||
: { left: r.left, top: r.bottom + 8 });
|
||||
};
|
||||
const setOpen = (v) => {
|
||||
if (visible === undefined) setInnerOpen(v);
|
||||
if (v) { syncPos(); document.addEventListener('click', onDocClick, true); window.addEventListener('resize', syncPos); }
|
||||
else { document.removeEventListener('click', onDocClick, true); window.removeEventListener('resize', syncPos); }
|
||||
};
|
||||
const onDocClick = (e) => {
|
||||
if (rootRef.current && rootRef.current.contains(e.target)) return;
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open) { syncPos(); document.addEventListener('click', onDocClick, true); window.addEventListener('resize', syncPos); }
|
||||
return () => { document.removeEventListener('click', onDocClick, true); window.removeEventListener('resize', syncPos); };
|
||||
}, [open]);
|
||||
|
||||
const confirm = () => { onConfirm && onConfirm(); setOpen(false); };
|
||||
const cancel = () => { onCancel && onCancel(); setOpen(false); };
|
||||
|
||||
return (
|
||||
<span className="aa-popconfirm" ref={rootRef}>
|
||||
<span onClick={() => setOpen(!open)}>{children}</span>
|
||||
{open && (
|
||||
<div className={`aa-popconfirm-pop placement-${placement}`} style={style}>
|
||||
<div className="aa-popconfirm-inner">
|
||||
<span className="aa-popconfirm-icon">!</span>
|
||||
<span className="aa-popconfirm-title">{title}</span>
|
||||
</div>
|
||||
<div className="aa-popconfirm-actions">
|
||||
<button className="aa-popconfirm-btn" onClick={cancel}>{cancelText}</button>
|
||||
<button className="aa-popconfirm-btn primary" onClick={confirm}>{okText}</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
用法示例:
|
||||
import Popconfirm from './Popconfirm';
|
||||
<Popconfirm title="确定要删除吗?" onConfirm={handleDelete}>
|
||||
<button>删除</button>
|
||||
</Popconfirm>
|
||||
*/
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<span class="aa-popconfirm" ref="root">
|
||||
<span @click="toggle">
|
||||
<slot />
|
||||
</span>
|
||||
<div
|
||||
v-if="open"
|
||||
class="aa-popconfirm-pop"
|
||||
:class="'placement-' + placement"
|
||||
>
|
||||
<div class="aa-popconfirm-inner">
|
||||
<span class="aa-popconfirm-icon">!</span>
|
||||
<span class="aa-popconfirm-title">{{ title }}</span>
|
||||
</div>
|
||||
<div class="aa-popconfirm-actions">
|
||||
<button class="aa-popconfirm-btn" @click="cancel">{{ cancelText }}</button>
|
||||
<button class="aa-popconfirm-btn primary" @click="confirm">{{ okText }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AaPopconfirm',
|
||||
props: {
|
||||
value: { type: Boolean, default: undefined }, // 受控打开
|
||||
title: { type: String, required: true },
|
||||
okText: { type: String, default: '确定' },
|
||||
cancelText: { type: String, default: '取消' },
|
||||
placement: { type: String, default: 'bottom' } // bottom | top
|
||||
},
|
||||
data() { return { innerOpen: false }; },
|
||||
computed: {
|
||||
open() { return this.value !== undefined ? this.value : this.innerOpen; }
|
||||
},
|
||||
watch: {
|
||||
value(v) { this.bindOutside(v); }
|
||||
},
|
||||
methods: {
|
||||
setOpen(v) {
|
||||
if (this.value !== undefined) this.$emit('input', v);
|
||||
else this.innerOpen = v;
|
||||
this.bindOutside(v);
|
||||
},
|
||||
bindOutside(v) {
|
||||
if (v) document.addEventListener('click', this.onDocClick, true);
|
||||
else document.removeEventListener('click', this.onDocClick, true);
|
||||
},
|
||||
toggle() { this.setOpen(!this.open); },
|
||||
confirm() { this.$emit('confirm'); this.setOpen(false); },
|
||||
cancel() { this.$emit('cancel'); this.setOpen(false); },
|
||||
onDocClick(e) {
|
||||
if (this.$refs.root && this.$refs.root.contains(e.target)) return;
|
||||
this.setOpen(false);
|
||||
}
|
||||
},
|
||||
beforeDestroy() { document.removeEventListener('click', this.onDocClick, true); }
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- 样式对齐 组件2.txt Popconfirm 规范 -->
|
||||
<style>
|
||||
.aa-popconfirm { position: relative; display: inline-block; }
|
||||
.aa-popconfirm-pop { position: absolute; z-index: 100; min-width: 220px; background: #fff; border: 1px solid #E8ECF1;
|
||||
border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.12); padding: 12px 14px; }
|
||||
.aa-popconfirm-pop.placement-bottom { top: calc(100% + 8px); left: 0; }
|
||||
.aa-popconfirm-pop.placement-top { bottom: calc(100% + 8px); left: 0; }
|
||||
.aa-popconfirm-inner { display: flex; gap: 8px; }
|
||||
.aa-popconfirm-icon { flex-shrink: 0; width: 16px; height: 16px; border-radius: 50%; background: #8C5A00; color: #fff;
|
||||
display: inline-flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; margin-top: 2px; }
|
||||
.aa-popconfirm-title { font-size: 14px; color: #262626; line-height: 1.5; }
|
||||
.aa-popconfirm-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; }
|
||||
.aa-popconfirm-btn { height: 28px; padding: 0 12px; border-radius: 4px; font-size: 13px; cursor: pointer;
|
||||
border: 1px solid #E8ECF1; background: #fff; color: #262626; transition: border-color .15s, color .15s; }
|
||||
.aa-popconfirm-btn:hover { color: #2F54EB; border-color: #2F54EB; }
|
||||
.aa-popconfirm-btn.primary { background: #2F54EB; border-color: #2F54EB; color: #fff; }
|
||||
.aa-popconfirm-btn.primary:hover { background: #1D39C4; border-color: #1D39C4; color: #fff; }
|
||||
</style>
|
||||
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<span class="aa-popconfirm" ref="root">
|
||||
<span @click="toggle">
|
||||
<slot />
|
||||
</span>
|
||||
<teleport to="body">
|
||||
<div
|
||||
v-if="open"
|
||||
class="aa-popconfirm-pop"
|
||||
:class="'placement-' + placement"
|
||||
:style="popStyle"
|
||||
>
|
||||
<div class="aa-popconfirm-inner">
|
||||
<span class="aa-popconfirm-icon">!</span>
|
||||
<span class="aa-popconfirm-title">{{ title }}</span>
|
||||
</div>
|
||||
<div class="aa-popconfirm-actions">
|
||||
<button class="aa-popconfirm-btn" @click="cancel">{{ cancelText }}</button>
|
||||
<button class="aa-popconfirm-btn primary" @click="confirm">{{ okText }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, onBeforeUnmount } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Boolean, default: undefined }, // 受控打开
|
||||
title: { type: String, required: true },
|
||||
okText: { type: String, default: '确定' },
|
||||
cancelText: { type: String, default: '取消' },
|
||||
placement: { type: String, default: 'bottom' } // bottom | top
|
||||
});
|
||||
const emit = defineEmits(['update:modelValue', 'confirm', 'cancel']);
|
||||
|
||||
const root = ref(null);
|
||||
const innerOpen = ref(false);
|
||||
const open = computed(() => props.modelValue !== undefined ? props.modelValue : innerOpen.value);
|
||||
|
||||
const popStyle = ref({});
|
||||
function syncPosition() {
|
||||
if (!root.value || !open.value) return;
|
||||
const r = root.value.getBoundingClientRect();
|
||||
popStyle.value = props.placement === 'top'
|
||||
? { left: r.left + 'px', bottom: (window.innerHeight - r.top) + 'px' }
|
||||
: { left: r.left + 'px', top: r.bottom + 8 + 'px' };
|
||||
}
|
||||
|
||||
function setOpen(v) {
|
||||
if (props.modelValue !== undefined) emit('update:modelValue', v);
|
||||
else innerOpen.value = v;
|
||||
if (v) { syncPosition(); document.addEventListener('click', onDocClick, true); window.addEventListener('resize', syncPosition); }
|
||||
else { document.removeEventListener('click', onDocClick, true); window.removeEventListener('resize', syncPosition); }
|
||||
}
|
||||
function toggle() { setOpen(!open.value); }
|
||||
function confirm() { emit('confirm'); setOpen(false); }
|
||||
function cancel() { emit('cancel'); setOpen(false); }
|
||||
function onDocClick(e) {
|
||||
if (root.value && root.value.contains(e.target)) return;
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, (v) => {
|
||||
if (v) { syncPosition(); document.addEventListener('click', onDocClick, true); window.addEventListener('resize', syncPosition); }
|
||||
else { document.removeEventListener('click', onDocClick, true); window.removeEventListener('resize', syncPosition); }
|
||||
});
|
||||
onBeforeUnmount(() => { document.removeEventListener('click', onDocClick, true); window.removeEventListener('resize', syncPosition); });
|
||||
</script>
|
||||
|
||||
<!-- 样式对齐 组件2.txt Popconfirm 规范 -->
|
||||
<style scoped>
|
||||
.aa-popconfirm { position: relative; display: inline-block; }
|
||||
.aa-popconfirm-pop { position: fixed; z-index: 100; min-width: 220px; background: #fff; border: 1px solid #E8ECF1;
|
||||
border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.12); padding: 12px 14px; }
|
||||
.aa-popconfirm-inner { display: flex; gap: 8px; }
|
||||
.aa-popconfirm-icon { flex-shrink: 0; width: 16px; height: 16px; border-radius: 50%; background: #8C5A00; color: #fff;
|
||||
display: inline-flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; margin-top: 2px; }
|
||||
.aa-popconfirm-title { font-size: 14px; color: #262626; line-height: 1.5; }
|
||||
.aa-popconfirm-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; }
|
||||
.aa-popconfirm-btn { height: 28px; padding: 0 12px; border-radius: 4px; font-size: 13px; cursor: pointer;
|
||||
border: 1px solid #E8ECF1; background: #fff; color: #262626; transition: border-color .15s, color .15s; }
|
||||
.aa-popconfirm-btn:hover { color: #2F54EB; border-color: #2F54EB; }
|
||||
.aa-popconfirm-btn.primary { background: #2F54EB; border-color: #2F54EB; color: #fff; }
|
||||
.aa-popconfirm-btn.primary:hover { background: #1D39C4; border-color: #1D39C4; color: #fff; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user