initial crm web
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="./src/assets/logo.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ZOCRM</title>
|
||||
</head>
|
||||
<body style="background-color: #FAFCFF;padding: 0;margin: 0;">
|
||||
<div id="app"></div>
|
||||
<script type="module" src="../src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+2308
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "web",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"ant-design-vue": "^3.2.13",
|
||||
"axios": "^1.1.3",
|
||||
"echarts": "^5.4.0",
|
||||
"less": "^4.1.3",
|
||||
"moment": "^2.29.4",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.0.23",
|
||||
"vue": "^3.2.41",
|
||||
"vue-router": "^4.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^3.2.0",
|
||||
"vite": "^3.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<transition name="fade">
|
||||
<router-view v-slot="{ Component }">
|
||||
<component :is="Component" />
|
||||
</router-view>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#nprogress .bar {
|
||||
background: #476FFF !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,55 @@
|
||||
import request from '../axios/index'
|
||||
|
||||
// 新建合同
|
||||
export function createContract(param) {
|
||||
return request({
|
||||
url: '/contract/create',
|
||||
method: 'post',
|
||||
data: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 更新合同
|
||||
export function updateContract(param) {
|
||||
return request({
|
||||
url: '/contract/update',
|
||||
method: 'put',
|
||||
data: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 删除合同
|
||||
export function deleteContract(param) {
|
||||
return request({
|
||||
url: '/contract/delete',
|
||||
method: 'delete',
|
||||
data: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 查询合同列表
|
||||
export function queryContractList(param) {
|
||||
return request({
|
||||
url: '/contract/list',
|
||||
method: 'get',
|
||||
params: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 查询合同信息
|
||||
export function queryContractInfo(param) {
|
||||
return request({
|
||||
url: '/contract/info',
|
||||
method: 'get',
|
||||
data: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 查询添加的产品列表
|
||||
export function queryContractPlist(param) {
|
||||
return request({
|
||||
url: '/contract/plist',
|
||||
method: 'get',
|
||||
params: param,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import request from '../axios/index'
|
||||
|
||||
// 新建客户
|
||||
export function createCustomer(param) {
|
||||
return request({
|
||||
url: '/customer/create',
|
||||
method: 'post',
|
||||
data: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 更新客户
|
||||
export function updateCustomer(param) {
|
||||
return request({
|
||||
url: '/customer/update',
|
||||
method: 'put',
|
||||
data: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 删除客户
|
||||
export function deleteCustomer(param) {
|
||||
return request({
|
||||
url: '/customer/delete',
|
||||
method: 'delete',
|
||||
data: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 查询客户列表
|
||||
export function queryCustomerList(param) {
|
||||
return request({
|
||||
url: '/customer/list',
|
||||
method: 'get',
|
||||
params: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 查询客户信息
|
||||
export function queryCustomerInfo(param) {
|
||||
return request({
|
||||
url: '/customer/info',
|
||||
method: 'get',
|
||||
params: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 查询客户信息
|
||||
export function queryCustomerOption(param) {
|
||||
return request({
|
||||
url: '/customer/option',
|
||||
method: 'get',
|
||||
params: param,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import request from '../axios/index'
|
||||
|
||||
// 获取数据汇总
|
||||
export function getSummary(param) {
|
||||
return request({
|
||||
url: '/dashboard/sum',
|
||||
method: 'get',
|
||||
params: param,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import request from '../axios/index'
|
||||
|
||||
// 新建产品
|
||||
export function createProduct(param) {
|
||||
return request({
|
||||
url: '/product/create',
|
||||
method: 'post',
|
||||
data: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 更新产品
|
||||
export function updateProduct(param) {
|
||||
return request({
|
||||
url: '/product/update',
|
||||
method: 'put',
|
||||
data: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 删除产品
|
||||
export function deleteProduct(param) {
|
||||
return request({
|
||||
url: '/product/delete',
|
||||
method: 'delete',
|
||||
data: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 查询产品信息
|
||||
export function queryProductInfo(param) {
|
||||
return request({
|
||||
url: '/product/info',
|
||||
method: 'get',
|
||||
params: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 查询产品列表
|
||||
export function queryProductList(param) {
|
||||
return request({
|
||||
url: '/product/list',
|
||||
method: 'get',
|
||||
params: param,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import request from '../axios/index'
|
||||
|
||||
// 用户登录
|
||||
export function userLogin(param) {
|
||||
return request({
|
||||
url: '/user/login',
|
||||
method: 'post',
|
||||
data: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 用户注册
|
||||
export function userRegister(param) {
|
||||
return request({
|
||||
url: '/user/register',
|
||||
method: 'post',
|
||||
data: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 获取验证码
|
||||
export function getVerifyCode(param) {
|
||||
return request({
|
||||
url: '/user/verifycode',
|
||||
method: 'get',
|
||||
params: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 忘记密码
|
||||
export function userForgotPass(param) {
|
||||
return request({
|
||||
url: '/user/pass',
|
||||
method: 'post',
|
||||
data: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 修改邮箱
|
||||
export function updateMail(param) {
|
||||
return request({
|
||||
url: '/user/mail',
|
||||
method: 'put',
|
||||
data: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
export function userLogout(param) {
|
||||
return request({
|
||||
url: '/user/logout',
|
||||
method: 'delete',
|
||||
data: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 注销账号
|
||||
export function userDelete(param) {
|
||||
return request({
|
||||
url: '/user/delete',
|
||||
method: 'delete',
|
||||
data: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
export function getUserInfo(param) {
|
||||
return request({
|
||||
url: '/user/info',
|
||||
method: 'get',
|
||||
params: param,
|
||||
})
|
||||
}
|
||||
|
||||
// 订阅个人版
|
||||
export function userBuy(param) {
|
||||
return request({
|
||||
url: '/user/buy',
|
||||
method: 'put',
|
||||
data: param,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M387.168 691.328s-21.216-48.8 3.584-83.264c24.96-34.688 53.888-69.6 119.584-152.128 6.368-6.944 15.136-28.096 17.12-40.672 1.184-7.488 0.896-15.552-0.16-25.44a176.992 176.992 0 0 0-3.2-17.92c-21.248-60.192-16.64-51.168-144.128-354.72C161.184 75.424 0 274.88 0 512c0 282.784 229.216 512 512 512 3.776 0 7.52-0.064 11.264-0.128l-136.096-332.544z" fill="#21B3FF" /><path d="M512 0c-3.584 0-7.136 0.032-10.688 0.128l151.424 373.216s24.96 49.088-2.688 94.016c-23.456 38.08-92.608 113.536-124.288 162.176-9.216 14.08-13.76 46.88-5.44 71.584 19.36 57.536 120.096 297.504 123.552 305.728C862.72 948.672 1024 749.184 1024 512c0-282.784-229.216-512-512-512z" fill="#1691FF" /></svg>
|
||||
|
After Width: | Height: | Size: 939 B |
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
import axios from "axios";
|
||||
import router from '../router/index';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
axios.defaults.baseURL = "http://localhost:8000/api";
|
||||
|
||||
const request = axios.create({
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
}
|
||||
});
|
||||
|
||||
request.interceptors.request.use(config => {
|
||||
config.headers['uid'] = localStorage.getItem('uid')
|
||||
config.headers['ver'] = localStorage.getItem('ver')
|
||||
config.headers['token'] = localStorage.getItem('token')
|
||||
return config
|
||||
})
|
||||
|
||||
request.interceptors.response.use(response => {
|
||||
console.log(response)
|
||||
if (response.data.code == 1) {
|
||||
message.error('服务器异常!')
|
||||
}
|
||||
return response;
|
||||
},error => {
|
||||
console.log(error)
|
||||
router.push('/error');
|
||||
return Promise.reject(error)
|
||||
})
|
||||
|
||||
export default request;
|
||||
@@ -0,0 +1,7 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import Antd from 'ant-design-vue';
|
||||
import 'ant-design-vue/dist/antd.less';
|
||||
import router from './router/index';
|
||||
|
||||
createApp(App).use(Antd).use(router).mount('#app')
|
||||
@@ -0,0 +1,96 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
import Index from '../views/Index.vue'
|
||||
import Home from '../views/Home.vue'
|
||||
import Error from '../views/Error.vue'
|
||||
import Login from '../views/Login.vue'
|
||||
import Register from '../views/Register.vue'
|
||||
import Pass from '../views/Pass.vue'
|
||||
import Dashboard from '../views/Dashboard.vue'
|
||||
import Customer from '../views/Customer.vue'
|
||||
import Contract from '../views/Contract.vue'
|
||||
import Product from '../views/Product.vue'
|
||||
import Subscribe from '../views/Subscribe.vue'
|
||||
import Result from '../views/Result.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
component: Index,
|
||||
redirect: '/login',
|
||||
children: [
|
||||
{
|
||||
path: '/login',
|
||||
component: Login,
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
component: Register,
|
||||
},
|
||||
{
|
||||
path: '/pass',
|
||||
component: Pass,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/home',
|
||||
component: Home,
|
||||
redirect: '/dashboard',
|
||||
children: [
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'dashboard',
|
||||
component: Dashboard,
|
||||
},
|
||||
{
|
||||
path: '/customer',
|
||||
name: 'customer',
|
||||
component: Customer,
|
||||
},
|
||||
{
|
||||
path: '/contract',
|
||||
name: 'contract',
|
||||
component: Contract,
|
||||
},
|
||||
{
|
||||
path: '/product',
|
||||
name: 'product',
|
||||
component: Product,
|
||||
},
|
||||
{
|
||||
path: '/subscribe',
|
||||
name: 'subscribe',
|
||||
component: Subscribe,
|
||||
},
|
||||
{
|
||||
path: '/result',
|
||||
name: 'result',
|
||||
component: Result,
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/error',
|
||||
name: 'error',
|
||||
component: Error,
|
||||
},
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(), routes
|
||||
})
|
||||
|
||||
NProgress.configure({ easing: 'ease', speed: 500, showSpinner: false });
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
NProgress.start() // 进度条开始
|
||||
next()
|
||||
})
|
||||
|
||||
router.afterEach(() => {
|
||||
NProgress.done() // 进度条结束
|
||||
})
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { createPinia, defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const pinia = createPinia();
|
||||
|
||||
export const useSpinStore = defineStore('spin', () => {
|
||||
const spinning = ref(true)
|
||||
return { spinning }
|
||||
})
|
||||
|
||||
export const spinStore = useSpinStore(pinia)
|
||||
@@ -0,0 +1,651 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-space style="margin-bottom: 20px; width: 100%;">
|
||||
<a-input v-model:value="keyWord" placeholder="合同编号" style="width: 280px; margin-right: 50px;">
|
||||
<template #suffix>
|
||||
<search-outlined style="color: rgba(0, 0, 0, 0.45)" @click="onSearch" />
|
||||
</template>
|
||||
</a-input>
|
||||
<a-button type="primary" @click="onContracts">全部合同</a-button>
|
||||
<a-button type="primary" @click="onDelete" :disabled="disabled" danger>删除</a-button>
|
||||
<a-button type="primary" @click="onCreate">新建</a-button>
|
||||
</a-space>
|
||||
<a-table rowKey="id"
|
||||
:row-selection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectedConteactIds, getCheckboxProps: defaultSelected }"
|
||||
:columns="columns" :data-source="data.contractList"
|
||||
:pagination="{ current: pagination.current, pageSize: pagination.pageSize, total: pagination.total, onChange: onPagination }"
|
||||
:scroll="{ y: '59vh' }" class="ant-table-striped"
|
||||
:row-class-name="(_record, index) => (index % 2 === 1 ? 'table-striped' : null)" bordered>
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<template v-if="column.dataIndex === 'id'">
|
||||
<a @click="onEdit(record)">{{ text }}</a>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'amount'">
|
||||
<span style="color: #ff991f">{{ text }}</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'status'">
|
||||
<a-tag v-if="text == 1" color="green">已签约</a-tag>
|
||||
<a-tag v-if="text == 2" color="red">未签约</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 新建、编辑合同 -->
|
||||
<a-modal v-model:visible="visible" :title="title" @ok="onSave" @cancel="onCancel" cancelText="取消" okText="保存"
|
||||
width="800px" style="top: 80px">
|
||||
<div style="height: 55vh;overflow-y: scroll;padding: 0 15px;">
|
||||
<a-form ref="modalFormRef" :model="contract" layout="vertical" name="contract">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="合同编号" name="id">
|
||||
<a-input v-model:value="contract.id" :disabled="true" placeholder="根据编号规则自动生成" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="合同名称" name="name" :rules="[{ required: true, message: '请输入合同名称' }]">
|
||||
<a-input v-model:value="contract.name" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="客户名称" name="cid" :rules="[{ required: true, message: '请选择客户' }]">
|
||||
<a-select v-model:value="contract.cid" style="width: 100%" placeholder="请选择"
|
||||
:fieldNames="{ label: 'name', value: 'id' }" :options="data.customerOption"
|
||||
@focus="getCustomerOption" @change="changeCustomerOption"></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="合同金额" name="amount" :rules="[{ required: true, message: '请输入合同金额' }]">
|
||||
<a-input-number v-model:value="contract.amount" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="合同开始时间" name="beginTime">
|
||||
<a-date-picker v-model:value="contract.beginTime" placeholder="选择日期"
|
||||
style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="合同结束时间" name="overTime">
|
||||
<a-date-picker v-model:value="contract.overTime" placeholder="选择日期"
|
||||
style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="合同状态" name="status" :rules="[{ required: true, message: '请选择合同状态' }]">
|
||||
<a-select v-model:value="contract.status" placeholder="请选择">
|
||||
<a-select-option :value="1">已生效</a-select-option>
|
||||
<a-select-option :value="2">未生效</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="备注" name="remarks">
|
||||
<a-input v-model:value="contract.remarks" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="产品" name="product">
|
||||
<a-button type="primary" @click="onAddProduct"
|
||||
style="float: right;margin-bottom: 10px;">
|
||||
添加产品</a-button>
|
||||
<a-table rowKey="id" :columns="productColumns" :data-source="data.addedProductList"
|
||||
:scroll="{ y: '59vh' }" class="ant-table-striped"
|
||||
:row-class-name="(_record, index) => (index % 2 === 1 ? 'table-striped' : null)"
|
||||
:pagination="false" bordered>
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<template v-if="column.dataIndex === 'type'">
|
||||
<span v-if="text == 1">默认</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'price'">
|
||||
<span style="color: #ff991f">{{ text }}</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'count'">
|
||||
<a-input-number v-model:value="record.count" @change="calculatedAmount" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'total'">
|
||||
<span>{{ record.total = record.price * record.count }}</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<a @click="delProduct(record)">删除</a>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col :span="24">
|
||||
<div style="float: right;margin: 0 20px;">
|
||||
<span>已选择产品:{{ data.addedProductList.length }}种 总金额:</span>
|
||||
<a-input-number v-model:value="contract.amount" style="width: 200px;" />
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
<!-- 添加产品 -->
|
||||
<a-modal v-model:visible="productListVisible" title="添加产品" @cancel="onCancelProductList" @ok="onConfirm"
|
||||
cancelText="取消" okText="确定" width="800px" style="top: 80px" :getCheckboxProps="defaultSelected">
|
||||
<div style="height: 55vh;padding: 0 15px;">
|
||||
<a-table rowKey="id"
|
||||
:row-selection="{ selectedRowKeys: data.defaultSelectedIds, onChange: onSelectedProductIds }"
|
||||
:columns="productListColumns" :data-source="data.productList"
|
||||
:pagination="{ current: pagination.current, pageSize: pagination.pageSize, total: pagination.total, onChange: onPaginationProduct }"
|
||||
:scroll="{ y: '40vh' }" class="ant-table-striped"
|
||||
:row-class-name="(_record, index) => (index % 2 === 1 ? 'table-striped' : null)" bordered>
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<template v-if="column.dataIndex === 'name'">
|
||||
<a @click="onEdit(record)">{{ text }}</a>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'status'">
|
||||
<span v-if="text == 1">上架</span>
|
||||
<span v-if="text == 2">下架</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'type'">
|
||||
<span v-if="text == 1">默认</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'price'">
|
||||
<span style="color: #ff991f">{{ text }}</span>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, onMounted, createVNode } from 'vue';
|
||||
import { SearchOutlined, ExclamationCircleOutlined, UpCircleOutlined, DownCircleOutlined } from '@ant-design/icons-vue';
|
||||
import moment from 'moment'
|
||||
import { createContract, updateContract, queryContractList, queryContractInfo, deleteContract, queryContractPlist } from '../api/contract';
|
||||
import { queryProductList } from "../api/product";
|
||||
import { queryCustomerOption } from "../api/customer";
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SearchOutlined,
|
||||
UpCircleOutlined,
|
||||
DownCircleOutlined
|
||||
},
|
||||
setup() {
|
||||
|
||||
// 合同表格字段
|
||||
const columns = [{
|
||||
title: '合同编号',
|
||||
dataIndex: 'id',
|
||||
width: 100,
|
||||
fixed: 'left',
|
||||
ellipsis: true,
|
||||
}, {
|
||||
title: '合同名称',
|
||||
dataIndex: 'name',
|
||||
width: 100,
|
||||
fixed: 'left',
|
||||
ellipsis: true,
|
||||
}, {
|
||||
title: '客户名称',
|
||||
dataIndex: 'cname',
|
||||
width: 240,
|
||||
}, {
|
||||
title: '合同金额',
|
||||
dataIndex: 'amount',
|
||||
width: 100,
|
||||
}, {
|
||||
title: '合同开始时间',
|
||||
dataIndex: 'beginTime',
|
||||
width: 150,
|
||||
}, {
|
||||
title: '合同结束时间',
|
||||
dataIndex: 'overTime',
|
||||
width: 150,
|
||||
}, {
|
||||
title: '备注',
|
||||
dataIndex: 'remarks',
|
||||
width: 240,
|
||||
ellipsis: true,
|
||||
}, {
|
||||
title: '签约状态',
|
||||
dataIndex: 'status',
|
||||
width: 100,
|
||||
ellipsis: true,
|
||||
}, {
|
||||
title: '创建时间',
|
||||
dataIndex: 'created',
|
||||
width: 185,
|
||||
customRender: text => {
|
||||
let m = moment(text.value * 1000).format('YYYY-MM-DD HH:mm:ss')
|
||||
return m == 'Invalid date' ? '' : m
|
||||
}
|
||||
}, {
|
||||
title: '更新时间',
|
||||
dataIndex: 'updated',
|
||||
width: 185,
|
||||
customRender: text => {
|
||||
let m = moment(text.value * 1000).format('YYYY-MM-DD HH:mm:ss')
|
||||
return m == 'Invalid date' ? '' : m
|
||||
}
|
||||
}];
|
||||
|
||||
// 新建或编辑合同,已添加产品表格字段
|
||||
const productColumns = [{
|
||||
title: '产品名称',
|
||||
dataIndex: 'name',
|
||||
width: 100,
|
||||
}, {
|
||||
title: '产品类别',
|
||||
dataIndex: 'type',
|
||||
width: 90,
|
||||
}, {
|
||||
title: '单位',
|
||||
dataIndex: 'unit',
|
||||
width: 80,
|
||||
}, {
|
||||
title: '价格',
|
||||
dataIndex: 'price',
|
||||
width: 100,
|
||||
}, {
|
||||
title: '数量',
|
||||
dataIndex: 'count',
|
||||
width: 120,
|
||||
}, {
|
||||
title: '合计',
|
||||
dataIndex: 'total',
|
||||
width: 100,
|
||||
}, {
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
width: 100,
|
||||
}]
|
||||
|
||||
// 产品表格字段
|
||||
const productListColumns = [{
|
||||
title: '产品名称',
|
||||
dataIndex: 'name',
|
||||
width: 100,
|
||||
fixed: 'left',
|
||||
ellipsis: true,
|
||||
}, {
|
||||
title: '是否上下架',
|
||||
dataIndex: 'status',
|
||||
width: 120,
|
||||
}, {
|
||||
title: '产品类型',
|
||||
dataIndex: 'type',
|
||||
width: 100,
|
||||
}, {
|
||||
title: '产品单位',
|
||||
dataIndex: 'unit',
|
||||
width: 100,
|
||||
}, {
|
||||
title: '产品编码',
|
||||
dataIndex: 'code',
|
||||
width: 150,
|
||||
}, {
|
||||
title: '价格',
|
||||
dataIndex: 'price',
|
||||
width: 150,
|
||||
}, {
|
||||
title: '产品描述',
|
||||
dataIndex: 'description',
|
||||
width: 240,
|
||||
ellipsis: true,
|
||||
}, {
|
||||
title: '创建时间',
|
||||
dataIndex: 'created',
|
||||
width: 185,
|
||||
customRender: text => {
|
||||
let m = moment(text.value * 1000).format('YYYY-MM-DD HH:mm:ss')
|
||||
return m == 'Invalid date' ? '' : m
|
||||
}
|
||||
}, {
|
||||
title: '更新时间',
|
||||
dataIndex: 'updated',
|
||||
width: 185,
|
||||
customRender: text => {
|
||||
let m = moment(text.value * 1000).format('YYYY-MM-DD HH:mm:ss')
|
||||
return m == 'Invalid date' ? '' : m
|
||||
}
|
||||
}, {
|
||||
title: '创建人',
|
||||
dataIndex: 'creator',
|
||||
width: 150,
|
||||
}];
|
||||
|
||||
// 合同属性
|
||||
let contract = reactive({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
amount: undefined,
|
||||
beginTime: undefined,
|
||||
cid: undefined,
|
||||
overTime: undefined,
|
||||
remarks: undefined,
|
||||
status: undefined,
|
||||
productlist: [],
|
||||
});
|
||||
|
||||
const data = reactive({
|
||||
contractList: [],
|
||||
contractIds: [],
|
||||
productList: [],
|
||||
productIds: [],
|
||||
addedProductList: [],
|
||||
customerOption: [],
|
||||
defaultSelectedIds: []
|
||||
})
|
||||
|
||||
// 表格分页
|
||||
let pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: undefined,
|
||||
})
|
||||
|
||||
// 点击搜索
|
||||
const onSearch = () => { getContractList() };
|
||||
|
||||
const title = ref('');
|
||||
const visible = ref(false);
|
||||
const disabled = ref(true)
|
||||
const operation = ref(0);
|
||||
const modalFormRef = ref();
|
||||
const keyWord = ref('')
|
||||
const productListVisible = ref(false);
|
||||
|
||||
// 点击新建合同
|
||||
const onCreate = () => {
|
||||
title.value = '新建合同'
|
||||
visible.value = true
|
||||
operation.value = 1
|
||||
}
|
||||
|
||||
// 点击编辑合同
|
||||
const onEdit = (row) => {
|
||||
title.value = '编辑合同'
|
||||
visible.value = true
|
||||
operation.value = 2
|
||||
getCustomerOption()
|
||||
let param = { id: row.id }
|
||||
queryContractInfo(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
let p = res.data.data
|
||||
contract.id = p.id
|
||||
contract.name = p.name
|
||||
contract.cid = p.cid
|
||||
contract.amount = p.amount
|
||||
contract.beginTime = moment(new Date(p.beginTime))
|
||||
contract.overTime = moment(new Date(p.overTime))
|
||||
contract.remarks = p.remarks
|
||||
contract.status = p.status
|
||||
contract.productlist = p.productlist
|
||||
data.addedProductList = p.productlist
|
||||
if (data.addedProductList.length > 0) {
|
||||
for (let i = 0; i < data.addedProductList.length; i++) {
|
||||
data.defaultSelectedIds.push(data.addedProductList[i].id)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 点击保存合同
|
||||
const onSave = () => {
|
||||
modalFormRef.value.validateFields().then(() => {
|
||||
if (operation.value == 1) {
|
||||
let param = {
|
||||
name: contract.name,
|
||||
cid: contract.cid,
|
||||
amount: contract.amount,
|
||||
beginTime: moment(contract.beginTime).format('YYYY-MM-DD'),
|
||||
overTime: moment(contract.overTime).format('YYYY-MM-DD'),
|
||||
remarks: contract.remarks,
|
||||
status: contract.status,
|
||||
productlist: data.addedProductList,
|
||||
}
|
||||
createContract(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
message.success('保存成功')
|
||||
data.defaultSelectedIds = []
|
||||
getContractList()
|
||||
}
|
||||
})
|
||||
}
|
||||
if (operation.value == 2) {
|
||||
let param = {
|
||||
id: contract.id,
|
||||
name: contract.name,
|
||||
cid: contract.cid,
|
||||
amount: contract.amount,
|
||||
beginTime: moment(contract.beginTime).format('YYYY-MM-DD'),
|
||||
overTime: moment(contract.overTime).format('YYYY-MM-DD'),
|
||||
remarks: contract.remarks,
|
||||
status: contract.status,
|
||||
productlist: data.addedProductList,
|
||||
}
|
||||
updateContract(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
message.success('保存成功')
|
||||
data.defaultSelectedIds = []
|
||||
getContractList()
|
||||
}
|
||||
})
|
||||
}
|
||||
modalFormRef.value.resetFields()
|
||||
visible.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 点击删除合同
|
||||
const onDelete = () => {
|
||||
let param = {
|
||||
ids: data.contractIds
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '确定删除选中的' + data.contractIds.length + '项吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
centered: true,
|
||||
cancelText: '取消',
|
||||
okText: '确定',
|
||||
onOk() {
|
||||
deleteContract(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
getContractList()
|
||||
disabled.value = true
|
||||
message.success('删除成功')
|
||||
}
|
||||
})
|
||||
},
|
||||
onCancel() {
|
||||
console.log('Cancel');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
onMounted(() => { getContractList() })
|
||||
|
||||
// 点击全部合同
|
||||
const onContracts = () => {
|
||||
keyWord.value = ''
|
||||
getContractList()
|
||||
}
|
||||
|
||||
// 分页查询合同列表
|
||||
const onPagination = (page) => {
|
||||
pagination.current = page
|
||||
getContractList()
|
||||
}
|
||||
const getContractList = () => {
|
||||
let param = {
|
||||
id: parseInt(keyWord.value == '' ? '0' : keyWord.value),
|
||||
pageNum: pagination.current,
|
||||
pageSize: pagination.pageSize
|
||||
}
|
||||
queryContractList(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
pagination.total = res.data.data.total
|
||||
data.contractList = res.data.data.list
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 点击添加产品
|
||||
const onAddProduct = () => {
|
||||
productListVisible.value = true
|
||||
let param = {
|
||||
pageNum: pagination.current,
|
||||
pageSize: pagination.pageSize
|
||||
}
|
||||
queryProductList(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
pagination.total = res.data.data.total
|
||||
data.productList = res.data.data.list
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 分页查询产品列表
|
||||
const onPaginationProduct = (page) => {
|
||||
pagination.current = page
|
||||
let param = {
|
||||
pageNum: pagination.current,
|
||||
pageSize: pagination.pageSize
|
||||
}
|
||||
queryProductList(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
pagination.total = res.data.data.total
|
||||
data.productList = res.data.data.list
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 已选中的合同ID
|
||||
const onSelectedConteactIds = selectedRowKeys => {
|
||||
data.contractIds = selectedRowKeys
|
||||
if (data.contractIds.length !== 0) {
|
||||
disabled.value = false
|
||||
} else {
|
||||
disabled.value = true
|
||||
}
|
||||
};
|
||||
|
||||
// 已选中的产品ID
|
||||
const onSelectedProductIds = selectedRowKeys => {
|
||||
data.productIds = selectedRowKeys
|
||||
data.defaultSelectedIds = selectedRowKeys
|
||||
};
|
||||
|
||||
// 删除选中的产品
|
||||
const delProduct = (row) => {
|
||||
for (let i = 0; i < data.addedProductList.length; i++) {
|
||||
if (data.addedProductList[i].id == row.id) {
|
||||
data.addedProductList.splice(i, 1);
|
||||
}
|
||||
}
|
||||
calculatedAmount()
|
||||
}
|
||||
|
||||
// 点击确定选中的产品ID
|
||||
const onConfirm = () => {
|
||||
console.log("xzx", data.productIds)
|
||||
let param = {
|
||||
ids: data.productIds
|
||||
}
|
||||
queryContractPlist(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
data.addedProductList = res.data.data
|
||||
}
|
||||
})
|
||||
productListVisible.value = false
|
||||
}
|
||||
|
||||
// 查询客户选项
|
||||
const getCustomerOption = () => {
|
||||
queryCustomerOption().then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
data.customerOption = res.data.data
|
||||
console.log("zxxzc", data.customerOption)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const changeCustomerOption = (value) => {
|
||||
contract.cid.value = value
|
||||
}
|
||||
|
||||
// 计算金额
|
||||
const calculatedAmount = () => {
|
||||
contract.amount = 0
|
||||
let totalAmount = 0
|
||||
for (let i = 0; i < data.addedProductList.length; i++) {
|
||||
totalAmount = totalAmount + (data.addedProductList[i].price * data.addedProductList[i].count)
|
||||
}
|
||||
contract.amount = totalAmount
|
||||
}
|
||||
|
||||
// 点击合同取消按钮
|
||||
const onCancel = () => {
|
||||
modalFormRef.value.resetFields()
|
||||
visible.value = false
|
||||
};
|
||||
|
||||
// 点击取消产品列表
|
||||
const onCancelProductList = () => {
|
||||
productListVisible.value = false
|
||||
pagination.current = 1,
|
||||
pagination.total = undefined
|
||||
}
|
||||
|
||||
return {
|
||||
columns,
|
||||
productColumns,
|
||||
productListColumns,
|
||||
data,
|
||||
onSelectedConteactIds,
|
||||
onSelectedProductIds,
|
||||
onSearch,
|
||||
contract,
|
||||
title,
|
||||
visible,
|
||||
disabled,
|
||||
productListVisible,
|
||||
operation,
|
||||
onCreate,
|
||||
onEdit,
|
||||
modalFormRef,
|
||||
onSave,
|
||||
onCancel,
|
||||
onDelete,
|
||||
onCancelProductList,
|
||||
getContractList,
|
||||
keyWord,
|
||||
onConfirm,
|
||||
onAddProduct,
|
||||
getCustomerOption,
|
||||
changeCustomerOption,
|
||||
calculatedAmount,
|
||||
delProduct,
|
||||
pagination,
|
||||
onPagination,
|
||||
onContracts,
|
||||
onPaginationProduct,
|
||||
};
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ant-table-striped :deep(.table-striped) td {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,402 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-space style="margin-bottom: 20px; width: 100%;">
|
||||
<a-input v-model:value="keyWord" placeholder="客户名称" style="width: 280px; margin-right: 50px;">
|
||||
<template #suffix>
|
||||
<search-outlined style="color: rgba(0, 0, 0, 0.45)" @click="onSearch" />
|
||||
</template>
|
||||
</a-input>
|
||||
<a-button type="primary" @click="onCustomers">全部客户</a-button>
|
||||
<a-button type="primary" @click="onDelete" :disabled="disabled" danger>删除</a-button>
|
||||
<a-button type="primary" @click="onCreate">新建</a-button>
|
||||
</a-space>
|
||||
|
||||
<a-table rowKey="id" :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }"
|
||||
:columns="columns" :data-source="data.customerList"
|
||||
:pagination="{ current: pagination.current, pageSize: pagination.pageSize, total: pagination.total, onChange: onPagination }"
|
||||
:scroll="{ y: '59vh' }" class="ant-table-striped"
|
||||
:row-class-name="(_record, index) => (index % 2 === 1 ? 'table-striped' : null)" bordered>
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<template v-if="column.dataIndex === 'name'">
|
||||
<a @click="onEdit(record)">{{ text }}</a>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'status'">
|
||||
<a-tag v-if="text == 1" color="green">已成交</a-tag>
|
||||
<a-tag v-if="text == 2" color="blue">未成交</a-tag>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'address'">
|
||||
<span>{{ record.region + " " + record.address }}</span>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<!-- 新建、编辑客户 -->
|
||||
<a-modal v-model:visible="visible" :title="title" @ok="onSave" @cancel="onCancel" cancelText="取消" okText="保存"
|
||||
width="800px" style="top: 80px;">
|
||||
<div style="height: 55vh;overflow-y: scroll;padding: 0 15px;">
|
||||
<a-form ref="modalFormRef" :model="customer" layout="vertical" name="customer">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="客户名称" name="name" :rules="[{ required: true, message: '请输入客户名称' }]">
|
||||
<a-input v-model:value="customer.name" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="客户来源" name="source">
|
||||
<a-select v-model:value="customer.source" placeholder="请选择">
|
||||
<a-select-option value="促销">促销</a-select-option>
|
||||
<a-select-option value="搜索引擎">搜索引擎</a-select-option>
|
||||
<a-select-option value="广告">广告</a-select-option>
|
||||
<a-select-option value="转介绍">转介绍</a-select-option>
|
||||
<a-select-option value="线上注册">线上注册</a-select-option>
|
||||
<a-select-option value="电话咨询">电话咨询</a-select-option>
|
||||
<a-select-option value="邮件咨询">邮件咨询</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="手机号" name="phone">
|
||||
<a-input v-model:value="customer.phone" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="邮箱" name="email">
|
||||
<a-input v-model:value="customer.email" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="客户行业" name="industry">
|
||||
<a-select v-model:value="customer.industry" placeholder="请选择">
|
||||
<a-select-option value="互联网">互联网</a-select-option>
|
||||
<a-select-option value="金融业">金融业</a-select-option>
|
||||
<a-select-option value="政府">政府</a-select-option>
|
||||
<a-select-option value="房地产">房地产</a-select-option>
|
||||
<a-select-option value="文化传媒">文化传媒</a-select-option>
|
||||
<a-select-option value="生产">生产</a-select-option>
|
||||
<a-select-option value="物流运输">物流运输</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="客户级别" name="level">
|
||||
<a-select v-model:value="customer.level" placeholder="请选择">
|
||||
<a-select-option value="重点客户">重点客户</a-select-option>
|
||||
<a-select-option value="普通客户">普通客户</a-select-option>
|
||||
<a-select-option value="非优先客户">非优先客户</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="所在地区" name="region">
|
||||
<a-cascader v-model:value="region" @change="selectedOptions" :options="options"
|
||||
placeholder="请选择" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="详细地址" name="address">
|
||||
<a-input v-model:value="customer.address" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="备注" name="remarks">
|
||||
<a-textarea v-model:value="customer.remarks" :rows="3" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, onMounted, createVNode } from 'vue';
|
||||
import { SearchOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import moment from 'moment'
|
||||
import { createCustomer, updateCustomer, queryCustomerList, queryCustomerInfo, deleteCustomer } from '../api/customer';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import regionData from '../assets/region';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SearchOutlined
|
||||
},
|
||||
setup() {
|
||||
const columns = [{
|
||||
title: '客户名称',
|
||||
dataIndex: 'name',
|
||||
width: 200,
|
||||
fixed: 'left',
|
||||
ellipsis: true,
|
||||
}, {
|
||||
title: '客户来源',
|
||||
dataIndex: 'source',
|
||||
width: 150,
|
||||
}, {
|
||||
title: '手机号',
|
||||
dataIndex: 'phone',
|
||||
width: 150,
|
||||
}, {
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
width: 200,
|
||||
}, {
|
||||
title: '客户行业',
|
||||
dataIndex: 'industry',
|
||||
width: 150,
|
||||
}, {
|
||||
title: '客户级别',
|
||||
dataIndex: 'level',
|
||||
width: 150,
|
||||
}, {
|
||||
title: '备注',
|
||||
dataIndex: 'remarks',
|
||||
width: 150,
|
||||
ellipsis: true,
|
||||
}, {
|
||||
title: '成交状态',
|
||||
dataIndex: 'status',
|
||||
width: 150,
|
||||
}, {
|
||||
title: '详细地址',
|
||||
dataIndex: 'address',
|
||||
width: 240,
|
||||
ellipsis: true,
|
||||
}, {
|
||||
title: '创建时间',
|
||||
dataIndex: 'created',
|
||||
width: 185,
|
||||
customRender: text => {
|
||||
let m = moment(text.value * 1000).format('YYYY-MM-DD HH:mm:ss')
|
||||
return m == 'Invalid date' ? '' : m
|
||||
}
|
||||
}, {
|
||||
title: '更新时间',
|
||||
dataIndex: 'updated',
|
||||
width: 185,
|
||||
customRender: text => {
|
||||
let m = moment(text.value * 1000).format('YYYY-MM-DD HH:mm:ss')
|
||||
return m == 'Invalid date' ? '' : m
|
||||
}
|
||||
}];
|
||||
|
||||
const data = reactive({
|
||||
customerList: [],
|
||||
selectedIds: []
|
||||
})
|
||||
|
||||
|
||||
const onSelectChange = selectedRowKeys => {
|
||||
data.selectedIds = selectedRowKeys
|
||||
if (data.selectedIds.length !== 0) {
|
||||
disabled.value = false
|
||||
} else {
|
||||
disabled.value = true
|
||||
}
|
||||
};
|
||||
|
||||
// 点击搜索
|
||||
const onSearch = () => {
|
||||
getCustomerList()
|
||||
};
|
||||
|
||||
// 点击全部客户
|
||||
const onCustomers = () => {
|
||||
keyWord.value = ''
|
||||
getCustomerList()
|
||||
}
|
||||
|
||||
// 客户属性
|
||||
let customer = reactive({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
source: undefined,
|
||||
phone: undefined,
|
||||
email: undefined,
|
||||
industry: undefined,
|
||||
level: undefined,
|
||||
remarks: undefined,
|
||||
region: undefined,
|
||||
address: undefined,
|
||||
status: undefined,
|
||||
});
|
||||
|
||||
// 表格分页
|
||||
let pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: undefined,
|
||||
})
|
||||
|
||||
const title = ref('');
|
||||
const visible = ref(false);
|
||||
const disabled = ref(true)
|
||||
const operation = ref(0);
|
||||
const modalFormRef = ref();
|
||||
const keyWord = ref('')
|
||||
|
||||
const region = ref([])
|
||||
|
||||
// 点击新建客户
|
||||
const onCreate = () => {
|
||||
title.value = '新建客户'
|
||||
visible.value = true
|
||||
operation.value = 1
|
||||
}
|
||||
|
||||
// 点击客户名称
|
||||
const onEdit = (row) => {
|
||||
title.value = '编辑客户'
|
||||
visible.value = true
|
||||
operation.value = 2
|
||||
let param = { id: row.id }
|
||||
queryCustomerInfo(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
let p = res.data.data
|
||||
customer.id = p.id
|
||||
customer.name = p.name
|
||||
customer.source = p.source
|
||||
customer.phone = p.phone
|
||||
customer.email = p.email
|
||||
customer.industry = p.industry
|
||||
customer.level = p.level
|
||||
customer.remarks = p.remarks
|
||||
customer.region = p.region
|
||||
region.value = customer.region.split(',')
|
||||
customer.address = p.address
|
||||
customer.status = p.status
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 点击保存客户
|
||||
const onSave = () => {
|
||||
console.log("zzz123")
|
||||
modalFormRef.value.validateFields().then(() => {
|
||||
if (operation.value == 1) {
|
||||
customer.region.toString()
|
||||
createCustomer(customer).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
message.success('保存成功')
|
||||
getCustomerList()
|
||||
}
|
||||
})
|
||||
}
|
||||
if (operation.value == 2) {
|
||||
updateCustomer(customer).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
message.success('保存成功')
|
||||
getCustomerList()
|
||||
}
|
||||
})
|
||||
}
|
||||
modalFormRef.value.resetFields()
|
||||
visible.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 点击删除客户
|
||||
const onDelete = () => {
|
||||
let param = {
|
||||
ids: data.selectedIds
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '确定删除选中的' + data.selectedIds.length + '项吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
centered: true,
|
||||
cancelText: '取消',
|
||||
okText: '确定',
|
||||
onOk() {
|
||||
deleteCustomer(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
getCustomerList()
|
||||
disabled.value = true
|
||||
message.success('删除成功')
|
||||
}
|
||||
})
|
||||
},
|
||||
onCancel() {
|
||||
console.log('Cancel');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 分页查询客户列表
|
||||
const onPagination = (page) => {
|
||||
pagination.current = page
|
||||
getCustomerList()
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
onMounted(() => { getCustomerList() })
|
||||
|
||||
const getCustomerList = () => {
|
||||
let param = {
|
||||
name: keyWord.value,
|
||||
pageNum: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
}
|
||||
queryCustomerList(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
pagination.total = res.data.data.total
|
||||
data.customerList = res.data.data.list
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 点击取消按钮
|
||||
const onCancel = () => {
|
||||
modalFormRef.value.resetFields()
|
||||
visible.value = false
|
||||
};
|
||||
|
||||
const options = regionData
|
||||
|
||||
const selectedOptions = (value) => {
|
||||
customer.region = value.toString()
|
||||
}
|
||||
|
||||
return {
|
||||
data,
|
||||
columns,
|
||||
onSearch,
|
||||
visible,
|
||||
disabled,
|
||||
onSelectChange,
|
||||
onSearch,
|
||||
customer,
|
||||
title,
|
||||
visible,
|
||||
operation,
|
||||
onCustomers,
|
||||
onCreate,
|
||||
onEdit,
|
||||
modalFormRef,
|
||||
onSave,
|
||||
onCancel,
|
||||
onDelete,
|
||||
getCustomerList,
|
||||
keyWord,
|
||||
options,
|
||||
region,
|
||||
onPagination,
|
||||
pagination,
|
||||
selectedOptions,
|
||||
};
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ant-table-striped :deep(.table-striped) td {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-card class="card">
|
||||
<a-statistic :value="data.customers" style="margin-right: 50px">
|
||||
<template #title>
|
||||
<span>全部客户</span>
|
||||
<a-tooltip placement="right">
|
||||
<template #title>
|
||||
<span>客户数量,单位(人)</span>
|
||||
</template>
|
||||
<question-circle-two-tone style="margin-left: 5px" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="card">
|
||||
<a-statistic :value="data.contracts" style="margin-right: 50px">
|
||||
<template #title>
|
||||
<span>全部合同</span>
|
||||
<a-tooltip placement="right">
|
||||
<template #title>
|
||||
<span>合同数量,单位(份)</span>
|
||||
</template>
|
||||
<question-circle-two-tone style="margin-left: 5px" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="card">
|
||||
<a-statistic :value="data.contractAmount" style="margin-right: 50px">
|
||||
<template #title>
|
||||
<span>合同金额</span>
|
||||
<a-tooltip placement="right">
|
||||
<template #title>
|
||||
<span>实际完成合同金额,单位(元)</span>
|
||||
</template>
|
||||
<question-circle-two-tone style="margin-left: 5px" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="card">
|
||||
<a-statistic :value="data.products" style="margin-right: 50px">
|
||||
<template #title>
|
||||
<span>全部产品</span>
|
||||
<a-tooltip placement="right">
|
||||
<template #title>
|
||||
<span>产品数量,单位(个)</span>
|
||||
</template>
|
||||
<question-circle-two-tone style="margin-left: 5px" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-card class="card">
|
||||
<div id="main" style="width: 100%; height: 400px"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { QuestionCircleTwoTone } from '@ant-design/icons-vue'
|
||||
import * as echarts from "echarts";
|
||||
import { reactive, onMounted } from 'vue';
|
||||
import { getSummary } from "../api/dashboard";
|
||||
import { getUserInfo } from "../api/user";
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
QuestionCircleTwoTone
|
||||
},
|
||||
setup() {
|
||||
onMounted(() => {
|
||||
checkVersion();
|
||||
initChart();
|
||||
});
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const data = reactive({
|
||||
customers: 0,
|
||||
contracts: 0,
|
||||
contractAmount: 0.00,
|
||||
products: 0,
|
||||
})
|
||||
|
||||
const initChart = () => {
|
||||
getSummary().then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
data.customers = res.data.data.customers
|
||||
data.contracts = res.data.data.contracts
|
||||
data.contractAmount = res.data.data.contractAmount
|
||||
data.products = res.data.data.products
|
||||
echarts.init(document.getElementById("main")).setOption({
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: res.data.data.date
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
},
|
||||
legend: {
|
||||
data: ['实际完成金额']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '实际完成金额',
|
||||
data: res.data.data.amount,
|
||||
type: 'line',
|
||||
smooth: true
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 查看用户系统版本
|
||||
const checkVersion = () => {
|
||||
getUserInfo().then((res) => {
|
||||
if (res.data.code == 0 && res.data.data.version == 1) {
|
||||
router.push('/result')
|
||||
}
|
||||
})
|
||||
}
|
||||
return {
|
||||
data,
|
||||
initChart,
|
||||
checkVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
margin-top: 20px;
|
||||
border: none;
|
||||
box-shadow: 0 1px 16px 0 rgb(33 41 48 / 5%);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<a-result status="404" title="404" sub-title="抱歉,您访问的页面不存在。">
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="refresh">刷新试试</a-button>
|
||||
</template>
|
||||
</a-result>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from '../router/index';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const refresh = () => {
|
||||
router.push('/')
|
||||
}
|
||||
return {
|
||||
refresh
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,351 @@
|
||||
<template>
|
||||
<a-layout style="min-height: 100vh">
|
||||
<a-layout-sider width="180" class="sider" v-model:collapsed="collapsed" :trigger="null" collapsible>
|
||||
<div class="logo">
|
||||
<div><img src="../assets/logo.svg" style="width: 32px;height: 32px;filter: drop-shadow(2px 2px 6px #79bbff);" />
|
||||
</div>
|
||||
<div v-if="collapsed == false" class="title"><b>Z</b>O<b style="color: #1283FF;">C</b>RM</div>
|
||||
</div>
|
||||
<a-menu style="border-right: none;" v-model:selectedKeys="selectedKeys" mode="inline">
|
||||
<a-menu-item :key="item.key" v-for="item in menuItem">
|
||||
<router-link :to="item.to">
|
||||
<component :is="item.icon" />
|
||||
<span>{{ item.name }}</span>
|
||||
</router-link>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-layout-sider>
|
||||
<a-layout>
|
||||
<a-layout-header class="header">
|
||||
<div>
|
||||
<menu-unfold-outlined v-if="collapsed" class="trigger" @click="() => (collapsed = !collapsed)" />
|
||||
<menu-fold-outlined v-else class="trigger" @click="() => (collapsed = !collapsed)" />
|
||||
</div>
|
||||
<div style="display: flex;align-items: center;justify-items: center;">
|
||||
<a-tooltip title="帮助">
|
||||
<a-button type="text" shape="circle">
|
||||
<template #icon>
|
||||
<QuestionCircleFilled style="color: #909399; font-size: 18px;" />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="消息">
|
||||
<a-button type="text" shape="circle">
|
||||
<template #icon>
|
||||
<BellFilled style="color: #909399; font-size: 20px;" />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<!-- 个人信息 -->
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-avatar @click="toMine" class="avatar" :size="28">U</a-avatar>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="0">
|
||||
<a @click="onMail">
|
||||
<MailOutlined /> 修改邮箱
|
||||
</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="1">
|
||||
<a @click="onDelete">
|
||||
<ClearOutlined /> 注销账号
|
||||
</a>
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="2">
|
||||
<a @click="onLogout">
|
||||
<LogoutOutlined /> 退出账号
|
||||
</a>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<!-- 修改邮箱弹出框 -->
|
||||
<a-modal v-model:visible="visible" title="修改邮箱" @ok="onSave" @cancel="onCancel" cancelText="取消" okText="保存"
|
||||
width="450px" style="top: 120px">
|
||||
<a-form :model="user" layout="vertical" @finish="onSubmit">
|
||||
<a-form-item name="email" :rules="[{ required: true, message: '请输入邮箱!' }]">
|
||||
<a-input v-model:value="user.email" size="large" placeholder="邮箱" disabled />
|
||||
</a-form-item>
|
||||
<a-form-item name="code" :rules="[{ required: true, message: '请输入验证码!' }]">
|
||||
<a-input v-model:value="user.code" size="large" style="width: 55%;" placeholder="验证码" />
|
||||
<a-button @click="onGetCode" size="large" style="width: 40%;float: right;" :disabled="disabled">
|
||||
{{ buttonText }}</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item name="newEmail" :rules="[{ required: true, message: '请输入新邮箱!' }]">
|
||||
<a-input v-model:value="user.newEmail" size="large" placeholder="新邮箱" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<!-- 注销账号弹出框 -->
|
||||
<a-modal v-model:visible="delUserVisible" title="注销账号" @ok="onConfirm" @cancel="onCancel" cancelText="取消"
|
||||
okText="注销" width="450px" style="top: 120px">
|
||||
<a-form :model="user" layout="vertical" @finish="onSubmit">
|
||||
<a-form-item name="email" :rules="[{ required: true, message: '请输入邮箱!' }]">
|
||||
<a-input v-model:value="user.email" size="large" placeholder="邮箱" disabled />
|
||||
</a-form-item>
|
||||
<a-form-item name="code" :rules="[{ required: true, message: '请输入验证码!' }]">
|
||||
<a-input v-model:value="user.code" size="large" style="width: 55%;" placeholder="验证码" />
|
||||
<a-button @click="onGetCode" size="large" style="width: 40%;float: right;" :disabled="disabled">
|
||||
{{ buttonText }}</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</a-layout-header>
|
||||
<a-layout-content :style="{ margin: '10px', padding: '18px 18px 12px 18px', background: '#fff' }">
|
||||
<transition name="fade">
|
||||
<router-view v-slot="{ Component }">
|
||||
<component :is="Component" />
|
||||
</router-view>
|
||||
</transition>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router'
|
||||
import { message } from 'ant-design-vue';
|
||||
import { getUserInfo, updateMail, getVerifyCode, userDelete, userLogout } from '../api/user';
|
||||
import { DashboardOutlined, SmileOutlined, MehOutlined, ShoppingOutlined } from '@ant-design/icons-vue';
|
||||
import { CrownOutlined, MenuUnfoldOutlined, MenuFoldOutlined, QuestionCircleFilled } from '@ant-design/icons-vue';
|
||||
import { SmileFilled, BellFilled, MailOutlined, ClearOutlined } from '@ant-design/icons-vue';
|
||||
import { LogoutOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DashboardOutlined,
|
||||
SmileOutlined,
|
||||
MehOutlined,
|
||||
ShoppingOutlined,
|
||||
CrownOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
MenuFoldOutlined,
|
||||
QuestionCircleFilled,
|
||||
SmileFilled,
|
||||
BellFilled,
|
||||
MailOutlined,
|
||||
ClearOutlined,
|
||||
LogoutOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
},
|
||||
setup() {
|
||||
// 菜单选项
|
||||
const menuItem = reactive([{
|
||||
key: "dashboard",
|
||||
to: "/dashboard",
|
||||
icon: "dashboard-outlined",
|
||||
name: "仪表盘"
|
||||
}, {
|
||||
key: "customer",
|
||||
to: "/customer",
|
||||
icon: "smile-outlined",
|
||||
name: "客户"
|
||||
}, {
|
||||
key: "contract",
|
||||
to: "/contract",
|
||||
icon: "meh-outlined",
|
||||
name: "合同"
|
||||
}, {
|
||||
key: "product",
|
||||
to: "/product",
|
||||
icon: "shopping-outlined",
|
||||
name: "产品"
|
||||
}, {
|
||||
key: "subscribe",
|
||||
to: "/subscribe",
|
||||
icon: "crown-outlined",
|
||||
name: "订阅"
|
||||
}])
|
||||
|
||||
const selectedKeys = ref(['dashboard'])
|
||||
const collapsed = ref(false)
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const user = reactive({
|
||||
name: undefined,
|
||||
email: undefined,
|
||||
verison: undefined,
|
||||
code: undefined,
|
||||
newEmail: undefined
|
||||
})
|
||||
|
||||
const visible = ref(false)
|
||||
const visibleLogo = ref(false)
|
||||
const delUserVisible = ref(false)
|
||||
const disabled = ref(false)
|
||||
const buttonText = ref('获取验证码')
|
||||
|
||||
// 初始化数据
|
||||
onMounted(() => { userInfo() })
|
||||
|
||||
// 获取用户信息
|
||||
const userInfo = () => {
|
||||
getUserInfo().then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
user.name = res.data.data.name
|
||||
user.email = res.data.data.email
|
||||
user.version = res.data.data.version
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 点击升级到专业版
|
||||
const onUpgrade = () => {
|
||||
router.push('/subscribe')
|
||||
}
|
||||
|
||||
// 点击修改邮箱
|
||||
const onMail = () => {
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
// 确认修改邮箱
|
||||
const onSave = () => {
|
||||
let param = {
|
||||
email: user.email,
|
||||
code: user.code,
|
||||
newEmail: user.newEmail
|
||||
}
|
||||
updateMail(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
router.push('/')
|
||||
message.success('修改成功,请重新登录!')
|
||||
}
|
||||
if (res.data.code == 10005) {
|
||||
message.error('验证码错误');
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 点击获取验证码
|
||||
const onGetCode = () => {
|
||||
if (user.email == '') {
|
||||
message.warn('邮箱不能为空')
|
||||
}
|
||||
let param = {
|
||||
email: user.email
|
||||
}
|
||||
getVerifyCode(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
disabled.value = true
|
||||
buttonText.value = '验证码已发送'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 点击注销账号
|
||||
const onDelete = () => {
|
||||
delUserVisible.value = true
|
||||
}
|
||||
|
||||
// 点击确认注销账号
|
||||
const onConfirm = () => {
|
||||
let param = {
|
||||
email: user.email,
|
||||
code: user.code
|
||||
}
|
||||
userDelete(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
router.push('/')
|
||||
message.success('账号已注销')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 点击退出账号
|
||||
const onLogout = () => {
|
||||
userLogout().then((res) => {
|
||||
if (res.data.code == 0) { router.push('/') }
|
||||
})
|
||||
}
|
||||
|
||||
// 点击取消按钮
|
||||
const onCancel = () => {
|
||||
modalFormRef.value.resetFields()
|
||||
visible.value = false
|
||||
delUserVisible.value = false
|
||||
};
|
||||
|
||||
return {
|
||||
menuItem,
|
||||
selectedKeys,
|
||||
collapsed,
|
||||
user,
|
||||
visible,
|
||||
visibleLogo,
|
||||
delUserVisible,
|
||||
disabled,
|
||||
buttonText,
|
||||
userInfo,
|
||||
onDelete,
|
||||
onLogout,
|
||||
onMail,
|
||||
onGetCode,
|
||||
onSave,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
onUpgrade,
|
||||
};
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sider {
|
||||
background: #fff;
|
||||
border-right: 0.5px solid #F0F2F5;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.trigger {
|
||||
font-size: 18px;
|
||||
padding: 0 8px;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.trigger:hover {
|
||||
color: #476FFF;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 32px;
|
||||
margin: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
color: #f56a00;
|
||||
background-color: #fde3cf;
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.popover {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 25px;
|
||||
color: rgba(31, 31, 31, 0.85);
|
||||
font-weight: 620;
|
||||
margin-left: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<img src="../assets/logo.svg" style="width: 60px;height: 60px;filter: drop-shadow(2px 2px 6px #79bbff);" />
|
||||
<span class="title">ZO<b style="color: #1283FF;">C</b>RM</span>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="main">
|
||||
<transition name="fade">
|
||||
<router-view v-slot="{ Component }">
|
||||
<component :is="Component" />
|
||||
</router-view>
|
||||
</transition>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="links">
|
||||
<a href="">许可证</a>
|
||||
<a href="https://mail.qq.com/">企鹅邮箱</a>
|
||||
</div>
|
||||
<div class="copyright">Copyright © 2022 Zocrm.cloud</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: #F0F2F5;
|
||||
}
|
||||
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 30%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 368px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
padding: 0 16px;
|
||||
margin: 80px 0 24px;
|
||||
text-align: center;
|
||||
color: rgba(0, 0, 0, .45);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.links {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.links a {
|
||||
color: rgba(0, 0, 0, .45);
|
||||
padding: 5px 20px;
|
||||
}
|
||||
|
||||
.links a:hover {
|
||||
color: #5b5b5c;
|
||||
}
|
||||
|
||||
.copyright {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 42px;
|
||||
color: rgba(31, 31, 31, 0.85);
|
||||
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
|
||||
font-weight: 600;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
padding: 20px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<a-form :model="formData" name="normal_login" class="login-form" @finish="onLogin" @finishFailed="onLoginFailed">
|
||||
<a-form-item name="email" :rules="[{ required: true, message: '请输入邮箱!' }]">
|
||||
<a-input v-model:value="formData.email" size="large" placeholder="邮箱">
|
||||
<template #prefix>
|
||||
<UserOutlined class="site-form-item-icon" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="password" :rules="[{ required: true, message: '请输入密码!' }]">
|
||||
<a-input-password v-model:value="formData.password" size="large" placeholder="密码">
|
||||
<template #prefix>
|
||||
<LockOutlined class="site-form-item-icon" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-form-item name="remember" no-style>
|
||||
<a-checkbox v-model:checked="formData.remember" style="float: left;">记住我</a-checkbox>
|
||||
</a-form-item>
|
||||
<a class="login-form-forgot" style="float: right;" @click="forgotPass">忘记密码?</a>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button size="large" type="primary" html-type="submit" class="login-form-button" style="width: 100%">
|
||||
登录
|
||||
</a-button><br />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
还没有账号?<a @click="toRegister"> 立即注册</a>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive } from 'vue';
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue';
|
||||
import { useRouter } from 'vue-router'
|
||||
import { userLogin } from '../api/user';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
UserOutlined,
|
||||
LockOutlined,
|
||||
},
|
||||
setup() {
|
||||
const router = useRouter()
|
||||
|
||||
// 用户登录
|
||||
const formData = reactive({
|
||||
email: '',
|
||||
password: '',
|
||||
remember: true,
|
||||
});
|
||||
const onLogin = () => {
|
||||
let param = {
|
||||
email: formData.email,
|
||||
password: formData.password
|
||||
}
|
||||
userLogin(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
localStorage.setItem('uid', res.data.data.uid)
|
||||
localStorage.setItem('ver', res.data.data.ver)
|
||||
localStorage.setItem('token', res.data.data.token)
|
||||
router.push("/home")
|
||||
}
|
||||
if (res.data.code == 10002) {
|
||||
message.error('用户不存在');
|
||||
}
|
||||
if (res.data.code == 10003) {
|
||||
message.error('用户名或密码错误');
|
||||
}
|
||||
})
|
||||
};
|
||||
const onLoginFailed = errorInfo => {
|
||||
console.log('Failed:', errorInfo);
|
||||
};
|
||||
|
||||
// 忘记密码
|
||||
const forgotPass = () => {
|
||||
router.push("/pass")
|
||||
}
|
||||
|
||||
// 用户注册
|
||||
const toRegister = () => {
|
||||
router.push("/register")
|
||||
}
|
||||
|
||||
return {
|
||||
formData,
|
||||
onLogin,
|
||||
onLoginFailed,
|
||||
forgotPass,
|
||||
toRegister
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.site-form-item-icon {
|
||||
color: rgba(0, 0, 0, .45);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<a-form :model="formData" layout="vertical" @finish="onSubmit">
|
||||
<a-form-item name="email" :rules="[{ required: true, message: '请输入邮箱!' }]">
|
||||
<a-input v-model:value="formData.email" size="large" placeholder="邮箱" />
|
||||
</a-form-item>
|
||||
<a-form-item name="code" :rules="[{ required: true, message: '请输入验证码!' }]">
|
||||
<a-input v-model:value="formData.code" size="large" style="width: 55%;" placeholder="验证码" />
|
||||
<a-button @click="onGetCode" size="large" :disabled="disabled" style="width: 40%;float: right;">
|
||||
{{ buttonText }}</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item name="password1" :rules="[{ required: true, message: '请输入密码!' }]">
|
||||
<a-input-password v-model:value="formData.password1" size="large" placeholder="密码" />
|
||||
</a-form-item>
|
||||
<a-form-item name="password2" :rules="[{ required: true, message: '请输入密码!' }]">
|
||||
<a-input-password v-model:value="formData.password2" size="large" placeholder="确认密码" />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" html-type="submit" size="large" style="width: 50%;">确定</a-button>
|
||||
<a-button type="link" style="width: 50%;" @click="onLogin">使用已有账户登录</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { userForgotPass, getVerifyCode } from '../api/user'
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const router = useRouter()
|
||||
|
||||
// 重置密码
|
||||
const formData = reactive({
|
||||
email: '',
|
||||
code: '',
|
||||
password1: '',
|
||||
password2: '',
|
||||
});
|
||||
|
||||
const disabled = ref(false)
|
||||
const buttonText = ref('获取验证码')
|
||||
|
||||
const onSubmit = () => {
|
||||
let param = {
|
||||
email: formData.email,
|
||||
code: formData.code,
|
||||
password: formData.password2,
|
||||
}
|
||||
userForgotPass(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
message.success('密码已重置')
|
||||
router.push("/login")
|
||||
}
|
||||
if (res.data.code == 10005) {
|
||||
message.error('验证码错误');
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
// 获取验证码
|
||||
const onGetCode = () => {
|
||||
if (formData.email == '') {
|
||||
message.warn('邮箱不能为空')
|
||||
}
|
||||
let param = {
|
||||
email: formData.email
|
||||
}
|
||||
getVerifyCode(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
disabled.value = true
|
||||
buttonText.value = '验证码已发送'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转到登录页面
|
||||
const onLogin = () => {
|
||||
router.push("/login")
|
||||
}
|
||||
|
||||
return {
|
||||
formData,
|
||||
disabled,
|
||||
buttonText,
|
||||
onSubmit,
|
||||
onGetCode,
|
||||
onLogin,
|
||||
};
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,350 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-space style="margin-bottom: 20px; width: 100%;">
|
||||
<a-input v-model:value="keyWord" placeholder="产品名称" style="width: 280px; margin-right: 50px;">
|
||||
<template #suffix>
|
||||
<search-outlined style="color: rgba(0, 0, 0, 0.45)" @click="onSearch" />
|
||||
</template>
|
||||
</a-input>
|
||||
<a-button type="primary" @click="onProducts">全部产品</a-button>
|
||||
<a-button type="primary" @click="onDelete" :disabled="disabled" danger>删除</a-button>
|
||||
<a-button type="primary" @click="onCreate">新建</a-button>
|
||||
</a-space>
|
||||
<a-table rowKey="id" :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }"
|
||||
:columns="columns" :data-source="data.productList"
|
||||
:pagination="{ current: pagination.current, pageSize: pagination.pageSize, total: pagination.total, onChange: onPagination }"
|
||||
:scroll="{ y: '59vh' }" class="ant-table-striped"
|
||||
:row-class-name="(_record, index) => (index % 2 === 1 ? 'table-striped' : null)" bordered>
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<template v-if="column.dataIndex === 'name'">
|
||||
<a @click="onEdit(record)">{{ text }}</a>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'status'">
|
||||
<a-tag v-if="text == 1" color="green">上架</a-tag>
|
||||
<a-tag v-if="text == 2" color="blue">下架</a-tag>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'type'">
|
||||
<span v-if="text == 1">默认</span>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'price'">
|
||||
<span style="color: #ff991f">{{ text }}</span>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 新建、编辑产品 -->
|
||||
<a-modal v-model:visible="visible" :title="title" @ok="onSave" @cancel="onCancel" cancelText="取消" okText="保存"
|
||||
width="800px" style="top: 80px">
|
||||
<a-form ref="productForm" :model="product" layout="vertical" name="product">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="产品名称" name="name" :rules="[{ required: true, message: '请输入产品名称' }]">
|
||||
<a-input v-model:value="product.name" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="产品类型" name="type" :rules="[{
|
||||
required: true, message: '请选择产品类型',
|
||||
}]">
|
||||
<a-select v-model:value="product.type" placeholder="请选择">
|
||||
<a-select-option :value="1">默认</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="产品单位" name="unit">
|
||||
<a-select v-model:value="product.unit" placeholder="请选择">
|
||||
<a-select-option value="个">个</a-select-option>
|
||||
<a-select-option value="只">只</a-select-option>
|
||||
<a-select-option value="块">块</a-select-option>
|
||||
<a-select-option value="瓶">瓶</a-select-option>
|
||||
<a-select-option value="盒">盒</a-select-option>
|
||||
<a-select-option value="台">台</a-select-option>
|
||||
<a-select-option value="箱">箱</a-select-option>
|
||||
<a-select-option value="吨">吨</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="产品编码" name="code" :rules="[{ required: true, message: '请输入产品编码' }]">
|
||||
<a-input v-model:value="product.code" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="价格" name="price" :rules="[{ required: true, message: '请输入产品价格' }]">
|
||||
<a-input-number v-model:value="product.price" style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="是否上下架" name="status" :rules="[{ required: true, message: '请选择是否上下架' }]">
|
||||
<a-select v-model:value="product.status" placeholder="请选择">
|
||||
<a-select-option :value="1">上架</a-select-option>
|
||||
<a-select-option :value="2">下架</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="产品描述" name="description">
|
||||
<a-textarea v-model:value="product.description" :rows="4" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, onMounted, createVNode } from 'vue';
|
||||
import { SearchOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import moment from 'moment'
|
||||
import { createProduct, updateProduct, queryProductList, deleteProduct, queryProductInfo } from '../api/product';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SearchOutlined,
|
||||
},
|
||||
setup() {
|
||||
// 表格字段
|
||||
const columns = [{
|
||||
title: '产品名称',
|
||||
dataIndex: 'name',
|
||||
width: 100,
|
||||
fixed: 'left',
|
||||
ellipsis: true,
|
||||
}, {
|
||||
title: '是否上下架',
|
||||
dataIndex: 'status',
|
||||
width: 120,
|
||||
}, {
|
||||
title: '产品类型',
|
||||
dataIndex: 'type',
|
||||
width: 100,
|
||||
}, {
|
||||
title: '产品单位',
|
||||
dataIndex: 'unit',
|
||||
width: 100,
|
||||
}, {
|
||||
title: '产品编码',
|
||||
dataIndex: 'code',
|
||||
width: 150,
|
||||
}, {
|
||||
title: '价格',
|
||||
dataIndex: 'price',
|
||||
width: 150,
|
||||
}, {
|
||||
title: '产品描述',
|
||||
dataIndex: 'description',
|
||||
width: 240,
|
||||
ellipsis: true,
|
||||
}, {
|
||||
title: '创建时间',
|
||||
dataIndex: 'created',
|
||||
width: 185,
|
||||
customRender: text => {
|
||||
let m = moment(text.value * 1000).format('YYYY-MM-DD HH:mm:ss')
|
||||
return m == 'Invalid date' ? '' : m
|
||||
}
|
||||
}, {
|
||||
title: '更新时间',
|
||||
dataIndex: 'updated',
|
||||
width: 185,
|
||||
customRender: text => {
|
||||
let m = moment(text.value * 1000).format('YYYY-MM-DD HH:mm:ss')
|
||||
return m == 'Invalid date' ? '' : m
|
||||
}
|
||||
}];
|
||||
|
||||
// 产品属性
|
||||
let product = reactive({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
type: undefined,
|
||||
unit: undefined,
|
||||
code: undefined,
|
||||
price: undefined,
|
||||
status: undefined,
|
||||
description: undefined,
|
||||
});
|
||||
|
||||
// 产品列表
|
||||
const data = reactive({
|
||||
productList: [],
|
||||
selectedIds: []
|
||||
})
|
||||
|
||||
// 表格分页
|
||||
let pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: undefined,
|
||||
})
|
||||
|
||||
const title = ref('');
|
||||
const visible = ref(false);
|
||||
const disabled = ref(true)
|
||||
const operation = ref(0);
|
||||
const productForm = ref();
|
||||
const keyWord = ref('')
|
||||
|
||||
// 初始化数据
|
||||
onMounted(() => { getProductList() })
|
||||
|
||||
// 表格记录多选
|
||||
const onSelectChange = selectedRowKeys => {
|
||||
data.selectedIds = selectedRowKeys
|
||||
if (data.selectedIds.length !== 0) {
|
||||
disabled.value = false
|
||||
} else {
|
||||
disabled.value = true
|
||||
}
|
||||
};
|
||||
|
||||
// 点击搜索
|
||||
const onSearch = () => { getProductList() };
|
||||
|
||||
// 点击全部产品
|
||||
const onProducts = () => {
|
||||
keyWord.value = ''
|
||||
getProductList()
|
||||
}
|
||||
|
||||
// 点击新建产品
|
||||
const onCreate = () => {
|
||||
title.value = '新建产品'
|
||||
visible.value = true
|
||||
operation.value = 1
|
||||
}
|
||||
|
||||
// 点击产品名称
|
||||
const onEdit = (row) => {
|
||||
title.value = '编辑产品'
|
||||
visible.value = true
|
||||
operation.value = 2
|
||||
let param = { id: row.id }
|
||||
queryProductInfo(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
let p = res.data.data
|
||||
product.id = p.id
|
||||
product.name = p.name
|
||||
product.type = p.type
|
||||
product.unit = p.unit
|
||||
product.code = p.code
|
||||
product.price = p.price
|
||||
product.status = p.status
|
||||
product.description = p.description
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 点击保存产品
|
||||
const onSave = () => {
|
||||
modalFormRef.value.validateFields().then(() => {
|
||||
if (operation.value == 1) {
|
||||
createProduct(product).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
message.success('保存成功')
|
||||
getProductList()
|
||||
}
|
||||
})
|
||||
}
|
||||
if (operation.value == 2) {
|
||||
updateProduct(product).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
message.success('保存成功')
|
||||
getProductList()
|
||||
}
|
||||
})
|
||||
}
|
||||
modalFormRef.value.resetFields()
|
||||
visible.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 点击删除产品
|
||||
const onDelete = () => {
|
||||
Modal.confirm({
|
||||
title: '确定删除选中的' + data.selectedIds.length + '项吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
centered: true,
|
||||
cancelText: '取消',
|
||||
okText: '确定',
|
||||
onOk() {
|
||||
deleteProduct({ ids: data.selectedIds }).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
getProductList()
|
||||
disabled.value = true
|
||||
message.success('删除成功')
|
||||
}
|
||||
})
|
||||
},
|
||||
onCancel() {
|
||||
console.log('Cancel');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 分页查询产品列表
|
||||
const onPagination = (page) => {
|
||||
pagination.current = page
|
||||
getProductList()
|
||||
}
|
||||
|
||||
// 查询产列表
|
||||
const getProductList = () => {
|
||||
let param = {
|
||||
name: keyWord.value,
|
||||
pageNum: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
}
|
||||
queryProductList(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
pagination.total = res.data.data.total
|
||||
data.productList = res.data.data.list
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 点击取消按钮
|
||||
const onCancel = () => {
|
||||
productForm.value.resetFields()
|
||||
visible.value = false
|
||||
};
|
||||
|
||||
return {
|
||||
columns,
|
||||
data,
|
||||
onSelectChange,
|
||||
onSearch,
|
||||
product,
|
||||
title,
|
||||
visible,
|
||||
disabled,
|
||||
operation,
|
||||
onProducts,
|
||||
onCreate,
|
||||
onEdit,
|
||||
productForm,
|
||||
onSave,
|
||||
onCancel,
|
||||
onDelete,
|
||||
getProductList,
|
||||
keyWord,
|
||||
pagination,
|
||||
onPagination,
|
||||
};
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ant-table-striped :deep(.table-striped) td {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<a-form :model="formData" layout="vertical" @finish="onRegister">
|
||||
<a-form-item name="email" :rules="[{ required: true, message: '请输入邮箱!' }]">
|
||||
<a-input v-model:value="formData.email" size="large" placeholder="邮箱">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="code" :rules="[{ required: true, message: '请输入验证码!' }]">
|
||||
<a-input v-model:value="formData.code" size="large" style="width: 55%;" placeholder="验证码" />
|
||||
<a-button @click="onGetCode" size="large" style="width: 40%;float: right;" :disabled="disabled">
|
||||
{{ buttonText }}</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item name="password1" :rules="[{ required: true, message: '请输入密码!' }]">
|
||||
<a-input-password v-model:value="formData.password1" size="large" placeholder="密码" />
|
||||
</a-form-item>
|
||||
<a-form-item name="password2" :rules="[{ required: true, message: '请输入密码!' }]">
|
||||
<a-input-password v-model:value="formData.password2" size="large" placeholder="确认密码" />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" html-type="submit" size="large" style="width: 50%;">注册</a-button>
|
||||
<a-button type="link" style="width: 50%;" @click="onLogin">使用已有账户登录</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { userRegister, getVerifyCode } from '../api/user';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const router = useRouter()
|
||||
|
||||
// 用户注册
|
||||
const formData = reactive({
|
||||
email: '',
|
||||
code: '',
|
||||
password1: '',
|
||||
password2: '',
|
||||
});
|
||||
|
||||
const disabled = ref(false)
|
||||
const buttonText = ref('获取验证码')
|
||||
|
||||
const onRegister = () => {
|
||||
if (formData.password1 != formData.password2) {
|
||||
message.info('密码不一致');
|
||||
return
|
||||
}
|
||||
let param = {
|
||||
email: formData.email,
|
||||
code: formData.code,
|
||||
password: formData.password2,
|
||||
}
|
||||
userRegister(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
message.success('注册成功');
|
||||
onLogin()
|
||||
}
|
||||
if (res.data.code == 10001) {
|
||||
message.warn('该用户已经存在');
|
||||
}
|
||||
if (res.data.code == 10005) {
|
||||
message.error('验证码错误');
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
// 获取验证码
|
||||
const onGetCode = () => {
|
||||
if (formData.email == '') {
|
||||
message.warn('邮箱不能为空')
|
||||
}
|
||||
let param = {
|
||||
email: formData.email
|
||||
}
|
||||
getVerifyCode(param).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
disabled.value = true
|
||||
buttonText.value = '验证码已发送'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转到登录页面
|
||||
const onLogin = () => {
|
||||
router.push("/login")
|
||||
}
|
||||
|
||||
return {
|
||||
formData,
|
||||
disabled,
|
||||
buttonText,
|
||||
onRegister,
|
||||
onLogin,
|
||||
onGetCode,
|
||||
};
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div style="margin-top: 50px;">
|
||||
<a-result status="403" sub-title="您需要订阅个人版才能使用该功能哦!">
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="onBuy">立即订阅</a-button>
|
||||
</template>
|
||||
</a-result>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const onBuy = () => {
|
||||
router.push('/subscribe')
|
||||
}
|
||||
return {
|
||||
onBuy
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div style="margin: 20vh auto;">
|
||||
<a-row :gutter="30">
|
||||
<a-col :span="6" :offset="6">
|
||||
<a-card class="card" :bordered="false">
|
||||
<h2 class="title">免费版
|
||||
<check-circle-filled v-if="ver == 1" class="icon" />
|
||||
</h2>
|
||||
<div class="content">满足基础功能需求,永久免费</div><br />
|
||||
<div class="price">¥0</div><br />
|
||||
<a-button size="large" class="btn">免费使用</a-button>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="card" :bordered="false">
|
||||
<h2 class="title">个人版
|
||||
<check-circle-filled v-if="ver == 2" class="icon" />
|
||||
</h2>
|
||||
<div class="content">能力不设限,新功能优先体验</div><br />
|
||||
<div class="price">¥28<span style="font-size: 20px;font-weight: 500;"> / 月</span></div><br />
|
||||
<a-button v-if="ver == 1" type="primary" size="large" @click="onBuy" style="width: 100%;">立即订阅
|
||||
</a-button>
|
||||
<a-button v-if="ver == 2" type="primary" size="large" style="width: 100%;">{{ expired }} 到期
|
||||
</a-button>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { CheckCircleFilled } from '@ant-design/icons-vue';
|
||||
import { getUserInfo, userBuy } from '../api/user';
|
||||
import moment from 'moment'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
CheckCircleFilled
|
||||
},
|
||||
setup() {
|
||||
|
||||
const ver = ref(1)
|
||||
const expired = ref(undefined)
|
||||
|
||||
// 初始化数据
|
||||
onMounted(() => { sysVersion() })
|
||||
|
||||
// 点击订阅
|
||||
const onBuy = () => {
|
||||
userBuy().then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
ver.value = res.data.data.version
|
||||
expired.value = moment(res.data.data.expired * 1000).format('YYYY-MM-DD')
|
||||
message.success('恭喜你!订阅成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取用户系统版本
|
||||
const sysVersion = () => {
|
||||
getUserInfo().then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
ver.value = res.data.data.version
|
||||
expired.value = moment(res.data.data.expired * 1000).format('YYYY-MM-DD')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
ver,
|
||||
expired,
|
||||
onBuy,
|
||||
sysVersion,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.title {
|
||||
color: #121212;
|
||||
line-height: 47px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: #3bc8b6;
|
||||
font-size: 30px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 14px;
|
||||
color: rgba(27, 27, 27, .5);
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.price {
|
||||
height: 54px;
|
||||
font-size: 40px;
|
||||
line-height: 54px;
|
||||
font-weight: 700;
|
||||
color: #212930;
|
||||
}
|
||||
|
||||
.card {
|
||||
box-shadow: 0 1px 16px 0 rgb(33 41 48 / 5%);
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
float: right;
|
||||
color: #476FFF;
|
||||
border-color: #476FFF;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
server: {
|
||||
host: '127.0.0.1',
|
||||
port: 8008
|
||||
},
|
||||
plugins: [vue()],
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
less: {
|
||||
modifyVars: {
|
||||
'primary-color': '#476FFF',
|
||||
'link-color': '#476FFF',
|
||||
'border-radius-base': '5px'
|
||||
},
|
||||
javascriptEnabled: true
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user