Commit 2c8c26b2 by 过去只有如今

Initial commit

parents
node_modules/
.codegraph/
.codex/
.cursor/
.playwright-mcp/
.impeccable/
.agents/
.claude/
*.log
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>学有为资产管理台</title>
<link rel="stylesheet" href="node_modules/element-plus/dist/index.css" />
<link rel="stylesheet" href="styles-v2.css" />
</head>
<body>
<div class="workspace" id="appShell">
<aside class="shell-nav" id="sidebar">
<div class="brand-panel">
<div class="brand-mark">
<img class="brand-logo" src="img/学有为logo.jpg" alt="瀛︽湁涓?LOGO" />
</div>
<div class="brand-copy">
<h1>&#23398;&#26377;&#20026;&#25968;&#25454;&#36164;&#20135;&#21518;&#21488;</h1>
</div>
</div>
<div class="nav-main">
<nav class="primary-nav" aria-label="&#20027;&#23548;&#33322;">
<button class="nav-link" type="button" data-module="overview">
<span class="nav-glyph"><img class="nav-icon" src="img/账户总览.png" alt="鎬昏" /></span>
<span class="nav-label">&#24635;&#35272;</span>
</button>
<button class="nav-link" type="button" data-module="domain">
<span class="nav-glyph"><img class="nav-icon" src="img/域名申请.png" alt="鍩熷悕璧勬枡" /></span>
<span class="nav-label">&#22495;&#21517;&#36164;&#26009;</span>
</button>
<button class="nav-link" type="button" data-module="wechat">
<span class="nav-glyph"><img class="nav-icon" src="img/企微_企微.png" alt="浼佸井璧勬枡" /></span>
<span class="nav-label">&#20225;&#24494;&#36164;&#26009;</span>
</button>
<button class="nav-link" type="button" data-module="phone">
<span class="nav-glyph"><img class="nav-icon" src="img/电话号码 (1).png" alt="鎵嬫満鍙峰崱" /></span>
<span class="nav-label">&#25163;&#26426;&#21495;&#21345;</span>
</button>
<button class="nav-link" type="button" data-module="alerts">
<span class="nav-glyph"><img class="nav-icon" src="img/提醒中心.png" alt="鎻愰啋涓績" /></span>
<span class="nav-label">&#25552;&#37266;&#20013;&#24515;</span>
</button>
</nav>
</div>
<div class="nav-bottom">
<div class="nav-toggle-wrap">
<button class="nav-toggle-button" id="sidebarToggle" type="button"
aria-label="&#25910;&#36215;&#20391;&#36793;&#23548;&#33322;" aria-expanded="true">
<span class="nav-toggle-glyph" aria-hidden="true">
<span></span>
<span></span>
</span>
<span class="nav-toggle-label">&#25910;&#36215;</span>
</button>
</div>
<div class="nav-footer">
<div class="nav-user-avatar">&#9679;</div>
<div class="nav-user-copy">
<p class="nav-user-name">&#31649;&#29702;&#21592;</p>
<p class="nav-user-role">&#36229;&#32423;&#31649;&#29702;&#21592;</p>
</div>
<button class="nav-settings" type="button" aria-label="&#35774;&#32622;">&#9881;</button>
</div>
</div>
</aside>
<main class="shell-main" id="vue-router-app">
<router-view></router-view>
</main>
</div>
<div id="addDomainDialogRoot"></div>
<div id="wechatDialogRoot"></div>
<div id="wechatDetailDrawerRoot"></div>
<div id="phoneDialogRoot"></div>
<div id="phoneDetailDrawerRoot"></div>
<script src="node_modules/vue/dist/vue.global.prod.js"></script>
<script src="node_modules/vue-router/dist/vue-router.global.prod.js"></script>
<script src="node_modules/element-plus/dist/index.full.min.js"></script>
<script src="src/modules/domain/domain-add-dialog.js"></script>
<script src="src/modules/domain/domain-list-runtime.js"></script>
<script src="src/modules/overview/overview-list-runtime.js"></script>
<script src="src/modules/wechat/wechat-add-dialog.js"></script>
<script src="src/modules/wechat/wechat-detail-drawer.js"></script>
<script src="src/modules/wechat/wechat-list-runtime.js"></script>
<script src="src/modules/phone/phone-add-dialog.js"></script>
<script src="src/modules/phone/phone-detail-drawer.js"></script>
<script src="src/modules/phone/phone-list-runtime.js"></script>
<script src="src/router/index.js"></script>
<script src="app-v2.js"></script>
</body>
</html>
{
"dependencies": {
"element-plus": "^2.14.2",
"jsdom": "^29.1.1",
"vue": "^3.5.39",
"vue-router": "^4.6.4"
}
}
(function initDomainAddDialog() {
const { createApp, ref, reactive, watch } = window.Vue;
const ElementPlus = window.ElementPlus;
const DomainAddDialog = {
/** 代码作用(白话):创建“新增域名”弹窗的表单状态、校验规则和提交行为;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/index.html;关联逻辑:调用链是“页面操作按钮 -> DomainAddDialogAPI.open() -> 弹窗显示 -> handleSubmit() 提交校验”。 */
setup() {
const visible = ref(false);
const formRef = ref(null);
const createDefaultFormData = () => ({
domain: '',
websiteName: '',
registrant: '',
accountId: '',
bindAccountId: '',
phone: '',
email: '',
password: '',
registerDate: '',
expirationDate: '',
autoRenewal: '0',
renewalStatus: 'pending',
sslInfo: '',
sslExpirationDate: ''
});
const formData = reactive(createDefaultFormData());
const rules = {
domain: [{ required: true, message: '请输入域名地址', trigger: 'blur' }]
};
watch(() => formData.autoRenewal, (newVal) => {
if (newVal === '1') {
formData.renewalStatus = 'success';
}
});
/** 代码作用(白话):打开弹窗并把表单重置到初始状态,避免上一次填写内容残留;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/index.html;关联逻辑:消息链是“页面按钮 -> DomainAddDialogAPI.open() -> visible=true -> 弹窗展示”。 */
const open = () => {
visible.value = true;
if (formRef.value) {
formRef.value.resetFields();
}
Object.assign(formData, createDefaultFormData());
};
/** 代码作用(白话):关闭当前域名新增弹窗;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/index.html;关联逻辑:消息链是“取消按钮/提交成功 -> close() -> visible=false”。 */
const close = () => {
visible.value = false;
};
/** 代码作用(白话):校验表单并给出保存成功提示,当前版本只做前端校验和反馈,不直接请求后端;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/index.html;关联逻辑:调用链是“提交按钮 -> handleSubmit() -> formRef.validate() -> ElMessage.success() -> close()”。 */
const handleSubmit = () => {
if (!formRef.value) return;
formRef.value.validate((valid) => {
if (!valid) return false;
ElementPlus.ElMessage.success('提交保存成功');
close();
return true;
});
};
window.DomainAddDialogAPI = { open };
return {
visible,
formRef,
formData,
rules,
close,
handleSubmit
};
},
template: `
<el-dialog v-model="visible" width="760px" top="8vh">
<template #header>
<div style="font-size: 18px; font-weight: bold; color: #303133;">新增域名记录</div>
</template>
<el-form :model="formData" :rules="rules" ref="formRef" label-width="110px" label-position="right">
<div style="font-size: 15px; font-weight: 600; color: #303133; margin: 0 0 16px 0; line-height: 1;">基础主体信息</div>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="域名地址" prop="domain">
<el-input v-model="formData.domain" placeholder="请输入域名地址,例如 example.com"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="网站名称/平台" prop="websiteName">
<el-input v-model="formData.websiteName" placeholder="请输入网站名称或所属平台"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="24">
<el-form-item label="注册主体" prop="registrant">
<el-input v-model="formData.registrant" placeholder="请输入注册主体,例如个人或企业全称"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-divider border-style="dashed" style="margin: 16px 0;"></el-divider>
<div style="font-size: 15px; font-weight: 600; color: #303133; margin: 0 0 16px 0; line-height: 1;">账户凭证管理</div>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="账号 ID" prop="accountId">
<el-input v-model="formData.accountId" placeholder="请输入托管平台账号 ID"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="绑定账号 ID" prop="bindAccountId">
<el-input v-model="formData.bindAccountId" placeholder="请输入内部绑定账号 ID"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="手机号" prop="phone">
<el-input v-model="formData.phone" placeholder="请输入绑定手机号"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="电子邮箱" prop="email">
<el-input v-model="formData.email" placeholder="请输入绑定邮箱"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="24">
<el-form-item label="登录密码" prop="password">
<el-input v-model="formData.password" placeholder="请输入平台登录密码" show-password></el-input>
</el-form-item>
</el-col>
</el-row>
<el-divider border-style="dashed" style="margin: 16px 0;"></el-divider>
<div style="font-size: 15px; font-weight: 600; color: #303133; margin: 0 0 16px 0; line-height: 1;">续费周期信息</div>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="注册日期" prop="registerDate">
<el-date-picker v-model="formData.registerDate" type="date" placeholder="请选择日期" style="width: 100%"></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="到期日期" prop="expirationDate">
<el-date-picker v-model="formData.expirationDate" type="date" placeholder="请选择日期" style="width: 100%"></el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="自动续费" prop="autoRenewal">
<el-radio-group v-model="formData.autoRenewal">
<el-radio label="0">未开启</el-radio>
<el-radio label="1">已开启</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="最新续费状态" prop="renewalStatus">
<el-select v-model="formData.renewalStatus" placeholder="请选择续费状态" style="width: 100%">
<el-option label="待续费" value="pending"></el-option>
<el-option label="续费成功" value="success"></el-option>
<el-option label="续费失败" value="failed"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-divider border-style="dashed" style="margin: 16px 0;"></el-divider>
<div style="font-size: 15px; font-weight: 600; color: #303133; margin: 0 0 16px 0; line-height: 1;">证书配置信息</div>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="SSL 证书信息" prop="sslInfo">
<el-input v-model="formData.sslInfo" placeholder="请输入证书类型或服务商,例如 Let's Encrypt"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="SSL 到期时间" prop="sslExpirationDate">
<el-date-picker v-model="formData.sslExpirationDate" type="date" placeholder="请选择日期" style="width: 100%"></el-date-picker>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="handleSubmit">提交保存</el-button>
</span>
</template>
</el-dialog>
`
};
const app = createApp(DomainAddDialog);
app.use(ElementPlus);
app.mount('#addDomainDialogRoot');
})();
(function initPhoneDetailDrawer() {
const { createApp, ref, reactive } = window.Vue;
const ElementPlus = window.ElementPlus;
const PhoneDetailDrawer = {
setup() {
const visible = ref(false);
const recordData = ref(null);
const open = (record) => {
if (!record) return;
recordData.value = { ...record };
visible.value = true;
};
const close = () => {
visible.value = false;
};
const handleEdit = () => {
if (window.PhoneAddDialogAPI && recordData.value) {
window.PhoneAddDialogAPI.open('edit', recordData.value);
}
};
// Expose to global scope so app-v2.js can call it
window.PhoneDetailDrawerAPI = {
open,
close
};
return {
visible,
recordData,
close,
handleEdit
};
},
template: `
<el-drawer
v-model="visible"
:with-header="false"
size="480px"
direction="rtl"
custom-class="wechat-detail-drawer-vue premium-drawer"
>
<div v-if="recordData" class="wechat-drawer-inner" style="height: 100%; display: flex; flex-direction: column; background: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;">
<!-- Hero Header -->
<div style="padding: 40px 32px 24px 32px; position: relative;">
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
<div style="display: flex; align-items: center; gap: 20px;">
<div style="width: 56px; height: 56px; border-radius: 14px; background: #f3f4f6; border: 1px solid #e5e7eb; display: flex; align-items: center; justify-content: center; font-size: 24px; font-weight: 600; color: #111827; box-shadow: 0 2px 4px rgba(0,0,0,0.02);">
📱
</div>
<div>
<h3 style="margin: 0 0 4px 0; font-size: 24px; font-weight: 700; color: #111827; letter-spacing: -0.5px; display: flex; align-items: center; gap: 10px;">
{{ recordData.phoneNumber || '未知号码' }}
<span :style="{
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
width: '8px', height: '8px', borderRadius: '50%',
background: (recordData.cardStatus === '异常停机' || recordData.numberStatus === false) ? '#ef4444' : '#22c55e'
}"></span>
</h3>
<div style="font-size: 14px; color: #6b7280; font-weight: 400; letter-spacing: 0.2px;">
{{ recordData.realPerson || '未登记实名' }} &nbsp;&middot;&nbsp; {{ recordData.channelOperator || '未知运营商' }}
</div>
</div>
</div>
<button @click="close" style="background: none; border: none; cursor: pointer; color: #9ca3af; padding: 4px; display: flex; align-items: center; justify-content: center; border-radius: 6px; transition: background 0.2s;" onmouseover="this.style.background='#f3f4f6'; this.style.color='#4b5563'" onmouseout="this.style.background='none'; this.style.color='#9ca3af'">
<svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
</div>
<!-- Body -->
<div style="flex: 1; overflow-y: auto; padding: 0 32px 40px 32px; display: flex; flex-direction: column; gap: 40px;">
<!-- Section 1: Basic Info Bento Grid -->
<section>
<h4 style="margin: 0 0 16px 0; font-size: 13px; font-weight: 600; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px;">基础属性</h4>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 24px 16px;">
<div style="display: flex; flex-direction: column; gap: 6px;">
<span style="font-size: 13px; color: #6b7280;">实名人</span>
<span style="font-size: 15px; font-weight: 500; color: #111827;">{{ recordData.realPerson || '—' }}</span>
</div>
<div style="display: flex; flex-direction: column; gap: 6px;">
<span style="font-size: 13px; color: #6b7280;">ICCID</span>
<span style="font-size: 15px; font-weight: 500; color: #111827; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;">{{ recordData.iccid || '—' }}</span>
</div>
<div style="display: flex; flex-direction: column; gap: 6px;">
<span style="font-size: 13px; color: #6b7280;">归属地</span>
<span style="font-size: 15px; font-weight: 500; color: #111827;">{{ recordData.city || '—' }}</span>
</div>
<div style="display: flex; flex-direction: column; gap: 6px;">
<span style="font-size: 13px; color: #6b7280;">使用地</span>
<span style="font-size: 15px; font-weight: 500; color: #111827;">{{ recordData.cardUsageLocation || '—' }}</span>
</div>
</div>
</section>
<!-- Section 2: 状态与生态 -->
<section>
<h4 style="margin: 0 0 16px 0; font-size: 13px; font-weight: 600; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px;">状态与生态</h4>
<div style="display: grid; grid-template-columns: 1fr; gap: 24px;">
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px;">
<div style="display: flex; flex-direction: column; gap: 6px;">
<span style="font-size: 13px; color: #6b7280;">外呼</span>
<span style="font-size: 15px; font-weight: 500; color: #111827;">{{ recordData.outboundCall ? '支持' : '不支持' }}</span>
</div>
<div style="display: flex; flex-direction: column; gap: 6px;">
<span style="font-size: 13px; color: #6b7280;">微信</span>
<span style="font-size: 15px; font-weight: 500; color: #111827;">{{ recordData.wechat ? '已开通' : '未开通' }}</span>
</div>
<div style="display: flex; flex-direction: column; gap: 6px;">
<span style="font-size: 13px; color: #6b7280;">企微</span>
<span style="font-size: 15px; font-weight: 500; color: #111827;">{{ recordData.wecom ? '已开通' : '未开通' }}</span>
</div>
</div>
<div style="display: flex; flex-direction: column; gap: 6px;">
<span style="font-size: 13px; color: #6b7280;">绑定企微号</span>
<span style="font-size: 15px; font-weight: 400; color: #374151;">{{ recordData.linkedWecom || '—' }}</span>
</div>
<div style="display: flex; flex-direction: column; gap: 6px;">
<span style="font-size: 13px; color: #6b7280;">抖音账号</span>
<span style="font-size: 15px; font-weight: 400; color: #374151;">{{ recordData.douyinAccount || '—' }}</span>
</div>
</div>
</section>
</div>
<!-- Footer -->
<div style="padding: 24px 32px; display: flex; gap: 16px; background: #ffffff; border-top: 1px solid #f3f4f6;">
<button @click="close" style="flex: 1; padding: 12px 0; background: #ffffff; border: 1px solid #e5e7eb; border-radius: 8px; font-size: 14px; font-weight: 500; color: #374151; cursor: pointer; transition: all 0.2s;" onmouseover="this.style.background='#f9fafb'" onmouseout="this.style.background='#ffffff'">
关闭面板
</button>
<button @click="handleEdit" style="flex: 1; padding: 12px 0; background: #111827; border: 1px solid #111827; border-radius: 8px; font-size: 14px; font-weight: 500; color: #ffffff; cursor: pointer; transition: all 0.2s; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);" onmouseover="this.style.transform='translateY(-1px)'; this.style.boxShadow='0 6px 8px -1px rgba(0,0,0,0.15)'" onmouseout="this.style.transform='none'; this.style.boxShadow='0 4px 6px -1px rgba(0,0,0,0.1)'">
编辑号卡
</button>
</div>
</div>
</el-drawer>
`
};
const app = createApp(PhoneDetailDrawer);
app.use(ElementPlus);
app.mount('#phoneDetailDrawerRoot');
})();
// Reusable wrapper template that provides the DOM structure required by app-v2.js
const LegacyShellTemplate = `
<div class="legacy-wrapper">
<div id="pageHeader"></div>
<div class="shell-body" id="pageBody">
<!-- Vue runtime roots for other modules will be moved or recreated here -->
<div id="overviewListRoot" style="display:none;"></div>
<div id="wechatListRoot" style="display:none;"></div>
<div id="phoneListRoot" style="display:none;"></div>
</div>
<div id="detailDrawer" class="detail-drawer hidden">
<div class="detail-drawer-head" id="detailShellTitle"></div>
<div class="detail-drawer-body" id="detailBody"></div>
<button class="drawer-close" id="drawerClose">脳</button>
</div>
</div>
`;
function createLegacyWrapper(moduleName) {
return {
template: LegacyShellTemplate,
/** 代码作用(白话):在当前路由页面挂载后,把旧版 app-v2 的状态和渲染入口接起来,这样 Vue Router 只是负责切页,真正的页面内容仍沿用原来的渲染逻辑;关联文件:F:/Project/xyw_console/app-v2.js、F:/Project/xyw_console/index.html;关联逻辑:调用链是“RouterView mounted -> updateDOMRefs() -> state.activeModule 切换 -> renderModule() -> updateActiveNav()”。 */
mounted() {
// Re-initialize the DOM refs because the DOM was just created by Vue.
if (window.updateDOMRefs) {
window.updateDOMRefs();
}
// Defensive initialization: if the legacy runtime has not exposed state yet, stop here instead of crashing the page.
if (!window.state) {
return;
}
// Force app-v2 state to update.
window.state.activeModule = moduleName;
// Let app-v2 render the module and sync nav highlight.
if (window.renderModule) {
window.renderModule();
}
if (window.updateActiveNav) {
window.updateActiveNav();
}
}
};
}
const OverviewView = createLegacyWrapper('overview');
const DomainView = createLegacyWrapper('domain');
const WechatView = createLegacyWrapper('wechat');
const PhoneView = createLegacyWrapper('phone');
const AlertsView = createLegacyWrapper('alerts');
const routes = [
{ path: '/', redirect: '/overview' },
{ path: '/overview', component: OverviewView },
{ path: '/domain', component: DomainView },
{ path: '/wechat', component: WechatView },
{ path: '/phone-card', component: PhoneView },
{ path: '/alerts', component: AlertsView }
];
const router = window.VueRouter.createRouter({
history: window.VueRouter.createWebHashHistory(),
routes
});
// Setup global Vue app that mounts the router
const app = window.Vue.createApp({});
app.use(router);
// We need to wait for DOMContentLoaded to mount the Vue app
document.addEventListener('DOMContentLoaded', () => {
// Let the sidebar buttons trigger router.push instead of the old vanilla js navigation
const sidebar = document.getElementById('sidebar');
if (sidebar) {
sidebar.addEventListener('click', (event) => {
const button = event.target.closest('.nav-link[data-module]');
if (!button) return;
const mod = button.dataset.module;
// Update UI active state manually or let updateActiveNav do it
// Map module to path
let path = '/' + mod;
if (mod === 'phone') path = '/phone-card';
router.push(path);
// We stop propagation so app-v2.js doesn't try to handle it
// (Wait, we can just remove the old listener from app-v2.js)
event.preventDefault();
event.stopPropagation();
}, true); // use capture to intercept
}
// Mount router to the shell-main
app.mount('#vue-router-app');
});
@echo off
:: Switch code page to UTF-8 to prevent garbled text
chcp 65001 >nul
set PORT=8000
set URL=http://localhost:%PORT%
:: ==========================================
:: Please set BACKEND_DIR to your Spring Boot project path
:: Example: set BACKEND_DIR=..\phone-card-backend
:: ==========================================
set BACKEND_DIR=backend
echo ===================================================
echo [Starting Project]
echo Frontend URL: %URL%
echo Backend URL: http://localhost:8080
echo ===================================================
:: Copy to clipboard
echo | set /p dummy="%URL%" | clip
echo [Success] URL copied to clipboard!
echo.
echo [1/2] Starting Spring Boot Backend...
if exist "%BACKEND_DIR%\pom.xml" (
start "Backend Service" cmd /k "cd /d %BACKEND_DIR% && mvn spring-boot:run"
echo Backend is starting in a new window...
) else (
echo [Warning] Backend directory '%BACKEND_DIR%' not found or no pom.xml inside!
echo Please edit start.bat and change BACKEND_DIR to your Spring Boot project.
)
:: Open in browser
start "" "%URL%#/phone-card"
echo.
echo [2/2] Starting Python HTTP server for Frontend...
echo (Press Ctrl+C or close this window to stop)
echo.
python -m http.server %PORT%
@echo off
set PORT=8000
echo ===================================================
echo [Stopping Project]
echo Looking for processes on port %PORT%...
echo ===================================================
for /f "tokens=5" %%a in ('netstat -aon ^| findstr "LISTENING" ^| findstr ":%PORT%"') do (
echo Found process PID: %%a, terminating...
taskkill /F /PID %%a
echo Server successfully stopped!
goto :end
)
echo No local server found running on port %PORT%.
:end
echo.
pause
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment