Skip to content
Toggle navigation
Projects
Groups
Snippets
Help
Toggle navigation
This project
Loading...
Sign in
李宁
/
Activity
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit c1b80523
authored
Nov 20, 2025
by
李宁
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
1
1 parent
994306b9
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
1132 additions
and
162 deletions
channelBusiManage/.claude/settings.local.json
channelBusiManage/package.json
channelBusiManage/src/assets/js/api/interface/account/index.js
channelBusiManage/src/assets/js/api/interface/common/index.js
channelBusiManage/src/assets/js/api/interface/index.js
channelBusiManage/src/assets/js/api/interface/login/index.js
channelBusiManage/src/assets/js/api/interface/order/index.js
channelBusiManage/src/assets/js/api/interface/reward/index.js
channelBusiManage/src/assets/js/api/interface/role/index.js
channelBusiManage/src/assets/js/api/request.js
channelBusiManage/src/assets/js/const/common.js
channelBusiManage/src/assets/js/stores/index.js
channelBusiManage/src/components/LoginPage.vue
channelBusiManage/src/components/icons/RoleIcon.vue
channelBusiManage/src/main.ts
channelBusiManage/vite.config.ts
channelBusiManage/.claude/settings.local.json
0 → 100644
View file @
c1b8052
{
"permissions"
:
{
"allow"
:
[
"Bash(grep -E
\"\\
.(js|vue|html)$
\"
)"
,
"Bash(npm install axios)"
],
"deny"
:
[],
"ask"
:
[]
}
}
\ No newline at end of file
channelBusiManage/package.json
View file @
c1b8052
...
...
@@ -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"
,
...
...
channelBusiManage/src/assets/js/api/interface/account/index.js
0 → 100644
View file @
c1b8052
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
channelBusiManage/src/assets/js/api/interface/common/index.js
0 → 100644
View file @
c1b8052
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
channelBusiManage/src/assets/js/api/interface/index.js
0 → 100644
View file @
c1b8052
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
channelBusiManage/src/assets/js/api/interface/login/index.js
0 → 100644
View file @
c1b8052
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
,
})
}
channelBusiManage/src/assets/js/api/interface/order/index.js
0 → 100644
View file @
c1b8052
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
channelBusiManage/src/assets/js/api/interface/reward/index.js
0 → 100644
View file @
c1b8052
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
channelBusiManage/src/assets/js/api/interface/role/index.js
0 → 100644
View file @
c1b8052
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
,
})
}
channelBusiManage/src/assets/js/api/request.js
0 → 100644
View file @
c1b8052
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
;
channelBusiManage/src/assets/js/const/common.js
0 → 100644
View file @
c1b8052
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
channelBusiManage/src/assets/js/stores/index.js
0 → 100644
View file @
c1b8052
const
storesData
=
{}
export
default
storesData
\ No newline at end of file
channelBusiManage/src/components/LoginPage.vue
View file @
c1b8052
...
...
@@ -4,24 +4,24 @@
<div
class=
"absolute inset-0"
>
<div
class=
"absolute inset-0 bg-[#001529]"
/>
<div
class=
"absolute inset-0 opacity-50 overflow-hidden"
>
<img
alt=
""
class=
"absolute h-full left-0 max-w-none top-0 w-auto min-w-full object-cover"
:src=
"loginBackground"
<img
alt=
""
class=
"absolute h-full left-0 max-w-none top-0 w-auto min-w-full object-cover"
:src=
"loginBackground"
/>
</div>
</div>
<!-- 内容区域 -->
<div
class=
"relative z-10 w-full flex flex-col items-center justify-center gap-8 px-6"
>
<!-- Logo和标题区 -->
<div
class=
"flex flex-col items-center gap-2 w-full max-w-[448px]"
>
<div
class=
"relative w-[88px] h-[88px]"
>
<div
class=
"absolute inset-0 overflow-hidden pointer-events-none"
>
<img
alt=
"享零工云平台"
class=
"absolute h-[85.72%] left-[-8.06%] max-w-none top-[7.7%] w-[329.57%]"
:src=
"logoIcon"
<img
alt=
"享零工云平台"
class=
"absolute h-[85.72%] left-[-8.06%] max-w-none top-[7.7%] w-[329.57%]"
:src=
"logoIcon"
/>
</div>
</div>
...
...
@@ -30,104 +30,109 @@
</div>
<!-- 登录卡片 -->
<div
<div
class=
"relative w-full max-w-[448px] backdrop-blur-[10px] backdrop-filter rounded-[10px] pt-10 pb-10 px-8 border border-[rgba(255,255,255,0.6)] shadow-[0_25px_50px_-12px_rgba(0,0,0,0.25)]"
:style=
"
{
background: 'linear-gradient(142deg, rgba(255, 255, 255, 0.32) 6%, rgba(255, 255, 255, 0.20) 90.9%)'
}"
>
<el-form
<el-form
ref=
"loginFormRef"
:model=
"loginForm"
:model=
"loginForm"
:rules=
"loginRules"
@
submit
.
prevent=
"handleSubmit"
class=
"relative flex flex-col gap-7"
>
<!-- 手机号 -->
<div
class=
"flex flex-col gap-2.5"
>
<label
for=
"phone"
class=
"text-white"
>
<label
class=
"text-white"
>
手机号
</label>
<el-input
id=
"phone"
v-model=
"loginForm.phone"
type=
"tel"
placeholder=
"请输入手机号"
maxlength=
"11"
class=
"login-input"
:disabled=
"isLoading"
size=
"large"
/>
<el-form-item
prop=
"phone"
class=
"mb-0"
>
<el-input
v-model=
"loginForm.phone"
type=
"tel"
placeholder=
"请输入手机号"
maxlength=
"11"
class=
"login-input"
: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-input
id=
"captcha"
v-model=
"loginForm.captchaInput"
type=
"text"
placeholder=
"请输入图形验证码"
maxlength=
"4"
class=
"login-input flex-1"
:disabled=
"isLoading"
size=
"large"
/>
<div
class=
"relative h-12 w-[120px] bg-white rounded-lg overflow-hidden cursor-pointer hover:opacity-80 transition-opacity"
@
click=
"generateCaptcha"
title=
"点击刷新验证码"
>
<img
v-if=
"captchaImage"
:src=
"captchaImage"
alt=
"验证码"
class=
"w-full h-full object-cover"
<el-form-item
prop=
"captchaInput"
class=
"mb-0"
>
<div
class=
"flex gap-2"
style=
"width: 100%;"
>
<el-input
v-model=
"loginForm.captchaInput"
type=
"text"
placeholder=
"请输入图形验证码"
maxlength=
"4"
class=
"login-input flex-1"
:disabled=
"isLoading"
size=
"large"
/>
<RefreshCw
class=
"absolute top-1 right-1 w-3 h-3 text-gray-400"
/>
<div
class=
"relative h-12 w-[120px] bg-white rounded-lg overflow-hidden cursor-pointer hover:opacity-80 transition-opacity"
@
click=
"generateCaptcha"
title=
"点击刷新验证码"
>
<img
v-if=
"captchaImage"
:src=
"captchaImage"
alt=
"验证码"
class=
"w-full h-full object-cover"
/>
<RefreshCw
class=
"absolute top-1 right-1 w-3 h-3 text-gray-400"
/>
</div>
</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-input
id=
"smsCode"
v-model=
"loginForm.smsCode"
type=
"text"
placeholder=
"请输入短信验证码"
maxlength=
"6"
class=
"login-input flex-1"
:disabled=
"isLoading"
size=
"large"
/>
<el-button
@
click=
"handleSendSms"
:disabled=
"!canSendSms || isLoading"
class=
"sms-button-white"
size=
"large"
>
{{
countdown
>
0
?
`${countdown
}
秒后重试`
:
'获取验证码'
}}
<
/el-button
>
<
/div
>
<el-form-item
prop=
"smsCode"
class=
"mb-0"
>
<div
class=
"flex gap-2"
style=
"width: 100%;"
>
<el-input
v-model=
"loginForm.smsCode"
type=
"text"
placeholder=
"请输入短信验证码"
maxlength=
"6"
class=
"login-input flex-1"
:disabled=
"isLoading"
size=
"large"
/>
<el-button
@
click=
"handleSendSms"
:disabled=
"!canSendSms || isLoading"
class=
"sms-button-white"
size=
"large"
>
{{
countdown
>
0
?
`${countdown
}
秒后重试`
:
'获取验证码'
}}
<
/el-button
>
<
/div
>
<
/el-form-item
>
<
/div
>
<
el
-
button
type
=
"primary"
@
click
=
"handleSubmit"
class
=
"login-submit-button"
:
loading
=
"isLoading"
size
=
"large"
>
{{
isLoading
?
'登录中...'
:
'登录'
}}
<
/el-button
>
<
div
class
=
"mt-2"
>
<
el
-
button
type
=
"primary"
@
click
=
"handleSubmit"
class
=
"login-submit-button"
:
loading
=
"isLoading"
size
=
"large"
>
{{
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
captchaT
ext
=
ref
(
''
)
const
captchaT
oken
=
ref
(
''
)
const
captchaImage
=
ref
(
''
)
const
countdown
=
ref
(
0
)
const
canSendSms
=
ref
(
true
)
// 表单数据
const
loginForm
=
reactive
<
LoginForm
>
({
phone
:
'13
800000001
'
,
phone
:
'13
112345678
'
,
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
(
'获取图形验证码失败'
)
}
}
...
...
@@ -252,7 +237,7 @@ let countdownTimer: NodeJS.Timeout | null = null
const
startCountdown
=
()
=>
{
countdown
.
value
=
60
canSendSms
.
value
=
false
countdownTimer
=
setInterval
(()
=>
{
countdown
.
value
--
if
(
countdown
.
value
<=
0
)
{
...
...
@@ -266,53 +251,74 @@ const startCountdown = () => {
}
// 发送短信验证码
const
handleSendSms
=
()
=>
{
const
handleSendSms
=
async
()
=>
{
// 验证手机号
if
(
!
loginForm
.
phone
.
trim
())
{
ElMessage
.
error
(
'请输入手机号'
)
return
}
const
phoneRegex
=
/^1
[
3-9
]\d
{9
}
$/
if
(
!
phoneRegex
.
test
(
loginForm
.
phone
))
{
ElMessage
.
error
(
'请输入正确的手机号'
)
return
}
// 验证图形验证码
if
(
!
loginForm
.
captchaInput
.
trim
())
{
ElMessage
.
error
(
'请输入图形验证码'
)
return
}
if
(
loginForm
.
captchaInput
.
toUpperCase
()
!==
captchaText
.
value
)
{
ElMessage
.
error
(
'图形验证码错误'
)
// 模拟验证图形验证码(实际应该调用接口验证)
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
=
''
return
}
// 模拟发送短信
ElMessage
.
success
(
'验证码已发送至您的手机,请注意查收'
)
startCountdown
()
// 演示用:实际验证码为 123456
console
.
log
(
'演示验证码:123456'
)
}
// 登录提交
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
=
''
}
}
,
800
)
}
catch
(
error
)
{
console
.
error
(
'登录失败:'
,
error
)
ElMessage
.
error
(
'登录失败'
)
generateCaptcha
()
loginForm
.
captchaInput
=
''
loginForm
.
smsCode
=
''
}
finally
{
isLoading
.
value
=
false
}
}
// 生命周期
...
...
@@ -440,4 +459,9 @@ label {
font
-
weight
:
500
;
line
-
height
:
1.5
;
}
<
/style
>
/* 重置 el-form-item 的默认边距 */
:
deep
(.
el
-
form
-
item
)
{
margin
-
bottom
:
0
;
}
<
/style>
\ No newline at end of file
channelBusiManage/src/components/icons/RoleIcon.vue
View file @
c1b8052
...
...
@@ -20,6 +20,5 @@
interface
Props
{
className
?:
string
}
defineProps
<
Props
>
()
</
script
>
channelBusiManage/src/main.ts
View file @
c1b8052
...
...
@@ -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'
)
channelBusiManage/vite.config.ts
View file @
c1b8052
...
...
@@ -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
'
)
},
}
},
...
...
Write
Preview
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment