未命名 4XuQ0zedit icon

创建者:
用户VaAPqVEZ
Fork(复制)
下载
嵌入
BUG反馈
index.html
md
README.md
index.html
            
            <!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>订单录入与审核系统</title>
    <!-- Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- React & ReactDOM -->
    <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <!-- Babel for JSX -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <!-- Lucide Icons -->
    <script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
    <script src="https://unpkg.com/lucide-react@latest/dist/umd/lucide-react.js"></script>
    <!-- Framer Motion -->
    <script src="https://unpkg.com/[email protected]/dist/framer-motion.js"></script>
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
        body {
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
        }
        /* Custom scrollbar */
        ::-webkit-scrollbar {
            width: 8px;
            height: 8px;
        }
        ::-webkit-scrollbar-track {
            background: #f1f1f1;
        }
        ::-webkit-scrollbar-thumb {
            background: #ccc;
            border-radius: 4px;
        }
        ::-webkit-scrollbar-thumb:hover {
            background: #bbb;
        }
    </style>
</head>
<body class="bg-[#F3F4F6] text-[#1F2937]">
    <div id="root"></div>

    <script type="text/babel">
        const { useState, useEffect, useMemo } = React;
        
        // Robust library access for single-file HTML environment
        const getMotion = () => {
            const m = (window.Motion && window.Motion.motion) || (window.framerMotion && window.framerMotion.motion);
            if (m) return m;
            // Fallback Proxy to prevent "invalid type" errors if library fails to load
            return new Proxy(({children}) => <div>{children}</div>, {
                get: (target, prop) => {
                    if (typeof prop !== 'string') return target[prop];
                    return ({children, ...props}) => {
                        // Filter out motion-specific props for fallback
                        const { initial, animate, exit, transition, ...cleanProps } = props;
                        return React.createElement(prop, cleanProps, children);
                    };
                }
            });
        };

        const getAnimatePresence = () => {
            return (window.Motion && window.Motion.AnimatePresence) || (window.framerMotion && window.framerMotion.AnimatePresence) || (({children}) => <>{children}</>);
        };

        const getIcons = () => {
            const icons = window.LucideReact || {};
            return new Proxy(icons, {
                get: (target, prop) => {
                    return target[prop] || (() => <span className="w-4 h-4 inline-block" />);
                }
            });
        };

        const motion = getMotion();
        const AnimatePresence = getAnimatePresence();
        const icons = getIcons();

        const { 
            Pill, 
            Activity, 
            CheckCircle2, 
            XCircle, 
            Clock, 
            ArrowLeft,
            Plus, 
            Trash2, 
            ChevronDown, 
            ChevronUp 
        } = icons;

        // --- Constants & Master Data ---
        const MEDICINE_DATABASE = [
            {
                name: '赛增®重组人生长激素注射液',
                type: '水剂',
                specifications: ['GH15IU/瓶', 'GH15IU (非重组) /瓶', 'GH2IU/瓶', 'GH30IU/瓶', 'GH30IU (非重组) /瓶']
            },
            {
                name: '赛增®注射用人生长激素',
                type: '粉剂',
                specifications: ['GH10IU/瓶', 'GH12IU/瓶', 'GH2.5IU/瓶', 'GH4.5IU/瓶', 'GH4IU/瓶']
            },
            {
                name: '金赛增®金培生长激素注射液',
                type: '长效',
                specifications: [
                    'GH27IU 2支装 西林瓶/瓶',
                    'GH54IU/瓶',
                    'GH54IU 卡式瓶 基金会/瓶',
                    'GH54IU 卡式瓶新/瓶',
                    'GH54IU (卡式瓶) 基金会/瓶',
                    'GH54IU (卡式瓶) /瓶'
                ]
            }
        ];

        const createEmptyMedicineItem = () => ({
            name: '',
            type: '',
            specification: '',
            quantity: '',
            price: '',
        });

        const createEmptyInvoice = () => ({
            id: Math.random().toString(36).substr(2, 9),
            medicineModule: {
                items: [createEmptyMedicineItem()],
                subtotal: '',
                medicalInsuranceReimbursement: '',
                thirdPartyReimbursement: '',
            },
            testingInfo: {
                amount: '',
                medicalInsuranceReimbursement: '',
                thirdPartyReimbursement: '',
            },
            outOfPocketAmount: '',
            invoiceNumber: '',
            invoiceDate: '',
            invoiceAmount: '',
        });

        const MOCK_ORDER = {
            id: 'ORD-20240407-001',
            status: 'pending',
            invoices: [createEmptyInvoice()],
            auditComment: ''
        };

        // --- Components ---

        function InputField({ label, value, onChange, placeholder, readOnly }) {
            return (
                <div className="space-y-1.5">
                    {label && <label className="text-xs font-bold text-gray-500 uppercase tracking-wider ml-1">{label}</label>}
                    <input 
                        type="text"
                        value={value}
                        readOnly={readOnly}
                        onChange={(e) => onChange(e.target.value)}
                        placeholder={placeholder || (label ? `请输入${label}` : '')}
                        className={`w-full px-4 py-2.5 rounded-lg border border-gray-200 transition-all outline-none text-sm font-medium ${readOnly ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-white focus:ring-2 focus:ring-blue-500 focus:border-transparent'}`}
                    />
                </div>
            );
        }

        function SelectField({ label, value, options, onChange }) {
            return (
                <div className="space-y-1.5">
                    {label && <label className="text-xs font-bold text-gray-500 uppercase tracking-wider ml-1">{label}</label>}
                    <div className="relative">
                        <select 
                            value={value}
                            onChange={(e) => onChange(e.target.value)}
                            className="w-full px-4 py-2.5 rounded-lg border border-gray-200 bg-white focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all outline-none text-sm font-medium appearance-none cursor-pointer"
                        >
                            <option value="">{label ? `请选择${label}` : '请选择'}</option>
                            {options.map((opt, i) => (
                                <option key={i} value={opt}>{opt}</option>
                            ))}
                        </select>
                        <ChevronDown className="w-4 h-4 text-gray-400 absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none" />
                    </div>
                </div>
            );
        }

        // --- Main App ---

        function App() {
            const [order, setOrder] = useState(MOCK_ORDER);
            const [isSubmitting, setIsSubmitting] = useState(false);
            const [auditComment, setAuditComment] = useState('');
            const [expandedInvoices, setExpandedInvoices] = useState([MOCK_ORDER.invoices[0].id]);

            const toggleInvoice = (id) => {
                setExpandedInvoices(prev => 
                    prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id]
                );
            };

            const handleAudit = async (status) => {
                setIsSubmitting(true);
                await new Promise(resolve => setTimeout(resolve, 1000));
                setOrder(prev => ({ ...prev, status, auditComment }));
                setIsSubmitting(false);
            };

            const addInvoice = () => {
                const newInvoice = createEmptyInvoice();
                setOrder(prev => ({
                    ...prev,
                    invoices: [...prev.invoices, newInvoice]
                }));
                setExpandedInvoices(prev => [...prev, newInvoice.id]);
            };

            const removeInvoice = (id) => {
                setOrder(prev => ({
                    ...prev,
                    invoices: prev.invoices.filter(inv => inv.id !== id)
                }));
            };

            const addMedicineItem = (invoiceId) => {
                setOrder(prev => ({
                    ...prev,
                    invoices: prev.invoices.map(inv => 
                        inv.id === invoiceId 
                            ? { 
                                    ...inv, 
                                    medicineModule: { 
                                        ...inv.medicineModule, 
                                        items: [...inv.medicineModule.items, createEmptyMedicineItem()] 
                                    } 
                                }
                            : inv
                    )
                }));
            };

            const removeMedicineItem = (invoiceId, itemIdx) => {
                setOrder(prev => ({
                    ...prev,
                    invoices: prev.invoices.map(inv => 
                        inv.id === invoiceId 
                            ? { 
                                    ...inv, 
                                    medicineModule: { 
                                        ...inv.medicineModule, 
                                        items: inv.medicineModule.items.filter((_, i) => i !== itemIdx) 
                                    } 
                                }
                            : inv
                    )
                }));
            };

            const updateMedicineItem = (invoiceId, itemIdx, field, value) => {
                setOrder(prev => ({
                    ...prev,
                    invoices: prev.invoices.map(inv => 
                        inv.id === invoiceId 
                            ? {
                                    ...inv,
                                    medicineModule: {
                                        ...inv.medicineModule,
                                        items: inv.medicineModule.items.map((item, i) => {
                                            if (i !== itemIdx) return item;
                                            
                                            const updatedItem = { ...item, [field]: value };
                                            
                                            if (field === 'name') {
                                                const masterData = MEDICINE_DATABASE.find(m => m.name === value);
                                                updatedItem.type = masterData ? masterData.type : '';
                                                updatedItem.specification = '';
                                            }
                                            
                                            return updatedItem;
                                        })
                                    }
                                }
                            : inv
                    )
                }));
            };

            const updateMedicineSummary = (invoiceId, field, value) => {
                setOrder(prev => ({
                    ...prev,
                    invoices: prev.invoices.map(inv => 
                        inv.id === invoiceId 
                            ? {
                                    ...inv,
                                    medicineModule: {
                                        ...inv.medicineModule,
                                        [field]: value
                                    }
                                }
                            : inv
                    )
                }));
            };

            const updateTesting = (invoiceId, field, value) => {
                setOrder(prev => ({
                    ...prev,
                    invoices: prev.invoices.map(inv => 
                        inv.id === invoiceId 
                            ? { ...inv, testingInfo: { ...inv.testingInfo, [field]: value } }
                            : inv
                    )
                }));
            };

            const updateInvoiceMeta = (invoiceId, field, value) => {
                setOrder(prev => ({
                    ...prev,
                    invoices: prev.invoices.map(inv => 
                        inv.id === invoiceId ? { ...inv, [field]: value } : inv
                    )
                }));
            };

            const getStatusColor = (status) => {
                switch (status) {
                    case 'approved': return 'text-emerald-600 bg-emerald-50 border-emerald-100';
                    case 'rejected': return 'text-rose-600 bg-rose-50 border-rose-100';
                    default: return 'text-amber-600 bg-amber-50 border-amber-100';
                }
            };

            return (
                <div className="min-h-screen bg-[#F3F4F6] text-[#1F2937] pb-32">
                    {/* Header */}
                    <header className="sticky top-0 z-50 bg-white border-b border-gray-200 px-6 py-4 shadow-sm">
                        <div className="max-w-6xl mx-auto flex items-center justify-between">
                            <div className="flex items-center gap-4">
                                <button className="p-2 hover:bg-gray-100 rounded-full transition-colors">
                                    <ArrowLeft className="w-5 h-5 text-gray-500" />
                                </button>
                                <h1 className="text-xl font-bold tracking-tight">订单录入与审核</h1>
                            </div>

                        </div>
                    </header>

                    <main className="max-w-6xl mx-auto px-6 py-8 space-y-8">
                        <AnimatePresence mode="popLayout">
                            {order.invoices.map((invoice, invIdx) => (
                                <motion.div 
                                    key={invoice.id}
                                    initial={{ opacity: 0, scale: 0.98 }}
                                    animate={{ opacity: 1, scale: 1 }}
                                    exit={{ opacity: 0, scale: 0.98 }}
                                    className="bg-white rounded-2xl border border-gray-200 shadow-sm overflow-hidden"
                                >
                                    {/* Invoice Header */}
                                    <div 
                                        className="px-6 py-4 bg-gray-50 border-b border-gray-200 flex items-center justify-between cursor-pointer hover:bg-gray-100 transition-colors"
                                        onClick={() => toggleInvoice(invoice.id)}
                                    >
                                        <div className="flex items-center gap-3">
                                            <div className="w-8 h-8 rounded-lg bg-blue-600 flex items-center justify-center text-white font-bold">
                                                {invIdx + 1}
                                            </div>
                                            <h2 className="font-bold text-gray-700">发票信息 #{invIdx + 1}</h2>
                                            {invoice.invoiceNumber && (
                                                <span className="text-sm text-gray-500 font-mono ml-2">[{invoice.invoiceNumber}]</span>
                                            )}
                                        </div>
                                        <div className="flex items-center gap-4">
                                            {order.invoices.length > 1 && (
                                                <button 
                                                    onClick={(e) => { e.stopPropagation(); removeInvoice(invoice.id); }}
                                                    className="p-2 text-gray-400 hover:text-rose-600 transition-colors"
                                                >
                                                    <Trash2 className="w-4 h-4" />
                                                </button>
                                            )}
                                            {expandedInvoices.includes(invoice.id) ? <ChevronUp className="w-5 h-5" /> : <ChevronDown className="w-5 h-5" />}
                                        </div>
                                    </div>

                                    {expandedInvoices.includes(invoice.id) && (
                                        <div className="p-6 space-y-10">
                                            {/* Medicine Module */}
                                            <div className="space-y-6">
                                                <div className="flex items-center justify-between border-b border-gray-100 pb-2">
                                                    <div className="flex items-center gap-2">
                                                        <Pill className="w-5 h-5 text-emerald-600" />
                                                        <h3 className="font-bold text-lg">购药信息</h3>
                                                    </div>
                                                    <button 
                                                        onClick={() => addMedicineItem(invoice.id)}
                                                        className="flex items-center gap-1.5 text-sm font-bold text-emerald-600 hover:text-emerald-700 transition-colors bg-emerald-50 px-3 py-1.5 rounded-lg border border-emerald-100"
                                                    >
                                                        <Plus className="w-4 h-4" />
                                                        新增药品
                                                    </button>
                                                </div>

                                                {/* Medicine Items List */}
                                                <div className="overflow-x-auto rounded-xl border border-gray-100">
                                                    <table className="w-full text-left border-collapse">
                                                        <thead className="bg-gray-50/80 text-xs font-bold text-gray-400 uppercase tracking-widest">
                                                            <tr>
                                                                <th className="px-4 py-3 border-b border-gray-100">药品名称</th>
                                                                <th className="px-4 py-3 border-b border-gray-100">药品类型</th>
                                                                <th className="px-4 py-3 border-b border-gray-100">药品规格</th>
                                                                <th className="px-4 py-3 border-b border-gray-100 w-24">数量</th>
                                                                <th className="px-4 py-3 border-b border-gray-100 w-32">金额</th>
                                                                <th className="px-4 py-3 border-b border-gray-100 w-12"></th>
                                                            </tr>
                                                        </thead>
                                                        <tbody className="divide-y divide-gray-50">
                                                            {invoice.medicineModule.items.map((item, itemIdx) => (
                                                                <tr key={itemIdx} className="hover:bg-gray-50/30 transition-colors">
                                                                    <td className="px-3 py-3">
                                                                        <SelectField 
                                                                            label="" 
                                                                            value={item.name} 
                                                                            options={MEDICINE_DATABASE.map(m => m.name)}
                                                                            onChange={(v) => updateMedicineItem(invoice.id, itemIdx, 'name', v)} 
                                                                        />
                                                                    </td>
                                                                    <td className="px-3 py-3">
                                                                        <InputField 
                                                                            label="" 
                                                                            value={item.type} 
                                                                            readOnly
                                                                            placeholder="自动显示"
                                                                            onChange={() => {}} 
                                                                        />
                                                                    </td>
                                                                    <td className="px-3 py-3">
                                                                        <SelectField 
                                                                            label="" 
                                                                            value={item.specification} 
                                                                            options={MEDICINE_DATABASE.find(m => m.name === item.name)?.specifications || []}
                                                                            onChange={(v) => updateMedicineItem(invoice.id, itemIdx, 'specification', v)} 
                                                                        />
                                                                    </td>
                                                                    <td className="px-3 py-3">
                                                                        <InputField label="" value={item.quantity} onChange={(v) => updateMedicineItem(invoice.id, itemIdx, 'quantity', v)} />
                                                                    </td>
                                                                    <td className="px-3 py-3">
                                                                        <InputField label="" value={item.price} onChange={(v) => updateMedicineItem(invoice.id, itemIdx, 'price', v)} />
                                                                    </td>
                                                                    <td className="px-3 py-3 text-right">
                                                                        {invoice.medicineModule.items.length > 1 && (
                                                                            <button 
                                                                                onClick={() => removeMedicineItem(invoice.id, itemIdx)}
                                                                                className="p-1.5 text-gray-300 hover:text-rose-500 transition-colors"
                                                                            >
                                                                                <Trash2 className="w-4 h-4" />
                                                                            </button>
                                                                        )}
                                                                    </td>
                                                                </tr>
                                                            ))}
                                                        </tbody>
                                                    </table>
                                                </div>

                                                {/* Medicine Summary Fields */}
                                                <div className="bg-emerald-50/30 p-6 rounded-xl border border-emerald-100">
                                                    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
                                                        <InputField label="药品金额小计" value={invoice.medicineModule.subtotal} onChange={(v) => updateMedicineSummary(invoice.id, 'subtotal', v)} />
                                                        <InputField label="药品医保报销金额" value={invoice.medicineModule.medicalInsuranceReimbursement} onChange={(v) => updateMedicineSummary(invoice.id, 'medicalInsuranceReimbursement', v)} />
                                                        <InputField label="药品第三方报销金额" value={invoice.medicineModule.thirdPartyReimbursement} onChange={(v) => updateMedicineSummary(invoice.id, 'thirdPartyReimbursement', v)} />
                                                    </div>
                                                </div>
                                            </div>

                                            {/* Testing Info */}
                                            <div className="space-y-6">
                                                <div className="flex items-center gap-2 border-b border-gray-100 pb-2">
                                                    <Activity className="w-5 h-5 text-indigo-600" />
                                                    <h3 className="font-bold text-lg">检测信息</h3>
                                                </div>
                                                <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
                                                    <InputField label="检测金额" value={invoice.testingInfo.amount} onChange={(v) => updateTesting(invoice.id, 'amount', v)} />
                                                    <InputField label="检测医保报销金额" value={invoice.testingInfo.medicalInsuranceReimbursement} onChange={(v) => updateTesting(invoice.id, 'medicalInsuranceReimbursement', v)} />
                                                    <InputField label="检测第三方报销金额" value={invoice.testingInfo.thirdPartyReimbursement} onChange={(v) => updateTesting(invoice.id, 'thirdPartyReimbursement', v)} />
                                                </div>
                                            </div>

                                            {/* Invoice Meta */}
                                            <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 pt-6 border-t border-gray-100">
                                                <InputField label="自付自费金额" value={invoice.outOfPocketAmount} onChange={(v) => updateInvoiceMeta(invoice.id, 'outOfPocketAmount', v)} placeholder="请输入自付金额" />
                                                <InputField label="发票号" value={invoice.invoiceNumber} onChange={(v) => updateInvoiceMeta(invoice.id, 'invoiceNumber', v)} placeholder="请输入发票号码" />
                                                <InputField label="开票日期" value={invoice.invoiceDate} onChange={(v) => updateInvoiceMeta(invoice.id, 'invoiceDate', v)} placeholder="YYYY-MM-DD" />
                                                <InputField label="发票金额" value={invoice.invoiceAmount} onChange={(v) => updateInvoiceMeta(invoice.id, 'invoiceAmount', v)} placeholder="请输入总金额" />
                                            </div>
                                        </div>
                                    )}
                                </motion.div>
                            ))}
                        </AnimatePresence>

                        {/* Add Invoice Button */}
                        <button 
                            onClick={addInvoice}
                            className="w-full py-6 border-2 border-dashed border-gray-300 rounded-2xl text-gray-500 font-bold hover:border-blue-400 hover:text-blue-500 hover:bg-blue-50 transition-all flex items-center justify-center gap-2"
                        >
                            <Plus className="w-6 h-6" />
                            新增一张发票
                        </button>

                        {/* Audit Comment */}
                        <div className="bg-white rounded-2xl border border-gray-200 p-8 space-y-4 shadow-sm">
                            <h3 className="font-bold text-lg flex items-center gap-2">
                                <Clock className="w-5 h-5 text-blue-500" />
                                审核意见
                            </h3>
                            <textarea 
                                className="w-full min-h-[120px] p-4 rounded-xl border border-gray-200 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all outline-none resize-none bg-gray-50/50"
                                placeholder="请输入审核备注..."
                                value={auditComment}
                                onChange={(e) => setAuditComment(e.target.value)}
                            />
                        </div>
                    </main>

                    {/* Bottom Action Bar */}
           
                </div>
            );
        }

        const root = ReactDOM.createRoot(document.getElementById('root'));
        root.render(<App />);
    </script>
</body>
</html>

        
编辑器加载中
预览
控制台