sdz 6 months ago committed by Gitea
parent fada98d77d
commit 1ae31c89da

8
.idea/.gitignore vendored

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$" />
<option name="settingsModule" value="dailyfresh/settings.py" />
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="doNotUseTestRunner" value="false" />
<option name="trackFilePattern" value="migrations" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.9 (dailyfresh-master)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/templates" />
</list>
</option>
</component>
</module>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="Django default" uuid="9064b16a-5590-4f62-8cff-ddf29d23a4a8">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<imported>true</imported>
<remarks>$PROJECT_DIR$/dailyfresh/settings.py</remarks>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://127.0.0.1:3306/dailyfresh</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/static/页面说明.txt" charset="GBK" />
</component>
</project>

@ -0,0 +1,24 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="HtmlUnknownAttribute" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="placeholder" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="Django" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.12 (online_store)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (dailyfresh-master)" project-jdk-type="Python SDK" />
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/dailyfresh-master.iml" filepath="$PROJECT_DIR$/.idea/dailyfresh-master.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -0,0 +1,198 @@
# Python-Django-天天生鲜项目
初学django框架时按照传智播客python教程所学习的项目,该项目包含了实际开发中的电商项目中大部分的功能开发和知识点实践。
功能:用户注册,用户登录,购物车,用户中心,首页,订单系统,地址信息管理,商品列表,商品详情,支付功能等等,是一个完整的电商项目流程
## 技术栈
python + django + mysql + redis + celery + FastDFS(分布式图片服务器) + nginx
## 目标功能:
- [x] 功能模块
- [x] 用户模块
- [x] 注册
- [x] 登录
- [x] 激活(celery)
- [x] 退出
- [x] 个人中心
- [x] 地址管理
- [x] 商品模块
- [x] 首页(celery)
- [x] 商品详情
- [x] 商品列表
- [x] 搜索功能(haystack+whoose)
- [x] 购物车模块(redis)
- [x] 增加
- [x] 删除
- [x] 修改
- [x] 查询
- [x] 订单模块
- [x] 确认订单页面
- [x] 订单创建
- [x] 请求支付(支付宝)
- [x] 查询支付结果
- [x] 评论
- 项目启动:
- **注意: 项目启动前请先安装好各个环境,mysql+redis+nginx+fastDFS+celery等**
```
项目包安装
pip install -r requirements.txt
Django启动命令
python manage.py runserver
```
- uwsgi web服务器启动
- **注意: uwsgi开启需要修改[配置文件](./dailyfresh/settings.py)中的DEBUG和ALLOWED_HOSTS**
```
启动: uwsgi --ini 配置文件路径 / uwsgi --ini uwsgi.ini
停止: uwsgi --stop uwsgi.pid路径 / uwsgi --stop uwsgi.pid
```
- celery分布式任务队列启动
```
celery -A celery_tasks.tasks worker -l info
```
- redis服务端启动
```
sudo redis-server /etc/redis/redis.conf
```
- FastDFS服务启动
```
Trackerd服务
sudo /usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf start
storge服务
sudo /usr/bin/fdfs_storaged /etc/fdfs/storage.conf start
```
- nginx
```
启动nginx
sudo /usr/local/nginx/sbin/nginx
重启nginx
sudo /usr/local/nginx/sbin/nginx -s reload
```
- 建立索引文件--搜索引擎
新环境需要配置jieba分词,生成[whoose_cn_backend]()文件
```
python manage.py rebuild_index
```
- mysql事务隔离级别设置
```
sudo vim /etc/mysql/mysql.conf.d/mysql.cnf
transaction-isolation = READ-COMMITTED (读已提交)
```
## 项目包介绍
```
alipay-sdk-python==3.7.137
amqp==5.2.0
asgiref==3.8.1
asn1crypto==0.24.0
async-timeout==4.0.3
Authlib==0.5.1
billiard==4.2.0
Brotli==1.1.0
celery==5.4.0
certifi==2024.2.2
cffi==1.16.0
chardet==3.0.4
charset-normalizer==3.3.2
click==8.1.7
click-didyoumean==0.3.1
click-plugins==1.1.1
click-repl==0.3.0
colorama==0.4.6
configparser==3.5.0
construct==2.5.3
cryptography==42.0.7
Django==3.2.13
django-filter==24.2
django-haystack==3.2.1
django-redis==5.4.0
django-redis-sessions==0.5.6
django-tinymce==4.0.0
djangorestframework==3.15.1
djangorestframework-jwt==1.11.0
haystack==0.42
idna==3.7
itsdangerous==2.2.0
jieba==0.42.1
kombu==5.3.7
mysqlclient==2.2.4
pefile==2023.2.7
Pillow==9.5.0
prompt_toolkit==3.0.45
pyasn1==0.6.0
pycparser==2.22
pycryptodome==3.20.0
pycryptodomex==3.20.0
pygame==2.5.2
PyJWT==1.7.1
PyMySQL==1.0.2
pyOpenSSL==24.1.0
python-alipay-sdk==3.3.0
python-dateutil==2.9.0.post0
python-ptrace==0.9.9
pytz==2024.1
redis==5.0.4
requests==2.32.3
rsa==4.9
six==1.16.0
sqlparse==0.5.0
typing_extensions==4.12.0
tzdata==2024.1
urllib3==2.2.1
vine==5.1.0
wcwidth==0.2.13
Whoosh==2.7.4
wincertstore==0.2.1
```
## 注意点
pip install fdfs_client-py-master 存在bug需要下载特定版本
redis版本需要2.10.6 否则会报错,因为使用django的版本过低问题
如果使用乐观锁,需要修改mysql事务的隔离级别设置
## 总结
需求分析
1.1 用户模块
1 注册页
 注册时校验用户名是否已被注册。
 完成用户信息的注册
 给用户的注册邮箱发送邮件,用户点击邮件中的激活链接完成用户账户的激活。
2登陆页
 实现用户的登录功能
3用户中心
 用户中心信息页,显示登录用户的信息,包括用户名、电话和地址,同时页面下方显示出用户最近浏览的商品信息。
 用户中心地址页:显示登陆用户的默认收件地址,页面下方的表单可以新增用户的收货地址。
 用户中心订单页:显示登录用户的订单信息。
4其他
 如果用户已经登陆,页面顶部显示用户的订单信息。
1.2 商品模块
1首页
 动态指定首页轮播商品信息。
 动态指定首页活动信息。
 动态获取商品的种类信息并显示。
 动态指定首页显示的每个种类的商品(包括图片商品的文字商品)。
 点击某一个商品时跳转到商品的详情页面。
2商品详情页
 显示出某个商品的详细信息。
 页面下方显示出该商品的两个新品信息。
3商品列表页
 显示出某一个种类的商品的列表数据,分页显示并支持按照默认、价格和人气进行排序。
 页面下方显示出该商品的两个新品信息。
4其他
 通过搜索框搜索商品信息。
1.3 购物车模块
 列表页和详情页将商品添加到购物车。
 用户登录后,首页,详情页,列表页显示用户购物车中的商品数目。
 购物车页面:对用户购物车中的商品操作。如选择某件商品,增加或减少购物车中的商品数目。
1.4 订单相关
 提交订单页面:显示用户准备购买的商品信息。
 点击提交订单完成订单的创建。
 用户中心订单页显示用户的订单信息。
 点击支付完成订单的支付。
 点击评价完成订单的评价。

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

@ -0,0 +1,5 @@
from django.apps import AppConfig
class CartConfig(AppConfig):
name = 'apps.cart'

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,11 @@
from django.urls import path
from apps.cart.views import CartAddView, CartInfoView, CartUpdateView, CartDeleteView
app_name = 'cart'
urlpatterns = [
path('add', CartAddView.as_view(), name='add'), # 添加购物车记录
path('', CartInfoView.as_view(), name='show'), # 显示购物车页面
path('update', CartUpdateView.as_view(), name='update'), # 更新购物车记录
path('delete', CartDeleteView.as_view(), name='delete'), # 删除购物车记录
]

@ -0,0 +1,161 @@
from django.http import JsonResponse
from django.shortcuts import render
from django.views.generic.base import View
from django_redis import get_redis_connection
from apps.goods.models import GoodsSKU
from utils.mixin import LoginRequiredMixin
# 购物车视图
class CartAddView(View):
"""购物车记录添加"""
def post(self, request):
"""处理购物车记录添加请求"""
user = request.user
if not user.is_authenticated:
return JsonResponse({'res': 0, 'errmsg': '请先登录'})
# 接收数据
sku_id = request.POST.get('sku_id')
count = request.POST.get('count')
# 数据校验
if not all([sku_id, count]):
return JsonResponse({'res': 1, 'errmsg': '数据不完整'})
# 校验添加商品的数量
try:
count = int(count)
except ValueError:
return JsonResponse({'res': 2, 'errmsg': '商品数量出错'})
# 校验商品是否存在
try:
sku = GoodsSKU.objects.get(id=sku_id)
except GoodsSKU.DoesNotExist:
return JsonResponse({'res': 3, 'errmsg': '商品不存在'})
# 业务处理:添加购物车记录
conn = get_redis_connection('default')
cart_key = f'cart_{user.id}'
cart_count = conn.hget(cart_key, sku_id)
if cart_count:
count += int(cart_count)
# 校验商品的库存
if count > sku.stock:
return JsonResponse({'res': 4, 'errmsg': '商品库存不足'})
conn.hset(cart_key, sku_id, count)
# 获取购物车商品条目数
total_count = conn.hlen(cart_key)
# 返回应答
return JsonResponse({'res': 5, 'message': '添加成功', 'total_count': total_count})
class CartInfoView(LoginRequiredMixin, View):
"""显示购物车页面"""
def get(self, request):
"""处理显示购物车页面请求"""
user = request.user
# 获取用户购物车中的商品信息
conn = get_redis_connection('default')
cart_key = f'cart_{user.id}'
cart_dict = conn.hgetall(cart_key)
skus = []
total_count = 0
total_price = 0
# 遍历获取商品的信息
for sku_id, count in cart_dict.items():
sku = GoodsSKU.objects.get(id=sku_id)
count = int(count)
amount = sku.price * count
sku.amount = amount
sku.count = count
skus.append(sku)
total_count += count
total_price += amount
context = {
'total_count': total_count,
'total_price': total_price,
'skus': skus,
}
return render(request, 'cart.html', context)
class CartUpdateView(View):
"""购物车记录更新"""
def post(self, request):
"""处理购物车记录更新请求"""
user = request.user
if not user.is_authenticated:
return JsonResponse({'res': 0, 'errmsg': '请先登录'})
sku_id = request.POST.get('sku_id')
count = request.POST.get('count')
if not all([sku_id, count]):
return JsonResponse({'res': 1, 'errmsg': '数据不完整'})
try:
count = int(count)
except ValueError:
return JsonResponse({'res': 2, 'errmsg': '商品数量出错'})
try:
sku = GoodsSKU.objects.get(id=sku_id)
except GoodsSKU.DoesNotExist:
return JsonResponse({'res': 3, 'errmsg': '商品不存在'})
conn = get_redis_connection('default')
cart_key = f'cart_{user.id}'
if count > sku.stock:
return JsonResponse({'res': 4, 'errmsg': '商品库存不足'})
conn.hset(cart_key, sku_id, count)
total_count = sum(int(val) for val in conn.hvals(cart_key))
return JsonResponse({'res': 5, 'message': '更新成功', 'total_count': total_count})
class CartDeleteView(View):
"""购物车记录删除"""
def post(self, request):
"""处理购物车记录删除请求"""
user = request.user
if not user.is_authenticated:
return JsonResponse({'res': 0, 'errmsg': '请先登录'})
sku_id = request.POST.get('sku_id')
if not sku_id:
return JsonResponse({'res': 1, 'errmsg': '数据不完整'})
try:
sku = GoodsSKU.objects.get(id=sku_id)
except GoodsSKU.DoesNotExist:
return JsonResponse({'res': 2, 'errmsg': '商品不存在'})
conn = get_redis_connection('default')
cart_key = f'cart_{user.id}'
conn.hdel(cart_key, sku_id)
total_count = sum(int(val) for val in conn.hvals(cart_key))
return JsonResponse({'res': 5, 'message': '删除成功', 'total_count': total_count})

@ -0,0 +1,54 @@
from django.contrib import admin
from django.core.cache import cache
from apps.goods.models import GoodsType, IndexPromotionBanner, IndexGoodsBanner, IndexTypeGoodsBanner, GoodsSKU, GoodsSPU, GoodsImage
# Register your models here.
class BaseModelAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
"""新增或更新表中的数据时调用"""
super().save_model(request, obj, form, change)
# 发出任务让celery worker重新生成首页静态页面
from celery_tasks.tasks import generate_static_index_html
generate_static_index_html.delay()
# 清除缓存
cache.delete('index_page_data')
def delete_model(self, request, obj):
"""新增或更新表中的数据时调用"""
super().delete_model(request, obj)
# 发出任务让celery worker重新生成首页静态页面
from celery_tasks.tasks import generate_static_index_html
generate_static_index_html.delay()
# 清除缓存
cache.delete('index_page_data')
class IndexPromotionBannerAdmin(BaseModelAdmin):
pass
class GoodsTypeAdmin(BaseModelAdmin):
pass
class IndexTpyeGoodsBannerAdmin(BaseModelAdmin):
pass
class IndexGoodsBannerAdmin(BaseModelAdmin):
pass
admin.site.register(GoodsType, GoodsTypeAdmin)
admin.site.register(IndexPromotionBanner, IndexPromotionBannerAdmin)
admin.site.register(IndexGoodsBanner, IndexGoodsBannerAdmin)
admin.site.register(IndexTypeGoodsBanner, IndexGoodsBannerAdmin)
admin.site.register(GoodsImage)
admin.site.register(GoodsSPU)
admin.site.register(GoodsSKU)

@ -0,0 +1,5 @@
from django.apps import AppConfig
class GoodsConfig(AppConfig):
name = 'apps.goods'

@ -0,0 +1,150 @@
# Generated by Django 2.2.3 on 2019-07-26 10:54
from django.db import migrations, models
import django.db.models.deletion
import tinymce.models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='GoodsSKU',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
('name', models.CharField(max_length=20, verbose_name='商品名称')),
('desc', models.CharField(max_length=256, verbose_name='商品简介')),
('price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='商品价格')),
('unite', models.CharField(max_length=20, verbose_name='商品单位')),
('image', models.ImageField(upload_to='goods', verbose_name='商品图片')),
('stock', models.IntegerField(default=1, verbose_name='商品库存')),
('sales', models.IntegerField(default=0, verbose_name='商品销量')),
('status', models.SmallIntegerField(choices=[(0, '下线'), (1, '上线')], default=1, verbose_name='商品状态')),
],
options={
'verbose_name': '商品',
'verbose_name_plural': '商品',
'db_table': 'df_goods_sku',
},
),
migrations.CreateModel(
name='GoodsSPU',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
('name', models.CharField(max_length=20, verbose_name='商品SPU名称')),
('detail', tinymce.models.HTMLField(blank=True, verbose_name='商品详情')),
],
options={
'verbose_name': '商品SPU',
'verbose_name_plural': '商品SPU',
'db_table': 'df_goods_spu',
},
),
migrations.CreateModel(
name='GoodsType',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
('name', models.CharField(max_length=20, verbose_name='种类名称')),
('logo', models.CharField(max_length=20, verbose_name='标识')),
('image', models.ImageField(upload_to='type', verbose_name='商品类型图片')),
],
options={
'verbose_name': '商品种类',
'verbose_name_plural': '商品种类',
'db_table': 'df_goods_type',
},
),
migrations.CreateModel(
name='IndexPromotionBanner',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
('url', models.URLField(verbose_name='活动链接')),
('name', models.CharField(max_length=20, verbose_name='活动名称')),
('image', models.ImageField(upload_to='goods', verbose_name='图片路径')),
('index', models.SmallIntegerField(default=0, verbose_name='展示顺序')),
],
options={
'verbose_name': '主页促销活动',
'verbose_name_plural': '主页促销活动',
'db_table': 'df_index_promotion',
},
),
migrations.CreateModel(
name='IndexTypeGoodsBanner',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
('display_type', models.SmallIntegerField(choices=[(0, '标题'), (1, '图片')], default=1, verbose_name='商品显示方式')),
('index', models.SmallIntegerField(default=0, verbose_name='展示顺序')),
('sku', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.GoodsSKU', verbose_name='商品')),
('type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.GoodsType', verbose_name='商品类型')),
],
options={
'verbose_name': '主页分类展示商品',
'verbose_name_plural': '主页分类展示商品',
'db_table': 'df_index_type_goods',
},
),
migrations.CreateModel(
name='IndexGoodsBanner',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
('image', models.ImageField(upload_to='banner', verbose_name='图片')),
('index', models.SmallIntegerField(default=0, verbose_name='展示顺序')),
('sku', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.GoodsSKU', verbose_name='商品')),
],
options={
'verbose_name': '首页轮播图片',
'verbose_name_plural': '首页轮播图片',
'db_table': 'df_index_banner',
},
),
migrations.AddField(
model_name='goodssku',
name='goods_spu',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.GoodsSPU', verbose_name='商品SPU'),
),
migrations.AddField(
model_name='goodssku',
name='type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.GoodsType', verbose_name='商品种类'),
),
migrations.CreateModel(
name='GoodsImage',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
('image', models.ImageField(upload_to='goods', verbose_name='图片路径')),
('sku', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.GoodsSKU', verbose_name='商品')),
],
options={
'verbose_name': '商品图片',
'verbose_name_plural': '商品图片',
'db_table': 'df_goods_image',
},
),
]

@ -0,0 +1,125 @@
from django.db import models
from tinymce.models import HTMLField
from db.base_model import BaseModel
# Create your models here.
class GoodsType(BaseModel):
"""商品类型模型类"""
name = models.CharField(max_length=20, verbose_name='种类名称')
logo = models.CharField(max_length=20, verbose_name='标识')
image = models.ImageField(upload_to='type', verbose_name='商品类型图片')
class Meta:
db_table = 'df_goods_type'
verbose_name = '商品种类'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class GoodsSPU(BaseModel):
"""商品SPU模型类"""
name = models.CharField(max_length=20, verbose_name='商品SPU名称')
detail = HTMLField(blank=True, verbose_name='商品详情')
class Meta:
db_table = 'df_goods_spu'
verbose_name = '商品SPU'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class GoodsSKU(BaseModel):
"""商品SKU模型类"""
STATUS_CHOICES = (
(0, '下线'),
(1, '上线'),
)
type = models.ForeignKey('GoodsType', on_delete=models.CASCADE, verbose_name='商品种类')
goods_spu = models.ForeignKey('GoodsSPU', on_delete=models.CASCADE, verbose_name='商品SPU')
name = models.CharField(max_length=20, verbose_name='商品名称')
desc = models.CharField(max_length=256, verbose_name='商品简介')
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='商品价格')
unite = models.CharField(max_length=20, verbose_name='商品单位')
image = models.ImageField(upload_to='goods', verbose_name='商品图片')
stock = models.IntegerField(default=1, verbose_name='商品库存')
sales = models.IntegerField(default=0, verbose_name='商品销量')
status = models.SmallIntegerField(default=1, choices=STATUS_CHOICES, verbose_name='商品状态')
class Meta:
db_table = 'df_goods_sku'
verbose_name = '商品'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class GoodsImage(BaseModel):
"""商品图片模型类"""
sku = models.ForeignKey('GoodsSKU', on_delete=models.CASCADE, verbose_name='商品')
image = models.ImageField(upload_to='goods', verbose_name='图片路径')
class Meta:
db_table = 'df_goods_image'
verbose_name = '商品图片'
verbose_name_plural = verbose_name
def __str__(self):
return self.sku.name
class IndexGoodsBanner(BaseModel):
"""首页轮播商品展示模型类"""
sku = models.ForeignKey('GoodsSKU', on_delete=models.CASCADE, verbose_name='商品')
image = models.ImageField(upload_to='banner', verbose_name='图片')
index = models.SmallIntegerField(default=0, verbose_name='展示顺序')
class Meta:
db_table = 'df_index_banner'
verbose_name = '首页轮播图片'
verbose_name_plural = verbose_name
def __str__(self):
return self.sku.name
class IndexTypeGoodsBanner(BaseModel):
"""首页分类商品展示模型类"""
DISPLAY_TYPE_CHOICES = (
(0, '标题'),
(1, '图片'),
)
type = models.ForeignKey('GoodsType', on_delete=models.CASCADE, verbose_name='商品类型')
sku = models.ForeignKey('GoodsSKU', on_delete=models.CASCADE, verbose_name='商品')
display_type = models.SmallIntegerField(default=1, choices=DISPLAY_TYPE_CHOICES, verbose_name='商品显示方式')
index = models.SmallIntegerField(default=0, verbose_name='展示顺序')
class Meta:
db_table = 'df_index_type_goods'
verbose_name = '主页分类展示商品'
verbose_name_plural = verbose_name
def __str__(self):
return self.sku.name
class IndexPromotionBanner(BaseModel):
"""首页促销活动模型类"""
url = models.CharField(max_length=256, verbose_name='活动链接')
name = models.CharField(max_length=20, verbose_name='活动名称')
image = models.ImageField(upload_to='goods', verbose_name='图片路径')
index = models.SmallIntegerField(default=0, verbose_name='展示顺序')
class Meta:
db_table = 'df_index_promotion'
verbose_name = '主页促销活动'
verbose_name_plural = verbose_name
def __str__(self):
return self.name

@ -0,0 +1,19 @@
# 定义索引类
from haystack import indexes
# 导入模型类
from apps.goods.models import GoodsSKU
# 指定对于某个类的某些数据建立索引
# 索引类名格式:模型类名+Index
class GoodsSKUIndex(indexes.SearchIndex, indexes.Indexable):
# 索引字段 指定根据表中哪些字段建立索引文件
text = indexes.CharField(document=True, use_template=True)
def get_model(self):
return GoodsSKU
# 建立索引的数据
def index_queryset(self, using=None):
return self.get_model().objects.all()

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,10 @@
from django.urls import path, re_path
from apps.goods.views import IndexView, DetailView, ListView
app_name = 'goods'
urlpatterns = [
path('index/', IndexView.as_view(), name='index'), # Index view
re_path(r'^goods/(?P<goods_id>\d+)$', DetailView.as_view(), name='detail'), # Detail view
re_path(r'^goods/list/(?P<type_id>\d+)/(?P<page>\d+)$', ListView.as_view(), name='list'), # List view
]

@ -0,0 +1,197 @@
from django.core.paginator import Paginator
from django.shortcuts import render, redirect, reverse
from django.views.generic.base import View
from django_redis import get_redis_connection
from django.core.cache import cache
from apps.goods.models import GoodsType, IndexGoodsBanner, IndexTypeGoodsBanner, IndexPromotionBanner, GoodsSKU
from apps.order.models import OrderGoods
class IndexView(View):
"""首页"""
def get(self, request):
"""首页"""
# 获取用户信息
user = request.user
# 尝试获取缓存数据
context = cache.get('index_page_data')
if not context:
# 获取商品种类信息
types = GoodsType.objects.all()
# 获取首页轮播商品信息
goods_banners = IndexGoodsBanner.objects.all().order_by('index')
# 获取首页促销商品信息
promotion_banners = IndexPromotionBanner.objects.all().order_by('index')
# 获取分类商品展示信息
for type in types:
image_goods_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=1)
font_goods_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=0)
type.image_goods_banners = image_goods_banners
type.font_goods_banners = font_goods_banners
# 组织上下文
context = {
'types': types,
'goods_banners': goods_banners,
'promotion_banners': promotion_banners,
}
# 设置缓存
cache.set('index_page_data', context, 3600)
# 获取购物车中商品数量
cart_count = self._get_cart_count(user)
context.update(user=user, cart_count=cart_count)
return render(request, 'index.html', context)
def _get_cart_count(self, user):
"""获取用户购物车商品数量"""
if user.is_authenticated:
conn = get_redis_connection('default')
cart_key = f'cart_{user.id}'
return conn.hlen(cart_key)
return 0
class DetailView(View):
"""详情页面"""
def get(self, request, goods_id):
"""显示详情页面"""
# 获取商品SKU信息
try:
sku = GoodsSKU.objects.get(id=goods_id)
except GoodsSKU.DoesNotExist:
return redirect(reverse('goods:index'))
# 获取商品的分类信息
types = GoodsType.objects.all()
# 获取商品的评论信息
sku_orders = OrderGoods.objects.filter(sku=sku)
# 获取新商品信息
new_skus = GoodsSKU.objects.filter(type=sku.type).order_by('-create_time')[:2]
# 获取同一个SPU的其他商品
same_spu_skus = GoodsSKU.objects.filter(goods_spu=sku.goods_spu).exclude(id=goods_id)
# 获取用户购物车中的商品数目
user = request.user
cart_count = self._get_cart_count(user)
# 添加用户的历史浏览记录
if user.is_authenticated:
self._add_user_history(user, goods_id)
# 组织上下文
context = {
'sku': sku,
'types': types,
'sku_orders': sku_orders,
'new_skus': new_skus,
'same_spu_skus': same_spu_skus,
'cart_count': cart_count,
}
return render(request, 'detail.html', context)
def _get_cart_count(self, user):
"""获取用户购物车商品数量"""
if user.is_authenticated:
conn = get_redis_connection('default')
cart_key = f'cart_{user.id}'
return conn.hlen(cart_key)
return 0
def _add_user_history(self, user, goods_id):
"""添加用户浏览历史记录"""
conn = get_redis_connection('default')
history_key = f'history_{user.id}'
conn.lrem(history_key, 0, goods_id)
conn.lpush(history_key, goods_id)
conn.ltrim(history_key, 0, 4)
class ListView(View):
"""列表页"""
def get(self, request, type_id, page):
"""显示列表页"""
# 获取种类信息
try:
type = GoodsType.objects.get(id=type_id)
except GoodsType.DoesNotExist:
return redirect(reverse('goods:index'))
# 获取排序方式
sort = request.GET.get('sort', 'default')
skus = self._get_skus_by_sort(type, sort)
# 对商品进行分页
paginator = Paginator(skus, 1)
page = self._get_valid_page(paginator, page)
skus_page = paginator.page(page)
pages = self._get_page_range(paginator, page)
# 获取商品的分类信息
types = GoodsType.objects.all()
# 获取新商品信息
new_skus = GoodsSKU.objects.filter(type=type).order_by('-create_time')[:2]
# 获取用户购物车中的商品数目
user = request.user
cart_count = self._get_cart_count(user)
# 组织上下文
context = {
'type': type,
'skus_page': skus_page,
'types': types,
'new_skus': new_skus,
'cart_count': cart_count,
'sort': sort,
'pages': pages,
}
return render(request, 'list.html', context)
def _get_skus_by_sort(self, type, sort):
"""获取排序后的商品SKU"""
if sort == 'price':
return GoodsSKU.objects.filter(type=type).order_by('price')
elif sort == 'hot':
return GoodsSKU.objects.filter(type=type).order_by('-sales')
return GoodsSKU.objects.filter(type=type).order_by('-id')
def _get_valid_page(self, paginator, page):
"""获取有效的分页页码"""
try:
page = int(page)
except ValueError:
page = 1
if page > paginator.num_pages:
page = 1
return page
def _get_page_range(self, paginator, page):
"""获取分页页码范围"""
num_pages = paginator.num_pages
if num_pages < 5:
return range(1, num_pages + 1)
if page <= 3:
return range(1, 6)
if num_pages - page <= 2:
return range(num_pages - 4, num_pages + 1)
return range(page - 2, page + 3)
def _get_cart_count(self, user):
"""获取用户购物车商品数量"""
if user.is_authenticated:
conn = get_redis_connection('default')
cart_key = f'cart_{user.id}'
return conn.hlen(cart_key)
return 0

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjvgjL5EnRL1KrPqLFTZ0D4TyTFbSmmg/UWgkm1QyuqOYHZZ679beAsy6d2oY6WIcp5AqNrFIRiPz6jejRMNE9zhMEXQOG8zi/ttmJlyBlyW77FN5zrG5GPtGeZcr9cPEjc7Mh1srQLPHRYwDyyEHWp3VKe+tYCZMnPvJSch3iVCwY1jP9ApdozBCrLCs83m5p4mgoJLvrV0fp1sJKODDqazm8JixjOTZc5iZwI/ue7AGrFgxzSaU+pzhJiqBCoriq6w3hhzkGryGp4cXWNmjytn9rEK1wXOjaJPbWwtDtKtZiH4ou35pqCjTIWesqod+vEit5BUQuFoSzrM19LUx1QIDAQAB
-----END PUBLIC KEY-----

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAr3dngISSMTXZmWPosXgeJSTz/lSnNT/zXRXeCLSWfTSq6yML
J9uuFxoiJJbHZdS1wFEnbqO8ZLX7Bqhm70/XITfrM8GDTjh8ITYvAnx60F/+7Fmk
f8FTEt9KUW4DQQyf45EiIKW82CTClns5/d6hqt0Lxac/4jP1nhhL4Jde5DLggYdv
sf2IlBOtqnhAcHOGFDq1ZBkp1uW457zjX0GujwbHKtI7YYf43aGtHwUtVBctyEXC
NMJMpioSQQuFo4+SGp211lVIPTpzv++zCmL67R+yrXTGFqbqJpoJnrE93Fuy8Qlg
HgHeTr75jic83+GwqOpN3KLy0Hpg595EoKx/PwIDAQABAoIBAA0rgipESRDGgPGh
bRq88E5LasDhK7e0eBi5hnPS0iTNqjKB69lvBK8ZOAzVAFxlTcsEjFgilAZfHltO
koNN09DbeJzm3msllDON9JNUMoenXOPyioVIRmr5NYPNJRNh1jJnd09KAVWb1Lsk
vqKObkX712Fbf1EEI2BdZHyT//xSsV/xTZ5kXHJIIYzGuXtP5/h6m3s7qkAOOVzG
LcFrCtGzifxZsprh8ztL0+PFvZywikGIg+GkBMaVA1Xz28WT9pggsa8KuY8dBI+K
XRgsbKuVwccA3tIkgkgvdKKCH5b6Vbco8dxY4f1ZMt0Id3ouEVY2cq2vFmrGbFOP
PIfucrkCgYEA43ZFFJDOon2yv2SydGlqXzKIEtJsRet0tpXu9pZFswt32vmIM/lX
RHYFiq5ulFPZiYw3HFr6CwDM44eIn0/VJDh3/qMUptFhlQtg2WurIvNmZ7HqDK3S
sXVr0jsFn1mD4t6PwMLNboCm6lfnQxvJXfHKaV/RW7RuVIcop6S/TFMCgYEAxXsd
2DR+A0AI/JbXbRlVsUvV7B0/UH8Cz+PPJXsjazY0H6s+HQbXPJa0sG8mehCgXsDU
wRz5n6gUy3ryBqzW/e8XTztZlHiRU+CXJNiqyfmzmfHGr+KKZvluFSWYdlQcu9qL
43cqUqYddfwnEo6Q+/Hkdwi5BUq88APjJW6uw+UCgYA74zzG8GVnRN8WI0YU/lhC
XkSTaBGXyyl8lTdId0I8pM1WuxJQVNrULJrC67Azn2wMGf28mntxADHxyhJ/l35P
vgph4cAjN8eQfWFvfTieyCTzMlWkJvPtQzQzMtUFIoVl6yFAKEn8SSUpWCGMerlm
4a1gVxkBIx1VZgyfLvIq/wKBgD+nyN35Rak8iekJolU7dmDZBhK+9rq2xixGzW3S
fH9BkJmotDPdEaIpHgNFQMzV8Su50pqRAXHSVymj7sHyErb1y7ixc9Wk64ty+KVa
5eqG/7qesaHeTyiUPES6wqNZx41SDAd9UPolK5fteJbFt7xOo4svF5y6E572UdCu
Fc11AoGAAitTUwM3rDHjgutUL6/ffE56rgrkh50Up9vyR+gH2WO7AfIYgB3RRS08
TQGnuS/CGCK8Q8tYS75W4wIrfdqVXAQbDf9skhIVUsVqKe9cC15nrmVZGTwToprt
lHvbvCgxjoVti9tBv7mSDet8TqxqB7n+kkzk/cgzp5ht4i6xdMk=
-----END RSA PRIVATE KEY-----

@ -0,0 +1,5 @@
from django.apps import AppConfig
class OrderConfig(AppConfig):
name = 'apps.order'

@ -0,0 +1,51 @@
# Generated by Django 2.2.3 on 2019-07-26 10:54
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='OrderGoods',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
('count', models.IntegerField(default=1, verbose_name='商品数目')),
('price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='商品价格')),
('comment', models.CharField(max_length=256, verbose_name='评论')),
],
options={
'verbose_name': '订单商品',
'verbose_name_plural': '订单商品',
'db_table': 'df_order_goods',
},
),
migrations.CreateModel(
name='OrderInfo',
fields=[
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
('order_id', models.CharField(max_length=20, primary_key=True, serialize=False, verbose_name='订单编号')),
('pay_method', models.SmallIntegerField(choices=[(1, '货到付款'), (2, '微信支付'), (3, '支付宝'), (4, '银联支付')], default=3, verbose_name='支付方式')),
('total_count', models.IntegerField(default=1, verbose_name='商品数量')),
('total_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='商品总价')),
('transit_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='运费')),
('order_status', models.SmallIntegerField(choices=[(1, '待支付'), (2, '待发货'), (3, '待收货'), (4, '待评价'), (5, '已完成')], default=1, verbose_name='订单状态')),
('trade_no', models.CharField(max_length=20, verbose_name='支付编号')),
],
options={
'verbose_name': '订单信息',
'verbose_name_plural': '订单信息',
'db_table': 'df_order_info',
},
),
]

@ -0,0 +1,40 @@
# Generated by Django 2.2.3 on 2019-07-26 10:54
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('goods', '0001_initial'),
('order', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('user', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='orderinfo',
name='address',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user.Address', verbose_name='地址'),
),
migrations.AddField(
model_name='orderinfo',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='用户'),
),
migrations.AddField(
model_name='ordergoods',
name='order',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='order.OrderInfo', verbose_name='订单'),
),
migrations.AddField(
model_name='ordergoods',
name='sku',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='goods.GoodsSKU', verbose_name='商品SKU'),
),
]

@ -0,0 +1,65 @@
from django.db import models
from db.base_model import BaseModel
# Create your models here.
class OrderInfo(BaseModel):
"""订单信息模型类"""
PAY_METHOD = {
'1': '货到付款',
'2': '微信支付',
'3': '支付宝',
'4': '银联支付',
}
PAY_METHOD_CHOICES = (
(1, '货到付款'),
(2, '微信支付'),
(3, '支付宝'),
(4, '银联支付'),
)
ORDER_STATUS = {
'1': '待支付',
'2': '待发货',
'3': '待收货',
'4': '待评价',
'5': '已完成',
}
ORDER_STATUS_CHOICES = (
(1, '待支付'),
(2, '待发货'),
(3, '待收货'),
(4, '待评价'),
(5, '已完成'),
)
order_id = models.CharField(max_length=20, primary_key=True, verbose_name='订单编号')
user = models.ForeignKey('user.User', on_delete=models.CASCADE, verbose_name='用户')
address = models.ForeignKey('user.Address', on_delete=models.CASCADE, verbose_name='地址')
pay_method = models.SmallIntegerField(default=3, choices=PAY_METHOD_CHOICES, verbose_name='支付方式')
total_count = models.IntegerField(default=1, verbose_name='商品数量')
total_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='商品总价')
transit_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='运费')
order_status = models.SmallIntegerField(default=1, choices=ORDER_STATUS_CHOICES, verbose_name='订单状态')
trade_no = models.CharField(max_length=20, default='', verbose_name='支付编号')
class Meta:
db_table = 'df_order_info'
verbose_name = '订单信息'
verbose_name_plural = verbose_name
class OrderGoods(BaseModel):
"""订单商品模型类"""
order = models.ForeignKey('OrderInfo', on_delete=models.CASCADE, verbose_name='订单')
sku = models.ForeignKey('goods.GoodsSKU', on_delete=models.CASCADE, verbose_name='商品SKU')
count = models.IntegerField(default=1, verbose_name='商品数目')
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='商品价格')
comment = models.CharField(max_length=256, default='', verbose_name='评论')
class Meta:
db_table = 'df_order_goods'
verbose_name = '订单商品'
verbose_name_plural = verbose_name

@ -0,0 +1,62 @@
import os
import traceback
import requests
from alipay.aop.api.AlipayClientConfig import AlipayClientConfig
from alipay.aop.api.DefaultAlipayClient import DefaultAlipayClient
from alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModel
from alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequest
from alipay.aop.api.request.AlipayTradeQueryRequest import AlipayTradeQueryRequest
from alipay.aop.api.response.AlipayTradePagePayResponse import AlipayTradePagePayResponse
from alipay.aop.api.response.AlipayTradePayResponse import AlipayTradePayResponse
from django.conf import settings
from django.test import TestCase
import django, json
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dailyfresh.settings')
# django.setup()
# Create your tests here.
transit_price = 10
order_id = '201911'
total_price = 20
"""
设置配置包括支付宝网关地址app_id应用私钥支付宝公钥等其他配置值可以查看AlipayClientConfig的定义
"""
alipay_client_config = AlipayClientConfig()
alipay_client_config.server_url = 'https://openapi.alipaydev.com/gateway.do'
alipay_client_config.app_id = '2016100100641374'
app_private_key = ''
with open(os.path.join(settings.BASE_DIR, 'apps/order/app_private_key.pem'), 'r') as f:
for line in f:
app_private_key += line
alipay_client_config.app_private_key = app_private_key
alipay_public_key = ''
with open(os.path.join(settings.BASE_DIR, 'apps/order/alipay_public_key.pem'), 'r') as f:
for line in f:
alipay_public_key += line
alipay_client_config.alipay_public_key = alipay_public_key
"""
得到客户端对象
注意一个alipay_client_config对象对应一个DefaultAlipayClient定义DefaultAlipayClient对象后alipay_client_config不得修改如果想使用不同的配置请定义不同的DefaultAlipayClient
logger参数用于打印日志不传则不打印建议传递
"""
client = DefaultAlipayClient(alipay_client_config=alipay_client_config)
total_pay = transit_price + total_price
model = AlipayTradePagePayModel()
model.out_trade_no = order_id
model.total_amount = 0.01
model.subject = "天天生鲜{0}".format(order_id)
model.product_code = "FAST_INSTANT_TRADE_PAY"
request = AlipayTradePagePayRequest(biz_model=model)
response = client.page_execute(request, http_method="GET")
print(response)
# request = AlipayTradeQueryRequest(biz_model=model)
# response = client.page_execute(request, http_method="GET")
# data = requests.get(response)
# print(json.loads(data.text))
# data = json.loads(data.text)
# print(data.get('alipay_trade_query_response').get('code'))

@ -0,0 +1,12 @@
from django.urls import path, re_path
from apps.order.views import OrderPlaceView, OrderCommitView, OrderPayView, CheckPayView, CommentView
app_name = 'order'
urlpatterns = [
path('place', OrderPlaceView.as_view(), name='place'), # 提交订单页面显示
path('commit', OrderCommitView.as_view(), name='commit'), # 订单创建
path('pay', OrderPayView.as_view(), name='pay'), # 订单支付
path('check', CheckPayView.as_view(), name='check'), # 查看订单支付状态
re_path(r'^comment/(?P<order_id>.+)$', CommentView.as_view(), name='comment'), # 订单评论
]

@ -0,0 +1,552 @@
import json
from datetime import datetime
import requests
from alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModel
from alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequest
from alipay.aop.api.request.AlipayTradeQueryRequest import AlipayTradeQueryRequest
from django.db import transaction
from django.http import JsonResponse
from django.shortcuts import render, redirect
from django.urls import reverse
from django.views.generic.base import View, logger
from django_redis import get_redis_connection
from apps.goods.models import GoodsSKU
from apps.order.models import OrderInfo, OrderGoods
from apps.user.models import Address
from utils.mixin import LoginRequiredMixin
from alipay.aop.api.AlipayClientConfig import AlipayClientConfig
from alipay.aop.api.DefaultAlipayClient import DefaultAlipayClient
from django.conf import settings
import os
# /order/place
class OrderPlaceView(LoginRequiredMixin, View):
"""提交订单页面显示"""
def post(self, request):
"""提交订单页面显示"""
# 获取登录的用户
user = request.user
# 获取参数sku_ids
sku_ids = request.POST.getlist('sku_ids')
# 校验数据
if not sku_ids:
# 跳转到购物车页面
return redirect(reverse('cart:show'))
# 遍历sku_ids获取用户要购买的商品信息
conn = get_redis_connection('default')
cart_key = 'cart_{0}'.format(user.id)
total_count = 0
total_price = 0
skus = list()
for sku_id in sku_ids:
sku = GoodsSKU.objects.get(id=sku_id)
count = conn.hget(cart_key, sku_id)
count = count.decode()
# 计算商品小计
amount = int(count) * sku.price
# 动态给sku增加amountcount属性
sku.count = count
sku.amount = amount
skus.append(sku)
total_count += int(count)
total_price += amount
# 运费: 实际开发需要单独设计,这里写死
transit_price = 10
# 实付款
total_pay = total_price + transit_price
# 获取用户的收件地址
addrs = Address.objects.filter(user=user)
# 组织上下文
sku_ids = ','.join(sku_ids)
context = {
'skus': skus,
'total_count': total_count,
'total_price': total_price,
'transit_price': transit_price,
'total_pay': total_pay,
'addrs': addrs,
'sku_ids': sku_ids,
}
# 使用模板
return render(request, 'place_order.html', context)
# ajax post
# 地址idaddr_id,支付方式: pay_method,商品id字符串 sku_ids
# /order/commit
# 悲观锁
class OrderCommitView(View):
"""订单创建"""
@transaction.atomic
def post(self, request):
"""订单创建"""
# 判断用户登录
user = request.user
if not user.is_authenticated:
return JsonResponse({'res': 0, 'errmsg': '用户未登陆'})
# 接收参数
addr_id = request.POST.get('addr_id')
pay_method = request.POST.get('pay_method')
sku_ids = request.POST.get('sku_ids')
# 校验参数
if not all([addr_id, pay_method, sku_ids]):
return JsonResponse({'res': 1, 'errmsg': '参数不完整'})
# 校验支付方式
if pay_method not in OrderInfo.PAY_METHOD.keys():
print(pay_method,type(pay_method))
return JsonResponse({'res': 2, 'errmsg': '无效支付方式'})
# 校验地址
try :
addr = Address.objects.get(id=addr_id)
except Exception as e:
return JsonResponse({'res': 3, 'errmsg': '无效地址'})
# todo: 创建订单核心业务
# 组织参数
# 订单id20190805181630+用户id
order_id = datetime.now().strftime('%Y%m%d%H%M%S') + str(user.id)
# 运费
transit_price = 10
# 总数目和总金额
total_count = 0
total_price = 0
# 设置保存点
save_id = transaction.savepoint()
try:
# todo 向df_order_info表中添加一条记录
order = OrderInfo.objects.create(order_id=order_id,
user=user,
address=addr,
pay_method=pay_method,
total_count=total_count,
total_price=total_price,
transit_price=transit_price)
# todo: 向df_order_goods表中添加记录
conn = get_redis_connection('default')
cart_key = 'cart_{0}'.format(user.id)
sku_ids = sku_ids.split(',')
for sku_id in sku_ids:
# 获取商品信息
try:
# 加悲观锁
# select * from df_goods_sku where id=sku_id for update;
sku = GoodsSKU.objects.select_for_update().get(id=sku_id)
except Exception as e:
transaction.savepoint_rollback(save_id)
return JsonResponse({'res': 4, 'errmsg': '商品不存在'})
# 从redis中获取商品的数量
count = conn.hget(cart_key, sku_id)
# todo: 判断某一个商品的库存
if int(count) > sku.stock:
transaction.savepoint_rollback(save_id)
return JsonResponse({'res': 6, 'errmsg': '商品库存不足'})
# todo: 向df_order_goods表中添加一条记录
OrderGoods.objects.create(order=order,
sku=sku,
count=count,
price=sku.price)
# todo: 更新商品的库存和销量
sku.stock -= int(count)
sku.sales += int(count)
sku.save()
# todo: 累加计算订单商品的总数量和总价格
amount = sku.price * int(count)
total_count += int(count)
total_price += amount
# todo: 更新订单信息表中的商品的总数量和总价格
order.total_price = total_price
order.total_count = total_count
order.save()
except Exception as e:
transaction.savepoint_rollback(save_id)
return JsonResponse({'res': 7, 'errmsg': '下单失败'})
# 提交事务
transaction.savepoint_commit(save_id)
# todo: 清楚用户车中对应的记录
conn.hdel(cart_key, *sku_ids)
# 返回应答
return JsonResponse({'res': 5, 'message': '创建成功'})
# 乐观锁
class OrderCommitView1(View):
"""订单创建"""
@transaction.atomic
def post(self, request):
"""订单创建"""
# 判断用户登录
user = request.user
if not user.is_authenticated:
return JsonResponse({'res': 0, 'errmsg': '用户未登陆'})
# 接收参数
addr_id = request.POST.get('addr_id')
pay_method = request.POST.get('pay_method')
sku_ids = request.POST.get('sku_ids')
# 校验参数
if not all([addr_id, pay_method, sku_ids]):
return JsonResponse({'res': 1, 'errmsg': '参数不完整'})
# 校验支付方式
if pay_method not in OrderInfo.PAY_METHOD.keys():
return JsonResponse({'res': 2, 'errmsg': '无效支付方式'})
# 校验地址
try :
addr = Address.objects.get(id=addr_id)
except Exception as e:
return JsonResponse({'res': 3, 'errmsg': '无效地址'})
# todo: 创建订单核心业务
# 组织参数
# 订单id20190805181630+用户id
order_id = datetime.now().strftime('%Y%m%d%H%M%S') + str(user.id)
# 运费
transit_price = 10
# 总数目和总金额
total_count = 0
total_price = 0
# 设置保存点
save_id = transaction.savepoint()
try:
# todo 向df_order_info表中添加一条记录
order = OrderInfo.objects.create(order_id=order_id,
user=user,
address=addr,
pay_method=pay_method,
total_count=total_count,
total_price=total_price,
transit_price=transit_price)
# todo: 向df_order_goods表中添加记录
conn = get_redis_connection('default')
cart_key = 'cart_{0}'.format(user.id)
sku_ids = sku_ids.split(',')
for sku_id in sku_ids:
# 使用乐观锁需多重复几次需要数据库的隔离级别为提交读Read committed。
for i in range(3):
# 获取商品信息
try:
sku = GoodsSKU.objects.get(id=sku_id)
except Exception as e:
transaction.savepoint_rollback(save_id)
return JsonResponse({'res': 4, 'errmsg': '商品不存在'})
# 从redis中获取商品的数量
count = conn.hget(cart_key, sku_id)
# todo: 判断某一个商品的库存
if int(count) > sku.stock:
transaction.savepoint_rollback(save_id)
return JsonResponse({'res': 6, 'errmsg': '商品库存不足'})
# todo: 更新商品的库存和销量
orgin_stock = sku.stock
orgin_sales = sku.sales
new_stock = orgin_stock - int(count)
new_sales = orgin_sales + int(count)
# 加乐观锁
# update df_goods_sku set stock=new_stock, sales=new_sales
# where id=sku_id and stock=orgin_stock;
res = GoodsSKU.objects.filter(id=sku_id, stock=orgin_stock).update(stock=new_stock, sales=new_sales)
if res == 0:
if i == 2:
transaction.savepoint_rollback(save_id)
return JsonResponse({'res': 7, 'errmsg': '下单失败2'})
continue
# todo: 向df_order_goods表中添加一条记录
OrderGoods.objects.create(order=order,
sku=sku,
count=count,
price=sku.price)
# todo: 累加计算订单商品的总数量和总价格
amount = sku.price * int(count)
total_count += int(count)
total_price += amount
# 如果成功了,跳出循环
break
# todo: 更新订单信息表中的商品的总数量和总价格
order.total_price = total_price
order.total_count = total_count
order.save()
except Exception as e:
transaction.savepoint_rollback(save_id)
return JsonResponse({'res': 7, 'errmsg': '下单失败'})
# 提交事务
transaction.savepoint_commit(save_id)
# todo: 清楚用户车中对应的记录
conn.hdel(cart_key, *sku_ids)
# 返回应答
return JsonResponse({'res': 5, 'message': '创建成功'})
# ajax post
# 订单idorder_id
# /order/pay
class OrderPayView(View):
'''订单支付'''
def post(self, request):
'''订单支付'''
# 判断用户登录
user = request.user
if not user.is_authenticated:
return JsonResponse({'res': 0, 'errmsg': '用户未登陆'})
# 接收参数
order_id = request.POST.get('order_id')
# 校验参数
if not order_id:
return JsonResponse({'res': 1, 'errmsg': '参数不完整'})
try:
order = OrderInfo.objects.get(order_id=order_id,
user=user,
pay_method=3,
order_status=1)
except OrderInfo.DoesNotExist:
return JsonResponse({'res': 2, 'errmsg': '无效的订单id'})
"""
设置配置包括支付宝网关地址app_id应用私钥支付宝公钥等其他配置值可以查看AlipayClientConfig的定义
"""
alipay_client_config = AlipayClientConfig()
alipay_client_config.server_url = 'https://openapi.alipaydev.com/gateway.do'
alipay_client_config.app_id = '2016100100641374'
app_private_key = ''
with open(os.path.join(settings.BASE_DIR, 'apps/order/app_private_key.pem'), 'r') as f:
for line in f:
app_private_key += line
alipay_client_config.app_private_key = app_private_key
alipay_public_key = ''
with open(os.path.join(settings.BASE_DIR, 'apps/order/alipay_public_key.pem'), 'r') as f:
for line in f:
alipay_public_key += line
alipay_client_config.alipay_public_key = alipay_public_key
"""
得到客户端对象
注意一个alipay_client_config对象对应一个DefaultAlipayClient定义DefaultAlipayClient对象后alipay_client_config不得修改如果想使用不同的配置请定义不同的DefaultAlipayClient
logger参数用于打印日志不传则不打印建议传递
"""
client = DefaultAlipayClient(alipay_client_config=alipay_client_config, logger=logger)
total_pay = order.transit_price + order.total_price
total_pay = round(float(total_pay), 2)
model = AlipayTradePagePayModel()
model.out_trade_no = order_id
model.total_amount = total_pay
model.subject = "天天生鲜{0}".format(order_id)
model.product_code = "FAST_INSTANT_TRADE_PAY"
request = AlipayTradePagePayRequest(biz_model=model)
response = client.page_execute(request, http_method="GET")
# 访问支付页面
return JsonResponse({'res': 3, 'response': response})
# ajax post
# 订单id: order_id
# /order/check
class CheckPayView(View):
"""查看订单支付状态"""
def post(self, request):
"""查看订单支付状态"""
# 判断用户登录
user = request.user
if not user.is_authenticated:
return JsonResponse({'res': 0, 'errmsg': '用户未登陆'})
# 接收参数
order_id = request.POST.get('order_id')
# 校验参数
if not order_id:
return JsonResponse({'res': 1, 'errmsg': '参数不完整'})
try:
order = OrderInfo.objects.get(order_id=order_id,
user=user,
pay_method=3,
order_status=1)
except OrderInfo.DoesNotExist:
return JsonResponse({'res': 2, 'errmsg': '无效的订单id'})
"""
设置配置包括支付宝网关地址app_id应用私钥支付宝公钥等其他配置值可以查看AlipayClientConfig的定义
"""
alipay_client_config = AlipayClientConfig()
alipay_client_config.server_url = 'https://openapi.alipaydev.com/gateway.do'
alipay_client_config.app_id = '2016100100641374'
app_private_key = ''
with open(os.path.join(settings.BASE_DIR, 'apps/order/app_private_key.pem'), 'r') as f:
for line in f:
app_private_key += line
alipay_client_config.app_private_key = app_private_key
alipay_public_key = ''
with open(os.path.join(settings.BASE_DIR, 'apps/order/alipay_public_key.pem'), 'r') as f:
for line in f:
alipay_public_key += line
alipay_client_config.alipay_public_key = alipay_public_key
"""
得到客户端对象
注意一个alipay_client_config对象对应一个DefaultAlipayClient定义DefaultAlipayClient对象后alipay_client_config不得修改如果想使用不同的配置请定义不同的DefaultAlipayClient
logger参数用于打印日志不传则不打印建议传递
"""
client = DefaultAlipayClient(alipay_client_config=alipay_client_config, logger=logger)
total_pay = order.transit_price + order.total_price
total_pay = round(float(total_pay), 2)
model = AlipayTradePagePayModel()
model.out_trade_no = order_id
model.total_amount = total_pay
model.subject = "天天生鲜{0}".format(order_id)
model.product_code = "FAST_INSTANT_TRADE_PAY"
while True:
request = AlipayTradeQueryRequest(biz_model=model)
response = client.page_execute(request, http_method="GET")
data = requests.get(response)
data = json.loads(data.text)
code = data.get('alipay_trade_query_response').get('code')
trade_status = data.get('alipay_trade_query_response').get('trade_status')
print(code, trade_status)
if code == '10000' and trade_status == 'TRADE_SUCCESS':
# 支付成功
# 获取支付宝交易号
trade_no = data.get('alipay_trade_query_response').get('code')
# 更新订单状态
order.trade_no = trade_no
order.order_status = 4
order.save()
# 返回结果
return JsonResponse({'res': 3, 'message': '支付成功'})
elif code=='40004' or (code == '10000' and trade_status == 'WAIT_BUYER_PAY'):
# 等待卖家付款
import time
time.sleep(5)
continue
else:
# 支付出错
return JsonResponse({'res': 4, 'errmsg': '支付失败'})
class CommentView(LoginRequiredMixin, View):
"""订单评论"""
def get(self, request, order_id):
"""提供订单评论页面"""
user = request.user
# 校验参数
if not order_id:
return redirect(reverse('user:order'))
try:
order = OrderInfo.objects.get(order_id=order_id,
user=user,
)
except OrderInfo.DoesNotExist:
return redirect(reverse('user:order'))
order.status_name = OrderInfo.ORDER_STATUS[str(order.order_status)]
# 获取订单的商品信息
order_skus = OrderGoods.objects.filter(order=order)
for order_sku in order_skus:
# 计算商品小计
amount = order_sku.price * order_sku.count
order_sku.amount = amount
order.order_skus = order_skus
return render(request, 'order_comment.html', {'order': order})
def post(self, request, order_id):
"""处理评论内容"""
user = request.user
# 校验参数
if not order_id:
return redirect(reverse('user:order'))
try:
order = OrderInfo.objects.get(order_id=order_id,
user=user,
)
except OrderInfo.DoesNotExist:
return redirect(reverse('user:order'))
# 获取评论条数
total_count = request.POST.get('total_count')
total_count = int(total_count)
for i in range(1, total_count+1):
# 获取评论的商品的id
sku_id = request.POST.get('sku_{0}'.format(i))
# 获取评论内容
content = request.POST.get('content_{0}'.format(i))
try:
order_goods = OrderGoods.objects.get(order=order, sku_id=sku_id)
except OrderGoods.DoesNotExist:
continue
order_goods.comment = content
order_goods.save()
order.order_status = 5
order.save()
return redirect(reverse('user:order', kwargs={'page': 1}))

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

@ -0,0 +1,5 @@
from django.apps import AppConfig
class UserConfig(AppConfig):
name = 'apps.user'

@ -0,0 +1,69 @@
# Generated by Django 2.2.3 on 2019-07-26 10:54
from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0011_update_proxy_permissions'),
]
operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
options={
'verbose_name': '用户',
'verbose_name_plural': '用户',
'db_table': 'df_user',
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='Address',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('is_delete', models.BooleanField(default=False, verbose_name='删除标记')),
('receiver', models.CharField(max_length=20, verbose_name='收件人')),
('addr', models.CharField(max_length=256, verbose_name='收货地址')),
('zip_code', models.CharField(max_length=6, null=True, verbose_name='邮政编码')),
('phone', models.CharField(max_length=11, verbose_name='联系电话')),
('is_default', models.BooleanField(default=False, verbose_name='是否默认')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='所属用户')),
],
options={
'verbose_name': '地址',
'verbose_name_plural': '地址',
'db_table': 'df_address',
},
),
]

@ -0,0 +1,49 @@
from django.conf import settings
from django.contrib.auth.models import AbstractUser
from itsdangerous import URLSafeTimedSerializer as Serializer
from db.base_model import BaseModel
from django.db import models
# AddressManager Class
class AddressManager(models.Manager):
"""地址模型管理类"""
def get_default_address(self, user):
"""获取用户的默认地址"""
try:
address = self.get(user=user, is_default=True)
except self.model.DoesNotExist:
address = None
return address
# User Model
class User(AbstractUser, BaseModel):
"""用户模型类"""
def generate_active_token(self):
"""生成用户签名字符串"""
serializer = Serializer(settings.SECRET_KEY, 3600)
info = {'confirm': self.id}
token = serializer.dumps(info)
return token.decode()
class Meta:
db_table = 'df_user'
verbose_name = '用户'
verbose_name_plural = verbose_name
# Address Model
class Address(BaseModel):
"""地址模型类"""
user = models.ForeignKey('User', on_delete=models.CASCADE, verbose_name='所属用户')
receiver = models.CharField(max_length=20, verbose_name='收件人')
addr = models.CharField(max_length=256, verbose_name='收货地址')
zip_code = models.CharField(max_length=6, null=True, verbose_name='邮政编码')
phone = models.CharField(max_length=11, verbose_name='联系电话')
is_default = models.BooleanField(default=False, verbose_name='是否默认')
# 自定义一个模型管理器对象
objects = AddressManager()
class Meta:
db_table = 'df_address'
verbose_name = '地址'
verbose_name_plural = verbose_name

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,19 @@
from django.urls import path, re_path
from apps.user.views import (
RegisterView, ActiveView, LoginView, UserInfoView,
UserOrderView, UserSiteView, LogoutView
)
from apps.user import views
app_name = 'user'
urlpatterns = [
path('index/', views.index, name='index'),
path('register/', RegisterView.as_view(), name='register'), # Register
re_path(r'active/(?P<token>.*)/$', ActiveView.as_view(), name='active'), # User Activation
path('login/', LoginView.as_view(), name='login'), # Login
path('logout/', LogoutView.as_view(), name='logout'), # Logout
path('', UserInfoView.as_view(), name='user'), # User Info
re_path(r'^order/(?P<page>\d+)/$', UserOrderView.as_view(), name='order'), # Orders
path('address/', UserSiteView.as_view(), name='address'), # Address
]

@ -0,0 +1,215 @@
import re
from django.contrib.auth import authenticate, login, logout
from django.core.paginator import Paginator
from django.shortcuts import render, redirect, reverse, HttpResponse
from django.views.generic import View
from itsdangerous import URLSafeTimedSerializer as Serializer, SignatureExpired
from django.conf import settings
from django.core.mail import send_mail
from celery_tasks.tasks import send_register_active_email
from utils.mixin import LoginRequiredMixin
from django_redis import get_redis_connection
from apps.goods.models import GoodsSKU
from apps.order.models import OrderInfo, OrderGoods
from apps.user.models import User, Address, AddressManager
# Index View
def index(request):
return render(request, 'index.html')
# Function-based Register View (Commented out)
# def register(request):
# """显示注册页面"""
# if request.method == 'GET':
# return render(request, 'register.html')
# elif request.method == 'POST':
# # Handle registration logic
# pass
# Class-based Register View
class RegisterView(View):
"""注册"""
def get(self, request):
return render(request, 'register.html')
def post(self, request):
# 接受数据
username = request.POST.get('user_name')
password = request.POST.get('pwd')
email = request.POST.get('email')
allow = request.POST.get('allow')
# 数据处理
if not all([username, password, email]):
return render(request, 'register.html', {'errmsg': '数据不完整'})
if not re.match(r'^[a-z0-9][\w.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
return render(request, 'register.html', {'errmsg': '邮箱格式不正确'})
if allow != 'on':
return render(request, 'register.html', {'errmsg': '请同意协议'})
# 校验用户名是否重复
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
user = None
if user:
return render(request, 'register.html', {'errmsg': '用户名已存在'})
# 进行用户注册
user = User.objects.create_user(username, email, password)
user.is_active = 0
user.is_superuser = False
user.save()
# 发送激活邮件,包含激活链接
serializer = Serializer(settings.SECRET_KEY, 3600)
info = {'confirm': user.id}
token = serializer.dumps(info).decode()
# 为处理者分配任务
send_register_active_email.delay(email, username, token)
return redirect(reverse('goods:index'))
# User Activation View
class ActiveView(View):
"""用户激活"""
def get(self, request, token):
try:
serializer = Serializer(settings.SECRET_KEY, 3600)
token = token.encode()
info = serializer.loads(token)
user_id = info['confirm']
user = User.objects.get(id=user_id)
user.is_active = 1
user.save()
return redirect(reverse('user:login'))
except SignatureExpired:
return HttpResponse('激活链接已过期')
# Login View
class LoginView(View):
"""登录"""
def get(self, request):
if 'username' in request.COOKIES:
username = request.COOKIES.get('username')
checked = 'checked'
else:
username = ''
checked = ''
context = {'username': username, 'checked': checked}
return render(request, 'login.html', context)
def post(self, request):
username = request.POST.get('username')
password = request.POST.get('pwd')
if not all([username, password]):
return render(request, 'login.html', {'errmsg': '数据不完整'})
user = authenticate(username=username, password=password)
if user:
if user.is_active:
login(request, user)
next_url = request.GET.get('next', reverse('goods:index'))
response = redirect(next_url)
remember = request.POST.get('remember')
if remember == 'on':
response.set_cookie('username', username, max_age=7 * 24 * 3600)
else:
response.delete_cookie('username')
return response
else:
return render(request, 'login.html', {'errmsg': '账户未激活'})
else:
return render(request, 'login.html', {'errmsg': '用户名或密码错误'})
# Logout View
class LogoutView(View):
"""退出登录"""
def get(self, request):
logout(request)
return redirect(reverse('goods:index'))
# User Info View
class UserInfoView(LoginRequiredMixin, View):
"""用户中心——信息页"""
def get(self, request):
user = request.user
address = Address.objects.get_default_address(user)
con = get_redis_connection('default')
history_key = f'history_{user.id}'
sku_ids = con.lrange(history_key, 0, 4)
goods_list = [GoodsSKU.objects.get(id=i) for i in sku_ids]
context = {'page': 'user', 'user': user, 'address': address, 'goods_list': goods_list}
return render(request, 'user_center_info.html', context)
# User Order View
class UserOrderView(LoginRequiredMixin, View):
"""用户中心——订单页"""
def get(self, request, page):
user = request.user
orders = OrderInfo.objects.filter(user=user)
for order in orders:
order_skus = OrderGoods.objects.filter(order_id=order.order_id)
for order_sku in order_skus:
order_sku.amount = order_sku.price * order_sku.count
order.order_skus = order_skus
order.status_name = OrderInfo.ORDER_STATUS[str(order.order_status)]
paginator = Paginator(orders, 1)
try:
page = int(page)
except Exception:
page = 1
order_page = paginator.page(page)
num_pages = paginator.num_pages
if num_pages < 5:
pages = range(1, num_pages + 1)
elif page <= 3:
pages = range(1, 6)
elif num_pages - page <= 2:
pages = range(num_pages - 4, num_pages + 1)
else:
pages = range(page - 2, page + 3)
context = {'order_page': order_page, 'pages': pages, 'page': 'order'}
return render(request, 'user_center_order.html', context)
# User Address View
class UserSiteView(LoginRequiredMixin, View):
"""用户中心——地址页"""
def get(self, request):
user = request.user
address = Address.objects.get_default_address(user)
return render(request, 'user_center_site.html', {'address': address, 'user': user})
def post(self, request):
receiver = request.POST.get('receiver')
addr = request.POST.get('addr')
zip_code = request.POST.get('zip_code')
phone = request.POST.get('phone')
if not all([receiver, addr, phone]):
return render(request, 'user_center_site.html', {'errmsg': '数据不完整'})
if not re.match(r'^1[3|4|5|7|8][0-9]{9}$', phone):
return render(request, 'user_center_site.html', {'errmsg': '手机格式错误'})
user = request.user
address = Address.objects.get_default_address(user)
is_default = not bool(address)
Address.objects.create(user=user, receiver=receiver, addr=addr, zip_code=zip_code, phone=phone,
is_default=is_default)
return redirect(reverse('user:address'))

@ -0,0 +1,73 @@
# 使用celery
# # 在任务一段加这几句代码
# import os
# import django
# os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dailyfresh.settings')
# django.setup()
import os
import time
from django.template import loader
from dailyfresh import settings
from celery import Celery
from django_redis import get_redis_connection
from apps.goods.models import GoodsType, IndexGoodsBanner, IndexTypeGoodsBanner, IndexPromotionBanner
# 创建一个Celery类的实例对象
from django.core.mail import send_mail
app = Celery('celery_tasks.tasks', broker='redis://192.168.209.130:6379/8')
# 定义任务函数
@app.task
def send_register_active_email(to_email, username, token):
"""发送激活邮件"""
# 发送邮件
subject = '天天生鲜欢迎信息'
message = ''
sender = settings.EMAIL_FROM
receiver = [to_email]
html_message = '<h1>{0}, 欢迎您成为天天生鲜注册会员</h1>请点击下面链接激活您的账户<br/><a href="http://127.0.0.1:8000/user/active/{1}">http://127.0.0.1/user/active/{2}</a>'.format(
username, token, token)
send_mail(subject, message, sender, receiver, html_message=html_message)
time.sleep(5)
@app.task
def generate_static_index_html():
"""产生首页静态页面"""
# 获取商品种类信息
types = GoodsType.objects.all()
# 获取首页轮播商品信息
goods_banners = IndexGoodsBanner.objects.all().order_by('index')
# 获取首页促销商品信息
promotion_banners = IndexPromotionBanner.objects.all().order_by('index')
# 获取分类商品展示信息
for type in types:
image_goods_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=1)
font_goods_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=0)
type.image_goods_banners = image_goods_banners
type.font_goods_banners = font_goods_banners
# 组织上下文
context = {
'types': types,
'goods_banners': goods_banners,
'promotion_goods': promotion_banners,
}
# s使用模板
# 1. 加载模板文件,返回模板对象
temp = loader.get_template('static_index.html')
# 2. 渲染模板
static_index_html = temp.render(context)
# 生成对应静态文件
save_path = os.path.join(settings.BASE_DIR, 'static/index.html')
with open(save_path, 'w') as f:
f.write(static_index_html)

@ -0,0 +1,202 @@
"""
Django settings for dailyfresh project.
Generated by 'django-admin startproject' using Django 2.2.3.
For more information on this file, see
https://docs.djangoproject.com/en/2.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.2/ref/settings/
"""
import os
import sys
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'qi1pgrrshxdstv3v71b8ax%@2p)zh#&_hy5mo633xh3$+g-bs('
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'tinymce', # 富文本编辑器
'haystack', # 全文检索框架
# sys.path.insert(0, os.path.join(BASE_DIR, 'apps')
'apps.goods', # 商品模块
'apps.cart', # 购物车模块
'apps.order', # 订单模块
'apps.user', # 用户模块
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'dailyfresh.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'dailyfresh.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'dailyfresh',
'USER': 'root',
'PASSWORD': '1234567',
'HOST': '127.0.0.1',
'POST': '3306',
}
}
# django认证系统使用的模型类
AUTH_USER_MODEL='user.User'
# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
# 富文本编辑器配置
TINYMCE_DEFAULT_CONFIG = {
'theme': 'advanced',
'width': 600,
'height': 400,
}
# 发送邮件配置
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# EMAIL_USE_TLS = False
# smpt服务地址
EMAIL_HOST = 'smtp.163.com'
EMAIL_PORT = 25
# 邮箱
EMAIL_HOST_USER = 'a1691795341@163.com'
# 授权密码
EMAIL_HOST_PASSWORD = 'w809326582'
# DEFAULT_FROM_EMAIL = 'w1691795341@163.com'
EMAIL_FROM = '天天生鲜<a1691795341@163.com>'
# Django的缓存配置
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://192.168.209.130:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
# 配置SESSION的存储
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
# 配置登录url地址
LOGIN_URL = '/user/login'
# 设置django的默认文件存储类
DEFAULT_FILE_STORAGE = 'utils.fdfs.storage.FDFSStorage'
# 设置fdfs使用的client.conf的文件路径
FDFS_CLIENT_CONF = '/etc/fdfs/client.conf'
# 设置fdfs存储服务器上Nginx的ip和port
FDFS_URL = 'http://192.168.209.130:8888/'
# 全文检索框架配置
HAYSTACK_CONNECTIONS = {
'default': {
# 使用whoosh
'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
# 索引文件路径
'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
}
}
# 当添加、修改、删除数据时,自动生成索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
# 指定搜索每页显示条数 默认20
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 1

@ -0,0 +1,37 @@
"""dailyfresh URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.urls import path
from django.contrib import admin
from django.urls import path, include
from apps.user import views
from apps.user.views import RegisterView, ActiveView, LoginView, UserInfoView, UserOrderView, UserSiteView, LogoutView
urlpatterns = [
path('', views.index, name='index'), # 根路径指向index视图
# path('index/', views.index, name='index'),
path('admin/', admin.site.urls),
# path('login/', views.login_view, name='login'),
# path('register/', views.register_view, name='register'),
path('login/', LoginView.as_view(), name='login'), # 登录
path('logout/', LogoutView.as_view(), name='logout'), # 退出登录
path('register/', RegisterView.as_view(), name='register'), # 注册
path('tinymce/', include('tinymce.urls')), # 富文本编辑器
path('search/', include('haystack.urls')), # 全文检索
path('user/', include(('apps.user.urls', 'user'), namespace='user')), # 用户模块
path('cart/', include(('apps.cart.urls', 'cart'), namespace='cart')), # 购物车模块
path('order/', include(('apps.order.urls', 'order'), namespace='order')), # 订单模块
path('goods/', include(('apps.goods.urls', 'goods'), namespace='goods')), # 商品模块
]

@ -0,0 +1,16 @@
"""
WSGI config for dailyfresh project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dailyfresh.settings')
application = get_wsgi_application()

@ -0,0 +1,11 @@
from django.db import models
class BaseModel(models.Model):
'''模型抽象基类'''
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
is_delete = models.BooleanField(default=False, verbose_name='删除标记')
class Meta:
# 说明是一个抽象基类
abstract = True

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

@ -0,0 +1,21 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dailyfresh.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

@ -0,0 +1,51 @@
https://blog.csdn.net/qq_31463999/article/details/82768466#commentBox
启动Tracker
可以用这种方式启动
# /etc/init.d/fdfs_trackerd start
也可以用这种方式启动,前提是上面创建了软链接,后面都用这种方式
# service fdfs_trackerd start
启动 Storage
启动Storage前确保Tracker是启动的。初次启动成功会在 /ljzsg/fastdfs/storage 目录下创建 data、 logs 两个目录。
可以用这种方式启动
# /etc/init.d/fdfs_storaged start
也可以用这种方式,后面都用这种
# service fdfs_storaged start
上传文件
在linux内部执行如下命令上传 namei.jpeg 图片
# /usr/bin/fdfs_upload_file /etc/fdfs/client.conf namei.jpeg
or
fdfs_upload_file /etc/fdfs/client.conf ~/Desktop/nav.png
启动和停止nginx
wang@ubuntu:/usr/local/nginx/sbin$ sudo ./nginx
ngx_http_fastdfs_set pid=22396
wang@ubuntu:/usr/local/nginx/sbin$ sudo ./nginx -s stop
错误1
wang@ubuntu:~$ sudo /usr/local/nginx/sbin/nginx
ngx_http_fastdfs_set pid=4472
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] still could not bind()
wang@ubuntu:~$ lsof -i:80
wang@ubuntu:~$ sudo fuser -k 80/tcp

@ -0,0 +1,21 @@
如果 安装有其他fdfs包, 要先卸载完再安装py3Fdfs
pip install py3Fdfs
 测试
from fdfs_client.client import *
client_conf_obj = get_tracker_conf('/etc/fdfs/client.conf')
client = Fdfs_client(client_conf_obj)
ret = client.upload_by_filename('test')
ret
{'Group name': b'group1', 'Remote file_id': b'group1/M00/00/00/wKjRgl09sfWALPKYAAAAAAAAAAA765.txt', 'Status': 'Upload successed.', 'Local file name': 'test.txt', 'Uploaded size': '0B', 'Storage IP': b'192.168.209.130'}
关于storege.conf配置文件
tracker_server = 填写所在服务器的外网ip
安全组开放 配置文件里的23000端口

@ -0,0 +1,215 @@
本文标题: Django中redis的使用方法(包括安装、配置、启动)
本文地址: http://www.cppcns.com/shujuku/redis/220910.html
./redis-4.0.8/src/redis-cli [-h] [-p]
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> select 5
OK
127.0.0.1:6379[5]> keys *
(empty list or set)
127.0.0.1:6379[5]> set name itchat
OK
127.0.0.1:6379[5]> get name
"itchat"
127.0.0.1:6379[5]> set name itchat2
OK
127.0.0.1:6379[5]> get name
"itchat2"
127.0.0.1:6379[5]> keys *
1) "name"
127.0.0.1:6379[5]> setex aa 3 ii
OK
127.0.0.1:6379[5]> keys *
1) "name"
127.0.0.1:6379[5]> setex aa 3 ii
OK
127.0.0.1:6379[5]> get aa
"ii"
127.0.0.1:6379[5]> get aa
(nil)
127.0.0.1:6379[5]> mset a1 java a2 python a3 c
OK
127.0.0.1:6379[5]> get a1
"java"
127.0.0.1:6379[5]> get a2
"python"
127.0.0.1:6379[5]> get a3
"c"
127.0.0.1:6379[5]> append a1 haha
(integer) 8
127.0.0.1:6379[5]> get a1
"javahaha"
127.0.0.1:6379[5]> exists a
(integer) 0
127.0.0.1:6379[5]> exists a2
(integer) 1
127.0.0.1:6379[5]> type name
string
127.0.0.1:6379[5]> mget a1 a2 a3
1) "javahaha"
2) "python"
3) "c"
127.0.0.1:6379[5]> del a1 a2 a3
(integer) 3
127.0.0.1:6379[5]> keys *
1) "name"
127.0.0.1:6379[5]> set a1 python
OK
127.0.0.1:6379[5]> expire a1 3
(integer) 1
127.0.0.1:6379[5]> get a1
(nil)
127.0.0.1:6379[5]> get a1
(nil)
127.0.0.1:6379[5]> expire a1 3
(integer) 0
127.0.0.1:6379[5]> get a1
(nil)
127.0.0.1:6379[5]> hset user name itthema
(integer) 1
127.0.0.1:6379[5]> keys *
1) "name"
2) "user"
127.0.0.1:6379[5]> hmset u2 name itcast age 12
OK
127.0.0.1:6379[5]> type u2
hash
127.0.0.1:6379[5]> hkeys user
1) "name"
127.0.0.1:6379[5]> hkeys u2
1) "name"
2) "age"
127.0.0.1:6379[5]> keys name
1) "name"
127.0.0.1:6379[5]> keys *
1) "name"
2) "user"
3) "u2"
127.0.0.1:6379[5]> hget u2 name
"itcast"
127.0.0.1:6379[5]> hget u2 age
"12"
127.0.0.1:6379[5]> hvals u2
1) "itcast"
2) "12"
127.0.0.1:6379[5]> hdel u2 name
(integer) 1
127.0.0.1:6379[5]> hkeys u2
1) "age"
127.0.0.1:6379[5]> set a1
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379[5]> lpush a1 a b c
(integer) 3
127.0.0.1:6379[5]> type a1
list
127.0.0.1:6379[5]> lrange a1
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379[5]> lrange a1 0 3
1) "c"
2) "b"
3) "a"
127.0.0.1:6379[5]> rpush a1 0 1
(integer) 5
127.0.0.1:6379[5]> lrange a1 0 5
1) "c"
2) "b"
3) "a"
4) "0"
5) "1"
127.0.0.1:6379[5]> lrange a1 0 4
1) "c"
2) "b"
3) "a"
4) "0"
5) "1"
127.0.0.1:6379[5]> lrange a1 0 3
1) "c"
2) "b"
3) "a"
4) "0"
127.0.0.1:6379[5]> linsert a1 before b 3
(integer) 6
127.0.0.1:6379[5]> lrange a1 0 5
1) "c"
2) "3"
3) "b"
4) "a"
5) "0"
6) "1"
127.0.0.1:6379[5]> lrange a1 0 -1
1) "c"
2) "3"
3) "b"
4) "a"
5) "0"
6) "1"
127.0.0.1:6379[5]> lset a1 1 z
OK
127.0.0.1:6379[5]> lrange a1 0 -1
1) "c"
2) "z"
3) "b"
4) "a"
5) "0"
6) "1"
127.0.0.1:6379[5]> sadd a3 1 2 3 4
(integer) 4
127.0.0.1:6379[5]> smembers a2
(empty list or set)
127.0.0.1:6379[5]> smembers a3
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379[5]> sadd a2 zhangsan wanger lisi
(integer) 3
127.0.0.1:6379[5]> smembers a2
1) "wanger"
2) "zhangsan"
3) "lisi"
127.0.0.1:6379[5]> smembers a3
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379[5]> srem a3 3
(integer) 1
127.0.0.1:6379[5]> smembers a3
1) "1"
2) "2"
3) "4"
127.0.0.1:6379[5]> zadd a4 4 lisi 5 wangwu 6 zhaoliu 3 zhangsan
(integer) 4
127.0.0.1:6379[5]> keys *
1) "name"
2) "a2"
3) "a1"
4) "user"
5) "a4"
6) "a3"
7) "u2"
127.0.0.1:6379[5]> type a4
zset
127.0.0.1:6379[5]> zrange a4 0 -1
1) "zhangsan"
2) "lisi"
3) "wangwu"
4) "zhaoliu"
127.0.0.1:6379[5]> zscore a4 zhangsan
"3"
127.0.0.1:6379[5]> zrem z4 zhangsan
(integer) 0
127.0.0.1:6379[5]> zrem a4 zhangsan
(integer) 1
127.0.0.1:6379[5]> zrange a4 0 -1
1) "lisi"
2) "wangwu"
3) "zhaoliu"
127.0.0.1:6379[5]> zremrangebyscore a4 5 6
(integer) 2
127.0.0.1:6379[5]> zrange a4 0 -1
1) "lisi"

@ -0,0 +1,25 @@
from redis import StrictRedis
if __name__=="__main__":
try:
sr = StrictRedis()
res = sr.set('name', 'itheima')
print(res) # True
res = sr.get('name')
print(res) # b'itheima'
res = sr.set('name', 'itheima2')
print(res) # True
res = sr.delete('name')
print(res) # 1
res = sr.delete('a1', 'a2')
print(res) # 0
res = sr.keys()
print(res)
except Exception as e:
print(e)

Binary file not shown.

@ -0,0 +1,109 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>天天生鲜-购物车</title>
<link rel="stylesheet" type="text/css" href="css/reset.css">
<link rel="stylesheet" type="text/css" href="css/main.css">
</head>
<body>
<div class="header_con">
<div class="header">
<div class="welcome fl">欢迎来到天天生鲜!</div>
<div class="fr">
<div class="login_info fl">
欢迎您:<em>李谟翰</em>
</div>
<div class="login_btn fl">
<a href="login.html">登录</a>
<span>|</span>
<a href="../templates/register.html">注册</a>
</div>
<div class="user_link fl">
<span>|</span>
<a href="user_center_info.html">用户中心</a>
<span>|</span>
<a href="cart.html">我的购物车</a>
<span>|</span>
<a href="user_center_order.html">我的订单</a>
</div>
</div>
</div>
</div>
<div class="search_bar clearfix">
<a href="index.html" class="logo fl"><img src="images/logo.png"></a>
<div class="sub_page_name fl">|&nbsp;&nbsp;&nbsp;&nbsp;购物车</div>
<div class="search_con fr">
<input type="text" class="input_text fl" name="" placeholder="搜索商品">
<input type="button" class="input_btn fr" name="" value="搜索">
</div>
</div>
<div class="total_count">全部商品<em>2</em></div>
<ul class="cart_list_th clearfix">
<li class="col01">商品名称</li>
<li class="col02">商品单位</li>
<li class="col03">商品价格</li>
<li class="col04">数量</li>
<li class="col05">小计</li>
<li class="col06">操作</li>
</ul>
<ul class="cart_list_td clearfix">
<li class="col01"><input type="checkbox" name="" checked></li>
<li class="col02"><img src="images/goods/goods012.jpg"></li>
<li class="col03">奇异果<br><em>25.80元/500g</em></li>
<li class="col04">500g</li>
<li class="col05">25.80元</li>
<li class="col06">
<div class="num_add">
<a href="javascript:;" class="add fl">+</a>
<input type="text" class="num_show fl" value="1">
<a href="javascript:;" class="minus fl">-</a>
</div>
</li>
<li class="col07">25.80元</li>
<li class="col08"><a href="javascript:;">删除</a></li>
</ul>
<ul class="cart_list_td clearfix">
<li class="col01"><input type="checkbox" name="" checked></li>
<li class="col02"><img src="images/goods/goods003.jpg"></li>
<li class="col03">大兴大棚草莓<br><em>16.80元/500g</em></li>
<li class="col04">500g</li>
<li class="col05">16.80元</li>
<li class="col06">
<div class="num_add">
<a href="javascript:;" class="add fl">+</a>
<input type="text" class="num_show fl" value="1">
<a href="javascript:;" class="minus fl">-</a>
</div>
</li>
<li class="col07">16.80元</li>
<li class="col08"><a href="javascript:;">删除</a></li>
</ul>
<ul class="settlements">
<li class="col01"><input type="checkbox" name="" checked=""></li>
<li class="col02">全选</li>
<li class="col03">合计(不含运费)<span>¥</span><em>42.60</em><br>共计<b>2</b>件商品</li>
<li class="col04"><a href="place_order.html">去结算</a></li>
</ul>
<div class="footer">
<div class="foot_link">
<a href="#">关于我们</a>
<span>|</span>
<a href="#">联系我们</a>
<span>|</span>
<a href="#">招聘人才</a>
<span>|</span>
<a href="#">友情链接</a>
</div>
<p>CopyRight © 2024 北京天天生鲜信息技术有限公司 All Rights Reserved</p>
<p>电话010-****888 京ICP备*******8号</p>
</div>
</body>
</html>

@ -0,0 +1,642 @@
body{font-family:'Microsoft Yahei';font-size:12px;color:#666;}
html,body{height:100%}
/* 顶部样式 */
.header_con{
background-color:#f7f7f7;
height:29px;
border-bottom:1px solid #dddddd
}
.header{
width:1200px;
height:29px;
margin:0 auto;
}
.welcome,.login_info,.login_btn,.user_link{
line-height:29px;
}
.login_info{
display:none;
}
.login_info em{color:#ff8800}
.login_btn a,.user_link a{
color:#666;
}
.login_btn a:hover,.user_link a:hover{
color:#ff8800;
}
.login_btn span,.user_link span{
color:#cecece;
margin:0 10px;
}
/* logo、搜索框、购物车样式 */
.search_bar{width:1200px;height:115px;margin:0 auto;}
.logo{width:150px;height:59px;margin:29px 0 0 17px;}
.search_con{width:616px;height:38px;border:1px solid #37ab40;margin:34px 0 0 124px;background:url(../images/icons.png) 10px -338px no-repeat;}
.search_con .input_text{width:470px;height:34px;border:0px;margin:2px 0 0 36px;outline:none;font-size:12px;color:#737272;font-family:'Microsoft Yahei'}
.search_con .input_btn{
width:100px;height:38px;background-color:#37ab40;border:0px;font-size:14px;color:#fff;font-family:'Microsoft Yahei';outline:none;cursor:pointer;
}
.guest_cart{
width:200px;height:40px;margin-top:34px;
}
.guest_cart .cart_name{
width:158px;height:38px;line-height:38px;border:1px solid #dddddd;display:block;background:url(../images/icons.png) 13px -300px no-repeat;font-size:14px;color:#37ab40;text-indent:56px;
}
.guest_cart .goods_count{
width:40px;height:40px;text-align:center;line-height:40px;font-size:18px;
font-weight:bold;color:#fff;background-color:#ff8800;
}
/* 菜单、幻灯片样式 */
.navbar_con{height:40px;border-bottom:2px solid #39a93e}
.navbar{width:1200px;margin:0 auto;}
.navbar h1{width:200px;height:40px;line-height:40px;text-align: center;font-size:14px;color:#fff;background-color:#39a93e;}
.navbar .subnav_con{width:200px;height:40px;background-color:#39a93e;position:relative;cursor:pointer;}
.navbar .subnav_con h1{position:absolute;left:0;top:0;text-align:left;text-indent:40px}
.navbar .subnav_con span{display:block;width:16px;height:9px;background:url(../images/down.png) no-repeat;position:absolute;right:27px;top:16px;transition:all 300ms ease-in;
}
.navbar .subnav_con:hover span{transform:rotateZ(180deg)}
.navbar .subnav_con .subnav{position:absolute;left:0;top:40px;display:none;border-top:2px solid #39a93e;}
.navbar .subnav_con:hover .subnav{display:block;}
.navlist{margin-left:34px;}
.navlist li{height:40px;float:left;line-height:40px;}
.navlist li a{color:#666;font-size:14px}
.navlist li a:hover{color:#ff8800}
.navlist .interval{margin:0 15px;}
.center_con{width:1200px;height:270px;margin:0 auto;}
.subnav{width:198px;height:270px;border-left:1px solid #eee;border-right:1px solid #eee;}
.subnav li{height:44px;border-bottom:1px solid #eee;background:url(../images/icons.png) 178px -257px no-repeat #fff;}
.subnav li a{display:block;height:44px;line-height:44px;text-indent:71px;font-size:14px;color:#333}
.subnav li a:hover{color:#ff8800}
.subnav li .fruit{background:url(../images/icons.png) 28px 0px no-repeat;}
.subnav li .seafood{background:url(../images/icons.png) 28px -43px no-repeat;}
.subnav li .meet{background:url(../images/icons.png) 28px -86px no-repeat;}
.subnav li .egg{background:url(../images/icons.png) 28px -132px no-repeat;}
.subnav li .vegetables{background:url(../images/icons.png) 28px -174px no-repeat;}
.subnav li .ice{background:url(../images/icons.png) 28px -220px no-repeat;}
.slide{width:760px;height:270px;position:relative;overflow:hidden;}
.slide .slide_pics{position:relative;left:0;top:0;width:760px;height:270px;}
.slide .slide_pics li{width:760px;height:270px;position:absolute;left:0;top:0}
.slide .prev,.slide .next{width:17px;height:23px;background:url(../images/icons.png) left -388px no-repeat;position:absolute;left:11px;top:122px;cursor:pointer;}
.slide .next{background-position:left -428px;left:732px;}
.points{width:100%;height:11px;position:absolute;left:0;top:250px;text-align:center;}
.points li{display:inline-block;width:11px;height:11px;margin:0 5px;background-color:#9f9f9f;border-radius:50%;cursor:pointer;}
.points li.active{background-color:#cecece}
.adv{width:240px;height:270px; overflow:hidden; background-color:gold;}
.adv a{display:block;float:left;}
/* 商品列表样式 */
.list_model{width:1200px;height:340px;margin:15px auto 0;}
.list_title{height:40px;border-bottom:2px solid #42ad46}
.list_title h3{height:40px;line-height:40px;font-size:16px;color:#37ab40;font-weight:bold;}
.list_title .subtitle{height:20px;line-height:20px;margin-top:15px;}
.list_title .subtitle span{color:#666;margin:0 10px 0 20px;}
.list_title .subtitle a{color:#666;margin:0 5px;}
.list_title .subtitle a:hover,.goods_more:hover{color:#ff8800}
.goods_more{height:20px;margin-top:15px;color:#666}
.goods_con{height:300px;}
.goods_banner{width:200px;height:300px;}
.goods_banner img{width:200px;height:300px;}
.goods_list{width:1000px;height:299px;border-bottom:1px solid #ededed}
.goods_list li{height:299px;width:249px;border-right:1px solid #ededed;float:left}
.goods_list li:hover{width:248px;height:297px;border:1px solid gold;}
.goods_list li:hover img{opacity:0.8}
.goods_list li h4{width:200px;height:50px;margin:20px auto 0;text-align:center;}
.goods_list li h4 a{font-size:14px;color:#666;font-weight:normal;line-height:24px;}
.goods_list li h4 a:hover{color:#ff8800}
.goods_list li img{display:block;width:180px;height:180px;margin:0 auto;}
.goods_list li .prize{text-align:center;font-size:20px;color:#c40000;margin-top:5px;}
/* 页面底部样式 */
.footer{
border-top:2px solid #42ad46;
margin:30px 0;
}
.foot_link{text-align:center;margin-top:30px;}
.foot_link a,.foot_link span{color:#4e4e4e;}
.foot_link a:hover{color:#ff8800}
.foot_link span{padding:0 10px}
.footer p{text-align:center; margin-top:10px;}
/* 二级页面面包屑导航 */
.breadcrumb{
width:1200px;height:40px;margin:0 auto;
}
.breadcrumb a{line-height:40px;color:#37ab40}
.breadcrumb a:hover{color:#ff8800}
.breadcrumb span{line-height:40px;color:#666;padding:0 5px;}
.main_wrap{width:1200px;margin:0 auto;}
.l_wrap{width:200px;}
.r_wrap{width:980px;}
/* 新品推荐样式 */
.new_goods{
border:1px solid #ededed;
border-top:2px solid #37ab40;
padding-bottom:10px;
}
.new_goods h3{
height:33px;line-height:33px;background-color:#fcfcfc;border-bottom:1px solid #ededed;font-size:14px;font-weight:normal;text-indent:10px;
}
.new_goods ul{width:160px;margin:0 auto;overflow:hidden;}
.new_goods li{border-bottom:1px solid #ededed;margin-bottom:-1px;}
.new_goods li img{display:block;width:150px;height:150px;margin:10px auto;}
.new_goods li h4{width:160px;margin:0 auto;}
.new_goods li h4 a{font-weight:normal;color:#666;display:block;width:160px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;}
.new_goods li .prize{font-size:14px;color:#da260e;margin:10px auto;}
/* 商品列表样式 */
.sort_bar{height:30px;background-color:#f0fdec}
.sort_bar a{display:block;height:30px;line-height:30px;padding:0 20px;float:left;color:#000}
.sort_bar .active{background-color:#37ab40;color:#fff;}
.goods_type_list{
margin:10px auto 0;
}
.goods_type_list li{
width:196px;
float:left;
margin-bottom:10px
}
.goods_type_list li img{width:160px;height:160px;display:block;margin:10px auto;}
.goods_type_list li h4{width:160px;margin:0 auto;}
.goods_type_list li h4 a{font-weight:normal;color:#666;display:block;width:160px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;}
.operate{width:160px;margin:10px auto;position:relative;}
.goods_type_list .operate .prize{color:#da260e; font-size:14px;}
.goods_type_list .operate .unit{color:#999;padding-left:5px;}
.goods_type_list .operate .add_goods{display:inline-block;width:15px;height:15px;background:url(../images/shop_cart.png);position:absolute;right:0;top:3px;}
/* 分页样式 */
.pagenation{height:32px;text-align:center;font-size:0;margin:30px auto;}
.pagenation a{display:inline-block;border:1px solid #d2d2d2;background-color:#f8f6f7;font-size:12px;padding:7px 10px;color:#666;margin:5px}
.pagenation .active{background-color:#fff;color:#43a200}
/* 商品详情样式 */
.goods_detail_con{
width:1198px;
height:398px;
border:1px solid #ededed;
margin:0 auto 20px;
}
.goods_detail_pic{width:350px;height:350px;margin:24px 0 0 24px;}
.goods_detail_list{
width:730px;height:350px;margin:24px 24px 0 0;
}
.goods_detail_list h3{font-size:24px;line-height:24px;color:#666;font-weight:normal;}
.goods_detail_list p{color:#666;line-height:40px;}
.prize_bar{height:72px;background-color:#fff5f5;line-height:72px;}
.prize_bar .show_pirze{font-size:20px;color:#ff3e3e;padding-left:20px}
.prize_bar .show_pirze em{font-style:normal;font-size:36px;padding-left:10px}
.prize_bar .show_unit{padding-left:150px}
.goods_num{height:52px;margin-top:19px;}
.goods_num .num_name{width:70px;height:52px;line-height:52px;}
.goods_num .num_add{width:75px;height:50px;border:1px solid #dddddd}
.goods_num .num_add input{width:49px;height:50px;text-align:center;line-height:50px;border:0px;outline:none;font-size:14px;color:#666}
.goods_num .num_add .add,.goods_num .num_add .minus{width:25px;line-height:25px;text-align:center;border-left:1px solid #ddd;border-bottom:1px solid #ddd;color:#666;font-size:14px}
.goods_num .num_add .minus{border-bottom:0px}
.total{height:35px;line-height:35px;margin-top:25px;}
.total em{font-style:normal;color:#ff3e3e;font-size:18px}
.operate_btn{height:40px;margin-top:35px;font-size:0;position:relative;}
.operate_btn .buy_btn,.operate_btn .add_cart{display:inline-block;width:178px;height:38px;border:1px solid #c40000;font-size:14px;color:#c40000;line-height:38px;text-align:center;background-color:#ffeded;}
.operate_btn .add_cart{background-color:#c40000;color:#fff;margin-left:10px;position:relative;z-index:10;}
.add_jump{width:20px;height:20px;background-color:#c40000;position:absolute;left:268px;top:10px;border-radius:50%;z-index:9;display:none;}
.detail_tab{
height:35px;
border-bottom:1px solid #37ab40
}
.detail_tab li{height:34px;line-height:34px;padding:0 30px;font-size:14px;color:#333333;float:left;border:1px solid #e8e8e8;border-bottom:0px;cursor:pointer;background-color:#faf8f8}
.detail_tab li.active{border-top:2px solid #37ab40;position:relative;background-color:#fff;border-left:1px solid #37ab40;border-right:1px solid #37ab40;top:-1px;height:35px;}
.tab_content dt{margin-top:10px;font-size:16px;color:#044d39}
.tab_content dd{line-height:24px;margin-top:5px;}
/* 登录页 */
.login_top{width:960px;height:130px;margin:0 auto;}
.login_logo{display:block;width:193px;height:76px;margin-top:30px;}
.login_form_bg{height:480px;background-color:#518e17}
.no-mp{margin-top:0px;}
.login_form_wrap{width:960px;height:480px;margin:0 auto;}
.login_banner{width:381px;height:322px;background:url(../images/login_banner.png) no-repeat;margin-top:90px;}
.slogan{width:40px;height:300px;font-size:30px;color:#f0f9e8;text-align:center;line-height:36px;margin:80px 0 0 120px}
.login_form{width:368px;height:378px;border:1px solid #c6c6c5;background-color:#fff; margin-top:50px;}
.login_title{height:60px;width:308px;margin:10px auto;border-bottom:1px solid #e0e0e0;}
.login_title h1{font-size:24px;height:60px;line-height:60px;color:#a8a8a8;float:left;font-weight:bold;margin-left:44px;}
.login_title a{width:100px;height:20px;display:block;font-size:16px;color:#5fb42a;text-indent:26px;background:url(../images/icons02.png) left 5px no-repeat;float:left;margin:20px 0 0 36px}
.form_input{width:308px;height:250px;margin:20px auto;position:relative;}
.name_input,.pass_input{width:306px;height:36px;border:1px solid #e0e0e0;background:url(../images/icons02.png) 280px -41px no-repeat #f8f8f8;outline:none;font-size:14px;text-indent:10px;position: absolute;left:0;top:0}
.pass_input{top:65px;background-position:280px -95px;}
.user_error,.pwd_error{color:#f00;position:absolute;left:0;top:43px;display:none}
.pwd_error{top:110px;}
.more_input{position:absolute;left:0;top:130px;width:100%}
.more_input input{float:left;margin-top:2px;}
.more_input label{float:left;margin-left:10px;}
.more_input a{float:right;color:#666}
.more_input a:hover{color:#ff8800}
.input_submit{width:100%;height:40px;position:absolute;left:0;top:180px;background-color:#47aa34;color:#fff;font-size:22px;border:0px;font-family:'Microsoft Yahei';cursor:pointer;}
/* 注册页面 */
.register_con{
width:700px;
height:560px;
margin:50px auto 0;
background:url(../images/interval_line.png) 300px top no-repeat;
}
.l_con{width:300px;}
.reg_logo{width:200px;height:76px;float:right;margin-right:30px;}
.reg_slogan{width:300px;height:30px;float:right;text-align:right;font-size:24px;color:#69a81e;margin:20px 30px 0 0;}
.reg_banner{width:251px;height:329px;background:url(../images/register_banner.png) no-repeat;float:right; margin:20px 10px 0 0;opacity:0.5}
.r_con{width:400px;}
.reg_title{width:360px;height:50px;float:left;margin-left:30px;border-bottom:1px solid #e0e0e0}
.reg_title h1{height:50px;line-height:50px;float:left;font-size:24px;color:#a8a8a8;font-weight:bold;}
.reg_title a{float:right;height:20px;line-height:20px;font-size:16px;color:#5fb42a;padding-right:20px;background:url(../images/icons02.png) 35px 3px no-repeat;margin-top:15px}
.reg_form{width:360px;margin:30px 0 0 30px;float:left;position:relative;}
.reg_form li{height:70px;}
.reg_form li label{width:70px;height:40px;line-height:40px;float:left;font-size:14px;color:#a8a8a8}
.reg_form li input{width:288px;height:38px;border:1px solid #e0e0e0;float:left;outline:none;text-indent:10px;background-color:#f8f8f8}
.reg_form li.agreement input{width:15px;height:15px;float:left;margin-top:13px}
.reg_form li.agreement label{width:300px;float:left;margin-left:10px;}
.reg_form li.reg_sub input{width:360px;height:40px;background-color:#47aa34;font-size:18px;color:#fff;font-family:'Microsoft Yahei';cursor:pointer;}
.reg_form li .error_tip{float:left;height:30px;line-height:30px;margin-left:70px;color:#e62e2e;display:none;}
.reg_form li .error_tip2{float:left;height:20px;line-height:20px;color:#e62e2e;display:none;}
.sub_page_name{font-size:18px;color:#666;margin:50px 0 0 20px}
.total_count{
width:1200px;margin:0 auto;height:40px;line-height:40px;font-size:14px;
}
.total_count em{
font-size:16px;color:#ff4200;margin:0 5px;
}
.cart_list_th{width:1198px;border:1px solid #ddd;background-color:#f7f7f7;margin:0 auto;}
.cart_list_th li{height:40px;line-height:40px;float:left;text-align:center;}
.cart_list_th .col01{width:26%;}
.cart_list_th .col02{width:16%;}
.cart_list_th .col03{width:13%;}
.cart_list_th .col04{width:12%;}
.cart_list_th .col05{width:15%;}
.cart_list_th .col06{width:18%;}
.cart_list_td{width:1198px;border:1px solid #ddd;background-color:#edfff9;margin:0 auto;margin-top:-1px;}
.cart_list_td li{height:120px;line-height:120px;float:left;text-align:center;}
.cart_list_td .col01{width:4%;}
.cart_list_td .col02{width:12%;}
.cart_list_td .col03{width:10%;}
.cart_list_td .col04{width:16%;}
.cart_list_td .col05{width:13%;}
.cart_list_td .col06{width:12%;}
.cart_list_td .col07{width:15%;}
.cart_list_td .col08{width:18%;}
.cart_list_td .col02 img{width:100px;height:100px;border:1px solid #ddd;display:block;margin:10px auto 0;}
.cart_list_td .col03{height:48px;text-align:left;line-height:24px;margin-top:38px;}
.cart_list_td .col03 em{color:#999}
.cart_list_td .col08 a{color:#666}
.cart_list_td .col06 .num_add{width:98px;height:28px;border:1px solid #ddd;margin:40px auto 0;}
.cart_list_td .col06 .num_add a{width:29px;height:28px;line-height:28px;background-color:#f3f3f3;font-size:14px;color:#666}
.cart_list_td .col06 .num_add input{width:38px;height:28px;text-align:center;line-height:30px;border:0px;display:block;float:left;outline:none;border-left:1px solid #ddd;border-right:1px solid #ddd;}
.settlements{width:1198px;height:78px;border:1px solid #ddd;background-color:#fff4e8;margin:-1px auto 0;}
.settlements li{line-height:78px;float:left;}
.settlements .col01{width:4%;text-align:center}
.settlements .col02{width:12%;}
.settlements .col03{width:69%; height:48px; line-height:28px;text-align:right;margin-top:10px;}
.settlements .col03 span{color:#ff0000;padding-right:5px}
.settlements .col03 em{color:#ff3d3d;font-size:22px;font-weight:bold;}
.settlements .col03 span{color:#ff0000;}
.settlements .col03 b{color:#ff0000;font-size:14px;padding:0 5px;}
.settlements .col04{width:14%;text-align:center;float:right;}
.settlements .col04 a{display:block;height:78px;background-color:#ff3d3d;text-align:center;line-height:78px;color:#fff;font-size:24px}
.common_title{width:1200px;margin:20px auto 0;font-size:14px;}
.common_list_con{width:1200px;border:1px solid #dddddd;border-top:2px solid #00bc6f;margin:10px auto 0;background-color:#f7f7f7;position:relative;}
.common_list_con dl{margin:20px;}
.common_list_con dt{font-size:14px;font-weight:bold;margin-bottom:10px}
.common_list_con dd input{vertical-align:bottom;margin-right:10px}
.edit_site{position:absolute; right:20px;top:30px;width:100px;height:30px;background-color:#37ab40;text-align:center;line-height:30px;color:#fff}
.pay_style_con{margin:20px;}
.pay_style_con input{float:left;margin:14px 7px 0 0;}
.pay_style_con label{float:left;border:1px solid #ccc;background-color:#fff;padding:10px 10px 10px 40px;margin-right:25px}
.pay_style_con .cash{background:url(../images/pay_icons.png) 8px top no-repeat #fff;}
.pay_style_con .weixin{background:url(../images/pay_icons.png) 6px -36px no-repeat #fff;}
.pay_style_con .zhifubao{background:url(../images/pay_icons.png) 12px -72px no-repeat #fff;width:50px;height:16px}
.pay_style_con .bank{background:url(../images/pay_icons.png) 6px -108px no-repeat #fff;}
.goods_list_th{height:40px;border-bottom:1px solid #ccc}
.goods_list_th li{float:left;line-height:40px;text-align:center;}
.goods_list_th .col01{width:25%}
.goods_list_th .col02{width:20%}
.goods_list_th .col03{width:25%}
.goods_list_th .col04{width:15%}
.goods_list_th .col05{width:15%}
.goods_list_td{height:80px;border-bottom:1px solid #eeeded}
.goods_list_td li{float:left;line-height:80px;text-align:center;}
.goods_list_td .col01{width:4%}
.goods_list_td .col02{width:6%}
.goods_list_td .col03{width:15%}
.goods_list_td .col04{width:20%}
.goods_list_td .col05{width:25%}
.goods_list_td .col06{width:15%}
.goods_list_td .col07{width:15%}
.goods_list_td .col02{text-align:right}
.goods_list_td .col02 img{width:63px;height:63px;border:1px solid #ddd;display:block;margin:7px 0;float:right;}
.goods_list_td .col03{text-align:left;text-indent:20px}
.settle_con{margin:10px}
.total_goods_count,.transit,.total_pay{line-height:24px;text-align:right}
.total_goods_count em,.total_goods_count b,.transit b,.total_pay b{font-size:14px;color:#ff4200;padding:0 5px;}
.order_submit{width:1200px;margin:20px auto;}
.order_submit a{width:160px;height:40px;line-height:40px;text-align:center;background-color:#47aa34;color:#fff;font-size:16px;display:block;float:right}
.order_list_th{width:1198px;border:1px solid #ddd;background-color:#f7f7f7;margin:20px auto 0;}
.order_list_th li{float:left;height:30px;line-height:30px}
.order_list_th .col01{width:20%;margin-left:20px}
.order_list_th .col02{width:20%}
.order_list_table{
width:1200px;
border-collapse:collapse;
border-spacing:0px;
border:1px solid #ddd;
margin:-1px auto 0;
}
.order_list_table td{
border:1px solid #ddd;
text-align:center;
}
.order_goods_list{border-bottom:1px solid #ddd;margin-bottom:-2px;}
.order_goods_list li{float:left; height:80px;line-height:80px;}
.order_goods_list .col01{width:20%}
.order_goods_list .col01 img{width:60px;height:60px;border:1px solid #ddd;margin:10px auto;}
.order_goods_list .col02{width:50%;text-align:left;}
.order_goods_list .col02 em{color:#999;margin-left:10px}
.order_goods_list .col03{width:10%}
.order_goods_list .col04{width:20%}
.oper_btn{display:inline-block;border:1px solid #ddd;color:#666;padding:5px 10px}
.popup_con{display:none;}
.popup{width:300px;height:150px;border:1px solid #dddddd;border-top:2px solid #00bc6f;background-color:#f7f7f7;position:fixed;
left:50%;
margin-left:-150px;
top:50%;
margin-top:-75px;
z-index:1000;
}
.popup p{height:150px;line-height:150px;text-align:center;font-size:18px;}
.mask{width:100%;height:100%;position:fixed;left:0;top:0;background-color:#000;opacity:0.3;z-index:999;}
.main_con{
width:1200px;
margin:0 auto;
background:url(../images/left_bg.jpg) repeat-y;
}
.left_menu_con{
width:200px;
float:left;
}
.left_menu_con h3{
font-size:16px;
line-height:40px;
border-bottom:1px solid #ddd;
text-align:center;
margin-bottom:10px;
}
.left_menu_con ul li{
line-height:40px;
text-align:center;
font-size:14px;
}
.left_menu_con ul li a{
color:#666;
}
.left_menu_con ul li .active{
color:#ff8800;
font-weight:bold;
}
.right_content{
width:980px;
float:right;
min-height:500px;
}
.w980{
width:980px;
}
.w978{
width:978px;
}
.common_title2{height:20px;line-height:20px;font-size:16px;margin:10px 0;}
.user_info_list{
background-color:#f9f9f9;
margin:10px 0 15px;
padding:10px 0;
height:90px;
}
.user_info_list li{
line-height:30px;
text-indent:30px;
font-size:14px;
}
.user_info_list li span{
width:100px;
float:left;
text-align:right;
}
.info_con{
width:980px;
}
.info_l{
width:600px;
float:left;
}
.info_r{
width:360px;
float:right;
}
.site_con{
background-color:#f9f9f9;
padding:10px 0;
margin-bottom:20px;
}
.site_con dt{
font-size:14px;
line-height:30px;
text-indent:30px;
font-weight:bold;
}
.site_con dd{
font-size:14px;
line-height:30px;
text-indent:30px;
}
.site_con .form_group{
height:40px;
line-height:40px;
margin-top:10px;
}
.site_con .form_group label{
width:100px;
float:left;
text-align:right;
font-size:14px;
height:40px;
line-height:40px;
}
.site_con .form_group input{
width:300px;
height:25px;
border:1px solid #ddd;
float:left;
outline:none;
margin-top:7px;
text-indent:10px;
}
.site_con .form_group2{
height:90px;
}
.site_area{
width:280px;
height:60px;
border:1px solid #ddd;
outline:none;
padding:10px;
}
.info_submit{
width:80px;
height:30px;
background-color:#37ab40;
border:0px;
color:#fff;
margin:10px 0 10px 100px;
cursor:pointer;
font-family:'Microsoft Yahei'
}
.stress{
color:#ff8800;
}

@ -0,0 +1,27 @@
/* 把标签默认的间距设为0 */
body,ul,ol,p,h1,h2,h3,h4,h5,h6,dl,dd,select,input,textarea,form{margin:0;padding:0}
/* 让h标签文字大小继承body的文字设置 */
h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}
/* 去掉列表默认的图标 */
ul,ol{list-style:none;}
/* 去掉em默认的斜体 */
em{font-style: normal;}
/* 去掉a标签默认的下划线 */
a{text-decoration:none;}
/* 去掉加链接时产生的框线 */
img{border:0;}
/* 清除浮动 */
.clearfix:before,.clearfix:after{content:"";display:table}
.clearfix:after{clear:both;}
.clearfix{zoom:1}
/* 浮动 */
.fl{float:left}
.fr{float:right}

@ -0,0 +1,178 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>天天生鲜-商品详情</title>
<link rel="stylesheet" type="text/css" href="css/reset.css">
<link rel="stylesheet" type="text/css" href="css/main.css">
</head>
<body>
<div class="header_con">
<div class="header">
<div class="welcome fl">欢迎来到天天生鲜!</div>
<div class="fr">
<div class="login_info fl">
欢迎您:<em>李谟翰</em>
</div>
<div class="login_btn fl">
<a href="login.html">登录</a>
<span>|</span>
<a href="../templates/register.html">注册</a>
</div>
<div class="user_link fl">
<span>|</span>
<a href="user_center_info.html">用户中心</a>
<span>|</span>
<a href="cart.html">我的购物车</a>
<span>|</span>
<a href="user_center_order.html">我的订单</a>
</div>
</div>
</div>
</div>
<div class="search_bar clearfix">
<a href="index.html" class="logo fl"><img src="images/logo.png"></a>
<div class="search_con fl">
<input type="text" class="input_text fl" name="" placeholder="搜索商品">
<input type="button" class="input_btn fr" name="" value="搜索">
</div>
<div class="guest_cart fr">
<a href="cart.html" class="cart_name fl">我的购物车</a>
<div class="goods_count fl" id="show_count">1</div>
</div>
</div>
<div class="navbar_con">
<div class="navbar clearfix">
<div class="subnav_con fl">
<h1>全部商品分类</h1>
<span></span>
<ul class="subnav">
<li><a href="#" class="fruit">新鲜水果</a></li>
<li><a href="#" class="seafood">海鲜水产</a></li>
<li><a href="#" class="meet">猪牛羊肉</a></li>
<li><a href="#" class="egg">禽类蛋品</a></li>
<li><a href="#" class="vegetables">新鲜蔬菜</a></li>
<li><a href="#" class="ice">速冻食品</a></li>
</ul>
</div>
<ul class="navlist fl">
<li><a href="">首页</a></li>
<li class="interval">|</li>
<li><a href="">手机生鲜</a></li>
<li class="interval">|</li>
<li><a href="">抽奖</a></li>
</ul>
</div>
</div>
<div class="breadcrumb">
<a href="#">全部分类</a>
<span>></span>
<a href="#">新鲜水果</a>
<span>></span>
<a href="#">商品详情</a>
</div>
<div class="goods_detail_con clearfix">
<div class="goods_detail_pic fl"><img src="images/goods_detail.jpg"></div>
<div class="goods_detail_list fr">
<h3>大兴大棚草莓</h3>
<p>草莓浆果柔软多汁,味美爽口,适合速冻保鲜贮藏。草莓速冻后,可以保持原有的色、香、味,既便于贮藏,又便于外销。</p>
<div class="prize_bar">
<span class="show_pirze">¥<em>16.80</em></span>
<span class="show_unit">单 位500g</span>
</div>
<div class="goods_num clearfix">
<div class="num_name fl">数 量:</div>
<div class="num_add fl">
<input type="text" class="num_show fl" value="1">
<a href="javascript:;" class="add fr">+</a>
<a href="javascript:;" class="minus fr">-</a>
</div>
</div>
<div class="total">总价:<em>16.80元</em></div>
<div class="operate_btn">
<a href="javascript:;" class="buy_btn">立即购买</a>
<a href="javascript:;" class="add_cart" id="add_cart">加入购物车</a>
</div>
</div>
</div>
<div class="main_wrap clearfix">
<div class="l_wrap fl clearfix">
<div class="new_goods">
<h3>新品推荐</h3>
<ul>
<li>
<a href="#"><img src="images/goods/goods001.jpg"></a>
<h4><a href="#">进口柠檬</a></h4>
<div class="prize">¥3.90</div>
</li>
<li>
<a href="#"><img src="images/goods/goods002.jpg"></a>
<h4><a href="#">玫瑰香葡萄</a></h4>
<div class="prize">¥16.80</div>
</li>
</ul>
</div>
</div>
<div class="r_wrap fr clearfix">
<ul class="detail_tab clearfix">
<li class="active">商品介绍</li>
<li>评论</li>
</ul>
<div class="tab_content">
<dl>
<dt>商品详情:</dt>
<dd>草莓采摘园位于北京大兴区 庞各庄镇四各庄村 每年1月-6月面向北京以及周围城市提供新鲜草莓采摘和精品礼盒装草莓草莓品种多样丰富个大香甜。所有草莓均严格按照有机标准培育不使用任何化肥和农药。草莓在采摘期间免洗可以直接食用。欢迎喜欢草莓的市民前来采摘也欢迎各大单位选购精品有机草莓礼盒有机草莓礼盒是亲朋馈赠、福利送礼的最佳选择。 </dd>
</dl>
</div>
</div>
</div>
<div class="footer">
<div class="foot_link">
<a href="#">关于我们</a>
<span>|</span>
<a href="#">联系我们</a>
<span>|</span>
<a href="#">招聘人才</a>
<span>|</span>
<a href="#">友情链接</a>
</div>
<p>CopyRight © 2024 北京天天生鲜信息技术有限公司 All Rights Reserved</p>
<p>电话010-****888 京ICP备*******8号</p>
</div>
<div class="add_jump"></div>
<script type="text/javascript" src="js/jquery-1.12.2.js"></script>
<script type="text/javascript">
var $add_x = $('#add_cart').offset().top;
var $add_y = $('#add_cart').offset().left;
var $to_x = $('#show_count').offset().top;
var $to_y = $('#show_count').offset().left;
$(".add_jump").css({'left':$add_y+80,'top':$add_x+10,'display':'block'})
$('#add_cart').click(function(){
$(".add_jump").stop().animate({
'left': $to_y+7,
'top': $to_x+7},
"fast", function() {
$(".add_jump").fadeOut('fast',function(){
$('#show_count').html(2);
});
});
})
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save