Commit c1b80523 by 李宁

1

1 parent 994306b9
{
"permissions": {
"allow": [
"Bash(grep -E \"\\.(js|vue|html)$\")",
"Bash(npm install axios)"
],
"deny": [],
"ask": []
}
}
\ No newline at end of file
......@@ -18,6 +18,7 @@
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"@tailwindcss/typography": "^0.5.19",
"axios": "^1.13.2",
"date-fns": "^4.1.0",
"element-plus": "^2.11.7",
"lucide-vue-next": "^0.552.0",
......
import request from '../../request'
/**
* 查询账号列表
*/
export function queryAccountList(data) {
return request({
url: '/compass/api/system/account/page',
data,
})
}
/**
* 查询角色列表
*/
export function queryRoleList(data) {
return request({
url: '/compass/api/common/enums/account-roles',
method: 'GET'
})
}
/**
* 删除账号
*/
export function deleteRole(data) {
return request({
url: '/compass/api/system/account/delete',
data,
})
}
/**
* 添加/更新账号
*/
export function addAndUpdateRole(data) {
return request({
url: '/compass/api/system/account' + (data.id?'/update':'/create'),
data,
})
}
\ No newline at end of file
import request from '../../request'
/**
* 根据级别查询区域列表
* 1-省级,2-市级,3-区级,4-网格级
*/
export function queryLevelAllArea(data) {
return request({
url: '/compass/api/common/areas/level?areaLevel='+data.areaLevel+'&parentAreaCode='+data.parentAreaCode,
method: 'GET'
})
}
/**
* 当前用户权限获取下级区域层级结构
*/
export function queryUserArea(data) {
return request({
url: '/compass/api/system/area/permission/hierarchy',
data,
})
}
/**
* 商机状态列表
*/
export function queryBusiStatus(data) {
return request({
url: '/compass/api/common/enums/opportunity-statuses',
method: 'GET',
data,
})
}
\ No newline at end of file
import * as account from './account'
import * as common from './common'
import * as login from './login'
import * as order from './order'
import * as reward from './reward'
import * as role from './role'
export default {
...account,
...common,
...login,
...order,
...reward,
...role
}
\ No newline at end of file
import request from '../../request'
/**
* 退出登录
*/
export function logout() {
return request({
url: '/compass/api/auth/logout',
data: {}
})
}
/**
* 手机账号登录
*/
export function pohoneLogin(data) {
return request({
url: '/crm/login',
data,
})
}
/**
* 获取图形验证码
* @param loginName
* @param password
*/
export function getImgCode(data) {
return request({
url: '/crm/getCode',
method: 'GET',
data,
})
}
/**
* 获取短信验证码
* @param loginName
* @param password
*/
export function getTelCode(data) {
return request({
url: '/crm/sendMessage',
data,
})
}
import request from '../../request'
/**
* 网格列表查询
* @returns {AxiosPromise}
*/
export function queryAllGridList(data) {
return request({
url: '/compass/api/grid/list',
data,
})
}
/**
* 获取该账号下可选的网格列表
*/
export function queryGridList(data) {
return request({
url: '/compass/api/grid/options',
data,
})
}
\ No newline at end of file
import request from '../../request'
/**
* 全部商机-列表查询
*/
export function queryAllBusi(data) {
return request({
url: '/compass/api/opportunity/page',
data,
})
}
/**
* 全部商机-商机审核
*/
export function audioBusi(data) {
return request({
url: '/compass/api/opportunity/audit',
data,
})
}
/**
* 全部商机-商机详情
*/
export function queryBusiDetail(data) {
return request({
url: '/compass/api/opportunity/detail',
data,
})
}
/**
* 全部商机-商机跟进记录查询
*/
export function queryBusiFollowList(data) {
return request({
url: '/compass/api/opportunity/follow/page',
data,
})
}
/**
* 全部商机-关闭商机
*/
export function closeBusi(data) {
return request({
url: '/compass/api/opportunity/close',
data,
})
}
/**
* 全部商机-标记成单
*/
export function dealBusi(data) {
return request({
url: '/compass/api/opportunity/deal',
data,
})
}
/**
* 全部商机-分配商机
*/
export function reassignBusi(data) {
return request({
url: '/compass/api/opportunity/reassign',
data,
})
}
/**
* 全部商机-记录管理员备注
*/
export function updateRemark(data) {
return request({
url: '/compass/api/opportunity/admin-remark',
data,
})
}
/**
* 全部商机-数据统计
*/
export function queryAllBusiStatistics(data) {
return request({
url: '/compass/api/opportunity/statistics',
data,
})
}
/**
* 全部商机-查询所有商机标签列表
*/
export function queryBusiLabelList(data) {
return request({
url: '/compass/api/opportunity/tag/page',
data,
})
}
/**
* 商机标签的开始和关闭
*/
export function updateBusiLabelStatus(data) {
return request({
url: '/compass/api/opportunity/tag/status',
data,
})
}
/**
* 商机标签的删除
*/
export function deleteBusiLabel(data) {
return request({
url: '/compass/api/opportunity/tag/delete',
data,
})
}
/**
* 商机标签的创建和更新
*/
export function createAndUpdateTag(data) {
return request({
url: '/compass/api/opportunity/tag' + (data.id?'/update':''),
data,
})
}
/**
* 商机关闭原因列表查询
*/
export function queryBusiCloseReansonList(data) {
return request({
url: '/compass/api/opportunity/close-reason/page',
data,
})
}
/**
* 商机关闭原因:根据区域列表查询
*/
export function queryAreaCloseReansonList(data) {
return request({
url: '/compass/api/common/close-reason/page',
data,
})
}
/**
* 商机关闭原因添加和更新
*/
export function busiCloseReasonUpdate(data) {
return request({
url: '/compass/api/opportunity/close-reason'+(data.id?'/update':''),
data,
})
}
/**
* 商机关闭原因删除
*/
export function busiCloseReasonDel(data) {
return request({
url: '/compass/api/opportunity/close-reason/delete',
data,
})
}
/**
* 全部商机-新增商机
*/
export function createBusi(data) {
return request({
url: '/compass/api/opportunity/create',
data,
})
}
\ No newline at end of file
import request from '../../request'
/**
* 查询人员列表
*/
export function queryAllPerson(data) {
return request({
url: '/compass/api/personnel/list',
data,
})
}
/**
* 添加人员
*/
export function addNewPerson(data) {
let url = '/compass/api/personnel/create'
if(data.id){
url = '/compass/api/personnel/update'
}
return request({
url: url,
data,
})
}
/**
* 删除人员
*/
export function deletePerson(data) {
return request({
url: '/compass/api/personnel/delete',
data,
})
}
/**
* 批量导入工维人员
*/
export function importGwPerson(data) {
return request({
url: '/compass/api/personnel/import-maintenance-preview',
data,
})
}
/**
* 批量导入营销人员
*/
export function importYxPerson(data) {
return request({
url: '/compass/api/marketing/import-preview',
data,
})
}
import axios from "axios";
import { ElMessageBox } from "element-plus";
import router from "@/router";
const service = axios.create({
baseURL: '/hallserver',
method: "post",
timeout: 150000,
withCredentials: true,
});
//请求拦截
service.interceptors.request.use(
(config) => {
if (!config.headers["Content-Type"])
config.headers["Content-Type"] = "application/json;charset=utf-8";
if (localStorage.pcUserInfo) {
let userInfo = JSON.parse(localStorage.pcUserInfo);
config.headers["x-access-token"] = userInfo.token
}
return config;
},
(error) => {
Promise.reject(error);
}
);
let ifCanShow = true; //为了防止页面有异常情况时,多个接口请求导致弹窗多次的问题
let catchFun = function (msg) {
if (!ifCanShow) {
return;
}
ifCanShow = false;
ElMessageBox.confirm(msg, "提示", {
showClose: false,
closeOnPressEscape: false,
closeOnClickModal: false,
showCancelButton: false,
}).then(() => {
ifCanShow = true;
router.push({ path: "/login" });
});
};
//响应拦截
service.interceptors.response.use(
(response) => {
console.log(response);
if (response.status == 200) {
if (response.data.code == "401") {
//登陆失效,重新登陆
catchFun("账户状态异常");
} else if (response.data.code == "133") {
//灰名单
ElMessageBox.alert(response.data.msg, "状态异常/错误提示", {
dangerouslyUseHTMLString: true,
});
} else {
if (response.data instanceof Blob) {
return new Promise(function (resolve, reject) {
var r = new FileReader();
var resData = response.data;
if (response.config.url.indexOf("poster/createPoster") >= 0) {
if (resData.type == "application/json") {
r.readAsText(resData);
} else {
r.readAsDataURL(resData);
}
} else {
r.readAsText(resData);
}
r.onload = function () {
let res = {};
//PK 为二进制压缩包(ZIP)导出数据流
if (
escape(r.result).indexOf("%u") == 0 ||
escape(r.result).indexOf("PK") == 0 ||
r.result.indexOf("pdf") >= 0 ||
r.result.indexOf("PDF") >= 0 ||
r.result.indexOf("data:image") >= 0
) {
res.type = "blob";
res.value = resData;
} else {
res.type = "object";
res.value = JSON.parse(r.result);
}
resolve(res);
};
}).catch((e) => {});
} else {
return {
url: response.config.url,
...response.data,
};
}
}
} else if (response.status == 302) {
catchFun("登陆失效,请重新登陆");
} else if (response.status == 401 || response.status == 403) {
catchFun("账户状态异常");
} else {
if (sessionStorage.notFirstIn) {
catchFun("网络异常,请稍后再试");
}
}
},
(error) => {
if (sessionStorage.notFirstIn) {
catchFun("网络异常,请稍后再试");
}
}
);
export default service;
import axios from 'axios'
import {
ElMessageBox
} from 'element-plus'
export default {
/**
* 数组元素查询——针对数组元素中object某一项值
* @param val
* @param store
* @param findKey
*/
arrayFind: function(val, store, findKey) {
let res = ''
if(!store) {
return val
}
if(!findKey) {
findKey = 'value'
}
try {
store.forEach(function(item, index) {
if(item[findKey] == val) {
let curItem = JSON.parse(JSON.stringify(item))
curItem.findIndex = index
res = curItem
throw new Error('end forEach')
}
})
} catch(e) {
if(e.message = 'end forEach') {
console.log(e.message)
}
}
return res
},
/**
* 枚举渲染
* @param ename
* @param store
* @returns {*}
*/
globalRender: function(ename, store, checkFlag) {
let val = ''
if (!checkFlag) {
val = ename
}
if (!store) {
return val
}
if (store.length != 0) {
for (let i = 0; i < store.length; i++) {
if (ename == store[i].key) {
return store[i].value || store[i].key
} else if (i == store.length - 1) {
return val;
}
}
} else {
return val;
}
},
/**
* 时间戳转字符串
* @param num
* @returns {*}
*/
detailTime: function(num) {
if (num == null) {
return '';
} else {
num = Number(num);
let d = new Date(num);
let year = d.getFullYear();
let month = d.getMonth() + 1;
let date = d.getDate();
let hour = d.getHours();
let minute = d.getMinutes();
let second = d.getSeconds();
month < 10 ? month = '0' + month : month;
date < 10 ? date = '0' + date : date;
hour < 10 ? hour = '0' + hour : hour;
minute < 10 ? minute = '0' + minute : minute;
second < 10 ? second = '0' + second : second;
return year + "-" + month + "-" + date + " " + hour + ":" + minute + ":" + second;
}
},
/**
* 文件后缀时间戳
* @param {*} num
* @returns
*/
fileTimeStamp: function(num) {
if (num == null) {
return '';
} else {
num = Number(num);
let d = new Date(num);
let year = d.getFullYear();
let month = d.getMonth() + 1;
let date = d.getDate();
let hour = d.getHours();
let minute = d.getMinutes();
let second = d.getSeconds();
month < 10 ? month = '0' + month : month;
date < 10 ? date = '0' + date : date;
hour < 10 ? hour = '0' + hour : hour;
minute < 10 ? minute = '0' + minute : minute;
second < 10 ? second = '0' + second : second;
return year + "-" + month + "-" + date + "-" + hour + "" + minute + "" + second;
}
},
/**
* 浮点数加法,用于解决精度丢失问题
* @param {*} num1
* @param {*} num2
* @returns
*/
addStr: function(num1, num2) {
var r1, r2, m;
try {
r1 = num1.toString().split('.')[1].length;
} catch (e) {
r1 = 0;
}
try {
r2 = num2.toString().split(".")[1].length;
} catch (e) {
r2 = 0;
}
m = Math.pow(10, Math.max(r1, r2));
return Math.round(num1 * m + num2 * m) / m;
},
/**
* 浮点数减法,用于解决精度丢失问题
* @param {*} arg1
* @param {*} arg2
* @returns
*/
subStr: function(arg1, arg2) {
var r1, r2, m, n;
try {
r1 = arg1.toString().split(".")[1].length
} catch (e) {
r1 = 0
}
try {
r2 = arg2.toString().split(".")[1].length
} catch (e) {
r2 = 0
}
m = Math.pow(10, Math.max(r1, r2));
n = (r1 >= r2) ? r1 : r2;
return ((arg1 * m - arg2 * m) / m).toFixed(n);
},
/**
* 浮点数乘法,用于解决精度丢失问题
* @param {*} arg1
* @param {*} arg2
* @param {*} n 保留位数
* @returns
*/
mulStr: function(arg1, arg2, n) {
var m = 0,
s1 = arg1.toString(),
s2 = arg2.toString();
try {
m += s1.split('.')[1].length
} catch (e) {
//TODO handle the exception
}
try {
m += s2.split('.')[1].length
} catch (e) {}
var val = Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m)
if(n) {
val = val.toFixed(n)
}
return val
},
/**
* 浮点数除法,用于解决精度丢失问题
* @param {*} arg1
* @param {*} arg2
* @param {*} n 保留位数
* @returns
*/
divStr: function(arg1, arg2, n) {
var t1 = 0,
t2 = 0,
r1, r2;
try {
t1 = arg1.toString().split('.')[1].length
} catch (e) {
//TODO handle the exception
}
try {
t2 = arg2.toString().split('.')[1].length
} catch (e) {
//TODO handle the exception
}
r1 = Number(arg1.toString().replace('.', ''));
r2 = Number(arg2.toString().replace('.', ''));
var val = (r1 / r2) * Math.pow(10, t2 - t1)
if(n) {
val = val.toFixed(n)
}
return val
},
/**
* 隐藏身份证号
* @param {*} idCard
* @returns
*/
hideIdNumber: function(idCard) {
if (idCard.length == 18) {
return idCard.substr(0, 6) + '********' + idCard.substr(-4, 4)
} else if (idCard.length == 15) {
return idCard.substr(0, 6) + '******' + idCard.substr(-3, 3)
} else {
return idCard
}
},
/**
* 隐藏手机号
* @param {*} phone
* @returns
*/
hidePhone: function(phone) {
if(phone.length == 11) {
return phone.substr(0,3) + '*****' + phone.substr(-3,3)
} else {
return phone
}
},
/**
* 导出excel
* @param content
* @param fileName
* @param fileParam
* @param blobType
* @param fileType
* */
exportExcel: function({
content,
fileName,
fileParam,
blobType,
fileType
}) {
let __this = this
if (!content) return
if (!blobType) blobType = 'application/vnd.ms-excel'
if (!fileName) fileName = '导出文件'
if (!fileType) fileType = 'xls'
if (!fileParam) fileParam = ''
let fileNames = fileName + fileParam
__this.createBlob(content, blobType, fileNames, fileType)
},
/**
* 下载本地文件
* @param fileJson
* */
downloadLocalFile: function(fileSrc, blobType, fileName, fileType) {
let __this = this
let getUrl = fileSrc
if (getUrl.indexOf('?') < 0) getUrl = fileSrc + '?' + new Date().getTime()
console.log(getUrl)
axios.create().get(getUrl, {
responseType: 'blob'
}).then(response => {
if (window.navigator.msSaveBlob) { //ie浏览器不支持通过a标签下载文件
try {
window.navigator.msSaveBlob(response.data, (fileName + '-' + __this.fileTimeStamp(new Date().getTime()) +
'.' + fileType))
} catch (e) {
ElMessageBox.confirm('该浏览器内核不支持下载此文件,推荐使用谷歌浏览器访问本平台', '提示', {
confirmButtonText: "确定",
showClose: false,
showCancelButton: false,
callback: function(action) {
// window.location.reload()
}
})
}
} else {
__this.createBlob(response.data, blobType, fileName, fileType)
}
})
},
/**
* 创建blob,用于下载文件
* @param {*} content
* @param {*} blobType
* @param {*} fileName
* @param {*} fileType
*/
createBlob: function(content, blobType, fileName, fileType) {
let __this = this
let blob = new Blob([content], {
type: blobType
})
let filename = fileName + '-' + __this.fileTimeStamp(new Date().getTime()) + '.' + fileType
if ('download' in document.createElement('a')) {
let eleLink = document.createElement('a')
eleLink.download = filename
eleLink.style.display = 'none'
eleLink.href = URL.createObjectURL(blob)
document.body.appendChild(eleLink)
eleLink.click()
URL.revokeObjectURL(eleLink.href)
document.body.removeChild(eleLink)
} else {
navigator.msSaveBlob(blob, filename)
}
},
/**
* 通过url下载文件
* @param {*} url
*/
downloadFile(url) {
let urlParam = '';
if (url.indexOf('?') >= 0) {
urlParam = url.split('?')[1]
url = url.split('?')[0]
}
let splitArr = url.split('/')
let fileName = splitArr[splitArr.length - 1].slice(0, -4)
let fileType = splitArr[splitArr.length - 1].slice(-4)
if (fileType.indexOf('.') < 0) {
fileName = fileName.slice(0, -1)
} else {
fileType = fileType.replace(/./, '')
}
let lastType = fileType
fileType = fileType.toLowerCase()
console.log(fileName, fileType)
let blobType = ''
switch (fileType) {
case 'xls':
case 'xlsx':
blobType = 'application/vnd.ms-excel'
break;
// case 'xlsx':
// blobType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.'
// break;
case 'doc':
case 'docx':
blobType = 'application/msword'
break;
case 'ppt':
case 'pptx':
blobType = 'application/vnd.ms-powerpoint'
break;
case 'txt':
blobType = 'text/plain'
break;
case 'jpg':
case 'jpeg':
blobType = 'image/jpeg'
break;
case 'png':
blobType = 'image/png'
break;
case 'pdf':
blobType = 'application/pdf'
break;
case 'zip':
blobType = 'application/zip'
break;
}
let imgType = '.jpg,.jpeg,.png'
if (imgType.indexOf(fileType) >= 0) {
let image = new Image()
image.setAttribute("crossOrigin", "anonymous");
image.onload = function() {
let canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
let context = canvas.getContext("2d");
context.drawImage(image, 0, 0, image.width, image.height);
let url = canvas.toDataURL(blobType, 1.0); //得到图片的base64编码数据
let a = document.createElement("a"); // 生成一个a元素
let event = new MouseEvent("click"); // 创建一个单击事件
// let filename = fileName + '-' + fileTimeStamp(new Date().getTime()) + '.' + fileType
let filename = fileName + '.' + lastType
a.download = filename; // 设置图片名称
a.href = url; // 将生成的URL设置为a.href属性
a.dispatchEvent(event); // 触发a的单击事件
};
image.src = url;
} else {
let fileSrc = splitArr.slice(0, -1).join('/') + '/' + encodeURIComponent(fileName) + '.' + fileType
if (fileType == 'txt') {
console.log(fileSrc, url)
downloadLocalFile(fileSrc, blobType, fileName, fileType)
} else {
let form = document.createElement('form')
form.method = 'get'
form.action = urlParam ? (fileSrc + '?' + urlParam) : fileSrc
document.body.append(form)
form.submit()
form.remove()
}
}
}
}
\ No newline at end of file
const storesData = {}
export default storesData
\ No newline at end of file
......@@ -45,11 +45,11 @@
>
<!-- 手机号 -->
<div class="flex flex-col gap-2.5">
<label for="phone" class="text-white">
<label class="text-white">
手机号
</label>
<el-form-item prop="phone" class="mb-0">
<el-input
id="phone"
v-model="loginForm.phone"
type="tel"
placeholder="请输入手机号"
......@@ -58,16 +58,17 @@
:disabled="isLoading"
size="large"
/>
</el-form-item>
</div>
<!-- 图形验证码 -->
<div class="flex flex-col gap-2.5">
<label for="captcha" class="text-white">
<label class="text-white">
图形验证码
</label>
<div class="flex gap-2">
<el-form-item prop="captchaInput" class="mb-0">
<div class="flex gap-2" style="width: 100%;">
<el-input
id="captcha"
v-model="loginForm.captchaInput"
type="text"
placeholder="请输入图形验证码"
......@@ -90,16 +91,17 @@
<RefreshCw class="absolute top-1 right-1 w-3 h-3 text-gray-400" />
</div>
</div>
</el-form-item>
</div>
<!-- 短信验证码 -->
<div class="flex flex-col gap-2.5">
<label for="smsCode" class="text-white">
<label class="text-white">
短信验证码
</label>
<div class="flex gap-2">
<el-form-item prop="smsCode" class="mb-0">
<div class="flex gap-2" style="width: 100%;">
<el-input
id="smsCode"
v-model="loginForm.smsCode"
type="text"
placeholder="请输入短信验证码"
......@@ -117,8 +119,10 @@
{{ countdown > 0 ? `${countdown}秒后重试` : '获取验证码' }}
</el-button>
</div>
</el-form-item>
</div>
<div class="mt-2">
<el-button
type="primary"
@click="handleSubmit"
......@@ -128,6 +132,7 @@
>
{{ isLoading ? '登录中...' : '登录' }}
</el-button>
</div>
</el-form>
</div>
</div>
......@@ -145,6 +150,15 @@
import { ref, onMounted, onUnmounted, reactive } from 'vue'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import { RefreshCw } from 'lucide-vue-next'
import { getCurrentInstance } from 'vue'
// 扩展组件实例类型以包含全局属性
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$api: any
$utils: any
}
}
// 导入图片资源
import loginBackgroundImg from '@/assets/7f0599d246217c734650d105801453a4919de13c.png'
......@@ -167,17 +181,20 @@ interface LoginProps {
// Props
const props = defineProps<LoginProps>()
// 获取全局API实例
const { $api } = getCurrentInstance()!.appContext.config.globalProperties
// 响应式数据
const loginFormRef = ref<FormInstance>()
const isLoading = ref(false)
const captchaText = ref('')
const captchaToken = ref('')
const captchaImage = ref('')
const countdown = ref(0)
const canSendSms = ref(true)
// 表单数据
const loginForm = reactive<LoginForm>({
phone: '13800000001',
phone: '13112345678',
captchaInput: '',
smsCode: ''
})
......@@ -198,51 +215,19 @@ const loginRules: FormRules<LoginForm> = {
]
}
// 生成图形验证码 - 完全复制React版本的逻辑
const generateCaptcha = () => {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
let text = ''
for (let i = 0; i < 4; i++) {
text += chars.charAt(Math.floor(Math.random() * chars.length))
}
captchaText.value = text
// 生成验证码图片 (使用 canvas)
const canvas = document.createElement('canvas')
canvas.width = 120
canvas.height = 40
const ctx = canvas.getContext('2d')
if (ctx) {
// 背景
ctx.fillStyle = '#f0f0f0'
ctx.fillRect(0, 0, canvas.width, canvas.height)
// 干扰线
for (let i = 0; i < 5; i++) {
ctx.strokeStyle = `rgba(${Math.random() * 255},${Math.random() * 255},${Math.random() * 255},0.3)`
ctx.beginPath()
ctx.moveTo(Math.random() * canvas.width, Math.random() * canvas.height)
ctx.lineTo(Math.random() * canvas.width, Math.random() * canvas.height)
ctx.stroke()
}
// 验证码文字
ctx.font = 'bold 24px Arial'
ctx.textBaseline = 'middle'
for (let i = 0; i < text.length; i++) {
ctx.fillStyle = `rgb(${Math.random() * 100},${Math.random() * 100},${Math.random() * 100})`
const x = 20 + i * 25
const y = 20 + (Math.random() - 0.5) * 8
const angle = (Math.random() - 0.5) * 0.4
ctx.save()
ctx.translate(x, y)
ctx.rotate(angle)
ctx.fillText(text[i], 0, 0)
ctx.restore()
// 从接口获取图形验证码
const generateCaptcha = async () => {
try {
const response = await $api.getImgCode({})
if (response && response.c === 0) {
captchaImage.value = 'data:image/png;base64,'+response.d.image
// 保存验证码标识,用于后续验证
captchaToken.value = response.d.imageId
} else {
ElMessage.error('获取图形验证码失败')
}
captchaImage.value = canvas.toDataURL()
} catch (error) {
ElMessage.error('获取图形验证码失败')
}
}
......@@ -266,7 +251,7 @@ const startCountdown = () => {
}
// 发送短信验证码
const handleSendSms = () => {
const handleSendSms = async () => {
// 验证手机号
if (!loginForm.phone.trim()) {
ElMessage.error('请输入手机号')
......@@ -285,34 +270,55 @@ const handleSendSms = () => {
return
}
if (loginForm.captchaInput.toUpperCase() !== captchaText.value) {
ElMessage.error('图形验证码错误')
generateCaptcha()
loginForm.captchaInput = ''
// 模拟验证图形验证码(实际应该调用接口验证)
if (!captchaToken.value) {
ElMessage.error('请先获取图形验证码')
return
}
// 模拟发送短信
try {
// 调用获取短信验证码接口
const response = await $api.getTelCode({
phone: loginForm.phone,
code: loginForm.captchaInput,
imageId: captchaToken.value
})
if (response && response.c === 0) {
ElMessage.success('验证码已发送至您的手机,请注意查收')
startCountdown()
// 演示用:实际验证码为 123456
console.log('演示验证码:123456')
} else {
ElMessage.error(response?.msg || '发送验证码失败')
generateCaptcha()
loginForm.captchaInput = ''
}
} catch (error) {
console.error('发送短信验证码失败:', error)
ElMessage.error('发送验证码失败')
generateCaptcha()
loginForm.captchaInput = ''
}
}
// 登录提交
const handleSubmit = async () => {
if (!loginFormRef.value) return
try {
await loginFormRef.value.validate()
} catch {
// 表单校验
const valid = await loginFormRef.value.validate()
console.log('表单验证结果:', valid)
if (!valid) {
console.log('表单验证失败')
ElMessage.error('请检查表单填写是否正确')
return
}
// 验证图形验证码
if (loginForm.captchaInput.toUpperCase() !== captchaText.value) {
ElMessage.error('图形验证码错误')
// 验证图形验证码(实际应该调用接口验证)
if (!captchaToken.value) {
ElMessage.error('请先获取图形验证码')
generateCaptcha()
loginForm.captchaInput = ''
return
......@@ -320,24 +326,37 @@ const handleSubmit = async () => {
isLoading.value = true
// 模拟登录验证 - 完全复制React版本的逻辑
setTimeout(() => {
// 演示账号:
// 13800000001 验证码123456 - 管理员
// 13800000002 验证码123456 - 普通用户
if (loginForm.phone === '13800000001' && loginForm.smsCode === '123456') {
try {
// 调用手机登录接口
const response = await $api.pohoneLogin({
phone: loginForm.phone,
code: loginForm.smsCode,
// captcha: loginForm.captchaInput,
// captchaToken: captchaToken.value
})
if (response && response.c === 0) {
ElMessage.success('登录成功')
// 保存登录信息
if (response.d) {
localStorage.setItem('pcUserInfo', JSON.stringify(response.d))
}
props.onLogin(loginForm.phone, 'admin')
} else if (loginForm.phone === '13800000002' && loginForm.smsCode === '123456') {
ElMessage.success('登录成功')
props.onLogin(loginForm.phone, 'viewer')
} else {
ElMessage.error('手机号或验证码错误')
isLoading.value = false
ElMessage.error(response?.msg || '登录失败')
generateCaptcha()
loginForm.captchaInput = ''
loginForm.smsCode = ''
}
} catch (error) {
console.error('登录失败:', error)
ElMessage.error('登录失败')
generateCaptcha()
loginForm.captchaInput = ''
loginForm.smsCode = ''
} finally {
isLoading.value = false
}
}, 800)
}
// 生命周期
......@@ -440,4 +459,9 @@ label {
font-weight: 500;
line-height: 1.5;
}
/* 重置 el-form-item 的默认边距 */
:deep(.el-form-item) {
margin-bottom: 0;
}
</style>
\ No newline at end of file
......@@ -20,6 +20,5 @@
interface Props {
className?: string
}
defineProps<Props>()
</script>
......@@ -12,6 +12,12 @@ import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import App from './App.vue'
import router from './router'
// 引入 API 接口和工具方法
import api from './assets/js/api/interface/index.js'
import request from './assets/js/api/request.js'
import stores from './assets/js/stores/index.js'
import commonUtils from './assets/js/const/common.js'
const app = createApp(App)
// 注册Element Plus图标
......@@ -23,4 +29,10 @@ app.use(createPinia())
app.use(router)
app.use(ElementPlus)
// 将 API 接口、请求实例、状态管理和工具方法挂载到全局
app.config.globalProperties.$api = api
app.config.globalProperties.$request = request
app.config.globalProperties.$stores = stores
app.config.globalProperties.$utils = commonUtils
app.mount('#app')
......@@ -20,10 +20,10 @@ export default defineConfig({
open: true,
proxy: {
// API 请求代理配置
'/crm': {
'/hallserver': {
target: 'http://thall.51xinpai.cn/', // 后端服务地址,根据你的实际情况修改
changeOrigin: true,
rewrite: (path) => path.replace(/^\/crm/, '/crm')
rewrite: (path) => path.replace(/^\/hallserver/, '/hallserver')
},
}
},
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!