Commit 5fd0db23 by 李宁

1

1 parent a60c9959
Showing 92 changed files with 4678 additions and 2 deletions
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
**/node_modules/ **/node_modules/
**/dist/ **/dist/
**/package-lock.json **/package-lock.json
**/yarn.lock
**/dist.zip **/dist.zip
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>视频质检系统</title> <title>视频质检系统</title>
<script type="module" crossorigin src="./assets/index-B7mitYIg.js"></script> <script type="module" crossorigin src="./assets/index-BJJz899K.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-KGZYoaHH.css"> <link rel="stylesheet" crossorigin href="./assets/index-KGZYoaHH.css">
</head> </head>
<body> <body>
......
质检随销项目代码
需求文档地址:https://jfq5tn3wbn.feishu.cn/docx/Koyndgr10oTDOnxMwaYcHhpznih
\ No newline at end of file \ No newline at end of file
# 上门随销商机管理平台 (Vue版本)
这是基于Vue 2.x的上门随销商机管理平台,完全按照技术要求实现。
## 技术栈
- **框架**: Vue 2.x
- **UI组件库**: ElementUI
- **状态管理**: Vuex
- **路由**: Vue Router
- **构建工具**: Vue CLI
- **样式**: SCSS
## 功能模块
1. **登录页面** - 用户身份验证
2. **主布局** - 侧边栏导航和顶部栏
3. **商机管理** - 商机列表、筛选、新增、查看详情
4. **数据报表** - 统计数据展示
5. **网格查询** - 网格信息查询和详情
6. **系统管理** - 人员管理、商机标签管理、账号管理
## 运行项目
### 安装依赖
```bash
npm install
```
### 启动开发服务器
```bash
npm run serve
```
### 构建生产版本
```bash
npm run build
```
## 项目结构
```
src/
├── assets/ # 静态资源
│ └── styles/ # 样式文件
├── components/ # 公共组件
│ └── system/ # 系统管理组件
├── views/ # 页面组件
├── router/ # 路由配置
├── store/ # 状态管理
└── App.vue # 根组件
```
## 测试账号
- 网格管理员: grid001 / 123456
- 区县管理员: county001 / 123456
- 地市管理员: city001 / 123456
- 省级管理员: province001 / 123456
## 注意事项
1. 项目使用了ElementUI组件库,请确保已正确安装依赖
2. 样式使用SCSS编写,需要相应的loader支持
3. 路由使用history模式,部署时需要注意服务器配置
\ No newline at end of file \ No newline at end of file
// babel.config.js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
\ No newline at end of file \ No newline at end of file
{
"name": "shang-men-sui-xiao-business-opportunity-management",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.6.5",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vuex": "^3.4.0",
"element-ui": "^2.15.14",
"axios": "^1.6.0",
"date-fns": "^2.30.0",
"echarts": "^5.4.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
\ No newline at end of file \ No newline at end of file
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>上门随销商机管理平台</title>
</head>
<body>
<noscript>
<strong>We're sorry but 上门随销商机管理平台 doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
\ No newline at end of file \ No newline at end of file
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style lang="scss">
#app {
min-height: 100vh;
background-color: #f7f9fc;
}
</style>
\ No newline at end of file \ No newline at end of file
// Element UI 样式覆盖
.el-card {
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
.el-card__header {
padding: 16px 20px;
border-bottom: 1px solid #ebeef5;
}
.el-card__body {
padding: 20px;
}
}
// 按钮样式
.el-button {
border-radius: 4px;
&.el-button--small {
padding: 8px 12px;
}
&.el-button--medium {
padding: 10px 16px;
}
&.el-button--large {
padding: 12px 20px;
}
}
// 表格样式
.el-table {
border-radius: 8px;
overflow: hidden;
th {
background-color: #f5f7fa;
font-weight: 500;
}
.el-table__row {
&:hover {
background-color: #f5f7fa;
}
}
}
// 分页样式
.el-pagination {
padding: 10px 0;
.el-pager {
li {
border-radius: 4px;
}
}
}
// 对话框样式
.el-dialog {
border-radius: 8px;
.el-dialog__header {
padding: 20px 20px 10px;
border-bottom: 1px solid #ebeef5;
}
.el-dialog__body {
padding: 20px;
}
.el-dialog__footer {
padding: 10px 20px 20px;
border-top: 1px solid #ebeef5;
}
}
// 标签样式
.el-tag {
border-radius: 4px;
}
// 输入框样式
.el-input {
.el-input__inner {
border-radius: 4px;
}
}
.el-textarea {
.el-textarea__inner {
border-radius: 4px;
}
}
// 下拉菜单样式
.el-dropdown-menu {
border-radius: 8px;
padding: 5px 0;
.el-dropdown-menu__item {
padding: 8px 16px;
&:hover {
background-color: #f5f7fa;
}
}
}
// 面包屑样式
.el-breadcrumb {
.el-breadcrumb__item {
.el-breadcrumb__inner {
color: #606266;
&.is-link {
color: #303133;
font-weight: 500;
}
}
&:last-child {
.el-breadcrumb__inner {
color: #909399;
}
}
}
}
// 侧边栏菜单样式
.el-menu {
border: none;
.el-menu-item,
.el-submenu__title {
height: 56px;
line-height: 56px;
}
}
// 卡片样式
.dashboard-card {
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
.el-card__body {
padding: 20px;
}
}
// 响应式工具类
.hidden-sm-and-down {
@media (max-width: 768px) {
display: none !important;
}
}
.hidden-md-and-up {
@media (min-width: 769px) {
display: none !important;
}
}
// 通用工具类
.text-center {
text-align: center;
}
.text-right {
text-align: right;
}
.text-muted {
color: #909399;
}
.mt-20 {
margin-top: 20px;
}
.mb-20 {
margin-bottom: 20px;
}
.mr-10 {
margin-right: 10px;
}
.ml-10 {
margin-left: 10px;
}
\ No newline at end of file \ No newline at end of file
// 主样式文件
@import './variables.scss';
@import './element-ui.scss';
// 全局样式
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
font-family: "Source Han Sans CN", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
background-color: $background-color;
color: $text-primary;
}
// 侧边栏相关样式
.sidebar-expanded {
width: $sidebar-width;
transition: width 0.3s ease;
}
.sidebar-collapsed {
width: $sidebar-collapsed-width;
transition: width 0.3s ease;
}
// 卡片样式
.dashboard-card {
position: relative;
height: 116px;
.card-icon {
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
width: 88px;
height: 88px;
}
}
// 表格样式
.el-table {
.cell {
line-height: 24px;
}
}
// 分页样式
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
padding: 0 10px;
}
// 页面标题
.page-header {
h1 {
font-size: 24px;
font-weight: 600;
margin: 0;
color: $text-primary;
}
}
// 卡片标题
.card-header {
h3 {
font-size: 18px;
font-weight: 600;
margin: 0;
color: $text-primary;
}
}
// 响应式设计
@media (max-width: 768px) {
.hidden-lg-only {
display: none !important;
}
}
\ No newline at end of file \ No newline at end of file
// 颜色变量
$primary-color: #409EFF;
$success-color: #67C23A;
$warning-color: #E6A23C;
$danger-color: #F56C6C;
$info-color: #909399;
$primary-color-light: #ecf5ff;
$success-color-light: #f0f9eb;
$warning-color-light: #fdf6ec;
$danger-color-light: #fef0f0;
$info-color-light: #f4f4f5;
// 背景色
$background-color: #f7f9fc;
$sidebar-background: #001529;
$sidebar-light-background: #ffffff;
// 文字颜色
$text-primary: #303133;
$text-regular: #606266;
$text-secondary: #909399;
$text-placeholder: #c0c4cc;
// 边框颜色
$border-color: #dcdfe6;
$border-color-light: #e4e7ed;
$border-color-lighter: #ebeef5;
// 阴影
$box-shadow-base: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
$box-shadow-dark: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.12);
// 字体
$font-family: "Source Han Sans CN", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
// 字体大小
$font-size-extra-large: 20px;
$font-size-large: 18px;
$font-size-medium: 16px;
$font-size-base: 14px;
$font-size-small: 13px;
$font-size-extra-small: 12px;
// 字体粗细
$font-weight-primary: 500;
$font-weight-secondary: 400;
// 边距
$spacer: 20px;
$spacer-small: 10px;
$spacer-large: 30px;
// 圆角
$border-radius-base: 4px;
$border-radius-medium: 8px;
$border-radius-large: 12px;
// 侧边栏宽度
$sidebar-width: 240px;
$sidebar-collapsed-width: 64px;
\ No newline at end of file \ No newline at end of file
<template>
<el-cascader
v-model="selectedValue"
:options="regionOptions"
:props="cascaderProps"
:placeholder="placeholder"
:clearable="clearable"
:show-all-levels="showAllLevels"
:style="style"
@change="handleChange"
></el-cascader>
</template>
<script>
// 地区数据
const regionOptions = [
{
value: '江苏省',
label: '江苏省',
children: [
{
value: '南京市',
label: '南京市',
children: [
{ value: '玄武区', label: '玄武区' },
{ value: '秦淮区', label: '秦淮区' },
{ value: '建邺区', label: '建邺区' },
{ value: '鼓楼区', label: '鼓楼区' },
{ value: '浦口区', label: '浦口区' },
{ value: '栖霞区', label: '栖霞区' },
{ value: '雨花台区', label: '雨花台区' },
{ value: '江宁区', label: '江宁区' },
{ value: '六合区', label: '六合区' },
{ value: '溧水区', label: '溧水区' },
{ value: '高淳区', label: '高淳区' }
]
},
{
value: '南通市',
label: '南通市',
children: [
{ value: '崇川区', label: '崇川区' },
{ value: '港闸区', label: '港闸区' }
]
},
{
value: '苏州市',
label: '苏州市',
children: [
{ value: '姑苏区', label: '姑苏区' },
{ value: '虎丘区', label: '虎丘区' }
]
}
]
}
]
export default {
name: 'CascadingRegionSelector',
props: {
value: {
type: [String, Array],
default: ''
},
placeholder: {
type: String,
default: '请选择区域'
},
clearable: {
type: Boolean,
default: true
},
showAllLevels: {
type: Boolean,
default: true
},
style: {
type: Object,
default: () => ({})
}
},
data() {
return {
selectedValue: this.value,
regionOptions,
cascaderProps: {
value: 'value',
label: 'label',
children: 'children',
checkStrictly: true
}
}
},
watch: {
value(newVal) {
this.selectedValue = newVal
}
},
methods: {
handleChange(value) {
this.$emit('input', value)
this.$emit('change', value)
}
}
}
</script>
\ No newline at end of file \ No newline at end of file
<template>
<div class="pagination-container">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="pageSizes"
:page-size="pageSize"
:layout="layout"
:total="totalItems"
>
</el-pagination>
</div>
</template>
<script>
export default {
name: 'Pagination',
props: {
currentPage: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 20
},
totalItems: {
type: Number,
default: 0
},
pageSizes: {
type: Array,
default: () => [20, 50, 100]
},
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
}
},
methods: {
handleSizeChange(val) {
this.$emit('page-size-change', val)
},
handleCurrentChange(val) {
this.$emit('current-change', val)
}
}
}
</script>
<style lang="scss" scoped>
.pagination-container {
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 20px;
padding: 0 10px;
}
</style>
\ No newline at end of file \ No newline at end of file
<template>
<div class="three-level-region-selector">
<el-row :gutter="10">
<el-col :span="8">
<el-select
v-model="selectedProvince"
placeholder="选择省份"
clearable
@change="handleProvinceChange"
style="width: 100%;"
>
<el-option
v-for="province in provinces"
:key="province"
:label="province"
:value="province"
></el-option>
</el-select>
</el-col>
<el-col :span="8">
<el-select
v-model="selectedCity"
placeholder="选择地市"
clearable
@change="handleCityChange"
:disabled="!selectedProvince"
style="width: 100%;"
>
<el-option
v-for="city in cities"
:key="city"
:label="city"
:value="city"
></el-option>
</el-select>
</el-col>
<el-col :span="8">
<el-select
v-model="selectedDistrict"
placeholder="选择区县"
clearable
:disabled="!selectedCity"
style="width: 100%;"
>
<el-option
v-for="district in districts"
:key="district"
:label="district"
:value="district"
></el-option>
</el-select>
</el-col>
</el-row>
</div>
</template>
<script>
// 地区数据
const regionData = {
'江苏省': {
'南京市': ['玄武区', '秦淮区', '建邺区', '鼓楼区', '浦口区', '栖霞区', '雨花台区', '江宁区', '六合区', '溧水区', '高淳区'],
'南通市': ['崇川区', '港闸区'],
'苏州市': ['姑苏区', '虎丘区']
}
}
export default {
name: 'ThreeLevelRegionSelector',
props: {
value: {
type: Object,
default: () => ({
province: '',
city: '',
district: ''
})
},
placeholder: {
type: String,
default: '请选择区域'
}
},
data() {
return {
selectedProvince: this.value.province,
selectedCity: this.value.city,
selectedDistrict: this.value.district
}
},
computed: {
provinces() {
return Object.keys(regionData)
},
cities() {
if (!this.selectedProvince) return []
return Object.keys(regionData[this.selectedProvince])
},
districts() {
if (!this.selectedProvince || !this.selectedCity) return []
return regionData[this.selectedProvince][this.selectedCity]
}
},
watch: {
value: {
handler(newVal) {
this.selectedProvince = newVal.province || ''
this.selectedCity = newVal.city || ''
this.selectedDistrict = newVal.district || ''
},
deep: true
},
selectedProvince() {
if (!this.provinces.includes(this.selectedProvince)) {
this.selectedCity = ''
this.selectedDistrict = ''
}
this.emitChange()
},
selectedCity() {
if (!this.cities.includes(this.selectedCity)) {
this.selectedDistrict = ''
}
this.emitChange()
},
selectedDistrict() {
this.emitChange()
}
},
methods: {
handleProvinceChange() {
if (!this.provinces.includes(this.selectedProvince)) {
this.selectedCity = ''
this.selectedDistrict = ''
}
this.emitChange()
},
handleCityChange() {
if (!this.cities.includes(this.selectedCity)) {
this.selectedDistrict = ''
}
this.emitChange()
},
emitChange() {
this.$emit('input', {
province: this.selectedProvince,
city: this.selectedCity,
district: this.selectedDistrict
})
this.$emit('change', {
province: this.selectedProvince,
city: this.selectedCity,
district: this.selectedDistrict
})
}
}
}
</script>
<style lang="scss" scoped>
.three-level-region-selector {
.el-select {
::v-deep .el-input__inner {
border-radius: 4px;
}
}
}
</style>
\ No newline at end of file \ No newline at end of file
<template>
<div class="account-management">
<div class="toolbar">
<p class="description">管理系统登录账号</p>
<el-button @click="isAddDialogOpen = true" type="primary" size="small">
<i class="el-icon-plus"></i>
添加账号
</el-button>
</div>
<div class="account-table">
<el-table :data="accounts" style="width: 100%">
<el-table-column prop="username" label="用户名" width="120"></el-table-column>
<el-table-column prop="name" label="姓名" width="100"></el-table-column>
<el-table-column prop="role" label="角色" width="120">
<template slot-scope="scope">
<el-tag size="mini">{{ scope.row.role }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="region" label="所属区域" width="150"></el-table-column>
<el-table-column label="联系方式" width="150">
<template slot-scope="scope">
<span>{{ scope.row.phone || scope.row.email || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="状态" width="80">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 'active' ? 'success' : 'info'">
{{ scope.row.status === 'active' ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="lastLogin" label="最近登录" width="150">
<template slot-scope="scope">
<span>{{ scope.row.lastLogin || '从未登录' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template slot-scope="scope">
<el-button
type="text"
size="small"
@click="editAccount(scope.row)"
>
编辑
</el-button>
<el-button
type="text"
size="small"
@click="deleteAccount(scope.row.id)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 添加/编辑账号对话框 -->
<el-dialog
:title="editingAccount ? '编辑账号' : '添加账号'"
:visible.sync="isAddDialogOpen"
width="600px"
>
<el-form :model="accountForm" :rules="accountRules" ref="accountForm" label-width="100px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="用户名" prop="username">
<el-input
v-model="accountForm.username"
placeholder="请输入用户名"
:disabled="!!editingAccount"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="姓名" prop="name">
<el-input
v-model="accountForm.name"
placeholder="请输入姓名"
></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="角色" prop="role">
<el-select v-model="accountForm.role" style="width: 100%;">
<el-option label="网格管理员" value="网格管理员"></el-option>
<el-option label="区县管理员" value="区县管理员"></el-option>
<el-option label="地市管理员" value="地市管理员"></el-option>
<el-option label="省级管理员" value="省级管理员"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属区域" prop="region">
<el-input
v-model="accountForm.region"
placeholder="请输入所属区域"
></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="手机号" prop="phone">
<el-input
v-model="accountForm.phone"
placeholder="请输入手机号"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="邮箱" prop="email">
<el-input
v-model="accountForm.email"
type="email"
placeholder="请输入邮箱"
></el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="状态" prop="status">
<el-select v-model="accountForm.status" style="width: 100%;">
<el-option label="启用" value="active"></el-option>
<el-option label="禁用" value="inactive"></el-option>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="isAddDialogOpen = false">取消</el-button>
<el-button type="primary" @click="submitAccountForm">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
// 模拟账号数据
const mockAccounts = [
{ id: 'ACC001', username: 'grid001', name: '李网格长', role: '网格管理员', region: '南京市玄武区A网格', phone: '13543210987', status: 'active', lastLogin: '2025-09-28 15:30:00', createTime: '2025-09-01 09:00:00' },
{ id: 'ACC002', username: 'county001', name: '王区长', role: '区县管理员', region: '南京市玄武区', phone: '13432109876', status: 'active', lastLogin: '2025-09-28 14:20:00', createTime: '2025-09-01 09:00:00' },
{ id: 'ACC003', username: 'city001', name: '张市长', role: '地市管理员', region: '南京市', phone: '13321098765', status: 'active', lastLogin: '2025-09-28 10:15:00', createTime: '2025-09-01 09:00:00' }
]
export default {
name: 'AccountManagement',
data() {
return {
accounts: mockAccounts,
isAddDialogOpen: false,
editingAccount: null,
accountForm: {
username: '',
name: '',
role: '网格管理员',
region: '',
phone: '',
email: '',
status: 'active'
},
accountRules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
name: [
{ required: true, message: '请输入姓名', trigger: 'blur' }
],
region: [
{ required: true, message: '请输入所属区域', trigger: 'blur' }
]
}
}
},
methods: {
editAccount(account) {
this.editingAccount = account
this.accountForm = { ...account }
this.isAddDialogOpen = true
},
deleteAccount(id) {
this.$confirm('确定要删除该账号吗?此操作不可撤销。', '确认删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.accounts = this.accounts.filter(a => a.id !== id)
this.$message.success('账号删除成功')
}).catch(() => {
// 用户取消删除
})
},
submitAccountForm() {
this.$refs.accountForm.validate((valid) => {
if (valid) {
if (this.editingAccount) {
// 更新账号信息
const index = this.accounts.findIndex(a => a.id === this.editingAccount.id)
if (index !== -1) {
this.$set(this.accounts, index, { ...this.editingAccount, ...this.accountForm })
this.$message.success('账号信息更新成功')
}
} else {
// 添加新账号
const newAccount = {
...this.accountForm,
id: `ACC${Date.now()}`,
createTime: new Date().toLocaleString('zh-CN')
}
this.accounts.push(newAccount)
this.$message.success('账号添加成功')
}
// 重置表单和状态
this.resetAccountForm()
this.isAddDialogOpen = false
this.editingAccount = null
}
})
},
resetAccountForm() {
this.accountForm = {
username: '',
name: '',
role: '网格管理员',
region: '',
phone: '',
email: '',
status: 'active'
}
}
}
}
</script>
<style lang="scss" scoped>
.account-management {
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.description {
color: #909399;
margin: 0;
}
}
.account-table {
margin-bottom: 20px;
}
}
</style>
\ No newline at end of file \ No newline at end of file
<template>
<div class="close-reason-management">
<div class="toolbar">
<p class="description">管理商机关闭时可选择的原因</p>
<el-button @click="isAddDialogOpen = true" type="primary" size="small">
<i class="el-icon-plus"></i>
添加原因
</el-button>
</div>
<div class="reason-table">
<el-table :data="reasons" style="width: 100%">
<el-table-column prop="reason" label="关闭原因" width="300"></el-table-column>
<el-table-column prop="category" label="分类" width="200">
<template slot-scope="scope">
<el-tag size="mini">{{ scope.row.category }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150">
<template slot-scope="scope">
<el-button
type="text"
size="small"
@click="editReason(scope.row)"
>
编辑
</el-button>
<el-button
type="text"
size="small"
@click="deleteReason(scope.row.id)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 添加/编辑原因对话框 -->
<el-dialog
:title="editingReason ? '编辑关闭原因' : '添加关闭原因'"
:visible.sync="isAddDialogOpen"
width="500px"
>
<el-form :model="reasonForm" :rules="reasonRules" ref="reasonForm" label-width="100px">
<el-form-item label="关闭原因" prop="reason">
<el-input
v-model="reasonForm.reason"
placeholder="请输入关闭原因"
></el-input>
</el-form-item>
<el-form-item label="分类" prop="category">
<el-input
v-model="reasonForm.category"
placeholder="请输入分类"
></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="isAddDialogOpen = false">取消</el-button>
<el-button type="primary" @click="submitReasonForm">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
// 模拟关闭原因数据
const mockCloseReasons = [
{ id: '1', reason: '客户价格不接受', category: '价格因素' },
{ id: '2', reason: '客户暂无需求', category: '需求因素' },
{ id: '3', reason: '竞品已安装', category: '竞争因素' },
{ id: '4', reason: '客户联系不上', category: '联系因素' },
{ id: '5', reason: '技术不支持', category: '技术因素' },
{ id: '6', reason: '其他原因', category: '其他' }
]
export default {
name: 'CloseReasonManagement',
data() {
return {
reasons: mockCloseReasons,
isAddDialogOpen: false,
editingReason: null,
reasonForm: {
reason: '',
category: ''
},
reasonRules: {
reason: [
{ required: true, message: '请输入关闭原因', trigger: 'blur' }
],
category: [
{ required: true, message: '请输入分类', trigger: 'blur' }
]
}
}
},
methods: {
editReason(reason) {
this.editingReason = reason
this.reasonForm = { ...reason }
this.isAddDialogOpen = true
},
deleteReason(id) {
this.$confirm('确定要删除该原因吗?此操作不可撤销。', '确认删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.reasons = this.reasons.filter(r => r.id !== id)
this.$message.success('关闭原因删除成功')
}).catch(() => {
// 用户取消删除
})
},
submitReasonForm() {
this.$refs.reasonForm.validate((valid) => {
if (valid) {
if (this.editingReason) {
// 更新原因信息
const index = this.reasons.findIndex(r => r.id === this.editingReason.id)
if (index !== -1) {
this.$set(this.reasons, index, { ...this.editingReason, ...this.reasonForm })
this.$message.success('关闭原因更新成功')
}
} else {
// 添加新原因
const newReason = {
...this.reasonForm,
id: `REASON${Date.now()}`
}
this.reasons.push(newReason)
this.$message.success('关闭原因添加成功')
}
// 重置表单和状态
this.resetReasonForm()
this.isAddDialogOpen = false
this.editingReason = null
}
})
},
resetReasonForm() {
this.reasonForm = {
reason: '',
category: ''
}
}
}
}
</script>
<style lang="scss" scoped>
.close-reason-management {
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.description {
color: #909399;
margin: 0;
}
}
.reason-table {
margin-bottom: 20px;
}
}
</style>
\ No newline at end of file \ No newline at end of file
<template>
<div class="tag-management">
<div class="toolbar">
<p class="description">管理商机录入时的标签选项</p>
<el-button @click="isAddDialogOpen = true" type="primary" size="small">
<i class="el-icon-plus"></i>
添加标签
</el-button>
</div>
<div class="tag-list">
<el-row :gutter="20">
<el-col
:xs="24"
:sm="12"
:md="8"
:lg="8"
:xl="6"
v-for="tag in tags"
:key="tag.id"
>
<el-card class="tag-card">
<div class="tag-header">
<div class="tag-title" v-if="tag.isSystem">
<h3>{{ tag.name }}</h3>
<el-tag size="mini" type="info" style="margin-left: 8px;">系统默认</el-tag>
</div>
<h3 v-else>{{ tag.name }}</h3>
</div>
<div class="tag-info" v-if="tag.category">
<i class="el-icon-folder"></i>
<span>分类:{{ tag.category }}</span>
</div>
<div class="tag-description" v-if="tag.description">
<i class="el-icon-document"></i>
<span>{{ tag.description }}</span>
</div>
<div class="tag-footer">
<el-switch
v-model="tag.enabled"
active-text="启用"
inactive-text="禁用"
@change="toggleTag(tag.id)"
:disabled="tag.isSystem"
></el-switch>
<div class="tag-actions">
<el-button
size="mini"
@click="editTag(tag)"
:disabled="tag.isSystem"
>
编辑
</el-button>
<el-button
size="mini"
@click="deleteTag(tag.id)"
v-if="!tag.isSystem"
>
删除
</el-button>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
<!-- 添加/编辑标签对话框 -->
<el-dialog
:title="editingTag ? '编辑标签' : '添加标签'"
:visible.sync="isAddDialogOpen"
width="500px"
>
<el-form :model="tagForm" :rules="tagRules" ref="tagForm" label-width="100px">
<el-form-item label="标签名称" prop="name">
<el-input
v-model="tagForm.name"
placeholder="请输入标签名称"
:disabled="!!editingTag && editingTag.isSystem"
></el-input>
<p v-if="editingTag && editingTag.isSystem" class="system-tag-info">系统默认标签只能修改描述信息</p>
</el-form-item>
<el-form-item label="标签分类" prop="category">
<el-input
v-model="tagForm.category"
placeholder="请输����标签分类"
:disabled="!!editingTag && editingTag.isSystem"
></el-input>
</el-form-item>
<el-form-item label="标签描述" prop="description">
<el-input
v-model="tagForm.description"
type="textarea"
:rows="3"
placeholder="可选,描述此标签的用途"
></el-input>
</el-form-item>
<el-form-item label="启用标签" v-if="!(editingTag && editingTag.isSystem)">
<el-switch
v-model="tagForm.enabled"
></el-switch>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="isAddDialogOpen = false">取消</el-button>
<el-button type="primary" @click="submitTagForm">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
// 模拟标签数据
const mockTags = [
{ id: 'SYS001', name: '小微工程', color: '#3b82f6', order: 1, description: '选择该标签后商机自动分配给区县负责人', enabled: true, category: '小微工程', isSystem: true },
{ id: 'SYS002', name: '政企业务', color: '#10b981', order: 2, description: '选择该标签后商机自动分配给区县负责人', enabled: true, category: '政企业务', isSystem: true },
{ id: '3', name: '网络升级', color: '#f59e0b', order: 3, description: '用户需要升级网络带宽', enabled: true, category: '产品服务' },
{ id: '4', name: '融合套餐', color: '#ef4444', order: 4, description: '手机+宽带融合套餐', enabled: true, category: '业务类型' },
{ id: '5', name: '智能家居', color: '#8b5cf6', order: 5, description: '智能家居产品需求', enabled: false, category: '产品服务' },
{ id: '6', name: '家庭安防', color: '#ec4899', order: 6, description: '家庭安防监控需求', enabled: true, category: '产品服务' }
]
export default {
name: 'TagManagement',
data() {
return {
tags: mockTags,
isAddDialogOpen: false,
editingTag: null,
tagForm: {
name: '',
color: '#3b82f6',
order: 1,
description: '',
enabled: true,
category: ''
},
tagRules: {
name: [
{ required: true, message: '请输入标签名称', trigger: 'blur' }
]
}
}
},
methods: {
editTag(tag) {
this.editingTag = tag
this.tagForm = { ...tag }
this.isAddDialogOpen = true
},
deleteTag(id) {
this.$confirm('确定要删除该标签吗?此操作不可撤销。', '确认删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.tags = this.tags.filter(t => t.id !== id)
this.$message.success('标签删除成功')
}).catch(() => {
// 用户取消删除
})
},
toggleTag(id) {
const tag = this.tags.find(t => t.id === id)
if (tag) {
const action = tag.enabled ? '开启' : '关闭'
this.$message.success(`标签已${action}`)
}
},
submitTagForm() {
this.$refs.tagForm.validate((valid) => {
if (valid) {
if (this.editingTag) {
// 更新标签信息
const index = this.tags.findIndex(t => t.id === this.editingTag.id)
if (index !== -1) {
// 保留系统标签的特殊属性
const isSystem = this.tags[index].isSystem
this.$set(this.tags, index, {
...this.tags[index],
...this.tagForm,
isSystem
})
this.$message.success('标签更新成功')
}
} else {
// 添加新标签
const newTag = {
...this.tagForm,
id: `TAG${Date.now()}`,
order: this.tags.length + 1
}
this.tags.push(newTag)
this.$message.success('标签添加成功')
}
// 重置表单和状态
this.resetTagForm()
this.isAddDialogOpen = false
this.editingTag = null
}
})
},
resetTagForm() {
this.tagForm = {
name: '',
color: '#3b82f6',
order: 1,
description: '',
enabled: true,
category: ''
}
}
}
}
</script>
<style lang="scss" scoped>
.tag-management {
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
.description {
color: #909399;
margin: 0;
}
}
.tag-list {
.tag-card {
margin-bottom: 20px;
position: relative;
height: 100%;
.tag-header {
display: flex;
align-items: center;
margin-bottom: 15px;
h3 {
margin: 0;
font-size: 18px;
font-weight: bold;
}
}
.tag-info,
.tag-description {
display: flex;
align-items: center;
margin-bottom: 10px;
font-size: 14px;
color: #606266;
i {
margin-right: 8px;
color: #909399;
}
}
.tag-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #ebeef5;
.tag-actions {
display: flex;
gap: 8px;
}
}
}
}
.system-tag-info {
font-size: 12px;
color: #909399;
margin: 5px 0 0 0;
}
}
</style>
\ No newline at end of file \ No newline at end of file
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import './assets/styles/index.scss'
// 导入全局组件
import Pagination from '@/components/Pagination.vue'
import CascadingRegionSelector from '@/components/CascadingRegionSelector.vue'
import ThreeLevelRegionSelector from '@/components/ThreeLevelRegionSelector.vue'
Vue.use(ElementUI)
// 注册全局组件
Vue.component('Pagination', Pagination)
Vue.component('CascadingRegionSelector', CascadingRegionSelector)
Vue.component('ThreeLevelRegionSelector', ThreeLevelRegionSelector)
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
\ No newline at end of file \ No newline at end of file
import Vue from 'vue'
import VueRouter from 'vue-router'
import LoginPage from '../views/LoginPage.vue'
import DashboardLayout from '../views/DashboardLayout.vue'
import OpportunityManagement from '../views/OpportunityManagement.vue'
import OpportunityDetail from '../views/OpportunityDetail.vue'
import GridQuery from '../views/GridQuery.vue'
import SystemManagement from '../views/SystemManagement.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/login',
name: 'Login',
component: LoginPage
},
{
path: '/',
component: DashboardLayout,
children: [
{
path: '',
redirect: '/opportunities'
},
{
path: '/opportunities',
name: 'OpportunityManagement',
component: OpportunityManagement
},
{
path: '/opportunities/:id',
name: 'OpportunityDetail',
component: OpportunityDetail
},
{
path: '/grid-query',
name: 'GridQuery',
component: GridQuery
},
{
path: '/system',
redirect: '/system/personnel'
},
{
path: '/system/personnel',
name: 'SystemPersonnel',
component: SystemManagement,
props: { activeTab: 'personnel' }
},
{
path: '/system/opportunity-config',
name: 'SystemOpportunityConfig',
component: SystemManagement,
props: { activeTab: 'tags' }
},
{
path: '/system/accounts',
name: 'SystemAccounts',
component: SystemManagement,
props: { activeTab: 'accounts' }
}
]
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
// 路由守卫
router.beforeEach((to, from, next) => {
// 检查是否已登录(演示模式下允许访问)
const isAuthenticated = localStorage.getItem('user') || sessionStorage.getItem('demoMode')
if (to.path !== '/login' && !isAuthenticated) {
next('/login')
} else {
next()
}
})
export default router
\ No newline at end of file \ No newline at end of file
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 模拟用户数据
const mockUsers = {
grid001: {
password: "123456",
user: {
id: "grid001",
name: "张网格",
role: "grid_manager",
permissions: [
"view_grid_opportunities",
"assign_opportunities",
"manage_opportunities"
],
region: "南京市玄武区A网格",
phone: "13812345678"
}
},
county001: {
password: "123456",
user: {
id: "county001",
name: "李区长",
role: "county_admin",
permissions: [
"view_county_opportunities",
"manage_opportunities",
"view_reports"
],
region: "南京市玄武区",
phone: "13887654321"
}
},
city001: {
password: "123456",
user: {
id: "city001",
name: "王市长",
role: "city_admin",
permissions: [
"view_city_opportunities",
"manage_opportunities",
"view_reports",
"manage_system"
],
region: "南京市",
phone: "13765432109"
}
},
province001: {
password: "123456",
user: {
id: "province001",
name: "赵省长",
role: "province_admin",
permissions: [
"view_all_opportunities",
"manage_opportunities",
"view_reports",
"manage_system"
],
region: "江苏省",
phone: "13654321098"
}
}
}
export default new Vuex.Store({
state: {
user: null
},
getters: {
isAuthenticated: state => !!state.user,
user: state => state.user
},
mutations: {
SET_USER(state, user) {
state.user = user
},
LOGOUT(state) {
state.user = null
localStorage.removeItem('user')
}
},
actions: {
login({ commit }, { username, password }) {
return new Promise((resolve, reject) => {
// 模拟登录验证
const userData = mockUsers[username]
if (userData && userData.password === password) {
commit('SET_USER', userData.user)
localStorage.setItem('user', JSON.stringify(userData.user))
resolve(true)
} else {
reject(new Error('账号或密码错误'))
}
})
},
logout({ commit }) {
commit('LOGOUT')
},
initializeAuth({ commit }) {
// 尝试从localStorage恢复用户状态
const savedUser = localStorage.getItem('user')
if (savedUser) {
commit('SET_USER', JSON.parse(savedUser))
} else {
// 如果没有保存的用户信息,提供默认用户用于演示
const demoUser = {
id: "demo001",
name: "演示用户",
role: "city_admin",
permissions: [
"view_city_opportunities",
"manage_opportunities",
"assign_opportunities",
"view_reports",
"manage_system"
],
region: "南京市",
phone: "13800138000"
}
commit('SET_USER', demoUser)
sessionStorage.setItem('demoMode', 'true')
}
}
},
modules: {
}
})
\ No newline at end of file \ No newline at end of file
<template>
<div class="login-container">
<!-- 左侧 - 品牌展示区 -->
<div class="login-left hidden-lg-only">
<div class="login-bg">
<img
src="https://images.unsplash.com/photo-1681321570365-df53da7dbaa2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w3Nzg4Nzd8MHwxfHNlYXJjaHwxfHxidXNpbmVzcyUyMHRlY2hub2xvZ3klMjBuZXR3b3JrfGVufDF8fHx8MTc2MTIxMTIyMXww&ixlib=rb-4.1.0&q=80&w=1080"
alt="背景"
class="login-bg-image"
>
</div>
<div class="login-content">
<div class="login-brand-info">
<h2 class="login-title">
智能商机管理<br>助力业务增长
</h2>
<p class="login-desc">
装维师傅上门服务时发现商机,系统自动分配给营销人员跟进,实现业务闭环管理
</p>
</div>
<!-- 特性卡片 -->
<div class="login-features">
<div class="login-feature-item">
<div class="login-feature-icon">
<i class="el-icon-mobile"></i>
</div>
<div class="login-feature-text">
<h3 class="login-feature-title">移动端采集</h3>
<p class="login-feature-desc">装维师傅随时随地记录商机信息</p>
</div>
</div>
<div class="login-feature-item">
<div class="login-feature-icon">
<i class="el-icon-user"></i>
</div>
<div class="login-feature-text">
<h3 class="login-feature-title">智能分配</h3>
<p class="login-feature-desc">基于网格自动分配给合适的营销人员</p>
</div>
</div>
<div class="login-feature-item">
<div class="login-feature-icon">
<i class="el-icon-data-analysis"></i>
</div>
<div class="login-feature-text">
<h3 class="login-feature-title">数据驱动</h3>
<p class="login-feature-desc">实时统计分析,洞察业务趋势</p>
</div>
</div>
</div>
</div>
<!-- 装饰元素 -->
<div class="login-decor-1"></div>
<div class="login-decor-2"></div>
</div>
<!-- 右侧 - 登录表单 -->
<div class="login-right">
<div class="login-form-wrapper">
<div class="login-header">
<img
src="../assets/icons/a74fada241bbe2bf828e041e685f9486904a94e9.png"
alt="中国移动 Logo"
class="login-logo"
>
<h1 class="login-app-title">上门随销商机管理平台</h1>
</div>
<el-form :model="loginForm" :rules="loginRules" ref="loginForm" class="login-form">
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
placeholder="请输入用户名"
prefix-icon="el-icon-user"
size="medium"
@keyup.enter.native="handleLogin"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="请输入密码"
prefix-icon="el-icon-lock"
size="medium"
@keyup.enter.native="handleLogin"
/>
</el-form-item>
<el-button
type="primary"
@click="handleLogin"
:loading="loading"
class="login-btn"
size="medium"
style="width: 100%;"
>
<i v-if="!loading" class="el-icon-right"></i>
{{ loading ? '登录中...' : '登录' }}
</el-button>
</el-form>
<div class="login-demo-info">
<p class="login-demo-title">测试账号:</p>
<div class="login-demo-accounts">
<p>网格管理员: grid001 / 123456</p>
<p>区县管理员: county001 / 123456</p>
<p>地市管理员: city001 / 123456</p>
<p>省级管理员: province001 / 123456</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
name: 'LoginPage',
data() {
return {
loginForm: {
username: '',
password: ''
},
loginRules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码长度不能少于6位', trigger: 'blur' }
]
},
loading: false
}
},
methods: {
...mapActions(['login']),
async handleLogin() {
try {
await this.$refs.loginForm.validate()
this.loading = true
await this.login({
username: this.loginForm.username,
password: this.loginForm.password
})
this.$message.success('登录成功')
this.$router.push('/')
} catch (error) {
console.error('Login error:', error)
this.$message.error(error.message || '登录失败,请稍后重试')
} finally {
this.loading = false
}
}
}
}
</script>
<style lang="scss" scoped>
.login-container {
min-height: 100vh;
display: flex;
background: #fff;
.login-left {
flex: 1;
background: linear-gradient(135deg, #3b82f6 0%, #6366f1 50%, #8b5cf6 100%);
position: relative;
overflow: hidden;
.login-bg {
position: absolute;
inset: 0;
opacity: 0.2;
.login-bg-image {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.login-content {
position: relative;
z-index: 10;
display: flex;
flex-direction: column;
justify-content: center;
padding: 100px 80px 100px 80px;
color: white;
.login-brand-info {
margin-bottom: 60px;
.login-title {
font-size: 42px;
margin: 0 0 20px 0;
line-height: 1.2;
font-weight: bold;
}
.login-desc {
font-size: 16px;
color: rgba(255, 255, 255, 0.8);
max-width: 420px;
}
}
.login-features {
.login-feature-item {
display: flex;
align-items: flex-start;
gap: 20px;
padding: 20px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
.login-feature-icon {
flex-shrink: 0;
width: 48px;
height: 48px;
background: rgba(255, 255, 255, 0.2);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
i {
font-size: 24px;
color: white;
}
}
.login-feature-text {
.login-feature-title {
font-size: 18px;
margin: 0 0 5px 0;
font-weight: 600;
}
.login-feature-desc {
font-size: 14px;
color: rgba(255, 255, 255, 0.8);
margin: 0;
}
}
}
}
}
.login-decor-1 {
position: absolute;
top: 80px;
right: 80px;
width: 288px;
height: 288px;
background: rgba(255, 255, 255, 0.05);
border-radius: 50%;
filter: blur(60px);
}
.login-decor-2 {
position: absolute;
bottom: 80px;
left: 80px;
width: 384px;
height: 384px;
background: rgba(139, 92, 246, 0.1);
border-radius: 50%;
filter: blur(60px);
}
}
.login-right {
width: 50%;
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
.login-form-wrapper {
width: 100%;
max-width: 420px;
.login-header {
margin-bottom: 40px;
text-align: center;
.login-logo {
height: 64px;
width: 64px;
margin-bottom: 16px;
}
.login-app-title {
font-size: 28px;
font-weight: bold;
margin: 0;
}
}
.login-form {
margin-bottom: 32px;
}
.login-btn {
height: 44px;
font-size: 16px;
font-weight: 500;
}
.login-demo-info {
padding: 20px;
background-color: #f5f7fa;
border-radius: 8px;
.login-demo-title {
font-size: 14px;
margin: 0 0 10px 0;
color: #606266;
}
.login-demo-accounts {
font-size: 12px;
color: #909399;
line-height: 1.8;
p {
margin: 0 0 5px 0;
}
}
}
}
}
}
// 响应式设计
@media (max-width: 992px) {
.login-container {
.login-left {
display: none;
}
.login-right {
width: 100%;
}
}
}
</style>
\ No newline at end of file \ No newline at end of file
<template>
<div class="opportunity-detail">
<div class="page-header">
<el-page-header @back="goBack" content="商机详情">
</el-page-header>
</div>
<el-card class="detail-card">
<div class="card-header">
<h3>基本信息</h3>
</div>
<div class="card-content">
<el-row :gutter="20">
<el-col :span="8">
<div class="info-item">
<span class="info-label">商机ID:</span>
<span class="info-value">{{ opportunity.id }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">创建时间:</span>
<span class="info-value">{{ opportunity.createTime }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">当前状态:</span>
<span class="info-value">
<el-tag :type="getStatusType(opportunity.status)">
{{ getStatusLabel(opportunity.status) }}
</el-tag>
</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<div class="info-item">
<span class="info-label">客户地址:</span>
<span class="info-value">{{ opportunity.customerAddress }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">用户账号:</span>
<span class="info-value">{{ opportunity.customerPhone }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">最新跟进:</span>
<span class="info-value">{{ opportunity.lastFollowTime || '-' }}</span>
</div>
</el-col>
</el-row>
</div>
</el-card>
<el-card class="detail-card">
<div class="card-header">
<h3>人员信息</h3>
</div>
<div class="card-content">
<el-row :gutter="20">
<el-col :span="8">
<div class="info-item">
<span class="info-label">装维师傅:</span>
<span class="info-value">{{ opportunity.installerName }} ({{ opportunity.installerId }})</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">联系电话:</span>
<span class="info-value">{{ opportunity.installerPhone }}</span>
</div>
</el-col>
<el-col :span="8">
<div class="info-item">
<span class="info-label">营销人员:</span>
<span class="info-value">{{ opportunity.assignedToName || '-' }} ({{ opportunity.assignedTo || '-' }})</span>
</div>
</el-col>
</el-row>
</div>
</el-card>
<el-card class="detail-card">
<div class="card-header">
<h3>商机详情</h3>
</div>
<div class="card-content">
<div class="info-item">
<span class="info-label">商机标签:</span>
<span class="info-value">
<el-tag
v-for="tag in opportunity.tags"
:key="tag"
size="small"
style="margin-right: 8px;"
>
{{ tag }}
</el-tag>
</span>
</div>
<div class="info-item" style="margin-top: 15px;">
<span class="info-label">商机描述:</span>
<span class="info-value">{{ opportunity.description }}</span>
</div>
</div>
</el-card>
<el-card class="detail-card">
<div class="card-header">
<h3>跟进记录</h3>
</div>
<div class="card-content">
<el-timeline>
<el-timeline-item
v-for="(activity, index) in activities"
:key="index"
:timestamp="activity.timestamp"
:type="activity.type"
>
<div class="activity-content">
<p>{{ activity.content }}</p>
<p v-if="activity.operator" class="operator">操作人:{{ activity.operator }}</p>
</div>
</el-timeline-item>
</el-timeline>
</div>
</el-card>
<div class="actions">
<el-button @click="goBack">返回</el-button>
<el-button type="primary" @click="handleFollowUp">跟进</el-button>
<el-button type="success" @click="handleComplete">成单</el-button>
</div>
</div>
</template>
<script>
// 商机状态映射
const statusMap = {
'assigned': { label: '待跟进', type: 'info' },
'following': { label: '跟进中', type: 'warning' },
'pending_review': { label: '成单待审核', type: 'primary' },
'completed': { label: '已成单', type: 'success' },
'closed': { label: '已关闭', type: 'info' }
}
// 模拟商机数据
const mockOpportunity = {
id: 'OP202500001',
createTime: '2025-09-27 10:30:00',
customerAddress: '南京市玄武区中山路123号',
installerId: 'INS001',
installerName: '王师傅',
installerPhone: '13987654321',
tags: ['网络升级', '家庭安防'],
status: 'following',
assignedTo: 'SALE001',
assignedToName: '张营销',
lastFollowTime: '2025-09-28 14:20:00',
customerPhone: '138****5678',
description: '用户反馈家中网络卡顿,希望升级宽带套餐,同时对家庭监控摄像头很感兴趣',
region: '南京市玄武区',
gridName: 'A网格'
}
// 模拟跟进记录
const mockActivities = [
{
content: '商机创建',
timestamp: '2025-09-27 10:30:00',
type: 'primary',
operator: '王师傅'
},
{
content: '首次跟进,联系客户确认需求',
timestamp: '2025-09-27 15:45:00',
type: 'success',
operator: '张营销'
},
{
content: '预约上门时间',
timestamp: '2025-09-28 09:30:00',
type: 'success',
operator: '张营销'
},
{
content: '上门服务完成,客户有意向办理套餐升级',
timestamp: '2025-09-28 14:20:00',
type: 'success',
operator: '张营销'
}
]
export default {
name: 'OpportunityDetail',
data() {
return {
opportunity: mockOpportunity,
activities: mockActivities
}
},
methods: {
goBack() {
this.$router.go(-1)
},
getStatusType(status) {
return statusMap[status]?.type || 'info'
},
getStatusLabel(status) {
return statusMap[status]?.label || status
},
handleFollowUp() {
this.$message.info('跟进功能开发中')
},
handleComplete() {
this.$message.info('成单功能开发中')
}
}
}
</script>
<style lang="scss" scoped>
.opportunity-detail {
.page-header {
margin-bottom: 24px;
}
.detail-card {
margin-bottom: 24px;
.card-header {
margin-bottom: 20px;
h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #303133;
}
}
.card-content {
.info-item {
display: flex;
margin-bottom: 15px;
.info-label {
width: 100px;
color: #606266;
font-weight: 500;
}
.info-value {
flex: 1;
color: #303133;
}
}
.activity-content {
p {
margin: 0 0 5px 0;
}
.operator {
font-size: 12px;
color: #909399;
}
}
}
}
.actions {
text-align: center;
padding: 20px 0;
.el-button {
margin: 0 10px;
}
}
}
</style>
\ No newline at end of file \ No newline at end of file
<template>
<div class="system-management">
<!-- 页面标题 -->
<div class="page-header">
<h1>{{ activeTabLabel }}</h1>
</div>
<!-- 人员管理页面特殊处理:显示业务类型Tab -->
<div v-if="activeTab === 'personnel'" class="personnel-management">
<el-tabs v-model="businessTab" @tab-click="handleBusinessTabClick">
<el-tab-pane label="常规业务" name="regular"></el-tab-pane>
<el-tab-pane label="政企业务" name="enterprise" v-if="hasEnterprisePermission"></el-tab-pane>
</el-tabs>
<!-- 常规业务 -->
<div v-if="businessTab === 'regular'">
<el-card>
<div class="card-content">
<PersonnelManagement />
</div>
</el-card>
</div>
<!-- 政企业务 -->
<div v-if="businessTab === 'enterprise' && hasEnterprisePermission">
<el-card>
<div class="card-content">
<EnterprisePersonnelManagement />
</div>
</el-card>
</div>
</div>
<!-- 商机管理配置 -->
<div v-else-if="activeTab === 'tags'" class="opportunity-config">
<el-tabs v-model="configTab" @tab-click="handleConfigTabClick">
<el-tab-pane label="商机标签" name="tags"></el-tab-pane>
<el-tab-pane label="商机关闭" name="reasons"></el-tab-pane>
</el-tabs>
<!-- 商机标签 -->
<div v-if="configTab === 'tags'">
<el-card>
<div class="card-content">
<TagManagement />
</div>
</el-card>
</div>
<!-- 商机关闭原因 -->
<div v-if="configTab === 'reasons'">
<el-card>
<div class="card-content">
<CloseReasonManagement />
</div>
</el-card>
</div>
</div>
<!-- 账号管理 -->
<div v-else-if="activeTab === 'accounts'">
<el-card>
<div class="card-content">
<AccountManagement />
</div>
</el-card>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import PersonnelManagement from '@/components/system/PersonnelManagement.vue'
import EnterprisePersonnelManagement from '@/components/system/EnterprisePersonnelManagement.vue'
import TagManagement from '@/components/system/TagManagement.vue'
import CloseReasonManagement from '@/components/system/CloseReasonManagement.vue'
import AccountManagement from '@/components/system/AccountManagement.vue'
export default {
name: 'SystemManagement',
components: {
PersonnelManagement,
EnterprisePersonnelManagement,
TagManagement,
CloseReasonManagement,
AccountManagement
},
props: {
activeTab: {
type: String,
default: 'personnel'
}
},
data() {
return {
businessTab: 'regular',
configTab: 'tags'
}
},
computed: {
...mapGetters(['user']),
hasEnterprisePermission() {
return this.user?.role === 'city_admin' || this.user?.role === 'province_admin'
},
activeTabLabel() {
const tabLabels = {
'personnel': '人员管理',
'tags': '商机管理',
'accounts': '账号管理'
}
return tabLabels[this.activeTab] || '系统管理'
}
},
methods: {
handleBusinessTabClick(tab) {
this.businessTab = tab.name
},
handleConfigTabClick(tab) {
this.configTab = tab.name
}
}
}
</script>
<style lang="scss" scoped>
.system-management {
.page-header {
margin-bottom: 24px;
h1 {
font-size: 24px;
font-weight: 600;
margin: 0;
color: #303133;
}
}
.personnel-management,
.opportunity-config {
.el-tabs {
margin-bottom: 24px;
}
}
.card-content {
padding: 24px 0;
}
}
</style>
\ No newline at end of file \ No newline at end of file
// vue.config.js
const path = require('path')
module.exports = {
publicPath: '/',
outputDir: 'dist',
assetsDir: 'static',
lintOnSave: process.env.NODE_ENV === 'development',
productionSourceMap: false,
devServer: {
port: 8080,
open: true,
overlay: {
warnings: false,
errors: true
}
},
configureWebpack: {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
},
chainWebpack: config => {
// 配置SVG图标
config.module
.rule('svg')
.exclude.add(path.resolve(__dirname, 'src/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(path.resolve(__dirname, 'src/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
.end()
},
css: {
loaderOptions: {
sass: {
prependData: `@import "@/assets/styles/variables.scss";`
}
}
}
}
\ No newline at end of file \ No newline at end of file
{
"permissions": {
"allow": [
"Bash(claude mcp add --transport http figma http://127.0.0.1:3845/mcp)",
"Bash(claude mcp list)",
"Bash(claude mcp tools)",
"Bash(claude mcp get)",
"Bash(claude mcp --help)",
"Bash(claude mcp get figma)",
"WebFetch(domain:www.figma.com)",
"Bash(mkdir -p css js images)",
"Bash(claude mcp ping figma)",
"Bash(claude mcp list --verbose)",
"Bash(claude mcp add --transport http figma-desktop http://127.0.0.1:3845/mcp)",
"mcp__figma__get_design_context",
"Read(//Users/lining/Desktop/xsdCode/Activity/zhiJianBusi/**)",
"Bash(claude mcp remove figma-desktop)",
"mcp__figma__get_screenshot",
"mcp__figma__get_metadata",
"Bash(python3 -m http.server 8080)",
"Bash(open \"http://localhost:8080/zjbPhone/busiDetail.html\")",
"Bash(open \"http://localhost:8080/myBusi.html\")"
],
"deny": [],
"ask": []
}
}
\ No newline at end of file \ No newline at end of file
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>商机信息</title>
<link rel="stylesheet" href="css/addBusi.css">
</head>
<body>
<div id="app" v-cloak>
<div class="app-container" data-name="填写商机-默认" data-node-id="6:195">
<audio ref="audioObj" playsinline></audio>
<!-- 联系方式 -->
<div class="section" data-node-id="79:2566">
<div class="section-card" data-node-id="6:249">
<h2 class="section-title" data-node-id="6:253">联系方式</h2>
<div class="contact-info" v-if="!isAlone">
<span class="contact-text" data-node-id="6:259">{{ contactPhone }}</span>
<button class="edit-button" data-node-id="6:263" @click="editContact">修改</button>
</div>
<input class="phoneInput" v-else type="text" placeholder="请输入手机号">
</div>
</div>
<!-- 用户地址 -->
<div class="section" v-if="isAlone">
<div class="section-card" data-node-id="6:249">
<h2 class="section-title" data-node-id="6:253">用户地址</h2>
<div class="addressLi">
<div class="name">所在地区</div>
<div class="selectCity">
<input v-model="areaStore.text" @touchend.prevent="selectPicker('areaStore')" type="text" placeholder="省、市、区、街道" readonly>
<img src="images/right.png" alt="">
</div>
</div>
<div class="addressLi">
<div class="name">详细地址</div>
<input type="text" placeholder="门牌号、楼栋号">
</div>
</div>
</div>
<!-- 商机类型 -->
<div class="section" data-node-id="6:612">
<div class="section-card" data-node-id="6:264">
<h2 class="section-title" data-node-id="6:265">商机类型</h2>
<div class="tags-container" data-node-id="6:281">
<div
v-for="type in businessTypes"
:key="type.id"
:class="['tag-button', { active: type.selected }]"
:data-node-id="type.nodeId"
@click="selectBusinessType(type.id)"
>
{{ type.name }}
</div>
</div>
</div>
</div>
<!-- 语音描述 -->
<div class="section" data-node-id="6:611">
<div class="section-card" data-node-id="6:282">
<h2 class="section-title" data-node-id="6:283">语音描述</h2>
<div class="cordList">
<div class="cordLi" v-for="(item,index) in recordingUrlArr">
<div class="left" @click="playAudio(index)">
<img class="pause" src="images/pause.png" alt="">
<img class="voice" src="images/voice.png" alt="">
<div class="time">{{item.time}}</div>
</div>
<img class="close" src="images/close.png" @click="removeAudio(index)">
</div>
</div>
<!-- 默认状态 -->
<button
v-if="!isRecording"
class="voice-button voice-button-default"
data-name="默认"
data-node-id="6:934"
@click="toggleRecording"
>
<div class="voice-button-content">
<div class="voice-icon" data-name="Icon" data-node-id="6:856">
<img src="images/c1789e63ea62900756fee248551c8d3beb5ef91a.svg" alt="语音图标">
</div>
<span class="voice-text" data-node-id="6:860">添加语音</span>
</div>
</button>
<!-- 录音状态 -->
<div
v-if="isRecording"
class="voice-button voice-button-recording"
data-name="录音"
data-node-id="6:935"
>
<div class="voice-button-content">
<div class="voice-waveform" data-node-id="6:864">
<!-- 音频波形条将通过JavaScript动态生成 -->
</div>
<span class="voice-timer" data-node-id="6:928">{{ formatRecordingTime(recordingDuration) }}</span>
<button class="voice-stop-button" data-name="Button" data-node-id="6:929" @click="toggleRecording">
<div class="voice-stop-icon" data-name="Icon" data-node-id="6:930"></div>
</button>
</div>
</div>
</div>
</div>
<!-- 文字描述 -->
<div class="section" data-node-id="6:508">
<div class="section-card" data-node-id="6:492">
<h2 class="section-title" data-node-id="6:493">文字描述</h2>
<div class="textarea-container" data-node-id="6:502">
<textarea
class="description-textarea"
placeholder="可在此补充用户家庭网络情况、具体需求等..."
v-model="textDescription"
data-node-id="6:506"
></textarea>
</div>
</div>
</div>
<!-- 底部提交区域 -->
<div class="bottom-bar" v-if="!isAlone">
<div class="submit-button" data-node-id="6:498" @click="submitBusiness">
提交商机
</div>
</div>
<div v-else class="submit-button" data-node-id="6:498" @click="submitBusiness">
提交商机
</div>
<!-- 底部导航 -->
<div class="bottom-nav" v-if="isAlone">
<div class="nav-item collect-business">
<img class="nav-icon" src="images/collect-icon.svg" alt="收集商机">
<span class="nav-text" data-node-id="355:528">收集商机</span>
</div>
<div class="nav-item all-business active">
<img class="nav-icon" src="images/business-icon.svg" alt="收集商机">
<span class="nav-text" data-node-id="355:532">全部商机</span>
</div>
</div>
<!-- 修改联系方式弹窗 -->
<div class="modal-overlay" v-if="modifyPhone.isShow">
<div class="modal">
<div class="til">修改联系方式</div>
<input class="input" v-model="modifyPhone.phone" type="text" placeholder="请输入" maxlength="11">
<div class="butBot">
<div class="cancel" @click="closeEditModal">取消</div>
<div class="confirm" @click="submitPhone">确认</div>
</div>
</div>
</div>
</div>
</div>
<!-- 引入Vue.js -->
<script src="js/vue.min.js"></script>
<script src="js/util.js"></script>
<script src="js/addBusi.js"></script>
</body>
</html>
\ No newline at end of file \ No newline at end of file
html {
font-size: calc(100vw/7.5);
}
[v-cloak] {
display: none !important;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
outline: none;
}
body {
font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
background-color: #F5F5F5;
}
.app-container {
position: relative;
background-color: #F5F5F5;
padding: 0 .24rem;
padding-bottom: 1.3rem;
}
.topImg{
position: absolute;
top: 0;
left: 0;
width: 100%;
z-index: 0;
}
.status-content {
padding: .48rem .32rem .4rem .32rem;
color: #fff;
font-size: .28rem;
}
.status-content .status-title{
font-size: 0.44rem;
font-weight: bold;
color: #fff;
margin-bottom: .28rem;
}
.outAlertBg{
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.outAlertBg .alertCon{
background: #fff;
width: 5.6rem;
padding: .48rem .32rem;
border-radius: .2rem;
}
.alertCon .title{
color: #333;
font-size: .36rem;
font-weight: 600;
margin-bottom: .48rem;
text-align: center;
}
.alertCon .ali{
margin-bottom: .48rem;
}
.alertCon .ali .til{
color: #666;
font-size: .28rem;
margin-bottom: .16rem;
}
.alertCon .ali textarea{
background: #F7F8FA;
padding: .28rem;
height: 1.76rem;
color: #333;
width: 100%;
border: none;
display: block;
}
.alertCon .ali textarea::placeholder{
color: #999;
}
.alertCon .ali .imgList{
display: flex;
margin: .16rem 0;
}
.alertCon .ali .imgList .addImg{
width: 1.44rem;
height: 1.44rem;
border: 1px dashed #DCDFE6;
border-radius: .12rem;
display: flex;
align-items: center;
justify-content: center;
}
.alertCon .ali .imgList .addImg img.add{
width: .56rem;
height: .56rem;
}
.alertCon .ali .imgList .addImg input{
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
opacity: 0;
}
.alertCon .ali .imgList .imgShow{
width: 1.44rem;
height: 1.44rem;
position: relative;
margin-left: .16rem;
border-radius: .12rem;
}
.alertCon .ali .imgList .imgShow img.show{
width: 100%;
height: 100%;
}
.alertCon .ali .imgList .imgShow .close{
position: absolute;
top: -.15rem;
right: -.15rem;
width: .3rem;
height: .3rem;
}
.alertCon .ali .imgts{
color: #999;
font-size: .26rem;
}
.alertCon .alertButt{
display: flex;
justify-content: space-between;
margin-top: .48rem;
}
.alertCon .alertButt div{
width: 2.4rem;
height: .88rem;
line-height: .88rem;
text-align: center;
font-size: .32rem;
color: #666;
border-radius: .08rem;
background: #F6F6F6;
}
.alertCon .alertButt div.submit{
color: #fff;
background: #0068EE;
}
.alertCon .botTs{
color: #666;
font-size: .24rem;
text-align: center;
margin-top: .24rem;
}
.alertCon .choList .choLi{
display: flex;
align-items: center;
color: #333;
font-size: .28rem;
margin-bottom: .24rem;
}
.alertCon .choList .choLi img{
width: .28rem;
height: .28rem;
margin-right: .16rem;
}
/* 通用卡片样式 */
.business-info-section,
.business-detail-section,
.admin-notes-section,
.progress-section {
margin-bottom: 0.24rem;
}
.info-card,
.detail-card,
.notes-card,
.progress-card {
background: #FFFFFF;
border-radius: 0.16rem;
box-shadow: 0 0.02rem 0.08rem rgba(0, 0, 0, 0.04);
}
.section-title {
font-size: 0.32rem;
font-weight: 600;
color: #000;
margin-bottom: .4rem;
}
.info-card .section-title{
padding: .4rem .32rem;
margin-bottom: 0;
}
.info-content{
padding: 0 .32rem;
}
.subtitle {
font-size: 0.28rem;
font-weight: 500;
color: #333;
margin-bottom: 0.28rem;
margin-top: .4rem;
}
.info-row {
display: flex;
align-items: center;
margin-bottom: 0.24rem;
min-height: 0.56rem;
}
.info-label {
width: 1.42rem;
font-size: 0.28rem;
color: #666;
font-weight: 400;
}
.info-value {
flex: 1;
font-size: 0.28rem;
color: #333;
font-weight: 400;
}
.phone-container {
display: flex;
align-items: center;
gap: 0.24rem;
}
.copy-icon {
width: 0.28rem;
height: 0.28rem;
}
.type-container {
display: flex;
gap: 0.16rem;
flex: 1;
}
.type-tag {
font-size: 0.24rem;
color: #007AFF;
border-radius: .08rem;
border: 1px solid #B1D3FF;
background: #E5F0FD;
padding: .08rem .12rem;
}
.action-buttons {
display: flex;
border-top: 1px solid #F1F1F1;
}
.action-button {
width: 50%;
text-align: center;
color: #0068EE;
font-size: .32rem;
font-weight: 500;
height: .88rem;
line-height: .88rem;
}
.action-button.contact-marketer {
border-right: 1px solid #F1F1F1;
}
.detail-card{
padding: .4rem .32rem;
}
.voiceLi .up{
display: flex;
align-items: center;
justify-content: space-between;
margin-top: .28rem;
}
.voiceLi .left{
width: 4.38rem;
height: .72rem;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 .2rem;
border-radius: 1.28rem;
background: #0068EE;
color: #fff;
font-size: .24rem;
}
.voiceLi .left .pause{
width: .48rem;
height: .48rem;
}
.voiceLi .left .voice{
height: .32rem;
}
.voiceLi .right{
display: flex;
align-items: center;
color: #0068EE;
font-size: .24rem;
border-radius: .08rem;
background: #F7F8FA;
padding: .1rem .16rem;
}
.voiceLi .right img{
width: .32rem;
height: .32rem;
margin-right: .08rem;
}
.voiceLi .textDiv{
border-radius: .08rem;
background: #F7F8FA;
padding: .2rem;
color: #333;
font-size: .28rem;
margin-top: .2rem;
}
.text-content {
border-radius: 0.12rem;
}
.text-content p {
font-size: 0.28rem;
color: #333;
line-height: 0.40rem;
white-space: pre-wrap;
}
/* 管理员备注 */
.notes-card {
padding:.4rem .32rem;
}
.notes-content p {
font-size: 0.28rem;
color: #333;
line-height: 0.40rem;
}
/* 处理进度 */
.progress-card {
padding:.4rem .32rem;
}
.progress-content {
position: relative;
}
.progress-item {
margin-bottom: 0.44rem;
display: flex;
}
.progress-item:last-child{
margin-bottom: 0;
}
.progress-item .circle{
width: .32rem;
height: .32rem;
border-radius: 50%;
background: #E5F0FD;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
margin-right: .28rem;
}
.progress-item .circle div{
width: .12rem;
height: .12rem;
border-radius: 50%;
background: #0068EE;
}
.progress-item .cgray{
background: #DADADA;
}
.progress-item .cgray div{
background: #949494;
}
.progress-title {
font-size: 0.32rem;
font-weight: 600;
color: #333;
margin-bottom: 0.16rem;
line-height: 1;
}
.progress-time {
font-size: 0.28rem;
color: #666;
margin-bottom: 0.08rem;
}
.progress-desc {
font-size: 0.28rem;
color: #333;
line-height: 0.40rem;
}
.botButt{
display: flex;
border-top: 1px solid #F1F1F1;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background: #fff;
}
.botButt .bbli{
width: 33.33%;
display: flex;
align-items: center;
justify-content: center;
font-size: .34rem;
font-weight: 500;
padding: .32rem 0;
color: #0068EE;
border-right: 1px solid #F1F1F1;
}
.botButt .bbli:last-child{
color: #666;
border-right: none;
}
.botButt .bbli img{
width: .4rem;
height: .4rem;
margin-right: .08rem;
}
/* 动画效果 */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(0.20rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.status-section,
.business-info-section,
.business-detail-section,
.admin-notes-section,
.progress-section {
animation: fadeInUp 0.5s ease-out;
position: relative;
}
.status-section { animation-delay: 0.1s; }
.business-info-section { animation-delay: 0.2s; }
.business-detail-section { animation-delay: 0.3s; }
.admin-notes-section { animation-delay: 0.4s; }
.progress-section { animation-delay: 0.5s; }
/* 交互反馈 */
.action-button,
.play-button,
.voice-action-button,
.bottom-button,
.copy-icon,
.back-icon {
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
/* 焦点样式 */
.action-button:focus,
.play-button:focus,
.voice-action-button:focus,
.bottom-button:focus,
.copy-icon:focus,
.back-icon:focus {
outline: 0.04rem solid rgba(0, 122, 255, 0.3);
outline-offset: 0.04rem;
}
\ No newline at end of file \ No newline at end of file
html{
font-size: calc(100vw/7.5) ;
}
html,body{
width: 100%;
height: 100%;
}
body{
min-height: 100vh;
}
*{
margin: 0;
padding: 0;
font-family: 'Source Han Sans', sans-serif;
box-sizing: border-box;
}
div{
line-height: 1;
}
[v-cloak] {
display: none !important;
}
.login-contalogoiner {
height: 100vh;
display: flex;
flex-direction: column;
}
.header-section {
height: 4.6rem;
position: relative;
}
.background-image {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
.background-image .bg {
width: 100%;
height: 100%;
object-fit: cover;
}
.header-content {
position: absolute;
top: 1.76rem;
left: .42rem;
color: #333;
font-size: .32rem;
}
.logo {
width: .8rem;
height: .8rem;
display: block;
}
.header-content .hello{
width: 1.6rem;
height: .46rem;
margin: .28rem 0;
}
.header-content .name{
font-weight: bold;
}
.login-form {
background: white;
padding: .64rem .48rem;
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
}
.input-group {
display: flex;
align-items: center;
margin-bottom: .48rem;
border-radius: .16rem;
height: .96rem;;
background: #f9f9f9;
padding-left: .32rem;
font-size: .32rem;
}
.loginTs{
color: #999;
font-size: .26rem;
margin-top: .48rem;
text-align: center;
}
.loginTs span{
color: #0068EE;
}
.input-icon {
width: .4rem;
height: .4rem;
flex-shrink: 0;
margin-right: .16rem;
}
.input-field {
flex: 1;
border: none;
background: transparent;
font-size: .32rem;
outline: none;
}
.input-field::placeholder {
color: #999;
}
.verify-btn {
color: #0068EE;
font-size: .32rem;
margin-right: .4rem;
flex-shrink: 0;
}
.verify-btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.login-btn {
background: #007bff;
color: white;
border: none;
padding: .32rem;
border-radius: .16rem;
font-size: .32rem;
font-weight: bold;
cursor: pointer;
text-align: center;
outline: none !important;
box-shadow: none !important;
-webkit-tap-highlight-color: transparent;
-webkit-box-shadow: none !important;
-moz-box-shadow: none !important;
}
\ No newline at end of file \ No newline at end of file
/**
* 选择列表插件
* varstion 2.0.0
* by Houfeng
* Houfeng@DCloud.io
**/
.mui-pciker-list li,.mui-picker,.mui-picker-inner{box-sizing:border-box;overflow:hidden}.mui-picker{background-color:#ddd;position:relative;height:200px;border:1px solid rgba(0,0,0,.1);-webkit-user-select:none;user-select:none}.mui-dtpicker,.mui-poppicker{left:0;background-color:#eee;box-shadow:0 -5px 7px 0 rgba(0,0,0,.1);-webkit-transition:.3s;width:100%}.mui-picker-inner{position:relative;width:100%;height:100%;}.mui-pciker-list,.mui-pciker-rule{box-sizing:border-box;padding:0;margin:-18px 0 0;width:100%;height:36px;line-height:36px;position:absolute;left:0;top:50%}.mui-pciker-rule-bg{z-index:0}.mui-pciker-rule-ft{z-index:2;border-top:solid 1px rgba(0,0,0,.1);border-bottom:solid 1px rgba(0,0,0,.1)}.mui-pciker-list{z-index:1;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;-webkit-transform:perspective(750pt) rotateY(0) rotateX(0);transform:perspective(750pt) rotateY(0) rotateX(0)}.mui-pciker-list li{width:100%;height:100%;position:absolute;text-align:center;vertical-align:middle;-webkit-backface-visibility:hidden;backface-visibility:hidden;font-size:1pc;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;color:#888;padding:0 8px;white-space:nowrap;-webkit-text-overflow:ellipsis;text-overflow:ellipsis;cursor:default;visibility:hidden}.mui-pciker-list li.highlight,.mui-pciker-list li.visible{visibility:visible}.mui-pciker-list li.highlight{color:#222}.mui-poppicker{position:fixed;z-index:999;border-top:solid 1px #ccc;bottom:0;-webkit-transform:translateY(300px)}.mui-poppicker.mui-active{-webkit-transform:translateY(0)}.mui-android-5-1 .mui-poppicker{bottom:-300px;-webkit-transition-property:bottom;-webkit-transform:none}.mui-android-5-1 .mui-poppicker.mui-active{bottom:0;-webkit-transition-property:bottom;-webkit-transform:none}.mui-poppicker-header{padding:6px;font-size:14px;color:#888}.mui-poppicker-header .mui-btn{font-size:9pt;padding:5px 10px}.mui-poppicker-btn-cancel{float:left}.mui-poppicker-btn-ok{float:right}.mui-poppicker-clear{clear:both;height:0;line-height:0;font-size:0;overflow:hidden}.mui-poppicker-body{position:relative;width:100%;height:200px;border-top:solid 1px #ddd}.mui-poppicker-body .mui-picker{width:100%;height:100%;margin:0;border:none;float:left}.mui-dtpicker{position:fixed;z-index:999999;border-top:solid 1px #ccc;bottom:0;-webkit-transform:translateY(300px)}.mui-dtpicker.mui-active{-webkit-transform:translateY(0)}.mui-dtpicker-active-for-page{overflow:hidden!important}.mui-android-5-1 .mui-dtpicker{bottom:-300px;-webkit-transition-property:bottom;-webkit-transform:none}.mui-android-5-1 .mui-dtpicker.mui-active{bottom:0;-webkit-transition-property:bottom;-webkit-transform:none}.mui-dtpicker-header{padding:6px;font-size:14px;color:#888}.mui-dtpicker-header button{font-size:9pt;padding:5px 10px}.mui-dtpicker-header button:last-child{float:right}.mui-dtpicker-body{position:relative;width:100%;height:200px}.mui-ios .mui-dtpicker-body{-webkit-perspective:75pc;perspective:75pc;-webkit-transform-style:preserve-3d;transform-style:preserve-3d}.mui-dtpicker-title h5{display:inline-block;width:20%;margin:0;padding:8px;text-align:center;border-top:solid 1px #ddd;background-color:#f0f0f0;border-bottom:solid 1px #ccc}[data-type=hour] [data-id=title-i],[data-type=hour] [data-id=picker-i],[data-type=month] [data-id=title-i],[data-type=month] [data-id=picker-d],[data-type=month] [data-id=title-d],[data-type=month] [data-id=picker-h],[data-type=month] [data-id=title-h],[data-type=month] [data-id=picker-i],[data-type=time] [data-id=picker-y],[data-type=time] [data-id=picker-m],[data-type=time] [data-id=picker-d],[data-type=time] [data-id=title-y],[data-type=time] [data-id=title-m],[data-type=time] [data-id=title-d],[data-type=date] [data-id=title-i],[data-type=date] [data-id=picker-h],[data-type=date] [data-id=title-h],[data-type=date] [data-id=picker-i]{display:none}.mui-dtpicker .mui-picker{width:20%;height:100%;margin:0;float:left;border:none}[data-type=hour] [data-id=picker-h],[data-type=hour] [data-id=title-h],[data-type=datetime] [data-id=picker-h],[data-type=datetime] [data-id=title-h]{border-left:dotted 1px #ccc}[data-type=datetime] .mui-picker,[data-type=time] .mui-dtpicker-title h5{width:20%}[data-type=date] .mui-dtpicker-title h5,[data-type=date] .mui-picker{width:33.3%}[data-type=hour] .mui-dtpicker-title h5,[data-type=hour] .mui-picker{width:25%}[data-type=month] .mui-dtpicker-title h5,[data-type=month] .mui-picker,[data-type=time] .mui-dtpicker-title h5,[data-type=time] .mui-picker{width:50%}
\ No newline at end of file \ No newline at end of file
html {
font-size: calc(100vw/7.5);
}
[v-cloak] {
display: none !important;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
outline: none;
}
body {
font-family: 'Source Han Sans CN', -apple-system, BlinkMacSystemFont, sans-serif;
background-color: #f7f8fa;
min-height: 100vh;
overflow-x: hidden;
}
.app-container {
background-color: #f7f8fa;
margin: 0 auto;
position: relative;
padding-bottom: 1.31rem; /* 为底部导航预留空间 */
}
/* 搜索区域 */
.search-section {
background-color: white;
padding: 0.2rem;
border-bottom: 1px solid #f1f1f1;
}
.search-bar {
background-color: #f7f8fa;
height: 0.96rem;
border-radius: 0.16rem;
position: relative;
display: flex;
align-items: center;
}
.search-input-container {
display: flex;
align-items: center;
gap: 0.24rem;
width: 100%;
padding: 0 0.32rem;
}
.search-icon {
width: 0.32rem;
height: 0.32rem;
flex-shrink: 0;
}
.search-input {
flex: 1;
background: transparent;
border: none;
outline: none;
font-family: 'Source Han Sans CN', sans-serif;
font-size: 0.28rem;
color: #999999;
}
.search-input::placeholder {
color: #999999;
}
/* 标签页统计 */
.tab-stats {
background-color: white;
position: relative;
height: 1.12rem;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 0.32rem;
}
.active-indicator {
position: absolute;
bottom: 0;
background-color: #0068ee;
height: 0.08rem;
border-radius: 0.04rem;
transition: all 0.3s ease;
}
.tab-texts {
display: flex;
gap: 0.64rem;
align-items: center;
}
.tab-text {
font-family: 'Source Han Sans CN', sans-serif;
font-size: 0.28rem;
color: #666666;
cursor: pointer;
transition: all 0.3s ease;
white-space: nowrap;
}
.tab-text.active {
font-weight: bold;
font-size: 0.32rem;
color: #333333;
}
/* 筛选标签 */
.filter-tags {
display: flex;
padding: 0.32rem;
}
.filter-tag {
background-color: #EDEEF0;
border-radius: 0.08rem;
padding: 0.12rem 0.2rem;
font-size: 0.28rem;
color: #666666;
margin-right: .16rem;
}
.filter-tag.active {
background-color: #fff;
color: #0068EE;
}
/* 商机列表 */
.business-list {
padding: 0 0.32rem;
height: calc(100vh - 5.2rem);
overflow: auto;
}
.business-card {
background-color: white;
border-radius: 0.16rem;
margin-bottom: 0.24rem;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
cursor: pointer;
transition: transform 0.2s ease;
}
.business-card:active {
transform: scale(0.98);
}
/* 卡片头部 */
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.24rem;
border-bottom: 1px solid #F1F1F1;
}
.business-number {
font-family: 'Source Han Sans CN', sans-serif;
font-size: 0.28rem;
color: #666666;
font-weight: 500;
}
.status-badge {
padding: 0.08rem 0.16rem;
border-radius: 0.08rem;
font-family: 'Source Han Sans CN', sans-serif;
font-size: 0.22rem;
font-weight: 500;
}
.status-badge.follow-up {
background-color: #f0e6ff;
color: #8b5cf6;
}
.status-badge.pending {
background-color: #e6f3ff;
color: #3b82f6;
}
.status-badge.completed {
background-color: #dcfce7;
color: #16a34a;
}
.status-badge.closed {
background-color: #f3f4f6;
color: #6b7280;
}
/* 商机详情 */
.business-details {
padding: 0 0.32rem 0.24rem;
}
.detail-row {
display: flex;
align-items: center;
margin-top: 0.24rem;
}
.detail-icon {
width: 0.32rem;
height: 0.32rem;
flex-shrink: 0;
margin-right: 0.16rem;
}
.detail-icon img {
width: 100%;
height: 100%;
object-fit: contain;
}
.detail-text {
flex: 1;
font-family: 'Source Han Sans CN', sans-serif;
font-size: 0.26rem;
color: #333333;
line-height: 0.4rem;
}
/* 商机标签 */
.business-tags {
display: flex;
gap: 0.16rem;
}
.business-tag {
background-color: #eff6ff;
border: 1px solid #bfdbfe;
border-radius: 0.08rem;
padding: 0.08rem 0.16rem;
font-family: 'Source Han Sans CN', sans-serif;
font-size: 0.22rem;
color: #1d4ed8;
}
/* 处理人信息 */
.processor-info {
background-color: #f8fafc;
border-radius: 0.08rem;
padding: 0.16rem 0.24rem;
margin: 0.16rem 0;
font-family: 'Source Han Sans CN', sans-serif;
font-size: 0.24rem;
color: #333333;
}
/* 时间信息 */
.time-info {
display: flex;
justify-content: space-between;
font-size: 0.24rem;
color: #999999;
}
.submit-time, .update-time {
flex: 1;
}
/* 底部导航 */
.bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: white;
border-top: 1px solid #f1f1f1;
height: 1.31rem;
display: flex;
justify-content: space-around;
align-items: center;
z-index: 1000;
}
.bottom-nav .nav-item {
display: flex;
align-items: center;
cursor: pointer;
transition: opacity 0.3s ease;
padding: 0.24rem 0;
}
.bottom-nav .nav-icon {
width: 0.48rem;
height: 0.48rem;
margin-right: .16rem;
}
.bottom-nav .nav-text {
font-family: 'Source Han Sans CN', sans-serif;
font-size: 0.32rem;
color: #999999;
text-align: center;
white-space: nowrap;
}
.bottom-nav .nav-item.active .nav-text {
color: #0068ee;
font-weight: 500;
}
/* 响应式适配 */
@media (min-width: 768px) {
.app-container {
max-width: 7.5rem;
margin: 0 auto;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
}
.bottom-nav {
max-width: 7.5rem;
left: 50%;
transform: translateX(-50%);
}
}
\ No newline at end of file \ No newline at end of file
html{
font-size: calc(100vw/7.5) ;
}
html,body{
width: 100%;
height: 100%;
}
*{
margin: 0;
padding: 0;
}
div{
line-height: 1;
box-sizing: border-box;
}
.pageDiv{
width: 100%;
height: 100%;
padding-top: .52rem;
background: #F7F8FA;
padding: .24rem;
padding-bottom: 1.2rem;
display: none;
}
.infoTop{
background: #fff;
border-radius: .08rem;
background: #FFF;
padding: .48rem .32rem;
margin-bottom: .48rem;
padding-top: .8rem;
}
.infoTop .icon{
width: 1.28rem;
height: 1.28rem;
display: block;
margin: auto;
}
.infoTop .status{
color: #333;
font-weight: bold;
font-size: .4rem;
margin-bottom: .8rem;
text-align: center;
margin-top: .32rem;
}
.infoTop .detail .detli{
display: flex;
color: #333;
font-size: .32rem;
margin-bottom: .28rem;
justify-content: space-between;
}
.infoTop .detail .detli .detL{
color: #666;
margin-right: .4rem;
width: 1.5rem;
flex-shrink: 0;
}
.infoTop .detail .detli .detR{
text-align: right;
line-height: 1.2;
}
.botButt{
width: 3.9rem;
height: .88rem;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
border-radius: .08rem;
background: #0068EE;
font-weight: 500;
font-size: .34rem;
z-index: 9;
margin: auto;
}
.noBusi{
color: #0068EE;
background: #fff;
margin-top: .32rem;
}
\ No newline at end of file \ No newline at end of file
html{
font-size: calc(100vw/7.5) ;
}
html,body{
width: 100%;
height: 100%;
}
*{
margin: 0;
padding: 0;
}
div{
line-height: 1;
box-sizing: border-box;
}
.pageDiv{
width: 100%;
height: 100%;
padding-top: .52rem;
background: #F7F8FA;
padding: .24rem;
padding-bottom: 1.2rem;
display: none;
}
.infoTop{
background: #fff;
border-radius: .08rem;
background: #FFF;
padding: .48rem .32rem;
margin-bottom: .48rem;
padding-top: .8rem;
}
.infoTop .icon{
width: 1.28rem;
height: 1.28rem;
display: block;
margin: auto;
}
.infoTop .status{
color: #333;
font-weight: bold;
font-size: .4rem;
margin-bottom: .8rem;
text-align: center;
margin-top: .32rem;
}
.botButt{
width: 3.38rem;
height: .88rem;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
border-radius: .08rem;
background: #568FFE;
font-weight: 500;
font-size: .34rem;
z-index: 9;
margin: auto;
}
\ No newline at end of file \ No newline at end of file
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 10 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="&#232;&#183;&#175;&#229;&#190;&#132;" d="M2.58665 9.168L9.68704 1.87825C10.1055 1.44785 10.1055 0.751297 9.68704 0.322279C9.26856 -0.107426 8.5896 -0.107426 8.17113 0.322279L0.313387 8.39018C-0.104462 8.81989 -0.104462 9.5168 0.313387 9.94515L8.17113 18.0127C8.38101 18.2276 8.65534 18.335 8.92971 18.335C9.2041 18.335 9.47842 18.2276 9.68704 18.012C10.1055 17.5823 10.1055 16.8868 9.68704 16.4571L2.58665 9.168" fill="var(--fill-0, #111111)"/>
</svg>
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="icon_&#230;&#144;&#156;&#231;&#180;&#162;">
<g id="Rectangle 51">
</g>
<path id="&#229;&#189;&#162;&#231;&#138;&#182;" fill-rule="evenodd" clip-rule="evenodd" d="M15.3017 14.3538L12.7982 11.85L12.7978 11.8504C13.7991 10.6329 14.3792 9.08416 14.3792 7.44042C14.3792 3.60708 11.2723 0.5 7.43959 0.5C5.59943 0.500439 3.83475 1.23151 2.53345 2.53251C-0.177498 5.24284 -0.177864 9.63753 2.53263 12.3483C5.05896 14.8749 9.09096 15.0704 11.8498 12.8L14.3525 15.3029C14.4786 15.4285 14.6491 15.4994 14.8271 15.5C15.0051 15.4994 15.1756 15.4285 15.3017 15.3029L15.3075 15.2971C15.5664 15.035 15.5638 14.6127 15.3017 14.3538ZM1.84256 7.44075C1.84757 4.35158 4.35021 1.84867 7.43955 1.84367C10.5306 1.84367 13.0361 4.3495 13.0361 7.44075C13.0361 10.5316 10.5306 13.0378 7.43955 13.0378C4.34854 13.0378 1.84256 10.5316 1.84256 7.44075Z" fill="var(--fill-0, #222222)"/>
</g>
</svg>
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="fanhui">
<path id="&#232;&#191;&#148;&#229;&#155;&#158;" d="M7.58665 10.0005L14.687 2.71076C15.1055 2.28037 15.1055 1.58382 14.687 1.1548C14.2686 0.725093 13.5896 0.725093 13.1711 1.1548L5.31339 9.2227C4.89554 9.65241 4.89554 10.3493 5.31339 10.7777L13.1711 18.8452C13.381 19.0601 13.6553 19.1675 13.9297 19.1675C14.2041 19.1675 14.4784 19.0601 14.687 18.8446C15.1055 18.4149 15.1055 17.7193 14.687 17.2896L7.58665 10.0005" fill="var(--fill-0, #111111)"/>
</g>
</svg>
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 219 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Notch">
<path id="Notch_2" d="M0 0H219C216.894 0.602689 215.418 2.49525 215.345 4.68446L215.317 5.5618C215.317 19.0587 204.375 30 190.878 30H28.1217C14.6249 30 3.68349 19.0587 3.68349 5.5618L3.65455 4.68447C3.58233 2.49525 2.10586 0.602689 0 0Z" fill="var(--fill-0, black)"/>
</g>
</svg>
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="quanbuyewu 1">
<g id="Vector">
<path d="M7.02598 9.66406H3.16543C2.0957 9.66406 1.22852 8.79687 1.22852 7.72715V4.29355C1.22852 2.98809 2.28691 1.92969 3.59238 1.92969H7.02598C8.0957 1.92969 8.96289 2.79687 8.96289 3.8666V7.72715C8.96289 8.79687 8.0957 9.66406 7.02598 9.66406ZM7.02598 18.7414H3.16543C2.0957 18.7414 1.22852 17.8742 1.22852 16.8045V13.3709C1.22852 12.0654 2.28691 11.007 3.59238 11.007H7.02598C8.0957 11.007 8.96289 11.8742 8.96289 12.9439V16.8045C8.96289 17.8742 8.0957 18.7414 7.02598 18.7414ZM16.1172 18.7414H12.2568C11.1871 18.7414 10.3199 17.8742 10.3199 16.8045V13.3709C10.3199 12.0654 11.3783 11.007 12.6838 11.007H16.1174C17.1871 11.007 18.0543 11.8742 18.0543 12.9439V16.8045C18.0541 17.8742 17.1869 18.7414 16.1172 18.7414ZM12.8174 9.75781L10.1377 7.07812C9.38125 6.32168 9.38125 5.09531 10.1377 4.33887L12.5154 1.96113C13.4385 1.03809 14.9354 1.03809 15.8584 1.96113L18.2361 4.33887C18.9926 5.09531 18.9926 6.32168 18.2361 7.07812L15.5566 9.75781C14.8002 10.5141 13.5738 10.5141 12.8174 9.75781Z" fill="var(--fill-0, #B4B4B4)"/>
<path d="M7.02598 9.66406H3.16543C2.0957 9.66406 1.22852 8.79687 1.22852 7.72715V4.29355C1.22852 2.98809 2.28691 1.92969 3.59238 1.92969H7.02598C8.0957 1.92969 8.96289 2.79687 8.96289 3.8666V7.72715C8.96289 8.79687 8.0957 9.66406 7.02598 9.66406ZM7.02598 18.7414H3.16543C2.0957 18.7414 1.22852 17.8742 1.22852 16.8045V13.3709C1.22852 12.0654 2.28691 11.007 3.59238 11.007H7.02598C8.0957 11.007 8.96289 11.8742 8.96289 12.9439V16.8045C8.96289 17.8742 8.0957 18.7414 7.02598 18.7414ZM16.1172 18.7414H12.2568C11.1871 18.7414 10.3199 17.8742 10.3199 16.8045V13.3709C10.3199 12.0654 11.3783 11.007 12.6838 11.007H16.1174C17.1871 11.007 18.0543 11.8742 18.0543 12.9439V16.8045C18.0541 17.8742 17.1869 18.7414 16.1172 18.7414ZM12.8174 9.75781L10.1377 7.07812C9.38125 6.32168 9.38125 5.09531 10.1377 4.33887L12.5154 1.96113C13.4385 1.03809 14.9354 1.03809 15.8584 1.96113L18.2361 4.33887C18.9926 5.09531 18.9926 6.32168 18.2361 7.07812L15.5566 9.75781C14.8002 10.5141 13.5738 10.5141 12.8174 9.75781Z" fill="url(#paint0_linear_388_662)"/>
</g>
</g>
<defs>
<linearGradient id="paint0_linear_388_662" x1="1.22852" y1="1.26885" x2="18.7008" y2="18.8435" gradientUnits="userSpaceOnUse">
<stop stop-color="#478FFF"/>
<stop offset="1" stop-color="#0062FF"/>
</linearGradient>
</defs>
</svg>
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon">
<path id="Vector" d="M8 12.6667V14.6667" stroke="var(--stroke-0, #0068EE)" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path id="Vector_2" d="M12.6667 6.66667V8C12.6667 9.23768 12.175 10.4247 11.2998 11.2998C10.4247 12.175 9.23768 12.6667 8 12.6667C6.76232 12.6667 5.57534 12.175 4.70017 11.2998C3.825 10.4247 3.33333 9.23768 3.33333 8V6.66667" stroke="var(--stroke-0, #0068EE)" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path id="Vector_3" d="M10 3.33333C10 2.22876 9.10457 1.33333 8 1.33333C6.89543 1.33333 6 2.22876 6 3.33333V8C6 9.10457 6.89543 10 8 10C9.10457 10 10 9.10457 10 8V3.33333Z" fill="var(--fill-0, #0068EE)" stroke="var(--stroke-0, #0068EE)" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="qiweishoujibiao 1">
<g id="Vector">
<path d="M14.5895 1.3043C15.3009 1.3043 15.8777 1.8783 15.8777 2.5866V17.4132C15.8777 18.1214 16.4545 18.6956 17.166 18.6956H2.5926C1.8811 18.6956 1.3044 18.1214 1.3044 17.4132V2.5866C1.3044 1.8784 1.8811 1.3043 2.5926 1.3043H14.5895Z" fill="var(--fill-0, #B4B4B4)"/>
<path d="M14.5895 1.3043C15.3009 1.3043 15.8777 1.8783 15.8777 2.5866V17.4132C15.8777 18.1214 16.4545 18.6956 17.166 18.6956H2.5926C1.8811 18.6956 1.3044 18.1214 1.3044 17.4132V2.5866C1.3044 1.8784 1.8811 1.3043 2.5926 1.3043H14.5895Z" fill="var(--fill-1, #B4B4B4)"/>
</g>
<path id="Vector_2" d="M15.8776 5.632H17.4134C18.1216 5.632 18.6957 6.206 18.6957 6.9142V17.4131C18.6957 18.1213 18.1216 18.6954 17.4134 18.6954H17.1599C16.4517 18.6954 15.8776 18.1213 15.8776 17.4131V5.6319V5.632Z" fill="var(--fill-0, #A9A9A9)"/>
<path id="Vector_3" d="M4.78256 10.7065L6.08696 9.40218L7.82606 11.1413L11.5217 7.44568L12.8261 8.74998L7.82606 13.75L4.78256 10.7065Z" fill="var(--fill-0, white)"/>
</g>
</svg>
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 67 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Right Side">
<g id="Battery">
<path id="Rectangle" opacity="0.35" d="M45.0002 0.502686H61.6662C62.8628 0.502686 63.8332 1.47306 63.8332 2.66968V8.66968C63.833 9.86615 62.8627 10.8357 61.6662 10.8357H45.0002C43.8037 10.8357 42.8334 9.86615 42.8332 8.66968V2.66968C42.8332 1.47306 43.8036 0.502686 45.0002 0.502686Z" stroke="var(--stroke-0, #333333)"/>
<path id="Combined Shape" opacity="0.4" d="M65.3332 3.66935V7.66935C66.1379 7.33058 66.6612 6.54249 66.6612 5.66935C66.6612 4.79622 66.1379 4.00813 65.3332 3.66935" fill="var(--fill-0, #333333)"/>
<path id="Rectangle_2" d="M44.3332 3.10269C44.3332 2.49517 44.8257 2.00269 45.4332 2.00269H61.2332C61.8407 2.00269 62.3332 2.49517 62.3332 3.10269V8.23602C62.3332 8.84353 61.8407 9.33602 61.2332 9.33602H45.4332C44.8257 9.33602 44.3332 8.84353 44.3332 8.23602V3.10269Z" fill="var(--fill-0, #333333)"/>
</g>
<path id="Wifi" fill-rule="evenodd" clip-rule="evenodd" d="M29.6637 2.27733C31.8796 2.27742 34.0107 3.12886 35.6167 4.65566C35.7376 4.77354 35.9309 4.77205 36.05 4.65233L37.206 3.48566C37.2663 3.42494 37.2999 3.34269 37.2994 3.25711C37.2989 3.17153 37.2644 3.08967 37.2033 3.02966C32.9882 -1.00989 26.3384 -1.00989 22.1233 3.02966C22.0623 3.08963 22.0276 3.17146 22.0271 3.25704C22.0265 3.34262 22.0601 3.42489 22.1203 3.48566L23.2767 4.65233C23.3957 4.77223 23.5891 4.77372 23.71 4.65566C25.3162 3.12876 27.4476 2.27732 29.6637 2.27733ZM29.6637 6.07299C30.8812 6.07292 32.0552 6.52545 32.9577 7.34266C33.0797 7.45864 33.272 7.45613 33.391 7.33699L34.5457 6.17033C34.6065 6.10913 34.6402 6.02612 34.6393 5.93985C34.6385 5.85359 34.603 5.77127 34.541 5.71133C31.7928 3.15494 27.5369 3.15494 24.7887 5.71133C24.7266 5.77127 24.6912 5.85363 24.6904 5.93992C24.6895 6.02621 24.7234 6.10922 24.7843 6.17033L25.9387 7.33699C26.0577 7.45613 26.2499 7.45864 26.372 7.34266C27.2739 6.52599 28.447 6.0735 29.6637 6.07299ZM31.9767 8.62681C31.9784 8.71332 31.9444 8.79672 31.8827 8.85733L29.8853 10.873C29.8268 10.9322 29.747 10.9656 29.6637 10.9656C29.5804 10.9656 29.5006 10.9322 29.442 10.873L27.4443 8.85733C27.3826 8.79668 27.3487 8.71325 27.3505 8.62674C27.3523 8.54023 27.3898 8.45831 27.454 8.40033C28.7296 7.32144 30.5978 7.32144 31.8733 8.40033C31.9375 8.45836 31.9749 8.54031 31.9767 8.62681Z" fill="var(--fill-0, #333333)"/>
<path id="Mobile Signal" fill-rule="evenodd" clip-rule="evenodd" d="M16 0.335938H15C14.4477 0.335938 14 0.783653 14 1.33594V10.0026C14 10.5549 14.4477 11.0026 15 11.0026H16C16.5523 11.0026 17 10.5549 17 10.0026V1.33594C17 0.783653 16.5523 0.335938 16 0.335938ZM10.3333 2.66927H11.3333C11.8856 2.66927 12.3333 3.11699 12.3333 3.66927V10.0026C12.3333 10.5549 11.8856 11.0026 11.3333 11.0026H10.3333C9.78105 11.0026 9.33333 10.5549 9.33333 10.0026V3.66927C9.33333 3.11699 9.78105 2.66927 10.3333 2.66927ZM6.66667 5.0026H5.66667C5.11438 5.0026 4.66667 5.45032 4.66667 6.0026V10.0026C4.66667 10.5549 5.11438 11.0026 5.66667 11.0026H6.66667C7.21895 11.0026 7.66667 10.5549 7.66667 10.0026V6.0026C7.66667 5.45032 7.21895 5.0026 6.66667 5.0026ZM2 7.0026H1C0.447715 7.0026 0 7.45032 0 8.0026V10.0026C0 10.5549 0.447715 11.0026 1 11.0026H2C2.55228 11.0026 3 10.5549 3 10.0026V8.0026C3 7.45032 2.55228 7.0026 2 7.0026Z" fill="var(--fill-0, #333333)"/>
</g>
</svg>
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 351 1" fill="none" xmlns="http://www.w3.org/2000/svg">
<line id="Line 2" y1="0.5" x2="351" y2="0.5" stroke="var(--stroke-0, #F1F1F1)"/>
</svg>
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon-weizhi">
<path id="Vector" d="M13.6001 6.60007C13.6001 10.0952 9.72272 13.7353 8.42071 14.8595C8.29941 14.9507 8.15176 15 8 15C7.84824 15 7.70059 14.9507 7.5793 14.8595C6.27728 13.7353 2.39993 10.0952 2.39993 6.60007C2.39993 5.11484 2.98994 3.69044 4.04016 2.64022C5.09037 1.59001 6.51477 1 8 1C9.48523 1 10.9096 1.59001 11.9599 2.64022C13.0101 3.69044 13.6001 5.11484 13.6001 6.60007Z" stroke="var(--stroke-0, #999999)" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path id="Vector_2" d="M8 8.5C9.10457 8.5 10 7.60457 10 6.5C10 5.39543 9.10457 4.5 8 4.5C6.89543 4.5 6 5.39543 6 6.5C6 7.60457 6.89543 8.5 8 8.5Z" stroke="var(--stroke-0, #999999)" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="icon_&#230;&#144;&#156;&#231;&#180;&#162;">
<g id="Rectangle 51">
</g>
<path id="&#229;&#189;&#162;&#231;&#138;&#182;" fill-rule="evenodd" clip-rule="evenodd" d="M15.3017 14.3538L12.7982 11.85L12.7978 11.8504C13.7991 10.6329 14.3792 9.08416 14.3792 7.44042C14.3792 3.60708 11.2723 0.5 7.43959 0.5C5.59943 0.500439 3.83475 1.23151 2.53345 2.53251C-0.177498 5.24284 -0.177864 9.63753 2.53263 12.3483C5.05896 14.8749 9.09096 15.0704 11.8498 12.8L14.3525 15.3029C14.4786 15.4285 14.6491 15.4994 14.8271 15.5C15.0051 15.4994 15.1756 15.4285 15.3017 15.3029L15.3075 15.2971C15.5664 15.035 15.5638 14.6127 15.3017 14.3538ZM1.84256 7.44075C1.84757 4.35158 4.35021 1.84867 7.43955 1.84367C10.5306 1.84367 13.0361 4.3495 13.0361 7.44075C13.0361 10.5316 10.5306 13.0378 7.43955 13.0378C4.34854 13.0378 1.84256 10.5316 1.84256 7.44075Z" fill="var(--fill-0, #222222)"/>
</g>
</svg>
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.5 8.5C2.5 9.60457 3.39543 10.5 4.5 10.5C5.60457 10.5 6.5 9.60457 6.5 8.5C6.5 7.39543 5.60457 6.5 4.5 6.5C3.39543 6.5 2.5 7.39543 2.5 8.5Z" stroke="#666666" stroke-width="1"/>
<path d="M10.5 8.5C10.5 9.60457 11.3954 10.5 12.5 10.5C13.6046 10.5 14.5 9.60457 14.5 8.5C14.5 7.39543 13.6046 6.5 12.5 6.5C11.3954 6.5 10.5 7.39543 10.5 8.5Z" stroke="#666666" stroke-width="1"/>
<path d="M7.5 12.5C7.5 13.6046 8.39543 14.5 9.5 14.5C10.6046 14.5 11.5 13.6046 11.5 12.5C11.5 11.3954 10.6046 10.5 9.5 10.5C8.39543 10.5 7.5 11.3954 7.5 12.5Z" stroke="#666666" stroke-width="1"/>
</svg>
\ No newline at end of file \ No newline at end of file
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon-biaoqian" clip-path="url(#clip0_388_665)">
<path id="Vector" d="M8.39067 1.724C8.14068 1.47393 7.80159 1.33341 7.448 1.33333H2.66667C2.31304 1.33333 1.97391 1.47381 1.72386 1.72386C1.47381 1.97391 1.33333 2.31304 1.33333 2.66667V7.448C1.33341 7.80159 1.47393 8.14068 1.724 8.39067L7.52667 14.1933C7.82968 14.4944 8.2395 14.6634 8.66667 14.6634C9.09384 14.6634 9.50366 14.4944 9.80667 14.1933L14.1933 9.80667C14.4944 9.50366 14.6634 9.09384 14.6634 8.66667C14.6634 8.2395 14.4944 7.82968 14.1933 7.52667L8.39067 1.724Z" stroke="var(--stroke-0, #999999)" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path id="Vector_2" d="M5 5.33333C5.18409 5.33333 5.33333 5.18409 5.33333 5C5.33333 4.8159 5.18409 4.66667 5 4.66667C4.8159 4.66667 4.66667 4.8159 4.66667 5C4.66667 5.18409 4.8159 5.33333 5 5.33333Z" fill="var(--fill-0, #999999)" stroke="var(--stroke-0, #999999)" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_388_665">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="yonghuming 1">
<path id="Vector" d="M9.92031 7.35938H6.07969C3.60938 7.35938 1.6 9.37031 1.6 11.8406V13.7609C1.6 14.8203 2.46094 15.6812 3.52031 15.6812H12.4797C13.5391 15.6812 14.4 14.8203 14.4 13.7609V11.8406C14.4 9.37031 12.3906 7.35938 9.92031 7.35938ZM13.1203 13.7594C13.1203 14.1125 12.8328 14.4 12.4797 14.4H3.52031C3.16719 14.4 2.87969 14.1125 2.87969 13.7594V11.8406C2.87969 10.0766 4.31563 8.64062 6.07969 8.64062H9.92031C11.6844 8.64062 13.1203 10.0766 13.1203 11.8406V13.7594ZM8 6.72031C9.76719 6.72031 11.2 5.2875 11.2 3.52031C11.2 1.75313 9.76719 0.320312 8 0.320312C6.23281 0.320312 4.8 1.75313 4.8 3.52031C4.8 5.2875 6.23281 6.72031 8 6.72031ZM8 1.6C9.05938 1.6 9.92031 2.46094 9.92031 3.52031C9.92031 4.57969 9.05938 5.44063 8 5.44063C6.94063 5.44063 6.07969 4.57812 6.07969 3.52031C6.07969 2.4625 6.94063 1.6 8 1.6Z" fill="var(--fill-0, #999999)"/>
</g>
</svg>
const util = new window.publicMethod() ;
new Vue({
el: '#app',
data: {
loginForm: {
employeeId: '',
phoneNumber: '',
verifyCode: ''
},
countdown: 0,
countdownTimer: null,
clickFlag: false
},
methods: {
getVerifyCode() {
let pa = this.loginForm
if (!pa.employeeId) {
util.toast('请输入工号');
return;
}
if (!pa.phoneNumber) {
util.toast('请输入手机号');
return;
}
if (!/^1[3-9]\d{9}$/.test(pa.phoneNumber)) {
util.toast('请输入正确的手机号');
return;
}
if(this.clickFlag){
return
}
this.clickFlag = true
util.httpRequest({
url: '/mobile/sendSms',
middleUrl: '/zhijian-trial/api',
data: {
campaignId: pa.employeeId,
phone: pa.phoneNumber,
},
}).then(res=>{
if(res.code == 200){
util.toast('验证码已发送,请注意查收')
// 开始倒计时
let __this = this
this.countdown = 60;
this.countdownTimer = setInterval(() => {
this.countdown--;
if (this.countdown <= 0) {
clearInterval(this.countdownTimer);
__this.clickFlag = false
}
}, 1000);
}else{
this.clickFlag = false
util.toast(res.msg || '获取失败')
}
}).catch(()=>{
this.clickFlag = false
})
},
login() {
let pa = this.loginForm
// 验证表单
if (!pa.employeeId) {
util.toast('请输入工号');
return;
}
if (!pa.phoneNumber) {
util.toast('请输入手机号');
return;
}
// 验证手机号格式
if (!/^1[3-9]\d{9}$/.test(pa.phoneNumber)) {
util.toast('请输入正确的手机号');
return;
}
if (!pa.verifyCode) {
util.toast('请输入验证码');
return;
}
// 验证验证码格式(假设6位数字)
if (!/^\d{6}$/.test(pa.verifyCode)) {
util.toast('请输入6位验证码');
return;
}
util.httpRequest({
url: '/mobile/login',
middleUrl: '/zhijian-trial/api',
data: {
campaignId: pa.employeeId,
phone: pa.phoneNumber,
smsCode: pa.verifyCode
},
}).then(res=>{
if(res.code == 200){
}else{
util.toast(res.msg || '登录失败')
}
})
}
},
created(){
},
beforeDestroy() {
// 清除定时器
if (this.countdownTimer) {
clearInterval(this.countdownTimer);
}
}
});
\ No newline at end of file \ No newline at end of file
This diff could not be displayed because it is too large.
(function($$) {
"use strict";
let vm
function init() {
vm = new Vue({
el: '#pageDiv',
data: {
info: {}
},
created: function() {
this.info = {
campaignId: '1334567687633555',
accNbr: '13466789999',
address: '江苏省淮安市幸福街道流星家园小区1号楼一单元202'
}
},
methods: {
goBack(){
history.go(-1)
}
}
})
}
init()
window.vm = vm
})(window.jQuery)
window.addEventListener('load', function () {
document.getElementById('pageDiv').style.display = 'block'; // 显示内容
});
\ No newline at end of file \ No newline at end of file
(function($$) {
"use strict";
let vm
function init() {
vm = new Vue({
el: '#pageDiv',
data: {
info: {}
},
created: function() {
this.info = JSON.parse(sessionStorage.getItem('huaiAnAppParam'))
},
methods: {
goBack(){
history.go(-1)
}
}
})
}
init()
window.vm = vm
})(window.jQuery)
window.addEventListener('load', function () {
document.getElementById('pageDiv').style.display = 'block'; // 显示内容
});
\ No newline at end of file \ No newline at end of file
(function(){
'use strict';
let utils=function(){}
let blackId = ''
let origin = window.location.origin
if(origin.includes('localhost')>0 || origin=='null' || origin.includes('file:')>0){
origin = 'https://testznzl.lgyzpt.com'
}
utils.prototype={
/**
* 获取地址栏后边的参数
*/
getUrlParam:function(m) {
let reg = new RegExp('(^|&)' + m + '=([^&]*)(&|$)')
let r = window.location.href.split('?')
r = r.length === 1 ? null : r[1].match(reg)
if (r == null) return ''
return decodeURIComponent(r[2]).replace(/(#|wechat_redirect|\/[0-9a-zA-Z]*)*/g, '')
},
/**
* 判断设备是Android还是iPhone
*/
androidOrIphone: function () {
var system = window.navigator.userAgent.toLowerCase();
if (system.indexOf("iphone") >= 0 || system.indexOf("ipad") >= 0 || system.indexOf("mac") >= 0 || system.indexOf("ipod") >= 0) {
return false;
}
return true;
},
/**
* 黑框提示
*/
toast: function (str) {
if (document.getElementById("blackDiv")) {
document.getElementById("blackSpan").innerHTML = str;
} else {
var blackDiv = document.createElement("div");
blackDiv.id = "blackDiv";
blackDiv.className = "blackts";
blackDiv.style.position = "fixed";
blackDiv.style.left = "0";
blackDiv.style.bottom = "20%";
blackDiv.style.width = "100%";
blackDiv.style.textAlign = "center";
blackDiv.style.zIndex = "999999";
blackDiv.style.display = 'flex'
blackDiv.style.justifyContent = 'center'
var html = '<div id="blackSpan" style="background: rgba(42,45,50,.94);color: white;';
html += 'border-radius: .1rem;font-size: 0.32rem;padding: .2rem .6rem;max-width: 80%;line-height: 1.5;">';
html += str + '</div>';
blackDiv.innerHTML = html;
document.getElementsByTagName("body")[0].appendChild(blackDiv);
}
if (blackId && blackId != "") {
clearTimeout(blackId);
}
blackId = setTimeout(function () {
if (document.getElementById("blackDiv")) {
document.getElementsByTagName("body")[0].removeChild(document.getElementById("blackDiv"));
}
}, 3000);
},
/**
* zhijian/ha
*
*/
httpRequest:function(param){
let middle = param.middleUrl || '/zhijian-trial/ha'
let pa = localStorage.getItem('appLoginInfo')
if(pa){
pa = JSON.parse(pa)
}else{
pa = {}
}
return new Promise(function(resolve, reject){
axios ({
method: 'post',
url: origin + middle + param.url,
timeout: param.time||15000,
data:param.data,
headers: {
'Content-Type': 'application/json',
'x-access-token': pa.tokenValue
}
}).then((res) => {
resolve(res.data)
}).catch(res => {
reject(res)
})
})
},
}
window.publicMethod = utils
})()
\ No newline at end of file \ No newline at end of file
This diff could not be displayed because it is too large.
<!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="css/login.css?9941">
</head>
<body>
<div id="app" v-cloak>
<div class="login-container">
<div class="header-section">
<div class="background-image">
<img class="bg" src="images/beijingtu.png" alt="背景图">
<div class="header-content">
<img src="images/logo.png" alt="Logo" class="logo">
<img src="images/hello.png" alt="Logo" class="hello">
<div class="name">欢迎怀化移动智能质检</div>
</div>
</div>
</div>
<div class="login-form">
<div class="input-group">
<img src="images/1.png" alt="工号图标" class="input-icon">
<input
type="text"
v-model="loginForm.employeeId"
placeholder="请输入工号"
class="input-field"
>
</div>
<div class="input-group">
<img src="images/2.png" alt="手机号图标" class="input-icon">
<input
type="tel"
v-model="loginForm.phoneNumber"
placeholder="请输入手机号"
class="input-field"
>
</div>
<div class="input-group" style="justify-content: space-between;">
<div style="display: flex; align-items: center;">
<img src="images/3.png" alt="验证码图标" class="input-icon">
<input
type="text"
v-model="loginForm.verifyCode"
placeholder="请输入验证码"
style="width: 2rem;"
class="input-field">
</div>
<div class="verify-btn" @click="getVerifyCode">{{ countdown > 0 ? ('重新发送('+countdown + 's)') : '获取验证码' }}</div>
</button>
</div>
<div @click="login" class="login-btn">登录</div>
<div class="loginTs">登录即表示您同意<span>《用户协议》</span><span>《隐私协议》</span></div>
</div>
</div>
</div>
<script src="js/axios.min.js"></script>
<script src="js/vue.min.js"></script>
<script src="js/util.js"></script>
<script src="js/login.js?444"></script>
</body>
</html>
\ No newline at end of file \ No newline at end of file
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>我的商机</title>
<link rel="stylesheet" href="css/myBusi.css">
</head>
<body>
<div id="app" v-cloak>
<div class="app-container" data-name="我的商机" data-node-id="355:428">
<!-- 标签页统计 -->
<div class="tab-stats" data-node-id="355:513">
<div class="active-indicator" data-node-id="355:514"></div>
<div class="tab-texts" data-node-id="355:515">
<div
class="tab-text"
:class="{ active: activeTab === 'all' }"
@click="switchTab('all')"
data-node-id="355:516"
>全部商机{{ totalCount }}</div>
<div
class="tab-text"
:class="{ active: activeTab === 'pending' }"
@click="switchTab('pending')"
data-node-id="355:517"
>待跟进{{ pendingCount }}</div>
<div
class="tab-text"
:class="{ active: activeTab === 'follow-up' }"
@click="switchTab('follow-up')"
data-node-id="355:518"
>跟进中{{ followUpCount }}</div>
<div
class="tab-text"
:class="{ active: activeTab === 'completed' }"
@click="switchTab('completed')"
data-node-id="355:519"
>已完结{{ completedCount }}</div>
</div>
</div>
<!-- 搜索区域 -->
<div class="search-section">
<div class="search-bar" data-name="矩形" data-node-id="355:503">
<div class="search-input-container" data-node-id="355:504">
<img class="search-icon" src="images/search-icon.svg" alt="搜索图标">
<input
class="search-input"
type="text"
placeholder="输入用户账号"
v-model="searchKeyword"
data-node-id="355:510"
>
</div>
</div>
</div>
<!-- 筛选标签 -->
<div class="filter-tags" data-node-id="355:533">
<div
v-for="tag in filterTags"
:key="tag.id"
:class="['filter-tag', { active: tag.selected }]"
:data-node-id="tag.nodeId"
@click="selectFilterTag(tag.id)"
>
{{ tag.name }}
</div>
</div>
<!-- 商机列表 -->
<div class="business-list">
<div
v-for="(business, index) in filteredBusinessList"
:key="business.id"
class="business-card"
:data-node-id="business.nodeId"
@click="viewBusinessDetail(business)">
<div class="card-content">
<!-- 顶部编号和状态 -->
<div class="card-header" data-node-id="355:432">
<div class="business-number" data-node-id="355:433">
<div class="number-text" data-node-id="355:434">#{{ business.orderNumber }}</div>
</div>
<div :class="['status-badge', business.statusClass]" data-node-id="355:435">
<span>{{ business.statusText }}</span>
</div>
</div>
<!-- 商机详情 -->
<div class="business-details" data-node-id="355:436">
<!-- 地址 -->
<div class="detail-row" data-node-id="355:437">
<img class="detail-icon" src="images/location-icon.svg" alt="地址图标">
<div class="detail-text" data-node-id="355:441">
<span>{{ business.address }}</span>
</div>
</div>
<!-- 联系方式 -->
<div class="detail-row contact-row" data-node-id="355:443">
<img class="detail-icon" src="images/user-icon.svg" alt="地址图标">
<div class="detail-text" data-node-id="355:446">
<span>{{ business.contactPhone }}</span>
</div>
</div>
<!-- 商机标签 -->
<div class="detail-row" data-node-id="355:448">
<img class="detail-icon" src="images/tag-icon.svg" alt="地址图标">
<div class="business-tags" data-node-id="355:452">
<div
v-for="tag in business.tags"
:key="tag.id"
class="business-tag"
:data-node-id="tag.nodeId"
>
{{ tag.name }}
</div>
</div>
</div>
<!-- 处理人信息 -->
<div class="processor-info" data-name="Container" data-node-id="355:457">
<span>处理人:{{ business.processor }}</span>
</div>
<!-- 时间信息 -->
<div class="time-info" data-name="Container" data-node-id="355:459">
<div class="submit-time" data-name="Container" data-node-id="355:460">
<span>提交:{{ business.submitTime }}</span>
</div>
<div class="update-time">
<span>更新:{{ business.updateTime }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 底部导航 -->
<div class="bottom-nav" data-name="tab" data-node-id="355:520">
<div
class="nav-item collect-business"
data-name="收集商机"
data-node-id="355:523"
@click="navigateToCollect">
<img class="nav-icon" src="images/collect-icon.svg" alt="收集商机">
<span class="nav-text" data-node-id="355:528">收集商机</span>
</div>
<div
class="nav-item all-business active"
data-name="全部商机"
data-node-id="355:529">
<img class="nav-icon" src="images/business-icon.svg" alt="收集商机">
<span class="nav-text" data-node-id="355:532">全部商机</span>
</div>
</div>
</div>
</div>
<!-- 引入Vue.js -->
<script src="js/vue.min.js"></script>
<script src="js/util.js"></script>
<script src="js/myBusi.js"></script>
</body>
</html>
\ No newline at end of file \ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>江苏移动</title>
<link rel="stylesheet" href="css/result.css?123123213455">
</head>
<body>
<div id="pageDiv" class="pageDiv">
<div class="infoTop">
<img class="icon" src="https://xpo.oss-cn-beijing.aliyuncs.com/huaian/success.png" alt="">
<div class="status">恭喜您完成质检流程</div>
<div class="detail">
<div class="detli">
<div class="detL">装维ID</div>
<div class="detR">{{info.campaignId}}</div>
</div>
<div class="detli">
<div class="detL">业务账号</div>
<div class="detR">{{info.accNbr}}</div>
</div>
<div class="detli">
<div class="detL">装机地址</div>
<div class="detR">{{info.address}}</div>
</div>
</div>
</div>
<div class="botButt" >发现商机</div>
<div class="botButt noBusi" @click="goBack">暂无商机</div>
</div>
<script src="js/vue.min.js"></script>
<script src="js/result.js?12312312332"></script>
</body>
</html>
\ No newline at end of file \ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>江苏移动</title>
<link rel="stylesheet" href="css/result.css?123123213455">
</head>
<body>
<div id="pageDiv" class="pageDiv">
<div class="infoTop">
<img class="icon" src="https://xpo.oss-cn-beijing.aliyuncs.com/huaian/success.png" alt="">
<div class="status">商机提交成功</div>
<div class="botButt" @click="goBack">继续添加商机</div>
</div>
</div>
<script src="js/vue.min.js"></script>
<script src="js/result.js?12312312332"></script>
</body>
</html>
\ No newline at end of file \ No newline at end of file
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!