Compare commits

..

10 Commits

Author SHA1 Message Date
weipengfei 81095f57a1 1 2024-04-06 10:21:06 +08:00
weipengfei 24728a6754 1 2024-04-05 17:46:48 +08:00
weipengfei 8b34814a90 更新 2024-04-05 17:45:32 +08:00
weipengfei 6d21ce159a 新增现金收款 2024-04-05 17:26:43 +08:00
weipengfei 781b267b6e 更新 2024-04-05 15:58:51 +08:00
weipengfei 66e606e417 更新 2024-04-05 14:23:21 +08:00
weipengfei 00567505c2 更新 2024-04-05 13:44:36 +08:00
weipengfei 176067a7bf 1 2024-04-02 18:48:27 +08:00
weipengfei c31591c837 更新 2024-04-02 18:42:05 +08:00
weipengfei b014eba006 更新 2024-04-02 18:18:48 +08:00
29 changed files with 2030 additions and 386 deletions

View File

@ -1,2 +1,2 @@
# VITE_BASE_URL = 'http://192.168.1.24:8324'
VITE_BASE_URL = 'https://crmeb-test.shop.lihaink.cn'
VITE_BASE_URL = 'http://192.168.1.22:8324'
# VITE_BASE_URL = 'https://crmeb-test.shop.lihaink.cn'

6
package-lock.json generated
View File

@ -11,6 +11,7 @@
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.6.8",
"element-plus": "^2.6.3",
"mitt": "^3.0.1",
"pinia": "^2.1.7",
"sass": "^1.72.0",
"vue": "^3.4.21",
@ -1308,6 +1309,11 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="
},
"node_modules/muggle-string": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",

View File

@ -12,6 +12,7 @@
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.6.8",
"element-plus": "^2.6.3",
"mitt": "^3.0.1",
"pinia": "^2.1.7",
"sass": "^1.72.0",
"vue": "^3.4.21",

22
src/api/shop.js Normal file
View File

@ -0,0 +1,22 @@
import request from '@/utils/axios.js'
/**
* @description 商品列表
*/
export function storeListApi(id, data) {
return request.get(`server/${id}/product/lst`, { params: data })
}
/**
* @description 商品详情
*/
export function getAttrValue(id, data) {
return request.get(`store/product/detail/${id}`, { params: data })
}
/**
* @description 免审编辑
*/
export function userFreeTrialApi(id, data) {
return request.post(`user_free_trial/${id}`, data)
}

View File

@ -1,12 +1,5 @@
import request from '@/utils/axios.js'
/**
* @description 商品列表
*/
export function storeListApi(id, data) {
return request.get(`server/${id}/product/lst`, { params: data })
}
/**
* @description 加入购物车
*/
@ -15,15 +8,72 @@ export function cartCreateApi(data) {
}
/**
* @description 购物
* @description 购物列表
*/
export function cartListApi(data) {
return request.get(`user/cart/lst`, { params: data })
}
// /**
// * @description 购物车数量
// */
// export function cartListApi(id, data) {
// return request.get(`count`, { params: data })
// }
/**
* @description 编辑购物车数据
*/
export function cartChangeApi(id, data) {
return request.post(`user/cart/change/${id}`, data)
}
/**
* @description 结算
*/
export function orderCheckApi(data) {
return request.post(`v2/order/check`, data)
}
/**
* @description 删除商品
*/
export function cartDeleteApi(data) {
return request.post(`user/cart/delete`, data)
}
/**
* @description 支付
*/
export function orderCreateApi(data) {
return request.post(`v2/order/create`, data)
}
/**
* @description 重新支付
*/
export function orderPayApi(id, data) {
return request.post(`order/pay/${id}`, data)
}
/**
* @description 订单状态
*/
export function orderStatusApi(data) {
return request.get(`micropay_query`, { params: data })
}
/**
* @description 订单列表
*/
export function orderListApi(id, data) {
return request.get(`admin/${id}/order_list`, { params: data })
}
/**
* @description 未支付订单列表
*/
export function groupOrderListApi(id, data) {
return request.get(`admin/${id}/group_order_list`, { params: data })
}
/**
* @description 提单
*/
export function orderLadingApi(data) {
return request.get(`order_lading`, { params: data })
}

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="859px" height="586px" viewBox="0 0 859 586" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 54 (76480) - https://sketchapp.com -->
<title>500-彩色-01</title>
<desc>Created with Sketch.</desc>
<g id="页面1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="500-彩色-01" transform="translate(-1.000000, -1.000000)">
<g id="图层_2" transform="translate(1.000000, 1.000000)">
<ellipse id="椭圆形" fill="#E7F4FE" fill-rule="nonzero" cx="452" cy="344" rx="406.2" ry="241.1"></ellipse>
<g id="编组" transform="translate(646.000000, 401.000000)" fill="#96CEF7" fill-rule="nonzero">
<path d="M20.7,9.5 L20.7,5.7 C20.7,2.6 23.2,0.1 26.3,0.1 L119.2,0.1 C122.3,0.1 124.8,2.6 124.8,5.7 L124.8,9.6 C124.8,12.7 122.3,15.1 119.3,15.1 L78.4,15.1 C75.3,15.1 72.8,17.6 72.8,20.7 L72.8,24.8 C72.8,27.9 75.3,30.4 78.4,30.4 L86.2,30.4 C89.2,30.4 91.7,32.8 91.8,35.9 L91.9,42.5 C91.9,45.6 89.4,48.1 86.3,48.1 L6.2,48.1 C3.1,48.1 0.6,45.6 0.6,42.5 L0.6,36.1 C0.6,33 3.1,30.6 6.1,30.5 L42.5,30.3 C45.5,30.3 48,27.9 48,24.8 L48.1,20.4 C48.2,17.3 45.6,14.7 42.5,14.7 L26.4,14.9 C23.3,15.1 20.7,12.6 20.7,9.5 Z" id="路径"></path>
<circle id="椭圆形" cx="144.6" cy="8.4" r="8.2"></circle>
</g>
<g id="编组" transform="translate(0.000000, 249.000000)" fill="#96CEF7" fill-rule="nonzero">
<path d="M158.3,12 L158.3,7.5 C158.3,3.8 155.3,0.8 151.6,0.8 L40.3,0.8 C36.6,0.8 33.6,3.8 33.6,7.5 L33.6,12.1 C33.6,15.8 36.6,18.7 40.2,18.7 L89.2,18.7 C92.9,18.7 95.9,21.7 95.9,25.4 L95.9,30.3 C95.9,34 92.9,37 89.2,37 L79.9,37 C76.3,37 73.3,39.9 73.2,43.5 L73.1,51.4 C73,55.1 76,58.2 79.8,58.2 L175.8,58.2 C179.5,58.2 182.5,55.2 182.5,51.5 L182.5,43.9 C182.5,40.2 179.5,37.3 175.9,37.2 L132.3,36.9 C128.7,36.9 125.8,34 125.7,30.4 L125.6,25.2 C125.5,21.5 128.6,18.4 132.3,18.4 L151.6,18.6 C155.2,18.7 158.3,15.7 158.3,12 Z" id="路径"></path>
<circle id="椭圆形" cx="9.9" cy="10.6" r="9.8"></circle>
</g>
<g id="XMLID_3_" transform="translate(306.000000, 202.000000)" fill-rule="nonzero">
<g id="编组">
<path d="M207.5,152.8 C207.5,152.8 205.5,167.9 218.6,174.6 L218.6,174.6 C213.1,184.2 206.2,193 198.3,200.7 L198.3,200.7 C178.2,164.9 157.7,186.2 157.7,186.2 C157.7,186.2 145.1,159.7 126.7,164.3 C144.9,152.4 163.5,139 181.6,125.3 C186.5,152.3 207.5,152.8 207.5,152.8 Z" id="路径" fill="#96CEF7"></path>
<path d="M218.7,174.6 L218.7,174.6 C205.6,167.9 207.5,152.8 207.5,152.8 C207.5,152.8 186.5,152.2 181.7,125.2 C198.4,112.5 214.7,99.4 230,86.7 C232.6,96.3 234,106.4 233.9,116.9 C233.9,138 228.4,157.6 218.7,174.6 Z" id="路径" fill="#309EED"></path>
<path d="M198.4,200.7 L198.4,200.7 C177.4,221 148.9,233.5 117.4,233.5 C114.6,233.5 111.9,233.4 109.2,233.2 L109.2,233.2 C109.2,233.2 100.2,217.1 112.9,208 C112.9,208 96.3,190.5 113.4,172.8 C117.8,170.1 122.3,167.2 126.7,164.3 C145.1,159.7 157.7,186.2 157.7,186.2 C157.7,186.2 178.3,164.9 198.4,200.7 Z" id="路径" fill="#309EED"></path>
<path d="M230,86.8 C214.7,99.5 198.4,112.6 181.7,125.3 C181.6,124.8 181.5,124.3 181.5,123.8 C177.3,95.3 205.7,84.4 205.7,84.4 C205.7,84.4 200.7,63.6 224.7,71.6 C226.8,76.4 228.6,81.5 230,86.8 Z" id="路径" fill="#309EED"></path>
<path d="M181.4,123.7 C181.5,124.2 181.6,124.7 181.6,125.2 C163.5,139 144.9,152.3 126.7,164.2 C123.7,164.9 120.6,166.5 117.3,169.1 C115.8,170.3 114.5,171.5 113.3,172.7 C88.3,188.2 64.3,200.5 43.4,207 C18.6,186.7 2.4,156.3 0.9,122.1 C0.9,122.1 0.9,122 0.9,122 C1.3,111.4 2.9,101 5.6,91.1 C10.4,92.2 23.3,94.8 29.1,90.6 C36.4,85.4 28.4,142.4 59.7,142.4 C91,142.4 93.4,90.2 93.4,90.2 C93.4,90.2 120.1,91.4 106.7,61.1 C106.7,61.1 124.5,62.6 129.3,44.7 C132.7,32.2 127.1,19.1 116.5,11.6 C112.3,8.6 106.8,5.2 99.6,1.5 C105.3,0.6 111.2,0.2 117.2,0.2 C165.4,0.2 206.8,29.5 224.5,71.2 C200.5,63.2 205.5,84 205.5,84 C205.5,84 177.2,95.2 181.4,123.7 Z M152,117 C152,117 159.6,110.4 157.8,104.2 C156,98 147.5,104.9 147.5,104.9 C147.5,104.9 146.9,86.2 130.5,92.8 C114.1,99.5 125,117 125,117 C137.8,130.8 152,117 152,117 Z" id="形状" fill="#96CEF7"></path>
<path d="M157.8,104.2 C159.6,110.4 152,117 152,117 C152,117 137.7,130.8 125,117 C125,117 114.1,99.5 130.5,92.8 C146.9,86.1 147.5,104.9 147.5,104.9 C147.5,104.9 156,98 157.8,104.2 Z" id="路径" fill="#309EED"></path>
<path d="M43.6,207.1 C64.5,200.6 88.5,188.3 113.5,172.8 C96.4,190.5 113,208 113,208 C100.3,217.1 109.3,233.2 109.3,233.2 L109.3,233.2 C84.4,231.5 61.7,222 43.6,207.1 Z" id="路径" fill="#96CEF7"></path>
<path d="M129.5,44.9 C124.6,62.8 106.9,61.3 106.9,61.3 C120.2,91.6 93.6,90.4 93.6,90.4 C93.6,90.4 91.2,142.6 59.9,142.6 C28.6,142.6 36.6,85.6 29.3,90.8 C23.4,95 10.5,92.4 5.8,91.3 C12.5,66.2 26,44 44.2,26.5 C59.9,13.8 79,5.1 99.9,1.9 C107.1,5.6 112.6,9 116.8,12 C127.3,19.3 132.9,32.4 129.5,44.9 Z" id="路径" fill="#309EED"></path>
</g>
</g>
<circle id="椭圆形" cx="423.4" cy="319" r="116.5"></circle>
<path d="M487,419.1 C484.6,420.5 482.1,421.9 479.6,423.2" id="路径" stroke="#000000"></path>
<path d="M459.6,429.9 C448.3,433.5 436.3,435.5 423.8,435.5 C359.5,435.5 307.3,383.3 307.3,319 C307.3,254.7 359.5,202.5 423.8,202.5 C488.1,202.5 540.3,254.6 540.3,319 C540.3,358.8 520.3,394 489.8,415" id="路径" stroke="#000000" stroke-width="2"></path>
<path d="M445.5,470.4 C523.151236,470.4 586.1,407.451236 586.1,329.8 C586.1,252.148764 523.151236,189.2 445.5,189.2 C367.848764,189.2 304.9,252.148764 304.9,329.8 C304.9,407.451236 367.848764,470.4 445.5,470.4 Z" id="椭圆形" stroke="#5C5C5C" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="3.0047,3.0047"></path>
<path d="M441.4,524 C547.217758,524 633,438.217758 633,332.4 C633,226.582242 547.217758,140.8 441.4,140.8 C335.582242,140.8 249.8,226.582242 249.8,332.4 C249.8,438.217758 335.582242,524 441.4,524 Z" id="椭圆形" stroke="#5C5C5C" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="2.994,2.994"></path>
<path d="M303.3,324 C303.3,324 236.4,436.7 338.2,411.2 C440,385.7 668.8,176 668.8,176" id="路径" stroke="#EF706D" stroke-width="2" stroke-dasharray="3,3"></path>
<path d="M304.8,330.5 C304.8,330.5 254.3,415.6 331.2,396.3 C408.1,377.1 652.2,153.4 652.2,153.4" id="路径" stroke="#EF706D" stroke-width="2" stroke-dasharray="3,3"></path>
<g id="XMLID_1_" transform="translate(624.000000, 0.000000)">
<g id="编组" transform="translate(0.000000, 19.000000)" fill-rule="nonzero">
<path d="M161.4,96 C179.2,122.5 186,145.3 176.8,154.4 C163.1,168.1 118.5,145.6 77.2,104.1 C59.5,86.3 45.3,68 35.9,51.7 C34.9,50 34,48.4 33.2,46.8 C31.4,43.5 29.9,40.3 28.6,37.2 C22.2,22.2 21.3,10.3 27.4,4.3 C35.9,-4.2 56.1,1.2 80.1,16.3 C95,25.6 111.2,38.7 127,54.6 C136.3,64 144.7,73.5 151.9,82.8 C155.3,87.3 158.5,91.7 161.4,96 Z M138.4,118.4 C143.7,113.1 130.7,91.5 109.3,70.1 C88,48.7 66.4,35.6 61.1,40.8 C55.8,46 68.9,67.7 90.2,89.1 C111.5,110.5 133.2,123.7 138.4,118.4 Z" id="形状" fill="#AFD7A3"></path>
<path d="M167.3,47.8 C172.5,60.9 174.4,78.5 161.3,96 C158.4,91.7 155.2,87.3 151.7,82.8 C172.5,69.5 165.6,44 165.6,44 C166.3,45.2 166.8,46.5 167.3,47.8 Z" id="路径" fill="#E7B976"></path>
<path d="M109.4,70.2 C130.7,91.6 143.8,113.2 138.5,118.5 C133.2,123.7 111.7,110.6 90.3,89.2 C68.9,67.8 55.9,46.2 61.2,40.9 C66.5,35.6 88,48.7 109.4,70.2 Z" id="路径" fill="#E7B976"></path>
<path d="M165.6,44 C165.6,44 172.4,69.5 151.8,82.8 C144.6,73.5 136.3,64 126.9,54.6 C111.1,38.7 94.8,25.7 80,16.3 C80,16.3 101.3,-3.4 138.4,16.1 C141.9,17.9 145.2,20.1 148.3,22.5 C150.6,24.3 152.8,26.3 154.8,28.4 C159.2,33 162.9,38.2 165.6,44 Z M132.3,24.4 C133,23.2 132.7,21.7 131.5,20.9 C130.3,20.2 128.7,20.5 128,21.7 C127.3,22.9 127.6,24.5 128.8,25.2 C130,25.9 131.6,25.6 132.3,24.4 Z M120.4,23.4 C121.9,23.5 123.3,22.7 123.7,21.3 C124,20.2 123.6,18.9 121.2,17.8 C117.1,15.8 109.4,16.3 106.2,16.6 C104.9,16.7 103.6,17.2 102.7,18 C99.6,20.6 102.7,23.8 106.3,23.1 C109.6,22.3 117.1,23 120.4,23.4 Z" id="形状" fill="#FED37C"></path>
<path d="M176,2.8 C177.2,3.5 154.8,28.4 154.8,28.4 C152.8,26.3 150.6,24.3 148.3,22.5 C148.3,22.6 174.8,2.1 176,2.8 Z" id="路径" fill="#AFD7A3"></path>
<path d="M131.5,20.9 C132.7,21.6 133.1,23.2 132.3,24.4 C131.6,25.6 130,26 128.8,25.2 C127.6,24.5 127.2,22.9 128,21.7 C128.7,20.5 130.3,20.2 131.5,20.9 Z" id="路径" fill="#FFFFFF"></path>
<path d="M123.7,21.2 C123.3,22.6 121.9,23.5 120.4,23.3 C117.1,22.9 109.6,22.2 106.3,22.9 C102.7,23.7 99.6,20.5 102.7,17.8 C103.7,16.9 104.9,16.5 106.2,16.4 C109.4,16.1 117.1,15.6 121.2,17.6 C123.6,18.9 124,20.2 123.7,21.2 Z" id="路径" fill="#FFFFFF"></path>
<path d="M3.9,161.6 C3.7,161.7 3.5,161.7 3.3,161.8 C2.2,159.9 1.1,158.1 0,156.2 C1.2,158 2.6,159.8 3.9,161.6 Z" id="路径" fill="#D8D7D7"></path>
</g>
<g id="编组" transform="translate(23.000000, 0.000000)" stroke="#000000" stroke-width="2">
<path d="M10.2,65.9 C8.4,62.6 6.9,59.4 5.6,56.3" id="路径"></path>
<path d="M12.9,70.8 C11.9,69.1 11,67.5 10.2,65.9" id="路径"></path>
<path d="M5.6,56.3 C-0.8,41.3 -1.7,29.4 4.4,23.4 C12.9,14.9 33.1,20.3 57.1,35.4 C72,44.7 88.2,57.8 104,73.7 C113.3,83.1 121.7,92.6 128.9,101.9 C132.4,106.4 135.6,110.8 138.5,115.1 C156.3,141.6 163.1,164.4 153.9,173.5 C140.2,187.2 95.6,164.7 54.3,123.2 C36.6,105.4 22.4,87.1 13,70.8" id="路径"></path>
<path d="M57,35.4 C57,35.4 78.3,15.7 115.4,35.2 C118.9,37 122.2,39.2 125.3,41.6 C127.6,43.4 129.8,45.4 131.8,47.5 C136.2,52.1 139.9,57.3 142.7,63.1 C143.3,64.3 143.9,65.6 144.4,66.9 C149.6,80 151.5,97.6 138.4,115.1" id="路径"></path>
<path d="M115.4,137.4 C110.1,142.6 88.6,129.5 67.2,108.1 C45.8,86.7 32.8,65.1 38.1,59.8 C43.4,54.6 64.9,67.7 86.3,89.1 C107.7,110.6 120.7,132.2 115.4,137.4 Z" id="路径"></path>
<path d="M125.3,41.6 C125.3,41.6 151.8,21.2 153,21.9 C154.2,22.6 131.8,47.5 131.8,47.5" id="路径"></path>
<path d="M149.4,15.2 L155.5,0.8" id="路径"></path>
<path d="M156.8,19.8 L166.9,10.1" id="路径"></path>
<path d="M159.3,25.7 L172.2,25" id="路径"></path>
</g>
</g>
<path d="M624,70.7 C624,70.7 631.6,66.6 639,70.7" id="路径" stroke="#FFFFFF"></path>
<path d="M657.2,22.8 C657.2,22.8 671.2,17.5 716.5,48.2" id="路径" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M720.5,51 C720.5,51 776.5,94.6 796.4,142.7" id="路径" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

584
src/components/pay.vue Normal file
View File

@ -0,0 +1,584 @@
<script setup>
import { ref, watch, nextTick, computed } from "vue";
import { orderCreateApi, orderStatusApi, orderPayApi } from "@/api/store.js";
import { ElMessage } from "element-plus";
import { audioplay } from "@/utils/audio.js";
const drawer = ref(false);
const active = ref(1);
const input = ref("");
const codeRef = ref("");
const cancelClick = () => {
beforeClose();
};
const open = () => {
nextTick(() => {
setTimeout(() => {
loading.value = false;
input.value = "";
reLoad.value = true;
codeRef.value.focus();
}, 300);
});
};
const changeActive = (e) => {
active.value = e;
if (active.value == 2) {
//
document.addEventListener("keydown", keyboard);
} else document.removeEventListener("keydown", keyboard);
};
const form = ref({});
const cart_id = ref([]);
const setForm = (e) => {
form.value = e.data;
cart_id.value = e.cart_id;
};
const setRePay = (e) => {
form.value.total_price = e.price;
order_id.value = e.order_id;
};
const emit = defineEmits(["paySuccess"]);
const reLoad = ref(false);
const numList = ref([
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"0",
"00",
".",
]);
let timecount = 0; //
const order_id = ref("");
//
const handleEnter = () => {
loading.value = true;
codeRef.value.blur();
if (order_id.value) orderPay(order_id.value);
else
orderCreateApi({
address_id: "",
key: form.value.key,
cart_id: cart_id.value,
pay_type: "micropay",
auth_code: input.value,
source: 300,
})
.then((res) => {
if (res.status == 200 && res.message == "支付成功") {
drawer.value = false;
ElMessage({
message: res.message,
type: "success",
});
audioplay(res.data.message);
beforeClose();
} else {
if (!res.data.group_order_sn) {
order_id.value = res.data.result.order_id;
loading.value = false;
return ElMessage({
message: res.message,
type: "error",
});
} else {
order_id.value = res.data.group_order_id;
count.value = 0;
timecount = 0;
getOrderStatus(res.data.group_order_sn);
}
}
})
.catch((err) => {
loading.value = false;
nextTick(() => {
codeRef.value?.focus();
});
});
};
const orderPay = (id) => {
orderPayApi(id, {
type: "micropay",
auth_code: input.value,
})
.then((res) => {
if (res.status == 200 && res.message == "支付成功") {
drawer.value = false;
ElMessage({
message: res.message,
type: "success",
});
audioplay(res.data.message);
beforeClose();
} else {
order_id.value = res.data.group_order_id;
count.value = 0;
timecount = 0;
getOrderStatus(res.data.group_order_sn);
}
})
.catch((err) => {
input.value = "";
loading.value = false;
nextTick(() => {
codeRef.value?.focus();
});
});
};
const count = ref(0); // 3, 3,
const getOrderStatus = (id) => {
count.value++;
timecount += 5000;
orderStatusApi({
order_sn: id,
})
.then((res) => {
if (res.data.paid == 1 || res.message == "支付成功") {
ElMessage({
message: res.message,
type: "success",
});
audioplay(res.data.message);
beforeClose();
} else {
ElMessage({
message: res.message,
type: "error",
});
input.value = "";
loading.value = false;
nextTick(() => {
codeRef.value?.focus();
});
}
})
.catch((err) => {
if (reLoad.value && count.value < 3)
setTimeout(
() => {
getOrderStatus(id);
},
15000 - timecount > 0 ? 15000 - timecount : 0
);
else {
input.value = "";
loading.value = false;
nextTick(() => {
codeRef.value?.focus();
});
}
});
};
const beforeClose = () => {
reLoad.value = false;
loading.value = false;
input.value = "";
collection.value = "";
collectionArray.value = [];
codeRef.value?.blur();
emit("paySuccess");
drawer.value = false;
};
const loading = ref(false);
defineExpose({
drawer,
setForm,
setRePay,
});
const collectionArray = ref([]);
const collection = ref(""); //
const changePrice = computed(() => {
//
if (+collection.value > 0) {
return (collection.value - form.value.order_price).toFixed(2);
}
return -1;
});
const defaultcalc = ref(false);
//
const delNum = (type) => {
if (type === -1) {
collectionArray.value = [];
} else {
collectionArray.value.pop();
}
collection.value = collectionArray.value.length
? collectionArray.value.join("")
: 0;
};
//
const numTap = (item) => {
if (defaultcalc.value === false) {
collection.value = "";
defaultcalc.value = true;
}
let x = String(collection.value).indexOf(".") + 1;
let y = String(collection.value).length - x;
console.log(x, y);
if (x === 0 || y < 2) {
if (collectionArray.value.join("") <= 9999999) {
collectionArray.value.push(item);
}
collection.value =
collectionArray.value.join("") > 99999999
? 99999999
: collectionArray.value.join("");
}
};
//
const cashBnt = () => {
if(changePrice.value!=''&&changePrice.value>=0) orderCreateApi({
address_id: "",
key: form.value.key,
cart_id: cart_id.value,
pay_type: "cash_payment",
source: 300,
})
.then((res) => {
if (res.status == 200 && res.message == "支付成功") {
drawer.value = false;
ElMessage({
message: res.message,
type: "success",
});
audioplay(res.data.message);
beforeClose();
} else {
if (!res.data.group_order_sn) {
order_id.value = res.data.result.order_id;
collection.value = "";
collectionArray.value = [];
loading.value = false;
return ElMessage({
message: res.message,
type: "error",
});
} else {
order_id.value = res.data.group_order_id;
count.value = 0;
timecount = 0;
getOrderStatus(res.data.group_order_sn);
}
}
})
.catch((err) => {
loading.value = false;
});
};
//
const keyboard = (event) => {
let e = event || window.event;
let key = e.keyCode;
if (true) {
event.stopPropagation(); //
event.preventDefault(); //
}
switch (key) {
case 96:
case 48:
numTap(0);
break;
case 97:
case 49:
numTap(1);
break;
case 98:
case 50:
numTap(2);
break;
case 99:
case 51:
numTap(3);
break;
case 100:
case 52:
numTap(4);
break;
case 101:
case 53:
numTap(5);
break;
case 102:
case 54:
numTap(6);
break;
case 103:
case 55:
numTap(7);
break;
case 104:
case 56:
numTap(8);
break;
case 105:
case 57:
numTap(9);
break;
case 110:
numTap(".");
break;
case 190:
numTap(".");
break;
case 8:
delNum();
break;
case 13:
cashBnt();
break;
}
};
</script>
<template>
<el-drawer
:size="800"
v-model="drawer"
direction="rtl"
@open="open"
:before-close="beforeClose"
>
<template #header>
<h4>选择支付方式</h4>
</template>
<template #default>
<div class="dra-body">
<div class="header">
<div
class="left"
:class="{ active: active == 1 }"
@click="changeActive(1)"
>
微信
</div>
<div
class="right"
:class="{ active: active == 2 }"
@click="changeActive(2)"
>
现金收款
</div>
</div>
<div style="color: #999; padding: 2rem 0 0.3rem 0">应收金额():</div>
<div style="color: #f5222d; padding-bottom: 2rem">
¥<span style="font-size: 1.6rem">{{ form.order_price }}</span>
</div>
<div
v-loading="loading"
element-loading-text="支付中"
class="card1"
v-if="active == 1"
>
<el-input
ref="codeRef"
v-model="input"
autofocus
class="code-input"
placeholder="请点击输入框聚焦扫码或输入编码号"
@keyup.enter="handleEnter"
/>
<div class="tips"></div>
</div>
<div class="card2" v-else>
<div class="drawer-body">
<div class="counter">
<div class="received">
<span v-if="collection">{{ collection }}</span>
<span v-else style="font-size: 1rem; color: #999"
>按下键盘输入客户支付金额</span
>
</div>
<div class="balance" v-if="changePrice >= 0">
需找零()<span class="money">{{ changePrice }}</span>
</div>
<div class="balance" v-else>不够找零, 请支付更多金额</div>
<div class="keypad">
<div class="left">
<el-button
v-for="item in numList"
:key="item"
@click="numTap(item)"
>{{ item }}</el-button
>
</div>
<div class="right">
<el-button @click="delNum"
><el-icon><Delete /></el-icon
></el-button>
<el-button @click="delNum(-1)">C</el-button>
<el-button class="enter" @click="cashBnt">确认</el-button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<template #footer>
<div style="width: 100%; display: flex; justify-content: center">
<el-button class="cancel-btn" @click="cancelClick">取消收款</el-button>
</div>
</template>
</el-drawer>
</template>
<style lang="scss" scoped>
.dra-body {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
.header {
width: 25rem;
display: flex;
& > div {
flex: 1;
border: 1px solid #ccc;
text-align: center;
padding: 0.6rem 0;
cursor: pointer;
}
.left {
border-right: none;
border-radius: 5rem 0 0 5rem;
}
.right {
border-left: none;
border-radius: 0 5rem 5rem 0;
}
.active {
background-color: #1890ff;
color: #fff;
transition: 300ms;
border-color: #1890ff;
}
}
.card1 {
.code-input {
width: 100%;
height: 3rem;
}
.tips {
width: 38rem;
height: 16rem;
background: url("https://lihai001.oss-cn-chengdu.aliyuncs.com/def/a4971202404051443356430.png");
background-size: 100% 100%;
background-repeat: no-repeat;
}
}
}
.cancel-btn {
width: 60%;
border-color: #1890ff;
color: #1890ff;
border-radius: 5rem;
height: 3rem;
font-size: 1.2rem;
}
.drawer-body {
width: 100%;
overflow-x: hidden;
}
.counter {
padding: 20px;
border-radius: 20px;
background-color: #f3f9ff;
.received {
height: 58px;
padding: 0 20px;
border: 1px solid #eeeeee;
border-radius: 8px;
background-color: #ffffff;
font-size: 26px;
line-height: 58px;
color: #303133;
}
.balance {
width: 100%;
box-sizing: border-box;
padding: 18px 0 18px 10px;
text-align: start;
font-size: 15px;
color: #303133;
.money {
color: #ff4a00;
}
}
.keypad {
display: grid;
grid-template-columns: auto auto auto auto;
grid-gap: 10px;
.left {
grid-column-end: span 3;
display: grid;
grid-template-columns: auto auto auto;
grid-gap: 10px;
}
.right {
display: grid;
grid-template-columns: auto;
grid-gap: 10px;
}
.el-button {
height: 62px;
width: 130px;
margin: 0 !important;
border: 0;
border-radius: 8px;
font-weight: 500;
font-size: 28px !important;
line-height: 62px;
color: #1890ff;
&:focus {
box-shadow: none;
}
}
.enter {
grid-row-end: span 4;
height: 134px;
background-color: #1890ff;
font-weight: 500;
font-size: 22px !important;
line-height: 134px;
color: #ffffff;
}
}
}
</style>

View File

@ -1,9 +1,8 @@
<script setup>
import myHeader from "./myHeader.vue"
import myAside from "./myAside.vue"
import myHeader from "./myHeader.vue";
import myAside from "./myAside.vue";
</script>
<template>
<div class="common-layout">
<el-container>
@ -15,7 +14,9 @@ import myAside from "./myAside.vue"
<myAside></myAside>
</el-aside>
<el-main>
<transition name="el-zoom-in-top">
<router-view class="my-main"></router-view>
</transition>
</el-main>
</el-container>
</el-container>
@ -36,8 +37,10 @@ import myAside from "./myAside.vue"
.el-main {
background-color: #f5f5f5;
width: calc(100vw - 100px);
min-height: calc(100vh - 60px);
height: calc(100vh - 60px);
border-radius: 2rem 0 0 0;
overflow-y: scroll;
.my-main {
height: 100%;
width: 100%;

View File

@ -1,14 +1,30 @@
<script setup>
import { useRoute, useRouter } from 'vue-router'
const router = useRouter()
const route = useRoute()
const navTo = (name) => {
router.push({ name })
}
</script>
<template>
<div class="my-card">
<div class="list-item active">
<div class="list-item" :class="{'active': route.name=='home'}" @click="navTo('home')">
<el-icon size="30"><Sell /></el-icon>
<div>收银</div>
</div>
<div class="list-item" :class="{'active': route.name=='orderList'}" @click="navTo('orderList')">
<el-icon size="30"><DataLine /></el-icon>
<div>订单</div>
</div>
<div class="list-item" :class="{'active': route.name=='shop'}" @click="navTo('shop')">
<el-icon size="30"><ShoppingBag /></el-icon>
<div>商品</div>
</div>
</div>
</template>
@ -23,6 +39,7 @@
width: 4.5rem;
height: 4.5rem;
border-radius: 0.7rem;
cursor: pointer;
display: flex;
flex-direction: column;
justify-content: center;
@ -31,6 +48,7 @@
&.active {
background-color: #1890ff;
transition: 300ms;
}
}
}

View File

@ -9,6 +9,8 @@ const userStore = useUserStore();
const merInfo = ref({});
merInfo.value = userStore.userInfo.mer_info;
const service = ref({});
service.value = userStore.userInfo.service;
const getInfo = () => {
info().then((res) => {
@ -36,18 +38,21 @@ const onLogout = () => {
<template>
<div class="my-card">
<div class="card-header">
<el-image
src="https://multi-store.crmeb.net/uploads/attach/store/2024/03/20240314/6cea2e0fd02480fc6fb62a9783a9ac43.png"
<el-image style="height: 3rem;width: 3rem;"
src="https://lihai001.oss-cn-chengdu.aliyuncs.com/def/56a52202404051428413664.png"
></el-image>
<div class="card-title">里海收银系统</div>
</div>
<div class="card-body">
<el-dropdown trigger="hover">
<div class="el-dropdown-link">
<el-avatar :src="merInfo.mer_avatar" icon="user-filled"/>
<el-avatar :src="service.avatar" icon="user-filled"/>
<div class="info">
<div>{{ merInfo.mer_name }}</div>
<div>{{ merInfo.service_phone }}</div>
<div>
{{ service.nickname }}
<span style="margin-left: 0.3rem;">({{ merInfo.company_name || merInfo.mer_name }})</span>
</div>
<div>{{ userStore.userInfo.account }}</div>
</div>
<el-icon class="el-icon--right">
<arrow-down />

View File

@ -1,4 +1,4 @@
import { createRouter, createWebHistory } from 'vue-router';
import { createRouter, createWebHashHistory } from 'vue-router';
import layout from '@/layout/index.vue';
import { useUserStore } from '@/store/user.js';
@ -14,6 +14,21 @@ const routes = [
path: '/home',
name: 'home',
component: () => import('@/views/home/index.vue'),
},
{
path: '/order',
name: 'order',
component: () => import('@/views/order/index.vue'),
},
{
path: '/orderList',
name: 'orderList',
component: () => import('@/views/order/indexList.vue'),
},
{
path: '/shop',
name: 'shop',
component: () => import('@/views/shop/index.vue'),
}
]
},
@ -22,10 +37,16 @@ const routes = [
name: 'login',
component: () => import('@/views/login/index.vue'),
},
// 注意:通配符路由要放在最后
{
path: '/:catchAll(.*)',
name: 'notFound',
component: () => import('@/views/notFound/index.vue') // 使用 404 页面组件
}
];
const router = createRouter({
history: createWebHistory(),
history: createWebHashHistory(),
routes
});

5
src/utils/audio.js Normal file
View File

@ -0,0 +1,5 @@
export const audioplay = (text) => {
let ssu = new window.SpeechSynthesisUtterance(text);
window.speechSynthesis.speak(ssu);
}

View File

@ -1,10 +1,14 @@
import axios from "axios";
import { ElMessage } from "element-plus";
import router from "@/router/index.js";
import { useUserStore } from "../store/user";
const request = axios.create({
baseURL: import.meta.env.VITE_BASE_URL + '/api',
timeout: 5000
timeout: 10000
})
// 请求拦截器
request.interceptors.request.use(
config => {
@ -27,11 +31,34 @@ request.interceptors.request.use(
request.interceptors.response.use(
response => {
// 对响应数据做些什么,例如解析数据、统一处理错误等
if (response.data.status === 401 || response.data.status === 40000) {
ElMessage({
message: response.data.message,
type: 'error',
})
setTimeout(()=>{
const userStore = useUserStore();
userStore.setUserInfo({});
userStore.setToken('');
router.push('/login');
},700)
}
if (response.data.status === 400) {
ElMessage({
message: response.data.message,
type: 'error',
})
throw new Error(response.data.message);
}
return response.data;
},
error => {
// 处理响应错误
console.error(error);
ElMessage({
message: error.response?.data?.message || error,
type: 'error',
})
return Promise.reject(error);
}
);

32
src/utils/ws.js Normal file
View File

@ -0,0 +1,32 @@
export const initWS = (token)=>{
let ws = new WebSocket('ws://192.168.1.22:8324?type=mer&token='+token);
ws.onopen = function (e) {
console.log('WebSocket 连接已建立');
// 开始发送心跳
startHeartbeat();
}
ws.onclose = () => {
console.log('WebSocket 连接已关闭');
// 停止发送心跳
stopHeartbeat();
};
let heartbeatTimer = null;
// 开始发送心跳
const startHeartbeat = () => {
heartbeatTimer = setInterval(() => {
console.log('发送心跳消息');
ws.send(JSON.stringify({ type: 'heartbeat' }));
}, 30*1000);
}
// 停止发送心跳
const stopHeartbeat = () => {
clearInterval(heartbeatTimer);
}
return ws;
}

View File

@ -1,57 +1,138 @@
<script setup>
import { ref } from 'vue';
import { cartListApi } from "@/api/store.js";
import { ref } from "vue";
import { cartListApi, cartDeleteApi, cartChangeApi } from "@/api/store.js";
import price from "./price.vue";
const list = ref([])
const list = ref([]);
const allPrice = ref(0); //
const discounts = ref(0); //
const clearAll = () => {
list.value = []
}
let cart_id = [];
list.value.map((item) => {
cart_id.push(item.cart_id);
});
if (cart_id.length == 0) return;
deleteShop(cart_id);
};
const deleteOne = (index)=>{
list.value.splice(index,1)
}
const deleteOne = (cart_id) => {
list.value = list.value.filter((item) => item.cart_id != cart_id);
deleteShop([cart_id]);
};
const getList = (item)=>{
const deleteShop = (arr) => {
cartDeleteApi({
cart_id: arr,
}).then((res) => {
console.log(res);
getList();
});
};
const getList = () => {
allPrice.value = 0;
discounts.value = 0;
cartListApi({
source: 300
}).then(res=>{
list.value = res.data.list
})
}
source: 300,
}).then((res) => {
if (res.data?.list?.length > 0) {
list.value = res.data.list[0].list;
list.value.forEach((item) => {
allPrice.value += item.productAttr.price * item.cart_num;
});
} else list.value = [];
});
};
getList();
const emit = defineEmits(['goPay'])
const emit = defineEmits(["goPay","editAttr"]);
const goPay = () => {
emit('goPay')
emit("goPay");
};
const changeCartNum = (val, old) => {
cartChangeApi(val.cart_id, {
cart_num: val.cart_num,
}).then((res) => {
allPrice.value = 0;
list.value.forEach((item) => {
allPrice.value += item.productAttr.price * item.cart_num;
});
});
};
const editAttr = (data)=>{
emit("editPupop", data);
}
const editItem = (id, data)=>{
cartChangeApi(id, data).then((res) => {
getList()
});
}
const changeAllPrice = (price)=>{
discounts.value = allPrice.value - price;
allPrice.value = +price;
}
const nowPrice = ref(0);
const priceRef = ref(null);
const showPrice = ()=>{
priceRef.value.show(true, allPrice.value);
}
defineExpose({
getList
})
getList,
list,
editItem,
discounts,
allPrice
});
</script>
<template>
<div class="my-order">
<div class="header-nav">
<div class="nav-item">已选购 <span>{{ 0 }}</span> </div>
<div class="nav-item-clear" @click="clearAll"><el-icon><Delete /></el-icon></div>
<div class="nav-item">
已选购 <span>{{ list.length }}</span>
</div>
<div class="nav-item-clear" @click="clearAll">
<el-icon><Delete /></el-icon>
</div>
</div>
<div class="order-list">
<el-empty v-if="list.length == 0" description="请点击右侧添加商品" />
<div v-else class="order-item" v-for="(item, index) in list" :key="index">
<el-image class="order-item-img" src="https://multi-store.crmeb.net/uploads/attach/2024/03/01/8149b6d6bfc22ad622ec478528310c43.jpg"></el-image>
<el-image loading="lazy"
class="order-item-img"
:src="
(item.productAttr && item.productAttr.image) || item.product.image
"
></el-image>
<div class="order-item-info">
<div class="order-item-title">
<div class="title">很不错的商品名称很不错的商品名称很不错的商品名称很不错的商品名称</div>
<div class="delete" @click="deleteOne">删除</div>
<div class="title">{{ item.spu.store_name }}</div>
<div class="delete" @click="deleteOne(item.cart_id)">删除</div>
</div>
<div class="order-item-sku">
<span style="display: flex;align-items: center;" @click="editAttr(item)">商品规格: {{ item.productAttr.sku || '默认规格' }}<el-icon size="16" style="margin-left: 0.2rem;"><ArrowDown /></el-icon></span>
</div>
<div class="order-item-sku">设备规格</div>
<div class="order-item-price">
<div>¥<span>100</span></div>
<div><el-input-number v-model="item.num" :min="1" :step="1" /></div>
<div>
¥<span>{{ item.productAttr.price }}</span>
</div>
<div>
<el-input-number
v-model="item.cart_num"
step-strictly
:min="1"
:step="1"
@change="changeCartNum(item)"
/>
</div>
</div>
</div>
</div>
@ -59,15 +140,32 @@ defineExpose({
<div class="order-footer">
<div class="order-total">
<div class="price">
<div class="total-item">实付: <span>¥<span style="font-size: 1.4rem;">{{ 10000.65 }}</span></span></div>
<div class="total-item">优惠: <span>¥<span>{{ 0 }}</span></span></div>
<div class="total-item">
实付:
<span
>¥<span style="font-size: 1.4rem">{{
allPrice.toFixed(2)
}}</span></span
>
</div>
<div class="total-item">
优惠:
<span
>¥<span>{{ discounts.toFixed(2) }}</span></span
>
</div>
</div>
<div class="update-price">
<el-button class="btn" type="primary" @click="showPrice">改价</el-button>
</div>
<div class="update-price"><el-button class="btn" type="primary">改价</el-button></div>
</div>
<div class="order-btn">
<el-button class="btn" type="primary" @click="goPay">立即结账</el-button>
<el-button class="btn" type="primary" @click="goPay" :disabled="list.length == 0"
>立即结账</el-button
>
</div>
</div>
<price ref="priceRef" @submit="changeAllPrice"></price>
</div>
</template>
@ -135,6 +233,7 @@ defineExpose({
}
}
.order-item-sku {
cursor: pointer;
font-size: 0.8rem;
color: #999;
}

View File

@ -1,112 +0,0 @@
<template>
<el-drawer :size="800" v-model="drawer" direction="rtl" @open="open">
<template #header>
<h4>选择支付方式</h4>
</template>
<template #default>
<div class="dra-body">
<div class="header">
<div class="left" :class="{'active': active==1}" @click="active=1">微信</div>
<div class="right" :class="{'active': active==2}" @click="active=2">现金收款</div>
</div>
<div style="color: #999;padding: 2rem 0 0.3rem 0;">应收金额(): </div>
<div style="color: #f5222d;padding-bottom: 2rem;">¥<span style="font-size: 1.6rem;">{{ '0.00' }}</span></div>
<div class="card1" v-if="active==1">
<el-input ref="codeRef" v-model="input" autofocus class="code-input" placeholder="请点击输入框聚焦扫码或输入编码号" />
<div class="tips"></div>
</div>
<div class="card2" v-else>
<el-input ref="codeRef" v-model="input" autofocus class="code-input" placeholder="请点击输入框输入金额" />
</div>
</div>
</template>
<template #footer>
<div style="width: 100%;display: flex;justify-content: center;">
<el-button class="cancel-btn" @click="cancelClick">取消收款</el-button>
</div>
</template>
</el-drawer>
</template>
<script setup>
import { ref, watch, nextTick } from "vue";
const drawer = ref(false);
const active = ref(1);
const input = ref("");
const codeRef = ref("");
const cancelClick = () => {
drawer.value = false;
};
const open = ()=>{
nextTick(()=>{
setTimeout(()=>{
codeRef.value.focus();
},300)
})
}
defineExpose({
drawer,
});
</script>
<style lang="scss" scoped>
.dra-body {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
.header{
width: 25rem;
display: flex;
&>div{
flex: 1;
border: 1px solid #ccc;
text-align: center;
padding: 0.6rem 0;
cursor: pointer;
}
.left{
border-right: none;
border-radius: 5rem 0 0 5rem;
}
.right{
border-left: none;
border-radius: 0 5rem 5rem 0;
}
.active{
background-color: #1890ff;
color: #fff;
transition: 300ms;
border-color: #1890ff;
}
}
.card1{
.code-input{
width: 100%;
height: 3rem;
}
.tips {
width: 38rem;
height: 16rem;
background: url("https://multi-store.crmeb.net/view_cashier/img/alipay.d0e0aa1f.png");
background-size: 100% 100%;
background-repeat: no-repeat;
}
}
}
.cancel-btn{
width: 60%;
border-color: #1890ff;
color: #1890ff;
border-radius: 5rem;
height: 3rem;
font-size: 1.2rem;
}
</style>

View File

@ -0,0 +1,79 @@
<script setup>
import { ref } from 'vue'
import { ElMessageBox } from 'element-plus'
const dialogVisible = ref(false)
const allPrice = ref(0)
const show = (e, p = 0)=>{
dialogVisible.value = e;
allPrice.value = p;
}
const priceInfo = ref({
nowPrice: '',
percentage: '',
})
const inputPrice = (e)=>{
priceInfo.value.percentage = (e / allPrice.value * 100).toFixed(2);
}
const inputPercentage = (e)=>{
priceInfo.value.nowPrice = (allPrice.value * e / 100).toFixed(2);
}
const emit = defineEmits(['submit'])
const submit = ()=>{
emit('submit', priceInfo.value.nowPrice);
dialogVisible.value = false;
}
defineExpose({
show
})
</script>
<template>
<el-dialog
v-model="dialogVisible"
title="订单改价"
width="650"
>
<div class="price">
<div class="flex">
<el-input style="flex: 3;margin: 10px;height: 2.5rem;" v-model="priceInfo.nowPrice" placeholder="请输入改价后的价格" @input="inputPrice">
<template #suffix></template>
</el-input>
<el-input style="flex: 2;margin: 10px;height: 2.5rem;" v-model="priceInfo.percentage" placeholder="请输入比例" @input="inputPercentage">
<template #suffix>%</template>
</el-input>
</div>
<div style="margin: 10px;padding-bottom: 40px;">改价后金额: <span style="color: #f5222d;font-weight: bold;margin-left: 1rem;">¥{{ priceInfo.nowPrice || allPrice || ' - - ' }}</span></div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button class="ok-btn" type="primary" @click="submit">
确定
</el-button>
</div>
</template>
</el-dialog>
</template>
<style scoped lang="scss">
.dialog-footer{
.ok-btn{
width: 100%;
height: 2.5rem;
border-radius: 2.5rem;
}
}
.price{
border-top: 1px solid #eee;
padding-top: 1rem;
.flex{
display: flex;
}
}
</style>

View File

@ -1,6 +1,7 @@
<script setup>
import { ref } from 'vue'
import { ElMessageBox } from 'element-plus'
import { getAttrValue } from "@/api/shop.js"
const dialogVisible = ref(false)
@ -8,15 +9,50 @@ const show = (e)=>{
dialogVisible.value = e;
}
const form = ref({});
const active = ref(null);
const loading = ref(false);
const mode = ref('add');
const editForm = ref({});
const setForm = (data, type='add')=>{
mode.value = type;
if(type == 'add'){
form.value = data;
active.value = data.attr[0];
}
else {
loading.value = true;
editForm.value = data;
getAttrValue(data.product_id).then(res=>{
res.data.attrValue = JSON.parse(JSON.stringify(res.data.attr))
res.data.attr = Object.keys(res.data.sku)
form.value = res.data;
active.value = res.data.attr[0];
loading.value = false;
}).catch(err=>{
loading.value = false;
})
}
}
const emit = defineEmits(['changeItem'])
const changeItem = ()=>{
emit('changeItem', item, change);
dialogVisible = false
if(mode.value == 'add') emit('changeItem', form.value, active.value);
else emit('editItem', editForm.value.cart_id, {
cart_num: editForm.value.cart_num,
product_attr_unique: form.value.sku[active.value].unique,
});
dialogVisible.value = false
}
const changeActive = (item)=>{
active.value = item;
}
defineExpose({
show
show,
setForm
})
</script>
@ -27,30 +63,28 @@ defineExpose({
title="商品规格"
width="650"
>
<div class="shop">
<div class="shop-info">
<div class="shop" v-loading="loading">
<div class="shop-info" v-if="form.sku">
<div class="shop-info-left">
<el-image src="https://multi-store.crmeb.net/uploads/attach/2024/03/01/8149b6d6bfc22ad622ec478528310c43.jpg"></el-image>
<el-image loading="lazy" :src="form.sku[active]?.image || form.image"></el-image>
</div>
<div class="shop-info-right">
<div class="shop-info-right-top">香奈儿Chanel五号香水经典50ml礼盒装送女 香奈儿Chanel五号香水经典50ml礼盒装送女 香奈儿Chanel五号香水经典50ml礼盒装送女香奈儿Chanel五号香水经典50ml礼盒装送女</div>
<div class="shop-info-right-center">库存100</div>
<div class="shop-info-right-price">¥<span>{{0.10}}</span></div>
<div class="shop-info-right-top">{{ form.store_name }}</div>
<div class="shop-info-right-center">库存{{ form.sku[active]?.stock || 0 }}</div>
<div class="shop-info-right-price">¥<span>{{form.sku[active]?.price || form.price}}</span></div>
</div>
</div>
<div class="shop-sku">
<div class="title">产品</div>
<div class="sku">
<el-space wrap :size="20">
<div class="sku-item sku-item_active"></div>
<div class="sku-item"></div>
<div class="sku-item"></div>
<div class="sku-item " :class="{'sku-item_active': active==item}" @click="changeActive(item)" v-for="(item, index) in form.attr" :key="index">{{ item || '默认规格' }}</div>
</el-space>
</div>
</div>
</div>
<template #footer>
<template #footer v-if="!(mode != 'add' && form.attr && form.attr.length==1)">
<div class="dialog-footer">
<el-button class="ok-btn" type="primary" @click="changeItem">
确定
@ -115,6 +149,7 @@ defineExpose({
}
.sku{
.sku-item{
cursor: pointer;
padding: 0.5rem 1rem;
background-color: #f5f5f5;
border-radius: 3rem;

View File

@ -4,28 +4,29 @@ import { ref, watch } from "vue";
const props = defineProps({
storeList: {
type: Array,
default:()=>[]
}
})
default: () => [],
},
});
const emit = defineEmits(['getStoreList'])
const emit = defineEmits(["getStoreList", "changeItem", "loadMore"]);
const bar_code = ref('');
const bar_code = ref("");
const loadMore = () => {
console.log("loadMore");
emit("loadMore", {
bar_code: bar_code.value,
});
};
const changeItem = (item) => {
emit('changeItem', item)
}
emit("changeItem", item);
};
const handleEnter = () => {
emit('getStoreList', {
bar_code: bar_code.value
})
}
emit("getStoreList", {
bar_code: bar_code.value,
});
};
</script>
<template>
@ -54,14 +55,23 @@ const handleEnter = () => {
style="overflow: auto"
>
<el-space wrap :size="20">
<div class="shop-item" v-for="(item, index) in storeList" :key="index" @click="changeItem(item)">
<el-image
:src="item.image"
></el-image>
<div
class="shop-item"
v-for="(item, index) in storeList"
:key="index"
@click="changeItem(item)"
>
<el-image loading="lazy" :src="item.image"></el-image>
<div class="shop-name">{{ item.store_name }}</div>
<div class="shop-price">
¥<span>{{ item.price }}</span>
</div>
<div class="no-stock" v-if="item.stock==0">
<div>
<span>暂无</span>
<span>库存</span>
</div>
</div>
</div>
</el-space>
</div>
@ -130,6 +140,8 @@ const handleEnter = () => {
display: flex;
justify-content: space-between;
flex-direction: column;
position: relative;
overflow: hidden;
.el-image {
border-radius: 0.5rem;
@ -161,6 +173,29 @@ const handleEnter = () => {
color: #fff;
}
}
.no-stock {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba($color: #000000, $alpha: 0.2);
display: flex;
justify-content: center;
align-items: center;
div{
background-color: #4e4e4e;
color: #fff;
border-radius: 50%;
width: 5rem;
height: 5rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
}
}
}
}

View File

@ -3,9 +3,10 @@ import order from "./component/order.vue";
import shop from "./component/shop.vue";
import padding from "./component/padding.vue";
import pupop from "./component/pupop.vue";
import pay from "./component/pay.vue";
import pay from "@/components/pay.vue";
import { ref, nextTick } from "vue";
import { storeListApi, cartCreateApi } from "@/api/store.js";
import { cartCreateApi, orderCheckApi } from "@/api/store.js";
import { storeListApi } from "@/api/shop.js";
import { useUserStore } from "@/store/user.js";
const pupopRef = ref(null);
@ -18,7 +19,7 @@ const storeList = ref([]);
const userStore = useUserStore();
const where = ref({
page: 1,
page: 0,
limit: 30,
});
const getStoreList = (data) => {
@ -28,21 +29,30 @@ const getStoreList = (data) => {
...data,
};
storeListApi(userStore.userInfo.service.mer_id, where.value).then((res) => {
storeList.value = res.data.list.map((item) => {
if(res.data?.list?.length < where.value.limit) loadEnd.value = true;
let list = res.data.list.map((item) => {
item.attr = Object.keys(item.sku);
return item;
});
if (storeList.value.length == 1 && isAllDigits(data.bar_code)) {
changeItem(storeList.value[0], storeList.value[0].attr[0]);
storeList.value = storeList.value.concat(list);
if (data.bar_code && storeList.value.length == 1 && isAllDigits(data.bar_code)) {
cartAddInfo(storeList.value[0], storeList.value[0].attr[0]);
}
});
};
const loadEnd = ref(false);
const loadMore = (data)=>{
if(loadEnd.value) return;
where.value.page++;
getStoreList(data);
}
function isAllDigits(str) {
return /^\d+$/.test(str);
}
const changeItem = (item, change = "") => {
const cartAddInfo = (item, change = "") => {
// console.log(item, change);
let q = {
is_new: 0,
@ -56,25 +66,65 @@ const changeItem = (item, change = "") => {
// sale_type: 1
};
cartCreateApi(q).then((res) => {
console.log(res);
orderRef.value.getList(item);
orderRef.value.getList();
});
};
getStoreList();
const changeItem = (item, change) => {
if(!item.attr || item.attr.length == 0 || item.attr.length==1) return cartAddInfo(item, item.attr[0] ? item.attr[0] : '');
else if(change) return cartAddInfo(item, change);
else {
pupopRef.value.setForm(item, 'add');
pupopRef.value.show(true);
}
}
const editItem = (id, data) => {
orderRef.value.editItem(id, data);
}
const editPupop = (item) => {
pupopRef.value.setForm(item, 'edit');
pupopRef.value.show(true);
}
//
const checkOut = () => {
let cart_id = orderRef.value.list.map((item) => item.cart_id);
let query = {
takes: [],
use_coupon: {},
use_integral: false,
cart_id: cart_id,
}
if(orderRef.value.discounts>0){
query.deduction_price = orderRef.value.discounts.toFixed(2);
query.pay_type = "micropay";
}
orderCheckApi(query).then((res) => {
payRef.value.setForm({
data: res.data,
cart_id: cart_id,
});
});
};
// getStoreList();
const goPay = () => {
checkOut();
payRef.value.drawer = true;
};
nextTick(() => {
// pupopRef.value.show(true);
});
const paySuccess = ()=>{
orderRef.value.getList();
}
</script>
<template>
<div class="my-card">
<order ref="orderRef" @goPay="goPay" />
<order ref="orderRef" @goPay="goPay" @editPupop="editPupop"/>
<padding />
<shop
ref="shopRef"
@ -82,9 +132,10 @@ nextTick(() => {
:storeList="storeList"
@getStoreList="getStoreList"
@changeItem="changeItem"
@loadMore="loadMore"
/>
<pupop ref="pupopRef" />
<pay ref="payRef" />
<pupop ref="pupopRef" @changeItem="changeItem" @editItem="editItem"/>
<pay ref="payRef" @paySuccess="paySuccess"/>
</div>
</template>

View File

@ -42,17 +42,23 @@ const onLogin = () => {
});
login(formLogin.value)
.then((res) => {
console.log(res);
if(res.data){
userStore.setToken(res.data.token);
info().then(({data}) => {
if(!data.service){
return ElMessage({
message: "请联系管理员开通服务",
type: "error",
})
}
userStore.setUserInfo(data);
router.push("/");
});
}
})
.catch((err) => {
ElMessage({
message: err.message,
type: "error",
});
});
};
@ -100,7 +106,7 @@ onMounted(() => {
width: 100vw;
height: 100vh;
box-sizing: border-box;
background-image: url(https://multi-store.crmeb.net/view_cashier/img/bg.b8f6b872.png);
background-image: url(https://lihai001.oss-cn-chengdu.aliyuncs.com/def/78559202404051452598712.png);
background-size: 100% 100%;
background-repeat: no-repeat;
display: flex;

View File

@ -0,0 +1,24 @@
<script setup>
const back = () => {
window.location = "/"
}
</script>
<template>
<div style="width: 100vw; height: 100vh">
<div style="display: flex; justify-content: center; padding-top: 20vh">
<el-image loading="lazy"
style="width: 30rem; height: 20rem"
src="/src/assets/icon-404-color.svg"
></el-image>
<div style="display: flex;flex-direction: column;justify-content: center;padding-left: 3rem;">
<div style="font-size: 5rem; font-weight: bold">404</div>
<div style="padding-bottom: 1rem">您的页面没有找到</div>
<el-button type="primary" @click="back">返回首页</el-button>
</div>
</div>
</div>
</template>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,127 @@
<script setup>
import { ref } from "vue";
import {
orderListApi,
orderStatusApi,
orderLadingApi,
cartListApi
} from "@/api/store.js";
import { useUserStore } from "@/store/user.js";
import { ElMessage } from "element-plus";
import { useRouter } from "vue-router";
const userStore = useUserStore();
const list = ref([]);
const keyword = ref('');
const tabPosition = ref(1); // 1-, 2-
const payRef = ref(null);
const where = ref({
page: 1,
limit: 20,
});
const loading = ref(false);
const total = ref(0);
const orderList = ref([]);
const getOrderList = () => {
loading.value = true;
if (tabPosition.value == 1) where.value.paid = null;
if (tabPosition.value == 2) where.value.paid = 0;
orderListApi(userStore.userInfo.service.mer_id, where.value).then((res) => {
orderList.value = res.data.list;
total.value = res.data.count;
loading.value = false;
});
};
getOrderList();
</script>
<template>
<div class="my-order">
<div class="header-nav">
<div class="nav-item">
订单列表
</div>
<div class="nav-item-clear">
<el-icon><Delete /></el-icon>
</div>
</div>
<div class="header-input">
<el-input v-model="keyword" placeholder="请输入订单编号">
<template #append>
<el-button type="primary" style="background-color: #1890ff;color: #fff;border-radius: 0 5px 5px 0;">搜索</el-button>
</template>
</el-input>
</div>
<div class="order-list">
<div class="item" v-for="(item, index) in orderList" :key="index">
<div class="top">
<div class="sn">单号: {{ item.order_sn }}</div>
<div class="create-time"> {{ item.create_time }}</div>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.my-order {
border-radius: 1.2rem;
height: 100%;
background-color: #fff;
width: 30rem;
position: relative;
overflow: hidden;
.header-nav {
display: flex;
justify-content: space-between;
padding: 1rem;
height: 1.5rem;
span {
color: #ff4a00;
}
.nav-item-clear {
display: flex;
align-items: center;
font-size: 0.8rem;
cursor: pointer;
}
}
.header-input{
padding: 1rem;
padding-top: 0;
height: 2.5rem;
border-bottom: 1px solid #eee;
}
.order-list {
height: calc(100vh - 100px - 8.2rem);
overflow-y: auto;
.item{
padding: 1rem;
.top{
display: flex;
justify-content: space-between;
align-items: flex-end;
.sn{
font-weight: bold;
font-size: 0.9rem;
&::before{
}
}
.create-time{
font-size: 0.8rem;
}
}
}
}
}
</style>

39
src/views/order/index.vue Normal file
View File

@ -0,0 +1,39 @@
<script setup>
import order from "./component/order.vue";
import { ref, nextTick } from "vue";
</script>
<template>
<div class="my-card">
<order ref="orderRef" @goPay="goPay" @editPupop="editPupop"/>
</div>
</template>
<style lang="scss">
.my-card {
display: flex;
}
/* 修改滚动条的样式 */
::-webkit-scrollbar {
width: 5px; /* 设置滚动条的宽度 */
}
/* 设置滚动条的轨道样式 */
::-webkit-scrollbar-track {
background-color: #f1f1f1; /* 设置轨道的背景色 */
margin: 20px 0;
}
/* 设置滚动条的滑块样式 */
::-webkit-scrollbar-thumb {
background-color: #ccc; /* 设置滑块的背景色 */
border-radius: 5px; /* 设置滑块的圆角 */
}
/* 设置滚动条鼠标悬停时的滑块样式 */
::-webkit-scrollbar-thumb:hover {
background-color: #999; /* 设置鼠标悬停时滑块的背景色 */
}
</style>

View File

@ -0,0 +1,238 @@
<script setup>
import { ref } from "vue";
import {
orderListApi,
orderStatusApi,
orderLadingApi,
cartListApi
} from "@/api/store.js";
import { useUserStore } from "@/store/user.js";
import pay from "@/components/pay.vue";
import { ElMessage } from "element-plus";
import { useRouter } from "vue-router";
const userStore = useUserStore();
const orderList = ref([]);
const router = useRouter();
const tabPosition = ref(1); // 1-, 2-
const payRef = ref(null);
const where = ref({
page: 1,
limit: 20,
});
const loading = ref(false);
const total = ref(0);
const getOrderList = () => {
loading.value = true;
if (tabPosition.value == 1) where.value.paid = null;
if (tabPosition.value == 2) where.value.paid = 0;
orderListApi(userStore.userInfo.service.mer_id, where.value).then((res) => {
orderList.value = res.data.list;
total.value = res.data.count;
loading.value = false;
});
};
getOrderList();
const cartCount = ref(0);
const getCartList = () => {
cartListApi({
source: 300,
}).then((res) => {
cartCount.value = res.data.list?.length;
});
};
getCartList();
const changeTabPosition = (e) => {
where.value.page = 1;
getOrderList();
};
const prevClick = (e) => {
where.value.page = e;
getOrderList();
};
const nextClick = (e) => {
where.value.page = e;
getOrderList();
};
const currentChange = (e) => {
where.value.page = e;
getOrderList();
};
const paySuccess = () => {
getOrderList();
};
const rePay = (row) => {
payRef.value.setRePay({
price: row.pay_price,
order_id: row.group_order_id,
});
payRef.value.drawer = true;
};
const getOrderStatus = (id) => {
orderStatusApi({
order_sn: id,
})
.then((res) => {
if (res.data.paid == 1 || res.message == "支付成功") {
ElMessage({
message: res.message,
type: "success",
});
getOrderList();
} else {
ElMessage({
message: res.message,
type: "error",
});
}
})
.catch((err) => {});
};
const orderLadingSn = ref('')
const orderLading = () => {
dialogVisible.value = false;
orderLadingApi({
order_sn: orderLadingSn.value,
}).then((res) => {
ElMessage({
message: res.message,
type: "success",
});
router.push({
name: "home",
});
});
};
const dialogVisible = ref(false);
const orderLadingComfirm = (order_sn)=>{
orderLadingSn.value = order_sn;
dialogVisible.value = true;
}
const goHome = ()=>{
router.push({
name: "home",
});
}
</script>
<template>
<div v-loading="loading" element-loading-text="加载中" class="my-order">
<el-radio-group
v-model="tabPosition"
style="margin-bottom: 30px"
@change="changeTabPosition"
>
<el-radio-button :value="1">全部</el-radio-button>
<el-radio-button :value="2">未支付</el-radio-button>
</el-radio-group>
<el-table :data="orderList" style="width: 100%">
<el-table-column prop="group_order_id" label="ID" width="100" />
<el-table-column prop="order_sn" label="订单号" width="260" />
<el-table-column prop="total_price" label="订单金额" />
<el-table-column prop="paid" label="支付状态">
<template #default="scope">
<span v-if="scope.row.paid == 1">已支付</span>
<span v-else style="color: #ff4a00">未支付</span>
</template>
</el-table-column>
<el-table-column prop="create_time" label="订单创建时间" />
<el-table-column prop="pay_time" label="订单支付时间">
<template #default="scope">
<span v-if="scope.row.pay_time">{{ scope.row.pay_time }}</span>
<div v-else class="flex">
<el-button type="primary" link @click="rePay(scope.row)"
>重新支付</el-button
>
<el-button
type="primary"
link
@click="getOrderStatus(scope.row.order_sn)"
>检测状态</el-button
>
<el-button
type="primary"
link
@click="orderLadingComfirm(scope.row.order_sn)"
>提单</el-button
>
</div>
</template>
</el-table-column>
</el-table>
<el-pagination
:page-size="where.limit"
layout="prev, pager, next"
:total="total"
@prev-click="prevClick"
@next-click="nextClick"
@current-change="currentChange"
/>
<pay ref="payRef" @paySuccess="paySuccess" />
<el-dialog
v-model="dialogVisible"
title="提示"
width="500"
>
<span>提单前请清空购物车, 避免提单的商品与购物车商品混合, 请确保购物车内无数据后再进行提单</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button v-if="cartCount>0" @click="goHome">
前去清空购物车
</el-button>
<el-button v-else type="primary" @click="orderLading">
确认提单
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<style lang="scss" scoped>
.my-order {
background-color: #fff;
border-radius: 1.2rem;
box-sizing: border-box;
padding: 1rem;
overflow-y: scroll;
}
/* 修改滚动条的样式 */
::-webkit-scrollbar {
width: 5px; /* 设置滚动条的宽度 */
}
/* 设置滚动条的轨道样式 */
::-webkit-scrollbar-track {
background-color: #f1f1f1; /* 设置轨道的背景色 */
margin: 20px 0;
}
/* 设置滚动条的滑块样式 */
::-webkit-scrollbar-thumb {
background-color: #ccc; /* 设置滑块的背景色 */
border-radius: 5px; /* 设置滑块的圆角 */
}
/* 设置滚动条鼠标悬停时的滑块样式 */
::-webkit-scrollbar-thumb:hover {
background-color: #999; /* 设置鼠标悬停时滑块的背景色 */
}
</style>

178
src/views/shop/index.vue Normal file
View File

@ -0,0 +1,178 @@
<script setup>
import { ref } from "vue";
import { storeListApi, userFreeTrialApi } from "@/api/shop.js";
import { useUserStore } from "@/store/user.js";
import { ElMessage } from "element-plus";
const orderList = ref([]);
const userStore = useUserStore();
const where = ref({
page: 1,
limit: 15,
});
const loading = ref(false);
const total = ref(0);
const getOrderList = () => {
loading.value = true;
storeListApi(userStore.userInfo.service.mer_id, where.value).then((res) => {
orderList.value = res.data.list;
total.value = res.data.count;
loading.value = false;
});
};
getOrderList();
const prevClick = (e) => {
where.value.page = e;
getOrderList();
};
const nextClick = (e) => {
where.value.page = e;
getOrderList();
};
const currentChange = (e) => {
where.value.page = e;
getOrderList();
};
const dialogFormVisible = ref(false);
const form = ref({});
const edit = (row) => {
form.value = row;
dialogFormVisible.value = true;
};
const submitUpdate = () => {
let obj = {
attr: form.value.attr||[],
attrValue: form.value.attrValue,
mer_cate_id: form.value.merCateId || [],
spec_type: form.value.spec_type,
is_stock: 1,
};
userFreeTrialApi(form.value.product_id, obj).then((res) => {
ElMessage({
message: res.message,
type: "success",
})
dialogFormVisible.value = false;
getOrderList();
}).catch(err=>{
ElMessage({
message: err,
type: "error",
})
});
};
</script>
<template>
<div v-loading="loading" element-loading-text="加载中" class="my-shop">
<el-table :data="orderList" style="width: 100%">
<el-table-column prop="product_id" label="ID" width="100" />
<el-table-column prop="image" label="图片" width="120">
<template #default="scope">
<el-image loading="lazy"
style="width: 60px; height: 60px"
:src="scope.row.image"
></el-image>
</template>
</el-table-column>
<el-table-column prop="store_name" label="商品名称" width="500" />
<el-table-column prop="price" label="售价" />
<el-table-column prop="stock" label="库存" />
<el-table-column label="操作" width="120">
<template #default="scope">
<el-button type="primary" link @click="edit(scope.row)"
>编辑</el-button
>
</template>
</el-table-column>
</el-table>
<el-pagination
:page-size="where.limit"
layout="prev, pager, next"
:total="total"
@prev-click="prevClick"
@next-click="nextClick"
@current-change="currentChange"
/>
<el-dialog v-model="dialogFormVisible" title="编辑商品库存" width="800">
<el-table
v-if="form.attrValue"
:data="form.attrValue"
stripe
style="width: 100%"
>
<el-table-column prop="image" label="图片" width="180">
<template #default="scope">
<el-image loading="lazy"
style="width: 5rem; height: 5rem"
:src="scope.row.image || form.image"
/>
</template>
</el-table-column>
<el-table-column prop="sku" label="名称" width="180">
<template #default="scope">
<span>{{ scope.row.sku || form.store_name }}</span>
</template>
</el-table-column>
<el-table-column prop="price" label="价格" />
<el-table-column prop="stock" label="库存">
<template #default="scope">
<el-input-number
v-model="scope.row.stock"
step-strictly
:min="0"
:step="1"
/>
</template>
</el-table-column>
</el-table>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogFormVisible = false">取消</el-button>
<el-button type="primary" @click="submitUpdate"> 确定 </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<style lang="scss" scoped>
.my-shop {
background-color: #fff;
border-radius: 1.2rem;
box-sizing: border-box;
padding: 1rem;
overflow-y: scroll;
}
/* 修改滚动条的样式 */
::-webkit-scrollbar {
width: 5px; /* 设置滚动条的宽度 */
}
/* 设置滚动条的轨道样式 */
::-webkit-scrollbar-track {
background-color: #f1f1f1; /* 设置轨道的背景色 */
margin: 20px 0;
}
/* 设置滚动条的滑块样式 */
::-webkit-scrollbar-thumb {
background-color: #ccc; /* 设置滑块的背景色 */
border-radius: 5px; /* 设置滑块的圆角 */
}
/* 设置滚动条鼠标悬停时的滑块样式 */
::-webkit-scrollbar-thumb:hover {
background-color: #999; /* 设置鼠标悬停时滑块的背景色 */
}
</style>

2
src/vite-env.d.ts vendored
View File

@ -1 +1,3 @@
/// <reference types="vite/client" />
declare module 'element-plus/dist/locale/zh-cn.mjs'
declare module './router'

View File

@ -14,6 +14,8 @@
"noEmit": true,
"jsx": "preserve",
"allowJs": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,

View File

@ -4,6 +4,7 @@ import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
base: './',
server: {
port: '5175',
host: '0.0.0.0',